This week you built regression models, interpreted their output, explored multi-variable graphs, and read about what happens when models go wrong. This challenge asks you to put those things together in one investigation.
Run this code to install the openintro package. It only needs to run
once, so I set eval=FALSE here to keep the knit from
reinstalling it every time.
install.packages("openintro")
library(tidyverse)
library(openintro)
data(county)
head(county)
## # A tibble: 6 × 15
## name state pop2000 pop2010 pop2017 pop_change poverty homeownership
## <chr> <fct> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 Autauga County Alaba… 43671 54571 55504 1.48 13.7 77.5
## 2 Baldwin County Alaba… 140415 182265 212628 9.19 11.8 76.7
## 3 Barbour County Alaba… 29038 27457 25270 -6.22 27.2 68
## 4 Bibb County Alaba… 20826 22915 22668 0.73 15.2 82.9
## 5 Blount County Alaba… 51024 57322 58013 0.68 15.6 82
## 6 Bullock County Alaba… 11714 10914 10309 -2.28 28.5 76.9
## # ℹ 7 more variables: multi_unit <dbl>, unemployment_rate <dbl>, metro <fct>,
## # median_edu <fct>, per_capita_income <dbl>, median_hh_income <int>,
## # smoking_ban <fct>
The county dataset has demographic and economic
information for counties across the United States — poverty rate,
unemployment, income, homeownership, education level, and more. The
quesiton you are going to explore is: what predicts poverty rate
at the county level?
Before fitting any model, look at the data. Make two exploratory
graphs. Use at least one color or fill
aesthetic and at least one facet_wrap() across the two
graphs.
# Per-capita income vs poverty, colored by whether the county is a metro area
ggplot(data = county, aes(x = per_capita_income, y = poverty, color = metro)) +
geom_point(alpha = 0.3) +
geom_smooth(method = "lm", se = FALSE) +
scale_color_manual(values = c("no" = "#FC8D62", "yes" = "#4C9BE0"),
na.value = "grey70") +
labs(title = "Per-capita income vs. poverty",
x = "Per-capita income ($)",
y = "Poverty rate (%)",
color = "Metro area") +
theme_minimal()
# Unemployment vs poverty, split into one panel per education level
ggplot(data = filter(county, !is.na(median_edu)),
aes(x = unemployment_rate, y = poverty)) +
geom_point(alpha = 0.25, color = "#66C2A5") +
geom_smooth(method = "lm", se = FALSE, color = "grey30") +
facet_wrap(~ median_edu) +
labs(title = "Unemployment vs. poverty, by median education level",
x = "Unemployment rate (%)",
y = "Poverty rate (%)") +
theme_minimal()
ANSWER: What patterns did you notice? Is there a
numeric variable that looks like it might predict poverty
well? Is there a categorical variable that seems to split the data into
meaningfully different groups?
Both graphs point the same way. Per-capita income has a strong, tight
negative relationship with poverty: as income climbs, poverty falls, and
the metro counties (blue) sit toward the higher-income, lower-poverty
end. Unemployment tracks poverty too, but more loosely, with a lot of
scatter. The facets by education show the split I care about. Poverty
sits lower in the some_college and bachelors
panels than in the hs_diploma panel, and the
below_hs and bachelors panels barely hold any
counties at all. So per_capita_income looks like the best
numeric predictor, and median_edu looks like a categorical
variable that sorts counties into clearly different poverty levels.
Pick the numeric variable from your exploration that you think best
predicts poverty and fit a linear model.
county_model <- lm(poverty ~ per_capita_income, data = county)
summary(county_model)
##
## Call:
## lm(formula = poverty ~ per_capita_income, data = county)
##
## Residuals:
## Min 1Q Median 3Q Max
## -15.244 -2.962 -0.485 2.112 33.838
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 3.550e+01 3.443e-01 103.10 <2e-16 ***
## per_capita_income -7.483e-04 1.283e-05 -58.34 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 4.514 on 3138 degrees of freedom
## (2 observations deleted due to missingness)
## Multiple R-squared: 0.5203, Adjusted R-squared: 0.5201
## F-statistic: 3403 on 1 and 3138 DF, p-value: < 2.2e-16
ANSWER — Interpret the output:
What variable did you choose as your predictor, and why?
Write a sentence interpreting the slope in context — what does a one-unit increase in your predictor actually mean for predicted poverty rate?
Is the intercept meaningful here, or does it involve extrapolation? Explain.
What is the R-squared? What fraction of the variation in county poverty rate does your predictor explain?
Is the slope statistically significant? What does the p-value tell you in plain language?
Answers:
I chose per_capita_income. In the exploration it had
one of the strongest negative correlations with poverty of any numeric
variable, about -0.72. Median household income edged it out slightly,
but the two measure nearly the same thing, and per-capita income is a
clean per-person number, so I went with it.
The slope is about -0.00075. Each additional dollar of per-capita income lowers a county’s predicted poverty rate by 0.00075 percentage points. That reads better scaled up: every extra $1,000 in per-capita income predicts a drop of roughly 0.75 percentage points in the poverty rate.
The intercept is about 35.5, the predicted poverty rate when per-capita income is $0. No county comes anywhere near $0 income (the lowest is about $10,500), so the intercept is pure extrapolation. It anchors the line but has no real-world meaning on its own.
The R-squared is about 0.52. Per-capita income explains roughly 52% of the variation in county poverty rates. The other half is driven by everything the model leaves out.
Yes. The p-value is below 2e-16, far under 0.05, so the slope is highly significant. In plain language, if income and poverty had no real relationship, we would almost never see a slope this steep just by chance. The negative link is real, not noise.
Now plot the model:
ggplot(data = county, aes(x = per_capita_income, y = poverty)) +
geom_point(alpha = 0.3) +
geom_smooth(method = "lm", se = TRUE)
# per_capita_income has 2 missing values, so lm() drops those rows. I pull
# the rows the model actually used (county_model$model) so fitted, residuals,
# and the data all line up.
county_aug <- county_model$model
county_aug$fitted <- fitted(county_model)
county_aug$residuals <- residuals(county_model)
ggplot(data = county_aug, aes(x = fitted, y = residuals)) +
geom_point(alpha = 0.3) +
geom_hline(yintercept = 0, color = "red", linetype = "dashed")
ANSWER: What pattern (or lack of pattern) do you see in the residual plot? Does a linear model seem like a reasonable fit?
The residuals are not a clean, formless band. There is a shallow curve: residuals sit above zero at the low and high ends of the fitted values and dip below zero in the middle, which hints the real relationship bends a little instead of running perfectly straight. The spread also widens as fitted poverty rises, a funnel shape, so the model’s error is bigger for higher-poverty counties. A handful of counties even get predicted poverty below zero, which is impossible and shows the straight line over-extrapolates for the richest counties. A linear model is a reasonable first pass and captures the main trend, but the curve and the uneven spread say it is not a perfect fit.
The county dataset includes median_edu —
the median education level in each county. It has four levels:
below_hs, hs_diploma,
some_college, bachelors.
Before fitting a model with this variable, look at it visually first.
ggplot(data = filter(county, !is.na(median_edu)),
aes(x = median_edu, y = poverty)) +
geom_boxplot() +
geom_jitter(alpha = 0.2, width = 0.2)
ANSWER:
Describe the pattern across education levels. As median education increases, what happens to poverty rate? Is the relationship consistent across all four levels?
Is median_edu a nominal or
ordinal categorical variable? How do you know, and why
does that distinction matter here?
Look at the spread (height of each box and jitter cloud) across groups. Is the spread similar across all four education levels, or does it differ? What might that mean for our model?
Predict before fitting: based purely on this graph, which education level do you expect R to use as the reference category? Which level do you expect to have the largest coefficient?
Answers:
Poverty drops steadily as median education rises.
below_hs counties sit highest, then
hs_diploma, then some_college, and
bachelors counties are lowest. The direction is consistent
across all four levels. The step down from below_hs to
hs_diploma looks huge, but below_hs holds only
a couple of counties, so that particular jump is shaky.
median_edu is ordinal. The four levels have a
natural order (below_hs < hs_diploma <
some_college < bachelors), not just
different names. That matters because “more education” is a real
direction, so we expect poverty to move one way as we climb the levels,
and we can talk about the ranking, not only which group a county falls
in.
The spread differs. The hs_diploma and
some_college boxes are tall with wide jitter clouds because
they hold most of the counties. below_hs and
bachelors have very few counties, so their spread is hard
to trust. Unequal spread plus very unequal group sizes means the model’s
estimates for the tiny groups will be far less reliable than for the big
ones.
R sorts factor levels alphabetically, and below_hs
also happens to be the lowest level, so I expect below_hs
to be the reference category. Since below_hs has the
highest poverty and every other level sits lower, I expect all the
coefficients to be negative, and bachelors, the level
furthest from below_hs, to have the largest coefficient in
size.
county_model_edu <- lm(poverty ~ median_edu, data = county)
summary(county_model_edu)
##
## Call:
## lm(formula = poverty ~ median_edu, data = county)
##
## Residuals:
## Min 1Q Median 3Q Max
## -15.758 -3.858 -0.758 3.056 32.742
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 29.900 4.091 7.308 3.41e-13 ***
## median_eduhs_diploma -10.642 4.094 -2.599 0.00938 **
## median_edusome_college -16.496 4.094 -4.030 5.71e-05 ***
## median_edubachelors -19.896 4.179 -4.761 2.02e-06 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 5.786 on 3136 degrees of freedom
## (2 observations deleted due to missingness)
## Multiple R-squared: 0.2123, Adjusted R-squared: 0.2115
## F-statistic: 281.7 on 3 and 3136 DF, p-value: < 2.2e-16
ANSWER:
Which education level is R using as the reference category? How can you tell from the output?
Interpret one non-reference coefficient (slope) in plain
language. What does it mean for counties where median education is
hs_diploma compared to the reference group?
Compare R-squared between county_model (your numeric
predictor) and county_model_edu (education level). Which
predictor explains more variation in poverty rate?
Did the output match your predictions from Part 4? What surprised you, if anything?
Answers:
The reference category is below_hs. I can tell
because it is the only one of the four levels with no coefficient row in
the output. Its value is folded into the intercept (about 29.9), and the
other three coefficients are all measured against it.
The hs_diploma coefficient is about -10.6. Counties
whose median education is hs_diploma have a predicted
poverty rate about 10.6 percentage points lower than
below_hs counties, the reference group. So moving from the
below_hs group to the hs_diploma group goes
with a large drop in predicted poverty.
county_model (per-capita income) has an R-squared of
about 0.52. county_model_edu (education level) has an
R-squared of about 0.21. Per-capita income explains far more of the
variation in poverty, more than double what education level alone does.
Income is the stronger single predictor.
It matched. below_hs was the reference, the
coefficients were all negative, and bachelors had the
largest one (about -19.9), exactly as I predicted. The surprise was the
reference group itself. below_hs holds only two counties,
so its intercept carries a huge standard error (about ±4.1). That makes
every comparison against it wobbly, even though the overall staircase
pattern is clear.
You just built two models that predict county-level poverty rates from demographic variables. Before finishing, think carefully about what these models are actually doing and what they could be used for.
ANSWER:
From The Signal and the Noise: Silver distinguishes between a model that describes a pattern and one that explains it. Your regression describes a relationship between education (or your chosen numeric variable) and poverty. Does it explain it? What would it take to move from description to explanation?
Imagine a bank uses a model like yours to decide which counties are “too risky” for small business loans — counties with predicted poverty above a threshold are automatically denied. Explain why this could be harmful even if the model’s predictions are statistically accurate.
Think back to your ethical model-building contract from this week. Identify one specific clause and evaluate whether the model you built here satisfies it.
Answers:
My regression only describes. It says counties with higher income tend to have lower poverty and puts a number on how tightly the two move together. It does not explain why. Income could lower poverty, poverty could hold income down, or a third factor like the local job market, school funding, or a region’s history could drive both at once. To move from description to explanation I would need a causal design, not a single snapshot: control for confounders, use time to check that income changes come before poverty changes, or find a natural experiment, and pair that with a real mechanism for why one produces the other. A correlation frozen at one moment cannot do that on its own.
Even if the predictions are statistically accurate, wiring the model into an automatic loan denial is harmful. It turns a description into a gate. Poverty is a symptom of missing capital, so pulling loans out of high-poverty counties starves the exact places that need investment and locks in the poverty the model measured, a feedback loop where the prediction helps make itself come true. It also punishes individuals for their county’s average. A creditworthy owner in a poor county gets denied for something they did not do. Accurate on average is not the same as fair to each case, and the model would launder that unfairness as neutral math.
One clause in my contract was that I would not let a model make a high-stakes decision about a person or group without a human review and a real way to appeal. The model I built here fails that clause if it is used the way question 2 describes. On its own it is just a description, which is fine. Bolted onto an automatic deny rule with no human check and no appeal, it breaks exactly the clause I wrote. The model is not unethical by itself. The use is, and my contract was written about use, not about the math.