Econometrics and Free Software by Bruno Rodrigues.
RSS feed for blog post updates.
Follow me on Mastodon, twitter, or check out my Github.
Check out my package that adds logging to R functions, {chronicler}.
Or read my free ebooks, to learn some R and build reproducible analytical pipelines..
You can also watch my youtube channel or find the slides to the talks I've given here.
Buy me a coffee, my kids don't let me sleep.

Analyzing NetHack data, part 1: What kills the players

R

Abstract

In this post, I will analyse the data I scraped and put into an R package, which I called {nethack}. NetHack is a roguelike game; for more context, read my previous blog post. You can install the {nethack} package and play around with the data yourself by installing it from github:

devtools::install_github("b-rodrigues/nethack")

And to use it:

library(nethack)
data("nethack")

The data contains information on games played from 2001 to 2018; 322485 rows and 14 columns. I will analyze the data in a future blog post. This post focuses on getting and then sharing the data. By the way, all the content from the public server I scrape is under the CC BY 4.0 license.

I built the package by using the very useful {devtools} package.

Introduction

What I want from this first analysis are several, simple things: how many players manage to ascend (meaning, winning), what monster kills most players, and finally extract data from the dumplog column. The dumplog column is a bit special; each element of the dumplog column is a log file that contains a lot of information from the last turns of a player. I will leave this for a future blog post, though.

Let’s load some packages first:

library(nethack)
library(tidyverse)
library(lubridate)
library(magrittr)
library(brotools)

{brotools} is my own package that contains some functions that I use daily. If you want to install it, run the following line:

devtools::install_github("b-rodrigues/brotools")

The documentation is not up-to-date, I think I’ll do that and release it on CRAN. Some day.

Now, let’s load the “nethack” data, included in the {nethack} package:

##   rank score     name time turns lev_max hp_max role race gender alignment
## 1    1   360      jkm <NA>    NA     2/2  -2/25  Sam  Hum    Mal       Law
## 2    2   172 yosemite <NA>    NA     1/1  -1/10  Tou  Hum    Fem       Neu
## 3    3  2092    dtype <NA>    NA     6/7  -2/47  Val  Hum    Fem       Neu
## 4    4    32   joorko <NA>    NA     1/1   0/15  Sam  Hum    Mal       Law
## 5    5   118    jorko <NA>    NA     1/1   0/11  Rog  Orc    Fem       Cha
## 6    6  1757   aaronl <NA>    NA     5/5   0/60  Bar  Hum    Mal       Neu
##                                                      death       date
## 1                                   killed by a brown mold 2001-10-24
## 2                                       killed by a jackal 2001-10-24
## 3                                     killed by a fire ant 2001-10-24
## 4                                       killed by a jackal 2001-10-24
## 5                                       killed by a jackal 2001-10-24
## 6 killed by a hallucinogen-distorted ghoul, while helpless 2001-10-24
##   dumplog
## 1      NA
## 2      NA
## 3      NA
## 4      NA
## 5      NA
## 6      NA
data("nethack")

head(nethack)

Let’s create some variables that might be helpful (or perhaps not, we’ll see):

nethack %<>% 
  mutate(date = ymd(date),
         year = year(date),
         month = month(date),
         day = day(date))

This makes it easy to look at the data from, say, June 2017:

nethack %>%
  filter(year == 2017, month == 6) %>%
  brotools::describe()
## # A tibble: 17 x 17
##    variable type   nobs    mean      sd mode    min     max   q05   q25
##    <chr>    <chr> <int>   <dbl>   <dbl> <chr> <dbl>   <dbl> <dbl> <dbl>
##  1 day      Nume…  1451    17.4  9.00e0 1         1      30     2   10 
##  2 month    Nume…  1451     6    0.     6         6       6     6    6 
##  3 rank     Nume…  1451    47.1  2.95e1 1         1     100     4   20 
##  4 score    Nume…  1451 38156.   3.39e5 488       0 5966425    94  402.
##  5 turns    Nume…  1451  4179.   1.23e4 812       1  291829   204  860.
##  6 year     Nume…  1451  2017    0.     2017   2017    2017  2017 2017 
##  7 alignme… Char…  1451    NA   NA      Law      NA      NA    NA   NA 
##  8 death    Char…  1451    NA   NA      kill…    NA      NA    NA   NA 
##  9 gender   Char…  1451    NA   NA      Mal      NA      NA    NA   NA 
## 10 hp_max   Char…  1451    NA   NA      -1/16    NA      NA    NA   NA 
## 11 lev_max  Char…  1451    NA   NA      4/4      NA      NA    NA   NA 
## 12 name     Char…  1451    NA   NA      ohno…    NA      NA    NA   NA 
## 13 race     Char…  1451    NA   NA      Hum      NA      NA    NA   NA 
## 14 role     Char…  1451    NA   NA      Kni      NA      NA    NA   NA 
## 15 time     Char…  1451    NA   NA      01:1…    NA      NA    NA   NA 
## 16 dumplog  List   1451    NA   NA      <NA>     NA      NA    NA   NA 
## 17 date     Date   1451    NA   NA      <NA>     NA      NA    NA   NA 
## # … with 7 more variables: median <dbl>, q75 <dbl>, q95 <dbl>,
## #   n_missing <int>, n_unique <int>, starting_date <date>,
## #   ending_date <date>

Let’s also take a look at a dumplog:

Click to expand; the dumplog is quite long

nethack %>%
    filter(year == 2018, month == 10) %>%
    slice(1) %>%
    pull(dumplog)
## [[1]]
##   [1] "Unix NetHack Version 3.6.1 - last build Fri Apr 27 19:25:48 2018. (d4ebae12f1a709d1833cf466dd0c553fb97518d2)"
##   [2] ""                                                                                                            
##   [3] "Game began 2018-09-30 22:27:18, ended 2018-10-01 00:01:12."                                                  
##   [4] ""                                                                                                            
##   [5] "brothertrebius, neutral female gnomish Ranger"                                                               
##   [6] ""                                                                                                            
##   [7] "                  -----"                                                                                     
##   [8] "   --------       |....#     -----      --------"                                                            
##   [9] "   |/..%.=|      #...^|######|...|    ##.......|"                                                            
##  [10] "   |/[%..%|      #|...|     #|...|    # |......|"                                                            
##  [11] "   |......|      #-----     #-...-######....<..|"                                                            
##  [12] "   -----|--      ###         -|-.-  #   |......|"                                                            
##  [13] "        ##         ##           #   #   ----f---"                                                            
##  [14] "         ####       #           #  ##       f@Y"                                                             
##  [15] "            #       #           #  #"                                                                        
##  [16] "       -----.-------#           #  #"                                                                        
##  [17] "       |........%..|#           #  #"                                                                        
##  [18] "       |............#           #  #"                                                                        
##  [19] "       |...........|          0##  #"                                                                        
##  [20] "       |...........|         -.--- #"                                                                        
##  [21] "       -------------         |^..|##"                                                                        
##  [22] "                             |...|#"                                                                         
##  [23] "                             |0>..#"                                                                         
##  [24] "                             -----"                                                                          
##  [25] ""                                                                                                            
##  [26] "Brothertre the Trailblazer   St:15 Dx:12 Co:16 In:13 Wi:15 Ch:6  Neutral"                                    
##  [27] "Dlvl:6  $:59 HP:0(54) Pw:40(40) AC:0  Exp:8 T:7398  Satiated Burdened"                                       
##  [28] ""                                                                                                            
##  [29] "Latest messages:"                                                                                            
##  [30] " In what direction? l"                                                                                       
##  [31] " You shoot 2 arrows."                                                                                        
##  [32] " The 1st arrow hits the ape."                                                                                
##  [33] " The 2nd arrow hits the ape!"                                                                                
##  [34] " The ape hits!"                                                                                              
##  [35] " The ape hits!"                                                                                              
##  [36] " The ape bites!"                                                                                             
##  [37] " You ready: q - 9 uncursed arrows."                                                                          
##  [38] " In what direction? l"                                                                                       
##  [39] " The arrow hits the ape."                                                                                    
##  [40] " The ape hits!"                                                                                              
##  [41] " The ape hits!"                                                                                              
##  [42] " The ape bites!"                                                                                             
##  [43] " The ape hits!"                                                                                              
##  [44] " The ape hits!"                                                                                              
##  [45] " The ape misses!"                                                                                            
##  [46] " In what direction? l"                                                                                       
##  [47] " You shoot 2 arrows."                                                                                        
##  [48] " The 1st arrow hits the ape!"                                                                                
##  [49] " The 2nd arrow hits the ape."                                                                                
##  [50] " The ape misses!"                                                                                            
##  [51] " The ape hits!"                                                                                              
##  [52] " The ape misses!"                                                                                            
##  [53] " In what direction? l"                                                                                       
##  [54] " You shoot 2 arrows."                                                                                        
##  [55] " The 1st arrow misses the ape."                                                                              
##  [56] " The 2nd arrow hits the ape."                                                                                
##  [57] " The ape misses!"                                                                                            
##  [58] " The ape hits!"                                                                                              
##  [59] " The ape bites!"                                                                                             
##  [60] " In what direction? l"                                                                                       
##  [61] " The arrow hits the ape!"                                                                                    
##  [62] " The ape hits!"                                                                                              
##  [63] " The ape misses!"                                                                                            
##  [64] " The ape bites!"                                                                                             
##  [65] " You hear someone cursing shoplifters."                                                                      
##  [66] " The ape misses!"                                                                                            
##  [67] " The ape hits!"                                                                                              
##  [68] " The ape bites!"                                                                                             
##  [69] " What do you want to write with? [- amnqsvBJM-OWZ or ?*] -"                                                  
##  [70] " You write in the dust with your fingertip."                                                                 
##  [71] " What do you want to write in the dust here? Elbereth"                                                       
##  [72] " The ape hits!"                                                                                              
##  [73] " The ape hits!"                                                                                              
##  [74] " You die..."                                                                                                 
##  [75] " Do you want your possessions identified? [ynq] (y) y"                                                       
##  [76] " Do you want to see your attributes? [ynq] (y) n"                                                            
##  [77] " Do you want an account of creatures vanquished? [ynaq] (y) n"                                               
##  [78] " Do you want to see your conduct? [ynq] (y) n"                                                               
##  [79] " Do you want to see the dungeon overview? [ynq] (y) q"                                                       
##  [80] ""                                                                                                            
##  [81] "Inventory:"                                                                                                  
##  [82] " Coins"                                                                                                      
##  [83] "  $ - 59 gold pieces"                                                                                        
##  [84] " Weapons"                                                                                                    
##  [85] "  m - 17 blessed +1 arrows"                                                                                  
##  [86] "  n - a blessed +0 arrow"                                                                                    
##  [87] "  q - 3 +0 arrows (in quiver)"                                                                               
##  [88] "  s - a +0 bow (weapon in hand)"                                                                             
##  [89] "  B - 11 +1 darts"                                                                                           
##  [90] "  N - 11 +0 darts"                                                                                           
##  [91] "  a - a +1 dagger (alternate weapon; not wielded)"                                                           
##  [92] " Armor"                                                                                                      
##  [93] "  T - an uncursed +0 dwarvish iron helm (being worn)"                                                        
##  [94] "  z - an uncursed +0 pair of leather gloves (being worn)"                                                    
##  [95] "  U - a cursed -4 pair of iron shoes (being worn)"                                                           
##  [96] "  e - an uncursed +2 cloak of displacement (being worn)"                                                     
##  [97] "  h - a blessed +0 dwarvish mithril-coat (being worn)"                                                       
##  [98] " Comestibles"                                                                                                
##  [99] "  f - 3 uncursed cram rations"                                                                               
## [100] "  j - 2 uncursed food rations"                                                                               
## [101] "  L - an uncursed food ration"                                                                               
## [102] "  P - an uncursed lembas wafer"                                                                              
## [103] "  I - an uncursed lizard corpse"                                                                             
## [104] "  o - an uncursed tin of spinach"                                                                            
## [105] " Scrolls"                                                                                                    
## [106] "  G - 2 uncursed scrolls of blank paper"                                                                     
## [107] "  t - an uncursed scroll of confuse monster"                                                                 
## [108] "  V - an uncursed scroll of identify"                                                                        
## [109] " Potions"                                                                                                    
## [110] "  x - an uncursed potion of gain ability"                                                                    
## [111] "  H - a blessed potion of sleeping"                                                                          
## [112] "  g - 3 uncursed potions of water"                                                                           
## [113] " Rings"                                                                                                      
## [114] "  O - an uncursed ring of slow digestion (on left hand)"                                                     
## [115] "  v - an uncursed ring of stealth (on right hand)"                                                           
## [116] " Tools"                                                                                                      
## [117] "  p - an uncursed magic lamp"                                                                                
## [118] "  k - an uncursed magic whistle"                                                                             
## [119] "  Q - an uncursed mirror"                                                                                    
## [120] "  C - an uncursed saddle"                                                                                    
## [121] "  D - an uncursed stethoscope"                                                                               
## [122] "  y - a +0 unicorn horn"                                                                                     
## [123] "  i - 7 uncursed wax candles"                                                                                
## [124] " Gems/Stones"                                                                                                
## [125] "  W - an uncursed flint stone"                                                                               
## [126] "  M - an uncursed worthless piece of red glass"                                                              
## [127] "  Z - an uncursed worthless piece of violet glass"                                                           
## [128] "  J - an uncursed worthless piece of white glass"                                                            
## [129] ""                                                                                                            
## [130] "Brothertrebius the Ranger's attributes:"                                                                     
## [131] ""                                                                                                            
## [132] "Background:"                                                                                                 
## [133] " You were a Trailblazer, a level 8 female gnomish Ranger."                                                   
## [134] " You were neutral, on a mission for Venus"                                                                   
## [135] " who was opposed by Mercury (lawful) and Mars (chaotic)."                                                    
## [136] ""                                                                                                            
## [137] "Final Characteristics:"                                                                                      
## [138] " You had 0 hit points (max:54)."                                                                             
## [139] " You had 40 magic power (max:40)."                                                                           
## [140] " Your armor class was 0."                                                                                    
## [141] " You had 1552 experience points."                                                                            
## [142] " You entered the dungeon 7398 turns ago."                                                                    
## [143] " Your strength was 15 (limit:18/50)."                                                                        
## [144] " Your dexterity was 12 (limit:18)."                                                                          
## [145] " Your constitution was 16 (limit:18)."                                                                       
## [146] " Your intelligence was 13 (limit:19)."                                                                       
## [147] " Your wisdom was 15 (limit:18)."                                                                             
## [148] " Your charisma was 6 (limit:18)."                                                                            
## [149] ""                                                                                                            
## [150] "Final Status:"                                                                                               
## [151] " You were satiated."                                                                                         
## [152] " You were burdened; movement was slightly slowed."                                                           
## [153] " You were wielding a bow."                                                                                   
## [154] ""                                                                                                            
## [155] "Final Attributes:"                                                                                           
## [156] " You were piously aligned."                                                                                  
## [157] " You were telepathic."                                                                                       
## [158] " You had automatic searching."                                                                               
## [159] " You had infravision."                                                                                       
## [160] " You were displaced."                                                                                        
## [161] " You were stealthy."                                                                                         
## [162] " You had slower digestion."                                                                                  
## [163] " You were guarded."                                                                                          
## [164] " You are dead."                                                                                              
## [165] ""                                                                                                            
## [166] "Vanquished creatures:"                                                                                       
## [167] "  a warhorse"                                                                                                
## [168] "  a tengu"                                                                                                   
## [169] "  a quivering blob"                                                                                          
## [170] " an iron piercer"                                                                                            
## [171] "  2 black lights"                                                                                            
## [172] "  a gold golem"                                                                                              
## [173] "  a werewolf"                                                                                                
## [174] "  3 lizards"                                                                                                 
## [175] "  2 dingoes"                                                                                                 
## [176] "  a housecat"                                                                                                
## [177] "  a white unicorn"                                                                                           
## [178] "  2 dust vortices"                                                                                           
## [179] "  a plains centaur"                                                                                          
## [180] " an ape"                                                                                                     
## [181] "  a Woodland-elf"                                                                                            
## [182] "  2 soldier ants"                                                                                            
## [183] "  a bugbear"                                                                                                 
## [184] " an imp"                                                                                                     
## [185] "  a wood nymph"                                                                                              
## [186] "  a water nymph"                                                                                             
## [187] "  a rock piercer"                                                                                            
## [188] "  a pony"                                                                                                    
## [189] "  3 fog clouds"                                                                                              
## [190] "  a yellow light"                                                                                            
## [191] "  a violet fungus"                                                                                           
## [192] "  2 gnome lords"                                                                                             
## [193] "  2 gnomish wizards"                                                                                         
## [194] "  2 gray oozes"                                                                                              
## [195] "  2 elf zombies"                                                                                             
## [196] "  a straw golem"                                                                                             
## [197] "  a paper golem"                                                                                             
## [198] "  2 giant ants"                                                                                              
## [199] "  2 little dogs"                                                                                             
## [200] "  3 floating eyes"                                                                                           
## [201] "  8 dwarves"                                                                                                 
## [202] "  a homunculus"                                                                                              
## [203] "  3 kobold lords"                                                                                            
## [204] "  3 kobold shamans"                                                                                          
## [205] " 13 hill orcs"                                                                                               
## [206] "  4 rothes"                                                                                                  
## [207] "  2 centipedes"                                                                                              
## [208] "  3 giant bats"                                                                                              
## [209] "  6 dwarf zombies"                                                                                           
## [210] "  a werejackal"                                                                                              
## [211] "  3 iguanas"                                                                                                 
## [212] " 23 killer bees"                                                                                             
## [213] " an acid blob"                                                                                               
## [214] "  a coyote"                                                                                                  
## [215] "  3 gas spores"                                                                                              
## [216] "  5 hobbits"                                                                                                 
## [217] "  7 manes"                                                                                                   
## [218] "  2 large kobolds"                                                                                           
## [219] "  a hobgoblin"                                                                                               
## [220] "  2 giant rats"                                                                                              
## [221] "  2 cave spiders"                                                                                            
## [222] "  a yellow mold"                                                                                             
## [223] "  6 gnomes"                                                                                                  
## [224] "  8 garter snakes"                                                                                           
## [225] "  2 gnome zombies"                                                                                           
## [226] "  8 geckos"                                                                                                  
## [227] " 11 jackals"                                                                                                 
## [228] "  5 foxes"                                                                                                   
## [229] "  2 kobolds"                                                                                                 
## [230] "  2 goblins"                                                                                                 
## [231] "  a sewer rat"                                                                                               
## [232] "  6 grid bugs"                                                                                               
## [233] "  3 lichens"                                                                                                 
## [234] "  2 kobold zombies"                                                                                          
## [235] "  5 newts"                                                                                                   
## [236] "206 creatures vanquished."                                                                                   
## [237] ""                                                                                                            
## [238] "No species were genocided or became extinct."                                                                
## [239] ""                                                                                                            
## [240] "Voluntary challenges:"                                                                                       
## [241] " You never genocided any monsters."                                                                          
## [242] " You never polymorphed an object."                                                                           
## [243] " You never changed form."                                                                                    
## [244] " You used no wishes."                                                                                        
## [245] ""                                                                                                            
## [246] "The Dungeons of Doom: levels 1 to 6"                                                                         
## [247] "   Level 1:"                                                                                                 
## [248] "      A fountain."                                                                                           
## [249] "   Level 2:"                                                                                                 
## [250] "      A sink."                                                                                               
## [251] "   Level 3:"                                                                                                 
## [252] "      A general store, a fountain."                                                                          
## [253] "   Level 4:"                                                                                                 
## [254] "      A general store, a fountain."                                                                          
## [255] "      Stairs down to The Gnomish Mines."                                                                     
## [256] "   Level 5:"                                                                                                 
## [257] "      A fountain."                                                                                           
## [258] "   Level 6: <- You were here."                                                                               
## [259] "      A general store."                                                                                      
## [260] "      Final resting place for"                                                                               
## [261] "         you, killed by an ape."                                                                             
## [262] "The Gnomish Mines: levels 5 to 8"                                                                            
## [263] "   Level 5:"                                                                                                 
## [264] "   Level 6:"                                                                                                 
## [265] "   Level 7:"                                                                                                 
## [266] "      Many shops, a temple, some fountains."                                                                 
## [267] "   Level 8:"                                                                                                 
## [268] ""                                                                                                            
## [269] "Game over:"                                                                                                  
## [270] "                       ----------"                                                                           
## [271] "                      /          \\"                                                                         
## [272] "                     /    REST    \\"                                                                        
## [273] "                    /      IN      \\"                                                                       
## [274] "                   /     PEACE      \\"                                                                      
## [275] "                  /                  \\"                                                                     
## [276] "                  |  brothertrebius  |"                                                                      
## [277] "                  |      59 Au       |"                                                                      
## [278] "                  | killed by an ape |"                                                                      
## [279] "                  |                  |"                                                                      
## [280] "                  |                  |"                                                                      
## [281] "                  |                  |"                                                                      
## [282] "                  |       2018       |"                                                                      
## [283] "                 *|     *  *  *      | *"                                                                    
## [284] "        _________)/\\\\_//(\\/(/\\)/\\//\\/|_)_______"                                                       
## [285] ""                                                                                                            
## [286] "Goodbye brothertrebius the Ranger..."                                                                        
## [287] ""                                                                                                            
## [288] "You died in The Dungeons of Doom on dungeon level 6 with 6652 points,"                                       
## [289] "and 59 pieces of gold, after 7398 moves."                                                                    
## [290] "You were level 8 with a maximum of 54 hit points when you died."                                             
## [291] ""

Now, I am curious to see how many games are played per day:

runs_per_day <- nethack %>%
  group_by(date) %>%
  count() %>%
  ungroup() 


ggplot(runs_per_day, aes(y = n, x = date)) + 
  geom_point(colour = "#0f4150") + 
  geom_smooth(colour = "#82518c") + 
  theme_blog()
## `geom_smooth()` using method = 'gam' and formula 'y ~ s(x, bs = "cs")'

The number of games seems to be stable since 2015, around 50. But what is also interesting is not only the number of games played, but also how many of these games resulted in a win.

For this, let’s also add a new column that tells us whether the played ascended (won the game) or not:

nethack %<>%
  mutate(Ascended = ifelse(death == "ascended", "Ascended", "Died an horrible death"))

I’m curious to see how many players managed to ascend… NetHack being as hard as diamonds, probably not a lot:

ascensions_per_day <- nethack %>%
  group_by(date, Ascended) %>%
  count() %>%
  rename(Total = n)

ggplot(ascensions_per_day) + 
  geom_area(aes(y = Total, x = as.Date(date), fill = Ascended)) +
  theme_blog() +
  labs(y = "Number of runs", x = "Date") +
  scale_fill_blog() +
  theme(legend.title = element_blank())

Yeah, just as expected. Because there is so much data, it’s difficult to see clearly, though. Depending on the size of the screen you’re reading this, it might seem that in some days there are a lot of ascensions. This is only an impression due to the resolution of the picture. Let’s see the share of ascensions per year (and how many times the quests fail miserably), and this will become more apparent:

ascensions_per_day %>%
  mutate(Year = year(as.Date(date))) %>%
  group_by(Year, Ascended) %>%
  summarise(Total = sum(Total, na.rm = TRUE)) %>%
  group_by(Year) %>%
  mutate(denom = sum(Total, na.rm = TRUE)) %>%
  ungroup() %>%
  mutate(Share = Total/denom) %>%
  ggplot() + 
  geom_col(aes(y = Share, x = Year, fill = Ascended)) + 
  theme_blog() + 
  scale_fill_blog() + 
  theme(legend.title = element_blank())

I will now convert the “time” column to seconds. I am not yet sure that this column is really useful, because NetHack is a turn based game. This means that when the player does not move, neither do the monsters. So the seconds spent playing might not be a good proxy for actual time spent playing. But it makes for a good exercise:

convert_to_seconds <- function(time_string){
    time_numeric <- time_string %>%
        str_split(":", simplify = TRUE) %>%
        as.numeric

    time_in_seconds <- sum(time_numeric * c(3600, 60, 1))

    time_in_seconds 
}

The strings I want to convert are of the form “01:34:43”, so I split at the “:” and then convert the result to numeric. I end up with an atomic vector (c(1, 34, 43)). Then I multiple each element by the right number of seconds, and sum that to get the total. Let’s apply it to my data:

nethack %<>%
  mutate(time_in_seconds = map_dbl(time, convert_to_seconds))

What is the distribution of “time_in_seconds”?

nethack %>%
  describe(time_in_seconds)
## # A tibble: 1 x 15
##   variable type    nobs   mean     sd mode    min    max   q05   q25 median
##   <chr>    <chr>  <int>  <dbl>  <dbl> <chr> <dbl>  <dbl> <dbl> <dbl>  <dbl>
## 1 time_in… Nume… 322485 23529. 2.73e5 <NA>     61 2.72e7   141   622   1689
## # … with 4 more variables: q75 <dbl>, q95 <dbl>, n_missing <int>,
## #   n_unique <lgl>

We see that the minimum of time_in_seconds is 61 whereas the maximum is of the order of 27200000… This must be a mistake, because that is almost one year!

nethack %>%
  filter(time_in_seconds == max(time_in_seconds, na.rm = TRUE))
##   rank      score   name       time   turns lev_max  hp_max role race
## 1   28 3173960108 fisted 7553:41:49 6860357    4/47 362/362  Wiz  Elf
##   gender alignment                      death       date dumplog year
## 1    Mal       Neu drowned in a pool of water 2017-02-02      NA 2017
##   month day               Ascended time_in_seconds
## 1     2   2 Died an horrible death        27193309

Well… maybe “fisted” wanted to break the record of the longest NetHack game ever. Congratulations!

Let’s take a look at the density but cut it at 90th percentile:

nethack %>%
  filter(!is.na(time_in_seconds),
         time_in_seconds < quantile(time_in_seconds, 0.9, na.rm = TRUE)) %>%
  ggplot() + 
  geom_density(aes(x = time_in_seconds), colour = "#82518c") + 
  theme_blog()

As expected, the distribution is right skewed. However, as explained above NetHack is a turn based game, meaning that if the player does not move, the monsters won’t move either. Perhaps it makes more sense to look at the turns column:

nethack %>%
  describe(turns)
## # A tibble: 1 x 15
##   variable type    nobs  mean     sd mode    min    max   q05   q25 median
##   <chr>    <chr>  <int> <dbl>  <dbl> <chr> <dbl>  <dbl> <dbl> <dbl>  <dbl>
## 1 turns    Nume… 322485 4495. 19853. <NA>      1 6.86e6   202   871   1818
## # … with 4 more variables: q75 <dbl>, q95 <dbl>, n_missing <int>,
## #   n_unique <lgl>

The maximum is quite large too. Just like before, let’s focus by cutting the variable at the 90th percentile:

nethack %>%
  filter(!is.na(turns),
         turns < quantile(turns, 0.9, na.rm = TRUE)) %>% 
  ggplot() + 
  geom_density(aes(x = turns), colour = "#82518c") + 
  theme_blog()

I think that using turns makes more sense. In the a future blog post, I will estimate a survival model and see how long players survive, and will use turns instead of time_in_seconds.

Analysis

What kills the players

To know what kills players so much, some cleaning of the death column is in order. Death can occur from poisoning, starvation, accidents, drowning… of course monsters can kill the player too. Here are some values of the death variable:

burned by a tower of flame
choked on a lichen corpse
died of starvation
fell into a pit of iron spikes
killed by a gnome
killed by a gnome called Blabla
killed by a gnome called Blabla while sleeping
slipped while mounting a saddled pony
slipped while mounting a saddled pony called Jolly Jumper
zapped her/himself with a spell

To know what is the most frequent cause of death, I have to do some cleaning, because if not, “killed by a gnome” and “killed by a gnome called Blabla” would be two different causes of death. In the end, what interests me is to know how many times the player got killed by a gnome.

The following lines do a cleanup of the death variable:

nethack %<>% 
  mutate(death2 = case_when(str_detect(death, "poisoned") ~ "poisoned",
                            str_detect(death, "slipped") ~ "accident",
                            str_detect(death, "petrified") ~ "petrified",
                            str_detect(death, "choked") ~ "accident",
                            str_detect(death, "caught.*self") ~ "accident",
                            str_detect(death, "starvation") ~ "starvation",
                            str_detect(death, "drowned") ~ "drowned",
                            str_detect(death, "fell") ~ "fell",
                            str_detect(death, "zapped") ~ "zapped",
                            str_detect(death, "killed") ~ "killed",
                            TRUE ~ death)) %>%
  mutate(death3 = str_extract(death, "(?<=by|while).*")) %>%
  mutate(death3 = case_when(str_detect(death3, ",|\\bcalled\\b") ~ str_extract(death3, "(.*?),|(.*?)\\bcalled\\b"), 
                            TRUE ~ death3)) %>%
  mutate(death3 = str_remove(death3, ",|called|\\ban?"),
         death3 = str_trim(death3))

death2 is a new variable, in which I broadly categorize causes of death. Using regular expressions I detect causes of death and aggregate some categories, for instance “slipped” and “chocked” into “accident”. Then, I want to extract everything that comes after the strings “by” or while, and put the result into a new variable called death3. Then I detect the string “,” or “called”; if one of these strings is present, I extract everything that comes before “,” or that comes before “called”. Finally, I remove “,”, “called” or “a” or “an” from the string and trim the whitespaces.

Let’s take a look at these new variables:

set.seed(123)
nethack %>%
    select(name, death, death2, death3) %>%
    sample_n(10)
##              name                     death   death2        death3
## 92740   DianaFury     killed by a death ray   killed     death ray
## 254216    Oddabit         killed by a tiger   killed         tiger
## 131889    shachaf      killed by a fire ant   killed      fire ant
## 284758        a43  poisoned by a killer bee poisoned    killer bee
## 303283      goast         killed by a gecko   killed         gecko
## 14692     liberty    killed by a gnome king   killed    gnome king
## 170303     arch18                  ascended ascended          <NA>
## 287786 foolishwtf           killed by a bat   killed           bat
## 177826    Renleve     killed by a giant bat   killed     giant bat
## 147248      TheOV killed by a black unicorn   killed black unicorn

Now, it is quite easy to know what monsters are the meanest buttholes; let’s focus on the top 15. Most likely, these are going to be early game monsters. Let’ see:

nethack %>%
    filter(!is.na(death3)) %>%
    count(death3) %>%
    top_n(15) %>%
    mutate(death3 = fct_reorder(death3, n, .desc = FALSE)) %>%
    ggplot() + 
    geom_col(aes(y = n, x = death3)) + 
    coord_flip() + 
    theme_blog() + 
    scale_fill_blog() + 
    ylab("Number of deaths caused") +
    xlab("Monster")
## Selecting by n

Seems like soldier ants are the baddest, followed by jackals and dwarfs. As expected, these are mostly early game monsters. Thus, it would be interesting to look at this distribution, but at different stages in the game. Let’s create a categorical variable that discretizes turns, and then create one plot per category:

Click to expand

nethack %>%
    filter(!is.na(death3)) %>%
    filter(!is.na(turns)) %>%
    mutate(turn_flag = case_when(between(turns, 1, 5000) ~ "Less than 5000",
                                 between(turns, 5001, 10000) ~ "Between 5001 and 10000",
                                 between(turns, 10001, 20000) ~ "Between 10001 and 20000",
                                 between(turns, 20001, 40000) ~ "Between 20001 and 40000",
                                 between(turns, 40001, 60000) ~ "Between 40001 and 60000",
                                 turns > 60000 ~ "More than 60000")) %>%
    mutate(turn_flag = factor(turn_flag, levels = c("Less than 5000", 
                                                    "Between 5001 and 10000",
                                                    "Between 10001 and 20000",
                                                    "Between 20001 and 40000",
                                                    "Between 40001 and 60000",
                                                    "More than 60000"), ordered = TRUE)) %>%
    group_by(turn_flag) %>%
    count(death3) %>%
    top_n(15) %>%
    nest() %>%
    mutate(data = map(data, ~mutate(., death3 = fct_reorder(death3, n, .desc = TRUE))))  %>%
    mutate(plots = map2(.x = turn_flag,
                         .y = data,
                         ~ggplot(data = .y) + 
                             geom_col(aes(y = n, x = death3)) + 
                             coord_flip() + 
                             theme_blog() + 
                             scale_fill_blog() + 
                             ylab("Number of deaths caused") +
                             xlab("Monster") + 
                             ggtitle(.x))) %>%
    pull(plots)
## Selecting by n
## [[1]]

## 
## [[2]]

## 
## [[3]]

## 
## [[4]]

## 
## [[5]]

## 
## [[6]]

Finally, for this section, I want to know if there are levels, or floors, where players die more often than others. For this, we can take a look at the lev_max column. Observations in this column are of the form “8/10”. This means that the player died on level 8, but the lowest level that was explored was the 10th. Let’s do this for the year 2017 first. Before anything, I have to explain the layout of the levels of the game. You can see a diagram here. The player starts on floor 1, and goes down to level 53. Then, the player can ascend, by going on levels -1 to -5. But there are more levels than these ones. -6 and -9 are the sky, and the player can teleport there (but will fall to his death). If the player teleports to level -10, he’ll enter heaven (and die too). Because these levels are special, I do not consider them here. I do not consider level 0 either, which is “Nowhere”. Let’s get the number of players who died on each floor, but also compute the cumulative death count:

died_on_level <- nethack %>%
    filter(Ascended == "Died an horrible death") %>%
    mutate(died_on = str_extract(lev_max, "-?\\d{1,}")) %>%
    mutate(died_on = as.numeric(died_on)) %>%
    group_by(year) %>%
    count(died_on) %>% 
    filter(died_on >= -5, died_on != 0) %>%
    mutate(died_on = case_when(died_on == -1 ~ 54,
                               died_on == -2 ~ 55,
                               died_on == -3 ~ 56,
                               died_on == -4 ~ 57,
                               died_on == -5 ~ 58,
                               TRUE ~ died_on)) %>%
    arrange(desc(died_on)) %>%
    mutate(cumul_deaths = cumsum(n))

Let’s take a look:

head(died_on_level)
## # A tibble: 6 x 4
## # Groups:   year [6]
##    year died_on     n cumul_deaths
##   <dbl>   <dbl> <int>        <int>
## 1  2002      58     5            5
## 2  2003      58    11           11
## 3  2004      58    19           19
## 4  2005      58    28           28
## 5  2006      58    25           25
## 6  2007      58    22           22

Now, let’s compute the number of players who ascended and add this to the cumulative count:

ascended_yearly <- nethack %>%
    filter(Ascended == "Ascended") %>%
    group_by(year) %>%
    count(Ascended)

Let’s take a look:

head(ascended_yearly)
## # A tibble: 6 x 3
## # Groups:   year [6]
##    year Ascended     n
##   <dbl> <chr>    <int>
## 1  2001 Ascended     4
## 2  2002 Ascended    38
## 3  2003 Ascended   132
## 4  2004 Ascended   343
## 5  2005 Ascended   329
## 6  2006 Ascended   459

I will modify the dataset a little bit and merge it with the previous one:

ascended_yearly %<>%
  rename(ascended_players = `n`) %>%
  select(-Ascended)

Let’s add this to the data frame from before by merging both, and then we can compute the surviving players:

died_on_level %<>%
  full_join(ascended_yearly, by = "year") %>%
  mutate(surviving_players = cumul_deaths + ascended_players)

Now we can compute the share of players who died on each level:

died_on_level %>%
    mutate(death_rate = n/surviving_players) %>% 
    ggplot(aes(y = death_rate, x = as.factor(died_on))) + 
    geom_line(aes(group = year, alpha = year), colour = "#82518c") +
    theme_blog() + 
    ylab("Death rate") +
    xlab("Level") + 
    theme(axis.text.x = element_text(angle = 90),
          legend.position = "none") + 
    scale_y_continuous(labels = scales::percent)

Looks like level 7 is consistently the most dangerous! The death rate there is more than 35%!

That’s it for this blog post, in the next one, I will focus on what players kill!

Hope you enjoyed! If you found this blog post useful, you might want to follow me on twitter for blog post updates or buy me an espresso.

Buy me an EspressoBuy me an Espresso