Skip to content

Instantly share code, notes, and snippets.

@walkerke
Last active June 27, 2025 23:05
Show Gist options
  • Save walkerke/8b4230d3e1a098ed9f79ee9c62ac5e74 to your computer and use it in GitHub Desktop.
Save walkerke/8b4230d3e1a098ed9f79ee9c62ac5e74 to your computer and use it in GitHub Desktop.

Revisions

  1. walkerke revised this gist Jun 27, 2025. 1 changed file with 1 addition and 4 deletions.
    5 changes: 1 addition & 4 deletions pyramids-2024.R
    Original file line number Diff line number Diff line change
    @@ -24,13 +24,10 @@ ggplot(pyramid_data, aes(x = value_pct, y = AGEGROUP, fill = SEX)) +
    geom_vline(xintercept = 0, color = "gray40", linewidth = 0.5) +
    scale_x_continuous(
    labels = function(x) paste0(abs(x), "%"),
    # breaks = seq(-2, 2, 0.5),
    expand = expansion(mult = c(0.05, 0.05))
    ) +
    scale_y_discrete(
    labels = function(x) str_remove_all(x, "Age\\s|\\syears") %>%
    str_replace("Under 5", "0-4") %>%
    str_replace("85 and older", "85+")
    labels = function(x) str_remove_all(x, "Age\\s|\\syears")
    ) +
    scale_fill_manual(
    values = c("Female" = "#A23B72", "Male" = "#2E86AB"),
  2. walkerke revised this gist Jun 27, 2025. 1 changed file with 8 additions and 6 deletions.
    14 changes: 8 additions & 6 deletions pyramids-2024.R
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,5 @@
    library(tidyverse)
    library(tidycensus) # `pak::pak("walkerke/tidycensus")` pending CRAN update
    library(tidycensus)

    # Get the data
    pyramid_data <- get_estimates(
    @@ -11,18 +11,20 @@ pyramid_data <- get_estimates(
    year = 2024
    ) %>%
    filter(AGEGROUP != "All ages", SEX != "Both sexes", NAME %in% c("Maine", "Utah")) %>%
    group_by(NAME) %>%
    mutate(
    value_pct = value / sum(value) * 100,
    value_pct = ifelse(SEX == "Male", -value_pct, value_pct)
    )
    ) %>%
    ungroup()

    # Build the visualization
    # Create enhanced population pyramids
    ggplot(pyramid_data, aes(x = value_pct, y = AGEGROUP, fill = SEX)) +
    geom_col(alpha = 0.9, width = 0.85) +
    geom_vline(xintercept = 0, color = "gray40", linewidth = 0.5) +
    scale_x_continuous(
    labels = function(x) paste0(abs(x), "%"),
    breaks = seq(-2, 2, 0.5),
    # breaks = seq(-2, 2, 0.5),
    expand = expansion(mult = c(0.05, 0.05))
    ) +
    scale_y_discrete(
    @@ -71,7 +73,7 @@ ggplot(pyramid_data, aes(x = value_pct, y = AGEGROUP, fill = SEX)) +
    # Panel styling
    panel.spacing = unit(1.5, "lines"),

    # Legend styling
    # Legend styling - moved to bottom with horizontal layout
    legend.position = "bottom",
    legend.direction = "horizontal",
    legend.title = element_text(size = 10, margin = margin(r = 3)), # Reduced spacing
    @@ -83,4 +85,4 @@ ggplot(pyramid_data, aes(x = value_pct, y = AGEGROUP, fill = SEX)) +

    # Overall plot margins
    plot.margin = margin(20, 20, 10, 20)
    )
    )
  3. walkerke revised this gist Jun 27, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion pyramids-2024.R
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,5 @@
    library(tidyverse)
    library(tidycensus)
    library(tidycensus) # `pak::pak("walkerke/tidycensus")` pending CRAN update

    # Get the data
    pyramid_data <- get_estimates(
  4. walkerke created this gist Jun 27, 2025.
    86 changes: 86 additions & 0 deletions pyramids-2024.R
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,86 @@
    library(tidyverse)
    library(tidycensus)

    # Get the data
    pyramid_data <- get_estimates(
    geography = "state",
    product = "characteristics",
    breakdown = c("AGEGROUP", "SEX"),
    breakdown_labels = TRUE,
    vintage = 2024,
    year = 2024
    ) %>%
    filter(AGEGROUP != "All ages", SEX != "Both sexes", NAME %in% c("Maine", "Utah")) %>%
    mutate(
    value_pct = value / sum(value) * 100,
    value_pct = ifelse(SEX == "Male", -value_pct, value_pct)
    )

    # Build the visualization
    ggplot(pyramid_data, aes(x = value_pct, y = AGEGROUP, fill = SEX)) +
    geom_col(alpha = 0.9, width = 0.85) +
    geom_vline(xintercept = 0, color = "gray40", linewidth = 0.5) +
    scale_x_continuous(
    labels = function(x) paste0(abs(x), "%"),
    breaks = seq(-2, 2, 0.5),
    expand = expansion(mult = c(0.05, 0.05))
    ) +
    scale_y_discrete(
    labels = function(x) str_remove_all(x, "Age\\s|\\syears") %>%
    str_replace("Under 5", "0-4") %>%
    str_replace("85 and older", "85+")
    ) +
    scale_fill_manual(
    values = c("Female" = "#A23B72", "Male" = "#2E86AB"),
    breaks = c("Male", "Female") # This controls the legend order
    ) +
    facet_wrap(~NAME, scales = "free_x") +
    labs(
    title = "The Oldest (Maine) and Youngest (Utah) States in the U.S.",
    subtitle = "Population distribution by age and sex, 2024",
    x = "Percentage of State Population",
    y = "Age Group",
    caption = "Source: U.S. Census Bureau, Vintage 2024 Population Estimates | tidycensus R package"
    ) +
    theme_minimal(base_size = 11) +
    theme(
    # Title and text styling
    plot.title = element_text(face = "bold", size = 18, hjust = 0.5,
    margin = margin(b = 5)),
    plot.subtitle = element_text(color = "gray50", size = 13, hjust = 0.5,
    margin = margin(b = 15)),
    plot.caption = element_text(color = "gray60", size = 9, hjust = 1,
    margin = margin(t = 15)),

    # Facet styling
    strip.text = element_text(size = 14, face = "bold", color = "gray20"),
    strip.background = element_rect(fill = "gray95", color = NA),

    # Axis styling
    axis.title.x = element_text(size = 11, margin = margin(t = 10)),
    axis.title.y = element_text(size = 11, margin = margin(r = 10)),
    axis.text.y = element_text(size = 10, color = "gray30"),
    axis.text.x = element_text(size = 10, color = "gray30"),

    # Grid styling
    panel.grid.major.y = element_line(color = "gray90", linewidth = 0.3),
    panel.grid.minor.y = element_blank(),
    panel.grid.major.x = element_line(color = "gray85", linewidth = 0.3),
    panel.grid.minor.x = element_blank(),

    # Panel styling
    panel.spacing = unit(1.5, "lines"),

    # Legend styling
    legend.position = "bottom",
    legend.direction = "horizontal",
    legend.title = element_text(size = 10, margin = margin(r = 3)), # Reduced spacing
    legend.text = element_text(size = 10),
    legend.key.width = unit(1.5, "cm"),
    legend.key.height = unit(0.3, "cm"),
    legend.margin = margin(t = 5, b = 0),
    legend.spacing.x = unit(0.3, "cm"),

    # Overall plot margins
    plot.margin = margin(20, 20, 10, 20)
    )