Story - 6 : What Is The State of Food Security and Nutrition in the US?

Background

The United Nations Food and Agriculture Organization’s report The State of Food Security and Nutrition in the World 2022 may lead some to assume that hunger, malnutrition, and starvation are primarily global or “elsewhere” problems. However, these issues are also serious and persistent within the United States. Despite being one of the wealthiest nations in the world, the U.S. faces significant and rising levels of food insecurity across many demographic groups.

Research Analysis

This analysis explores food security and nutrition across the United States, focusing on differences between states and key demographic groups. It examines food insecurity among men, women, and various age groups using federal data sources. A main goal is to understand how food insecurity relates to poverty, nutrition outcomes, and demographic factors.

Data for this analysis will come from Feeding America, the U.S. Census, FEO, BRFSS, and NHANES:

Research Question

What is the relationship between food insecurity, nutrition outcomes, and poverty in the U.S, and how do these conditions differ by social and economic groups?

Hypothesis

States with higher poverty rates will have significantly higher levels of food insecurity and poorer nutrition outcomes. Additionally, food insecurity will disproportionately affect women, lower SES groups, and older adults.

Data Import and Preparation

# Feeding America Data
feedamer <- read_excel(
  "C://Users//Kesha//Desktop//Fall 2025//Data 608//MMG2025_2019-2023_Data_To_Share.xlsx",
  sheet = "State"
)

# US Census
readRenviron("~/.Renviron")
census_api_key("4b762226a7f4207fda1f9a0dc463d6a5c84458cc", install = TRUE, overwrite = TRUE)
## Your original .Renviron will be backed up and stored in your R HOME directory if needed.
## Your API key has been stored in your .Renviron and can be accessed by Sys.getenv("CENSUS_API_KEY"). 
## To use now, restart R or run `readRenviron("~/.Renviron")`
## [1] "4b762226a7f4207fda1f9a0dc463d6a5c84458cc"
poverty_data <- get_acs(
  geography = "state",
  variables = c(
    # e.g., total below poverty (B17001_002) and total population (B17001_001)
    overall_poverty = "B17001_002",
    total_pop        = "B17001_001"
  ),
  survey = "acs5",    
  year = 2023
)
## Getting data from the 2019-2023 5-year ACS
# NHANES data (17-18)

# Demographics 
demo_j <- read_xpt(
  "https://wwwn.cdc.gov/Nchs/Data/Nhanes/Public/2017/DataFiles/DEMO_J.XPT")

# Food security 
fsq_j <- read_xpt("https://wwwn.cdc.gov/Nchs/Data/Nhanes/Public/2017/DataFiles/FSQ_J.XPT")

# Dietary
dr1tot <- read_xpt("https://wwwn.cdc.gov/Nchs/Data/Nhanes/Public/2017/DataFiles/DR1TOT_J.xpt")

# World Food Insecurity Data
feo_data <- read_csv("C://Users//Kesha//Desktop//Fall 2025//Data 608//world_feo.csv")
## Rows: 3124 Columns: 60
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (39): STRUCTURE, STRUCTURE_ID, STRUCTURE_NAME, ACTION, FREQ, Frequency, ...
## dbl  (6): TIME_PERIOD, OBS_VALUE, UNIT_MULT, UPPER_BOUND, LOWER_BOUND, REF_A...
## lgl (15): Time Period, Observation value, BASE_PER, Base period, Time detail...
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# BRFSS Nutritional Data

brfss <- read_csv("C://Users//Kesha//Desktop//Fall 2025//Data 608//brfss.csv")
## Rows: 106260 Columns: 33
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (25): LocationAbbr, LocationDesc, Datasource, Class, Topic, Question, Da...
## dbl  (6): YearStart, YearEnd, Data_Value, Data_Value_Alt, Low_Confidence_Lim...
## num  (1): Sample_Size
## lgl  (1): Data_Value_Unit
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

Let’s look at where the U.S falls in terms of Food Insecurity compared to other Countries

regions <- c(
  "Africa", "Americas", "Eastern Africa", "Eastern Asia", "Southern Africa", 
  "Northern Africa", "South-eastern Asia", "Western Africa", "Western Asia",
  "Middle Africa", "High income countries", "Low income countries",
  "Upper middle income countries", "Lower middle income countries",
  "Small Island Developing States (SIDS)", "Land Locked Developing Countries (LLDCs)",
  "Least Developed Countries (LDCs)", "Low Income Food Deficit Countries (LIFDCs)",
  "Europe, Northern America, Australia and New Zealand",
  "Northern America and Europe", "Oceania excluding Australia and New Zealand",
  "Central Asia", "Central Asia and Southern Asia", "Eastern Asia and South-eastern Asia",
  "Southern Asia", "Southern Europe", "Northern Europe", "South America", "Central America", "Caribbean", 
  "United Kingdom of Great Britain and Northern Ireland", "Latin America and the Caribbean", "Western Asia and Northern Africa")

df_feo <- feo_data %>%
  filter(
    TIME_PERIOD == 2023,
    !is.na(OBS_VALUE),
    SEX %in% c("T", "_T"),   
    AGE == "_T",            
    !Area %in% regions       
  ) %>%
  select(
    Country = Area,
    FoodInsecurity = OBS_VALUE,
    TIME_PERIOD
  ) %>%
  distinct(Country, .keep_all = TRUE)  

df_countries <- df_feo  %>%
  filter(!Country %in% regions)

# Get top and bottom 15
top_bottom <- df_countries %>%
  arrange(desc(FoodInsecurity)) %>%
  slice(c(1:15, (n()-14):n()))

# Incljude US
us_row <- df_countries %>% filter(Country == "United States of America")
top_bottom <- bind_rows(top_bottom, us_row) %>%
  distinct(Country, .keep_all = TRUE)  # remove duplicates if US is already in top/bottom

labels <- top_bottom %>%
  filter(Country %in% c("United States of America", "Sierra Leone", "Belarus"))

# Plot
plot_data <- top_bottom %>%
  arrange(FoodInsecurity) %>%
  mutate(
    row_id = row_number(),
    highlight = case_when(
      FoodInsecurity == max(FoodInsecurity) ~ "highest",
      FoodInsecurity == min(FoodInsecurity) ~ "lowest",
      Country == "United States of America" ~ "US",
      TRUE ~ "normal"
    ),
    label = ifelse(highlight != "normal", round(FoodInsecurity, 1), NA)
  )

# Build the plot
ggplot(plot_data, aes(x = FoodInsecurity, y = reorder(Country, row_id), fill = highlight)) +
  geom_col(width = 0.7) +
  
  geom_text(
    aes(label = label),
    hjust = -0.15,
    size = 4,
    fontface = "bold",
    na.rm = TRUE
  ) +

  scale_fill_manual(values = c(
    "highest" = "#f46d60",  
    "lowest" = "#74add1",   
    "US" = "#fed976",   
    "normal" = "#d3d3d3"  
  )) +

  scale_x_continuous(expand = expansion(mult = c(0, 0.15))) +

  labs(
    title = "Where Does the U.S. Stand in Global Food Insecurity?",
    subtitle = "The United States (10.4%) sits far below the worst-hit countries like Sierra Leone (89.8%)\nbut above the most food-secure nations such as Belarus (1.1%).",
    caption = "Despite being a wealthy nation, the U.S. still experiences higher food insecurity than other developed countries.\nRed highlights the worst-affected country, blue the most food-secure, and gold marks the U.S.",
    x = "Food Insecurity (%)",
    y = "Country"
  ) +

  theme_minimal(base_size = 15) +
  theme(
    plot.title = element_text(face = "bold", size = 20, margin = margin(b = 10)),
    plot.subtitle = element_text(size = 14, margin = margin(b = 15)),
    plot.caption = element_textbox_simple(
      size = 13, face = "italic", color = "grey20",
      margin = margin(t = 20), lineheight = 1.2
    ),
    axis.text.x = element_text(size = 12),
    axis.text.y = element_text(size = 10),
    axis.title.x = element_text(face = "bold"),
    axis.title.y = element_text(face = "bold"),
    panel.grid.minor = element_blank(),
    panel.grid.major.y = element_blank(),
    legend.position = "none"
  )

Food Insecurity by State

df_2023 <- feedamer %>% 
  filter(Year == 2023) %>% 
  mutate(
    OverallFI = as.numeric(str_replace(`Overall Food Insecurity Rate`, "%", "")),
    state_abbrev = State)


highest <- df_2023 %>% slice_max(OverallFI, n = 1)
lowest  <- df_2023 %>% slice_min(OverallFI, n = 1)

p <- plot_ly(
  df_2023,
  type = "choropleth",
  locationmode = "USA-states",
  locations = ~state_abbrev,
  z = ~OverallFI,
  colorscale = list(
  c(0, '#ffffcc'),  
  c(0.25, '#ffed66'), 
  c(0.5, '#ff9900'),  
  c(0.75, '#ff3300'), 
  c(1, '#990000') 
)
,
  colorbar = list(title = "<b>Food Insecurity (%)</b>"),
  marker = list(line = list(color = "white", width = 1)),
  text = ~paste0(
    "<b>", `State Name`, "</b><br>",
    "Food Insecurity: <b>", OverallFI, "%</b><br>",
    "# Insecure: ", format(`# of Food Insecure Persons Overall`, big.mark=",")),
  hoverinfo = "text") %>%
  layout(
    width = 1200,   
    height = 900, 
    title = list(
      text = paste0(
        "<b>The Hunger Gap Across the U.S.: A 2023 Map of States with the Highest and Lowest Food Insecurity Rates</b><br>",
        "<sup>In 2023, food insecurity varied dramatically across the country, ",
        "with Southern states facing some of the highest rates.</sup>"),
      x = 0.5),
    geo = list(
      scope = "usa",
      projection = list(type = "albers usa"),
      showlakes = TRUE,
      lakecolor = "white"),
    margin = list(l = 30, r = 30, t = 120, b = 20))
## Warning: Specifying width/height in layout() is now deprecated.
## Please specify in ggplotly() or plot_ly()
p <- suppressWarnings({
  p %>%
    add_trace(
      type = "scattergeo",
      mode = "markers+text",
      locations = highest$state_abbrev,
      locationmode = "USA-states",
      marker = list(size = 10, color = "lightyellow", line = list(width = 1, color = "lightyellow")),
      text = paste0("Highest: ", highest$`State Name`, "\n", highest$OverallFI, "%"),
      textposition = "top center",
       textfont = list(color = "white", size = 12),
      showlegend = FALSE
    ) %>%
    add_trace(
      type = "scattergeo",
      mode = "markers+text",
      locations = lowest$state_abbrev,
      locationmode = "USA-states",
      marker = list(size = 10, color = "darkred", line = list(width = 1, color = "darkred")),
      text = paste0("Lowest: ", lowest$`State Name`, "\n", lowest$OverallFI, "%"),
      textposition = "bottom center",
      textfont = list(color = "black", size = 12),
      showlegend = FALSE
    )
})


p
## Warning: 'scattergeo' objects don't have these attributes: 'z', 'colorscale', 'colorbar'
## Valid attributes include:
## 'connectgaps', 'customdata', 'customdatasrc', 'featureidkey', 'fill', 'fillcolor', 'geo', 'geojson', 'hoverinfo', 'hoverinfosrc', 'hoverlabel', 'hovertemplate', 'hovertemplatesrc', 'hovertext', 'hovertextsrc', 'ids', 'idssrc', 'lat', 'latsrc', 'legendgroup', 'legendgrouptitle', 'legendrank', 'line', 'locationmode', 'locations', 'locationssrc', 'lon', 'lonsrc', 'marker', 'meta', 'metasrc', 'mode', 'name', 'opacity', 'selected', 'selectedpoints', 'showlegend', 'stream', 'text', 'textfont', 'textposition', 'textpositionsrc', 'textsrc', 'texttemplate', 'texttemplatesrc', 'transforms', 'type', 'uid', 'uirevision', 'unselected', 'visible', 'key', 'set', 'frame', 'transforms', '_isNestedKey', '_isSimpleKey', '_isGraticule', '_bbox'
## Warning: 'scattergeo' objects don't have these attributes: 'z', 'colorscale', 'colorbar'
## Valid attributes include:
## 'connectgaps', 'customdata', 'customdatasrc', 'featureidkey', 'fill', 'fillcolor', 'geo', 'geojson', 'hoverinfo', 'hoverinfosrc', 'hoverlabel', 'hovertemplate', 'hovertemplatesrc', 'hovertext', 'hovertextsrc', 'ids', 'idssrc', 'lat', 'latsrc', 'legendgroup', 'legendgrouptitle', 'legendrank', 'line', 'locationmode', 'locations', 'locationssrc', 'lon', 'lonsrc', 'marker', 'meta', 'metasrc', 'mode', 'name', 'opacity', 'selected', 'selectedpoints', 'showlegend', 'stream', 'text', 'textfont', 'textposition', 'textpositionsrc', 'textsrc', 'texttemplate', 'texttemplatesrc', 'transforms', 'type', 'uid', 'uirevision', 'unselected', 'visible', 'key', 'set', 'frame', 'transforms', '_isNestedKey', '_isSimpleKey', '_isGraticule', '_bbox'

How does demographics play a role in Food Insecurity

# Prepare dataset
demo_fi <- demo_j %>%
  select(SEQN, Age = RIDAGEYR, Gender = RIAGENDR, Race = RIDRETH1, Income = INDHHIN2, Education = DMDEDUC2) %>%
  filter(Age >= 18) %>%  # adults only
  left_join(fsq_j %>% select(SEQN, FSDAD), by = "SEQN") %>%
  filter(!is.na(FSDAD)) %>%
  mutate(
    FoodInsecure = ifelse(FSDAD %in% c(3,4), 1, 0),
    Gender = factor(Gender, labels = c("Men", "Women")),
    # Age categories
    AgeGroup = case_when(
      Age >= 18 & Age <= 24 ~ "18-24",
      Age >= 25 & Age <= 44 ~ "25-44",
      Age >= 45 & Age <= 59 ~ "45-59",
      Age >= 60 & Age <= 74 ~ "60-74",
      Age >= 75             ~ "75+"
    ),
    AgeGroup = factor(AgeGroup, levels = c("18-24","25-44","45-59","60-74","75+"))
  )

# Education factor
demo_fi <- demo_fi %>%
  filter(Education %in% c(1,2,3,4)) %>%
  mutate(Education = factor(Education,
                            levels = c(1,2,3,4),
                            labels = c("Less than 9th grade",
                                       "9-11th grade",
                                       "High school/GED",
                                       "Some college/AA")))

# Race factor
demo_fi <- demo_fi %>%
  filter(Race %in% c(1,2,3,4,5)) %>%
  mutate(Race = factor(Race,
                       levels = c(1,2,3,4,5),
                       labels = c("Mexican American",
                                  "Other Hispanic",
                                  "Non-Hispanic White",
                                  "Non-Hispanic Black",
                                  "Other / Multi-Racial")))

# Income factor
demo_fi <- demo_fi %>%
  filter(Income %in% c(1:14)) %>%  # keep valid codes, exclude Refused (15)
  mutate(Income = factor(Income,
                         levels = c(1:14),
                         labels = c("$0-4,999","$5,000-9,999","$10,000-14,999",
                                    "$15,000-19,999","$20,000-24,999","$25,000-34,999",
                                    "$35,000-44,999","$45,000-54,999","$55,000-64,999",
                                    "$65,000-74,999","$20,000 and over","Under $20,000",
                                    "$75,000-99,999","$100,000 and over")))


fi_by_demo <- demo_fi %>%
  pivot_longer(cols = c(Gender, AgeGroup, Race, Income, Education),
               names_to = "DemographicType",
               values_to = "Group") %>%
  group_by(DemographicType, Group) %>%
  summarise(FI_Rate = mean(FoodInsecure, na.rm = TRUE) * 100, .groups = "drop")


fi_by_demo <- fi_by_demo %>%
  group_by(DemographicType) %>%
  mutate(
    highlight = ifelse(FI_Rate %in% head(sort(FI_Rate, decreasing = TRUE), 1), "high", "normal")
  ) %>%
  ungroup()

# Plot
ggplot(fi_by_demo, aes(x = reorder(Group, FI_Rate), y = FI_Rate, fill = highlight)) +
  geom_col(show.legend = FALSE, width = 0.7) +
  geom_text(aes(label = paste0(round(FI_Rate, 1), "%")),
            hjust = -0.1,
            size = 4,
            fontface = "bold",
            color = ifelse(fi_by_demo$highlight == "high", "red", "black")) +
  facet_wrap(~DemographicType, scales = "free_y") +
  coord_flip() +
  scale_fill_manual(values = c("high" = "#f46d60", "normal" = "#d3d3d3")) +
  scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) + 
  scale_y_continuous(expand = expansion(mult = c(0, 0.15))) +
  labs(
    title = "Unequal Plates: How Social and Economic Factors Shape Food Insecurity in the U.S.",
    subtitle = "Rates of food insecurity among adults differ sharply by age, gender, race, income, and education, with middle-aged adults, women,\nlower-income households, and less-educated groups most affected.",
    x = "",
    y = "Food Insecure (%)",
    caption = "This analysis highlights the populations at greatest risk: adults aged 45–59, women, those with less than a 9th-grade education,\nlower-income households, and certain Hispanic groups experience higher rates of low or very low food security."
  ) +
  theme_minimal(base_size = 11) +
  theme(
    axis.text.y = element_text(size = 10),  
    strip.text = element_text(face = "bold", size = 13),
    plot.margin = margin(10, 50, 13, 13),  
    plot.title = element_text(face = "bold", size = 20),
    plot.subtitle = element_text(size = 14, margin = margin(b = 15)),
    plot.caption = element_text(size = 12, face = "italic", color = "gray20", margin = margin(t = 10)),
    axis.text.x = element_text(size = 11)
  )

Poverty, Nutrition, and Food Insecurity by State

# Clean data before merging
acs_clean <- poverty_data %>%
  select(NAME, variable, estimate) %>%
  pivot_wider(names_from = variable, values_from = estimate) %>%
  mutate(
    PovertyRate = overall_poverty / total_pop * 100
  ) %>%
  rename(State = NAME)

merged_df <- df_2023 %>%
  select(State = `State Name`, `Overall Food Insecurity Rate`) %>% 
  left_join(acs_clean %>% select(State, PovertyRate), by = "State") %>%
  mutate(
    OverallFoodInsecurity = as.numeric(gsub("%", "", `Overall Food Insecurity Rate`))
  )

merged_df <- merged_df %>%
  mutate(
    Highlight = case_when(
      OverallFoodInsecurity >= quantile(OverallFoodInsecurity, 0.95, na.rm = TRUE) ~ "Highest FI",
      OverallFoodInsecurity <= quantile(OverallFoodInsecurity, 0.05, na.rm = TRUE) ~ "Lowest FI",
      TRUE ~ "Other"
    )
  )

colors <- c("Highest FI" = "darkred", "Lowest FI" = "darkgreen", "Other" = "lightblue")

# Plot
p1 <- plot_ly(
  merged_df,
  x = ~PovertyRate,
  y = ~OverallFoodInsecurity,
  type = 'scatter',
  mode = 'markers',
  color = ~Highlight,
  colors = colors,
  text = ~paste(
    "<b>", State, "</b><br>",
    "Poverty Rate: ", round(PovertyRate,1), "%<br>",
    "Food Insecurity: ", round(OverallFoodInsecurity,1), "%"
  ),
  hoverinfo = "text",
  marker = list(size = 12)
) %>%
  layout(
     width = 1200,
    height = 900,
    title = list(
      text = paste0(
        "States Facing Higher Poverty Also Experience Higher Food Insecurity",
        "<br><sup>States with the highest food insecurity—Mississippi, Arkansas, Oklahoma, and Louisiana—also face elevated poverty, while Minnesota,\nNorth Dakota, and New Hampshire report the lowest levels of both.</sup>"
      ),
      x = 0.5,
      xanchor = "center",
      font = list(size = 18)
    ),
    xaxis = list(title = "Poverty Rate (%)"),
    yaxis = list(title = "Food Insecurity Rate (%)"),
    legend = list(title = list(text = "Highlight")),
    hovermode = "closest"
  )
## Warning: Specifying width/height in layout() is now deprecated.
## Please specify in ggplotly() or plot_ly()
p1
# Food Insecurity vs Nutrition

# Filter only fruit/vegetable questions
nutrition_state <- brfss %>%
  filter(YearStart == 2019,
         Topic == "Fruits and Vegetables - Behavior",
         Question %in% c(
           "Percent of adults who report consuming fruit less than one time daily",
           "Percent of adults who report consuming vegetables less than one time daily"
         ))


nutrition_summary <- nutrition_state %>%
  group_by(State = LocationDesc, Question) %>%
  summarise(mean_percent = mean(Data_Value, na.rm = TRUE), .groups = "drop") %>%
  pivot_wider(names_from = Question, values_from = mean_percent) %>%
  mutate(
    fruit_meeting_rec = 100 - `Percent of adults who report consuming fruit less than one time daily`,
    veg_meeting_rec   = 100 - `Percent of adults who report consuming vegetables less than one time daily`
  ) %>%
  select(State, fruit_meeting_rec, veg_meeting_rec) %>%
  filter(!is.na(fruit_meeting_rec) & !is.na(veg_meeting_rec))  # remove states with NA


full_df <- merged_df %>%
  left_join(nutrition_summary, by = "State")


scatter_df <- full_df %>%
  select(State, OverallFoodInsecurity, fruit_meeting_rec, veg_meeting_rec) %>%
  pivot_longer(
    cols = c(fruit_meeting_rec, veg_meeting_rec),
    names_to = "Type",
    values_to = "PercentMeetingRec"
  ) %>%
  filter(!is.na(PercentMeetingRec) & !is.na(OverallFoodInsecurity)) %>%
  mutate(Color = ifelse(Type == "fruit_meeting_rec", "forestgreen", "orange"),
         Label = paste(State, "<br>", Type, "% meeting rec:", round(PercentMeetingRec,1),
                       "<br>Food Insecurity:", round(OverallFoodInsecurity,1), "%"))


top3_states <- scatter_df %>%
  group_by(State) %>%
  summarise(FI = unique(OverallFoodInsecurity)) %>%
  slice_max(FI, n = 3) %>%
  pull(State)

bottom3_states <- scatter_df %>%
  group_by(State) %>%
  summarise(FI = unique(OverallFoodInsecurity)) %>%
  slice_min(FI, n = 3) %>%
  pull(State)

# Label only points in top/bottom states
scatter_df <- scatter_df %>%
  mutate(LabelState = ifelse(State %in% c(top3_states, bottom3_states), State, NA))

# Scatter plot
p2 <- plot_ly(
  scatter_df,
  x = ~PercentMeetingRec,
  y = ~OverallFoodInsecurity,
  type = 'scatter',
  mode = 'markers+text',
  color = ~OverallFoodInsecurity,
  colors = colorRamp(c("gold", "#990000")),
  symbol = ~Type,
  symbols = c('circle','square'),
  text = ~LabelState,             
  textposition = "top center",
  hoverinfo = "text",             
  marker = list(size = 12),
  hovertext = ~Label              
) %>%
  layout(
     width = 1500,   
    height = 800, 
    title = list(
  text = paste0(
    "States With The Highest Food Insecurity are less likely to Meet Fruit Intake",
    "<br><sup>Mississippi, Arkansas, and Oklahoma have the highest food insecurity rates and the lowest proportion of adults meeting recommended fruit intake, highlighting persistent nutritional disparities.</sup>"
  ),
  x = 0.5,
  xanchor = "center",
  font = list(size = 18)
),
    xaxis = list(title = "% Adults Meeting Fruit & Vegetable Recommendations"),
    yaxis = list(title = "Overall Food Insecurity (%)"),
    legend = list(title = list(text = "Nutrition Type / Food Insecurity")),
    hovermode = "closest",
    coloraxis = list(colorbar = list(title = "Food Insecurity (%)"))
  )
## Warning: Specifying width/height in layout() is now deprecated.
## Please specify in ggplotly() or plot_ly()
p2
## Warning: textfont.color doesn't (yet) support data arrays
## Warning: textfont.color doesn't (yet) support data arrays
## Warning: textfont.color doesn't (yet) support data arrays
## Warning: textfont.color doesn't (yet) support data arrays

What is the relationship between food insecurity, nutrition outcomes, and poverty the across U.S.?

df <- demo_j %>%
  select(SEQN,
         Age = RIDAGEYR,
         Gender = RIAGENDR,
         PovertyRatio = INDFMPIR) %>%
  left_join(fsq_j %>% select(SEQN, FSDAD), by = "SEQN") %>% 
  left_join(dr1tot %>% 
              select(SEQN,
                     Calories = DR1TKCAL,
                     AddedSugar = DR1TSUGR,
                     TotalFat = DR1TTFAT,
                     Fiber = DR1TFIBE),
            by = "SEQN") %>%
  filter(Age >= 18, !is.na(FSDAD), !is.na(PovertyRatio))


df <- df %>% mutate(
  FoodInsecure = case_when(
    FSDAD %in% c(1,2) ~ "Food Secure",
    FSDAD %in% c(3,4) ~ "Food Insecure"
  )
)

df <- df %>% mutate(
  PovertyLevel = case_when(
    PovertyRatio < 1           ~ "Below Poverty",
    PovertyRatio >= 1 & PovertyRatio < 2 ~ "Low Income",
    PovertyRatio >= 2 & PovertyRatio < 4 ~ "Middle Income",
    PovertyRatio >= 4           ~ "High Income"
  ),
  PovertyLevel = factor(PovertyLevel,
                        levels = c("Below Poverty", "Low Income", "Middle Income", "High Income"))
)

df <- df %>% mutate(
  NutritionScore = scale(Fiber - AddedSugar - TotalFat)[,1]
)

ggplot(df, aes(x = PovertyLevel, y = NutritionScore, fill = FoodInsecure)) +
  geom_violin(alpha = 0.7) +
  scale_fill_manual(
    values = c("Food Secure" = "#74add1", "Food Insecure" = "#f46d60"),
    name = "Food Security Status"   # This sets the legend title
  ) +
  labs(
    title = "Food Insecurity Is Linked to Lower Nutrition Scores Across All Poverty Levels in the U.S.",
    subtitle = "Food insecurity is associated with worse nutrition no matter what income level you’re in. Even within the same poverty category,\nfood-insecure people tend to have lower nutrition scores and more variability.",
    x = "Poverty Level",
    y = "Nutrition Score"
  ) +
  theme_minimal(base_size = 14) +
  theme(
    plot.title = element_text(size = 20, face = "bold", margin = margin(b = 5)),
    plot.subtitle = element_text(size = 15, margin = margin(b = 15)),
    plot.caption = element_text(size = 10, color = "gray40", margin = margin(t = 15)),
    strip.text = element_text(size = 14, face = "bold"),
    axis.text.x = element_text(size = 12, angle = 15, hjust = 1),
    axis.text.y = element_text(size = 12),
    legend.position = "right"  # show the legend on the right
  )
## Warning: Removed 628 rows containing non-finite outside the scale range
## (`stat_ydensity()`).

Conclusion

This analysis demonstrates that food insecurity remains a significant challenge in the United States, affecting millions of households despite the country’s wealth. Food insecurity is closely tied to poverty, with states that have higher poverty rates, particularly in the South, also experiencing the highest levels of food insecurity. Vulnerable populations, including women, middle-aged adults, lower-income households, less-educated individuals, and certain racial and ethnic groups, are disproportionately affected.

Nutrition outcomes are also impacted: food-insecure individuals consistently show poorer dietary quality, consuming fewer fruits and vegetables and having lower overall nutrition scores, regardless of income level. While the U.S. fares better than the most food-insecure nations globally, it lags behind other high-income countries, highlighting the need for targeted interventions.

These findings underscore the importance of policies and programs that address both economic and nutritional vulnerabilities to reduce food insecurity and improve health outcomes across the U.S.

Citations

Coleman-Jensen, A., Rabbitt, M. P., Gregory, C. A., & Singh, A. (2024). Household food security in the United States in 2023 (ERR-337). U.S. Department of Agriculture, Economic Research Service. https://www.ers.usda.gov/webdocs/publications/109896/err-337.pdf

Feeding America. Map the Meal Gap. “Local Food Insecurity & Cost Estimates by County.” https://www.feedingamerica.org/research/map-the-meal-gap/by-county

Food and Agriculture Organization of the United Nations. (2025). SDG Indicator 2.1.2: Prevalence of moderate or severe food insecurity in the population (FIES-based). FAO Sustainable Development Goals Data Portal.

U.S. Department of Agriculture, Economic Research Service. (2024). Food security and nutrition assistance. https://www.ers.usda.gov/data-products/ag-and-food-statistics-charting-the-essentials/food-security-and-nutrition-assistance/

U.S. Department of Agriculture, Economic Research Service. (2024). Key statistics & graphics: Food security in the U.S. https://www.ers.usda.gov/topics/food-nutrition-assistance/food-security-in-the-us/key-statistics-graphics/