---
title: "Every Product Group Has Trade Deals, But Import Taxes Still Vary Widely"
subtitle: "Trade deals offering lower tariffs are nearly identical across product groups (right), while standard import tax rates vary widely (left) — based on current U.S. tariffs (2025)."
description: "U.S. agricultural import tax rates vary dramatically across product groups — Beverages & Spirits and Dairy & Eggs face median rates above 17% — yet the number of active trade agreements offering lower rates is nearly identical across all categories (17–19). Trade deals are widespread; protection is structural. Built with ggplot2 and patchwork in R."
date: "2026-04-18"
author:
- name: "Steven Ponce"
url: "https://stevenponce.netlify.app"
citation:
url: "https://stevenponce.netlify.app/data_visualizations/TidyTuesday/2026/tt_2026_17.html"
categories: ["TidyTuesday", "2026"]
tags: [
"Agricultural Tariffs",
"Trade Policy",
"International Trade",
"Boxplot",
"Bar Chart",
"Patchwork",
"ggplot2",
"USITC",
"HTS",
"Trade Agreements",
"Food & Agriculture",
"Economic Policy"
]
image: "thumbnails/tt_2026_17.png"
format:
html:
toc: true
toc-depth: 5
code-link: true
code-fold: true
code-tools: true
code-summary: "Show code"
editor_options:
chunk_output_type: inline
execute:
freeze: true
cache: true
error: false
message: false
warning: false
eval: true
---
{#fig-1}
### [**Steps to Create this Graphic**]{.mark}
#### [1. Load Packages & Setup]{.smallcaps}
```{r}
#| label: load
#| warning: false
#| message: false
#| results: "hide"
## 1. LOAD PACKAGES & SETUP ----
suppressPackageStartupMessages({
if (!require("pacman")) install.packages("pacman")
pacman::p_load(
tidyverse, ggtext, showtext, janitor, ggrepel,
scales, glue, skimr, patchwork
)
})
### |- figure size ----
camcorder::gg_record(
dir = here::here("temp_plots"),
device = "png",
width = 10,
height = 8,
units = "in",
dpi = 320
)
# Source utility functions
suppressMessages(source(here::here("R/utils/fonts.R")))
source(here::here("R/utils/social_icons.R"))
source(here::here("R/utils/image_utils.R"))
source(here::here("R/themes/base_theme.R"))
```
#### [2. Read in the Data]{.smallcaps}
```{r}
#| label: read
#| include: true
#| eval: true
#| warning: false
tt <- tidytuesdayR::tt_load(2026, week = 17)
agreements <- tt$agreements |> clean_names()
quantity_codes <- tt$quantity_codes |> clean_names()
tariff_agricultural <- tt$tariff_agricultural |> clean_names()
tariff_codes <- tt$tariff_codes |> clean_names()
rm(tt)
```
#### [3. Examine the Data]{.smallcaps}
```{r}
#| label: examine
#| include: true
#| eval: true
#| results: 'hide'
#| warning: false
glimpse(agreements)
glimpse(quantity_codes)
glimpse(tariff_agricultural)
glimpse(tariff_codes)
```
#### [4. Tidy Data]{.smallcaps}
```{r}
#| label: tidy
#| warning: false
### |- Chapter groups: 8 editorial buckets ----
chapter_groups <- c(
"01" = "Live Animals",
"02" = "Meat",
"03" = "Fish & Seafood",
"04" = "Dairy & Eggs",
"08" = "Fruit & Nuts",
"10" = "Grains & Cereals",
"17" = "Sugar & Confections",
"22" = "Beverages & Spirits"
)
### |- Current standard (MFN) ad valorem rates — Panel A ----
mfn_current <- tariff_agricultural |>
filter(
agreement == "mfn",
rate_type_code == "7",
begin_effective_date <= today(),
end_effective_date >= today()
) |>
mutate(chapter = substr(hts8, 1, 2)) |>
filter(chapter %in% names(chapter_groups)) |>
mutate(chapter_label = chapter_groups[chapter]) |>
group_by(hts8, chapter_label) |>
summarise(ad_val_rate = mean(ad_val_rate, na.rm = TRUE), .groups = "drop") |>
mutate(rate_pct = ad_val_rate * 100) |>
filter(rate_pct <= 100)
### |- Sort order: descending median tariff rate ----
chapter_order <- mfn_current |>
group_by(chapter_label) |>
summarise(median_rate = median(rate_pct, na.rm = TRUE)) |>
arrange(median_rate) |>
pull(chapter_label)
mfn_current <- mfn_current |>
mutate(chapter_label = factor(chapter_label, levels = chapter_order))
### |- Trade deal counts per chapter — Panel B ----
deal_counts <- tariff_agricultural |>
filter(
!agreement %in% c("mfn", "col2"),
!str_detect(agreement, "\\*|\\+"),
begin_effective_date <= today(),
end_effective_date >= today()
) |>
mutate(chapter = substr(hts8, 1, 2)) |>
filter(chapter %in% names(chapter_groups)) |>
mutate(chapter_label = chapter_groups[chapter]) |>
group_by(chapter_label) |>
summarise(n_deals = n_distinct(agreement), .groups = "drop") |>
mutate(chapter_label = factor(chapter_label, levels = chapter_order))
### |- Highlight flag: top 2 highest-tariff categories ----
top2 <- tail(chapter_order, 2)
mfn_current <- mfn_current |>
mutate(is_top2 = chapter_label %in% top2)
### |- Median labels per chapter (for Panel A) ----
chapter_medians <- mfn_current |>
group_by(chapter_label) |>
summarise(median_rate = median(rate_pct, na.rm = TRUE), .groups = "drop") |>
mutate(
label_med = glue("{round(median_rate, 1)}%"),
is_top2 = chapter_label %in% top2
)
```
#### [5. Visualization Parameters]{.smallcaps}
```{r}
#| label: params
#| include: true
#| warning: false
### |- plot aesthetics ----
colors <- get_theme_colors(
palette = list(
"burgundy" = "#722F37",
"gray_mid" = "#9E9E9E",
"gray_bar" = "#BDBDBD",
"text_dark" = "#2C2C2C",
"text_muted" = "#7A7A7A"
)
)
### |- titles and caption ----
title_text <- str_glue("Every Product Group Has Trade Deals, But Import Taxes Still Vary Widely")
subtitle_text <- str_glue(
"Trade deals offering lower tariffs are nearly identical across product groups (right), ",
"while standard import tax rates vary widely (left) — ",
"based on current U.S. tariffs (2025)."
)
caption_text <- create_social_caption(
tt_year = 2025,
tt_week = 17,
source_text = "USITC Tariff Database"
)
### |- fonts ----
setup_fonts()
fonts <- get_font_families()
### |- plot theme ----
base_theme <- create_base_theme(colors)
weekly_theme <- extend_weekly_theme(
base_theme,
theme(
axis.title.x = element_text(size = 8, color = colors$palette$text_muted, margin = margin(t = 6)),
axis.title.y = element_blank(),
axis.text.y = element_text(size = 9, color = colors$palette$text_dark, hjust = 1),
axis.text.x = element_text(size = 8, color = colors$palette$text_muted),
panel.grid.major.x = element_line(color = "gray97", linewidth = 0.2),
panel.grid.major.y = element_blank(),
panel.grid.minor = element_blank(),
panel.background = element_rect(fill = "white", color = NA),
plot.background = element_rect(fill = "white", color = NA),
plot.margin = margin(10, 12, 10, 12)
)
)
theme_set(weekly_theme)
```
#### [6. Plot]{.smallcaps}
```{r}
#| label: plot
#| warning: false
### |- Panel A: Boxplot + median labels ----
pa <- ggplot(mfn_current, aes(x = rate_pct, y = chapter_label)) +
# Geoms
geom_boxplot(
aes(
fill = is_top2,
color = is_top2
),
width = 0.55,
coef = 1.0,
outlier.shape = NA,
linewidth = 0.35
) +
geom_text(
data = filter(chapter_medians, is_top2),
aes(x = median_rate, y = chapter_label, label = label_med),
nudge_x = 1.5,
hjust = 0,
size = 3.4,
color = colors$palette$burgundy,
family = fonts$text,
fontface = "bold",
inherit.aes = FALSE
) +
# Scales
scale_fill_manual(
values = c("TRUE" = colors$palette$burgundy, "FALSE" = colors$palette$gray_mid),
guide = "none"
) +
scale_color_manual(
values = c("TRUE" = colors$palette$burgundy, "FALSE" = "gray45"),
guide = "none"
) +
scale_x_continuous(
labels = label_percent(scale = 1, suffix = "%"),
breaks = c(0, 10, 20, 30),
limits = c(0, 30),
expand = expansion(mult = c(0.01, 0.04))
) +
# Labs
labs(
x = "Import tax rate (%)",
title = "A · Tariffs Vary Widely Across Products"
) +
# Theme
theme(
plot.title = element_text(
size = 12, face = "bold",
family = fonts$title, color = colors$palette$text_dark,
margin = margin(b = 8)
),
plot.margin = margin(10, 12, 10, 12)
)
### |- Panel B: Trade deal counts ----
pb <- ggplot(deal_counts, aes(x = n_deals, y = chapter_label)) +
# Geoms
geom_col(
fill = colors$palette$gray_bar,
width = 0.55
) +
geom_text(
aes(label = n_deals),
hjust = -0.3,
size = 2.8,
color = colors$palette$text_muted,
family = fonts$text
) +
# Annotation
annotate(
"text",
x = 11,
y = 8.5,
label = "Nearly identical across all groups (17–19 agreements)",
hjust = 0.5,
size = 2.5,
color = colors$palette$text_dark,
family = fonts$text,
fontface = "italic"
) +
# Scales
scale_x_continuous(
expand = expansion(mult = c(0, 0.22)),
breaks = c(0, 5, 10, 15, 20),
limits = c(0, 22)
) +
# Labs
labs(
x = "Number of trade agreements",
title = "B · Trade Deals Are Nearly Identical Across Products"
) +
# Theme
theme(
axis.text.y = element_blank(),
plot.title = element_text(
size = 12, face = "bold",
family = fonts$title, color = colors$palette$text_dark,
margin = margin(b = 8)
)
)
### |- Combined plots ----
combined_plots <- pa + pb +
plot_layout(widths = c(1.4, 1)) +
plot_annotation(
title = title_text,
subtitle = subtitle_text,
caption = caption_text,
theme = theme(
plot.title = element_textbox_simple(
size = 22,
face = "bold",
family = fonts$title,
color = colors$palette$text_dark,
margin = margin(b = 6),
lineheight = 1.2
),
plot.subtitle = element_textbox_simple(
size = 11,
family = fonts$text,
color = colors$palette$text_muted,
margin = margin(t = 5, b = 10),
lineheight = 1.2
),
plot.caption = element_textbox_simple(
size = 7,
family = fonts$text,
color = colors$palette$text_muted,
margin = margin(t = 12),
lineheight = 1.3
),
plot.margin = margin(16, 16, 18, 16),
plot.background = element_rect(fill = "white", color = NA)
)
)
```
#### [7. Save]{.smallcaps}
```{r}
#| label: save
#| warning: false
### |- plot image ----
save_plot_patchwork(
plot = combined_plots,
type = "tidytuesday",
year = 2026,
week = 17,
width = 11,
height = 8
)
```
#### [8. Session Info]{.smallcaps}
::: {.callout-tip collapse="true"}
##### Expand for Session Info
```{r, echo = FALSE}
#| eval: true
#| warning: false
sessionInfo()
```
:::
#### [9. GitHub Repository]{.smallcaps}
::: {.callout-tip collapse="true"}
##### Expand for GitHub Repo
The complete code for this analysis is available in [`tt_2026_17.qmd`](https://github.com/poncest/personal-website/blob/master/data_visualizations/TidyTuesday/2026/tt_2026_17.qmd).
For the full repository, [click here](https://github.com/poncest/personal-website/).
:::
#### [10. References]{.smallcaps}
::: {.callout-tip collapse="true"}
##### Expand for References
1. **Data Source:**
- TidyTuesday 2026 Week 17: [US Agricultural Tariffs](https://github.com/rfordatascience/tidytuesday/blob/main/data/2026/2026-04-28/readme.md)
:::
#### [11. Custom Functions Documentation]{.smallcaps}
::: {.callout-note collapse="true"}
##### 📦 Custom Helper Functions
This analysis uses custom functions from my personal module library for efficiency and consistency across projects.
**Functions Used:**
- **`fonts.R`**: `setup_fonts()`, `get_font_families()` - Font management with showtext
- **`social_icons.R`**: `create_social_caption()` - Generates formatted social media captions
- **`image_utils.R`**: `save_plot()` - Consistent plot saving with naming conventions
- **`base_theme.R`**: `create_base_theme()`, `extend_weekly_theme()`, `get_theme_colors()` - Custom ggplot2 themes
**Why custom functions?**\
These utilities standardize theming, fonts, and output across all my data visualizations. The core analysis (data tidying and visualization logic) uses only standard tidyverse packages.
**Source Code:**\
View all custom functions → [GitHub: R/utils](https://github.com/poncest/personal-website/tree/master/R)
:::