{"id":21427035,"url":"https://github.com/japhir/dndraces","last_synced_at":"2026-05-19T11:04:19.178Z","repository":{"id":93589346,"uuid":"247489114","full_name":"japhir/DnDRaces","owner":"japhir","description":"Simulating D\u0026D Race heights and weights in R","archived":false,"fork":false,"pushed_at":"2020-08-28T17:26:19.000Z","size":652,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-23T07:44:54.280Z","etag":null,"topics":["dice","dplyr","emacs","ggplot2","org-mode","purrr","r","simulation"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/japhir.png","metadata":{"files":{"readme":"README.org","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2020-03-15T15:02:51.000Z","updated_at":"2024-07-10T13:56:24.000Z","dependencies_parsed_at":"2023-04-05T13:30:58.061Z","dependency_job_id":null,"html_url":"https://github.com/japhir/DnDRaces","commit_stats":{"total_commits":22,"total_committers":2,"mean_commits":11.0,"dds":"0.36363636363636365","last_synced_commit":"27bd458f21988d98934631f6474d5f3befca4971"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/japhir%2FDnDRaces","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/japhir%2FDnDRaces/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/japhir%2FDnDRaces/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/japhir%2FDnDRaces/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/japhir","download_url":"https://codeload.github.com/japhir/DnDRaces/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243933425,"owners_count":20370989,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dice","dplyr","emacs","ggplot2","org-mode","purrr","r","simulation"],"created_at":"2024-11-22T21:43:45.441Z","updated_at":"2026-05-19T11:04:14.115Z","avatar_url":"https://github.com/japhir.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"#+TITLE: Simulating D\u0026D 5e Race Heights and Weights\n#+OPTIONS: ^:{}\n\n* D\u0026D Character height and weight\nIn D\u0026D you can roll dice to determine your character's height and weight based\non it's race.\n\nThis is the table of race size and weight in the D\u0026D player handbook:\n\n| Race            | Base Height | Height Modifier | Base Weight | Weight Modifier |\n|-----------------+-------------+-----------------+-------------+-----------------|\n| Human           | 4'8\"        |           +2d10 | 110 lb.     | × (2d4) lb.     |\n| Dwarf, hill     | 3'8\"        |            +2d4 | 115 lb.     | × (2d6) lb.     |\n| Dwarf, mountain | 4'          |            +2d4 | 130 lb.     | × (2d6) lb.     |\n| Elf, high       | 4'6\"        |           +2d10 | 90 lb.      | × (1d4) lb.     |\n| Elf, wood       | 4'6\"        |           +2d10 | 100 lb.     | × (1d4) lb.     |\n| Elf, drow       | 4'5\"        |            +2d6 | 75 lb.      | × (1d6) lb.     |\n| Halfling        | 2'7\"        |            +2d4 | 35 lb.      | × 1 lb.         |\n| Dragonborn      | 5'6\"        |            +2d8 | 175 lb.     | × (2d6) lb.     |\n| Gnome           | 2'11\"       |            +2d4 | 35 lb.      | × 1 lb.         |\n| Half-elf        | 4'9\"        |            +2d8 | 110 lb.     | × (2d4) lb.     |\n| Half-orc        | 4'10\"       |           +2d10 | 140 lb.     | × (2d6) lb.     |\n| Tiefling        | 4'9\"        |            +2d8 | 110 lb.     | × (2d4) lb.     |\n\nTo calculate the height, you have to add the ~Base Height~ to, e.g. 2d10 dice\nrolls for the Human, which means you have to roll two 10-sided-die to determine\nhow much to add, in inches.\n\n* Problem: I think metric\nI don't think in terms of feet, inches, and pounds, so I thought I'd convert\nsome things to centimetres and kilogrammes in stead. Furthermore, I wanted to\nbetter understand how these dice-rolls influence the final distribution of\nweights and heights in the D\u0026D universe.\n\nAnd so I've simulated some dice-rolls to answer those questions!\n\n* Rolling Virtual Dice\n** Load libraries\n# this is so that we work in an R session in emacs with ess\n#+PROPERTY: header-args:R  :session *R*\n\nFirst load the libraries we use here:\n#+begin_src R :results none\n  library(dplyr)\n  library(ggplot2)\n  library(purrr)\n  library(tidyr)\n#+end_src\n\n** Virtual dice\nThen, let's figure out how to roll a virtual die in R. This function should do it:\n\n#+begin_src R\n  roll_dice \u003c- function(n = 1, d = 20) {\n    sum(sample(seq_len(d), size = n, replace = TRUE))\n  }\n#+end_src\n\n#+RESULTS:\n\nBy creating a vector of length ~d~, and sampling from it ~n~ times, with\nreplacement, and then taking the sum of those numbers we get simulated\ndice-rolls!\n\nTest if it works\n#+begin_src R\n    c(roll_dice(),       # 1d20\n      roll_dice(1, 10),  # 1d10\n      roll_dice(2, 6),   # 2d6\n      roll_dice(2, 4))   # 2d4\n#+end_src\n\n#+RESULTS:\n| 9 |\n| 2 |\n| 8 |\n| 8 |\n\n| 10 |\n|  7 |\n|  7 |\n|  3 |\n\nThis seems to work!\n\n** Repeated rolls\nNow we need to call the ~roll_die~ function many times for the simulations.\n\nWe can do this with ~replicate~ as follows:\n\n#+begin_src R :results none\n  replicate(1e3, roll_dice(1, 20))\n#+end_src\n\n** Histogram of roll results\nWe can have a very quick glance at what this results in by creating a bargraph\nwith the resulting roll total on the x-axis, and the probability of getting\nthat roll on the y-axis.\n\n#+begin_src R\n  make_hist \u003c- function(vec) {\n    dat \u003c- tibble(Result = vec)\n\n    pl \u003c- dat %\u003e%\n      ggplot(aes(Result)) +\n      geom_bar(aes(y = stat(count) / sum(stat(count)))) +\n      labs(y = \"Probability\")\n\n    pl\n  }\n#+end_src\n\n#+RESULTS:\n\n#+begin_src R :results graphics file :file 1d20hist.png :height 300\n  replicate(1e5, roll_dice(1, 20)) %\u003e%\n    make_hist() + labs(title = \"Histogram of 1e5 simulations of a d20 roll\")\n#+end_src\n\n#+RESULTS:\n\n[[file:1d20hist.png]]\n\nThis seems ok.\n\nWhat happens if we roll 2d4?\n#+begin_src R :results graphics file :file 2d4hist.png :height 300\n  replicate(1e5, roll_dice(2, 4)) %\u003e%\n    make_hist() + labs(title = \"Histogram of 1e5 simulations of 2d4 rolls\")\n#+end_src\n\n#+RESULTS:\n\n[[file:2d4hist.png]]\n\nOr 2d6?\n#+begin_src R :results graphics file :file 2d6hist.png :height 300\n  replicate(1e5, roll_dice(2, 6)) %\u003e%\n    make_hist() + labs(title = \"Histogram of 1e5 simulations of 2d6 rolls\")\n#+end_src\n\n#+RESULTS:\n\n[[file:2d6hist.png]]\n\n* Tidying of the table\nHere I've quickly (manually) tidied the table up for use in R.\n\n#+TBLNAME: races\n| Race            | Base Height | bh_f | bh_i | Height Modifier | n_height | d_height | Base Weight | Weight Modifier | n_weight | d_weight |\n|-----------------+-------------+------+------+-----------------+----------+----------+-------------+-----------------+----------+----------|\n| Human           | \"4\\'8\\\"\"    |    4 |    8 |           +2d10 |        2 |       10 |         110 | ×(2d4) lb.      |        2 |        4 |\n| Dwarf, hill     | \"3\\'8\\\"\"    |    3 |    8 |            +2d4 |        2 |        4 |         115 | ×(2d6) lb.      |        2 |        6 |\n| Dwarf, mountain | \"4\\'\"       |    4 |    0 |            +2d4 |        2 |        4 |         130 | ×(2d6) lb.      |        2 |        6 |\n| Elf,  high      | \"4\\'6\\\"\"    |    4 |    6 |           +2d10 |        2 |       10 |          90 | ×(1d4) lb.      |        1 |        4 |\n| Elf, wood       | \"4\\'6\\\"\"    |    4 |    6 |           +2d10 |        2 |       10 |         100 | ×(1d4) lb.      |        1 |        4 |\n| Elf,  drow      | \"4\\'5\\\"\"    |    4 |    5 |            +2d6 |        2 |        6 |          75 | ×(1d6) lb.      |        1 |        6 |\n| Halfling        | \"2\\'7\\\"\"    |    2 |    7 |            +2d4 |        2 |        4 |          35 | ×1 lb.          |          |          |\n| Dragonborn      | \"5\\'6\\\"\"    |    5 |    6 |            +2d8 |        2 |        8 |         175 | ×(2d6) lb.      |        2 |        6 |\n| Gnome           | \"2\\'11\\\"\"   |    2 |   11 |            +2d4 |        2 |        4 |          35 | ×1 lb.          |          |          |\n| Half-elf        | \"4\\'9\\\"\"    |    4 |    9 |            +2d8 |        2 |        8 |         110 | ×(2d4) lb.      |        2 |        4 |\n| Half-orc        | \"4\\'10\\\"\"   |    4 |   10 |           +2d10 |        2 |       10 |         140 | ×(2d6) lb.      |        2 |        6 |\n| Tiefling        | \"4\\'9\\\"\"    |    4 |    9 |            +2d8 |        2 |        8 |         110 | ×(2d4) lb.      |        2 |        4 |\n\nNOTE: I'm using [[emacs'][emacs]] with [[https://ess.r-project.org/][ess]] in [[https://orgmode.org/][org-mode]], and this allows me to name the\nsheet with ~#+TBLNAME:~ so that I can pass it into the header argument of a\ncodeblock later on with ~:var dat=races~. If you don't use emacs/org-mode but,\ne.g. RStudio with RMarkdown, it's easier to save the table as a csv first.\n\n** Sensible units\nNow it's time to read in the data and do some simulations!\n\nWe also convert everything into sensible units.\n\n#+begin_src R :var dat=races :colnames yes\n  races \u003c- dat %\u003e%\n    mutate(base_cm = bh_f * 30.48 + bh_i * 2.54,\n           base_kg = Base.Weight * 0.4535923) %\u003e%\n    as_tibble()\n#+end_src\n\n| Race            | Base.Height | bh_f | bh_i | Height.Modifier | n_height | d_height | Base.Weight | Weight.Modifier | n_weight | d_weight | base_cm |    base_kg |\n|-----------------+-------------+------+------+-----------------+----------+----------+-------------+-----------------+----------+----------+---------+------------|\n| Human           |           4 |    4 |    8 |           +2d10 |        2 |       10 |         110 | ×(2d4) lb.      |        2 |        4 |  142.24 |  49.895153 |\n| Dwarf, hill     |           3 |    3 |    8 |            +2d4 |        2 |        4 |         115 | ×(2d6) lb.      |        2 |        6 |  111.76 | 52.1631145 |\n| Dwarf, mountain |           4 |    4 |    0 |            +2d4 |        2 |        4 |         130 | ×(2d6) lb.      |        2 |        6 |  121.92 |  58.966999 |\n| Elf,  high      |           4 |    4 |    6 |           +2d10 |        2 |       10 |          90 | ×(1d4) lb.      |        1 |        4 |  137.16 |  40.823307 |\n| Elf, wood       |           4 |    4 |    6 |           +2d10 |        2 |       10 |         100 | ×(1d4) lb.      |        1 |        4 |  137.16 |   45.35923 |\n| Elf,  drow      |           4 |    4 |    5 |            +2d6 |        2 |        6 |          75 | ×(1d6) lb.      |        1 |        6 |  134.62 | 34.0194225 |\n| Halfling        |           2 |    2 |    7 |            +2d4 |        2 |        4 |          35 | ×1 lb.          |      nil |      nil |   78.74 | 15.8757305 |\n| Dragonborn      |           5 |    5 |    6 |            +2d8 |        2 |        8 |         175 | ×(2d6) lb.      |        2 |        6 |  167.64 | 79.3786525 |\n| Gnome           |           2 |    2 |   11 |            +2d4 |        2 |        4 |          35 | ×1 lb.          |      nil |      nil |    88.9 | 15.8757305 |\n| Half-elf        |           4 |    4 |    9 |            +2d8 |        2 |        8 |         110 | ×(2d4) lb.      |        2 |        4 |  144.78 |  49.895153 |\n| Half-orc        |           4 |    4 |   10 |           +2d10 |        2 |       10 |         140 | ×(2d6) lb.      |        2 |        6 |  147.32 |  63.502922 |\n| Tiefling        |           4 |    4 |    9 |            +2d8 |        2 |        8 |         110 | ×(2d4) lb.      |        2 |        4 |  144.78 |  49.895153 |\n\n* Simulate weight and height dice-rolls\nNow let's simulate some dice-rolls! We're creating some new list-columns, using\n~purrr::map~ and then unnesting them for easier calculations.\n\nFirst we define a new function that replicates the analysis:\n#+begin_src R\n  rep_dice \u003c- function(n, d, n_sim = 1e5) {\n    replicate(n_sim, roll_dice(n, d))\n  }\n#+end_src\n\n#+RESULTS:\n\nAnd then we run it for all the Races.\n\n#+begin_src R\n  races_stats  \u003c- races %\u003e%\n    mutate(height_rolls = map2(n_height, d_height, possibly(rep_dice, otherwise = 1)),\n           weight_rolls = map2(n_weight, d_weight, possibly(rep_dice, otherwise = 1))) %\u003e%\n    unnest(cols = c(height_rolls, weight_rolls)) %\u003e%\n    mutate(height = base_cm + height_rolls * 2.54,  # convert roll from inches to cm\n           weight = base_kg + height_rolls * weight_rolls * 0.4535923)  # convert rolls from lbs to kg\n#+end_src\n\n#+RESULTS:\n\nNote the ~tidyr::possibly~ here, which allows me to ignore the weight rolls for\nthe Halfling and Gnome and instead set their value to 1.\n\n* Averages\nThen we calculate median height and weight and append them back to the original data.\n\nWe also convert Race to a factor, which is sorted by the average height.\n\n#+begin_src R :colnames yes\n  races_sum \u003c- races_stats %\u003e%\n    group_by(Race) %\u003e%\n    summarize(height_med = median(height),\n              weight_med = median(weight)) %\u003e%\n    left_join(races, by = \"Race\") %\u003e%\n    arrange(height_med) %\u003e%\n    mutate(Race = factor(Race, levels = Race),\n           lab_kg = paste0(Height.Modifier, Weight.Modifier))\n#+end_src\n\n#+RESULTS:\n\n| Race            | height_med |  weight_med | Base.Height | bh_f | bh_i | Height.Modifier | n_height | d_height | Base.Weight | Weight.Modifier | n_weight | d_weight | base_cm |    base_kg | lab_kg          |\n|-----------------+------------+-------------+-------------+------+------+-----------------+----------+----------+-------------+-----------------+----------+----------+---------+------------+-----------------|\n| Halfling        |      91.44 |   18.143692 |           2 |    2 |    7 |            +2d4 |        2 |        4 |          35 | ×1 lb.          |      nil |      nil |   78.74 | 15.8757305 | +2d4×1 lb.      |\n| Gnome           |      101.6 |   18.143692 |           2 |    2 |   11 |            +2d4 |        2 |        4 |          35 | ×1 lb.          |      nil |      nil |    88.9 | 15.8757305 | +2d4×1 lb.      |\n| Dwarf, hill     |     124.46 |  66.6780681 |           3 |    3 |    8 |            +2d4 |        2 |        4 |         115 | ×(2d6) lb.      |        2 |        6 |  111.76 | 52.1631145 | +2d4×(2d6) lb.  |\n| Dwarf, mountain |     134.62 |  73.9355449 |           4 |    4 |    0 |            +2d4 |        2 |        4 |         130 | ×(2d6) lb.      |        2 |        6 |  121.92 |  58.966999 | +2d4×(2d6) lb.  |\n| Elf,  drow      |      152.4 |  43.5448608 |           4 |    4 |    5 |            +2d6 |        2 |        6 |          75 | ×(1d6) lb.      |        1 |        6 |  134.62 | 34.0194225 | +2d6×(1d6) lb.  |\n| Elf,  high      |      165.1 |  51.7095222 |           4 |    4 |    6 |           +2d10 |        2 |       10 |          90 | ×(1d4) lb.      |        1 |        4 |  137.16 |  40.823307 | +2d10×(1d4) lb. |\n| Elf, wood       |      165.1 |  56.2454452 |           4 |    4 |    6 |           +2d10 |        2 |       10 |         100 | ×(1d4) lb.      |        1 |        4 |  137.16 |   45.35923 | +2d10×(1d4) lb. |\n| Half-elf        |     167.64 |  68.9460296 |           4 |    4 |    9 |            +2d8 |        2 |        8 |         110 | ×(2d4) lb.      |        2 |        4 |  144.78 |  49.895153 | +2d8×(2d4) lb.  |\n| Tiefling        |     167.64 |  68.9460296 |           4 |    4 |    9 |            +2d8 |        2 |        8 |         110 | ×(2d4) lb.      |        2 |        4 |  144.78 |  49.895153 | +2d8×(2d4) lb.  |\n| Human           |     170.18 |  73.4819526 |           4 |    4 |    8 |           +2d10 |        2 |       10 |         110 | ×(2d4) lb.      |        2 |        4 |  142.24 |  49.895153 | +2d10×(2d4) lb. |\n| Half-orc        |     175.26 |  96.1615676 |           4 |    4 |   10 |           +2d10 |        2 |       10 |         140 | ×(2d6) lb.      |        2 |        6 |  147.32 |  63.502922 | +2d10×(2d6) lb. |\n| Dragonborn      |      190.5 | 106.5941905 |           5 |    5 |    6 |            +2d8 |        2 |        8 |         175 | ×(2d6) lb.      |        2 |        6 |  167.64 | 79.3786525 | +2d8×(2d6) lb.  |\n\n* Plot of Heights\nGreat! Now let's create a plot of the average height by race, with a violin\nplot to illustrate the distribution.\n\nI further annotate the plot with base height points and which modifier was used\nto get the distribution of heights.\n\n#+begin_src R :results graphics file :file raceheights.png :width 600\n  pl_h \u003c- races_sum %\u003e%\n    ggplot(aes(x = Race, y = height_med)) +\n    geom_bar(stat=\"identity\", alpha = .5) +\n    geom_violin(aes(y = height), bw = 2.54, scale= \"width\", colour = NA, fill = \"cornflowerblue\", alpha = .8, data = races_stats) +\n    geom_text(aes(y = base_cm + 2, hjust = 0, label = paste0(Height.Modifier, \"'\")), angle = 90) +\n    geom_point(aes(y = base_cm)) +\n    ylim(c(0, NA)) +\n    labs(y = \"Height (cm)\") # +\n    ## coord_flip()\n  pl_h\n#+end_src\n\n#+RESULTS:\n\n[[file:raceheights.png]]\n\nNotice the ~bw~ argument to ~geom_violin~: this is used to adjust the smoothing\nkernel a bit. I've used the value to convert my units in cm back to inches,\nbecause with lower values we get artificial jittering.\n\n* Plot of Weights\nNow we do the same for weight:\n#+begin_src R :results graphics file :file raceweights.png :width 600\n  pl_w \u003c- races_sum %\u003e%\n    ggplot(aes(x = Race, y = weight_med)) +\n    geom_bar(stat=\"identity\", alpha = .5) +\n    geom_violin(aes(y = weight), bw = 1 / 0.4535923, scale= \"width\", colour = NA, fill = \"cornflowerblue\", alpha = .8, data = races_stats) +\n    geom_point(aes(y = base_kg)) +\n    geom_text(aes(y = base_kg, label = lab_kg), hjust = -.05, angle = 90) +\n    ylim(c(0, NA)) +\n    labs(y = \"Weight (kg)\") # +\n  pl_w\n#+end_src\n\n#+RESULTS:\n\n[[file:raceweights.png]]\n\n(Again, we set ~bw~ to the value to convert kg to lbs.)\n\n* Combined Plot\nTo ultimately combine the two into one figure using ~patchwork~.\n\n#+begin_src R :results graphics file :file races_stats.png :width 600 :height 600\n  library(patchwork)\n  pl \u003c- (pl_h + labs(title = \"D\u0026D 5e Race size and weight distributions based on rolls\") \u0026\n         theme(axis.title.x = element_blank(),\n               axis.text.x = element_blank(),\n               axis.ticks.x = element_blank())) /\n    (pl_w \u0026 theme(axis.text.x = element_text(size = 10, angle = 30, hjust = 1, face = \"bold\")))\n  pl\n#+end_src\n\n#+RESULTS:\n\n[[file:races_stats.png]]\n\n* Body Mass Index\nOkay now for some more mental picturing, let's calculate the average BMI for\nthese races. BMI is a troublesome indicator for humans alone already, and will\ncertainly be wrong for the heavy-boned dwarfs, but it's nice to give us a\nlittle bit more of a mental picture.\n\nI found these BMI categories [[https://en.wikipedia.org/wiki/Body_mass_index#Categories][on the WikiPedia article on BMI]].\n\n#+TBLNAME: bmi\n| category                              | from |   to |\n|---------------------------------------+------+------|\n| Very severely underweight             |      |   15 |\n| Severely underweight                  |   15 |   16 |\n| Underweight                           |   16 | 18.5 |\n| Normal (healthy weight)               | 18.5 |   25 |\n| Overweight                            |   25 |   30 |\n| Obese Class I (Moderately obese)      |   30 |   35 |\n| Obese Class II (Severely obese)       |   35 |   40 |\n| Obese Class III (Very severely obese) |   40 |      |\n\n#+begin_src R :var categories=bmi :results graphics file :file races_bmi.png :width 600\n  # clean up the categories\n  cat \u003c- categories %\u003e%\n    mutate(from = ifelse(is.na(from), -Inf, from),\n           to = ifelse(is.na(to), Inf, to),\n           category = factor(category, levels = rev(category), ordered = TRUE))\n\n  # calculate average bmi\n  bmi_avg \u003c- races_sum %\u003e% mutate(bmi = weight_med / (height_med/100)^2)\n\n  # calculate all bmi's\n  bmi \u003c- races_stats %\u003e%\n    mutate(bmi = weight / (height / 100)^2)\n\n  # plot them\n  bmi_avg %\u003e%\n    ggplot(aes(x = Race, y = bmi)) +\n    # annotate the categories\n    geom_point() + # It looks like this is necessary to keep the factor levels in\n                   # the right order\n    geom_rect(aes(xmin = -Inf, xmax = Inf,\n                  ymin = from,\n                  ymax = to,\n                  fill = category),\n              inherit.aes = FALSE, data = cat) +\n    scale_fill_brewer(palette = \"RdBu\") +\n    geom_violin(data = bmi, bw = .8, fill = \"gray\", draw_quantiles = c(.25, .5, .75)) +\n    labs(fill = \"BMI category\\nif they would have been human\", y = \"BMI (kg /\"~m^2*\")\") +\n    geom_point() +\n    theme(axis.text.x = element_text(angle = 30, hjust = 1, face = \"bold\"))\n#+end_src\n\n#+RESULTS:\n\n[[file:races_bmi.png]]\n\nSo most dwarves are, according to the human BMI, very severely obese 😉.\n\nAnd that's it! A quick dive into some simulations with R! Any feedback on how\nto improve this workflow is welcome.\n* Other Dice roll simulations\n** Rolling With Advantage\nIn D\u0026D-land you sometimes get to roll with advantage. This means that you roll\na d20 twice, and take the higher of the two. I also wanted to study what\nhappens when we do that, so we add a new function!\n\n#+begin_src R\n  roll_with_advantage \u003c- function(d = 20) {\n    max(sample(seq_len(d), size = 2, replace = TRUE))\n  }\n#+end_src\n\n#+RESULTS:\n\n#+begin_src R :results graphics file :file d20_advantage.png :height 300\n  replicate(1e5, roll_with_advantage(20)) %\u003e%\n    make_hist() + labs(title = \"Histogram of 1e5 simulations of d20 rolls with advantage\")\n#+end_src\n\n#+RESULTS:\n\n[[file:d20_advantage.png]]\n\n** Rolling With Disadvantage\nWhen you're particularly unskilled at something, your DM may ask you to roll\nwith disadvantage. This means: roll 2d20 and take the lower.\n\n#+begin_src R\n  roll_with_disadvantage \u003c- function(d = 20) {\n    min(sample(seq_len(d), size = 2, replace = TRUE))\n  }\n#+end_src\n\n#+begin_src R :results graphics file :file d20_disadvantage.png :height 300\n  replicate(1e5, roll_with_disadvantage(20)) %\u003e%\n    make_hist() + labs(title = \"Histogram of 1e5 simulations of d20 rolls with disadvantage\")\n#+end_src\n\n#+RESULTS:\n\n[[file:d20_disadvantage.png]]\n\n** Rolling for Stats\nSome DM's let you roll for stats. The common way of doing so is by rolling 4d6\nand dropping the lower. Then repeating this 6 times.\n\nWhen I did this the first time, I got some pretty high rolls and I wondered\nwhat the odds are. So again, time to simulate!\n\n#+begin_src R :results none\n   stat_roll \u003c- function() {\n     # roll 4d6, drop the lowest\n     rolls \u003c- sample(1:6, 4, replace = TRUE)\n     # use the highest two values only\n     sum(sort(rolls)[-1])\n   }\n#+end_src\n\nSo if you want to roll for stats without rolling any dice (BOOOOO!):\n#+begin_src R\n  replicate(6, stat_roll())\n#+end_src\n\n#+RESULTS:\n| 15 |\n| 11 |\n| 13 |\n| 16 |\n| 11 |\n| 11 |\n\ncomparison to pointbuy and standard array\n#+begin_src R :colnames \"yes\"\n  comparison \u003c- tribble( ~ name, ~ array,\n                        \"standard\", c(8, 10, 12, 13, 14, 15),\n                        \"pointbuy 3 high 3 low\", c(15, 15, 15, 8, 8, 8),\n                        \"pointbuy all medium\", c(13, 13, 13, 12, 12, 12),\n                        ) %\u003e%\n    mutate(sum = map_dbl(array, sum))\n  comparison %\u003e% select(-array)\n#+end_src\n\n#+RESULTS:\n| name                  | sum |\n|-----------------------+-----|\n| standard              |  72 |\n| pointbuy 3 high 3 low |  69 |\n| pointbuy all medium   |  75 |\n\nLet's visualize the likelihood of all the total values:\n#+begin_src R :results graphics file :file stat_rolls.png :height 300\n  replicate(1e5, stat_roll()) %\u003e%\n    make_hist() + labs(title = \"Rolling for stats using the roll 4d6 drop lowest method\") +\n    geom_bar(aes(x = array, y = stat(count) / sum(stat(count)), group = name, fill = name),\n             data = comparison %\u003e% unnest(array),\n             width = .5,\n             alpha = .6)\n#+end_src\n\n#+RESULTS:\n[[file:stat_rolls.png]]\n\n[[file:stat_rolls.png]]\n#+begin_src R :colnames yes\n  sr \u003c- replicate(1e5, stat_roll()) %\u003e%\n    as_tibble() %\u003e%\n    mutate(name = \"roll for stats\") %\u003e%\n    bind_rows(comparison %\u003e% unnest(array) %\u003e% rename(value=array)) %\u003e%\n    group_by(name) %\u003e%\n    summarize(min = min(value),\n              mean = mean(value),\n              median = median(value),\n              max = max(value))\n#+end_src\n\n#+RESULTS:\n| name                  | min |     mean | median | max |\n|-----------------------+-----+----------+--------+-----|\n| pointbuy 3 high 3 low |   8 |     11.5 |   11.5 |  15 |\n| pointbuy all medium   |  12 |     12.5 |   12.5 |  13 |\n| roll for stats        |   3 | 12.24506 |     12 |  18 |\n| standard              |   8 |       12 |   12.5 |  15 |\n\nlet's calculate when the sum of ability scores is highest\n\nsimulate 1e5 sets of 6 stats, take the sum\n#+begin_src R\n  sr \u003c- replicate(1e5, sum(replicate(6, stat_roll()))) %\u003e%\n    as_tibble()\n#+end_src\n\n#+begin_src R :results output graphics file :file stat_sum_vs_stdarray.png :width 700 :height 300\n  make_hist(sr$value) +\n   geom_vline(aes(xintercept = sum, colour = name), data = comparison, size = 2, alpha = .5)\n#+end_src\n\n#+RESULTS:\n\n[[file:stat_sum_vs_stdarray.png]]\ndo some calculations\n#+begin_src R\n  sb \u003c- sr %\u003e%\n    mutate(\n      rbs = value \u003e comparison$sum[[1]],\n      rbp1 = value \u003e comparison$sum[[2]],\n      rbp2 = value \u003e comparison$sum[[3]],\n    )\n#+end_src\n\nhow often is rolling better than pointbuy or standard array?\n#+begin_src R :colnames \"yes\"\n  tribble(~ name, ~ `P roll better than X`,\n    \"standardarray\", sum(sb$rbs) / nrow(sb),\n    \"pointbuy 3 high 3 low\", sum(sb$rbp1) / nrow(sb),\n    \"pointbuy all medium\", sum(sb$rbp2) / nrow(sb)\n  )\n#+end_src\n\n#+RESULTS:\n| name                  | P roll better than X |\n|-----------------------+----------------------|\n| standardarray         |              0.56215 |\n| pointbuy 3 high 3 low |              0.71725 |\n| pointbuy all medium   |              0.39306 |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaphir%2Fdndraces","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjaphir%2Fdndraces","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaphir%2Fdndraces/lists"}