Data exploration

The conjoint formula is derived based on the author’s code. It was not initially intuitive that these are the necessary factors based on the title of the figure, which affected the initial results. However, after the conjoint formula was fixed, the figures derived were exactly the same.

# Look at data
table(data$respondent_type)
## 
##    Citizen Politician 
##      53760      26298
table(data$country)
## 
##       Belgium         Chile       Denmark United States 
##         21360         17150         23820         17728
table(data$woman_pol)
## 
##   Man politician Woman politician 
##            40095            39913
table(data$woman_user)
## 
## Woman user   Man user 
##      39937      40071
table(data$party_pol_copartisan)
## 
## Non co-partisan     Co-partisan 
##           68221           11837
table(data$gendered)
## 
## Non-gendered text     Gendered text 
##             40112             39896
table(data$chose_profile)
## 
##     0     1 
## 36321 36321
table(data$text_pol)
## 
## PolText01 PolText02 PolText03 PolText04 PolText05 PolText06 PolText07 PolText08 
##      4043      3990      3905      4127      3990      4022      3925      3990 
## PolText09 PolText10 PolText11 PolText12 PolText13 PolText14 PolText15 PolText16 
##      4082      4002      3992      3994      3995      3947      4036      4080 
## PolText17 PolText18 PolText19 PolText20 
##      3917      4008      3952      4011
table(data$text_user)
## 
## UserText01 UserText02 UserText03 UserText04 UserText05 UserText06 UserText07 
##       5036       5121       5027       4925       4949       4986       4909 
## UserText08 UserText09 UserText10 UserText11 UserText12 UserText13 UserText14 
##       4927       4997       5015       5070       5087       4970       4930 
## UserText15 UserText16 
##       5116       4943
# Factor
data$respondent_type <- factor(data$respondent_type, levels = c("Citizen", "Politician"))

# subset data -> politician vs citizen
pol_data <- subset(data,respondent_type == "Politician")
cit_data <- subset(data,respondent_type == "Citizen")

vig_pol <- subset(vig, respondent_type == "Politician")
vig_cit <- subset(vig, respondent_type == "Citizen")

# conjoint formula
cj_formula <- chose_profile ~ text_pol +
                             text_user +
                             party_pol_copartisan +
                             woman_user +
                             woman_pol +
                             gendered

Estimate the ACME

All the codes are done with the package “cregg”.

Figure 3: Effects of Politician’s Gender, User’s Gender, Co-partisanship and Gendered Text on Perceptions of Toxicity

# 1. AMCE estimates (per country + pooled)

# Country subsets
pol_us <- subset(pol_data, country == "United States")
pol_dk <- subset(pol_data, country == "Denmark")
pol_cl <- subset(pol_data, country == "Chile")
pol_be <- subset(pol_data, country == "Belgium")

# AMCE by country
amce_us <- amce(cj_formula, data = pol_us, id = ~ id)
amce_dk <- amce(cj_formula, data = pol_dk, id = ~ id)
amce_cl <- amce(cj_formula, data = pol_cl, id = ~ id)
amce_be <- amce(cj_formula, data = pol_be, id = ~ id)

# combine into df
df_us <- as.data.frame(amce_us); df_us$country <- "United States"
df_dk <- as.data.frame(amce_dk); df_dk$country <- "Denmark"
df_cl <- as.data.frame(amce_cl); df_cl$country <- "Chile"
df_be <- as.data.frame(amce_be); df_be$country <- "Belgium"

plot_df <- rbind(df_us, df_dk, df_cl, df_be)

# Pooled AMCE (politician sample)
amce_pooled <- amce(cj_formula, data = pol_data, id = ~ id)
pooled_df <- as.data.frame(amce_pooled)
pooled_df$country <- "Pooled"

# Use percentage points to match the figure
plot_df$estimate <- plot_df$estimate * 100
plot_df$lower <- plot_df$lower * 100
plot_df$upper <- plot_df$upper * 100

pooled_df$estimate <- pooled_df$estimate * 100
pooled_df$lower <- pooled_df$lower * 100
pooled_df$upper <- pooled_df$upper * 100

A helper function is used to draw the panels since this step is repeated throughout the figure which is the preferred method over manually code all the variables.

# 2. Helper function

plot_amce_panel <- function(feature_name, y_levels, y_label, main_title) {
  panel <- subset(plot_df, feature == feature_name)
  pooled <- subset(pooled_df, feature == feature_name)

  all_dat <- rbind(panel, pooled)

  # order levels
  all_dat$level <- factor(all_dat$level, levels = y_levels)

  # split into country treatment, pooled treatment, pooled baseline
  by_country <- subset(all_dat, country != "Pooled" & !is.na(std.error))
  pooled_treat <- subset(all_dat, country == "Pooled" & !is.na(std.error))
  pooled_base <- subset(all_dat, country == "Pooled" &  is.na(std.error))

  ggplot() +
    geom_vline(xintercept = 0) +

    # country-specific treatment points + CIs
    geom_point(data = by_country,
               aes(x = estimate, y = level, colour = country),
               position = position_dodge(width = 0.6), size = 2.5) +
    geom_errorbarh(data = by_country,
                   aes(xmin = lower, xmax = upper, y = level, colour = country),
                   position = position_dodge(width = 0.6), height = 0) +

    # pooled treatment (diamond)
    geom_point(
      data = pooled_treat,
      aes(x = estimate, y = level),
      shape = 23, fill = "white", colour = "black",
      size = 4, stroke = 1.2
    ) +
    geom_errorbarh(
      data = pooled_treat,
      aes(xmin = lower, xmax = upper, y = level),
      height = 0, colour = "black"
    ) +

    # pooled baseline (dot)
    geom_point(
      data = pooled_base,
      aes(x = estimate, y = level),
      shape = 16, colour = "black", size = 2.5
    ) +

    scale_x_continuous(breaks = seq(-4, 12, by = 2)) +
    coord_cartesian(xlim = c(-4, 12)) +
    scale_y_discrete(limits = y_levels, drop = FALSE) +
    labs(
      x = "AMCE (percentage points)",
      y = y_label,
      title    = main_title,
      subtitle = "Politician sample only, by country"
    ) +
    theme_minimal(base_size = 13)
}

The plots derived are exactly the same as the plots in Fig 3.

# 3. Plots

# 1) Gender of politician
plot_gender_pol <- plot_amce_panel(
  feature_name = "woman_pol",
  y_levels = c("Man politician", "Woman politician"),
  y_label = "Gender of politician",
  main_title = "Effect of Politician's Gender on Toxicity Perceptions"
)

# 2) Gendered text
plot_gendered_text <- plot_amce_panel(
  feature_name = "gendered",
  y_levels = c("Non-gendered text", "Gendered text"),
  y_label = "Gendered text",
  main_title = "Effect of Gendered Text on Toxicity Perceptions"
)

# 3) Gender of user
plot_gender_user <- plot_amce_panel(
  feature_name = "woman_user",
  y_levels = c("Woman user", "Man user"),
  y_label = "Gender of user",
  main_title = "Effect of User's Gender on Toxicity Perceptions"
)

# 4) Co-partisan politician
plot_copartisan <- plot_amce_panel(
  feature_name = "party_pol_copartisan",
  y_levels = c("Non co-partisan", "Co-partisan"),
  y_label = "Co-partisan politician",
  main_title = "Effect of Co-partisanship on Toxicity Perceptions"
)

plot_gender_pol

plot_gendered_text

plot_gender_user

plot_copartisan

# 1. AMCE estimates

# country subsets (citizen)
cit_us <- subset(cit_data, country == "United States")
cit_dk <- subset(cit_data, country == "Denmark")
cit_cl <- subset(cit_data, country == "Chile")
cit_be <- subset(cit_data, country == "Belgium")

# AMCE by country
amce_us_cit <- amce(cj_formula, data = cit_us, id = ~ id)
amce_dk_cit <- amce(cj_formula, data = cit_dk, id = ~ id)
amce_cl_cit <- amce(cj_formula, data = cit_cl, id = ~ id)
amce_be_cit <- amce(cj_formula, data = cit_be, id = ~ id)

# combine into df
df_us_cit <- as.data.frame(amce_us_cit); df_us_cit$country <- "United States"
df_dk_cit <- as.data.frame(amce_dk_cit); df_dk_cit$country <- "Denmark"
df_cl_cit <- as.data.frame(amce_cl_cit); df_cl_cit$country <- "Chile"
df_be_cit <- as.data.frame(amce_be_cit); df_be_cit$country <- "Belgium"

plot_df_cit <- rbind(df_us_cit, df_dk_cit, df_cl_cit, df_be_cit)

# pooled AMCE (citizen)
amce_pooled_cit <- amce(cj_formula, data = cit_data, id = ~ id)
pooled_df_cit <- as.data.frame(amce_pooled_cit)
pooled_df_cit$country <- "Pooled"

# percentage points
plot_df_cit$estimate <- plot_df_cit$estimate * 100
plot_df_cit$lower <- plot_df_cit$lower    * 100
plot_df_cit$upper <- plot_df_cit$upper    * 100

pooled_df_cit$estimate <- pooled_df_cit$estimate * 100
pooled_df_cit$lower <- pooled_df_cit$lower    * 100
pooled_df_cit$upper <- pooled_df_cit$upper    * 100
# 2. Helper 
plot_amce_panel_cit <- function(feature_name, y_levels, y_label, main_title) {
  panel <- subset(plot_df_cit, feature == feature_name)

  pooled <- subset(pooled_df_cit, feature == feature_name)
  all_dat <- rbind(panel, pooled)

  # order levels (baseline then treatment)
  all_dat$level <- factor(all_dat$level, levels = y_levels)

  # split into country treatment, pooled treatment, pooled baseline
  by_country <- subset(all_dat, country != "Pooled" & !is.na(std.error))
  pooled_treat <- subset(all_dat, country == "Pooled" & !is.na(std.error))
  pooled_base <- subset(all_dat, country == "Pooled" &  is.na(std.error))

  ggplot() +
    geom_vline(xintercept = 0) +

    # country-specific treatment points & CIs
    geom_point(data = by_country,
               aes(x = estimate, y = level, colour = country),
               position = position_dodge(width = 0.6), size = 2.5) +
    geom_errorbarh(data = by_country,
                   aes(xmin = lower, xmax = upper, y = level, colour = country),
                   position = position_dodge(width = 0.6), height = 0) +

    # pooled treatment (diamond)
    geom_point(
      data = pooled_treat,
      aes(x = estimate, y = level),
      shape = 23, fill = "white", colour = "black",
      size = 4, stroke = 1.2
    ) +
    geom_errorbarh(
      data = pooled_treat,
      aes(xmin = lower, xmax = upper, y = level),
      height = 0, colour = "black"
    ) +

    # pooled baseline (dot)
    geom_point(
      data = pooled_base,
      aes(x = estimate, y = level),
      shape = 16, colour = "black", size = 2.5
    ) +

    scale_x_continuous(breaks = seq(-4, 12, by = 2)) +
    coord_cartesian(xlim = c(-4, 12)) +
    scale_y_discrete(limits = y_levels, drop = FALSE) +
    labs(
      x = "AMCE (percentage points)",
      y = y_label,
      title = main_title,
      subtitle = "Citizen sample only, by country"
    ) +
    theme_minimal(base_size = 13)
}
# 3. Plots

# 1) gender of politician
plot_gender_pol_cit <- plot_amce_panel_cit(
  feature_name = "woman_pol",
  y_levels = c("Man politician", "Woman politician"),
  y_label = "Gender of politician",
  main_title = "Effect of Politician's Gender on Toxicity Perceptions"
)

# 2) Ggndered text
plot_gendered_text_cit <- plot_amce_panel_cit(
  feature_name = "gendered",
  y_levels = c("Non-gendered text", "Gendered text"),
  y_label = "Gendered text",
  main_title = "Effect of Gendered Text on Toxicity Perceptions"
)

# 3) user gender 
plot_gender_user_cit <- plot_amce_panel_cit(
  feature_name = "woman_user",
  y_levels = c("Woman user", "Man user"),
  y_label = "Gender of user",
  main_title = "Effect of User's Gender on Toxicity Perceptions"
)

# 4) co-partisan politician
plot_copartisan_cit <- plot_amce_panel_cit(
  feature_name = "party_pol_copartisan",
  y_levels = c("Non co-partisan", "Co-partisan"),
  y_label = "Co-partisan politician",
  main_title = "Effect of Co-partisanship on Toxicity Perceptions"
)

plot_gender_pol_cit

plot_gendered_text_cit

plot_gender_user_cit

plot_copartisan_cit

Figure 4: Effects of Politician’s Gender Conditional on the User’s Gender and whether the text is Gendered Text

# 4A: politician gender × gendered text
# Base terms + dummy for woman politician × gendered text
cj_pol_gentext <- chose_profile ~
  text_pol +
  text_user +
  party_pol_copartisan +
  woman_user +
  woman_pol +
  gendered +
  woman_pol_gendered

# 4B: politician gender × user gender
# Base terms + dummy for woman politician × man user
cj_pol_usergender <- chose_profile ~
  text_pol +
  text_user +
  party_pol_copartisan +
  woman_user +
  woman_pol +
  gendered +
  woman_pol_man_user

amce_pol_4A <- amce(cj_pol_gentext, data = pol_data, id = ~ id)
amce_cit_4A <- amce(cj_pol_gentext, data = cit_data, id = ~ id)

amce_pol_4B <- amce(cj_pol_usergender, data = pol_data, id = ~ id)
amce_cit_4B <- amce(cj_pol_usergender, data = cit_data, id = ~ id)

df_pol_4A <- as.data.frame(amce_pol_4A); df_pol_4A$sample <- "Politician sample"
df_cit_4A <- as.data.frame(amce_cit_4A); df_cit_4A$sample <- "Citizen sample"
fig4A <- rbind(df_pol_4A, df_cit_4A)
fig4A$sample <- factor(fig4A$sample, levels = c("Politician sample", "Citizen sample"))

df_pol_4B <- as.data.frame(amce_pol_4B); df_pol_4B$sample <- "Politician sample"
df_cit_4B <- as.data.frame(amce_cit_4B); df_cit_4B$sample <- "Citizen sample"
fig4B <- rbind(df_pol_4B, df_cit_4B)
fig4B$sample <- factor(fig4B$sample, levels = c("Politician sample", "Citizen sample"))

# percentage points
fig4A$estimate <- fig4A$estimate * 100
fig4A$lower <- fig4A$lower * 100
fig4A$upper <- fig4A$upper * 100

fig4B$estimate <- fig4B$estimate * 100
fig4B$lower <- fig4B$lower * 100
fig4B$upper <- fig4B$upper * 100
# Interaction row: 2 points (citizen, politician), no baseline
plot_fig4_interaction <- function(df, feature_name, y_label, main_title) {
  panel <- subset(df, feature == feature_name & !is.na(std.error))
  panel$level <- factor(y_label, levels = y_label)

  ggplot(panel,
         aes(x = estimate, y = level, colour = sample)) +
    geom_vline(xintercept = 0) +
    geom_point(size = 2.5, position = position_dodge(width = 0.4)) +
    geom_errorbarh(aes(xmin = lower, xmax = upper),
                   height = 0,
                   position = position_dodge(width = 0.4)) +
    scale_x_continuous(breaks = seq(-4, 12, by = 2)) +
    coord_cartesian(xlim = c(-4, 12)) +
    labs(
      x = "AMCE (percentage points)",
      y = "",
      title = main_title
    ) +
    theme_minimal(base_size = 13) +
    theme(legend.position = "top")
}

plot_fig4_main <- function(df, feature_name, y_levels, y_label, main_title) {

  panel <- subset(df, feature == feature_name)
  panel$level <- factor(panel$level, levels = y_levels)

  baseline <- subset(panel, is.na(std.error))
  if (nrow(baseline) > 0) baseline <- baseline[1, , drop = FALSE]

  by_sample <- subset(panel, !is.na(std.error))

  ggplot() +
    geom_vline(xintercept = 0) +
    geom_point(data = by_sample,
               aes(x = estimate, y = level, colour = sample),
               size = 2.5, position = position_dodge(width = 0.4)) +
    geom_errorbarh(data = by_sample,
                   aes(xmin = lower, xmax = upper, y = level, colour = sample),
                   height = 0, position = position_dodge(width = 0.4)) +
    geom_point(data = baseline,
               aes(x = estimate, y = level),
               shape = 16, colour = "black", size = 2.5) +
    scale_x_continuous(breaks = seq(-4, 12, by = 2)) +
    coord_cartesian(xlim = c(-4, 12)) +
    scale_y_discrete(limits = y_levels, drop = FALSE) +
    labs(
      x = "AMCE (percentage points)",
      y = y_label,
      title = main_title
    ) +
    theme_minimal(base_size = 13) +
    theme(legend.position = "top")
}
## 5. A plots (politician gender × gendered text)

fig4a_interaction <- plot_fig4_interaction(
  df = fig4A,
  feature_name = "woman_pol_gendered",
  y_label = "Woman politician × gendered text",
  main_title = "Interaction: Politician gender × gendered text"
)

fig4a_polgender <- plot_fig4_main(
  df = fig4A,
  feature_name = "woman_pol",
  y_levels = c("Man politician", "Woman politician"),
  y_label = "Gender of politician",
  main_title = "Gender of politician"
)

fig4a_gendered <- plot_fig4_main(
  df = fig4A,
  feature_name = "gendered",
  y_levels = c("Non-gendered text", "Gendered text"),
  y_label = "Gendered text",
  main_title = "Gendered text"
)

## 6. B plots (politician gender × user gender)

fig4b_interaction <- plot_fig4_interaction(
  df = fig4B,
  feature_name = "woman_pol_man_user",
  y_label = "Woman politician × man user",
  main_title = "Interaction: Politician gender × user gender"
)

fig4b_polgender <- plot_fig4_main(
  df = fig4B,
  feature_name = "woman_pol",
  y_levels = c("Man politician", "Woman politician"),
  y_label  = "Gender of politician",
  main_title = "Gender of politician"
)

fig4b_usergender <- plot_fig4_main(
  df = fig4B,
  feature_name = "woman_user",
  y_levels = c("Woman user", "Man user"),
  y_label = "Gender of user",
  main_title = "Gender of user"
)

fig4a_interaction

fig4a_polgender

fig4a_gendered

fig4b_interaction

fig4b_polgender

fig4b_usergender

Figure 5: Effects of Politician’s Gender on Perceptions of Toxicity by Respondent Subgroup

# 1.AMCEs for each subgroup and keep woman-politician effect
# Helper: only  woman-politician row
extract_woman_pol <- function(amce_obj) {
  df <- as.data.frame(amce_obj)
  df <- subset(df, feature == "woman_pol" & level == "Woman politician")
  df[, c("estimate", "lower", "upper")]
}

fig5_rows <- list()
cj_formula_copartisan <- chose_profile ~ text_pol +
                                       text_user +
                                       woman_user +
                                       woman_pol +
                                       gendered
# Additional formula for co-partisan split (no party_pol_copartisan)
# Politician
# respondent gender
amce_pol_gender_w <- amce(cj_formula,
                          data = subset(pol_data, resp_gender == "Female"),
                          id   = ~ id)
temp <- extract_woman_pol(amce_pol_gender_w)
temp$group <- "Respondent gender"
temp$subgroup <- "Woman"
temp$sample <- "Politician sample"
fig5_rows[[length(fig5_rows) + 1]] <- temp

amce_pol_gender_m <- amce(cj_formula,
                          data = subset(pol_data, resp_gender == "Male"),
                          id   = ~ id)
temp <- extract_woman_pol(amce_pol_gender_m)
temp$group <- "Respondent gender"
temp$subgroup <- "Man"
temp$sample <- "Politician sample"
fig5_rows[[length(fig5_rows) + 1]] <- temp

# respondent ideology 
amce_pol_ideo_r <- amce(cj_formula,
                        data = subset(pol_data, resp_ideology_discrete == "Right-wing"),
                        id  = ~ id)
temp <- extract_woman_pol(amce_pol_ideo_r)
temp$group <- "Respondent ideology"
temp$subgroup <- "Right-wing"
temp$sample <- "Politician sample"
fig5_rows[[length(fig5_rows) + 1]] <- temp

amce_pol_ideo_l <- amce(cj_formula,
                        data = subset(pol_data, resp_ideology_discrete == "Left-wing"),
                        id = ~ id)
temp <- extract_woman_pol(amce_pol_ideo_l)
temp$group <- "Respondent ideology"
temp$subgroup <- "Left-wing"
temp$sample <- "Politician sample"
fig5_rows[[length(fig5_rows) + 1]] <- temp

# co-partisan with politician
amce_pol_copart_non <- amce(cj_formula_copartisan,
                            data = subset(pol_data, party_pol_copartisan == "Non co-partisan"),
                            id  = ~ id)
temp <- extract_woman_pol(amce_pol_copart_non)
temp$group <- "Respondent is a co-partisan of the politician"
temp$subgroup <- "Non co-partisan"
temp$sample <- "Politician sample"
fig5_rows[[length(fig5_rows) + 1]] <- temp

amce_pol_copart_co <- amce(cj_formula_copartisan,
                           data = subset(pol_data, party_pol_copartisan == "Co-partisan"),
                           id   = ~ id)
temp <- extract_woman_pol(amce_pol_copart_co)
temp$group <- "Respondent is a co-partisan of the politician"
temp$subgroup <- "Co-partisan"
temp$sample <- "Politician sample"
fig5_rows[[length(fig5_rows) + 1]] <- temp

# social media harassment experience
amce_pol_harass_no <- amce(cj_formula,
                           data = subset(pol_data,
                                         resp_exposure_binary ==
                                           "Has not experienced social media harassment"),
                           id = ~ id)
temp <- extract_woman_pol(amce_pol_harass_no)
temp$group <- "Respondent experience with social media harassment"
temp$subgroup <- "No exp. w/ harassment"
temp$sample <- "Politician sample"
fig5_rows[[length(fig5_rows) + 1]] <- temp

amce_pol_harass_yes <- amce(cj_formula,
                            data = subset(pol_data,
                                          resp_exposure_binary ==
                                            "Experienced social media harassment"),
                            id = ~ id)
temp <- extract_woman_pol(amce_pol_harass_yes)
temp$group <- "Respondent experience with social media harassment"
temp$subgroup <- "Exp. w/ harassment"
temp$sample <- "Politician sample"
fig5_rows[[length(fig5_rows) + 1]] <- temp
# citizen 
# respondent gender
amce_cit_gender_w <- amce(cj_formula,
                          data = subset(cit_data, resp_gender == "Female"),
                          id = ~ id)
temp <- extract_woman_pol(amce_cit_gender_w)
temp$group <- "Respondent gender"
temp$subgroup <- "Woman"
temp$sample <- "Citizen sample"
fig5_rows[[length(fig5_rows) + 1]] <- temp

amce_cit_gender_m <- amce(cj_formula,
                          data = subset(cit_data, resp_gender == "Male"),
                          id = ~ id)
temp <- extract_woman_pol(amce_cit_gender_m)
temp$group <- "Respondent gender"
temp$subgroup <- "Man"
temp$sample <- "Citizen sample"
fig5_rows[[length(fig5_rows) + 1]] <- temp

# respondent ideology
amce_cit_ideo_r <- amce(cj_formula,
                        data = subset(cit_data, resp_ideology_discrete == "Right-wing"),
                        id   = ~ id)
temp <- extract_woman_pol(amce_cit_ideo_r)
temp$group <- "Respondent ideology"
temp$subgroup <- "Right-wing"
temp$sample <- "Citizen sample"
fig5_rows[[length(fig5_rows) + 1]] <- temp

amce_cit_ideo_l <- amce(cj_formula,
                        data = subset(cit_data, resp_ideology_discrete == "Left-wing"),
                        id = ~ id)
temp <- extract_woman_pol(amce_cit_ideo_l)
temp$group <- "Respondent ideology"
temp$subgroup <- "Left-wing"
temp$sample <- "Citizen sample"
fig5_rows[[length(fig5_rows) + 1]] <- temp

## co-partisan with politician
amce_cit_copart_non <- amce(cj_formula_copartisan,
                            data = subset(cit_data, party_pol_copartisan == "Non co-partisan"),
                            id = ~ id)
temp <- extract_woman_pol(amce_cit_copart_non)
temp$group <- "Respondent is a co-partisan of the politician"
temp$subgroup <- "Non co-partisan"
temp$sample <- "Citizen sample"
fig5_rows[[length(fig5_rows) + 1]] <- temp

amce_cit_copart_co <- amce(cj_formula_copartisan,
                           data = subset(cit_data, party_pol_copartisan == "Co-partisan"),
                           id = ~ id)
temp <- extract_woman_pol(amce_cit_copart_co)
temp$group <- "Respondent is a co-partisan of the politician"
temp$subgroup <- "Co-partisan"
temp$sample <- "Citizen sample"
fig5_rows[[length(fig5_rows) + 1]] <- temp

# social media harassment experience
amce_cit_harass_no <- amce(cj_formula,
                           data = subset(cit_data,
                                         resp_exposure_binary ==
                                           "Has not experienced social media harassment"),
                           id = ~ id)
temp <- extract_woman_pol(amce_cit_harass_no)
temp$group <- "Respondent experience with social media harassment"
temp$subgroup <- "No exp. w/ harassment"
temp$sample <- "Citizen sample"
fig5_rows[[length(fig5_rows) + 1]] <- temp

amce_cit_harass_yes <- amce(cj_formula,
                            data = subset(cit_data,
                                          resp_exposure_binary ==
                                            "Experienced social media harassment"),
                            id = ~ id)
temp <- extract_woman_pol(amce_cit_harass_yes)
temp$group <- "Respondent experience with social media harassment"
temp$subgroup <- "Exp. w/ harassment"
temp$sample <- "Citizen sample"
fig5_rows[[length(fig5_rows) + 1]] <- temp
# 2. Combine rows into one data frame

fig5 <- rbind(
  fig5_rows[[1]],  fig5_rows[[2]],  fig5_rows[[3]],  fig5_rows[[4]],
  fig5_rows[[5]],  fig5_rows[[6]],  fig5_rows[[7]],  fig5_rows[[8]],
  fig5_rows[[9]],  fig5_rows[[10]], fig5_rows[[11]], fig5_rows[[12]],
  fig5_rows[[13]], fig5_rows[[14]], fig5_rows[[15]], fig5_rows[[16]]
)

# order panels and subgroup labels
fig5$group <- factor(
  fig5$group,
  levels = c(
    "Respondent gender",
    "Respondent ideology",
    "Respondent is a co-partisan of the politician",
    "Respondent experience with social media harassment"
  )
)

fig5$subgroup <- factor(
  fig5$subgroup,
  levels = c(
    "Woman", "Man",
    "Right-wing", "Left-wing",
    "Non co-partisan", "Co-partisan",
    "No exp. w/ harassment", "Exp. w/ harassment"
  )
)

# percentage points
fig5$estimate <- fig5$estimate * 100
fig5$lower <- fig5$lower * 100
fig5$upper <- fig5$upper * 100
# 3. Plots
fig5_pol <- subset(fig5, sample == "Politician sample")
fig5_cit <- subset(fig5, sample == "Citizen sample")

fig5_pol_plot <- ggplot(fig5_pol,
                        aes(x = estimate, y = subgroup)) +
  geom_vline(xintercept = 0) +
  geom_point(size = 2.5) +
  geom_errorbarh(aes(xmin = lower, xmax = upper), height = 0) +
  facet_grid(group ~ ., scales = "free_y") +
  scale_x_continuous(breaks = seq(0, 10, by = 2)) +
  coord_cartesian(xlim = c(0, 10)) +
  labs(
    x = "AMCE (percentage points)\nfor whether the politician is a woman",
    y = "",
    title = "Politician sample"
  ) +
  theme_minimal(base_size = 12) +
  theme(strip.text.y = element_blank())

fig5_cit_plot <- ggplot(fig5_cit,
                        aes(x = estimate, y = subgroup)) +
  geom_vline(xintercept = 0) +
  geom_point(size = 2.5) +
  geom_errorbarh(aes(xmin = lower, xmax = upper), height = 0) +
  facet_grid(group ~ ., scales = "free_y") +
  scale_x_continuous(breaks = seq(0, 10, by = 2)) +
  coord_cartesian(xlim = c(0, 10)) +
  labs(
    x = "AMCE (percentage points)\nfor whether the politician is a woman",
    y = "",
    title = "Citizen sample"
  ) +
  theme_minimal(base_size = 12) +
  theme(strip.text.y = element_blank())

fig5_pol_plot

fig5_cit_plot

Figure 6: Effects of Politician’s Gender on Perceptions of Motivations behind a Toxic Message

# 1. Helper: run vignette model with felm
run_vig_felm <- function(data, outcome_var) {
  form_text <- paste0(
    outcome_var,
    " ~ text_pol + text_user + party_pol_copartisan + ",
    "woman_pol + gendered + woman_user | country | 0 | id"
  )
  form <- as.formula(form_text)
  felm(form, data = data)
}

# 2. Helper: extract woman-politician coef + CI
extract_woman_pol_felm <- function(model, group_label, sample_label) {
  sm <- summary(model)
  coef_mat <- sm$coef
  row_index <- grep("^woman_pol", rownames(coef_mat))[1]
  est <- coef_mat[row_index, "Estimate"]

  if ("Cluster s.e." %in% colnames(coef_mat)) {
    se <- coef_mat[row_index, "Cluster s.e."]
  } else if ("Std. Error" %in% colnames(coef_mat)) {
    se <- coef_mat[row_index, "Std. Error"]
  } else {
    se <- coef_mat[row_index, 2]
  }

  lower <- est - 1.96 * se
  upper <- est + 1.96 * se

  data.frame(
    group = group_label, 
    respondent_type = sample_label,
    estimate = est,
    lower = lower,
    upper = upper,
    stringsAsFactors = FALSE
  )
}
# 3. Run models for the 7 motivations
# Labels based on figure given in the paper
motivation_labels <- c(
  prejudice = "Motivated by prejudice",
  discourage_pol = "To discourage politician from being in politics",
  opinion_diff = "Opinion difference with politician",
  dislike_party = "Dislike of the politician's party",
  dissatisfied = "Dissatisfaction with own life",
  troll_pol = "To get a reaction from the politician",
  troll_user = "To get a reaction from other users"
)

fig6_rows <- list()

for (mot in names(motivation_labels)) {
label <- motivation_labels[[mot]]

# politician
mod_pol <- run_vig_felm(data = vig_pol,outcome_var = mot)
temp_pol <- extract_woman_pol_felm( model = mod_pol, group_label = label, sample_label  = "Politician sample")
fig6_rows[[length(fig6_rows) + 1]] <- temp_pol

# citizen 
mod_cit <- run_vig_felm(data = vig_cit,outcome_var = mot)
temp_cit <- extract_woman_pol_felm(model = mod_cit, group_label = label, sample_label= "Citizen sample")
fig6_rows[[length(fig6_rows) + 1]] <- temp_cit

}
## 4. make df

fig6 <- rbind(
  fig6_rows[[1]],  fig6_rows[[2]],  fig6_rows[[3]],  fig6_rows[[4]],
  fig6_rows[[5]],  fig6_rows[[6]],  fig6_rows[[7]],  fig6_rows[[8]],
  fig6_rows[[9]],  fig6_rows[[10]], fig6_rows[[11]], fig6_rows[[12]],
  fig6_rows[[13]], fig6_rows[[14]]
)

fig6$group <- factor( fig6$group, levels = rev(c(
    "Motivated by prejudice",
    "To discourage politician from being in politics",
    "Opinion difference with politician",
    "Dislike of the politician's party",
    "Dissatisfaction with own life",
    "To get a reaction from the politician",
    "To get a reaction from other users"
  ))
)

fig6$respondent_type <- factor(fig6$respondent_type, levels = c("Politician sample", "Citizen sample"))
# 5. Plot

fig6_plot <- ggplot(fig6,
                    aes(x = estimate, y = group,
                        colour = respondent_type)) +
  geom_vline(xintercept = 0) +
  geom_point(size = 2.5) +
  geom_errorbarh(aes(xmin = lower, xmax = upper), height = 0) +
  facet_wrap(~ respondent_type) +
  coord_cartesian(xlim = c(-0.15, 0.35)) +
  scale_x_continuous(
    breaks = seq(-0.1, 0.3, by = 0.1),
    labels = c("-0.1", "0", "0.1", "0.2", "0.3")
  ) +
  scale_colour_manual(
    values = c("Politician sample" = "#FF3E00",
               "Citizen sample"    = "#5200FF")
  ) +
  labs(
    x = "Coefficient",
    y = NULL
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "none",
    strip.text.x = element_text(face = "bold")
  )

fig6_plot

Figure 7: Effects of Politician’s Gender on Perceptions of Whether a User is Prejudiced Conditional on Whether the Message is Gendered and the User’s Gender

# Helper: coef + 95% CI for one term from a felm()

get_ci <- function(model, term_name) {
  sm <- summary(model, robust = TRUE)
  coefs <- sm$coefficients
  rn <- rownames(coefs)

  idx <- which(rn == term_name)
  if (length(idx) != 1L) stop(paste("Term not found or not unique:", term_name))

  est <- coefs[idx, "Estimate"]
  if ("Cluster s.e." %in% colnames(coefs)) {
    se <- coefs[idx, "Cluster s.e."]
  } else {
    se <- coefs[idx, "Std. Error"]
  }
  z <- qnorm(0.975)

  lower <- est - z * se
  upper <- est + z * se

  c(estimate = est, lower = lower, upper = upper, se = se)
}
# 7A: woman politician × gendered text

mod7A_pol <- felm(
  prejudice ~ text_pol + text_user + party_pol_copartisan +
    woman_pol * gendered + woman_user + woman_pol |
    country | 0 | id,
  data = subset(vig, respondent_type == "Politician")
)

mod7A_cit <- felm(
  prejudice ~ text_pol + text_user + party_pol_copartisan +
    woman_pol * gendered + woman_user + woman_pol |
    country | 0 | id,
  data = subset(vig, respondent_type == "Citizen")
)

fig7A <- data.frame()

# interaction: Woman politician x gendered text
ci <- get_ci(mod7A_pol, "woman_polWoman politician:genderedGendered text")
temp <- data.frame(
  estimate  = ci["estimate"],
  lower = ci["lower"],
  upper = ci["upper"],
  std.error = ci["se"],
  feature = "Interaction",
  level = "Woman politician x gendered text",
  sample = "Politician sample",
  stringsAsFactors = FALSE
)
fig7A <- rbind(fig7A, temp)

ci <- get_ci(mod7A_cit, "woman_polWoman politician:genderedGendered text")
temp <- data.frame(
  estimate  = ci["estimate"],
  lower = ci["lower"],
  upper = ci["upper"],
  std.error = ci["se"],
  feature = "Interaction",
  level = "Woman politician x gendered text",
  sample = "Citizen sample",
  stringsAsFactors = FALSE
)
fig7A <- rbind(fig7A, temp)

# main effect: gender of politician
ci <- get_ci(mod7A_pol, "woman_polWoman politician")
temp <- data.frame(
  estimate  = ci["estimate"],
  lower = ci["lower"],
  upper = ci["upper"],
  std.error = ci["se"],
  feature = "Gender of politician",
  level = "Woman politician",
  sample = "Politician sample",
  stringsAsFactors = FALSE
)
fig7A <- rbind(fig7A, temp)

ci <- get_ci(mod7A_cit, "woman_polWoman politician")
temp <- data.frame(
  estimate = ci["estimate"],
  lower = ci["lower"],
  upper = ci["upper"],
  std.error = ci["se"],
  feature = "Gender of politician",
  level = "Woman politician",
  sample = "Citizen sample",
  stringsAsFactors = FALSE
)
fig7A <- rbind(fig7A, temp)

# main effect: Gendered text
ci <- get_ci(mod7A_pol, "genderedGendered text")
temp <- data.frame(
  estimate  = ci["estimate"],
  lower = ci["lower"],
  upper = ci["upper"],
  std.error = ci["se"],
  feature = "Gendered text",
  level = "Gendered text",
  sample = "Politician sample",
  stringsAsFactors = FALSE
)
fig7A <- rbind(fig7A, temp)

ci <- get_ci(mod7A_cit, "genderedGendered text")
temp <- data.frame(
  estimate = ci["estimate"],
  lower = ci["lower"],
  upper = ci["upper"],
  std.error = ci["se"],
  feature = "Gendered text",
  level = "Gendered text",
  sample = "Citizen sample",
  stringsAsFactors = FALSE
)
fig7A <- rbind(fig7A, temp)

baseline7A <- data.frame(
  estimate = c(0, 0),
  lower = c(NA, NA),
  upper = c(NA, NA),
  std.error = c(NA, NA),
  feature = c("Gender of politician", "Gendered text"),
  level = c("Man politician", "Non-gendered text"),
  sample = c("Baseline", "Baseline"),
  stringsAsFactors = FALSE
)
fig7A <- rbind(fig7A, baseline7A)

fig7A$sample <- factor(fig7A$sample,
                       levels = c("Politician sample", "Citizen sample", "Baseline"))
# 7B: woman politician × man user

mod7B_pol <- felm(
  prejudice ~ text_pol + text_user + party_pol_copartisan +
    woman_pol * woman_user + woman_user + woman_pol |
    country | 0 | id,
  data = subset(vig, respondent_type == "Politician")
)

mod7B_cit <- felm(
  prejudice ~ text_pol + text_user + party_pol_copartisan +
    woman_pol * woman_user + woman_user + woman_pol |
    country | 0 | id,
  data = subset(vig, respondent_type == "Citizen")
)

fig7B <- data.frame()

# interaction: Woman politician x man user
ci <- get_ci(mod7B_pol, "woman_polWoman politician:woman_userMan user")
temp <- data.frame(
  estimate = ci["estimate"],
  lower = ci["lower"],
  upper = ci["upper"],
  std.error = ci["se"],
  feature = "Interaction",
  level = "Woman politician x man user",
  sample = "Politician sample",
  stringsAsFactors = FALSE
)
fig7B <- rbind(fig7B, temp)

ci <- get_ci(mod7B_cit, "woman_polWoman politician:woman_userMan user")
temp <- data.frame(
  estimate  = ci["estimate"],
  lower = ci["lower"],
  upper = ci["upper"],
  std.error = ci["se"],
  feature = "Interaction",
  level = "Woman politician x man user",
  sample = "Citizen sample",
  stringsAsFactors = FALSE
)
fig7B <- rbind(fig7B, temp)

# main effect: Gender of politician
ci <- get_ci(mod7B_pol, "woman_polWoman politician")
temp <- data.frame(
  estimate  = ci["estimate"],
  lower = ci["lower"],
  upper = ci["upper"],
  std.error = ci["se"],
  feature = "Gender of politician",
  level = "Woman politician",
  sample = "Politician sample",
  stringsAsFactors = FALSE
)
fig7B <- rbind(fig7B, temp)

ci <- get_ci(mod7B_cit, "woman_polWoman politician")
temp <- data.frame(
  estimate  = ci["estimate"],
  lower = ci["lower"],
  upper = ci["upper"],
  std.error = ci["se"],
  feature = "Gender of politician",
  level = "Woman politician",
  sample = "Citizen sample",
  stringsAsFactors = FALSE
)
fig7B <- rbind(fig7B, temp)

# main effect: Gender of user
ci <- get_ci(mod7B_pol, "woman_userMan user")
temp <- data.frame(
  estimate  = ci["estimate"],
  lower = ci["lower"],
  upper = ci["upper"],
  std.error = ci["se"],
  feature = "Gender of user",
  level = "Man user",
  sample = "Politician sample",
  stringsAsFactors = FALSE
)
fig7B <- rbind(fig7B, temp)

ci <- get_ci(mod7B_cit, "woman_userMan user")
temp <- data.frame(
  estimate  = ci["estimate"],
  lower = ci["lower"],
  upper = ci["upper"],
  std.error = ci["se"],
  feature = "Gender of user",
  level = "Man user",
  sample = "Citizen sample",
  stringsAsFactors = FALSE
)
fig7B <- rbind(fig7B, temp)

# baselines for 7B
baseline7B <- data.frame(
  estimate  = c(0, 0),
  lower = c(NA, NA),
  upper = c(NA, NA),
  std.error = c(NA, NA),
  feature = c("Gender of politician", "Gender of user"),
  level = c("Man politician", "Woman user"),
  sample = c("Baseline", "Baseline"),
  stringsAsFactors = FALSE
)
fig7B <- rbind(fig7B, baseline7B)

fig7B$sample <- factor(fig7B$sample,
                       levels = c("Politician sample", "Citizen sample", "Baseline"))
plot_fig7_interaction <- function(df, feature_name, y_label, main_title) {

  panel <- subset(df, feature == feature_name & !is.na(std.error))
  panel$level <- factor(panel$level)

  ggplot(panel,
         aes(x = estimate, y = level, colour = sample)) +
    geom_vline(xintercept = 0) +
    geom_point(size = 2.5,
               position = position_dodge(width = 0.4)) +
    geom_errorbarh(aes(xmin = lower, xmax = upper),
                   height = 0,
                   position = position_dodge(width = 0.4)) +
    scale_x_continuous(
      breaks = seq(-0.2, 0.5, by = 0.1),
      labels = c("-.2", "-.1", "0", ".1", ".2", ".3", ".4", ".5")
    ) +
    coord_cartesian(xlim = c(-0.2, 0.5)) +
    labs(
      x = "Coefficient",
      y = y_label,
      title = main_title
    ) +
    theme_minimal(base_size = 13) +
    theme(legend.position = "top")
}

plot_fig7_main <- function(df, feature_name, y_levels, y_label, main_title) {

  panel <- subset(df, feature == feature_name)
  panel$level <- factor(panel$level, levels = y_levels)

  baseline <- subset(panel, is.na(std.error))
  by_sample <- subset(panel, !is.na(std.error))

  ggplot() +
    geom_vline(xintercept = 0) +

    geom_point(data = by_sample,
               aes(x = estimate, y = level, colour = sample),
               size = 2.5,
               position = position_dodge(width = 0.4)) +
    geom_errorbarh(data = by_sample,
                   aes(xmin = lower, xmax = upper, y = level, colour = sample),
                   height = 0,
                   position = position_dodge(width = 0.4)) +

    geom_point(data = baseline,
               aes(x = estimate, y = level),
               shape = 16, colour = "black", size = 2.5) +

    scale_x_continuous(
      breaks = seq(-0.2, 0.5, by = 0.1),
      labels = c("-.2", "-.1", "0", ".1", ".2", ".3", ".4", ".5")
    ) +
    coord_cartesian(xlim = c(-0.2, 0.5)) +
    scale_y_discrete(limits = y_levels, drop = FALSE) +
    labs(
      x = "Coefficient",
      y = y_label,
      title = main_title
    ) +
    theme_minimal(base_size = 13) +
    theme(legend.position = "top")
}

# plots

# A: woman politician x gendered text
fig7a_interaction <- plot_fig7_interaction(
  df = fig7A,
  feature_name = "Interaction",
  y_label = "Woman politician x gendered text",
  main_title = "Interaction: Politician gender × gendered text"
)

fig7a_polgender <- plot_fig7_main(
  df = fig7A,
  feature_name = "Gender of politician",
  y_levels = c("Man politician", "Woman politician"),
  y_label = "Gender of politician",
  main_title = "Gender of politician"
)

fig7a_gendered <- plot_fig7_main(
  df = fig7A,
  feature_name = "Gendered text",
  y_levels = c("Non-gendered text", "Gendered text"),
  y_label = "Gendered text",
  main_title = "Gendered text"
)

# B: woman politician x man user
fig7b_interaction <- plot_fig7_interaction(
  df = fig7B,
  feature_name = "Interaction",
  y_label = "Woman politician x man user",
  main_title = "Interaction: Politician gender × user gender"
)

fig7b_polgender <- plot_fig7_main(
  df = fig7B,
  feature_name = "Gender of politician",
  y_levels = c("Man politician", "Woman politician"),
  y_label = "Gender of politician",
  main_title = "Gender of politician"
)

fig7b_usergender <- plot_fig7_main(
  df = fig7B,
  feature_name = "Gender of user",
  y_levels = c("Woman user", "Man user"),
  y_label = "Gender of user",
  main_title = "Gender of user"
)

fig7a_interaction; fig7a_polgender; fig7a_gendered

fig7b_interaction; fig7b_polgender; fig7b_usergender

Quantitative Modifications

library(sandwich)
library(lmtest)

# 1. Original LPM (baseline AMCEs)
lpm_model <- lm(cj_formula, data = data)

# Cluster-robust SEs by respondent (id = respondent identifier)
vcov_lpm_cluster <- vcovCL(lpm_model, cluster = ~ id)

summary_lpm <- coeftest(lpm_model, vcov. = vcov_lpm_cluster)
summary_lpm
## 
## t test of coefficients:
## 
##                                    Estimate  Std. Error  t value  Pr(>|t|)    
## (Intercept)                      0.43933074  0.01116637  39.3441 < 2.2e-16 ***
## text_polPolText02                0.00437634  0.01152442   0.3797 0.7041359    
## text_polPolText03                0.01341268  0.01159157   1.1571 0.2472326    
## text_polPolText04                0.01339311  0.01158184   1.1564 0.2475261    
## text_polPolText05                0.03598143  0.01167846   3.0810 0.0020638 ** 
## text_polPolText06                0.02191279  0.01146466   1.9113 0.0559657 .  
## text_polPolText07                0.02291110  0.01162186   1.9714 0.0486843 *  
## text_polPolText08                0.02100116  0.01172156   1.7917 0.0731900 .  
## text_polPolText09                0.01191254  0.01159000   1.0278 0.3040335    
## text_polPolText10                0.00718687  0.01164173   0.6173 0.5370146    
## text_polPolText11                0.00754466  0.01172105   0.6437 0.5197822    
## text_polPolText12                0.01558060  0.01164875   1.3375 0.1810525    
## text_polPolText13                0.02246017  0.01163637   1.9302 0.0535897 .  
## text_polPolText14                0.02932963  0.01165266   2.5170 0.0118383 *  
## text_polPolText15                0.01136193  0.01148011   0.9897 0.3223216    
## text_polPolText16                0.01995472  0.01140151   1.7502 0.0800912 .  
## text_polPolText17                0.01850446  0.01181037   1.5668 0.1171662    
## text_polPolText18                0.01705913  0.01158307   1.4728 0.1408188    
## text_polPolText19                0.00825457  0.01156716   0.7136 0.4754634    
## text_polPolText20                0.01401783  0.01163251   1.2051 0.2281855    
## text_userUserText02             -0.04765184  0.01048933  -4.5429 5.558e-06 ***
## text_userUserText03              0.05729123  0.01064073   5.3841 7.302e-08 ***
## text_userUserText04              0.03400212  0.01074014   3.1659 0.0015467 ** 
## text_userUserText05             -0.11905930  0.01060319 -11.2286 < 2.2e-16 ***
## text_userUserText06              0.00168817  0.01075011   0.1570 0.8752156    
## text_userUserText07             -0.11040585  0.01061900 -10.3970 < 2.2e-16 ***
## text_userUserText08             -0.00541292  0.01087970  -0.4975 0.6188205    
## text_userUserText09             -0.09515705  0.01065987  -8.9267 < 2.2e-16 ***
## text_userUserText10              0.01486436  0.01071236   1.3876 0.1652664    
## text_userUserText11              0.00039408  0.01086786   0.0363 0.9710744    
## text_userUserText12             -0.17207339  0.01054474 -16.3184 < 2.2e-16 ***
## text_userUserText13              0.01839457  0.01071552   1.7166 0.0860513 .  
## text_userUserText14              0.02377557  0.01084551   2.1922 0.0283679 *  
## text_userUserText15              0.11554113  0.01039994  11.1098 < 2.2e-16 ***
## text_userUserText16             -0.01503253  0.01090655  -1.3783 0.1681140    
## party_pol_copartisanCo-partisan  0.00414877  0.00440031   0.9428 0.3457673    
## woman_userMan user               0.01260403  0.00368475   3.4206 0.0006252 ***
## woman_polWoman politician        0.05209800  0.00370790  14.0506 < 2.2e-16 ***
## genderedGendered text            0.06118113  0.00374031  16.3572 < 2.2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# 2. Logistic regression + clustered SEs
logit_model <- glm(
  cj_formula,
  data = data,
  family = binomial(link = "logit")
)

vcov_logit_cluster <- vcovCL(logit_model, cluster = ~ id)

summary_logit <- coeftest(logit_model, vcov. = vcov_logit_cluster)
summary_logit
## 
## z test of coefficients:
## 
##                                   Estimate Std. Error  z value  Pr(>|z|)    
## (Intercept)                     -0.2505017  0.0455622  -5.4980 3.841e-08 ***
## text_polPolText02                0.0181284  0.0473834   0.3826 0.7020248    
## text_polPolText03                0.0551924  0.0476951   1.1572 0.2471932    
## text_polPolText04                0.0552433  0.0476269   1.1599 0.2460821    
## text_polPolText05                0.1480795  0.0480765   3.0801 0.0020694 ** 
## text_polPolText06                0.0902087  0.0471290   1.9141 0.0556100 .  
## text_polPolText07                0.0941668  0.0477683   1.9713 0.0486870 *  
## text_polPolText08                0.0864184  0.0482001   1.7929 0.0729876 .  
## text_polPolText09                0.0490306  0.0476736   1.0285 0.3037306    
## text_polPolText10                0.0295895  0.0478798   0.6180 0.5365781    
## text_polPolText11                0.0311189  0.0482145   0.6454 0.5186507    
## text_polPolText12                0.0641486  0.0478886   1.3395 0.1803956    
## text_polPolText13                0.0924745  0.0478533   1.9325 0.0533032 .  
## text_polPolText14                0.1206896  0.0479182   2.5187 0.0117802 *  
## text_polPolText15                0.0467877  0.0471950   0.9914 0.3215048    
## text_polPolText16                0.0821955  0.0468995   1.7526 0.0796729 .  
## text_polPolText17                0.0760772  0.0485589   1.5667 0.1171844    
## text_polPolText18                0.0700657  0.0476490   1.4705 0.1414383    
## text_polPolText19                0.0340321  0.0475616   0.7155 0.4742773    
## text_polPolText20                0.0578516  0.0478184   1.2098 0.2263481    
## text_userUserText02             -0.1922450  0.0423456  -4.5399 5.628e-06 ***
## text_userUserText03              0.2331393  0.0433802   5.3743 7.687e-08 ***
## text_userUserText04              0.1376503  0.0435364   3.1617 0.0015684 ** 
## text_userUserText05             -0.4853072  0.0436144 -11.1272 < 2.2e-16 ***
## text_userUserText06              0.0067480  0.0433596   0.1556 0.8763257    
## text_userUserText07             -0.4490739  0.0435441 -10.3131 < 2.2e-16 ***
## text_userUserText08             -0.0219442  0.0438674  -0.5002 0.6169072    
## text_userUserText09             -0.3858433  0.0434744  -8.8752 < 2.2e-16 ***
## text_userUserText10              0.0600034  0.0432690   1.3868 0.1655178    
## text_userUserText11              0.0015525  0.0438317   0.0354 0.9717444    
## text_userUserText12             -0.7142034  0.0446754 -15.9865 < 2.2e-16 ***
## text_userUserText13              0.0743130  0.0433094   1.7159 0.0861872 .  
## text_userUserText14              0.0960507  0.0438674   2.1896 0.0285556 *  
## text_userUserText15              0.4788454  0.0435052  11.0066 < 2.2e-16 ***
## text_userUserText16             -0.0606564  0.0439602  -1.3798 0.1676467    
## party_pol_copartisanCo-partisan  0.0170250  0.0180982   0.9407 0.3468582    
## woman_userMan user               0.0518271  0.0151490   3.4211 0.0006236 ***
## woman_polWoman politician        0.2138494  0.0152480  14.0247 < 2.2e-16 ***
## genderedGendered text            0.2509599  0.0153816  16.3156 < 2.2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# 3. Turn logit coefficients into odds ratios
logit_coef <- summary_logit

logit_results <- data.frame(
  term = rownames(logit_coef),
  estimate = logit_coef[, "Estimate"],
  se = logit_coef[, "Std. Error"],
  z = logit_coef[, "z value"],
  p = logit_coef[, "Pr(>|z|)"],
  OR = exp(logit_coef[, "Estimate"]),
  OR_low = exp(logit_coef[, "Estimate"] - 1.96 * logit_coef[, "Std. Error"]),
  OR_high = exp(logit_coef[, "Estimate"] + 1.96 * logit_coef[, "Std. Error"])
)

logit_results
##                                                            term     estimate
## (Intercept)                                         (Intercept) -0.250501689
## text_polPolText02                             text_polPolText02  0.018128368
## text_polPolText03                             text_polPolText03  0.055192424
## text_polPolText04                             text_polPolText04  0.055243340
## text_polPolText05                             text_polPolText05  0.148079547
## text_polPolText06                             text_polPolText06  0.090208740
## text_polPolText07                             text_polPolText07  0.094166755
## text_polPolText08                             text_polPolText08  0.086418405
## text_polPolText09                             text_polPolText09  0.049030635
## text_polPolText10                             text_polPolText10  0.029589539
## text_polPolText11                             text_polPolText11  0.031118947
## text_polPolText12                             text_polPolText12  0.064148630
## text_polPolText13                             text_polPolText13  0.092474451
## text_polPolText14                             text_polPolText14  0.120689605
## text_polPolText15                             text_polPolText15  0.046787713
## text_polPolText16                             text_polPolText16  0.082195544
## text_polPolText17                             text_polPolText17  0.076077243
## text_polPolText18                             text_polPolText18  0.070065742
## text_polPolText19                             text_polPolText19  0.034032078
## text_polPolText20                             text_polPolText20  0.057851616
## text_userUserText02                         text_userUserText02 -0.192245036
## text_userUserText03                         text_userUserText03  0.233139330
## text_userUserText04                         text_userUserText04  0.137650257
## text_userUserText05                         text_userUserText05 -0.485307164
## text_userUserText06                         text_userUserText06  0.006747992
## text_userUserText07                         text_userUserText07 -0.449073874
## text_userUserText08                         text_userUserText08 -0.021944170
## text_userUserText09                         text_userUserText09 -0.385843264
## text_userUserText10                         text_userUserText10  0.060003377
## text_userUserText11                         text_userUserText11  0.001552541
## text_userUserText12                         text_userUserText12 -0.714203443
## text_userUserText13                         text_userUserText13  0.074312996
## text_userUserText14                         text_userUserText14  0.096050655
## text_userUserText15                         text_userUserText15  0.478845446
## text_userUserText16                         text_userUserText16 -0.060656439
## party_pol_copartisanCo-partisan party_pol_copartisanCo-partisan  0.017025006
## woman_userMan user                           woman_userMan user  0.051827057
## woman_polWoman politician             woman_polWoman politician  0.213849383
## genderedGendered text                     genderedGendered text  0.250959941
##                                         se            z            p        OR
## (Intercept)                     0.04556218  -5.49801794 3.840839e-08 0.7784102
## text_polPolText02               0.04738344   0.38258869 7.020248e-01 1.0182937
## text_polPolText03               0.04769506   1.15719365 2.471932e-01 1.0567439
## text_polPolText04               0.04762693   1.15991813 2.460821e-01 1.0567977
## text_polPolText05               0.04807647   3.08008356 2.069425e-03 1.1596051
## text_polPolText06               0.04712905   1.91407947 5.561000e-02 1.0944027
## text_polPolText07               0.04776831   1.97132283 4.868696e-02 1.0987430
## text_polPolText08               0.04820013   1.79290807 7.298762e-02 1.0902624
## text_polPolText09               0.04767355   1.02846615 3.037306e-01 1.0502525
## text_polPolText10               0.04787984   0.61799577 5.365781e-01 1.0300317
## text_polPolText11               0.04821453   0.64542670 5.186507e-01 1.0316082
## text_polPolText12               0.04788862   1.33953803 1.803956e-01 1.0662509
## text_polPolText13               0.04785332   1.93245651 5.330318e-02 1.0968851
## text_polPolText14               0.04791816   2.51866094 1.178020e-02 1.1282746
## text_polPolText15               0.04719499   0.99137026 3.215048e-01 1.0478995
## text_polPolText16               0.04689955   1.75258707 7.967291e-02 1.0856681
## text_polPolText17               0.04855885   1.56670176 1.171844e-01 1.0790459
## text_polPolText18               0.04764899   1.47045600 1.414383e-01 1.0725787
## text_polPolText19               0.04756159   0.71553696 4.742773e-01 1.0346178
## text_polPolText20               0.04781838   1.20981955 2.263481e-01 1.0595578
## text_userUserText02             0.04234560  -4.53990634 5.627922e-06 0.8251047
## text_userUserText03             0.04338024   5.37432140 7.687163e-08 1.2625574
## text_userUserText04             0.04353642   3.16172631 1.568369e-03 1.1475741
## text_userUserText05             0.04361439 -11.12722620 9.246826e-29 0.6155081
## text_userUserText06             0.04335956   0.15562869 8.763257e-01 1.0067708
## text_userUserText07             0.04354407 -10.31308986 6.149189e-25 0.6382189
## text_userUserText08             0.04386742  -0.50023841 6.169072e-01 0.9782949
## text_userUserText09             0.04347444  -8.87517529 6.982336e-19 0.6798771
## text_userUserText10             0.04326904   1.38675074 1.655178e-01 1.0618401
## text_userUserText11             0.04383173   0.03542049 9.717444e-01 1.0015537
## text_userUserText12             0.04467544 -15.98648750 1.587322e-57 0.4895819
## text_userUserText13             0.04330940   1.71586279 8.618716e-02 1.0771439
## text_userUserText14             0.04386741   2.18956753 2.855562e-02 1.1008148
## text_userUserText15             0.04350525  11.00661354 3.551015e-28 1.6142096
## text_userUserText16             0.04396015  -1.37980498 1.676467e-01 0.9411465
## party_pol_copartisanCo-partisan 0.01809821   0.94070084 3.468582e-01 1.0171708
## woman_userMan user              0.01514903   3.42114781 6.235743e-04 1.0531936
## woman_polWoman politician       0.01524804  14.02471135 1.100590e-44 1.2384361
## genderedGendered text           0.01538160  16.31559527 7.645722e-60 1.2852586
##                                    OR_low   OR_high
## (Intercept)                     0.7119102 0.8511220
## text_polPolText02               0.9279818 1.1173948
## text_polPolText03               0.9624339 1.1602955
## text_polPolText04               0.9626114 1.1601997
## text_polPolText05               1.0553259 1.2741884
## text_polPolText06               0.9978381 1.2003122
## text_polPolText07               1.0005410 1.2065833
## text_polPolText08               0.9919785 1.1982842
## text_polPolText09               0.9565621 1.1531194
## text_polPolText10               0.9377659 1.1313754
## text_polPolText11               0.9385853 1.1338506
## text_polPolText12               0.9707240 1.1711783
## text_polPolText13               0.9986828 1.2047438
## text_polPolText14               1.0271315 1.2393775
## text_polPolText15               0.9553147 1.1494573
## text_polPolText16               0.9903196 1.1901968
## text_polPolText17               0.9810831 1.1867905
## text_polPolText18               0.9769437 1.1775756
## text_polPolText19               0.9425289 1.1357041
## text_polPolText20               0.9647634 1.1636663
## text_userUserText02             0.7593880 0.8965084
## text_userUserText03             1.1596452 1.3746025
## text_userUserText04             1.0537117 1.2497976
## text_userUserText05             0.5650781 0.6704387
## text_userUserText06             0.9247455 1.0960718
## text_userUserText07             0.5860089 0.6950806
## text_userUserText08             0.8976955 1.0661308
## text_userUserText09             0.6243444 0.7403492
## text_userUserText10             0.9755011 1.1558208
## text_userUserText11             0.9191025 1.0914016
## text_userUserText12             0.4485356 0.5343845
## text_userUserText13             0.9894823 1.1725718
## text_userUserText14             1.0101214 1.1996511
## text_userUserText15             1.4822707 1.7578926
## text_userUserText16             0.8634508 1.0258335
## party_pol_copartisanCo-partisan 0.9817216 1.0538999
## woman_userMan user              1.0223818 1.0849340
## woman_polWoman politician       1.2019716 1.2760068
## genderedGendered text           1.2470890 1.3245965
library(ggplot2)

plot_data <- subset(logit_results, term != "(Intercept)")

# order terms
plot_data$term <- factor(plot_data$term, levels = rev(plot_data$term))

ggplot(plot_data, aes(x = estimate, y = term)) +
  geom_vline(xintercept = 0, linetype = "dashed") +
  geom_point() +
  geom_errorbar(aes(
    xmin = estimate - 1.96 * se,
    xmax = estimate + 1.96 * se
  ),
  width = 0
  ) +
  labs(
    x = "Logit coefficient (log-odds, 95% CI)",
    y = "",
    title = "Clustered logit estimates for conjoint attributes"
  ) +
  theme_minimal()

plot_data$term <- factor(plot_data$term, levels = rev(plot_data$term))

ggplot(plot_data, aes(x = OR, y = term)) +
  geom_vline(xintercept = 1, linetype = "dashed") +
  geom_point() +
  geom_errorbar(aes(xmin = OR_low, xmax = OR_high), width = 0) +
  labs(
    x = "Odds ratio (95% CI)",
    y = "",
    title = "Odds ratios from clustered logit model"
  ) +
  theme_minimal()