One natural thing to do is look at the number of spells each class has in common with another class. Here I calculate the intersection of spells between each each class. There is probably more elegant code to do this, but this was the fastest way I could figure to do this.
One way to transform this is to transform the intersection into a similarity metric. I normalize by the total number of spells so that the numbers range from 0 to 1. I then need to transform the similarity matrix into a dissimilarity matrix. I subtract the matrix from 1 to do this, using the as.dist() function to transform the matrix into a distance matrix.
Code
sim <-function(x, y){ len <-length(x) sim_out <-sum(as.numeric(x & y)) / len sim_out}sim_matrix <-matrix(data=NA, nrow =length(dd_classes), length(dd_classes),dimnames=list(dd_classes, dd_classes) )for(cl in dd_classes){for(c2 in dd_classes){ sim_matrix[cl, c2] <-sim(spells_class[[cl]], spells_class[[c2]]) }}sim_matrix <-1-as.dist(sim_matrix)sim_matrix
Here is the hierarchical clustering using this similarity metric. As you can see, Sorcerer and Wizard are the closest in terms of spells in common. There are two distinct clusters of similarity: [sorcerer, wizard, warlock, bard] and [druid, cleric, ranger, paladin].
Code
plot(hclust(sim_matrix))
Finally, we pass our calculated data.frames and matrices onto Observable by using the ojs_define() function:
Code
ojs_define(sim_matrix2, spells_long, spell_count)
Visualizing Spells (in Observable)
The first thing I need to do is transpose the data objects from R. This is because Observable expects the data to be in this transposed format.
Using the breakdown of spell counts, I plot the number of spells in each spellcasting school for each spellcasting class.
Code
Plot.plot({title:"Spells for each class by school",subtitle:"Wizards can learn the most spells",color: {scheme:"Spectral",legend:true,width:340,label:"School"},marks: [ Plot.barY(spell_c, {x:"class",y:"n",fill:"school",sort: {color:null,x:"y"}}) ]})
Finally, using the intersection of spells between classes, I build an intersection matrix in Observable.
Code
Plot.plot({title:"Spellcasting Classes compared by spells in common",marginLeft:100,label:null,color: { scheme:"RdYlBu",pivot:0,legend:false,label:"Num Common Spells",domain: [0,200] },marks: [ Plot.cell(sim_t, { x:"Var1",y:"Var2",fill:"Freq" }), Plot.text(sim_t, {x:"Var1",y:"Var2",text:"Freq",fill: (d) => (Math.abs(d.Freq) >80?"white":"black") }) ]})
Citation
BibTeX citation:
@online{laderas2024,
author = {Laderas, Ted},
title = {Dungeons and {Dragons} {Spells}},
date = {2024-12-20},
url = {https://laderast.github.io/articles/spells/},
langid = {en}
}