Global Cuisine Nutrition Profiles

Created with ggplot2 + coord_polar() for pie charts, ggflags for country indicators, showtext for custom fonts, and scales for proportional sizing based on average calories per recipe

#TidyTuesday
Author

Aditya Dahiya

Published

September 23, 2025

About the Data

This week’s dataset explores a comprehensive collection of recipes from Allrecipes.com, curated through the tastyR package and prepared for analysis. The collection includes two complementary datasets: all_recipes containing 14,426 general recipes, and cuisines featuring 2,218 recipes categorized by country of origin. Both datasets provide rich recipe information including ingredients, comprehensive nutritional data (calories, fat, carbohydrates, protein), cooking logistics (preparation and cooking times), user engagement metrics (ratings and reviews), and serving sizes. All data has been systematically cleaned and standardized to facilitate straightforward comparisons and visual exploration. The datasets open up numerous analytical opportunities, from identifying the most successful recipe authors and exploring relationships between cooking time and ratings, to discovering which cuisines achieve the highest user satisfaction and finding the most “actionable” recipes that balance high ratings with minimal time investment. This curated collection was assembled by Brian Mubia and represents a valuable resource for exploring culinary trends, user preferences, and recipe performance patterns across diverse cooking traditions and styles.

Figure 1: This visualization explores the nutritional profiles of 42 global cuisines from the Allrecipes dataset, with each pie chart representing a different culinary tradition. The size of each pie is proportional to the average calories per recipe, ranging from Malaysian cuisine at 247 calories to Italian at 488 calories. Within each pie, colored segments reveal the macronutrient balance: red for carbohydrates, yellow for fats, and green for proteins, with percentages displayed for each segment. Country flags at the center help identify each cuisine’s origin. The data reveals fascinating patterns—Italian and German cuisines tend to be more calorie-dense, while Asian cuisines like Vietnamese and Thai generally show lower calorie averages. Some cuisines like Brazilian and Turkish show relatively balanced macronutrient distributions, while others like certain European cuisines lean more heavily toward carbohydrates, reflecting their traditional grain and pasta-based dishes.

How I Made This Graphic

I created this comprehensive visualization using R and the tidyverse ecosystem, leveraging several specialized packages for enhanced functionality. The foundation was built with ggplot2 for the core plotting framework, enhanced by ggtext for markdown text support in titles and subtitles. I used showtext and Google Fonts to incorporate custom typography (Saira family fonts) for a polished aesthetic. The polar coordinate system transformed rectangular bar charts into pie charts using coord_polar(), while ggflags added country flags at each pie center. Data manipulation relied heavily on dplyr for grouping and summarizing nutritional data, with tidyr for reshaping data into the long format required for visualization. The scales package normalized calorie values to create proportional pie sizes, and patchwork enabled the multi-panel layout through facet_wrap(). Color choices used a custom palette to distinguish carbohydrates, fats, and proteins across the 42 featured cuisines.

Loading required libraries

Code
pacman::p_load(
  tidyverse, # All things tidy

  scales, # Nice Scales for ggplot2
  fontawesome, # Icons display in ggplot2
  ggtext, # Markdown text support for ggplot2
  showtext, # Display fonts in ggplot2
  colorspace, # Lighten and Darken colours

  patchwork  # Composing Plots
)

all_recipes <- readr::read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2025/2025-09-16/all_recipes.csv')
cuisines <- readr::read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2025/2025-09-16/cuisines.csv')

# Source: Claude Sonnet 4.0
# Create a tibble mapping cuisines to their primary country ISO-2 codes
library(tibble)

cuisine_country_mapping <- tibble(
  country = c(
    "Southern Recipes", "Italian", "Cajun and Creole", "Malaysian", "Indonesian", 
    "Vietnamese", "Spanish", "Tex-Mex", "Portuguese", "Soul Food", "Thai", 
    "Pakistani", "Peruvian", "Cuban", "Puerto Rican", "Canadian", "French", 
    "Korean", "Polish", "Chinese", "Austrian", "German", "Persian", "Filipino", 
    "Turkish", "Greek", "Jamaican", "Brazilian", "Russian", "Japanese", 
    "Indian", "Jewish", "Argentinian", "Chilean", "Scandinavian", 
    "Amish and Mennonite", "Swedish", "Australian and New Zealander", 
    "Lebanese", "Danish", "Israeli", "Norwegian"
  ),
  country_code = c(
    "us",  # Southern Recipes - United States (Southern US cuisine)
    "it",  # Italian - Italy
    "us",  # Cajun and Creole - United States (Louisiana)
    "my",  # Malaysian - Malaysia
    "id",  # Indonesian - Indonesia
    "vn",  # Vietnamese - Vietnam
    "es",  # Spanish - Spain
    "us",  # Tex-Mex - United States (Texas-Mexico border cuisine)
    "pt",  # Portuguese - Portugal
    "us",  # Soul Food - United States (African-American cuisine)
    "th",  # Thai - Thailand
    "pk",  # Pakistani - Pakistan
    "pe",  # Peruvian - Peru
    "cu",  # Cuban - Cuba
    "pr",  # Puerto Rican - Puerto Rico
    "ca",  # Canadian - Canada
    "fr",  # French - France
    "kr",  # Korean - South Korea
    "pl",  # Polish - Poland
    "cn",  # Chinese - China
    "at",  # Austrian - Austria
    "de",  # German - Germany
    "ir",  # Persian - Iran
    "ph",  # Filipino - Philippines
    "tr",  # Turkish - Turkey
    "gr",  # Greek - Greece
    "jm",  # Jamaican - Jamaica
    "br",  # Brazilian - Brazil
    "ru",  # Russian - Russia
    "jp",  # Japanese - Japan
    "in",  # Indian - India
    "il",  # Jewish - Israel (modern Jewish cuisine center)
    "ar",  # Argentinian - Argentina
    "cl",  # Chilean - Chile
    "se",  # Scandinavian - Sweden (largest Scandinavian country)
    "us",  # Amish and Mennonite - United States (primarily Pennsylvania)
    "se",  # Swedish - Sweden
    "au",  # Australian and New Zealander - Australia (larger population)
    "lb",  # Lebanese - Lebanon
    "dk",  # Danish - Denmark
    "il",  # Israeli - Israel
    "no"   # Norwegian - Norway
  )
)

Visualization Parameters

Code
# Font for titles
font_add_google("Saira",
  family = "title_font"
)

# Font for the caption
font_add_google("Saira Condensed",
  family = "body_font"
)

# Font for plot text
font_add_google("Saira Extra Condensed",
  family = "caption_font"
)

showtext_auto()

# A base Colour
bg_col <- "white"
seecolor::print_color(bg_col)

# Colour for highlighted text
text_hil <- "grey40"
seecolor::print_color(text_hil)

# Colour for the text
text_col <- "grey30"
seecolor::print_color(text_col)

line_col <- "grey30"

# Define Base Text Size
bts <- 80

# Caption stuff for the plot
sysfonts::font_add(
  family = "Font Awesome 6 Brands",
  regular = here::here("docs", "Font Awesome 6 Brands-Regular-400.otf")
)
github <- "&#xf09b"
github_username <- "aditya-dahiya"
xtwitter <- "&#xe61b"
xtwitter_username <- "@adityadahiyaias"
social_caption_1 <- glue::glue("<span style='font-family:\"Font Awesome 6 Brands\";'>{github};</span> <span style='color: {text_hil}'>{github_username}  </span>")
social_caption_2 <- glue::glue("<span style='font-family:\"Font Awesome 6 Brands\";'>{xtwitter};</span> <span style='color: {text_hil}'>{xtwitter_username}</span>")
plot_caption <- paste0(
  "**Data:**  Brian Mubia",
  " |  **Code:** ",
  social_caption_1,
  " |  **Graphics:** ",
  social_caption_2
)
rm(
  github, github_username, xtwitter,
  xtwitter_username, social_caption_1,
  social_caption_2
)

# Add text to plot-------------------------------------------------
plot_title <- "Global Cuisine Nutrition Profiles"

plot_subtitle <- "Pie chart size reflects average calories per recipe (value in brackets), while segments<br>show the balance of <b style='color:#FF7676'>**Carbohydrates**</b>, <b style='color:#F9D662'>**Fats**</b>, and <b style='color:#7CAB7D'>**Proteins**</b> across the cuisines."

str_view(plot_subtitle)

Exploratory Data Analysis and Wrangling

Code
# all_recipes
# 
# cuisines |> 
#   count(country, sort = T) |> 
#   print(n = 30)
# 
# pacman::p_load(summarytools)
# cuisines |> 
#   dfSummary() |> 
#   view()
# 
# all_recipes |> 
#   dfSummary() |> 
#   view()
# pacman::p_unload(summarytools)

# Clause Sonnet 4.0

# Calculate summary statistics and carbs proportion for sorting
df1 <- cuisines |> 
  group_by(country) |> 
  summarise(
    n = n(),
    calories = mean(calories, na.rm = T),
    fat = mean(fat, na.rm = TRUE),
    carbs = mean(carbs, na.rm = TRUE),
    protein = mean(protein, na.rm = TRUE),
    avg_rating = mean(avg_rating, na.rm = TRUE),
    .groups = 'drop'
  ) |> 
  
  # Calculate carbs proportion for sorting
  # mutate(
  #   total_macros = fat + carbs + protein,
  #   carbs_prop = carbs / total_macros
  # ) |> 
  slice_max(
    order_by = n,
    n = 42,
    with_ties = FALSE
  ) |> 

  # Sort by carbs proportion (descending)
  arrange(desc(calories)) |> 
  
  # Create radius scale based on calories (normalize between 0.1 and 0.4)
  mutate(
    radius = scales::rescale(
      calories, to = c(247/488, 1), 
      from = range(calories, na.rm = TRUE)
      )
  ) |> 
  # pull(country) |> 
  # paste0(collapse = ", ")
  left_join(
    cuisine_country_mapping
  )

plot_data <-  df1 |> 
  pivot_longer(
    cols = c(fat, carbs, protein),
    names_to = "nutrient",
    values_to = "value"
  ) |> 
  # Reorder country factor to maintain the carbs proportion order
  mutate(
    country = factor(country, levels = df1$country)
  ) |> 
  group_by(
    country
  ) |> 
  mutate(value = value / sum(value)) |>
  group_by(country) |>
  mutate(
    ymax = cumsum(value),
    ymin = lag(ymax, default = 0),
    ymid = (ymax + ymin) / 2,
    xmin = 0,
    xmax = radius
  )

# Create custom labels for the facets
strip_labels <- plot_data |> 
  distinct(
    country, calories
  ) |> 
  mutate(
    label = str_wrap(paste0(country, " (", round(calories, 0), ")"), 18)
  ) |> 
  pull(label)
names(strip_labels) <- levels(plot_data$country)

The Plot

Code
# Create the plot
g <- plot_data |> 
  ggplot() +
  geom_rect(
    mapping = aes(
    xmin = xmin, 
    xmax = xmax, 
    ymin = ymin, 
    ymax = ymax, 
    fill = nutrient
    ),
    colour = bg_col
  ) +
  geom_rect(
    data = tibble(
      xmin = rep(0, nrow(plot_data)),
      xmax = rep(0.22, nrow(plot_data)),
      ymin = rep(0, nrow(plot_data)),
      ymax = rep(1, nrow(plot_data))
    ),
    mapping = aes(
      xmin = xmin, 
      xmax = xmax, 
      ymin = ymin, 
      ymax = ymax
    ),
    fill = bg_col,
    colour = bg_col
  ) +
  geom_text(
    mapping = aes(
      x = xmax * 0.75,  # Position at ~3/4 way to edge
      y = ymid,
      label = paste0(round(value*100, 0), "%"),
      size = radius * bts / 5
    ),
    family = "body_font",
    colour = "black"
  ) +
  scale_size_identity() +
  ggflags::geom_flag(
    mapping = aes(
      x = 0,  # Center position
      y = 0,  # Center position
      country = country_code
    ),
    size = 10
  ) +
  facet_wrap(
    ~country,
    ncol = 6,
    labeller = labeller(
      country = strip_labels
    )
  ) +
  coord_polar(
    theta = "y",
    start = 90 * pi / 180,
    direction = 1,
    clip = "off"
  ) +
  scale_fill_manual(
    name = "Nutrient",
    labels = c("Carbohydrates", "Fat", "Protein"),
    values = c("#FF7676FF", "#F9D662FF", "#7CAB7DFF")
  ) +
  labs(
    title = plot_title,
    subtitle = plot_subtitle,
    caption = plot_caption,
    x = NULL,
    y = NULL,
    colour = NULL,
    fill = NULL
  ) +
  theme_minimal(
    base_family = "body_font",
    base_size = bts
  ) +
  theme(
    legend.position = "none",
    
    # Overall
    text = element_text(
      margin = margin(0, 0, 0, 0, "mm"),
      colour = text_col,
      lineheight = 0.3
    ),
    
    # Axes
    axis.text = element_blank(),
    axis.ticks = element_blank(),
    axis.ticks.length = unit(0, "mm"),
    axis.line = element_blank(),
    panel.grid = element_blank(),
 
    # Panels
    strip.text = element_text(
      size = 0.75 * bts,
      margin = margin(0,0,-3,0, "mm"),
      lineheight = 0.3,
      vjust = 0,
      hjust = 0.5,
      colour = text_hil
    ),
    panel.spacing.x = unit(2, "mm"),
    panel.spacing.y = unit(-5, "mm"),
    
    # Labels and Strip Text
    plot.title = element_text(
      margin = margin(5, 0, 2, 0, "mm"),
      hjust = 0.5,
      vjust = 0.5,
      colour = text_hil,
      size = 3.2 * bts,
      family = "body_font",
      face = "bold"
    ),
    plot.subtitle = element_textbox(
      margin = margin(2, 0, 5, 0, "mm"),
      vjust = 0.5,
      colour = text_hil,
      size = 1.3 * bts,
      hjust = 0.5,
      halign = 0.5,
      family = "body_font",
      lineheight = 0.3
    ),
    plot.caption = element_markdown(
      family = "caption_font",
      hjust = 0.5,
      margin = margin(-5,0,0,0, "mm"),
      colour = text_hil
    ),
    plot.caption.position = "plot",
    plot.title.position = "plot",
    plot.margin = margin(5, 5, 5, 5, "mm")
  )

ggsave(
  filename = here::here(
    "data_vizs",
    "tidy_allrecipes.png"
  ),
  plot = g,
  width = 400,
  height = 500,
  units = "mm",
  bg = bg_col
)

Savings the thumbnail for the webpage

Code
# Saving a thumbnail

library(magick)

# Saving a thumbnail for the webpage
image_read(here::here(
  "data_vizs",
  "tidy_allrecipes.png"
)) |>
  image_resize(geometry = "x400") |>
  image_write(
    here::here(
      "data_vizs",
      "thumbnails",
      "tidy_allrecipes.png"
    )
  )

Session Info

Code
sessioninfo::session_info()$packages |>
  as_tibble() |>
  dplyr::select(package,
    version = loadedversion,
    date, source
  ) |>
  dplyr::arrange(package) |>
  janitor::clean_names(
    case = "title"
  ) |>
  gt::gt() |>
  gt::opt_interactive(
    use_search = TRUE
  ) |>
  gtExtras::gt_theme_espn()
Table 1: R Packages and their versions used in the creation of this page and graphics

Links