D&D Monsters Analysis: Challenge Ratings and Type Distribution

Author

Data Analysis Report

Published

June 7, 2025

Note

This report was generated using artificial intelligence (Claude from Anthropic) under general human direction. At the time of generation, the contents have not been comprehensively reviewed by a human analyst.

# Load required libraries
library(tidyverse)
Warning: package 'tidyverse' was built under R version 4.4.2
Warning: package 'tibble' was built under R version 4.4.2
Warning: package 'tidyr' was built under R version 4.4.2
Warning: package 'readr' was built under R version 4.4.1
Warning: package 'dplyr' was built under R version 4.4.2
Warning: package 'stringr' was built under R version 4.4.2
Warning: package 'forcats' was built under R version 4.4.2
Warning: package 'lubridate' was built under R version 4.4.2
library(ggplot2)

# Load the monsters dataset
monsters <- readr::read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2025/2025-05-27/monsters.csv', 
                           show_col_types = FALSE)

Introduction

This report analyzes a dataset of Dungeons & Dragons monsters, examining the distribution of monster types and their challenge ratings. The dataset contains 330 different creatures with 33 attributes each, providing insights into game balance and encounter design.

The analysis focuses on understanding: - The composition of monster types in the dataset - How challenge ratings vary across different creature types - Patterns that might inform game masters about encounter planning

Data Overview

# Display basic dataset information
glimpse(monsters)
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  ~

The dataset includes 330 monsters with comprehensive game statistics including:

  • Identification: name, category, type, size
  • Combat attributes: challenge rating (CR), armor class, hit points, ability scores
  • Special features: resistances, immunities, skills, gear
# Check for missing values in key columns
monsters |>
  summarise(
    total_monsters = n(),
    missing_type = sum(is.na(type)),
    missing_cr = sum(is.na(cr)),
    missing_size = sum(is.na(size))
  )
# A tibble: 1 x 4
  total_monsters missing_type missing_cr missing_size
           <int>        <int>      <int>        <int>
1            330            0          0            0

Monster Type Analysis

Distribution of Monster Types

# Most common monster types
type_counts <- monsters |> 
  count(type, sort = TRUE)

type_counts |> head(10)
# A tibble: 10 x 2
   type            n
   <chr>       <int>
 1 Beast          84
 2 Dragon         45
 3 Monstrosity    37
 4 Fiend          29
 5 Humanoid       26
 6 Undead         18
 7 Elemental      17
 8 Fey            15
 9 Celestial      13
10 Construct      10

The dataset is dominated by Beasts (84 creatures), followed by Dragons (45 creatures) and Monstrosities (37 creatures).

# Visualize monster type distribution
monsters |>
  count(type, sort = TRUE) |>
  head(10) |>
  ggplot(aes(x = reorder(type, n), y = n, fill = type)) +
  geom_col(alpha = 0.8) +
  coord_flip() +
  labs(
    title = "Distribution of Monster Types",
    subtitle = "Top 10 most common creature types in the dataset",
    x = "Monster Type",
    y = "Number of Creatures",
    caption = "Data: D&D Monsters Dataset"
  ) +
  theme_minimal() +
  theme(legend.position = "none") +
  scale_fill_viridis_d()

Category Analysis

# Most common monster categories (more specific groupings)
monsters |> 
  count(category, sort = TRUE) |>
  head(10)
# A tibble: 10 x 2
   category           n
   <chr>          <int>
 1 Animals           95
 2 Black Dragons      4
 3 Blue Dragons       4
 4 Brass Dragons      4
 5 Bronze Dragons     4
 6 Copper Dragons     4
 7 Gold Dragons       4
 8 Green Dragons      4
 9 Mephits            4
10 Red Dragons        4

The category classification provides more granular groupings, with Animals being the largest single category, followed by various dragon subtypes (Black, Blue, Brass, etc.).

Challenge Rating Analysis

Challenge Rating Distribution by Type

# Summary statistics of challenge rating by monster type
cr_summary <- monsters |>
  group_by(type) |>
  summarise(
    count = n(),
    min_cr = min(cr, na.rm = TRUE),
    max_cr = max(cr, na.rm = TRUE),
    mean_cr = round(mean(cr, na.rm = TRUE), 2),
    median_cr = median(cr, na.rm = TRUE),
    .groups = "drop"
  ) |>
  arrange(desc(count))

cr_summary
# A tibble: 16 x 6
   type                 count min_cr max_cr mean_cr median_cr
   <chr>                <int>  <dbl>  <dbl>   <dbl>     <dbl>
 1 Beast                   84  0          8    1.07     0.25 
 2 Dragon                  45  0.125     24   11.1     10    
 3 Monstrosity             37  0.125     30    5.04     3    
 4 Fiend                   29  0         20    7.15     6    
 5 Humanoid                26  0         12    2.61     2    
 6 Undead                  18  0.25      21    4.47     2    
 7 Elemental               17  0.125     11    3.79     5    
 8 Fey                     15  0.125      3    1.23     1    
 9 Celestial               13  0.25      21    7.71     5    
10 Construct               10  0         16    5.53     5    
11 Giant                   10  0.5       13    6.25     6    
12 Aberration               9  0.25      10    4.08     4    
13 Plant                    6  0          9    2.71     1.12 
14 Swarm of Tiny Beasts     6  0.25       2    0.71     0.375
15 Ooze                     4  0.5        4    2.12     2    
16 Swarm of Tiny Undead     1  3          3    3        3    

Challenge Rating Patterns

# Create a boxplot showing CR distribution by monster type
monsters |>
  # Focus on the most common types for clarity
  filter(type %in% c("Beast", "Dragon", "Monstrosity", "Fiend", "Humanoid", "Undead", "Elemental", "Fey")) |>
  ggplot(aes(x = reorder(type, cr, median), y = cr, fill = type)) +
  geom_boxplot(alpha = 0.7) +
  geom_jitter(width = 0.2, alpha = 0.5, size = 1) +
  labs(
    title = "Challenge Rating Distribution by Monster Type",
    subtitle = "Most common monster types (15+ creatures each)",
    x = "Monster Type",
    y = "Challenge Rating",
    fill = "Type",
    caption = "Points show individual monsters; boxes show quartiles"
  ) +
  theme_minimal() +
  theme(
    axis.text.x = element_text(angle = 45, hjust = 1),
    legend.position = "none"
  ) +
  scale_fill_viridis_d()

Highest Challenge Rating Creatures

# Highest CR monsters from each type
highest_cr <- monsters |>
  group_by(type) |>
  filter(cr == max(cr)) |>
  select(name, type, cr, size) |>
  arrange(desc(cr)) |>
  head(10)

highest_cr
# A tibble: 10 x 4
# Groups:   type [9]
   name                type           cr size           
   <chr>               <chr>       <dbl> <chr>          
 1 Tarrasque           Monstrosity    30 Gargantuan     
 2 Ancient Gold Dragon Dragon         24 Gargantuan     
 3 Ancient Red Dragon  Dragon         24 Gargantuan     
 4 Lich                Undead         21 Medium         
 5 Solar               Celestial      21 Large          
 6 Pit Fiend           Fiend          20 Large          
 7 Iron Golem          Construct      16 Large          
 8 Storm Giant         Giant          13 Huge           
 9 Archmage            Humanoid       12 Medium or Small
10 Djinni              Elemental      11 Large          

The most challenging creature in the dataset is the Tarrasque with a challenge rating of 30.

Key Findings

1. Monster Type Distribution

  • Beasts dominate the dataset with 84 creatures (25.5% of all monsters)
  • Dragons form the second-largest group with 45 creatures
  • The dataset includes 16 distinct monster types

2. Challenge Rating Patterns

  • Dragons have the highest average difficulty: Mean CR of 11.12, median CR of 10
  • Beasts are predominantly low-level: Mean CR of 1.07, median CR of 0.25
  • Clear difficulty tiers emerge:
    • Low-threat: Beasts (median 0.25), Fey (median 1)
    • Mid-threat: Humanoids (median 2), Undead (median 2)
    • High-threat: Dragons (median 10), Fiends (median 6)

3. Notable Outliers

  • Tarrasque (Monstrosity) represents the ultimate challenge at CR 30
  • Humanoids can reach surprisingly high levels (max CR 12)
  • Monstrosities show the widest range (CR 0.125 to 30)

Conclusion

This analysis reveals clear patterns in D&D monster design:

  1. Encounter Variety: The dominance of Beasts provides numerous low-level encounter options for new adventures
  2. Scaling Difficulty: Dragons consistently provide high-level challenges, making them ideal for climactic encounters
  3. Type Diversity: Each monster type occupies a distinct niche in the difficulty spectrum, supporting varied gameplay experiences

These patterns suggest thoughtful game design that provides appropriate challenges across all player levels, with clear expectations for encounter difficulty based on creature type.


Analysis completed on 2025-06-07 using R R version 4.4.0 (2024-04-24)