Week 9, Lecture 15
October 27, 2026
When you reach your parents’ age, do you expect to be financially better off, about the same, or worse off than they are now?
Harvard asked young adults how they expect to do financially at their parents’ age.
Only about one in four expect to be better off.
What percentage of 18–29-year-olds call AI replacing jobs a serious threat to their long-term financial security? (Enter a number from 0 to 100.)
list.files("data/harvard")items.csv lists the questions, their type (kind), and the file containing each one’s responses.
items <- read_csv("data/harvard/items.csv")
items |>
select(item, kind, file) |>
print(n = Inf)Which questions would you most like to investigate?
for loop and map().ai <- read_csv("data/harvard/threat_ai_jobs.csv")
glimpse(ai)item identifies the survey question.group identifies all respondents or a party group.response gives the answer category.percent gives the weighted percentage choosing that answer within the group.base_n gives the number of respondents in the group.What percentage of all respondents consider AI a “Very serious” or “Somewhat serious” threat?
ai |> filter(group == "All") |> select(response, percent)
# Calculate the overall serious-threat percentage.overall <- ai |> filter(group == "All")
overall |>
ggplot(aes(response)) +
geom_bar()Why are all the bars equal? Fix the plot to show the response percentages and submit it.
Investigate the response distributions for Democrats, Republicans, and independents.
by_party <- ai |> filter(group != "All")
# Investigate how the responses differ across parties.We don’t have the raw respondent-level data, only the summarized percentages, so estimate counts as round(base_n * percent / 100). Omit Not sure and Refused.
party_counts <- ai |>
filter(group %in% c("Democrat", "Republican"),
!response %in% c("Not sure", "Refused")) |>
mutate(estimated_n = round(base_n * percent / 100))
# Create a serious/not-serious column.
# Sum the counts into a table with:
# xtabs(estimated_n ~ serious + group, data = ...)AI was one of seven financial-threat questions. The survey also asked about nine policies.
Are young Democrats, Republicans, and independents worried about the same things? Do they support the same policies?
We need to bring the question files together to investigate.
We have sixteen questions, each in its own file. We want to analyze them together. Here are a few rows from the combined table.
# A tibble: 4 × 5
item group response percent base_n
<chr> <chr> <chr> <dbl> <dbl>
1 Large corporations All Very serious 40.1 2024
2 Large corporations All Somewhat serious 28.1 2024
3 AI replacing jobs All Very serious 35.7 2024
4 AI replacing jobs All Somewhat serious 28.7 2024
The columns stay the same. We stack the rows from each file.
bind_rows() stacks tables, matching their columns by name.
survey_manual <- bind_rows(
read_csv("data/harvard/threat_billionaires.csv", show_col_types = FALSE),
read_csv("data/harvard/threat_corporations.csv", show_col_types = FALSE), read_csv("data/harvard/threat_immigration_jobs.csv", show_col_types = FALSE),
read_csv("data/harvard/threat_ai_jobs.csv", show_col_types = FALSE),
read_csv("data/harvard/threat_cost_of_living.csv", show_col_types = FALSE),
read_csv("data/harvard/threat_national_debt.csv", show_col_types = FALSE),
read_csv("data/harvard/threat_trade_wars.csv", show_col_types = FALSE),
read_csv("data/harvard/policy_medicare_for_all.csv", show_col_types = FALSE),
read_csv("data/harvard/policy_wealth_tax.csv", show_col_types = FALSE),
read_csv("data/harvard/policy_deportations.csv", show_col_types = FALSE),
read_csv("data/harvard/policy_israel_aid.csv", show_col_types = FALSE),
read_csv("data/harvard/policy_transgender_sports.csv", show_col_types = FALSE),
read_csv("data/harvard/policy_stock_trading.csv", show_col_types = FALSE),
read_csv("data/harvard/policy_voter_id.csv", show_col_types = FALSE),
read_csv("data/harvard/policy_abortion.csv", show_col_types = FALSE),
read_csv("data/harvard/policy_tariffs.csv", show_col_types = FALSE)
)
survey_manual |> count(item)What would you have to change if Harvard added ten more questions?
The filenames are already listed in items.csv. We will study ways to read them all efficiently.
survey_files <- paste0("data/harvard/", items$file)
survey_filesA for loop runs the code inside its braces once for each file. file takes the next filename on each pass.
for (file in survey_files) {
survey <- read_csv(
file, show_col_types = FALSE)
}
survey |> count(item)
# Repair the loop to keep every question.A list can hold all sixteen tables. survey_tables[[file]] stores a table under its filename.
survey_tables <- list()
for (file in survey_files) {
survey_tables[[file]] <- read_csv(file, show_col_types = FALSE)
}
survey_loop <- list_rbind(survey_tables)
survey_loop |> count(item)list_rbind() stacks the tables in the list into one data frame.
Each colored box is an input. The same function f is applied to each one.
Our inputs are filenames, f is read_csv(), and each output is a table.
map() calls a function on each input and collects its outputs in a list. Here the inputs are filenames and the function is read_csv().
survey_tables <- map(survey_files, read_csv, show_col_types = FALSE)
survey_tables[[1]][[1]] displays the first table in the list. Which part of the loop is map() handling for us?
Use map() and list_rbind() to create the combined table.
survey_files
# Read every file and combine its rows into survey.survey <- survey_files |> map(read_csv, show_col_types = FALSE) |>
list_rbind()
survey |> count(item)Six response categories for four groups: 24 rows per question.
items identifies seven financial-security questions and nine policy questions.
survey_with_types <- survey |>
left_join(items |> select(item, kind), by = "item")threats <- survey_with_types |> filter(kind == "threat")
policies <- survey_with_types |> filter(kind == "policy")
survey_with_types |> count(kind)Use threats for questions about financial security and policies for questions about policy support.
Which of these do you feel is the biggest threat to your long-term financial security?
Among the seven questions in threats, which financial-security issue concerns each party most? Find which financial-security issue has the highest combined percentage of Very serious and Somewhat serious responses for each party.
threats |> count(response)
# Find the biggest concern within each party group.What is the top financial concern among Democrats, Republicans, and independents? Report the concern and percentage for each group.
threat_results <- threats |>
filter(response %in% c("Very serious", "Somewhat serious")) |> group_by(item, group) |>
summarize(percent = sum(percent), .groups = "drop")
threat_results |>
filter(group != "All") |>
group_by(group) |>
top_n(1, percent)Cost of living tops the list for all three groups.
Define net serious as the percentage saying Very serious or Somewhat serious minus the percentage saying Not very serious or Not a threat.
How do party groups differ on this score?
threats |>
filter(response %in% c("Very serious", "Somewhat serious",
"Not very serious", "Not a threat"))
# Calculate net-serious scores and find the largest party gap.threat_net <- threats |>
filter(response %in% c("Very serious", "Somewhat serious",
"Not very serious", "Not a threat")) |>
mutate(contribution = ifelse(
response %in% c("Very serious", "Somewhat serious"), percent, -percent)) |>
group_by(item, group) |>
summarize(net_serious = sum(contribution), .groups = "drop")threat_comparison <- threat_net |>
pivot_wider(names_from = group, values_from = net_serious) |> mutate(party_gap = Democrat - Republican, abs_gap = abs(party_gap))
threat_comparison |>
arrange(desc(party_gap)) |>
select(item, Democrat, Republican, Independent, party_gap)Our code sorts the issues from biggest to smallest gap. Can you spot a bug?
threat_comparison |>
arrange(desc(abs_gap)) |>
select(item, Democrat, Republican, Independent, abs_gap)Immigration is near the top because the Republican score is much higher.
Make a plot of the net-serious scores for each issue across dems, republicans, and independents.
threat_net
# Plot the scores for each party group.Plot net-serious scores for all seven concerns by party. Identify one threat that is widely shared, and one that is divisive.
Which policy do you predict will have majority support among Democrats, Republicans, and independents? What makes you think so?
Calculate support for all nine policies, then find those that pass the 50% threshold in all three party groups.
policies |> count(response)
# Calculate policy_results and find majority support across parties.policy_attempt <- policies |>
filter(response %in% c("Strongly support", "Somewhat support")) |>
group_by(group) |>
summarize(percent = sum(percent), .groups = "drop")
policy_attemptWhy are these support percentages greater than 100? Explain what went wrong and how to repair the calculation.
policy_results <- policies |>
filter(response %in% c("Strongly support", "Somewhat support")) |> group_by(item, group) |>
summarize(percent = sum(percent), .groups = "drop")
policy_results |> count(item)policy_comparison <- policy_results |>
pivot_wider(names_from = group, values_from = percent) |> mutate(party_gap = Democrat - Republican, abs_gap = abs(party_gap))
policy_comparison |>
filter(Democrat > 50, Republican > 50, Independent > 50) |>
select(item, Democrat, Republican, Independent)Did either result surprise you?
policy_comparison
# Find the largest and smallest absolute party gaps.Which policies have the biggest and smallest Democrat–Republican gaps in support?
The proposal would prevent members of Congress and their spouses from trading individual stocks while in office.
# A tibble: 1 × 5
item All Democrat Republican Independent
<chr> <dbl> <dbl> <dbl> <dbl>
1 Congressional stock-trading ban 63.3 70.7 62.4 62.4
A majority supports it in each party group. It also has the smallest Democratic–Republican gap among these nine policies.
Name a policy on which Democrats and Republicans strongly disagree.
Which policies have majority support overall but fall below 50% in at least one party group?
policy_comparison
# Find policies that meet the condition above.policy_comparison |>
filter(All > 50,
Democrat < 50 | Republican < 50 | Independent < 50) |>
select(item, All, Democrat, Republican, Independent)We have each group’s support for nine policies.
Across those policies, does the pattern among independents resemble Democrats’ or Republicans’ more closely?
Use scatterplots and correlation to investigate.
independent_plot_data <- policy_comparison |>
select(item, Independent, Democrat, Republican) |>
pivot_longer(c(Democrat, Republican),
names_to = "party", values_to = "support")
# Compare each party with independents.policy_comparison |>
select(item, Democrat, Independent, Republican)
# Find a counterexample.Name a policy where independents are closer to Republicans. Give the three parties’ support percentages.
map().list_rbind().items and separate threats from policies.