# Remove citations and record notes from team names before matching locations.
clean_team_name <- function(x) {
x |>
str_replace_all("\\[[^]]*\\]", "") |>
str_replace("\\s*\\([^)]*\\).*$", "") |>
str_replace("\\.mw-parser-output.*$", "") |>
str_replace("†.*$", "") |>
str_squish()
}
# The NBA table lists both finalists, so the series score identifies the winner.
nba_finals <- championship_tables$NBA[[2]]
nba_championships <- nba_finals |>
mutate(
west_wins = as.integer(str_extract(Result, "^[0-9]+")),
east_wins = as.integer(str_extract(Result, "[0-9]+$")),
winning_team = if_else(
west_wins > east_wins,
`Western champion`,
`Eastern champion`
)
) |>
transmute(
league = "NBA",
season = as.integer(str_extract(Year, "[0-9]{4}")),
winning_team = clean_team_name(winning_team)
)
# The Super Bowl table already identifies the winning team.
# Its final letter is a conference marker added by the source page.
nfl_championships <- championship_tables$NFL[[2]] |>
transmute(
league = "NFL",
season = as.integer(str_match(`Date (season)`, "\\(([0-9]{4})")[, 2]),
winning_team = clean_team_name(`Winning team`) |>
str_replace("[ANan]$", "") |>
str_squish()
)
# The NHL table includes one cancelled season, which was not a championship.
nhl_championships <- championship_tables$NHL[[3]] |>
transmute(
league = "NHL",
season = as.integer(str_extract(Year, "[0-9]{4}")),
winning_team = clean_team_name(`Winning team`)
) |>
filter(!str_detect(winning_team, regex("cancelled", ignore_case = TRUE)))
# The MLB table includes seasons when the World Series was not played.
mlb_championships <- championship_tables$MLB[[1]] |>
transmute(
league = "MLB",
season = as.integer(str_extract(Year, "[0-9]{4}")),
winning_team = clean_team_name(`Winning team`)
) |>
filter(
winning_team != "",
!str_detect(
winning_team,
regex("not played|cancelled", ignore_case = TRUE)
)
)
championships_raw <- bind_rows(
nba_championships,
nfl_championships,
nhl_championships,
mlb_championships
) |>
mutate(
# Golden State played in Oakland for its earlier titles and in
# San Francisco after moving to Chase Center.
team_key = case_when(
winning_team == "Golden State Warriors" & season >= 2020 ~
"Golden State Warriors - San Francisco",
winning_team == "Golden State Warriors" ~
"Golden State Warriors - Oakland",
TRUE ~ winning_team
)
)
# This lookup keeps historical team locations separate from the API data.
# Canadian province codes are retained, but only U.S. state codes enter the map.
team_locations <- tribble(
~team_key, ~winning_city, ~state_abbr,
"Anaheim Angels", "Anaheim", "CA",
"Anaheim Ducks", "Anaheim", "CA",
"Arizona Diamondbacks", "Phoenix", "AZ",
"Atlanta Braves", "Atlanta", "GA",
"Baltimore Bullets", "Baltimore", "MD",
"Baltimore Colts", "Baltimore", "MD",
"Baltimore Orioles", "Baltimore", "MD",
"Baltimore Ravens", "Baltimore", "MD",
"Boston Americans", "Boston", "MA",
"Boston Braves", "Boston", "MA",
"Boston Bruins", "Boston", "MA",
"Boston Celtics", "Boston", "MA",
"Boston Red Sox", "Boston", "MA",
"Brooklyn Dodgers", "Brooklyn", "NY",
"Calgary Flames", "Calgary", "AB",
"Carolina Hurricanes", "Raleigh", "NC",
"Chicago Bears", "Chicago", "IL",
"Chicago Black Hawks", "Chicago", "IL",
"Chicago Blackhawks", "Chicago", "IL",
"Chicago Bulls", "Chicago", "IL",
"Chicago Cubs", "Chicago", "IL",
"Chicago White Sox", "Chicago", "IL",
"Cincinnati Reds", "Cincinnati", "OH",
"Cleveland Cavaliers", "Cleveland", "OH",
"Cleveland Indians", "Cleveland", "OH",
"Colorado Avalanche", "Denver", "CO",
"Dallas Cowboys", "Dallas", "TX",
"Dallas Mavericks", "Dallas", "TX",
"Dallas Stars", "Dallas", "TX",
"Denver Broncos", "Denver", "CO",
"Denver Nuggets", "Denver", "CO",
"Detroit Pistons", "Detroit", "MI",
"Detroit Red Wings", "Detroit", "MI",
"Detroit Tigers", "Detroit", "MI",
"Edmonton Oilers", "Edmonton", "AB",
"Florida Marlins", "Miami", "FL",
"Florida Panthers", "Sunrise", "FL",
"Golden State Warriors - Oakland", "Oakland", "CA",
"Golden State Warriors - San Francisco", "San Francisco", "CA",
"Green Bay Packers", "Green Bay", "WI",
"Houston Astros", "Houston", "TX",
"Houston Rockets", "Houston", "TX",
"Indianapolis Colts", "Indianapolis", "IN",
"Kansas City Chiefs", "Kansas City", "MO",
"Kansas City Royals", "Kansas City", "MO",
"Los Angeles Dodgers", "Los Angeles", "CA",
"Los Angeles Kings", "Los Angeles", "CA",
"Los Angeles Lakers", "Los Angeles", "CA",
"Los Angeles Raiders", "Los Angeles", "CA",
"Los Angeles Rams", "Los Angeles", "CA",
"Miami Dolphins", "Miami", "FL",
"Miami Heat", "Miami", "FL",
"Milwaukee Braves", "Milwaukee", "WI",
"Milwaukee Brewers", "Milwaukee", "WI",
"Milwaukee Bucks", "Milwaukee", "WI",
"Minneapolis Lakers", "Minneapolis", "MN",
"Minnesota Twins", "Minneapolis", "MN",
"Montreal Canadiens", "Montreal", "QC",
"Montreal Maroons", "Montreal", "QC",
"New England Patriots", "Foxborough", "MA",
"New Jersey Devils", "East Rutherford", "NJ",
"New Orleans Saints", "New Orleans", "LA",
"New York Giants", "New York", "NY",
"New York Islanders", "Uniondale", "NY",
"New York Jets", "New York", "NY",
"New York Knicks", "New York", "NY",
"New York Mets", "New York", "NY",
"New York Rangers", "New York", "NY",
"New York Yankees", "New York", "NY",
"Oakland Athletics", "Oakland", "CA",
"Oakland Raiders", "Oakland", "CA",
"Oklahoma City Thunder", "Oklahoma City", "OK",
"Ottawa Senators", "Ottawa", "ON",
"Philadelphia 76ers", "Philadelphia", "PA",
"Philadelphia Athletics", "Philadelphia", "PA",
"Philadelphia Eagles", "Philadelphia", "PA",
"Philadelphia Flyers", "Philadelphia", "PA",
"Philadelphia Phillies", "Philadelphia", "PA",
"Philadelphia Warriors", "Philadelphia", "PA",
"Pittsburgh Penguins", "Pittsburgh", "PA",
"Pittsburgh Pirates", "Pittsburgh", "PA",
"Pittsburgh Steelers", "Pittsburgh", "PA",
"Portland Trail Blazers", "Portland", "OR",
"Rochester Royals", "Rochester", "NY",
"San Antonio Spurs", "San Antonio", "TX",
"San Francisco 49ers", "San Francisco", "CA",
"San Francisco Giants", "San Francisco", "CA",
"Seattle Seahawks", "Seattle", "WA",
"Seattle SuperSonics", "Seattle", "WA",
"St. Louis Blues", "St. Louis", "MO",
"St. Louis Cardinals", "St. Louis", "MO",
"St. Louis Hawks", "St. Louis", "MO",
"St. Louis Rams", "St. Louis", "MO",
"Syracuse Nationals", "Syracuse", "NY",
"Tampa Bay Buccaneers", "Tampa", "FL",
"Tampa Bay Lightning", "Tampa", "FL",
"Texas Rangers", "Arlington", "TX",
"Toronto Blue Jays", "Toronto", "ON",
"Toronto Maple Leafs", "Toronto", "ON",
"Toronto Raptors", "Toronto", "ON",
"Vegas Golden Knights", "Las Vegas", "NV",
"Washington Bullets", "Washington", "DC",
"Washington Capitals", "Washington", "DC",
"Washington Nationals", "Washington", "DC",
"Washington Redskins", "Washington", "DC",
"Washington Senators", "Washington", "DC"
)
unmatched_teams <- anti_join(championships_raw, team_locations, by = "team_key")
if (nrow(unmatched_teams) > 0) {
stop(
paste0(
"Missing location lookup: ",
paste(
unmatched_teams$league,
unmatched_teams$season,
shQuote(unmatched_teams$team_key),
collapse = "; "
)
)
)
}
championships_clean <- championships_raw |>
left_join(team_locations, by = "team_key") |>
select(league, season, winning_team, winning_city, state_abbr) |>
arrange(league, season)
# I saved the completed challenge table in the same SQLite database.
invisible(dbWriteTable(
con,
"championships_clean",
championships_clean,
overwrite = TRUE
))
# Confirming that every league-season is unique and every location was matched.
challenge_checks <- tibble(
check = c(
"Championship records",
"Duplicate league-season rows",
"Missing city or region codes"
),
result = c(
nrow(championships_clean),
sum(duplicated(championships_clean[c("league", "season")])),
sum(is.na(championships_clean$winning_city) |
is.na(championships_clean$state_abbr))
)
)
kable(
challenge_checks,
col.names = c("Check", "Result"),
caption = "Challenge 1 data checks",
align = c("l", "r")
)