TidyTuesday
    • About TidyTuesday
    • Datasets
      • 2025
      • 2024
      • 2023
      • 2022
      • 2021
      • 2020
      • 2019
      • 2018
    • Useful links

    On this page

    • The Cappuccino Index
      • The Data
      • How to Participate
        • PydyTuesday: A Posit collaboration with TidyTuesday
      • Data Dictionary
        • cafe.csv
        • cappuccino_index.csv
      • Cleaning Script

    The Cappuccino Index

    James Hoffmann is a YouTuber who explores coffee from many different angles. In a recent video he explored how long a barista would need to work to pay for a small cappuccino.

    Coffee, as a part of our economy, as an industry, is full of these awkward questions that I do think are worth asking.

    In the video, James presents the cappuccino index, which answers this at a country level. There are many interesting findings. You may want to watch the whole video as context and entertainment. There are some important caveats, such as tips being excluded.

    You might think about:

    • Some sample sizes are small. How uncertain is the ranking?
    • Which countries have the biggest variability in the price of a small cappuccino?
    • Are there any outliers?

    Thank you to Filip Reierson for curating this week’s dataset.

    Note: In both cafe.csv and cappuccino_index.csv, the country values for New Zealand and South Africa contain a non-breaking space (U+00A0) instead of a regular space. This can make those countries fail to match or join as expected, so the code below replaces them with regular spaces.

    The Data

    # Using R
    # Option 1: tidytuesdayR R package 
    ## install.packages("tidytuesdayR")
    
    tuesdata <- tidytuesdayR::tt_load('2026-09-08')
    ## OR
    tuesdata <- tidytuesdayR::tt_load(2026, week = 36)
    
    cafe <- tuesdata$cafe
    cappuccino_index <- tuesdata$cappuccino_index
    
    # Option 2: Read directly from GitHub
    
    cafe <- readr::read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2026/2026-09-08/cafe.csv')
    cappuccino_index <- readr::read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2026/2026-09-08/cappuccino_index.csv')
    
    # With either option, replace the non-breaking spaces in `country`
    
    cafe$country <- gsub("\u00a0", " ", cafe$country)
    cappuccino_index$country <- gsub("\u00a0", " ", cappuccino_index$country)
    # Using Python
    # Option 1: pydytuesday python library
    ## pip install pydytuesday
    
    import pydytuesday
    
    # Download files from the week, which you can then read in locally
    pydytuesday.get_date('2026-09-08')
    
    # Option 2: Read directly from GitHub and assign to an object
    
    cafe = pandas.read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2026/2026-09-08/cafe.csv')
    cappuccino_index = pandas.read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2026/2026-09-08/cappuccino_index.csv')
    
    # With either option, replace the non-breaking spaces in `country`
    
    cafe['country'] = cafe['country'].str.replace('\u00a0', ' ')
    cappuccino_index['country'] = cappuccino_index['country'].str.replace('\u00a0', ' ')
    # Using Julia
    # Option 1: TidierTuesday.jl library
    ## Pkg.add(url="https://github.com/TidierOrg/TidierTuesday.jl")
    
    using TidierTuesday
    
    # Download datasets for the week, and load them as a NamedTuple of DataFrames
    data = tt_load("2026-09-08")
    
    # Option 2: Read directly from GitHub and assign to an object with TidierFiles
    
    cafe = read_csv("https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2026/2026-09-08/cafe.csv")
    cappuccino_index = read_csv("https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2026/2026-09-08/cappuccino_index.csv")
    
    # Option 3: Read directly from Github and assign without Tidier dependencies
    cafe = CSV.read("https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2026/2026-09-08/cafe.csv", DataFrame)
    cappuccino_index = CSV.read("https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2026/2026-09-08/cappuccino_index.csv", DataFrame)
    
    # With any of the options above, replace the non-breaking spaces in `country`
    
    cafe.country = replace.(cafe.country, '\ua0' => ' ')
    cappuccino_index.country = replace.(cappuccino_index.country, '\ua0' => ' ')

    How to Participate

    • Explore the data, watching out for interesting relationships. We would like to emphasize that you should not draw conclusions about causation in the data. There are various moderating variables that affect all data, many of which might not have been captured in these datasets. As such, our suggestion is to use the data provided to practice your data tidying and plotting techniques, and to consider for yourself what nuances might underlie these relationships.
    • Create a visualization, a model, a Quarto report, a shiny app, or some other piece of data-science-related output, using R, Python, or another programming language.
    • Share your output and the code used to generate it on social media with the #TidyTuesday hashtag.
    • Submit your own dataset!

    PydyTuesday: A Posit collaboration with TidyTuesday

    • Exploring the TidyTuesday data in Python? Posit has some extra resources for you! Have you tried making a Quarto dashboard? Find videos and other resources in Posit’s PydyTuesday repo.
    • Share your work with the world using the hashtags #TidyTuesday and #PydyTuesday so that Posit has the chance to highlight your work, too!
    • Deploy or share your work however you want! If you’d like a super easy way to publish your work, give Connect Cloud a try.

    Data Dictionary

    cafe.csv

    variable class description
    country character The country of the cafe.
    city character The city of the cafe.
    urban logical Whether the cafe is in an urban area.
    suburban logical Whether the cafe is in a suburban area.
    rural logical Whether the cafe is in a rural area.
    hourly_wage_gbp double The hourly wage of a barista working in the cafe, converted to British pounds.
    price_gbp double The price of a small cappuccino from the cafe, converted to British pounds.
    original_currency character The original currency used by the cafe.
    price double The price of a small cappuccino from the cafe, in the original currency.
    hourly_wage double The hourly wage of a barista working in the cafe, in the original currency.

    cappuccino_index.csv

    variable class description
    country character Name of country for which cappuccino index was calculated.
    index double The cappuccino index: the time in minutes a barista needs to work to afford a small cappuccino in their country.
    n integer Number of cafes that were included in the calculation of the index.
    minutes double The number of whole minutes in index.
    seconds double The remaining number of seconds in index.
    index_as_time character Index formatted as minutes and seconds, i.e., MM:SS.

    Cleaning Script

    # Shared by James Hoffmann in a YouTube video titled 'The Surprising Things We Discovered In The Cost Of A Cappuccino' shared on 6 August 2026
    # James Hoffmann encouraged us to explore the figures ourselves and shared the full dataset in a public google sheet
    # Below is the cleaning process preparing the data for TidyTuesday
    
    library(tidyverse)
    library(janitor)
    
    cafe <- readr::read_csv(
      "https://docs.google.com/spreadsheets/d/1l3nitpsActIEqjaHk0wQNno8FDStYLsG_amSvAbuI3w/export?format=csv",
      show_col_types = FALSE
    ) |>
      janitor::clean_names() |>
      dplyr::mutate(
        urban = stringr::str_detect(urban_classification, "Urban"),
        suburban = stringr::str_detect(urban_classification, "Suburban"),
        rural = stringr::str_detect(urban_classification, "Rural")
      ) |>
      dplyr::select(
        country,
        city,
        urban,
        suburban,
        rural,
        hourly_wage_gbp = hourly_wage_in_gbp,
        price_gbp = price_per_cappuccino_in_gbp,
        original_currency = currency,
        price = price_per_small_cappuccino,
        hourly_wage = hourly_wage
      )
    
    cappuccino_index <- cafe |>
      summarise(index = sum(price_gbp)/sum(hourly_wage_gbp)*60, n=n(), .by=country) |>
      arrange(index) |>
      mutate(minutes = floor(index), seconds = floor((index - floor(index))*60)) |>
      mutate(index_as_time = paste0(minutes,":",stringr::str_pad(seconds, width = 2, pad = "0"))) |>
      readr::write_csv("cappuccino_index.csv")