Magic: the Gathering

The plot below visualizes the most popular cards in MTG tournament play over time, looking specifically at legacy events (where decks can be made up of essentially any cards out of the 20,000 that have been printed since 1993). I also have a shiny app which offers a more detailed breakdown of all major formats along with annotated timelines of major events over the last decade.

The chart shows the most popular cards during a given month, use the buttons below to navigate by months and years. For each card, there are two values being visualized:
  • Prevalence: the proportion of decks playing at least one copy of the card (bar length and ordering)
  • Average Copies: the average number of copies of the card played in decks playing at least one copy (bar color)

Tournament data from MTGTOP8.com, scraped with rvest.

Legacy metagame breakdown:

R Code (Data Wrangling)
library("tidyverse")
# Reading in data
# Data is scraped from MTGTOP8, details coming soon in a blog post
df_mtg <- read_csv(here::here("posts/2022-11-25-JavaScript-and-Quarto/data/legacy.csv")) 

# Helper to fix encoding of dates
## 2000.05 => 2000-01-01
## 2004.25 => 2000-04-01
fix_time <- function(t) {
  
  year <- floor(t)
  month <- round(20 * (t - year))
  
  lubridate::ymd(paste(year, month, "1"))
  
}

# We don't want basic lands in our viz
basics <- c("Plains", "Mountain", "Forest", "Island", "Swamp")

df_mtg <-
  df_mtg |>
  filter(!is.na(time)) |>
  # Only want to look at top-8 places
  filter(place %in% c(1, 2, 5, 8)) |> 
  mutate(time = fix_time(time)) |>
  filter(lubridate::year(time) >= 2011) |>
  # Don't want cards from sideboard 
  filter(!SB) |>
  filter(!card %in% basics) |>
  # Find count of each card at each timepoint, regardless of place (data is grouped by place)
  group_by(time, card) |>
  summarize(k = n(), copies = sum(copies), decks = sum(decks), total_decks = sum(total_decks), .groups = "drop_last") |> 
  mutate(prevalence = decks / total_decks, average = copies / decks) |>
  # Randomized pertubation to avoid ties return > 30 rows
  top_n(30, wt = (prevalence + rnorm(length(prevalence), sd = .000001))) |> 
  # Break ties w/ average
  arrange(desc(time), desc(prevalence), average) |>
  # For extensibiliity
  mutate(format = "legacy")

# Make `df_mtg` available in ojs chunks:
ojs_define(df_mtg = df_mtg)