# read the JSON file, convert to data frame, and unnest some columns
read_json(list.files()[grep(".jscor", list.files())], simplifyVector = TRUE) |>
as.data.frame() |>
unnest(cols = c(games_attributes.sixths_attributes,
games_attributes.final_attributes),
names_repair = "universal") |>
# keep only regular difficulty games
filter(!games_attributes.play_type %in% c("toc", "masters")) |>
# keep only the columns we need, add a round indicator, pivot to one row per
# clue instead of per category, and calculate each clue's contribution to the
# Coryat score
select(2, 10:14) |>
mutate(round = rep(c(rep(1, 6), rep(2, 6)), n()/12)) |>
pivot_longer(cols = 2:6, names_to = "clue", names_prefix = "result") |>
rename(result = value) |>
mutate(result = recode(result, `1` = -1, `3` = 1, `7` = 1, .default = 0),
score = round * as.numeric(clue) * 200 * result) |>
# group by game, add up the total score, sort by game order, and throw away
# the time stamps
group_by(games_attributes.date_played) |>
summarize(sum(score)) |>
arrange(1) |>
mutate(game = row_number()) |>
select(-1) |>
rename(score = `sum(score)`) ->
# store the results
coryat_scoresCoryat Scores
Introduction
Coryat Scores
The Coryat score is a way of measuring one’s performance when playing along with Jeopardy! at home. It is named after musician, philosopher of physics, and two-day Jeopardy! champion Karl Coryat.
A player’s Coryat score is the total value of clues answered correctly, minus that of those answered incorrectly, counting correctly-answered Daily Doubles according to their board position and ignoring Final Jeopardy! and any incorrectly-answered Daily Doubles.
Thus the Coryat score is a measure of one’s knowledge of the trivia material used on the show, ignoring other strategic elements like wagering.
J! Scorer
J! Scorer is a convenient way to record games and determine one’s Coryat score. The site was created by two-time TV game show contestant Steve McClellan and has a public GitHub repository.
J! Scorer users can download a JSON file of their games.
Data
It took a little effort to reverse-engineer the format of the files produced by J! Scorer. (I did this before finding the above-mentioned GitHub repo.) I used jsonlite::read_json plus a little trial and error. Then it’s just a matter of straightforward data transformations using dplyr.
Table
coryat_scores |>
kable(caption = "My Jeopardy! Coryat Scores") |>
scroll_box(height = "5in")| score | game |
|---|---|
| 17800 | 1 |
| 10200 | 2 |
| 16000 | 3 |
| 12400 | 4 |
| 16400 | 5 |
| 16800 | 6 |
| 18200 | 7 |
| 16000 | 8 |
| 16000 | 9 |
| 18400 | 10 |
| 18600 | 11 |
| 22600 | 12 |
| 25000 | 13 |
| 17600 | 14 |
| 19600 | 15 |
| 24000 | 16 |
| 19600 | 17 |
| 18600 | 18 |
| 20200 | 19 |
| 17800 | 20 |
| 26800 | 21 |
| 21000 | 22 |
| 20800 | 23 |
| 19600 | 24 |
| 17800 | 25 |
| 18200 | 26 |
| 16600 | 27 |
| 25400 | 28 |
| 4000 | 29 |
| 27800 | 30 |
| 18600 | 31 |
| 27000 | 32 |
| 24200 | 33 |
| 21600 | 34 |
| 22600 | 35 |
| 21400 | 36 |
| 18800 | 37 |
| 11800 | 38 |
| 23200 | 39 |
| 28000 | 40 |
| 24400 | 41 |
| 14800 | 42 |
| 17000 | 43 |
| 16200 | 44 |
| 27800 | 45 |
| 18000 | 46 |
| 18200 | 47 |
| 28600 | 48 |
| 22600 | 49 |
| 13600 | 50 |
| 18000 | 51 |
| 16200 | 52 |
| 18400 | 53 |
| 17200 | 54 |
| 24200 | 55 |
| 19400 | 56 |
| 20400 | 57 |
| 21000 | 58 |
| 16800 | 59 |
| 19800 | 60 |
| 24800 | 61 |
| 19600 | 62 |
| 20400 | 63 |
| 24400 | 64 |
| 18600 | 65 |
| 17200 | 66 |
| 23400 | 67 |
| 26600 | 68 |
| 24800 | 69 |
| 21600 | 70 |
| 24600 | 71 |
| 21600 | 72 |
| 17600 | 73 |
| 12000 | 74 |
| 21600 | 75 |
| 17800 | 76 |
| 23600 | 77 |
| 21800 | 78 |
| 22400 | 79 |
| 18800 | 80 |
| 28400 | 81 |
| 16800 | 82 |
| 21400 | 83 |
| 5600 | 84 |
| 15000 | 85 |
| 19400 | 86 |
| 14200 | 87 |
| 16400 | 88 |
| 23200 | 89 |
| 20400 | 90 |
| 22600 | 91 |
| 18200 | 92 |
| 14000 | 93 |
| 17000 | 94 |
| 19000 | 95 |
| 18400 | 96 |
| 18400 | 97 |
| 26200 | 98 |
| 26400 | 99 |
| 22800 | 100 |
| 33200 | 101 |
| 19800 | 102 |
| 26400 | 103 |
| 18600 | 104 |
| 13600 | 105 |
| 22200 | 106 |
| 24800 | 107 |
| 27400 | 108 |
| 13400 | 109 |
| 14000 | 110 |
| 13600 | 111 |
| 15200 | 112 |
| 12000 | 113 |
| 20800 | 114 |
| 26200 | 115 |
| 16800 | 116 |
| 20400 | 117 |
| 28800 | 118 |
| 32400 | 119 |
| 10400 | 120 |
| 20000 | 121 |
| 12400 | 122 |
| 11400 | 123 |
| 20600 | 124 |
| 19800 | 125 |
| 18200 | 126 |
| 20200 | 127 |
| 20000 | 128 |
| 15800 | 129 |
| 16400 | 130 |
| 17000 | 131 |
| 22400 | 132 |
| 15600 | 133 |
| 20200 | 134 |
| 20600 | 135 |
| 18600 | 136 |
| 19400 | 137 |
| 22800 | 138 |
| 13400 | 139 |
| 18800 | 140 |
| 15200 | 141 |
| 20000 | 142 |
| 14000 | 143 |
| 25200 | 144 |
| 25800 | 145 |
| 20000 | 146 |
| 28600 | 147 |
| 16200 | 148 |
| 16400 | 149 |
| 18000 | 150 |
| 20200 | 151 |
| 18600 | 152 |
| 19800 | 153 |
| 13000 | 154 |
| 10200 | 155 |
| 15600 | 156 |
| 14400 | 157 |
| 25200 | 158 |
| 26800 | 159 |
| 14800 | 160 |
| 21400 | 161 |
| 16800 | 162 |
| 19200 | 163 |
| 17600 | 164 |
| 20800 | 165 |
| 25000 | 166 |
| 27400 | 167 |
| 20800 | 168 |
| 20000 | 169 |
| 26800 | 170 |
| 22800 | 171 |
| 33800 | 172 |
| 16600 | 173 |
| 20800 | 174 |
| 20800 | 175 |
| 27800 | 176 |
| 19200 | 177 |
| 16200 | 178 |
Average Score
An average score of around $25,000 is considered appropriate for prospective contestants.
# calculate my mean score
coryat_scores |> pull(score) |> mean() |> round()[1] 19787
Clearly, I have some studying to do before I consider trying to compete on the show.
Visualization
We can create a histogram showing the distribution of my scores and a line chart showing the evolution of my scores over time using ggplot2.
Code
# main plot
(coryat_scores |>
ggplot() +
# aesthetic mapping
aes(x = game, y = score) +
# visual elements representing the data
geom_line(colour = "#b4b4b4") +
geom_smooth(se = FALSE, colour = "black") +
geom_hline(yintercept = 25000, linetype = "dashed") +
geom_point(colour = "#dc2828") +
# scales
scale_y_continuous(labels = scales::label_dollar(),
n.breaks = 6,
limits = c(0, 30000)) +
scale_x_continuous(expand = c(0, 0), breaks = NULL) +
# labels
labs(title = "My Jeopardy! Coryat Scores",
subtitle = "Trend and distribution",
x = "",
y = "Score",
caption = "Dashed black line indicates target average of $25,000.") +
# theming
theme_bw()) |>
# add the marginal histogram
ggMarginal(type = "histogram", margins = "y", fill = "#b4b4b4")Code
coryat_scores |>
arrange(score) |>
ggplot(aes(x = score, y = cumsum(score)/sum(score))) +
geom_line(size = 2, colour = "#dc2828") +
scale_x_continuous(labels = scales::label_dollar(),
n.breaks = 6,
expand = c(0, 0)) +
scale_y_continuous(labels = scales::label_percent(), expand = c(0, 0)) +
labs(title = "My Jeopardy! Coryat Scores",
subtitle = "Cumulative distribution",
x = "Score",
y = "") +
theme_bw()
