The Milan-Cortina 2026 Winter Olympics are now complete and medal counts are a thing.

The official web site shows the medal counts as a table. It might be nice to show the results graphically.

The official web site is challenging to scrape, but an alternative is available:

https://en.wikipedia.org/wiki/2026_Winter_Olympics_medal_table

The Wikipedia page on 2026-02-24 contains the standings data in a form that is easy to scrape.

Using the rvest and xml2 packages we can extract the table from the page:

library(rvest)
## URL <- "https://en.wikipedia.org/wiki/2026_Winter_Olympics_medal_table"
## Use permanent link in case the page changes:
URL <- "https://en.wikipedia.org/w/index.php?title=2026_Winter_Olympics_medal_table&oldid=1340005726"
page <- read_html(URL)
page_tables <- html_table(page, fill = TRUE)
length(page_tables)
## [1] 6
mtable <- page_tables[[3]]

The table is unusually clean:

head(mtable)
## # A tibble: 6 × 6
##   Rank  .mw-parser-output .tooltip-dotted{border-bot…¹  Gold Silver Bronze Total
##   <chr> <chr>                                          <int>  <int>  <int> <int>
## 1 1     Norway                                            18     12     11    41
## 2 2     United States                                     12     12      9    33
## 3 3     Netherlands                                       10      7      3    20
## 4 4     Italy*                                            10      6     14    30
## 5 5     Germany                                            8     10      8    26
## 6 6     France                                             8      9      6    23
## # ℹ abbreviated name:
## #   ¹​`.mw-parser-output .tooltip-dotted{border-bottom:1px dotted;cursor:help}NOC`
tail(mtable)
## # A tibble: 6 × 6
##   Rank                .mw-parser-output .tooltip-dot…¹  Gold Silver Bronze Total
##   <chr>               <chr>                            <int>  <int>  <int> <int>
## 1 25                  Estonia                              0      1      0     1
## 2 25                  Georgia                              0      1      0     1
## 3 –                   Individual Neutral Athletes[A][…     0      1      0     1
## 4 28                  Bulgaria                             0      0      2     2
## 5 29                  Belgium                              0      0      1     1
## 6 Totals (29 entries) Totals (29 entries)                116    118    115   349
## # ℹ abbreviated name:
## #   ¹​`.mw-parser-output .tooltip-dotted{border-bottom:1px dotted;cursor:help}NOC`

Only Rank might need adjusting (or could be dropped), the name of the second column needs fixing, and the final Totals row can be dropped:

library(dplyr)
mtable <- filter(mtable, ! grepl("^Totals", Rank)) |>
    mutate(Rank = as.numeric(Rank))
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `Rank = as.numeric(Rank)`.
## Caused by warning:
## ! NAs introduced by coercion
colnames(mtable)[[2]] <- "NOC"
tail(mtable)
## # A tibble: 6 × 6
##    Rank NOC                                Gold Silver Bronze Total
##   <dbl> <chr>                             <int>  <int>  <int> <int>
## 1    25 Denmark                               0      1      0     1
## 2    25 Estonia                               0      1      0     1
## 3    25 Georgia                               0      1      0     1
## 4    NA Individual Neutral Athletes[A][B]     0      1      0     1
## 5    28 Bulgaria                              0      0      2     2
## 6    29 Belgium                               0      0      1     1

For plotting it is useful to have a longer form with a variable for medal type and medal count:

library(tidyr)
mtable_long <- pivot_longer(mtable, Gold : Bronze,
                            names_to = "medal",
                            values_to = "count")
head(mtable_long)
## # A tibble: 6 × 5
##    Rank NOC           Total medal  count
##   <dbl> <chr>         <int> <chr>  <int>
## 1     1 Norway           41 Gold      18
## 2     1 Norway           41 Silver    12
## 3     1 Norway           41 Bronze    11
## 4     2 United States    33 Gold      12
## 5     2 United States    33 Silver    12
## 6     2 United States    33 Bronze     9

A first graph:

library(ggplot2)
ggplot(mtable_long) +
    geom_col(aes(y = NOC, x = count, fill = medal))

One way to match the country order on the web site:

ggplot(mutate(mtable_long, NOC = factor(NOC, rev(mtable$NOC)))) +
    geom_col(aes(y = NOC, x = count, fill = medal))

Reordering the medal type levels makes the reason for the country ordering easier to see:

p <- ggplot(mutate(mtable_long,
                   NOC = factor(NOC, rev(mtable$NOC)),
                   medal = factor(medal, c("Bronze", "Silver", "Gold")))) +
    geom_col(aes(y = NOC, x = count, fill = medal))
p

We can choose more reasonable colors. The Hex code for Bronze, for example, is from https://www.99colors.net/name/bronze:

p <- p + scale_fill_manual(values = c("Gold" = "#D4AF37",
                                      "Silver" = "#C0C0C0",
                                      "Bronze" = "#CD7F32"))
p

Finally some theme adjustments:

p + theme_minimal() +
    theme(legend.position = "top",
          panel.grid.major.y = element_blank()) +
    guides(fill = guide_legend(reverse = TRUE)) +
    scale_x_continuous(expand = c(0, 0)) +
    labs(y=NULL, x = "Count", fill = "Medal")