In March, news broke that a French scientist had been denied entry into the United States after making critical comments about the Trump Administration’s research policies.
The story wasn’t a one-off — it seemed to hint at a broader trend. Increasingly, tourists and travelers are being denied entry into the country or even detained for days or weeks. And now, the chill is starting to show up in the numbers, as foreigners rethink their travel plans.
To get a better sense of the impact, I turned to the data: how much is foreign travel to the US really slowing down?
The Data
To get a sense of how damaging these policies have been, I wanted to dig into the hard data - are foreign travelers really canceling or postponing trips to the US?
Fortunately for us, the Department of Commerce publishes I-92 data with only a month of latency, which gives us a very up-to-date view on the composition and volume of international air travel out of and into the US. Here’s a quick blurb from their site on what this data includes:
The International Air Travel Statistics program provides information on international air traffic between the United States and other countries. The data has been collected from the Department of Homeland Security (DHS) and Customs and Border Protection’s (CBP) Advance Passenger Information System (APIS) since July 2010 …
The APIS-based I-92 system provides air traffic data on the following parameters: number of passengers, by country, airport, scheduled or chartered, U.S. Flag, foreign flag, citizens, and non-citizens.
However, the data published is just a plain tabular view - which makes it difficult to intuitively analyze:
Visualizations
To bring this data to life, I spent some time messing around in R, building a number of ggplot
charts in hopes of surfacing latent trends. Ultimately, I settled on a year-over-year (YoY) comparison being the best way to show the recent trend and normalize for the monthly seasonality we see over the course of a year.
What the YoY data shows us is a significant decline in foreign originating air passengers to the USA in February and March - with March 2025 nearly 10% lower than March 2024, a huge departure from the pre-Trump Administration trend.
Slicing by geo shows declines in almost all regions, with only the Middle East showing a YoY increase in traffic. Of particular note are the significant percent declines in Europe and Mexico, since those are the highest-volume markets.
Code Reference
Computing MoM and YoY Change
df <- df %>%
arrange(geo, date) %>%
group_by(geo) %>%
mutate(
# Month-over-Month
mom_change = `Foreign Originating` - lag(`Foreign Originating`, 1),
mom_change_pct = (mom_change / lag(`Foreign Originating`, 1)),
# Year-over-Year (12 months prior)
yoy_change = `Foreign Originating` - lag(`Foreign Originating`, 12),
yoy_change_pct = (yoy_change / lag(`Foreign Originating`, 12))
) %>%
ungroup()
Trend Chart
last_point_global <- df %>%
filter(geo == "global", date >= as.Date("2024-01-01")) %>%
group_by(geo) %>%
filter(date == max(date)) %>%
ungroup()
geo_labels <- c(
africa = "Africa",
asia = "Asia",
canada = "Canada",
carribean = "Caribbean",
central_america = "Central America",
europe = "Europe",
mexico = "Mexico",
mideast = "Middle East",
oceania = "Oceania",
south_america = "South America"
)
df %>%
filter(geo == "global") %>%
filter(date >= as.Date("2024-01-01")) %>%
ggplot(aes(x = date, y = `yoy_change_pct`, fill = `yoy_change_pct`)) +
geom_col() +
scale_y_continuous(label = scales::percent, breaks = seq(-0.20, 0.20, 0.05)) +
scale_x_date(
date_labels = "%b %Y" # "Mar 2025" format
, date_breaks = "2 month"
) +
scale_fill_gradient2(
low = "#ff4d4d", # red for negative
mid = "gray70", # near zero
high = "#4daf4a", # green for positive
midpoint = 0, # center at 0%
guide = "none"
) +
geom_text(
data = last_point_global,
aes(label = scales::percent(yoy_change_pct, accuracy = 0.1), y = yoy_change_pct),
vjust = 1.5, # adjust vertical position above the bar
size = 3,
fontface = "bold"
) +
theme_minimal() +
theme(
strip.background = element_rect(fill = "grey30"),
strip.text = element_text(color = "grey97", face = "bold"),
legend.background = element_rect(color = NA),
plot.title = element_text(size = 20, face = "bold"),
plot.subtitle = element_text(size = 12),
plot.caption = element_text(colour = "grey60"),
plot.title.position = "plot",
# panel.grid.minor.x = element_blank(),
# panel.grid.minor.y = element_blank()
) +
labs(
x = "",
y = "Year over Year Change (%)",
title = "Foreign Air Arrivals into the US Plummet",
subtitle = "Inbound passenger volume from non-US passport holders dropped nearly 10% year-over-year\nin March 2025, as the Trump administration's erratic behavior has depressed business and\nleisure travel into the US.",
caption = "Source: I-92 data from trade.gov\nconormclaughlin.net"
)
Faceted Trend Chart
# First find the latest date per geo
last_points <- df %>%
filter(geo != "global", date >= as.Date("2024-01-01")) %>%
group_by(geo) %>%
filter(date == max(date)) %>%
ungroup()
df %>%
filter(geo != "global", date >= as.Date("2024-01-01")) %>%
ggplot(aes(x = date, y = yoy_change_pct, fill = yoy_change_pct)) +
geom_col() +
scale_x_date(
date_labels = "%b %y" # "Mar 2025" format
, date_breaks = "4 month"
) +
scale_y_continuous(label = scales::percent) +
geom_text(
data = last_points,
aes(label = scales::percent(yoy_change_pct, accuracy = 0.1), y = yoy_change_pct),
vjust = 1.5, # adjust vertical position above the bar
size = 2,
fontface = "bold"
) +
facet_wrap(~geo, labeller = as_labeller(geo_labels)) +
scale_fill_gradient2(
low = "#ff4d4d", # red for negative
mid = "gray70", # near zero
high = "#4daf4a", # green for positive
midpoint = 0, # center at 0%
guide = "none"
) +
theme_minimal() +
theme(
strip.background = element_rect(fill = "grey30"),
strip.text = element_text(color = "grey97", face = "bold"),
legend.background = element_rect(color = NA),
plot.title = element_text(size = 20, face = "bold"),
plot.subtitle = element_text(size = 12),
plot.caption = element_text(colour = "grey60"),
plot.title.position = "plot",
panel.grid.minor.x = element_blank(),
#panel.grid.minor.y = element_blank(),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
labs(
x = "",
y = "Year over Year Change (%)",
title = "YoY Change in Foreign Air Arrivals to the US, by Region",
subtitle = "February and March 2025 saw year-over-year (YoY) declines from most originating regions, with traffic from Europe\nand Latin America dropping most sharply in response to the change in Administration.",
caption = "Source: I-92 data from trade.gov\nconormclaughlin.net"
)
March 2025 YoY Chart
df %>%
filter(geo != "global", date == as.Date("2025-03-01")) %>%
ggplot(aes(x = yoy_change, y = reorder(geo, yoy_change), fill = yoy_change)) +
geom_col() +
scale_x_continuous(label = scales::comma) +
geom_text(
data = last_points,
aes(label = scales::comma(yoy_change/1000, accuracy = 0.1, suffix = "k"), x = yoy_change),
vjust = 0.5, # adjust vertical position above the bar
hjust = -0.1,
size = 3,
fontface = "bold"
) +
scale_fill_gradient2(
low = "#ff4d4d", # red for negative
mid = "gray70", # near zero
high = "#4daf4a", # green for positive
midpoint = 0, # center at 0%
guide = "none"
) +
theme_minimal() +
theme(
strip.background = element_rect(fill = "grey30"),
strip.text = element_text(color = "grey97", face = "bold"),
legend.background = element_rect(color = NA),
plot.title = element_text(size = 20, face = "bold"),
plot.subtitle = element_text(size = 12),
plot.caption = element_text(colour = "grey60"),
plot.title.position = "plot",
panel.grid.minor.x = element_blank(),
#panel.grid.minor.y = element_blank(),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
labs(
x = "\nYoY Change in Passengers",
y = "",
title = "Absolute Change in Foreign Air Arrivals to the USA, by Region",
subtitle = "Year-over-year (YoY) for March 2025 vs March 2024",
caption = "Source: I-92 data from trade.gov\nconormclaughlin.net"
)