1 Tutorial

This tutorial uses descriptive statistics and visualizations to examine rurality, climate normals, and American Community Survey data. The climate normals and ACS values are already aggregated over time or space, so the results below are summaries of data that have already been summarized.

1.1 Orienting Ourselves to the Datasets

glimpse() provides a quick look at the number of observations, variable names, and data types in each dataset.

glimpse(rurality)
## Rows: 2,672
## Columns: 4
## $ GEOID           <chr> "37141920300", "37141990100", "37071031600", "37071031…
## $ ruca_code       <chr> "Metropolitan high commuting", "Not coded", "Metropoli…
## $ nc_rural_center <chr> "Rural", "Rural", "Not Rural", "Not Rural", "Rural", "…
## $ geometry        <MULTIPOLYGON [°]> MULTIPOLYGON (((-78.15648 3..., MULTIPOLY…
glimpse(climate_normals)
## Rows: 469,758
## Columns: 2
## $ summer_temp <dbl> 56.25781, 56.25312, 56.27656, 56.40313, 56.46875, 57.36875…
## $ geometry    <POINT [°]> POINT (-124.6875 48.0625), POINT (-124.6875 48.10417…
glimpse(acs_tract_nc)
## Rows: 2,672
## Columns: 54
## $ STATEFP          <chr> "37", "37", "37", "37", "37", "37", "37", "37", "37",…
## $ COUNTYFP         <chr> "031", "031", "031", "031", "031", "031", "031", "119…
## $ TRACTCE          <chr> "970402", "970502", "970805", "970903", "970101", "97…
## $ GEOID            <chr> "37031970402", "37031970502", "37031970805", "3703197…
## $ NAME             <chr> "9704.02", "9705.02", "9708.05", "9709.03", "9701.01"…
## $ NAMELSAD         <chr> "Census Tract 9704.02", "Census Tract 9705.02", "Cens…
## $ MTFCC            <chr> "G5020", "G5020", "G5020", "G5020", "G5020", "G5020",…
## $ FUNCSTAT         <chr> "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S"…
## $ ALAND            <dbl> 2395384, 6094915, 139248627, 5864308, 28018671, 97430…
## $ AWATER           <dbl> 1553995, 4244211, 421209, 30151466, 35773987, 1568180…
## $ INTPTLAT         <chr> "+34.7239933", "+34.7517653", "+34.7817392", "+34.701…
## $ INTPTLON         <chr> "-076.7087383", "-076.7387561", "-077.0184797", "-076…
## $ tract_name       <chr> "Census Tract 9704.02, Carteret County, North Carolin…
## $ total_pop        <dbl> 1309, 2177, 1694, 1211, 965, 4244, 2306, 2720, 6076, …
## $ median_age       <dbl> 39.3, 56.1, 43.3, 62.1, 48.4, 55.9, 42.3, 28.3, 29.7,…
## $ pct_white        <dbl> 64.17, 93.11, 85.36, 85.47, 86.22, 72.64, 72.94, 6.32…
## $ pct_black        <dbl> 21.01, 2.30, 0.00, 0.66, 3.21, 17.86, 9.71, 45.96, 30…
## $ pct_aian         <dbl> 0.15, 0.00, 0.41, 0.41, 0.00, 2.00, 0.00, 0.00, 0.05,…
## $ pct_asian        <dbl> 0.00, 0.00, 1.89, 0.00, 0.00, 0.00, 4.68, 2.90, 1.50,…
## $ pct_nhpi         <dbl> 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.17, 1.32, 0.00,…
## $ pct_other_race   <dbl> 0.00, 0.00, 0.00, 0.00, 0.00, 1.06, 0.22, 0.00, 0.00,…
## $ pct_two_races    <dbl> 12.15, 0.32, 10.68, 12.80, 0.83, 3.79, 8.24, 1.69, 0.…
## $ pct_hisp         <dbl> 2.52, 4.27, 1.65, 0.66, 9.74, 2.64, 4.03, 41.80, 63.8…
## $ pct_family_hh    <dbl> 39.31, 64.53, 73.30, 64.78, 65.03, 64.45, 62.60, 61.2…
## $ married_fam      <dbl> 19.08, 55.75, 55.45, 61.55, 45.08, 55.44, 42.31, 28.3…
## $ male_hh          <dbl> 3.71, 0.00, 3.81, 0.48, 9.84, 3.64, 2.88, 9.40, 8.66,…
## $ female_hh        <dbl> 16.52, 8.77, 14.03, 2.75, 10.11, 5.37, 17.40, 23.56, …
## $ pct_nonfam_hh    <dbl> 60.69, 35.47, 26.70, 35.22, 34.97, 35.55, 37.40, 38.7…
## $ nonfam_male_hh   <dbl> 37.00, 16.13, 15.26, 14.22, 18.31, 17.25, 13.37, 13.7…
## $ nonfam_fem_hh    <dbl> 23.69, 19.34, 11.44, 21.00, 16.67, 18.30, 24.04, 25.0…
## $ av_hh_size       <dbl> 1.64, 2.05, 2.31, 1.96, 2.37, 2.03, 2.21, 3.01, 2.89,…
## $ pct_less_hs      <dbl> 17.40, 7.95, 9.49, 0.00, 8.64, 18.64, 6.50, 19.05, 29…
## $ pct_hs           <dbl> 52.21, 53.92, 69.15, 41.87, 67.83, 59.63, 61.34, 60.9…
## $ pc_bach          <dbl> 30.38, 38.13, 21.36, 58.13, 23.54, 21.73, 32.17, 19.9…
## $ pct_unemp        <dbl> 5.67, 0.41, 0.86, 1.47, 1.45, 0.13, 0.95, 5.91, 8.52,…
## $ med_hh_inc       <dbl> 45391, 86583, 66548, 96198, 58750, 67195, 63393, 4844…
## $ gini_index       <dbl> 0.4094, 0.4039, 0.4377, 0.4689, 0.5036, 0.4248, 0.462…
## $ pct_owner        <dbl> 41.87, 87.64, 81.47, 86.11, 81.42, 70.29, 62.98, 25.1…
## $ pct_renter       <dbl> 58.13, 12.36, 18.53, 13.89, 18.58, 29.71, 37.02, 74.8…
## $ pct_vacant       <dbl> 19.57, 10.09, 18.90, 78.84, 45.29, 14.68, 8.29, 6.22,…
## $ med_year_built   <dbl> 1962, 1985, 1997, 1988, 1965, 1994, 1984, 1983, 1986,…
## $ pct_gas          <dbl> 18.95, 13.02, 7.36, 13.41, 31.15, 2.30, 3.56, 49.89, …
## $ pct_electric     <dbl> 77.21, 85.66, 85.01, 84.81, 53.83, 96.26, 95.67, 49.7…
## $ med_house_val    <dbl> 234100, 329900, 208800, 669300, 168500, 262600, 23640…
## $ med_rent         <dbl> 781, 1241, 1133, 1489, 1000, 762, 1184, 1355, 1309, 2…
## $ pct_pov          <dbl> 26.02, 4.10, 9.63, 4.21, 16.63, 9.50, 18.35, 16.09, 1…
## $ pct_drive_work   <dbl> 90.34, 89.37, 87.40, 75.43, 78.46, 85.78, 94.20, 86.3…
## $ pct_public_trans <dbl> 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 4.12, 3.60,…
## $ bike_walk        <dbl> 3.22, 0.00, 2.95, 0.00, 2.25, 0.00, 1.40, 2.22, 0.00,…
## $ pct_wfh          <dbl> 6.44, 7.95, 8.17, 24.57, 8.36, 10.30, 4.12, 5.43, 11.…
## $ av_commute       <dbl> 17, 20, 26, 28, 32, 17, 23, 19, 25, 24, 25, 23, 25, 2…
## $ pct_no_veh       <dbl> 27.53, 0.57, 0.00, 3.07, 4.92, 2.97, 3.94, 7.52, 7.90…
## $ no_health_insur  <dbl> 19.39, 7.21, 15.13, 4.29, 7.85, 16.02, 6.16, 14.72, 4…
## $ geometry         <MULTIPOLYGON [°]> MULTIPOLYGON (((-76.74294 3..., MULTIPOL…

1.2 Describing Rurality

1.2.1 Q1. What does each observation represent?

Each row represents one North Carolina census tract. I can tell because every observation has a census tract GEOID, one RUCA category, and a multipolygon geometry defining the tract’s boundary.

1.2.2 Q2. What is the level of measurement for ruca_code?

ruca_code is a categorical variable. The labels contain information about the type of place and commuting relationship, but they are not a continuous numeric scale. Counts, percentages, and the mode are appropriate. A mean or standard deviation would not make sense.

1.2.3 Creating a Basic Table

rural_summary <- rurality |>
  st_drop_geometry() |>
  count(ruca_code, name = "n") |>
  mutate(percent = 100 * n / sum(n)) |>
  arrange(desc(n))

rural_summary |>
  gt() |>
  tab_header(title = "North Carolina Census Tracts by RUCA Category") |>
  cols_label(
    ruca_code = "RUCA category",
    n = "Number of tracts",
    percent = "Percent"
  ) |>
  fmt_number(columns = percent, decimals = 1)
North Carolina Census Tracts by RUCA Category
RUCA category Number of tracts Percent
Metropolitan core 1436 53.7
Metropolitan high commuting 406 15.2
Rural area 225 8.4
Micropolitan core 218 8.2
Micropolitan high commuting 134 5.0
Metropolitan low commuting 87 3.3
Micropolitan low commuting 79 3.0
Small town core 39 1.5
Small town high commuting 20 0.7
Small town low commuting 16 0.6
Not coded 12 0.4

1.2.4 Q3. What is the mode of ruca_code?

The mode is Metropolitan core, which is the most common RUCA category in the dataset.

1.2.5 Visualizing Rurality

1.2.5.1 Bar Chart

ggplot(rural_summary, aes(x = reorder(ruca_code, n), y = n)) +
  geom_col(fill = "#4C78A8") +
  coord_flip() +
  labs(
    title = "North Carolina Census Tracts by RUCA Category",
    x = "RUCA category",
    y = "Number of census tracts"
  ) +
  theme_minimal()

1.2.5.2 Map

tm_shape(rurality) +
  tm_polygons(
    fill = "ruca_code",
    col = "white",
    lwd = 0.25,
    fill.scale = tm_scale_categorical(values = "brewer.paired"),
    fill.legend = tm_legend(
      title = "RUCA category",
      position = tm_pos_out("right", "center"),
      frame = FALSE
    )
  ) +
  tm_title("Rural-Urban Commuting Areas in North Carolina", size = 1.5) +
  tm_layout(frame = TRUE)

1.2.6 Q4. What spatial patterns appear in the RUCA codes?

Metropolitan-core tracts cluster around Charlotte, the Triangle, the Triad, Asheville, Wilmington, and other cities. Rural and small-town categories are more common between metropolitan areas and in parts of eastern and western North Carolina. The table shows which categories are common, but the map shows that those categories are geographically clustered rather than randomly scattered.

1.3 Describing Climate Normals

1.3.1 Q5. What does each row represent?

Each row represents one point in a regularly spaced geographic grid across the contiguous United States. The POINT geometry gives the location, and summer_temp gives the 1991–2020 average summer temperature at that location.

1.3.2 Creating a Basic Table

climate_stats <- climate_values |>
  summarise(
    n = n(),
    num_na = sum(is.na(summer_temp)),
    mean_temp = mean(summer_temp, na.rm = TRUE),
    median_temp = median(summer_temp, na.rm = TRUE),
    sd_temp = sd(summer_temp, na.rm = TRUE),
    mean_dev = mean(
      abs(summer_temp - mean(summer_temp, na.rm = TRUE)),
      na.rm = TRUE
    ),
    min_temp = min(summer_temp, na.rm = TRUE),
    q1_temp = quantile(summer_temp, 0.25, na.rm = TRUE),
    q3_temp = quantile(summer_temp, 0.75, na.rm = TRUE),
    max_temp = max(summer_temp, na.rm = TRUE),
    skewness = e1071::skewness(summer_temp, na.rm = TRUE),
    kurtosis = e1071::kurtosis(summer_temp, na.rm = TRUE)
  )

climate_stats |>
  pivot_longer(
    cols = everything(),
    names_to = "Statistic",
    values_to = "Value"
  ) |>
  gt() |>
  tab_header(title = "U.S. Summer Temperatures, 1991–2020") |>
  fmt_number(columns = Value, decimals = 2)
U.S. Summer Temperatures, 1991–2020
Statistic Value
n 469,758.00
num_na 0.00
mean_temp 71.86
median_temp 71.55
sd_temp 7.64
mean_dev 6.26
min_temp 29.91
q1_temp 66.53
q3_temp 77.87
max_temp 98.04
skewness −0.09
kurtosis −0.25

1.3.3 Q6. What do the descriptive statistics tell us?

The average summer temperature is about 71.9°F, and the median is about 71.6°F. Their similarity and the skewness near zero suggest an almost symmetric distribution. A standard deviation of about 7.6°F shows meaningful variation across the country. Temperatures range from roughly 29.9°F to 98.0°F, although those extremes represent a small share of grid locations. The slightly negative kurtosis indicates a distribution that is a little flatter than a normal distribution rather than one dominated by unusually heavy tails.

1.3.4 Visualizing Climate Normals

1.3.4.1 Histogram

ggplot(climate_values, aes(x = summer_temp)) +
  geom_histogram(
    binwidth = 1,
    fill = "lightgray",
    color = "black",
    alpha = 0.8
  ) +
  labs(
    title = "Distribution of Average Summer Temperatures, 1991–2020",
    x = "Summer temperature (°F)",
    y = "Number of grid points"
  ) +
  theme_minimal()

1.3.4.2 Cumulative Frequency Plot

ggplot(climate_values, aes(x = summer_temp)) +
  stat_ecdf(geom = "step", color = "#2C7FB8", linewidth = 0.8) +
  labs(
    title = "Cumulative Distribution of Average Summer Temperatures",
    x = "Summer temperature (°F)",
    y = "Cumulative proportion"
  ) +
  theme_minimal()

1.3.4.3 Boxplot

ggplot(climate_values, aes(y = summer_temp)) +
  geom_boxplot(fill = "lightgray", width = 0.35) +
  labs(
    title = "Boxplot of Average Summer Temperatures",
    x = NULL,
    y = "Summer temperature (°F)"
  ) +
  theme_minimal()

1.3.4.4 Violin Plot

ggplot(climate_values, aes(x = "", y = summer_temp)) +
  geom_violin(fill = "lightgray", bw = 1.2) +
  geom_boxplot(width = 0.1, outlier.alpha = 0.25) +
  labs(
    title = "Violin Plot of Average Summer Temperatures",
    x = NULL,
    y = "Summer temperature (°F)"
  ) +
  theme_minimal() +
  theme(
    axis.text.x = element_blank(),
    axis.ticks.x = element_blank()
  )

1.3.5 Q7. What does each visualization add?

The histogram shows the overall shape of the distribution and where temperatures are most concentrated. The cumulative frequency plot makes it easy to find the proportion of locations below a particular temperature. The boxplot summarizes the median, quartiles, spread, and possible outliers. The violin plot combines the compact boxplot summary with a density shape, so it shows where observations are especially concentrated.

1.3.5.1 Map

tm_shape(climate_normals) +
  tm_dots(
    fill = "summer_temp",
    size = 0.015,
    fill.scale = tm_scale_intervals(
      values = "brewer.yl_or_rd",
      style = "pretty",
      n = 7
    ),
    fill.legend = tm_legend(
      title = "Summer temperature (°F)",
      position = tm_pos_out("right", "center"),
      frame = FALSE
    )
  ) +
  tm_title("Average U.S. Summer Temperature, 1991–2020", size = 1.5) +
  tm_layout(frame = TRUE)

1.3.6 Q8. What does the climate map show?

Summer temperatures generally increase toward the South, with especially warm conditions across the southern and southwestern United States. Cooler values appear farther north and in high-elevation areas such as the Rocky Mountains. The map shows that temperature is spatially structured by latitude, elevation, and regional climate—patterns that cannot be seen in the national summary statistics alone.

2 Mini-Challenge: Census-Tract Poverty

I selected pct_pov, the percentage of residents living below the poverty line. This is useful for seeing both the overall distribution of poverty across North Carolina census tracts and the places where high rates are concentrated.

2.1 Descriptive Statistics

poverty_stats <- acs_tract_nc |>
  st_drop_geometry() |>
  summarise(
    n = n(),
    num_na = sum(is.na(pct_pov)),
    mean = mean(pct_pov, na.rm = TRUE),
    median = median(pct_pov, na.rm = TRUE),
    sd = sd(pct_pov, na.rm = TRUE),
    mean_dev = mean(
      abs(pct_pov - mean(pct_pov, na.rm = TRUE)),
      na.rm = TRUE
    ),
    minimum = min(pct_pov, na.rm = TRUE),
    q1 = quantile(pct_pov, 0.25, na.rm = TRUE),
    q3 = quantile(pct_pov, 0.75, na.rm = TRUE),
    maximum = max(pct_pov, na.rm = TRUE),
    skewness = e1071::skewness(pct_pov, na.rm = TRUE),
    kurtosis = e1071::kurtosis(pct_pov, na.rm = TRUE)
  )

poverty_stats |>
  pivot_longer(
    cols = everything(),
    names_to = "Statistic",
    values_to = "Value"
  ) |>
  gt() |>
  tab_header(title = "Census-Tract Poverty Rates in North Carolina") |>
  fmt_number(columns = Value, decimals = 2)
Census-Tract Poverty Rates in North Carolina
Statistic Value
n 2,672.00
num_na 0.00
mean 13.94
median 11.57
sd 10.48
mean_dev 7.97
minimum 0.00
q1 6.34
q3 19.11
maximum 100.00
skewness 1.49
kurtosis 3.73

2.2 Non-Map Visualization

ggplot(acs_tract_nc |> st_drop_geometry(), aes(x = pct_pov)) +
  geom_histogram(
    binwidth = 2,
    fill = "#F16913",
    color = "white",
    boundary = 0
  ) +
  geom_vline(
    xintercept = poverty_stats$median,
    color = "#54278F",
    linewidth = 1,
    linetype = "dashed"
  ) +
  labs(
    title = "Distribution of Census-Tract Poverty Rates",
    subtitle = "Dashed line shows the median tract",
    x = "Population below the poverty line (%)",
    y = "Number of census tracts"
  ) +
  theme_minimal()

2.3 Map

tm_shape(acs_tract_nc) +
  tm_polygons(
    fill = "pct_pov",
    col = "white",
    lwd = 0.25,
    fill.scale = tm_scale_intervals(
      values = "brewer.or_rd",
      style = "quantile",
      n = 5
    ),
    fill.legend = tm_legend(
      title = "Below poverty line (%)",
      position = tm_pos_in("left", "bottom"),
      frame = TRUE,
      frame.r = 5,
      bg.color = "white"
    )
  ) +
  tm_title("Poverty Rate by North Carolina Census Tract", size = 1.6) +
  tm_layout(
    frame = TRUE,
    frame.r = 12,
    text.fontfamily = "sans"
  )

2.4 Q9. What did I learn about the variable?

The median census-tract poverty rate is 11.6%, while the mean is 13.9%. The histogram shows a right-skewed distribution: most tracts have relatively low or moderate poverty rates, but a smaller number of tracts have much higher rates. This means the statewide mean does not describe every tract equally well, so the median and full distribution are important.

2.5 Q10. What did I learn about its spatial pattern?

Poverty rates are spatially clustered rather than evenly spread across North Carolina. Higher-rate tracts appear in several rural parts of eastern North Carolina and in particular neighborhoods within cities, while many suburban tracts fall into lower categories. The map also shows differences within the same metropolitan area that would disappear in a county- or statewide average. Because the map uses quantiles, the colors show relative differences among North Carolina tracts rather than fixed policy thresholds.