Assignment 2

DnD Monsters

Author

Андрій Кондраток

Я обрав датасет про монстрів з настільно рольової гри DnD, оскільки маю награних 1000+ годин

Спочатку зчитаємо його та подивимось на структуру

library(tidyverse)
library(dplyr)
library(stringr)
dnd_data = read_csv("https://raw.githubusercontent.com/rfordatascience/tidytuesday/refs/heads/main/data/2025/2025-05-27/monsters.csv")
knitr::kable(head(dnd_data |> select(-c(full_text,immunities)), 3), format = "html")
name category cr size type descriptive_tags alignment ac initiative hp hp_number speed speed_base_number str dex con int wis cha str_save dex_save con_save int_save wis_save cha_save skills resistances vulnerabilities gear senses languages
Aboleth Aboleth 10 Large Aberration NA Lawful Evil 17 7 150 (20d10 + 40) 150 Speed 10 ft., Swim 40 ft. 10 21 9 15 18 15 18 5 3 6 8 6 4 History +12, Perception +10 NA NA NA Darkvision 120 ft.; Passive Perception 20 Deep Speech; telepathy 120 ft.
Air Elemental Air Elemental 5 Large Elemental NA Neutral 15 5 90 (12d10 + 24) 90 Speed 10 ft., Fly 90 ft. (hover) 10 14 20 14 6 10 6 2 5 2 -2 0 -2 NA Bludgeoning, Lightning, Piercing, Slashing NA NA Darkvision 60 ft.; Passive Perception 10 Primordial (Auran)
Animated Armor Animated Objects 1 Medium Construct NA Unaligned 18 2 33 (6d8 + 6) 33 Speed 25 ft. 25 14 11 13 1 3 1 2 0 1 -5 -4 -5 NA NA NA NA Blindsight 60 ft.; Passive Perception 6 None

Тепер подивимось структуру через glimpse:

glimpse(dnd_data)
Rows: 330
Columns: 33
$ name              <chr> "Aboleth", "Air Elemental", "Animated Armor", "Anima…
$ category          <chr> "Aboleth", "Air Elemental", "Animated Objects", "Ani…
$ cr                <dbl> 10.000, 5.000, 1.000, 0.250, 2.000, 2.000, 8.000, 0.…
$ size              <chr> "Large", "Large", "Medium", "Small", "Large", "Large…
$ type              <chr> "Aberration", "Elemental", "Construct", "Construct",…
$ descriptive_tags  <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "Demon",…
$ alignment         <chr> "Lawful Evil", "Neutral", "Unaligned", "Unaligned", …
$ ac                <dbl> 17, 15, 18, 17, 12, 14, 16, 9, 13, 11, 17, 19, 12, 1…
$ initiative        <dbl> 7, 5, 2, 4, 4, 0, 10, -1, -2, 1, 1, 14, 1, 3, 3, -1,…
$ hp                <chr> "150 (20d10 + 40)", "90 (12d10 + 24)", "33 (6d8 + 6)…
$ hp_number         <dbl> 150, 90, 33, 14, 27, 45, 97, 10, 59, 19, 39, 287, 11…
$ speed             <chr> "Speed 10 ft., Swim 40 ft.", "Speed 10 ft., Fly 90 f…
$ speed_base_number <dbl> 10, 10, 25, 5, 10, 30, 30, 20, 20, 50, 30, 40, 30, 3…
$ str               <dbl> 21, 14, 14, 12, 17, 17, 11, 3, 19, 14, 17, 26, 11, 1…
$ dex               <dbl> 9, 20, 11, 15, 14, 11, 18, 8, 6, 12, 12, 15, 12, 16,…
$ con               <dbl> 15, 14, 13, 11, 10, 14, 14, 11, 15, 12, 15, 22, 12, …
$ int               <dbl> 18, 6, 1, 1, 1, 1, 16, 10, 10, 2, 12, 20, 10, 14, 12…
$ wis               <dbl> 15, 10, 3, 5, 3, 13, 11, 10, 10, 10, 13, 16, 10, 11,…
$ cha               <dbl> 18, 6, 1, 1, 1, 6, 10, 6, 7, 5, 10, 22, 10, 14, 14, …
$ str_save          <dbl> 5, 2, 2, 1, 3, 3, 0, -4, 4, 2, 3, 8, 0, 4, 6, 3, 5, …
$ dex_save          <dbl> 3, 5, 0, 4, 2, 0, 7, -1, -2, 1, 1, 2, 1, 5, 3, -1, 2…
$ con_save          <dbl> 6, 2, 1, 0, 0, 2, 2, 0, 2, 1, 4, 12, 1, 2, 7, 2, 4, …
$ int_save          <dbl> 8, -2, -5, -5, -5, -5, 6, 0, 0, -4, 1, 5, 0, 2, 1, -…
$ wis_save          <dbl> 6, 0, -4, -3, -4, 1, 0, 0, 0, 0, 1, 9, 0, 2, 5, -1, …
$ cha_save          <dbl> 4, -2, -5, -5, -5, -2, 0, -2, -2, -3, 0, 6, 0, 2, 5,…
$ skills            <chr> "History +12, Perception +10", NA, NA, NA, NA, NA, "…
$ resistances       <chr> NA, "Bludgeoning, Lightning, Piercing, Slashing", NA…
$ vulnerabilities   <chr> NA, NA, NA, NA, NA, NA, NA, "Fire", "Fire", NA, NA, …
$ immunities        <chr> NA, "Poison, Thunder; Exhaustion, Grappled, Paralyze…
$ gear              <chr> NA, NA, NA, NA, NA, NA, "Light Crossbow, Shortsword,…
$ senses            <chr> "Darkvision 120 ft.; Passive Perception 20", "Darkvi…
$ languages         <chr> "Deep Speech; telepathy 120 ft.", "Primordial (Auran…
$ full_text         <chr> "Aboleth\nLarge Aberration, Lawful Evil\nAC 17\t\t  …

І саммарі(тільки тих змінних, у яких в структурі double, і без saving throws, бо це реально залежить від настрою дм’а):

dnd_data |> 
  select(cr, ac, initiative, speed_base_number, str, dex, con, int, wis, cha) |> 
  summary()
       cr               ac          initiative     speed_base_number
 Min.   : 0.000   Min.   : 5.00   Min.   :-5.000   Min.   : 5.00    
 1st Qu.: 0.500   1st Qu.:12.00   1st Qu.: 1.000   1st Qu.:30.00    
 Median : 2.000   Median :14.00   Median : 2.000   Median :30.00    
 Mean   : 4.551   Mean   :14.29   Mean   : 3.148   Mean   :30.88    
 3rd Qu.: 6.000   3rd Qu.:17.00   3rd Qu.: 4.000   3rd Qu.:40.00    
 Max.   :30.000   Max.   :25.00   Max.   :20.000   Max.   :60.00    
      str             dex             con             int        
 Min.   : 1.00   Min.   : 1.00   Min.   : 8.00   Min.   : 1.000  
 1st Qu.:11.00   1st Qu.:10.00   1st Qu.:12.00   1st Qu.: 2.000  
 Median :16.00   Median :13.00   Median :14.50   Median : 7.000  
 Mean   :15.38   Mean   :12.83   Mean   :15.18   Mean   : 7.864  
 3rd Qu.:19.00   3rd Qu.:15.00   3rd Qu.:17.00   3rd Qu.:12.000  
 Max.   :30.00   Max.   :28.00   Max.   :30.00   Max.   :25.000  
      wis             cha        
 Min.   : 3.00   Min.   : 1.000  
 1st Qu.:10.00   1st Qu.: 5.000  
 Median :12.00   Median : 8.000  
 Mean   :11.82   Mean   : 9.918  
 3rd Qu.:13.00   3rd Qu.:14.000  
 Max.   :25.00   Max.   :30.000  

У нас є непотрібний стовпчик hp, який заміняється hp_number. Видалимо його:

dnd_data <- dnd_data |> 
  select(-hp)

Фільтрування

Відфільтруємо лише монстрів, які мають резіст до вогняного дамагу:

knitr::kable(dnd_data |> 
  filter(str_detect(resistances, regex("FIRE", ignore_case = TRUE))) |> 
  select(name, size, alignment, hp_number, resistances) |> 
    slice(1:10))
Фільтрування за наявністю резисту до вогню
name size alignment hp_number resistances
Dragon Turtle Gargantuan Neutral 356 Fire
Dretch Small Chaotic Evil 18 Cold, Fire, Lightning
Ghost Medium Neutral 45 Acid, Bludgeoning, Cold, Fire, Lightning, Piercing, Slashing, Thunder
Glabrezu Large Chaotic Evil 189 Cold, Fire, Lightning
Gray Ooze Medium Unaligned 22 Acid, Cold, Fire
Hezrou Large Chaotic Evil 157 Cold, Fire, Lightning
Incubus Medium Neutral Evil 66 Cold, Fire, Poison, Psychic
Marilith Large Chaotic Evil 220 Cold, Fire, Lightning
Nalfeshnee Large Chaotic Evil 184 Cold, Fire, Lightning
Night Hag Medium Neutral Evil 112 Cold, Fire

А тепер тих, хто має резист до блискавки та більше 100хп:

knitr::kable(dnd_data |> 
  filter(str_detect(resistances, regex("LIGHTNING", ignore_case = TRUE)) & hp_number>=100) |> 
  select(name, size, alignment, hp_number, resistances))
Фільтрування за наявністю резисту до блискавки та хп>100
name size alignment hp_number resistances
Balor Huge Chaotic Evil 287 Cold, Lightning
Glabrezu Large Chaotic Evil 189 Cold, Fire, Lightning
Hezrou Large Chaotic Evil 157 Cold, Fire, Lightning
Lich Medium Neutral Evil 315 Cold, Lightning
Marilith Large Chaotic Evil 220 Cold, Fire, Lightning
Nalfeshnee Large Chaotic Evil 184 Cold, Fire, Lightning
Vrock Large Chaotic Evil 152 Cold, Fire, Lightning

Тепер оберемо найшвидших монстрів, від яки не втече жоден шукач пригод:

knitr::kable(dnd_data |> 
  arrange(desc(speed_base_number)) |> 
  select(name, size, alignment, hp_number, speed_base_number) |> 
    slice(1:5))
5 Найшвидших монстрів
name size alignment hp_number speed_base_number
Nightmare Large Neutral Evil 68 60
Pegasus Large Chaotic Good 59 60
Warhorse Skeleton Large Lawful Evil 22 60
Tarrasque Gargantuan Unaligned 697 60
Allosaurus Large Unaligned 51 60

Додавання нових змінних

Я додам факторну змінну, яка означатиме, який левел фаєрболу потрібен, щоб знищити монстра. Якщо має резист до вогню - дамаг ріжеться у 2 рази. 3 левел фаєрбола це 8д6 дамагу, тобто максимум 48. Далі кожен наступний рівень по +1д6. Якщо монстра одним фаєрболом вбити не можна - пишемо нуль. Кріти не враховуємо, мені не подобається як вони калічно обраховуються.

dnd_data <- dnd_data |> 
  mutate(
    resistances = replace_na(resistances, ""),
    effective_hp = if_else(
      str_detect(resistances, regex("fire", ignore_case = TRUE)),
      hp_number * 2,
      hp_number
    ),
    fireball_level = case_when(
      effective_hp <= 48 ~ 3,
      effective_hp <= 54 ~ 4,
      effective_hp <= 60 ~ 5,
      effective_hp <= 66 ~ 6,
      effective_hp <= 72 ~ 7,
      effective_hp <= 78 ~ 8,
      effective_hp <= 84 ~ 9,
      TRUE ~ 0
    ),
    fireball_level = factor(fireball_level, levels = 0:9)
  )
knitr::kable(dnd_data[str_detect(dnd_data$resistances, regex("fire", ignore_case = TRUE)), ] |> 
  select(name, hp_number, effective_hp, resistances, fireball_level) |> 
  slice(1:5))
Додавання факторної змінної
name hp_number effective_hp resistances fireball_level
Dragon Turtle 356 712 Fire 0
Dretch 18 36 Cold, Fire, Lightning 3
Ghost 45 90 Acid, Bludgeoning, Cold, Fire, Lightning, Piercing, Slashing, Thunder 0
Glabrezu 189 378 Cold, Fire, Lightning 0
Gray Ooze 22 44 Acid, Cold, Fire 3
knitr::kable(dnd_data |> 
  select(name, hp_number, effective_hp, resistances, fireball_level) |> 
  slice(1:5))
Монстри без резисту до вогню з факторною змінною
name hp_number effective_hp resistances fireball_level
Aboleth 150 150 0
Air Elemental 90 90 Bludgeoning, Lightning, Piercing, Slashing 0
Animated Armor 33 33 3
Animated Flying Sword 14 14 3
Animated Rug of Smothering 27 27 3

Групування

Згрупувати можна по різним колонкам, наприклад size, type, або alignment.

knitr::kable(dnd_data |> 
  group_by(alignment) |> 
  count() |> 
  ungroup())
Amount of monsters per allignment
alignment n
Chaotic Evil 45
Chaotic Good 11
Chaotic Neutral 7
Lawful Evil 35
Lawful Good 20
Lawful Neutral 3
Neutral 50
Neutral Evil 28
Neutral Good 6
Unaligned 125
knitr::kable(dnd_data |> 
  group_by(size) |> 
  count() |> 
  ungroup())
Amount of monsters per size
size n
Gargantuan 15
Huge 34
Large 107
Medium 90
Medium or Small 36
Small 23
Tiny 25
knitr::kable(dnd_data |> 
  group_by(type) |> 
  count() |> 
  ungroup())
Amount of monsters per type
type n
Aberration 9
Beast 84
Celestial 13
Construct 10
Dragon 45
Elemental 17
Fey 15
Fiend 29
Giant 10
Humanoid 26
Monstrosity 37
Ooze 4
Plant 6
Swarm of Tiny Beasts 6
Swarm of Tiny Undead 1
Undead 18