An Analysis of Global and U.S. democracy

I gave a one hour presentation at the 5th annual international symposium at Colorado State University. The topic of my talk was a global assessment of democracy with a deep dive into the U.S. case. We examined global trends in democratization and democratic backsliding as well as trends in the U.S.
There was a special focus on media, Congress, parties, voters, and states. Below is code that replicates some of the figures I generated with publicly available data.
You can check out the code on Github Gist or examine it below. Let me know if you have any questions or suggestions!
You can learn more about the amazing work of the International Program on their website!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## This R Script has code for some of the visualizations I made for my talk | |
## at the CSU International Symposium on Democracy | |
# Install Packages | |
if(!require('pacman')) { | |
install.packages('pacman') | |
library('pacman') | |
} | |
# loading the p_load function | |
p_load(devtools, tidyverse, scales,haven,sjlabelled, | |
ggrepel, viridis, ggthemes, maps, countrycode, | |
install = TRUE) | |
devtools::install_github("vdeminstitute/vdemdata") | |
install_github("jamesmartherus/anesr") | |
library(vdemdata) | |
library(anesr) | |
# Generate a world map with changes between 2002 and 2022 | |
df_vdem_map <- | |
vdem |> | |
dplyr::select(country_name, country_id, year, | |
v2x_polyarchy, v2x_libdem, v2x_partipdem, v2x_egaldem) |> | |
filter(year %in% c(2002, 2022)) |> | |
group_by(country_name) |> | |
mutate(delta_polyarchy = v2x_polyarchy - lag(v2x_polyarchy)) |> | |
filter(year == 2022) | |
world_map = map_data("world") | |
world_map$country_id <- countrycode(world_map$region, origin = 'country.name', destination = 'vdem') | |
df_democracy <- | |
world_map |> | |
left_join(df_vdem_map) | |
world_map = map_data("world") %>% | |
filter(! long > 180) | |
world_map$country_id <- countrycode(world_map$region, origin = 'country.name', destination = 'vdem') | |
df_democracy <- | |
world_map |> | |
left_join(df_vdem_map) | |
countries = df_democracy %>% | |
distinct(region,delta_polyarchy) | |
countries %>% | |
filter(region != "Antarctica") |> | |
ggplot(aes(fill = delta_polyarchy, map_id = region)) + | |
geom_map(map = world_map) + | |
expand_limits(x = world_map$long, y = world_map$lat) + | |
scale_fill_viridis(option="inferno") + theme_bw() + | |
labs(x = "Longitude", y = "Lattitude", | |
fill='Change in Democracy', | |
title = "Democracy around the world", | |
subtitle = "V-Dem's Polyarchy between 2002 and 2022") + | |
theme(legend.position="bottom") + | |
coord_map("moll") + | |
theme_map() + theme(text=element_text(size=15)) | |
# Delta Plot of democracy scores for a country in 2002 and 2022 | |
vdem |> | |
dplyr::select(country_name, year, v2x_polyarchy) |> | |
group_by(country_name) |> | |
filter(year %in% c(2002, 2022)) |> | |
mutate(delta_polyarchy = v2x_polyarchy - lag(v2x_polyarchy), | |
lag_polyarchy = lag(v2x_polyarchy), | |
pos_dummy = ifelse(delta_polyarchy >=0, "positive", "negative")) |> | |
drop_na() |> | |
filter(year == 2022) |> | |
ggplot(aes(y = v2x_polyarchy, x = lag_polyarchy, color = pos_dummy, label = country_name)) + | |
geom_point(alpha = 1/2) + theme_bw() + | |
geom_abline(intercept = 0, slope = 1, color = "black", linetype = "dashed") + | |
ylim(0,1) + xlim(0,1) + geom_text_repel() + theme(legend.position = "none") + | |
labs(title = "Changes in Polyarchy between 2002 and 2022", | |
caption = "Note: Analysis based on V-Dem Version 14", | |
y = "Polyarchy, 2022", | |
x = "Polyarchy, 2002") + theme(text=element_text(size=15)) | |
# Gobal democracy average since 1789 | |
vdem |> | |
group_by(year) |> | |
summarize(mean_polyarchy = mean(v2x_polyarchy, na.rm = TRUE), n = n()) |> | |
ggplot(aes(y = mean_polyarchy, x = year)) + | |
geom_line() + | |
theme_bw() + | |
labs(title = "Average Democracy in the World", | |
caption = "Note: Analysis based on V-Dem Version 14", | |
x = "Year", | |
y = "V-Dem Polyarchy, global average") + | |
ylim(0,1)+ theme(text=element_text(size=15))+ | |
annotate('rect', xmin=2008, xmax=2024, ymin=0.4, ymax=0.62, alpha=.2, fill='red') | |
## Examining attitudes of American voters | |
data(timeseries_2020) | |
data(timeseries_cum) #Load Time Series Cumulative File (1948-2016) | |
# 2020 is missing from the cumulative file. The code below prepares 2020 for a merge | |
# with the cumulative file | |
df_anes_2020 <- | |
timeseries_2020 |> | |
remove_all_labels() |> | |
dplyr::select(V201156, V201157, V201228, V201229, V201230, V201231x, V200010a) |> | |
mutate(VCF0004 = 2020) |> | |
rename(year = VCF0004)|> # Year of response | |
mutate(pid_3 = ifelse(V201231x %in% c(1,2, 3), "Democrat", | |
ifelse(V201231x %in% c(5,6,7), "Republican", | |
ifelse(V201231x == 4, "Independent", NA)))) |> | |
rename(pid_7 = V201231x)|> #7 scale Party ID val: 1-7. Strong Democrat 2. Weak Democrat3. Independent - Democrat4. Independent - Independent5. Independent - Republican6. Weak Republican7. Strong Republican | |
#rename(pid_3 = V201228)|> # | |
rename(weight = V200010a) |> | |
rename(therm_dem = V201156)|> # val 00-96 cold-warm as coded; 97: 97-100, 98: DK, 99. NA | |
rename(therm_rep = V201157) |> # val 00-96 cold-warm as coded; 97: 97-100, 98: DK, 99. NA | |
mutate(therm_dem = ifelse(therm_dem <0 | therm_dem > 100 , NA, therm_dem), | |
therm_dem = ifelse(therm_dem %in% c(97,98,99,100), 97, therm_dem), | |
therm_rep =ifelse(therm_rep <0 | therm_rep > 100, NA, therm_rep), | |
therm_rep = ifelse(therm_rep %in% c(97,98,99,100), 97, therm_rep), | |
strong_partisan = if_else(pid_7 == 1|pid_7 ==7, 1, 0), | |
pid_7_num = as.numeric(pid_7), | |
pid_7 = recode(pid_7, | |
"1" = "Strong Democrat", | |
"2" = "Weak Democrat", | |
"3" = "Independent - Democrat", | |
"4" = "Independent - Independent", | |
"5" = "Independent - Republican", | |
"6" = "Weak Republican", | |
"7" = "Strong Republican"), | |
pid_7 = reorder(pid_7, pid_7_num), | |
pid_3_num = as.numeric(pid_3), | |
pid_3 = recode(pid_3, | |
"1" = "Democrat", | |
"3" = "Independent", | |
"2" = "Republican"), | |
pid_3 = reorder(pid_3, pid_3_num), | |
pid_3_sort = factor(recode(pid_7_num, #different from regular pid_3 which codes 3, 5 as partisan | |
"1" = "Democrat", | |
"2" = "Democrat", | |
"3" = "Independent", | |
"4" = "Independent", | |
"5" = "Independent", | |
"6" = "Republican", | |
"7" = "Republican"), | |
levels = c("Democrat", | |
"Independent", | |
"Republican")), | |
pid_2_sort = na_if(pid_3_sort, "Independent"), #better just to filter(pid_3_sort != "Independent"), but used to build other vars | |
pid_2 = na_if(pid_3, "Independent"), | |
therm_inparty = if_else(pid_3=="Democrat", therm_dem, therm_rep), | |
therm_inparty = if_else(pid_3=="Democrat" | pid_3 == "Republican", | |
if_else(pid_3=="Democrat", therm_dem, therm_rep), | |
(therm_dem + therm_rep)/2), #therm in/out are equal | |
therm_inparty = na_if(therm_inparty, -9), | |
therm_outparty = if_else(pid_3=="Democrat" | pid_3 == "Republican", | |
if_else(pid_3=="Democrat", therm_rep, therm_dem), | |
(therm_dem + therm_rep)/2), | |
npa_party = therm_inparty - therm_outparty, | |
therm_parties_mean = (therm_dem + therm_rep)/2) | |
# The cumulative file | |
df_anes <- | |
timeseries_cum |> | |
remove_all_labels() |> | |
dplyr::select(VCF0004, VCF0301, VCF0303, VCF0305, VCF0311, VCF0312, VCF0803, | |
VCF0218, VCF0224, VCF0201, VCF0202,VCF0009z) |> | |
rename(year = VCF0004)|> # Year of response | |
rename(pid_7 = VCF0301)|> #7 scale Party ID val: 1-7. Strong Democrat 2. Weak Democrat3. Independent - Democrat4. Independent - Independent5. Independent - Republican6. Weak Republican7. Strong Republican | |
rename(pid_3 = VCF0303)|> # Party ID 3 categories val: "Republican", "Independent", "Democrat" (Dem/Rep include Leaners) | |
rename(pid_str = VCF0305)|> # PID strength val: 1. Independent 2. Leaning Independent 3. Weak Partisan 4. Strong Partisan Kept this because I wanted to create basically this variable later | |
rename(win_care_pres = VCF0311)|> # How much do you care which party wins presidency? val: 1. Don't care very much or DK, pro-con, depends, and other, 2. Care a great deal | |
rename(win_care_cong = VCF0312)|> # How much do you care which party wins congress? val: 1. Don't care very much or DK, pro-con, depends, and other, 2. Care a great deal notes: only asked through 2008 | |
rename(respondent_ideo = VCF0803)|> # Liberal-conservative scale val: 1(extremely liberal)- 7(extremely conservative) 9. DK; haven't much thought about it | |
rename(therm_dem = VCF0218)|> # val 00-96 cold-warm as coded; 97: 97-100, 98: DK, 99. NA | |
rename(therm_rep = VCF0224) |> # val 00-96 cold-warm as coded; 97: 97-100, 98: DK, 99. NA | |
rename(therm_dem_old = VCF0201) |> | |
rename(therm_rep_old = VCF0202) |> | |
rename(weight = VCF0009z) |> | |
mutate(therm_dem = na_if(therm_dem, 98), | |
therm_dem = na_if(therm_dem, 99), | |
therm_rep = na_if(therm_rep, 98), | |
therm_rep = na_if(therm_rep, 99), | |
respondent_ideo = na_if(respondent_ideo, 9), #the recode() function is used in the next 4 pipes to apply new values to observation in the columns. ANES uses numerical values to represent factors. | |
strong_partisan = if_else(pid_7 == 1|pid_7 ==7, 1, 0), | |
pid_7_num = as.numeric(pid_7), | |
pid_7 = recode(pid_7, | |
"1" = "Strong Democrat", | |
"2" = "Weak Democrat", | |
"3" = "Independent - Democrat", | |
"4" = "Independent - Independent", | |
"5" = "Independent - Republican", | |
"6" = "Weak Republican", | |
"7" = "Strong Republican"), | |
pid_7 = reorder(pid_7, pid_7_num), | |
pid_3_num = as.numeric(pid_3), | |
pid_3 = recode(pid_3, | |
"1" = "Democrat", | |
"2" = "Independent", | |
"3" = "Republican"), | |
pid_3 = reorder(pid_3, pid_3_num), | |
pid_str_num = as.numeric(pid_str), | |
pid_str = recode(pid_str, | |
"1" = "Independent", | |
"2" = "Leaning Independent", | |
"3" = "Weak Partisan", | |
"4" = "Strong Partisan"), | |
pid_str = reorder(pid_str, pid_str_num), | |
respondent_ideo_num = as.numeric(respondent_ideo), | |
respondent_ideo = recode(respondent_ideo, | |
"1" = "Extremely Liberal", | |
"2" = "Liberal", | |
"3" = "Somewhat Liberal", | |
"4" = "Moderate", | |
"5" = "Somewhat Conservative", | |
"6" = "Conservative", | |
"7" = "Extremely Conservative"), | |
respondent_ideo = reorder(respondent_ideo, respondent_ideo_num), | |
pid_3_sort = factor(recode(pid_7_num, #different from regular pid_3 which codes 3, 5 as partisan | |
"1" = "Democrat", | |
"2" = "Democrat", | |
"3" = "Independent", | |
"4" = "Independent", | |
"5" = "Independent", | |
"6" = "Republican", | |
"7" = "Republican"), | |
levels = c("Democrat", | |
"Independent", | |
"Republican")), | |
pid_2_sort = na_if(pid_3_sort, "Independent"), #better just to filter(pid_3_sort != "Independent"), but used to build other vars | |
pid_2 = na_if(pid_3, "Independent"), | |
therm_inparty = if_else(pid_3=="Democrat", therm_dem, therm_rep), | |
therm_inparty = if_else(pid_3=="Democrat" | pid_3 == "Republican", | |
if_else(pid_3=="Democrat", therm_dem, therm_rep), | |
(therm_dem + therm_rep)/2), #therm in/out are equal | |
therm_inparty = na_if(therm_inparty, -9), | |
therm_outparty = if_else(pid_3=="Democrat" | pid_3 == "Republican", | |
if_else(pid_3=="Democrat", therm_rep, therm_dem), | |
(therm_dem + therm_rep)/2), | |
npa_party = therm_inparty - therm_outparty, | |
therm_parties_mean = (therm_dem + therm_rep)/2, | |
therm_dem_old = na_if(therm_dem_old, 98), | |
therm_dem_old = na_if(therm_dem_old, 99), | |
therm_rep_old = na_if(therm_rep_old, 98), | |
therm_rep_old = na_if(therm_rep_old, 99), | |
therm_party_ingroup = if_else(pid_2_sort=="Democrat", therm_dem_old, therm_rep_old), | |
therm_party_outgroup = if_else(pid_2_sort=="Democrat", therm_rep_old, therm_dem_old), | |
npa_partisans = therm_party_ingroup - therm_party_outgroup) |> | |
bind_rows(df_anes_2020) | |
# Prepare a data frame for visualizations | |
fig_1_df <- df_anes %>% | |
mutate(independent_count = str_count(pid_7, "Independent"), # Code here moves leaners into partisan coding | |
pid_3_lean = ifelse(str_detect(pid_7, "Republican"), "Republican", | |
ifelse(str_detect(pid_7, "Democrat"), "Democrat", | |
ifelse(independent_count == 2, "Independent", NA)))) |> | |
dplyr::select(-pid_3) |> | |
rename(pid_3 = pid_3_lean) |> | |
filter(pid_3 != "Independent") %>% | |
#filter(pid_3 != "Independent")%>% | |
# filter(year <= 2008)%>% #un/comment this line for the original data. | |
filter(year >= 1978)%>% | |
select(year, | |
weight, | |
pid_3, | |
therm_inparty, | |
therm_outparty) %>% | |
pivot_longer(therm_inparty:therm_outparty, names_to = "ft_towards", values_to = "ft")%>% | |
unite("group", pid_3:ft_towards)%>% | |
mutate(group = recode(group, | |
"Democrat_therm_inparty" = "Democrat - In Party", | |
"Democrat_therm_outparty" = "Democrat - Out Party", | |
"Republican_therm_inparty" = "Republican - In Party", | |
"Republican_therm_outparty" = "Republican - Out Party"))%>% | |
group_by(year, group)%>% | |
summarize(mean = weighted.mean(ft, weight, na.rm = TRUE), | |
#mean = mean(ft, na.rm = TRUE), | |
sd = sd(ft, na.rm = TRUE), | |
n = n()) |> | |
separate(group, into = c("party", "group_type"), sep = " - ", remove = FALSE) | |
# In and out party affect over time | |
fig_1_df |> | |
filter(year >= 1978) |> | |
#filter(str_detect(group_type, "In")) |> | |
mutate(mean = ifelse(str_detect(group_type, "Out"), NA, mean)) |> | |
ggplot(aes(x = year, y=mean)) + | |
geom_point(aes(shape = group_type, color = party)) + | |
geom_smooth(aes(linetype = group_type, color = party), se=F)+ | |
scale_color_manual(values = c("blue", "red"))+ theme_bw() + | |
scale_linetype_manual(values = c("In Party" = "solid", | |
"Out Party" = "dashed", | |
"In Party" = "solid", | |
"Out Party" = "dashed")) + | |
scale_shape_manual(values = c("In Party" = 3, | |
"Out Party" = 2)) + | |
scale_x_continuous(breaks = seq(1976, 2020, by = 4)) + | |
scale_y_continuous(limits = c(10,80)) + | |
theme(legend.position = "none") + | |
labs(title = "Partisan In and Outgroup Feeling Thermometer", | |
caption = "Solid lines: In group, Dashed lines: Out group, Leaners coded as partisans", | |
y = "Mean Thermometer Ratings of Partisans", | |
x = "Year")+ theme(text=element_text(size=15)) | |
# The difference between in and out party affect | |
df_anes |> | |
dplyr::select(year, pid_3, therm_inparty, therm_outparty, weight) |> | |
drop_na(pid_3) |> | |
filter(pid_3 != "Independent")%>% | |
mutate(delta_therm = therm_inparty- therm_outparty) |> | |
group_by(year) |> | |
summarize(mean = weighted.mean(delta_therm, weight, na.rm = TRUE), | |
sd = sd(delta_therm, na.rm = TRUE), | |
n = n()) |> | |
filter(year >= 1978) |> | |
ggplot(aes(x = year, y = mean)) + | |
geom_point() + | |
geom_smooth(color = "gray50", se=T) + | |
scale_x_continuous(breaks = seq(1976, 2020, by = 4)) + | |
theme_bw() + | |
theme(legend.position = "none") + | |
labs(title = "Affective Polarization", | |
caption = "Note: Analysis based on American National Election Study data.", | |
y = "Average Difference between In and Outgroup Affect", | |
x = "Year")+ theme(text=element_text(size=15)) | |
# The difference between in and out party affect by partisans | |
df_anes |> | |
dplyr::select(year, pid_3, therm_inparty, therm_outparty, weight) |> | |
drop_na(pid_3) |> | |
filter(pid_3 != "Independent")%>% | |
mutate(delta_therm = therm_inparty- therm_outparty) |> | |
group_by(year, pid_3) |> | |
summarize(mean = weighted.mean(delta_therm, weight, na.rm = TRUE), | |
sd = sd(delta_therm, na.rm = TRUE), | |
n = n()) |> | |
filter(year >= 1978) |> | |
ggplot(aes(x = year, y = mean, group = pid_3, color = pid_3)) + | |
geom_point() + | |
geom_smooth(aes(color = pid_3), se=F) + | |
scale_x_continuous(breaks = seq(1976, 2020, by = 4)) + | |
scale_color_manual(values = c("red", "blue"))+ theme_bw() + | |
theme(legend.position = "none") + | |
labs(title = "Affective Polarization", | |
caption = "Note: Analysis based on American National Election Study data.", | |
y = "Average Difference between In and Outgroup Affect", | |
x = "Year")+ theme(text=element_text(size=15)) | |
# US Party Analysis | |
# Loading in the V-Dem VParty data set | |
df_vparty <- vparty |> | |
mutate(v2paopresp = scales::rescale(v2paopresp), | |
v2xpa_antiplural = scales::rescale(v2xpa_antiplural), | |
v2xpa_popul = scales::rescale(v2xpa_popul), | |
v2paviol = scales::rescale(v2paviol)) |> | |
filter(country_name == "United States of America") |> | |
dplyr::select(country_name, year,v2paenname, v2paopresp,v2xpa_antiplural, | |
v2xpa_popul, v2paviol) | |
## Plotting rejection of political violence by U.S. political parties over time | |
df_vparty |> | |
drop_na(v2paviol) |> | |
ggplot(aes(x = year, y = v2paviol, color = v2paenname)) + | |
geom_line() + | |
scale_color_manual(values = c("blue", "red")) + | |
theme_bw() + | |
labs(title = "Rejection of political violence by U.S. political parties", | |
x = "Year", | |
y = "Rejection of political violence", | |
subtitle = "Analysis based on V-Party Version 2", | |
caption = "Question: To what extent does the leadership of this party explicitly discourage the use of violence | |
against domestic political opponents?", | |
color = "Party:") + ylim(0,1)+ | |
theme(legend.position = "bottom") + theme(text=element_text(size=15)) | |
## Plotting attacks against political opponents by U.S. political parties over time | |
df_vparty |> | |
drop_na(v2paopresp) |> | |
ggplot(aes(x = year, y = v2paopresp, color = v2paenname)) + | |
geom_line() + | |
scale_color_manual(values = c("blue", "red")) + | |
theme_bw() + | |
labs(title = "Attacks against political opponents by U.S. political parties", | |
subtitle = "Analysis based on V-Party Version 2", | |
x = "Year", | |
y = "Political opponents", | |
color = "Party:", | |
caption = "Question: Prior to this election, have leaders of this party used severe personal attacks or tactics of | |
demonization against their opponents?") +ylim(0,1)+ | |
theme(legend.position = "bottom")+ theme(text=element_text(size=15)) | |
## Plotting populism by U.S. political parties over time | |
df_vparty |> | |
drop_na(v2xpa_popul) |> | |
ggplot(aes(x = year, y = v2xpa_popul, color = v2paenname)) + | |
geom_line() + | |
scale_color_manual(values = c("blue", "red")) + | |
theme_bw() + | |
labs(title = "Populism in U.S. political parties", | |
subtitle = "Analysis based on V-Party Version 2", | |
x = "Year", | |
y = "Populism Index", | |
color = "Party:", | |
caption = "Question: To what extent do representatives of the party use populist rhetoric?") + | |
theme(legend.position = "bottom") + ylim(0,1)+theme(text=element_text(size=15)) | |
## Plotting anti-pluralism by U.S. political parties over time | |
df_vparty |> | |
drop_na(v2xpa_antiplural) |> | |
ggplot(aes(x = year, y = v2xpa_antiplural, color = v2paenname)) + | |
geom_line() + | |
scale_color_manual(values = c("blue", "red")) + | |
theme_bw() + | |
labs(title = "Anti-pluralism in U.S. political parties", | |
subtitle = "Analysis based on V-Party Version 2", | |
x = "Year", | |
y = "Anti-Pluralism Index", | |
color = "Party:", | |
caption = "Question = To what extent does the party show a lacking commitment to democratic norms prior to | |
elections?") +ylim(0,1)+ | |
theme(legend.position = "bottom") + theme(text=element_text(size=15)) |