Publishable Stuff

Rasmus Bååth's Research Blog

A Fun Gastronomical Dataset: What's on the Menu?

I just found a fun food themed dataset that I’d never heard about and that I thought I’d share. It’s from a project called What’s on the menu where the New York Public Library has crowdsourced a digitization of their collection of historical restaurant menus. The collection stretches all the way back to the 19th century and well into the 1990’s, and on the home page it is stated that there are “1,332,271 dishes transcribed from 17,545 menus”. Here is one of those menus, from a turn of the (old) century Chinese-American restaurant:

The data is freely available in csv format (yay!) and here I ‘ll just show how to the get the data into R and I’ll use it to plot the popularity of some foods over time.

First we’re going to download the data, “unzip” csv files into a temporary directory, and read them into R.

1
2
3
4
5
6
7
8
9
10
11
12
13
library(tidyverse)
library(stringr)
library(curl)

# This url changes every month, check what's the latest at http://menus.nypl.org/data
menu_data_url <- "https://s3.amazonaws.com/menusdata.nypl.org/gzips/2016_09_16_07_00_30_data.tgz"
temp_dir <- tempdir()
curl_download(menu_data_url, file.path(temp_dir, "menu_data.tgz"))
untar(file.path(temp_dir, "menu_data.tgz"), exdir = temp_dir)
dish <- read_csv(file.path(temp_dir, "Dish.csv"))
menu <- read_csv(file.path(temp_dir, "Menu.csv"))
menu_item <- read_csv(file.path(temp_dir, "MenuItem.csv"))
menu_page <- read_csv(file.path(temp_dir, "MenuPage.csv"))

The resulting tables together describe the contents of the menus, but in order to know which dish was on which menu we need to join together the four tables. While doing this we’re also going to remove some uninteresting columns and remove some records that were not coded correctly.

1
2
3
4
5
6
7
8
9
10
11
d <- menu_item %>% select( id, menu_page_id, dish_id, price) %>%
  left_join(dish %>% select(id, name) %>% rename(dish_name = name),
            by = c("dish_id" = "id")) %>%
  left_join(menu_page %>% select(id, menu_id),
            by = c("menu_page_id" = "id")) %>%
  left_join(menu %>% select(id, date, place, location),
            by = c("menu_id" = "id")) %>%
  mutate(year = lubridate::year(date)) %>%
  filter(!is.na(year)) %>%
  filter(year > 1800 & year <= 2016) %>%
  select(year, location, menu_id, dish_name, price, place)

What we are left with in the d data frame is a table of what dishes were served, where they were served and when. Here is a sampler:

1
d[sample(1:nrow(d), 10), ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# A tibble: 10 × 6
    year                      location menu_id                         dish_name price
   <dbl>                         <chr>   <int>                             <chr> <dbl>
1   1900            Fifth Avenue Hotel   25394            Broiled Mutton Kidneys    NA
2   1971                 Tadlich Grill   26670                       Mixed Green  0.85
3   1939                Maison Prunier   30325                  Entrecote Minute    NA
4   1914          The Beekman Café Co.   33898                  Camembert cheese  0.10
5   1900         Carlton Hotel Company   21865                        Pork Chops  0.15
6   1914 Gutmann's Café and Restaurant   33982 Cold Boiled Ham with Potato Salad  0.40
7   1912               Waldorf-Astoria   34512            Stuffed Figs and Dates  0.30
8   1933                   Hotel Astor   31262              Assorted Small Cakes  0.25
9   1933              Ambassador Grill   31291                    Stuffed celery  0.55
10  1901            Del Coronado Hotel   14512                           peaches    NA
# ... with 1 more variables: place <chr>

Personally I’d go for the Stuffed Figs and Dates at the Waldorf-Astoria followed by some Assorted Small Cakes 21 years later at the Astor. If you want to download this slightly processed version of the dataset it’s available here in csv format. We can also see which are the most common menu items in the dataset:

1
d %>% count(tolower(dish_name)) %>% arrange(desc(n)) %>% head(10)
1
2
3
4
5
6
7
8
9
10
11
12
13
# A tibble: 10 × 2
   `tolower(dish_name)`     n
                  <chr> <int>
1                coffee  8532
2                celery  4865
3                olives  4737
4                   tea  4682
5              radishes  3426
6       mashed potatoes  2999
7       boiled potatoes  2502
8     vanilla ice cream  2379
9         chicken salad  2306
10                 milk  2218

That coffee is king isn’t that surprising, but the popularity of celery seems weird. My current hypothesis is that “celery” often refers to some kind of celery salad, or maybe it was common as a snack in the New York area in the 1900s. It should be remembered that the dataset does not represent what people ate in general, but is based on what menus were collected by the New York public library (presumably from the New York area). Also the bulk of the menus are from between 1900 and 1980:

1
2
3
ggplot(d, aes(year)) +
  geom_histogram(binwidth = 5, center = 1902.5, color = "black", fill = "lightblue") +
  scale_y_continuous("N.o. menu items")

Even though it’s not completely clear what the dataset represents we could still have a look at some food trends over time. Below I’m going to go through a couple of common foodstuffs and, for each decennium, calculate what proportion of menus includes that foodstuff.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
d$decennium = floor(d$year / 10) * 10
foods <- c("coffee", "tea", "pancake", "ice cream", "french frie",
           "french peas", "apple", "banana", "strawberry")
# Above I dropped the "d" in French fries in order to also match 
#"French fried potatoes." Also, thanks to @patternproject, I added \\b 
# in front of the regexp below which requires the food words to start with
# a word boundary, removing the situation where tea matches to, e.g., steak.
food_over_time <- map_df(foods, function(food) {
  d %>%
    filter(year >= 1900 & year <= 1980) %>%
    group_by(decennium, menu_id) %>%
    summarise(contains_food =
      any(str_detect(dish_name, regex(paste0("\\b", food), ignore_case = TRUE)),
          na.rm = TRUE)) %>%
    summarise(prop_food = mean(contains_food, na.rm = TRUE)) %>%
    mutate(food = food)
})

First up, Coffee vs. Tea:

1
2
3
4
5
6
7
8
9
10
11
12
13
# A reusable list of ggplot2 directives to produce a lineplot
food_time_plot <- list(
  geom_line(),
  geom_point(),
  scale_y_continuous("% of menus include",labels = scales::percent,
                     limits = c(0, NA)),
  scale_x_continuous(""),
  facet_wrap(~ food),
  theme_minimal(),
  theme(legend.position = "none"))

food_over_time %>% filter(food %in% c("coffee", "tea")) %>%
  ggplot(aes(decennium, prop_food, color = food)) + food_time_plot

Both pretty popular menu items, but I’m not sure what to make of the trends… Next up Ice cream vs. Pancakes:

1
2
food_over_time %>% filter(food %in% c("pancake", "ice cream")) %>%
  ggplot(aes(decennium, prop_food, color = food)) + food_time_plot

Ice cream wins, but again I’m not sure what to make of how ice cream varies over time. Maybe it’s just an artifact of how the data was collected or maybe it actually reflects the icegeist somehow. What about French fries vs. French peas:

1
2
food_over_time %>% filter(food %in% c("french frie", "french peas")) %>%
  ggplot(aes(decennium, prop_food, color = food)) + food_time_plot

Seems like the heyday of French peas are over, but French fries also seemed to peak in the 40s… Finally let’s look at some fruit:

1
2
food_over_time %>% filter(food %in% c("apple", "banana", "strawberry")) %>%
  ggplot(aes(decennium, prop_food, color = food)) + food_time_plot

Banana has really dropped in menu popularity since the early 1900s…

Anyway, this is a really cool dataset and I barely scratched the surface of what could be done with it. If you decide to explore this dataset further, and you make some plots and/or analyses, do send me a link and I will link to it here.

To finish off let’s look at this elegant cocktail menu from 1937 which, among cocktails and fizzes, advertises tiny cocktail tamales:

Comments