Week 7
Data Wrangling and Visualization
Problem Set 7 Solutions
This weekโs solution is again a small project: a master script that reads the two ANES files, source()s a management script for each, stacks the two summaries, and then draws every version of Bartelsโs Figures 1 and 2 โ original years, extended to 2024, with and without midterms โ by re-running the same three figure scripts against a differently filtered dataset each time. A Quarto write-up pulls in the saved figures.
Download the whole project here โ every script and the write-up, already laid out in the folder structure described below.
Every path in these scripts is relative to the project folder, and the master source()s the other scripts by their exact file names, so the layout has to be exactly this:
bartels2026/ the project folder; the setwd() lines point here
anes_timeseries_cdf_stata_20220916.dta the cumulative ANES (not included; get it from electionstudies.org)
anes_timeseries_2024_stata_20250808.dta the 2024 ANES time series (not included; same place)
figures/ the master saves its eight figure PDFs here
DWV Assign 7 - Bartels (Master).r run this; it sources the rest in order
DWV Assign 7 - Bartels (Cumulative Management).R
DWV Assign 7 - Bartels (2024 Management).R
DWV Assign 7 - Bartels (Figure 1A).r
DWV Assign 7 - Bartels (Figure 1B).r
DWV Assign 7 - Bartels (Figure 1 Combine).r
DWV Assign 7 - Bartels (Figure 2).r
DWV Assign 7 - Bartels (draft).r the first working-out; not sourced by the master
Bartels Replication.qmd the write-up, plus its rendered PDF
The zip unpacks into this layout, minus the two data files (download them yourself). One line to edit before running: the setwd() at the top of the master must point at wherever you put the folder. Packages: tidyverse, haven, srvyr, and patchwork.
The download links below use tidier file names (dwv_ps7_bartels_master.R and so on); the files inside the zip keep the original names, because that is what the masterโs source() calls look for.
The design
The trick that keeps this project short is in the master. Each figure script draws from an object called figure1_data (or figure2_data) and reads the end year and the caption from endyear and caption_text. The master sets those three things, sources the figure script, saves the result, and then sets them again for the next variant โ so one Figure 1 script yields four PDFs, and one Figure 2 script yields four more. Compare that with the single-file draft at the bottom of this page, where each variant would have meant copying the whole plot.
The scripts
Here they are in the order the master runs them.
Download dwv_ps7_bartels_master.R
DWV Assign 7 - Bartels (Master).r
#Title: DWV Assign 7 - Bartels.R
#Author: Jack Reilly
#Purpose: Replicate Figures 1 and 2 from Bartels. Manage other project files
#Requires: Cumulative ANES; 2024 ANES
#Output: Figures as PDFs (ggplot only)
library(tidyverse)
library(haven)
setwd("~/desktop/working/dwv/bartels2026")
#For this one, we're going to approach things a little differently than we did in the big file
#We're going to create a big dataset
#And create a graphing file
#And then filter down to just the data that we want and run the graphing "command" file
#Step 1. Manage Cumulative NES
anes_full<-read_dta("anes_timeseries_cdf_stata_20220916.dta")
source("DWV Assign 7 - Bartels (Cumulative Management).R")
#Step 2. Manage 2024 NES
source("DWV Assign 7 - Bartels (2024 Management).R")
#Step 3. Merge the two
#Meticulously check things
figure1_cumul
figure1_24
figure1_master <- rbind(figure1_cumul, figure1_24)
head(figure1_master)
tail(figure1_master)
#Meticulously check things
figure2_cumul
figure2_24
figure2_master <- rbind(figure2_cumul, figure2_24)
head(figure2_master)
tail(figure2_master)
#Ed. Note: this took a long time, making everything fit just right!
#Looks clean in code . . . not clean in process
#Step 4. Draw Figure 1
figure1_data <- figure1_master |> filter(presyear == 1, year<1997)
endyear<-1996
caption_text<-"Presidential Election Years Only, Bartels Years"
source("DWV Assign 7 - Bartels (Figure 1A).r")
bartels1A
source("DWV Assign 7 - Bartels (Figure 1B).r")
bartels1B
# "Patch" the two together
library(patchwork)
bartels1A / bartels1B
#We can do better.
source("DWV Assign 7 - Bartels (Figure 1 Combine).r")
bartels1
ggsave("figures/bartels1_1original.pdf", bartels1,
width = 7, height = 10, dpi = 300)
#Step 5. Draw Figure 1, with Expanded Years
figure1_data <- figure1_master |> filter(presyear == 1)
endyear<-2024
caption_text<-"Presidential Election Years Only, All Years"
source("DWV Assign 7 - Bartels (Figure 1 Combine).r")
bartels1
ggsave("figures/bartels1_2expanded.pdf", bartels1,
width = 7, height = 10, dpi = 300)
#Step 6. Draw Figure 1, with Midterms
figure1_data <- figure1_master |> filter(year<1997)
endyear<-1997
caption_text<-"Midterms Included, Bartels Years"
source("DWV Assign 7 - Bartels (Figure 1 Combine).r")
bartels1
ggsave("figures/bartels1_3midterms.pdf", bartels1,
width = 7, height = 10, dpi = 300)
#Step 7. Draw Figure 1, with Midterms and Expanded Years
figure1_data <- figure1_master
endyear<-2024
caption_text<-"Midterms Included, All Years"
source("DWV Assign 7 - Bartels (Figure 1 Combine).r")
bartels1
ggsave("figures/bartels1_4midterms_expanded.pdf", bartels1,
width = 7, height = 10, dpi = 300)
#Step 8. Draw Figure 2 - Bartels, Presidential
figure2_data<-figure2_master |> filter(presyear == 1, year<1997)
endyear<-1997
caption_text<-"Presidential Election Years Only, Bartels Years"
source("DWV Assign 7 - Bartels (Figure 2).r")
bartels2
ggsave("figures/bartels2_1original.pdf", bartels2,
width = 7, height = 10, dpi = 300)
#Step 9. Draw Figure 2 - All Years, Presidential
figure2_data<-figure2_master |> filter(presyear == 1)
endyear<-2024
caption_text<-"Midterms Included, All Years"
source("DWV Assign 7 - Bartels (Figure 2).r")
bartels2
ggsave("figures/bartels2_2expanded.pdf", bartels2,
width = 7, height = 10, dpi = 300)
#Step 10. Draw Figure 2 - Bartels Years, Midterms Included
figure2_data<-figure2_master |> filter(year<1997)
endyear<-1997
caption_text<-"Midterms Included, Bartels Years"
source("DWV Assign 7 - Bartels (Figure 2).r")
bartels2
ggsave("figures/bartels2_3midterms.pdf", bartels2,
width = 7, height = 10, dpi = 300)
#Step 10. Draw Figure 2 - All Years, Midterms Included
figure2_data<-figure2_master
endyear<-2024
caption_text<-"Midterms Included, Bartels Years"
source("DWV Assign 7 - Bartels (Figure 2).r")
bartels2
ggsave("figures/bartels2_4midterms_expanded.pdf", bartels2,
width = 7, height = 10, dpi = 300)
Download dwv_ps7_bartels_cumulative_management.R โ recodes the cumulative file (party ID, turnout, a presidential-year flag) and collapses it to one weighted row per year with srvyr.
DWV Assign 7 - Bartels (Cumulative Management).R
#Title: DWV Assign 7 - Bartels.R
#Author: Jack Reilly
#Purpose: Replicate Figures 1 and 2 from Bartels
#Requires: Cumulative ANES; 2024 ANES
#Output: Figures as PDFs (ggplot only)
#anes_full<-read_dta("anes_timeseries_cdf_stata_20220916.dta")
anes<-anes_full
names(anes)<-tolower(names(anes))
#0. Find Variables, note missing
unique(anes$vcf0301) #orgpid
unique(anes$vcf0004) #year
unique(anes$vcf0702) #voted
unique(anes$vcf0703) #randvote
unique(anes$vcf0704) #presvote
unique(anes$vcf0704a)#presvotemajparty
unique(anes$vcf0009z)#survey weight
#optional: rename them intuitively
anes$orgpid <- anes$vcf0301
anes$year <- anes$vcf0004
anes$voted <- anes$vcf0702
anes$randvote <- anes$vcf0703
anes$presvote <- anes$vcf0704
anes$presvotemajparty <- anes$vcf0704a
anes$weight <- anes$vcf0009z
#1. Filter and select
#anes <- anes |> select(vcf0301, vcf0004, vcf0702, vcf0703, vcf0704, vcf0704a, vcf0009z)
anes <- anes |> select(orgpid, year, voted, randvote, presvote, presvotemajparty, weight)
#Later, we'll need to manipulate things around midterm and not midterm years. So I'm just going to make a variable to make that easy now
anes <- anes %>%
mutate(presyear = if_else(
year %in% c(1948, 1952, 1956, 1960, 1964, 1968,
1972, 1976, 1980, 1984, 1988, 1992,
1996, 2000, 2004, 2008, 2012, 2016,
2020, 2024),
1, 0
))
#2. Manipulate
#Drop the DKs, etc
anes <- anes %>%
mutate(pid = if_else(orgpid == 0, NA_real_, orgpid))
#Now, this requires a little bit of thought.
#We need proportions, so we need 1 and 0 for strong, weak, leaners, and indeps
#For when we summarize the data later
anes <- anes %>%
mutate(
strong = case_when(
pid %in% c(1, 7) ~ 1,
pid %in% 2:6 ~ 0,
),
weak = case_when(
pid %in% c(2, 6) ~ 1,
pid %in% c(1, 3, 4, 5, 7) ~ 0,
),
leaners = case_when(
pid %in% c(3, 5) ~ 1,
pid %in% c(1, 2, 4, 6, 7) ~ 0,
),
indeps = case_when(
pid == 4 ~ 1,
pid %in% c(1, 2, 3, 5, 6, 7) ~ 0,
)
)
#For figure 2, we need to combine some of them
anes <- anes %>%
mutate(
# Generate a dummy variable for all party identifiers, strong or weak
identifiers = strong + weak,
# Variable equals "identifiers" for voters (voted==2), missing otherwise
identvote = if_else(voted == 2, identifiers, NA),
# Variable equals "identifiers" for nonvoters (voted==1), missing otherwise
identnovote = if_else(voted == 1, identifiers, NA),
# Simple turnout variable: recode voted (1=0 nonvoter, 2=1 voter, else missing)
turnout = case_when(
voted == 1 ~ 0,
voted == 2 ~ 1,
)
)
#3. Declare the survey and collapse the data
anes_svy <- anes %>%
as_survey_design(weights = weight)
# now your dplyr pipeline works, but with survey-weighted means
figure1_cumul <- anes_svy %>%
filter(year > 1950) %>%
group_by(year) %>%
summarise(
strong = survey_mean(strong, na.rm = TRUE, vartype = NULL),
weak = survey_mean(weak, na.rm = TRUE, vartype = NULL),
leaners = survey_mean(leaners,na.rm = TRUE, vartype = NULL),
indeps = survey_mean(indeps, na.rm = TRUE, vartype = NULL),
presyear = round(survey_mean(presyear, vartype = NULL), 1) #this round command fixes an annoying filter error later!
)
figure2_cumul <- anes_svy %>%
filter(year > 1950) %>%
group_by(year, turnout) %>%
summarise(
identifiers = survey_mean(identifiers, na.rm = TRUE, vartype = NULL),
presyear = round(survey_mean(presyear, vartype = NULL), 1) #this round command fixes an annoying filter error later!
)Download dwv_ps7_bartels_2024_management.R โ the same recodes for the 2024 file, whose variable names and turnout coding differ, built to produce rows that stack onto the cumulative summary. Note the detour on weights.
DWV Assign 7 - Bartels (2024 Management).R
#Title: DWV Assign 7 - Bartels.R
#Author: Jack Reilly
#Purpose: Replicate Figures 1 and 2 from Bartels
#Requires: Cumulative ANES; 2024 ANES
#Output: Figures as PDFs (ggplot only)
anes24<-read_dta("anes_timeseries_2024_stata_20250808.dta")
names(anes24)<-tolower(names(anes24))
#0. Find Variables, note missing
unique(anes24$v241227x) #orgpid
unique(anes24$v242095x) #voted
unique(anes24$v240103b)# post survey weight
#optional: rename them intuitively
anes24$orgpid <- anes24$v241227x
anes24$voted <- anes24$v242095x
anes24$weight_post <- anes24$v240103a
#1. Filter and select
anes24 <- anes24 |> select(orgpid, voted, weight_post)
#Check
anes24
#2. Manipulate
#Drop the DKs, etc
anes24 <- anes24 %>%
mutate(pid = if_else(orgpid %in% -9:-4, NA_real_, orgpid))
#Check
table(anes24$orgpid)
table(anes24$pid)
#Now, this requires a little bit of thought.
#We need proportions, so we need 1 and 0 for strong, weak, leaners, and indeps
#For when we summarize the data later
anes24 <- anes24 %>%
mutate(
strong = case_when(
pid %in% c(1, 7) ~ 1,
pid %in% 2:6 ~ 0,
),
weak = case_when(
pid %in% c(2, 6) ~ 1,
pid %in% c(1, 3, 4, 5, 7) ~ 0,
),
leaners = case_when(
pid %in% c(3, 5) ~ 1,
pid %in% c(1, 2, 4, 6, 7) ~ 0,
),
indeps = case_when(
pid == 4 ~ 1,
pid %in% c(1, 2, 3, 5, 6, 7) ~ 0,
)
)
#For figure 2, we need to combine some of them
anes24 <- anes24 %>%
mutate(
# Generate a dummy variable for all party identifiers, strong or weak
identifiers = strong + weak,
# Variable equals "identifiers" for voters (voted==2), missing otherwise
identvote = if_else(voted == 2, identifiers, NA),
# Variable equals "identifiers" for nonvoters (voted==1), missing otherwise
identnovote = if_else(voted == 1, identifiers, NA),
# Simple turnout variable: recode voted (1=0 nonvoter, 2=1 voter, else missing)
turnout = case_when(
voted == 1 ~ 0,
voted == 2 ~ 1,
)
)
#Figure 2
anes24 <- anes24 %>%
mutate(
# Generate a dummy variable for all party identifiers, strong or weak
identifiers = strong + weak,
# Variable equals "identifiers" for voters (voted==2), missing otherwise
identvote = if_else(voted == 2, identifiers, NA),
# Variable equals "identifiers" for nonvoters (voted==1), missing otherwise
identnovote = if_else(voted == 1, identifiers, NA),
# Simple turnout variable: recode voted (1=0 nonvoter, 2=1 voter, else missing)
#Coding is different than cumulative: we still recode so NAs implicitly become NAs
turnout = case_when(
voted == 0 ~ 0,
voted == 1 ~ 1,
)
)
#Now we need year variables
anes24$year<-2024
anes24$presyear<-1
#3. Declare the survey and collapse the data
#anes24_svy <- anes24 %>% as_survey_design(weights = weight_post)
#Huh. That's weird. A lot of the observations don't have a weight (yet?)
#Two options: eliminate non-weighted observations, and don't weight
#Option 1: Eliminate non-weighted observations
anes24_clean <- anes24 %>% filter(!is.na(weight_post))
anes24_svy <- anes24_clean %>% as_survey_design(weights = weight_post)
# now your dplyr pipeline works, but with survey-weighted means
figure1_data24_w <- anes24_svy %>%
summarise(
strong = survey_mean(strong, na.rm = TRUE, vartype = NULL),
weak = survey_mean(weak, na.rm = TRUE, vartype = NULL),
leaners = survey_mean(leaners,na.rm = TRUE, vartype = NULL),
indeps = survey_mean(indeps, na.rm = TRUE, vartype = NULL)
)
figure1_data24_w
#Option 2: Don't use the weight for 2024
#How many obserations don't have a weight?
sum(is.na(anes24$weight_post))
#Erm . . . that's a lot of observations
figure1_24 <- anes24 %>%
# summarize means of selected vars
#note ordering - done this way to match cumulative
summarise(
year = mean(year),
strong = mean(strong, na.rm=T),
weak = mean(weak, na.rm=T),
leaners = mean(leaners, na.rm=T),
indeps = mean(indeps, na.rm=T),
presyear = mean(presyear)
)
figure1_24
figure2_24 <- anes24 %>%
group_by(turnout) %>%
summarise(
year = mean(year),
identifiers = mean(identifiers, na.rm = TRUE),
presyear = mean(presyear)
)
figure2_24Download dwv_ps7_bartels_fig1a.R โ the top half of Figure 1: strong and weak identifiers.
DWV Assign 7 - Bartels (Figure 1A).r
#Title: DWV Assign 7 - Bartels (Figure 1A).R
#Author: Jack Reilly
#Purpose: Replicate Figures 1A from Bartels
#Requires: Managed objects from prior scripts
#Output: Figures as PDFs (ggplot only)
#You can build a GGPlot in any order you like
#Generally, it's advisable to follow similar patterns
#I like to build "up" from the base plot area, finishing with styling
#1. Plot Area
#2. Scales & Axes
#3. Main Data
#4. Labels/Titles
#5. Styling
bartels1A <- ggplot(figure1_data, aes(x = year)) +
#Next, our scales and axes
scale_y_continuous(
limits = c(0, 0.5),
breaks = seq(0, 0.5, 0.1),
minor_breaks = seq(0, 0.5, 0.05),
name = NULL
) +
scale_x_continuous(
breaks = seq(1956, endyear, 8),
minor_breaks = seq(1952, endyear, 4),
name = "Year"
) +
# Reference lines
# Horizontal reference lines at 0.1 ... 0.5 (vvthin + dotted + black)
geom_hline(yintercept = seq(0.1, 0.5, 0.1),
linetype = "dotted", linewidth = 0.2, color = "black") +
# Vertical reference lines at election years (vvthin + dotted + black)
geom_vline(
xintercept = seq(1956, endyear, by = 8),
linetype = "dotted",
linewidth = 0.2,
color = "black"
) +
# Strong: connected (solid line + filled points)
geom_line(aes(y = strong, linetype = "Strong Identifiers"), linewidth = 0.7, color = "black") +
geom_point(aes(y = strong, shape = "Strong Identifiers"), size = 2.2, stroke = 0.8,
color = "black", fill = "black") +
# Weak: connected (dashed line + hollow circle points)
geom_line(aes(y = weak, linetype = "Weak Identifiers"), linewidth = 0.7, color = "black") +
geom_point(aes(y = weak, shape = "Weak Identifiers"), size = 2.2, stroke = 0.9,
color = "black", fill = NA) +
#Titles and labeling
labs(
title = paste0("Distribution of Party Identification 1952-", endyear), #Paste puts a string in as specified, which lets me use my passed endyear variable
subtitle = "Proportions of National Election Study Sample",
caption = caption_text
) +
# Map linetypes and shapes to series (order matches legend order)
scale_linetype_manual(
values = c("Strong Identifiers" = "solid",
"Weak Identifiers" = "dashed"),
guide = guide_legend(order = 1)
) +
scale_shape_manual(
values = c("Strong Identifiers" = 16, # filled circle
"Weak Identifiers" = 1), # hollow circle
guide = guide_legend(order = 1)
) +
# Legend: one column, inside the plot
guides(
linetype = guide_legend(ncol = 1, title = NULL),
shape = guide_legend(ncol = 1, title = NULL)
) +
theme_minimal(base_size = 12) +
theme(
panel.border = element_rect(color = "black", fill = NA, linewidth = 0.5),
legend.position = c(0.6, 0.2), # tweak as desired
legend.background = element_rect(fill = "white", color = "grey70", linewidth = 0.3), # light box
panel.grid.minor = element_line(linewidth = 0.2),
panel.grid.major = element_line(linewidth = 0.3),
plot.title = element_text(face = "bold")
)
bartels1ADownload dwv_ps7_bartels_fig1b.R โ the bottom half: leaners and pure independents.
DWV Assign 7 - Bartels (Figure 1B).r
#Title: DWV Assign 7 - Bartels (Figure 1B).R
#Author: Jack Reilly
#Purpose: Draw Figure 1B from Bartels
#Requires: Managed objects from prior scripts
#Output: Figures as PDFs (ggplot only)
bartels1B<-ggplot(figure1_data, aes(x = year)) +
#Next, our scales and axes
scale_y_continuous(
limits = c(0, 0.5),
breaks = seq(0, 0.5, 0.1),
minor_breaks = seq(0, 0.5, 0.05),
name = NULL
) +
scale_x_continuous(
breaks = seq(1956, endyear, 8),
minor_breaks = seq(1952, endyear, 4),
name = "Year"
) +
# Reference lines
# Horizontal reference lines at 0.1 ... 0.5 (vvthin + dotted + black)
geom_hline(yintercept = seq(0.1, 0.5, 0.1),
linetype = "dotted", linewidth = 0.2, color = "black") +
# Vertical reference lines at election years (vvthin + dotted + black)
geom_vline(
xintercept = seq(1956, endyear, by = 8),
linetype = "dotted",
linewidth = 0.2,
color = "black"
) +
# Leaners: dashed + hollow
geom_line(aes(y = leaners, linetype = "Independent Leaners"), linewidth = 0.7, color = "black") +
geom_point(aes(y = leaners, shape = "Independent Leaners"),
size = 2.2, stroke = 0.9, color = "black", fill = NA) +
# Pure Independents: solid + filled
geom_line(aes(y = indeps, linetype = "Pure Independents"), linewidth = 0.7, color = "black") +
geom_point(aes(y = indeps, shape = "Pure Independents"),
size = 2.2, stroke = 0.8, color = "black", fill = "black") +
labs(
title = paste0("Distribution of Party Identification 1952-", endyear), #Paste puts a string in as specified, which lets me use my passed endyear variable
subtitle = "Proportions of National Election Study Sample",
caption = caption_text
) +
# Map linetypes and shapes to series (order matches your legend order)
scale_linetype_manual(
values = c("Independent Leaners" = "dashed",
"Pure Independents" = "solid"),
guide = guide_legend(order = 1)
) +
scale_shape_manual(
values = c("Independent Leaners" = 1, # hollow circle
"Pure Independents" = 16), # filled circle
guide = guide_legend(order = 1)
) +
# Legend: one column, inside the plot (similar to position(5) ring(0))
guides(
linetype = guide_legend(ncol = 1, title = NULL),
shape = guide_legend(ncol = 1, title = NULL)
) +
theme_minimal(base_size = 12) +
theme(
panel.border = element_rect(color = "black", fill = NA, linewidth = 0.5),
legend.position = c(0.4, 0.8), # tweak as desired
legend.background = element_rect(fill = "white", color = "grey70", linewidth = 0.3), # light box
panel.grid.minor = element_line(linewidth = 0.2),
panel.grid.major = element_line(linewidth = 0.3),
plot.title = element_text(face = "bold")
)
bartels1BDownload dwv_ps7_bartels_fig1_combine.R โ stacks the two halves with patchwork and strips the now-redundant axis and title elements.
DWV Assign 7 - Bartels (Figure 1 Combine).r
#Just in case these weren't run before
source("DWV Assign 7 - Bartels (Figure 1A).r")
bartels1A
source("DWV Assign 7 - Bartels (Figure 1B).r")
bartels1B
# "Patch" the two together
library(patchwork)
bartels1A / bartels1B
#Let's just use GGplot to clean away some of those now extraneous elements
bartels1 <- (bartels1A +
theme(
axis.title.x = element_blank(),
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
plot.caption = element_blank()
)
) /
(bartels1B +
labs(title = NULL, subtitle = NULL) +
theme(axis.title.x = element_blank())
)
bartels1Download dwv_ps7_bartels_fig2.R โ Figure 2: identifiers among voters and nonvoters.
DWV Assign 7 - Bartels (Figure 2).r
#Title: DWV Assign 7 - Bartels (Figure 2).R
#Author: Jack Reilly
#Purpose: Draw Figure 2 from Bartels
#Requires: Managed objects from prior scripts
#Output: Figures as PDFs (ggplot only)
bartels2<-ggplot() +
# horizontal y reference lines at .1(.1)1
geom_hline(yintercept = seq(0.1, 1.0, 0.1),
linetype = "dotted", linewidth = 0.2, color = "black") +
# vertical x reference lines at listed years
geom_vline(xintercept = seq(1952, endyear, by = 4),
linetype = "dotted", linewidth = 0.2, color = "black") +
# nonvoters: dashed line + hollow circles
geom_line(data = subset(figure2_data, turnout == 0),
aes(x = year, y = identifiers, linetype = "Nonvoters"),
linewidth = 0.7, color = "black") +
geom_point(data = subset(figure2_data, turnout == 0),
aes(x = year, y = identifiers, shape = "Nonvoters"),
size = 2.2, stroke = 0.9, color = "black", fill = NA) +
# voters: solid line + filled circles
geom_line(data = subset(figure2_data, turnout == 1),
aes(x = year, y = identifiers, linetype = "Voters"),
linewidth = 0.7, color = "black") +
geom_point(data = subset(figure2_data, turnout == 1),
aes(x = year, y = identifiers, shape = "Voters"),
size = 2.2, stroke = 0.8, color = "black", fill = "black") +
# axes, labels, scales
scale_y_continuous(
limits = c(0, 1),
breaks = seq(0, 1, 0.1),
minor_breaks = seq(0, 1, 0.05),
name = NULL # ytitle("")
) +
scale_x_continuous(
breaks = seq(1956, endyear, 8),
minor_breaks = seq(1956, endyear, 4),
name = "Year" # xtitle
) +
# title/subtitle/note
labs(
title = "Party Identification Among Presidential Voters and Nonvoters",
subtitle = "Proportions of Strong or Weak Identfiers in National Election Study Sample",
caption = "Presidential Election Years Only Weighted",
linetype = NULL, shape = NULL
) +
# legend styling & ordering
scale_linetype_manual(values = c("Nonvoters" = "dashed", "Voters" = "solid")) +
scale_shape_manual(values = c("Nonvoters" = 1, "Voters" = 16)) +
guides(
linetype = guide_legend(ncol = 1, order = 1),
shape = guide_legend(ncol = 1, order = 1)
) +
# theme tweaks (panel box + light legend box, legend inside near top-right)
theme_minimal(base_size = 12) +
theme(
panel.border = element_rect(color = "black", fill = NA, linewidth = 0.5),
panel.grid.minor = element_line(linewidth = 0.2),
panel.grid.major = element_line(linewidth = 0.3),
legend.position = c(0.2, 0.2), # tweak
legend.background= element_rect(fill = "white", color = "grey70", linewidth = 0.3),
plot.title = element_text(face = "bold")
)
bartels2The write-up
Download the Quarto write-up and its rendered PDF. In the project it lives next to the scripts as Bartels Replication.qmd, which is why its image paths start with figures/.
Bartels Replication.qmd
---
title: "Bartels Replication"
author: "Jack Reilly"
format:
pdf: default
---
## Figure 1
What follows are three replications of Bartels, Figure 1: the original, one with expanded years, and one with expanded years and midterms.
### Bartels Original
{fig-align="center" width="70%"}
### Bartels Extended
{fig-align="center" width="70%"}
### Bartels Extended, with Midterms
{fig-align="center" width="70%"}
## Figure 2
What follows are three replications of Bartels, Figure 2: the original, one with expanded years, and one with expanded years and midterms.
{fig-align="center" width="70%"}
{fig-align="center" width="70%"}
{fig-align="center" width="70%"}
1. Many things were very tricky.
2. While initially falling, the number of strong identifiers has increased significantly in recent years.
3. Bartels' initial finding is accurate, but the years since illustrate a notably different increased patterns of partisanship.
4. The relationship looks a little less clear, as there is a little more noise, but does not substantively change.The draft
Download the single-file draft here โ the first pass at the problem, worked out top to bottom in one script with quick base-R plots.
The draft is where the thinking happens: it opens by listing the six jobs the assignment breaks into, finds the variables, gets a rough Figure 1 on screen, notices the unweighted lines look wrong, and fixes that with srvyr. Only once that worked was it split into the project above. The draft never reaches 2024 or the midterm years, and adding them to it would have meant more copies of the same plot โ which is exactly the problem the projectโs design solves.
DWV Assign 7 - Bartels (draft).r
#Title: DWV Assign 7 - Bartels (draft).R
#Author: Jack Reilly
#Purpose: Replicate Figures 1 and 2 from Bartels
#Requires: Cumulative ANES; 2024 ANES
#Output: Figures as PDFs (ggplot only)
library(tidyverse)
library(haven)
setwd("~/desktop/working/dwv/bartels2026")
#OK, so let's think big picture here. What are we doing?
#We need to get from two files (one quite large, one still somewhat large) with an individaul UoA
#To a single, small file with "year" as a UoA.
#There'll also be some data recoding/managing, graphing, etc.
#And we need to have graphs with and without midterms
#We also know this is a survey, so we have to keep an eye out for weights
#So we know we have:
#1. Variable and Observation Identification/filtering/selecting (x2)
#2. Variable manipulation (x2)
#3. Dataset summarisation/collapse (x2)
#4. Data merge
#5. Possibly: weight?
#6. Graph
#Load data
anes_full<-read_dta("anes_timeseries_cdf_stata_20220916.dta")
anes<-anes_full
anes_24<-read_dta("anes_timeseries_2024_stata_20250808.dta")
names(anes)<-tolower(names(anes))
names(anes_24)<-tolower(names(anes_24))
#0. Find Variables, note missing
unique(anes$vcf0301) #orgpid
unique(anes$vcf0004) #year
unique(anes$vcf0702) #voted
unique(anes$vcf0703) #randvote
unique(anes$vcf0704) #presvote
unique(anes$vcf0704a)#presvotemajparty
unique(anes$vcf0009z)#survey weight
#optional: rename them intuitively
anes$orgpid <- anes$vcf0301
anes$year <- anes$vcf0004
anes$voted <- anes$vcf0702
anes$randvote <- anes$vcf0703
anes$presvote <- anes$vcf0704
anes$presvotemajparty <- anes$vcf0704a
anes$weight <- anes$vcf0009z
#1. Filter and select
#anes <- anes |> select(vcf0301, vcf0004, vcf0702, vcf0703, vcf0704, vcf0704a, vcf0009z)
anes <- anes |> select(orgpid, year, voted, randvote, presvote, presvotemajparty, weight)
#Later, we'll need to manipulate things around midterm and not midterm years.
#So I'm just going to make a variable to make that easy now
anes <- anes %>%
mutate(presyear = if_else(
year %in% c(1948, 1952, 1956, 1960, 1964, 1968,
1972, 1976, 1980, 1984, 1988, 1992,
1996, 2000, 2004, 2008, 2012, 2016,
2020, 2024),
1, 0
))
#2. Manipulate
#Drop the DKs, etc
anes <- anes %>%
mutate(pid = if_else(orgpid == 0, NA_real_, orgpid))
#Now, this requires a little bit of thought.
#We need proportions, so we need 1 and 0 for strong, weak, leaners, and indeps
#For when we summarize the data later
anes <- anes %>%
mutate(
strong = case_when(
pid %in% c(1, 7) ~ 1,
pid %in% 2:6 ~ 0,
),
weak = case_when(
pid %in% c(2, 6) ~ 1,
pid %in% c(1, 3, 4, 5, 7) ~ 0,
),
leaners = case_when(
pid %in% c(3, 5) ~ 1,
pid %in% c(1, 2, 4, 6, 7) ~ 0,
),
indeps = case_when(
pid == 4 ~ 1,
pid %in% c(1, 2, 3, 5, 6, 7) ~ 0,
)
)
#For figure 2, we need to combine some of them
anes <- anes %>%
mutate(
# Generate a dummy variable for all party identifiers, strong or weak
identifiers = strong + weak,
# Variable equals "identifiers" for voters (voted==2), missing otherwise
identvote = if_else(voted == 2, identifiers, NA),
# Variable equals "identifiers" for nonvoters (voted==1), missing otherwise
identnovote = if_else(voted == 1, identifiers, NA),
# Simple turnout variable: recode voted (1=0 nonvoter, 2=1 voter, else missing)
turnout = case_when(
voted == 1 ~ 0,
voted == 2 ~ 1,
)
)
#3. Collapse the data
figure1_data <- anes %>%
# filter first
filter(presyear == 1, year > 1948) %>%
# then group by year
group_by(year) %>%
# summarize means of selected vars
summarise(
strong = mean(strong, na.rm=T),
weak = mean(weak, na.rm=T),
leaners = mean(leaners, na.rm=T),
indeps = mean(indeps, na.rm=T)
)
#Check the data, to make sure it looks like what it should
#4. Let's Graph!
plot(x=0, type='n',
xlim=c(1952, 2024), xaxt = "n",
ylim=c(0,.6), yaxt = "n",
xlab=" ",
ylab=" ",
main="Bartels 1")
lines(figure1_data$year, figure1_data$strong, type = "o", col = "black", lty=1)
lines(figure1_data$year, figure1_data$weak, type = "o", col = "black", lty=2)
legend("topright", legend = c("Strong", "Weak"),
col = c("black", "black"), lty = c(1,2) , pch = 1)
#OK, so where are we?
#We have the basics!
#But
#1. some things don't quite look quite right yet.
#2. #And we're still missing 2024
#For #1
#Let's try a weight
library(srvyr)
# define a survey design
anes_svy <- anes %>%
as_survey_design(weights = weight)
# now your dplyr pipeline works, but with survey-weighted means
figure1_data <- anes_svy %>%
filter(presyear == 1, year > 1948) %>%
group_by(year) %>%
summarise(
strong = survey_mean(strong, na.rm = TRUE, vartype = NULL),
weak = survey_mean(weak, na.rm = TRUE, vartype = NULL),
leaners = survey_mean(leaners,na.rm = TRUE, vartype = NULL),
indeps = survey_mean(indeps, na.rm = TRUE, vartype = NULL)
)
plot(x=0, type='n',
xlim=c(1952, 2024), xaxt = "n",
ylim=c(0,.6), yaxt = "n",
xlab=" ",
ylab=" ",
main="Bartels 1")
lines(figure1_data$year, figure1_data$strong, type = "o", col = "black", lty=1)
lines(figure1_data$year, figure1_data$weak, type = "o", col = "black", lty=2)
legend("topright", legend = c("Strong", "Weak"),
col = c("black", "black"), lty = c(1,2) , pch = 1)
#Now, we're cooking with gas
#Let's double check figure 1b
plot(x=0, type='n',
xlim=c(1952, 2024), xaxt = "n",
ylim=c(0,.6), yaxt = "n",
xlab=" ",
ylab=" ",
main="Bartels 1")
lines(figure1_data$year, figure1_data$indeps, type = "o", col = "black", lty=1)
lines(figure1_data$year, figure1_data$leaners, type = "o", col = "black", lty=2)
legend("topright", legend = c("Strong", "Weak"),
col = c("black", "black"), lty = c(1,2) , pch = 1)
#Double check figure 2
#This is a different kind of summarise
#I'm doing it by two groups: year and turnout
#So first, let's do the summarisation
figure2_data <- anes_svy %>%
filter(presyear == 1, year > 1948) %>%
group_by(year, turnout) %>%
summarise(
identifiers = survey_mean(identifiers, na.rm = TRUE),
)
#Make a picture
plot(x=0, type='n',
xlim=c(1952, 2024), xaxt = "n",
ylim=c(0,1), yaxt = "n",
xlab=" ",
ylab=" ",
main="Bartels 2")
lines(figure2_data$year[figure2_data$turnout==1], figure2_data$identifiers[figure2_data$turnout==1], type = "o", col = "black", lty=1)
lines(figure2_data$year[figure2_data$turnout==0], figure2_data$identifiers[figure2_data$turnout==0], type = "o", col = "black", lty=2)
nonvoters <- subset(figure2_data, turnout == 0)
voters <- subset(figure2_data, turnout == 1)
plot(x=0, type='n',
xlim=c(1952, 2024), xaxt = "n",
ylim=c(0,1), yaxt = "n",
xlab=" ",
ylab=" ",
main="Bartels 2")
lines(voters$year, voters$identifiers, type = "o", col = "black", lty=1)
lines(nonvoters$year, nonvoters$identifiers, type = "o", col = "black", lty=2)
# Legend
legend("topleft",
legend = c("Nonvoters", "Voters"),
lty = c(2, 1), pch = c(1, 16),
bty = "n")