Scope / provenance / decisions

Methods and sources

The sources, filters and uncertainty behind the story. These are descriptive snapshots, not measures of individual behavior or a representative survey.

Populations and provenance

Large source: Maxime Bonnin, DnD characters (Kaggle), attributed to D&D Beyond. Kaggle version 3 was updated 2022-11-20; its data card lists ODC Attribution 1.0. The repository archive contains 1,204,252 rows, 23 columns and 511,272 distinct character IDs. The uncompressed CSV is 289,086,985 bytes. Modification timestamps span 2018-06-29 to 2022-11-16; 84.41% of raw rows have a 2018 modification date. These are last-modified timestamps, not collection dates. The 2025 ZIP timestamp is a packaging date. Neither a maximum ID near two million nor older “2M” descriptions establishes two million observations. The original extraction and completeness cannot be independently reconstructed from the repository.

Small source: B. O. Mancarci, dnddata, verified at commit 68cbb7f177025edd564929c5d26f15f976ffcf0e (MIT license). The repository's cleaned CSV has 7,620 sheet records, all with file hashes matching upstream dnd_chars_all.tsv (10,894 rows). The hash set matches upstream first-record-per-name selection, but the cleaning script is unavailable. The upstream dnd_chars_unique.tsv contains 7,946 records under a different heuristic; 105 repository records lie outside that set. Hashed names and sheet hashes do not establish unique people or unique characters. Preserve the supplied cleaned population, count sheet-level presence, and disclose this selection limitation. Do not invent a crosswalk to large-source character IDs or pool the populations.

Small-source dates span 2018-04-06 to 2022-08-23. The upstream source flags dates before 2018-04-16 as unreliable (11 records overall, 3 in the selected classes). Dates are not used for chart inclusion. The 53 recorded country labels and 2,234 missing countries do not establish geographic representativeness; geography is outside this analysis.

Canonical character rule

  1. Load the full large schema, retaining the original bytes unchanged. Ignore the exported Unnamed: 0 index for comparisons. Parse numeric fields using the pinned pandas 2.2.3 high-precision CSV parser; collapse exact repeated records across all substantive columns. This is exact equality after parsing, not exact raw-text equality or a tolerance-based numeric match. This leaves 511,318 parsed record variants (692,934 repeated rows removed). Raw-string comparison instead leaves 511,328 because ten tiny floating-point gold differences collapse under that parser. This behavior depends on the declared parser; it does not prove those original strings were identical.
  2. Parse date_modified as UTC. For each nonmissing char_id, retain its complete record at the latest valid timestamp. Do not combine fields or select the largest gold or level. Invalid timestamps are ineligible; IDs with no valid timestamp would be excluded and counted (none in the current input).
  3. There are 46 IDs with conflicting parsed variants. Latest timestamps resolve 41. Exclude all variants for the five tied IDs: five IDs (two in the six-class sample). Export order cannot establish correctness. Four of these tied IDs differ only in tiny floating-point gold values (four IDs); the fifth differs in inventory. All five IDs are excluded because their latest records cannot be resolved. The selected exclusions are one Sorcerer and one Warlock. The canonical population is 511,267 IDs, with 223,284 in the six selected starting classes.
  4. Apply the six-class filter after deduplication. The selected sample includes 11,597 multiclass records, assigned to their starting class; level bins use total character level. It is a chosen scope, not every magic-capable class. Never label this population as players.

An exclusion log is retained with the analysis. This site provides aggregate tables only, with no raw character or sheet records.

Chart definitions

  1. Class counts: one canonical character ID per starting class. Shares use all 223,284 six-class IDs.
  2. Levels: integer total levels 1–5, 6–10, 11–15 and 16–20, including level 1. Four out-of-range records are excluded. Each percentage divides by the six-class count in its own level group. This is a cross-sectional composition, not progression or a change in individual preferences.
  3. Race labels and 4. backgrounds: trim and collapse whitespace and combine case-only aliases. Preserve distinct subraces, UA and homebrew labels. The most frequent spelling is the display label; alphabetical order breaks spelling ties only. Show the top three occupied count ranks and retain all ties. Percentages use all class records, with known, missing and omitted counts in the coverage table. “Other” and missing categories are not silently treated as zero. Original labels remain in the immutable input.
  4. Subclasses: same normalization and ranking, plus combine Circle of the Land terrain options into Circle of the Land for Druids. The sunburst shows the top subclass ranks and other recorded subclasses among the 139,905 records with a recorded subclass. The 83,379 records without a subclass are accounted for separately, within the total of 223,284 character IDs. Ring areas use recorded subclasses only. Class breakdowns report both recorded-only and full-class percentages. Missingness can include characters below the relevant subclass level and incomplete sheets. Do not infer a choice to have no subclass or assume every character was eligible.
  5. Spells: the separate cleaned sample contributes 3,056 records whose justClass exactly matches one of the six classes; 459 multiclass rows across the full sample are excluded by this definition. Of the selected records, 1,089 lack lists. Parse name*level entries separated by |, retain named spells at numeric levels 0, 1 or 2, and count each (sheet row, spell name, spell level) once. Exclude 880 malformed or unknown-level tokens and 4,082 tokens at other levels; collapse 27 repeated within-sheet tokens, yielding 15,651 incidences. Retain all ties for most common spell. The displayed rate divides by sheets with at least one parseable spell at that level; hover and tables also show all-class sheet counts and shares. Missing lists are unknown. Lists are neither casts nor verified prepared/legal choices. Alternative upstream deduplication changes the Warlock level-2 leader, so winners are sample-specific.
  6. Gold: primary summary includes recorded zeros, 0 ≤ gold < 350,000; positive-only sensitivity uses 0 < gold < 350,000. Values exactly at the cap are excluded. The cap is inherited for comparability and is not a validated error threshold; it affects the result. Source metadata describes currency converted to gold-equivalent units. Show N, mean, median, quartiles and p95, plus excluded/missing/zero counts. Holdings do not measure spending, earnings or motives.
  7. Notes: primary notes_len ≥ 0, sensitivity notes_len > 0. Exclude negative values as invalid; 18 canonical selected records have negative lengths. Show N, mean, median, quartiles, p95 and positive-record percentage among valid lengths. The source does not establish the exact unit or derivation of this field. Do not label it words or characters or infer storytelling effort.

For gold and notes, recorded zeros may mean genuine absence or incomplete sheets. Both views are shown without claiming either estimates the underlying population without bias. Gold values at/above the cap and negative values are exclusions, not missing. The models do not adjust for level, campaign, time, rules version, access to paid content or selection. No causal or representative-population conclusions follow.

Reproduction and presentation

This site is generated by build_static_story.py using the analysis in analysis.py. The project includes a Streamlit app, an executed notebook and the source data. The main story presents three findings; the appendix contains all eight charts and accessible data tables. Every chart is pre-rendered HTML/CSS or SVG, so it remains readable without JavaScript. Browser code changes the theme and switches between precomputed subclass views; it never recalculates a statistic.

The independent CSV/Counter verifier checks canonical selection, chart counts, percentages, spell incidences and distribution summaries. Static export checks compare all downloaded chart tables with the authoritative Python output. Numeric parsing follows the declared pandas precision contract. Exact aggregate values are in the downloadable CSVs; displayed summaries round to two decimals.

Reading the Charts

Horizontal bars use direct labels so names and values remain visible. Notes use connected policy points, with both values and sample sizes in text. The interactive subclass sunburst uses recorded-only areas, retains other recorded categories, and reports missing records separately. Class views give both recorded-only and full-class percentages; the complete count tables remain available. No raw character IDs, names, notes, inventories or sheet hashes are shipped with this site.

The visual design uses midnight navy, slate, restrained gold details, classical serif headings, and class colors for class totals and subclasses. Serif headings use the local Georgia/Palatino system stack; body fonts retain their documented licenses. Self-hosted fonts are distributed with their SIL Open Font License notices in assets/fonts/. No unverified fantasy illustrations are used here. Credits from the source project are listed below; those illustrations are not reproduced on this site.

Licenses and illustration credits

The repository's BSD 2-Clause license applies to its software; it does not replace the data providers' or artists' terms. Kaggle lists ODC Attribution 1.0 for the large dataset; upstream dnddata carries MIT. The cited license statements do not establish the rights of every underlying contributor.

The following original illustration credits are retained for provenance only. These images are not included in this static site because image-specific reuse permission remains unverified.