library(tidyverse)
library(mice)
library(ggcorrplot)
We’re using midwest — a dataset built into ggplot2 with
demographic and economic data for counties across five midwestern US
states.
data(midwest)
head(midwest)
## # A tibble: 6 × 28
## PID county state area poptotal popdensity popwhite popblack popamerindian
## <int> <chr> <chr> <dbl> <int> <dbl> <int> <int> <int>
## 1 561 ADAMS IL 0.052 66090 1271. 63917 1702 98
## 2 562 ALEXAND… IL 0.014 10626 759 7054 3496 19
## 3 563 BOND IL 0.022 14991 681. 14477 429 35
## 4 564 BOONE IL 0.017 30806 1812. 29344 127 46
## 5 565 BROWN IL 0.018 5836 324. 5264 547 14
## 6 566 BUREAU IL 0.05 35688 714. 35157 50 65
## # ℹ 19 more variables: popasian <int>, popother <int>, percwhite <dbl>,
## # percblack <dbl>, percamerindan <dbl>, percasian <dbl>, percother <dbl>,
## # popadults <int>, perchsd <dbl>, percollege <dbl>, percprof <dbl>,
## # poppovertyknown <int>, percpovertyknown <dbl>, percbelowpoverty <dbl>,
## # percchildbelowpovert <dbl>, percadultpoverty <dbl>,
## # percelderlypoverty <dbl>, inmetro <int>, category <chr>
Your outcome variable for modeling is
percbelowpoverty (percentage of the county
population below the poverty line).
# Summary Statistics
glimpse(midwest)
## Rows: 437
## Columns: 28
## $ PID <int> 561, 562, 563, 564, 565, 566, 567, 568, 569, 570,…
## $ county <chr> "ADAMS", "ALEXANDER", "BOND", "BOONE", "BROWN", "…
## $ state <chr> "IL", "IL", "IL", "IL", "IL", "IL", "IL", "IL", "…
## $ area <dbl> 0.052, 0.014, 0.022, 0.017, 0.018, 0.050, 0.017, …
## $ poptotal <int> 66090, 10626, 14991, 30806, 5836, 35688, 5322, 16…
## $ popdensity <dbl> 1270.9615, 759.0000, 681.4091, 1812.1176, 324.222…
## $ popwhite <int> 63917, 7054, 14477, 29344, 5264, 35157, 5298, 165…
## $ popblack <int> 1702, 3496, 429, 127, 547, 50, 1, 111, 16, 16559,…
## $ popamerindian <int> 98, 19, 35, 46, 14, 65, 8, 30, 8, 331, 51, 26, 17…
## $ popasian <int> 249, 48, 16, 150, 5, 195, 15, 61, 23, 8033, 89, 3…
## $ popother <int> 124, 9, 34, 1139, 6, 221, 0, 84, 6, 1596, 20, 7, …
## $ percwhite <dbl> 96.71206, 66.38434, 96.57128, 95.25417, 90.19877,…
## $ percblack <dbl> 2.57527614, 32.90043290, 2.86171703, 0.41225735, …
## $ percamerindan <dbl> 0.14828264, 0.17880670, 0.23347342, 0.14932156, 0…
## $ percasian <dbl> 0.37675897, 0.45172219, 0.10673071, 0.48691813, 0…
## $ percother <dbl> 0.18762294, 0.08469791, 0.22680275, 3.69733169, 0…
## $ popadults <int> 43298, 6724, 9669, 19272, 3979, 23444, 3583, 1132…
## $ perchsd <dbl> 75.10740, 59.72635, 69.33499, 75.47219, 68.86152,…
## $ percollege <dbl> 19.63139, 11.24331, 17.03382, 17.27895, 14.47600,…
## $ percprof <dbl> 4.355859, 2.870315, 4.488572, 4.197800, 3.367680,…
## $ poppovertyknown <int> 63628, 10529, 14235, 30337, 4815, 35107, 5241, 16…
## $ percpovertyknown <dbl> 96.27478, 99.08714, 94.95697, 98.47757, 82.50514,…
## $ percbelowpoverty <dbl> 13.151443, 32.244278, 12.068844, 7.209019, 13.520…
## $ percchildbelowpovert <dbl> 18.011717, 45.826514, 14.036061, 11.179536, 13.02…
## $ percadultpoverty <dbl> 11.009776, 27.385647, 10.852090, 5.536013, 11.143…
## $ percelderlypoverty <dbl> 12.443812, 25.228976, 12.697410, 6.217047, 19.200…
## $ inmetro <int> 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0…
## $ category <chr> "AAR", "LHR", "AAR", "ALU", "AAR", "AAR", "LAR", …
summary(midwest)
## PID county state area
## Min. : 561 Length :437 Length :437 Min. :0.00500
## 1st Qu.: 670 N.unique :320 N.unique : 5 1st Qu.:0.02400
## Median :1221 N.blank : 0 N.blank : 0 Median :0.03000
## Mean :1437 Min.nchar: 3 Min.nchar: 2 Mean :0.03317
## 3rd Qu.:2059 Max.nchar: 14 Max.nchar: 2 3rd Qu.:0.03800
## Max. :3052 Max. :0.11000
## poptotal popdensity popwhite popblack
## Min. : 1701 Min. : 85.05 Min. : 416 Min. : 0
## 1st Qu.: 18840 1st Qu.: 622.41 1st Qu.: 18630 1st Qu.: 29
## Median : 35324 Median : 1156.21 Median : 34471 Median : 201
## Mean : 96130 Mean : 3097.74 Mean : 81840 Mean : 11024
## 3rd Qu.: 75651 3rd Qu.: 2330.00 3rd Qu.: 72968 3rd Qu.: 1291
## Max. :5105067 Max. :88018.40 Max. :3204947 Max. :1317147
## popamerindian popasian popother percwhite
## Min. : 4.0 Min. : 0 Min. : 0 Min. :10.69
## 1st Qu.: 44.0 1st Qu.: 35 1st Qu.: 20 1st Qu.:94.89
## Median : 94.0 Median : 102 Median : 66 Median :98.03
## Mean : 343.1 Mean : 1310 Mean : 1613 Mean :95.56
## 3rd Qu.: 288.0 3rd Qu.: 401 3rd Qu.: 345 3rd Qu.:99.07
## Max. :10289.0 Max. :188565 Max. :384119 Max. :99.82
## percblack percamerindan percasian percother
## Min. : 0.0000 Min. : 0.05623 Min. :0.0000 Min. :0.00000
## 1st Qu.: 0.1157 1st Qu.: 0.15793 1st Qu.:0.1737 1st Qu.:0.09102
## Median : 0.5389 Median : 0.21502 Median :0.2972 Median :0.17844
## Mean : 2.6763 Mean : 0.79894 Mean :0.4872 Mean :0.47906
## 3rd Qu.: 2.6014 3rd Qu.: 0.38362 3rd Qu.:0.5212 3rd Qu.:0.48050
## Max. :40.2100 Max. :89.17738 Max. :5.0705 Max. :7.52427
## popadults perchsd percollege percprof
## Min. : 1287 Min. :46.91 Min. : 7.336 Min. : 0.5203
## 1st Qu.: 12271 1st Qu.:71.33 1st Qu.:14.114 1st Qu.: 2.9980
## Median : 22188 Median :74.25 Median :16.798 Median : 3.8142
## Mean : 60973 Mean :73.97 Mean :18.273 Mean : 4.4473
## 3rd Qu.: 47541 3rd Qu.:77.20 3rd Qu.:20.550 3rd Qu.: 4.9493
## Max. :3291995 Max. :88.90 Max. :48.079 Max. :20.7913
## poppovertyknown percpovertyknown percbelowpoverty percchildbelowpovert
## Min. : 1696 Min. :80.90 Min. : 2.180 Min. : 1.919
## 1st Qu.: 18364 1st Qu.:96.89 1st Qu.: 9.199 1st Qu.:11.624
## Median : 33788 Median :98.17 Median :11.822 Median :15.270
## Mean : 93642 Mean :97.11 Mean :12.511 Mean :16.447
## 3rd Qu.: 72840 3rd Qu.:98.60 3rd Qu.:15.133 3rd Qu.:20.352
## Max. :5023523 Max. :99.86 Max. :48.691 Max. :64.308
## percadultpoverty percelderlypoverty inmetro category
## Min. : 1.939 Min. : 3.547 Min. :0.0000 Length :437
## 1st Qu.: 7.668 1st Qu.: 8.912 1st Qu.:0.0000 N.unique : 16
## Median :10.008 Median :10.869 Median :0.0000 N.blank : 0
## Mean :10.919 Mean :11.389 Mean :0.3432 Min.nchar: 3
## 3rd Qu.:13.182 3rd Qu.:13.412 3rd Qu.:1.0000 Max.nchar: 3
## Max. :43.312 Max. :31.162 Max. :1.0000
sum(is.na(midwest))
## [1] 0
ANSWER: This is a clean dataset: 437 counties, 28
variables, and zero missing values. It also carries the “good dataset”
traits from class, since each row is one county and each column is one
measured trait. Two things stand out in the summary. The percent columns
are mostly redundant with their population-count columns
(percwhite vs popwhite), and the racial
percentages are wildly skewed. percwhite sits near 90% for
most counties while percamerindan and
percasian rarely clear a couple percent, so a handful of
counties drag those ranges way out.
# Visualize missing data structure with md.pattern()
md.pattern(midwest, rotate.names = TRUE)
## /\ /\
## { `---' }
## { O O }
## ==> V <== No need for mice. This data set is completely observed.
## \ \|/ /
## `-----'
## PID county state area poptotal popdensity popwhite popblack popamerindian
## 437 1 1 1 1 1 1 1 1 1
## 0 0 0 0 0 0 0 0 0
## popasian popother percwhite percblack percamerindan percasian percother
## 437 1 1 1 1 1 1 1
## 0 0 0 0 0 0 0
## popadults perchsd percollege percprof poppovertyknown percpovertyknown
## 437 1 1 1 1 1 1
## 0 0 0 0 0 0
## percbelowpoverty percchildbelowpovert percadultpoverty percelderlypoverty
## 437 1 1 1 1
## 0 0 0 0
## inmetro category
## 437 1 1 0
## 0 0 0
ANSWER: There is nothing to fix.
md.pattern() returns a single row of all 1s and a final row
of all 0s, which is its way of saying every observation is complete. No
imputation needed here.
Make one exploratory graph of this dataset. Your choice of variables, geom, and aesthetics — just make sure it tells you something genuine about the data.
ggplot(midwest, aes(x = percollege, y = percbelowpoverty, color = state)) +
geom_point(alpha = 0.6) +
geom_smooth(method = "lm", se = FALSE, color = "black")
ANSWER: I wanted to know whether more educated counties are less poor. The cloud slopes down: counties with a higher share of college graduates tend to have lower poverty, though the spread is wide and plenty of well-educated counties still show middling poverty rates. Education looks related, but it is clearly not the whole story.
Your candidate numeric predictors are: percollege,
percpovertyknown, percwhite,
percblack, percamerindan,
percasian, popdensity, and
percadults. Make scatterplots of each against
percbelowpoverty.
# Note: the help page (?midwest) confirms there is no `percadults` column.
# The closest match, `popadults`, is a raw head count rather than a rate, so I
# leave it out and check the eight predictors that actually exist as percentages.
predictors <- c("percollege", "percpovertyknown", "percwhite", "percblack",
"percamerindan", "percasian", "popdensity")
midwest %>%
select(percbelowpoverty, all_of(predictors)) %>%
pivot_longer(-percbelowpoverty, names_to = "predictor", values_to = "value") %>%
ggplot(aes(x = value, y = percbelowpoverty)) +
geom_point(alpha = 0.4) +
geom_smooth(method = "lm", se = FALSE, color = "red") +
facet_wrap(~ predictor, scales = "free_x")
ANSWER: percollege,
percwhite, and percpovertyknown show
reasonably straight downward trends, and percamerindan and
percblack show usable upward ones. popdensity
is the worst offender: nearly every county piles up at low density with
a few extreme cities, so its relationship is a nonlinear smear, not a
line. percasian is similar and barely trends at all. I’ll
drop popdensity and percasian from the model
for weak, non-linear relationships.
# Select your candidate numeric predictors and outcome
model_vars <- midwest %>% select(percbelowpoverty, all_of(predictors))
corr_matrix <- cor(model_vars, use = "complete.obs")
ggcorrplot(corr_matrix, lab = TRUE, type = "lower")
ANSWER: Two pairs are too cozy.
percwhite and percblack correlate at about
-0.76, and percollege and percasian at about
0.75. Keeping both members of either pair would make their coefficients
unstable. I already cut percasian in Part 3, which handles
the second pair. For the first, I keep percwhite (it has
the stronger tie to poverty) and drop percblack.
Based on your decisions in Parts 3 and 4, fit a multiple regression
model predicting percbelowpoverty. Include at least one
categorical predictor (state is available and has five
levels).
midwest_model <- lm(percbelowpoverty ~ percollege + percwhite + percamerindan +
percpovertyknown + state, data = midwest)
summary(midwest_model)
##
## Call:
## lm(formula = percbelowpoverty ~ percollege + percwhite + percamerindan +
## percpovertyknown + state, data = midwest)
##
## Residuals:
## Min 1Q Median 3Q Max
## -10.7833 -2.4632 -0.6051 2.0349 15.8659
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 88.07482 7.96604 11.056 < 2e-16 ***
## percollege -0.36676 0.03470 -10.569 < 2e-16 ***
## percwhite -0.24196 0.03677 -6.580 1.38e-10 ***
## percamerindan 0.14194 0.05715 2.484 0.0134 *
## percpovertyknown -0.46794 0.07318 -6.394 4.23e-10 ***
## stateIN -2.63045 0.58392 -4.505 8.58e-06 ***
## stateMI 1.44790 0.59885 2.418 0.0160 *
## stateOH -0.29790 0.58677 -0.508 0.6119
## stateWI -0.45083 0.64444 -0.700 0.4846
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 4.004 on 428 degrees of freedom
## Multiple R-squared: 0.4066, Adjusted R-squared: 0.3955
## F-statistic: 36.66 on 8 and 428 DF, p-value: < 2.2e-16
ANSWER:
All 437 counties were used. Nothing was dropped, because the dataset has no missing values.
Holding the others constant:
percollege (-0.37): each extra
percentage point of college graduates is linked to a 0.37-point lower
poverty rate.percwhite (-0.24): a one-point rise in
percent white is linked to a 0.24-point lower poverty rate.percamerindan (+0.14): a one-point
rise here is linked to a 0.14-point higher poverty rate, the only
predictor pushing poverty up.percpovertyknown (-0.47): counties
that measured poverty status for a larger share of residents report
lower poverty. This is more a data-coverage effect than a social
cause.state: the reference category is
Illinois. Indiana counties average about 2.63 points
lower poverty than comparable Illinois counties, while Michigan
counties average about 1.45 points higher.R-squared is 0.41. These predictors together explain roughly 41% of the county-to-county variation in poverty rate. Respectable for social data, but most of the variation still lives outside this model.
Significant at p < 0.05: percollege,
percwhite, percpovertyknown,
percamerindan, and the Indiana and Michigan state
contrasts. The Ohio and Wisconsin contrasts are not significantly
different from Illinois.
plot(midwest_model, which = 1)
plot(midwest_model, which = 2)
ANSWER: The residual plot is mostly a flat, even band around zero, so linearity and constant variance hold up well through the middle. The trouble is in the tails: a few high-poverty counties (like the ones flagged by number) sit far above the line, and the Q-Q plot bows up sharply at the top right. That means the residuals are right-skewed with heavier-than-normal tails, driven by a small set of very high-poverty counties. The model is solid for typical counties but underpredicts the extremes.
Take one graph that’s meaningful for understanding poverty rates in
this dataset and make it publication-ready using theme()
and other ggplot customization tools. Your polished graph must include
at minimum:
theme() modifications (font size,
background, gridlines, legend position, etc.)ggplot(midwest, aes(x = percollege, y = percbelowpoverty)) +
geom_point(aes(color = percbelowpoverty), alpha = 0.75, size = 2) +
geom_smooth(method = "lm", se = FALSE, color = "#1b3a5c", linewidth = 1.1) +
scale_color_gradient(low = "#7fb3d5", high = "#922b21") +
labs(
title = "More College Graduates, Less County Poverty",
subtitle = "437 counties across five midwestern states",
x = "Adults with a college degree (%)",
y = "Population below the poverty line (%)",
color = "Poverty rate (%)"
) +
theme_minimal(base_size = 13) +
theme(
plot.title = element_text(face = "bold", size = 16),
plot.subtitle = element_text(color = "gray40"),
panel.grid.minor = element_blank(),
legend.position = "bottom",
plot.background = element_rect(fill = "white", color = NA)
)
ANSWER: The story is the same downward slope from Part 2, dressed for presentation. I mapped color to the poverty rate itself so the hardest-hit counties glow red and reinforce the y-axis, then dropped the minor gridlines and moved the legend to the bottom to keep the eye on the data instead of the frame. The plain-English title states the finding outright, since a reader should get the point before reading a single axis label.
Answer each of the following in complete sentences. Be specific!
8A. Classification and Machine Learning
In the Teachable Machine activity, you trained a classification model by providing examples. In your own words, explain what a classification model is trying to do (how is it different from the regression models you’ve been building?), and describe one real-world situation where you’d reach for classification instead of regression. What could go wrong if a classification model trained on a narrow set of examples is deployed in a much broader context?
ANSWER: A classification model sorts an input into one of a set of labeled buckets. It answers “which category,” not “how much.” That is the split from regression: my poverty model predicts a number on a continuous scale, while a classifier predicts a class like spam or not-spam. I’d reach for classification to flag whether an incoming email is phishing, since the useful output is a yes-or-no decision, not a quantity. The danger with narrow training data is that the model only knows the examples it saw. Teachable Machine trained on my face in my lighting will confidently mislabel a stranger in a dark room, because it never learned the range it’s now being asked to judge. Deployed broadly, that shows up as biased, overconfident predictions on any group the examples left out.
8B. The Python Demo — Decision Trees
A real data scientist showed you how to build a decision tree model in Python. Reflect on that demo: what was one thing about how decision trees work that surprised you or made you think differently about how models make predictions? How did seeing a working data scientist’s actual code and workflow compare to what you expected data science to look like?
ANSWER: What surprised me was how literal a decision tree is. It’s just a stack of yes/no questions, and you can trace any single prediction straight down the branches, which feels far more readable than the wall of coefficients in my regression output. Watching a real workflow was the bigger reality check. I pictured data science as writing one clean model that works. Instead the demo was mostly loading data, printing it, fixing a bug, re-running a cell, and checking the output again before any modeling happened. The actual skill looked less like math and more like patient tinkering.