Demographics

Gender

df_cbzs_elg %>% 
  mutate(gender = ifelse(is.na(gender) | gender == "","other",gender)) %>% 
  group_by(gender) %>% 
  summarise(N = n()) %>% 
  ungroup() %>% 
  mutate(Perc = round(100*(N/sum(N)),2)) %>% 
  ungroup() %>% 
  arrange(desc(Perc)) %>% 
  kbl() %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
gender N Perc
man 252 51.53
woman 230 47.03
other 7 1.43

Race

race N Perc
White 349 71.37
Black or African American 54 11.04
multiracial 37 7.57
Asian 27 5.52
Hispanic, Latino, or Spanish origin 16 3.27
Middle Eastern or North African 2 0.41
Other (please specify) 2 0.41
American Indian or Alaska Native 1 0.20
NA 1 0.20

Age

Mean age: 41.02.

Income

median_income_num <- df_cbzs_elg %>% 
  mutate(income_num = as.numeric(income)) %>% 
  summarise(median = median(income_num, na.rm = TRUE)) %>% 
  pull(median)

df_cbzs_elg %>% 
  ggplot(aes(x = income)) +
  geom_bar() +
  geom_vline(xintercept = median_income_num, 
             color = "lightblue", linetype = "dashed") +
  theme(panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.ticks = element_blank(),
        axis.line = element_line(color = "grey66"),
        axis.text.y = element_text(color = "black"),
        axis.text.x = element_text(color = "black", face = "bold"),
        axis.title.x = element_blank(),
        axis.title.y = element_blank()) +
  coord_flip()

Education

edu N Perc
noHS 1 0.20
GED 87 17.79
2yearColl 47 9.61
4yearColl 239 48.88
MA 93 19.02
PHD 21 4.29
NA 1 0.20

SES

ses N Perc
Lower Class 53 10.84
Lower Middle Class 124 25.36
Middle Class 229 46.83
Upper Middle Class 80 16.36
Upper Class 2 0.41
NA 1 0.20

Politics

Political ideology

Participants were asked about the extent to which they subscribe to the following ideologies on a scale of 1-7 (select NA if unfamiliar): Conservatism, Liberalism, Democratic Socialism, Libertarianism, Progressivism.

means <- df_cbzs_elg %>%
  dplyr::select(PID,ideo_con:ideo_prog) %>% 
  pivot_longer(-PID,
               names_to = "ideo",
               values_to = "score") %>% 
  filter(!is.na(score)) %>% 
  group_by(ideo) %>% 
  summarise(score = mean(score)) %>% 
  ungroup()

df_cbzs_elg %>%
  dplyr::select(PID,ideo_con:ideo_prog) %>% 
  pivot_longer(-PID,
               names_to = "ideo",
               values_to = "score") %>% 
  filter(!is.na(score)) %>%  
  ggplot() +
  geom_density(aes(x = score), fill = "lightblue",color = NA) +
  scale_x_continuous(limits = c(1,7),
                     breaks = seq(1,7,1)) +
  geom_vline(data = means,mapping = aes(xintercept = score),
             color = "black",
             linetype = "dashed",
             size = 1.1) +
  theme(panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.ticks = element_blank(),
        axis.line = element_line(color = "grey66"),
        axis.text.y = element_text(color = "black"),
        axis.text.x = element_text(color = "black",
                                   face = "bold")) +
  facet_wrap(~ideo,nrow = 2)

Party affiliation

party_id N Perc
Independent 164 33.54
Democrat 163 33.33
Republican 162 33.13

Measures

Class-based Zero-Sum Beliefs

  1. If the upper class becomes richer, this comes at the expense of the working class
  2. If the upper class makes more money, then the working class makes less money
  3. If the upper class does better economically, this does NOT come at the expense of the working class [R]

alpha = 0.89

df_cbzs_elg %>% 
  ggplot(aes(x = zs_class)) +
  geom_density(fill = "lightblue",
                 color = NA) +
  scale_x_continuous(breaks = seq(1,7,1),
                     limits = c(1,7)) +
  ylab("density") +
  geom_vline(xintercept = mean(df_cbzs_elg$zs_class,na.rm = T),
             color = "black",
             linetype = "dashed",
             size = 1.1) +
  theme(panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.ticks = element_blank(),
        axis.line = element_line(color = "grey66"),
        axis.text.y = element_text(color = "black"),
        axis.text.x = element_text(color = "black",
                                   face = "bold"),
        axis.title.x = element_text(color = "black",
                                   face = "bold"))

Relative Deprivation

  1. When I think about what I have compared to others, I feel deprived
  2. I feel privileged compared to other people like me [R]
  3. I feel resentful when I see how prosperous other people seem to be
  4. When I compare what I have with others, I realize that I am quite well off [R]

alpha = 0.72

df_cbzs_elg %>% 
  ggplot(aes(x = reldep)) +
  geom_density(fill = "lightblue",
                 color = NA) +
  scale_x_continuous(breaks = seq(1,7,1),
                     limits = c(1,7)) +
  ylab("density") +
  geom_vline(xintercept = mean(df_cbzs_elg$reldep,na.rm = T),
             color = "black",
             linetype = "dashed",
             size = 1.1) +
  theme(panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.ticks = element_blank(),
        axis.line = element_line(color = "grey66"),
        axis.text.y = element_text(color = "black"),
        axis.text.x = element_text(color = "black",
                                   face = "bold"),
        axis.title.x = element_text(color = "black",
                                   face = "bold"))

Perceived working-class disadvantage

  1. Working-class people are struggling to make ends meet.
  2. Most working-class people live paycheck to paycheck.
  3. Working-class people are facing serious financial hardship.

alpha = 0.9

df_cbzs_elg %>% 
  ggplot(aes(x = dis)) +
  geom_density(fill = "lightblue",
                 color = NA) +
  scale_x_continuous(breaks = seq(1,7,1),
                     limits = c(1,7)) +
  ylab("density") +
  geom_vline(xintercept = mean(df_cbzs_elg$dis,na.rm = T),
             color = "black",
             linetype = "dashed",
             size = 1.1) +
  theme(panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.ticks = element_blank(),
        axis.line = element_line(color = "grey66"),
        axis.text.y = element_text(color = "black"),
        axis.text.x = element_text(color = "black",
                                   face = "bold"),
        axis.title.x = element_text(color = "black",
                                   face = "bold"))

System justification

  1. In general, I find society to be fair.
  2. In general, the American political system operates as it should.
  3. American society needs to be radically restructured. [R]
  4. The United States is the best country in the world to live in.
  5. Most policies serve the greater good.
  6. Everyone has a fair shot at wealth and happiness.
  7. Our society is getting worse every year. [R]
  8. Society is set up so that people usually get what they deserve.

alpha = 0.92

df_cbzs_elg %>% 
  ggplot(aes(x = sj)) +
  geom_density(fill = "lightblue",
                 color = NA) +
  scale_x_continuous(breaks = seq(1,7,1),
                     limits = c(1,7)) +
  ylab("density") +
  geom_vline(xintercept = mean(df_cbzs_elg$sj,na.rm = T),
             color = "black",
             linetype = "dashed",
             size = 1.1) +
  theme(panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.ticks = element_blank(),
        axis.line = element_line(color = "grey66"),
        axis.text.y = element_text(color = "black"),
        axis.text.x = element_text(color = "black",
                                   face = "bold"),
        axis.title.x = element_text(color = "black",
                                   face = "bold"))

General zero-sum mindset

  1. The success of one person is usually the failure of another person.
  2. Life is such that when one person gains, someone else has to lose.
  3. When someone does much for others, they lose.
  4. In most situations, different people’s interests are incompatible.
  5. When one person is winning, it does not mean that someone else is losing. [R]
  6. Life is like a tennis game - A person wins only when another person loses.
  7. One person’s success is not another person’s failure. [R]

alpha = 0.89

df_cbzs_elg %>% 
  ggplot(aes(x = zsm)) +
  geom_density(fill = "lightblue",
                 color = NA) +
  scale_x_continuous(breaks = seq(1,7,1),
                     limits = c(1,7)) +
  ylab("density") +
  geom_vline(xintercept = mean(df_cbzs_elg$zsm,na.rm = T),
             color = "black",
             linetype = "dashed",
             size = 1.1) +
  theme(panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.ticks = element_blank(),
        axis.line = element_line(color = "grey66"),
        axis.text.y = element_text(color = "black"),
        axis.text.x = element_text(color = "black",
                                   face = "bold"),
        axis.title.x = element_text(color = "black",
                                   face = "bold"))

Social dominance orientation

  1. An ideal society requires some groups to be on top and others to be on the bottom.
  2. Some groups of people are simply inferior to other groups.
  3. No one group should dominate in society. [R]
  4. Groups at the bottom are just as deserving as groups at the top. [R]
  5. Group equality should not be our primary goal.
  6. It is unjust to try to make groups equal.
  7. We should do what we can to equalize conditions for different groups. [R]
  8. We should work to give all groups an equal chance to succeed. [R]

alpha = 0.91

df_cbzs_elg %>% 
  ggplot(aes(x = SDO)) +
  geom_density(fill = "lightblue",
                 color = NA) +
  scale_x_continuous(breaks = seq(1,7,1),
                     limits = c(1,7)) +
  ylab("density") +
  geom_vline(xintercept = mean(df_cbzs_elg$SDO,na.rm = T),
             color = "black",
             linetype = "dashed",
             size = 1.1) +
  theme(panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.ticks = element_blank(),
        axis.line = element_line(color = "grey66"),
        axis.text.y = element_text(color = "black"),
        axis.text.x = element_text(color = "black",
                                   face = "bold"),
        axis.title.x = element_text(color = "black",
                                   face = "bold"))

Class Solidarity

  1. I feel a sense of solidarity with the working class
  2. I support policy that helps the working class
  3. I stand united with the working class
  4. Policies negatively affecting the working class should be changed
  5. More people should know about how the working class are negatively affected by economic issues
  6. It’s important to challenge the power structures that disadvantage the working class

alpha = 0.9

df_cbzs_elg %>% 
  ggplot(aes(x = soli)) +
  geom_density(fill = "lightblue",
                 color = NA) +
  scale_x_continuous(breaks = seq(1,7,1),
                     limits = c(1,7)) +
  ylab("density") +
  geom_vline(xintercept = mean(df_cbzs_elg$soli,na.rm = T),
             color = "black",
             linetype = "dashed",
             size = 1.1) +
  theme(panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.ticks = element_blank(),
        axis.line = element_line(color = "grey66"),
        axis.text.y = element_text(color = "black"),
        axis.text.x = element_text(color = "black",
                                   face = "bold"),
        axis.title.x = element_text(color = "black",
                                   face = "bold"))

Linked fate

Mean score of the out-group items in the following scale (we exclude each participant’s own self-reported in-group). If they are not affiliated with any of the following racial groups, we take the mean score of all items. If they are affiliated with more than one group, we take only the items of the groups they are not affiliated with.

  1. What happens to working-class White people in this country will have something to do with what happens to working-class people of my racial group.
  2. Working-class White people and working-class people from my racial group share a common destiny
  3. What happens to working-class Black people in this country will have something to do with what happens to working-class people of my racial group.
  4. Working-class Black people and working-class people from my racial group share a common destiny
  5. What happens to working-class Asian people in this country will have something to do with what happens to working-class people of my racial group.
  6. Working-class Asian people and working-class people from my racial group share a common destiny
  7. What happens to working-class Hispanic people in this country will have something to do with what happens to working-class people of my racial group.
  8. Working-class Hispanic people and working-class people from my racial group share a common destiny

alpha (of all items) = 0.87

df_cbzs_elg %>% 
  ggplot(aes(x = lf)) +
  geom_density(fill = "lightblue",
                 color = NA) +
  scale_x_continuous(breaks = seq(1,7,1),
                     limits = c(1,7)) +
  ylab("density") +
  geom_vline(xintercept = mean(df_cbzs_elg$lf,na.rm = T),
             color = "black",
             linetype = "dashed",
             size = 1.1) +
  theme(panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.ticks = element_blank(),
        axis.line = element_line(color = "grey66"),
        axis.text.y = element_text(color = "black"),
        axis.text.x = element_text(color = "black",
                                   face = "bold"),
        axis.title.x = element_text(color = "black",
                                   face = "bold"))

Cross-Race Class Solidarity

Same procedure as the one in the linked fate measure.

  1. I feel a sense of solidarity with working-class White people
  2. I feel a sense of solidarity with working-class Black people
  3. I feel a sense of solidarity with working-class Asian people
  4. I feel a sense of solidarity with working-class Hispanic people

alpha (of all items) = 0.86

df_cbzs_elg %>% 
  ggplot(aes(x = crs)) +
  geom_density(fill = "lightblue",
                 color = NA) +
  scale_x_continuous(breaks = seq(1,7,1),
                     limits = c(1,7)) +
  ylab("density") +
  geom_vline(xintercept = mean(df_cbzs_elg$crs,na.rm = T),
             color = "black",
             linetype = "dashed",
             size = 1.1) +
  theme(panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.ticks = element_blank(),
        axis.line = element_line(color = "grey66"),
        axis.text.y = element_text(color = "black"),
        axis.text.x = element_text(color = "black",
                                   face = "bold"),
        axis.title.x = element_text(color = "black",
                                   face = "bold"))

Working-class identification

To what extent do you identify as working-class? (0 = Not at All Working-Class; 50 = Moderately Working-Class; 100 = Very Strongly Working-Class)

df_cbzs_elg %>% 
  ggplot(aes(x = class_id)) +
  geom_histogram(fill = "lightblue",
                 binwidth = 5,
                 color = NA) +
  scale_x_continuous(breaks = seq(0,100,10),
                     limits = c(-10,110)) +
  ylab("count") +
  geom_vline(xintercept = mean(df_cbzs_elg$class_id,na.rm = T),
             color = "black",
             linetype = "dashed",
             size = 1.1) +
  theme(panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.ticks = element_blank(),
        axis.line = element_line(color = "grey66"),
        axis.text.y = element_text(color = "black"),
        axis.text.x = element_text(color = "black",
                                   face = "bold"),
        axis.title.x = element_text(color = "black",
                                   face = "bold"))

Support for redistributive policy

Participants saw one of the following policies and indicated their support for it (1 = Strongly Oppose to 7 = Strongly Support)

Minimum wage increase

Congress has not increased the federal minimum wage, currently set at $7.25, since 2009. Some Congresspeople are proposing a policy that would gradually raise the federal minimum wage to $20 an hour by 2028. After 2028, the minimum wage would be adjusted each year to keep pace with growth in the median wage, a measure of wages for typical workers.

Student debt relief

Some Congresspeople are proposing a policy that would help to address the student loan debt crisis by forgiving up to $50,000 in loans per borrower. Approximately 42 million Americans, or about 1 in 6 American adults, owe a cumulative $1.6 trillion in student loans. Student loans are now the second-largest slice of household debt after mortgages, bigger than credit card debt.

Housing

Some Congresspeople are proposing a housing affordability policy that would help ensure that every American has a place to live. The policy would allow for smaller, lower cost homes like duplexes, townhouses, and garden apartments to be built and developed, allowing new nonprofit homes and reducing overall housing prices.

Climate change

Some Congresspeople are proposing a Green New Deal bill which would phase out the use of fossil fuels, with the government providing clean energy jobs for people who can’t find employment in the private sector. All jobs would pay at least $20 an hour, and include healthcare benefits and collective bargaining rights.

df_cbzs_elg %>% 
  ggplot(aes(x = support)) +
  geom_histogram(fill = "lightblue",
                 binwidth = 1,
                 color = NA) +
  scale_x_continuous(breaks = seq(1,7,1),
                     limits = c(0,8)) +
  ylab("count") +
  geom_vline(xintercept = mean(df_cbzs_elg$support,na.rm = T),
             color = "black",
             linetype = "dashed",
             size = 1.1) +
  theme(panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.ticks = element_blank(),
        axis.line = element_line(color = "grey66"),
        axis.text.y = element_text(color = "black"),
        axis.text.x = element_text(color = "black",
                                   face = "bold"),
        axis.title.x = element_text(color = "black",
                                   face = "bold"))

Correlations

Analysis Plan

Linear Model 1A

Predictor: CZSB

Outcome: Solidarity

m1 <- lm(soli ~ zs_class,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 4.49 [4.26, 4.73] 37.35 487 < .001 NA
Zs class 0.28 [0.23, 0.33] 11.71 487 < .001 0.22

Linear Model 1B

Predictor: CZSB

Outcome: Solidarity

Controls: Conservatism

m1 <- lm(soli ~ zs_class + ideo_con,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2_partial)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 4.84 [4.49, 5.19] 26.98 468 < .001 NA
Zs class 0.25 [0.20, 0.31] 9.36 468 < .001 0.241
Ideo con -0.06 [-0.10, -0.02] -2.85 468 .005 0.017

Linear Model 1C

Predictor: CZSB

Outcome: Solidarity

Controls: Conservatism + SDO + SJ

m1 <- lm(soli ~ zs_class + ideo_con + SDO + sj,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2_partial)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 6.25 [5.79, 6.70] 26.94 466 < .001 NA
Zs class 0.13 [0.08, 0.19] 4.62 466 < .001 0.278
Ideo con 0.04 [0.00, 0.08] 1.90 466 .058 0.021
SDO -0.23 [-0.28, -0.17] -7.77 466 < .001 0.141
Sj -0.17 [-0.24, -0.10] -4.79 466 < .001 0.047

Linear Model 1D

Predictor: CZSB

Outcome: Solidarity

Controls: Conservatism + SDO + SJ + Relative Deprivation + working-class disadvantage + ZS Mindset + Class Identification

m1 <- lm(soli ~ zs_class + ideo_con + SDO + sj + reldep + dis + zsm + class_id,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2_partial)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 4.30 [3.66, 4.93] 13.31 462 < .001 NA
Zs class 0.09 [0.03, 0.15] 2.92 462 .004 0.324
Ideo con 0.04 [0.00, 0.07] 1.84 462 .067 0.026
SDO -0.22 [-0.27, -0.16] -8.06 462 < .001 0.170
Sj -0.10 [-0.17, -0.04] -3.03 462 .003 0.058
Reldep -0.04 [-0.09, 0.02] -1.22 462 .223 0.000
Dis 0.28 [0.21, 0.35] 7.49 462 < .001 0.138
Zsm -0.03 [-0.09, 0.03] -0.95 462 .345 0.003
Class id 0.01 [0.01, 0.01] 6.26 462 < .001 0.078

Linear Model 1E

Predictor: CZSB

Outcome: Solidarity

Controls: Conservatism + SDO + SJ + Relative Deprivation + ZS Mindset + working-class disadvantage + Class Identification + income + education + gender (man) + race (white) + age

m1 <- lm(soli ~ zs_class + ideo_con + SDO + sj + reldep + dis + zsm + class_id + income_num + edu_num + man + white + age,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2_partial)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 4.01 [3.27, 4.74] 10.66 446 < .001 NA
Zs class 0.09 [0.03, 0.15] 2.91 446 .004 0.322
Ideo con 0.04 [0.00, 0.08] 2.16 446 .031 0.024
SDO -0.24 [-0.29, -0.18] -8.50 446 < .001 0.186
Sj -0.10 [-0.17, -0.03] -2.88 446 .004 0.060
Reldep -0.01 [-0.07, 0.05] -0.46 446 .647 0.000
Dis 0.28 [0.20, 0.35] 7.43 446 < .001 0.136
Zsm -0.03 [-0.09, 0.03] -1.07 446 .287 0.005
Class id 0.01 [0.01, 0.01] 6.59 446 < .001 0.078
Income num 0.03 [0.01, 0.06] 2.57 446 .011 0.016
Edu num 0.00 [-0.06, 0.06] 0.08 446 .936 0.000
Man 0.02 [-0.10, 0.14] 0.33 446 .740 0.000
White 0.01 [-0.13, 0.16] 0.21 446 .834 0.000
Age 0.00 [0.00, 0.01] 0.16 446 .876 0.000

Linear Model 2A

Predictor: CZSB

Outcome: Support for policy

m1 <- lm(support ~ zs_class,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 2.38 [1.89, 2.86] 9.64 487 < .001 NA
Zs class 0.61 [0.51, 0.70] 12.38 487 < .001 0.239

Linear Model 2B

Predictor: CZSB

Outcome: Support for policy

Controls: Conservatism

m1 <- lm(support ~ zs_class + ideo_con,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2_partial)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 4.19 [3.49, 4.88] 11.85 468 < .001 NA
Zs class 0.44 [0.34, 0.55] 8.29 468 < .001 0.276
Ideo con -0.29 [-0.37, -0.21] -7.25 468 < .001 0.101

Linear Model 2C

Predictor: CZSB

Outcome: Support for policy

Controls: Conservatism + SDO + SJ

m1 <- lm(support ~ zs_class + ideo_con + SDO + sj,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2_partial)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 4.92 [4.00, 5.84] 10.54 466 < .001 NA
Zs class 0.39 [0.28, 0.50] 6.78 466 < .001 0.307
Ideo con -0.20 [-0.29, -0.12] -4.83 466 < .001 0.116
SDO -0.51 [-0.62, -0.39] -8.69 466 < .001 0.132
Sj 0.17 [0.03, 0.30] 2.37 466 .018 0.012

Linear Model 2D

Predictor: CZSB

Outcome: Support for policy

Controls: Conservatism + SDO + SJ + Relative Deprivation + ZS Mindset + working-class disadvantage + Class Identification

m1 <- lm(support ~ zs_class + ideo_con + SDO + sj + reldep + dis + zsm + class_id,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2_partial)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 5.22 [3.81, 6.63] 7.29 462 < .001 NA
Zs class 0.30 [0.17, 0.43] 4.40 462 < .001 0.312
Ideo con -0.22 [-0.31, -0.14] -5.31 462 < .001 0.118
SDO -0.54 [-0.65, -0.42] -9.00 462 < .001 0.135
Sj 0.11 [-0.03, 0.26] 1.52 462 .130 0.012
Reldep -0.05 [-0.17, 0.08] -0.76 462 .446 0.000
Dis -0.05 [-0.21, 0.11] -0.59 462 .555 0.000
Zsm 0.15 [0.02, 0.28] 2.27 462 .024 0.010
Class id 0.01 [0.00, 0.01] 2.64 462 .009 0.015

Linear Model 2E

Predictor: CZSB

Outcome: Support for policy

Controls: Conservatism + SDO + SJ + Relative Deprivation + working-class disadvantage + ZS Mindset + Class Identification + income + education + gender (man) + race (white) + age

m1 <- lm(support ~ zs_class + ideo_con + SDO + sj + reldep + dis + zsm + class_id + income_num + edu_num + man + white + age,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2_partial)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 5.00 [3.37, 6.63] 6.04 446 < .001 NA
Zs class 0.30 [0.17, 0.43] 4.42 446 < .001 0.330
Ideo con -0.22 [-0.30, -0.14] -5.22 446 < .001 0.128
SDO -0.53 [-0.65, -0.41] -8.67 446 < .001 0.125
Sj 0.10 [-0.05, 0.25] 1.27 446 .204 0.008
Reldep 0.01 [-0.12, 0.14] 0.15 446 .884 0.000
Dis -0.03 [-0.19, 0.13] -0.39 446 .698 0.001
Zsm 0.14 [0.01, 0.27] 2.06 446 .040 0.010
Class id 0.01 [0.00, 0.01] 3.10 446 .002 0.017
Income num 0.05 [0.00, 0.11] 1.92 446 .055 0.010
Edu num -0.01 [-0.14, 0.13] -0.09 446 .925 0.000
Man 0.18 [-0.10, 0.45] 1.27 446 .204 0.005
White -0.15 [-0.46, 0.16] -0.96 446 .338 0.003
Age -0.01 [-0.02, 0.00] -1.37 446 .170 0.004

Mediation Model

Predictor: CZSB

Mediator: Solidarity

Outcome: Support for policy

# Formula for mediator model: soli ~ zs_class
form.m <- reformulate(c("zs_class"), response = "soli")

# Formula for outcome model: support ~ zs_class + crs
form.y <- reformulate(c("zs_class", "soli"), response = "support")

# Fit linear models
m.fit <- lm(form.m, data = df_cbzs_elg)        # a-path
y.fit <- lm(form.y, data = df_cbzs_elg)        # b and c'-paths

# Fit outcome model WITHOUT mediator to get c-path (total effect)
y.fit.total <- lm(
  reformulate(c("zs_class"), response = "support"),
  data = df_cbzs_elg
)

# Mediation analysis with bootstrapping (10,000 sims)
med.fit <- mediation::mediate(
  model.m   = m.fit,
  model.y   = y.fit,
  treat     = "zs_class",
  mediator  = "soli",
  boot      = TRUE,
  sims      = 10000
)

med_tbl <- tibble(
  Effect   = c("ACME (indirect)", "ADE (direct)",
               "Total Effect", "Prop. Mediated"),
  Estimate = c(med.fit$d0,        med.fit$z0,
               med.fit$tau.coef,  med.fit$n0),
  CI.lower = c(med.fit$d0.ci[1],  med.fit$z0.ci[1],
               med.fit$tau.ci[1], med.fit$n0.ci[1]),
  CI.upper = c(med.fit$d0.ci[2],  med.fit$z0.ci[2],
               med.fit$tau.ci[2], med.fit$n0.ci[2]),
  p.value  = c(med.fit$d0.p,      med.fit$z0.p,
               med.fit$tau.p,     med.fit$n0.p)
)

kbl(med_tbl) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
Effect Estimate CI.lower CI.upper p.value
ACME (indirect) 0.1426025 0.0860258 0.2029462 0
ADE (direct) 0.4632951 0.3386043 0.5852656 0
Total Effect 0.6058976 0.4975303 0.7084127 0
Prop. Mediated 0.2353574 0.1371336 0.3540627 0
# Helper to generate significance stars
p_stars <- function(p) {
  if (p < .001) return("***")
  if (p < .01)  return("**")
  if (p < .05)  return("*")
  return("")
}

# Extract coefficients & p-values
a_coef   <- coef(m.fit)["zs_class"]
a_p      <- summary(m.fit)$coefficients["zs_class", "Pr(>|t|)"]

b_coef   <- coef(y.fit)["soli"]
b_p      <- summary(y.fit)$coefficients["soli", "Pr(>|t|)"]

cprime   <- coef(y.fit)["zs_class"]
cprime_p <- summary(y.fit)$coefficients["zs_class", "Pr(>|t|)"]

c_total  <- coef(y.fit.total)["zs_class"]
c_total_p <- summary(y.fit.total)$coefficients["zs_class", "Pr(>|t|)"]

# Paste coefficient + stars
a_label      <- paste0("a = ", round(a_coef, 3), p_stars(a_p))
b_label      <- paste0("b = ", round(b_coef, 3), p_stars(b_p))
cprime_label <- paste0("c' = ", round(cprime, 3), p_stars(cprime_p))
c_label      <- paste0("c = ", round(c_total, 3), p_stars(c_total_p))

# Plot
ggplot() +
  xlim(0, 3) + ylim(0, 2) +

  # Nodes
  annotate("text", x = 0.5, y = 1,   label = "zs_class", fontface = "bold") +
  annotate("text", x = 1.5, y = 1,   label = "soli",     fontface = "bold") +
  annotate("text", x = 2.5, y = 1,   label = "support",  fontface = "bold") +

  # a-path (X → M)
  annotate("segment",
           x = 0.7, xend = 1.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.0, y = 1.15, label = a_label) +

  # b-path (M → Y)
  annotate("segment",
           x = 1.7, xend = 2.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 2.0, y = 1.15, label = b_label) +

  # c'-path (direct effect)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 0.9, yend = 0.9,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 0.75, label = cprime_label) +

  # c-path (total effect, dashed)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 1.1, yend = 1.1,
           linetype = "dashed",
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 1.25, label = c_label) +

  theme_void()

Exploratory analysis

Mediation model with controls

Model 1A

Predictor: CBZS

Mediator: Solidarity

Outcome: Support for Policy

Controls: Conservatism

Bootstraps: 10,000

# Vector of control variable names
controls <- c(
  "ideo_con")

# Formula for mediator model: soli ~ zs_class + controls
form.m <- reformulate(c("zs_class", controls), response = "soli")

# Formula for outcome model: support ~ zs_class + soli + controls
form.y <- reformulate(c("zs_class", "soli", controls), response = "support")

# Fit linear models
m.fit <- lm(form.m, data = df_cbzs_elg)        # a-path
y.fit <- lm(form.y, data = df_cbzs_elg)        # b and c'-paths

# Fit outcome model WITHOUT mediator to get c-path (total effect)
y.fit.total <- lm(
  reformulate(c("zs_class", controls), response = "support"),
  data = df_cbzs_elg
)

# Mediation analysis with bootstrapping (10,000 sims)
med.fit <- mediation::mediate(
  model.m   = m.fit,
  model.y   = y.fit,
  treat     = "zs_class",
  mediator  = "soli",
  boot      = TRUE,
  sims      = 10000
)

med_tbl <- tibble(
  Effect   = c("ACME (indirect)", "ADE (direct)",
               "Total Effect", "Prop. Mediated"),
  Estimate = c(med.fit$d0,        med.fit$z0,
               med.fit$tau.coef,  med.fit$n0),
  CI.lower = c(med.fit$d0.ci[1],  med.fit$z0.ci[1],
               med.fit$tau.ci[1], med.fit$n0.ci[1]),
  CI.upper = c(med.fit$d0.ci[2],  med.fit$z0.ci[2],
               med.fit$tau.ci[2], med.fit$n0.ci[2]),
  p.value  = c(med.fit$d0.p,      med.fit$z0.p,
               med.fit$tau.p,     med.fit$n0.p)
)

kbl(med_tbl) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
Effect Estimate CI.lower CI.upper p.value
ACME (indirect) 0.0988496 0.0459909 0.1564592 0
ADE (direct) 0.3431750 0.2195482 0.4673219 0
Total Effect 0.4420245 0.3276731 0.5569364 0
Prop. Mediated 0.2236291 0.1019088 0.3786827 0
# Helper to generate significance stars
p_stars <- function(p) {
  if (p < .001) return("***")
  if (p < .01)  return("**")
  if (p < .05)  return("*")
  return("")
}

# Extract coefficients & p-values
a_coef   <- coef(m.fit)["zs_class"]
a_p      <- summary(m.fit)$coefficients["zs_class", "Pr(>|t|)"]

b_coef   <- coef(y.fit)["soli"]
b_p      <- summary(y.fit)$coefficients["soli", "Pr(>|t|)"]

cprime   <- coef(y.fit)["zs_class"]
cprime_p <- summary(y.fit)$coefficients["zs_class", "Pr(>|t|)"]

c_total  <- coef(y.fit.total)["zs_class"]
c_total_p <- summary(y.fit.total)$coefficients["zs_class", "Pr(>|t|)"]

# Paste coefficient + stars
a_label      <- paste0("a = ", round(a_coef, 3), p_stars(a_p))
b_label      <- paste0("b = ", round(b_coef, 3), p_stars(b_p))
cprime_label <- paste0("c' = ", round(cprime, 3), p_stars(cprime_p))
c_label      <- paste0("c = ", round(c_total, 3), p_stars(c_total_p))

# Plot
ggplot() +
  xlim(0, 3) + ylim(0, 2) +

  # Nodes
  annotate("text", x = 0.5, y = 1,   label = "zs_class", fontface = "bold") +
  annotate("text", x = 1.5, y = 1,   label = "soli",     fontface = "bold") +
  annotate("text", x = 2.5, y = 1,   label = "support",  fontface = "bold") +

  # a-path (X → M)
  annotate("segment",
           x = 0.7, xend = 1.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.0, y = 1.15, label = a_label) +

  # b-path (M → Y)
  annotate("segment",
           x = 1.7, xend = 2.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 2.0, y = 1.15, label = b_label) +

  # c'-path (direct effect)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 0.9, yend = 0.9,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 0.75, label = cprime_label) +

  # c-path (total effect, dashed)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 1.1, yend = 1.1,
           linetype = "dashed",
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 1.25, label = c_label) +

  theme_void()

Model 1B

Predictor: CBZS

Mediator: Solidarity

Outcome: Support for Policy

Controls: SDO

Bootstraps: 10,000

# Vector of control variable names
controls <- c(
  "SDO")

# Formula for mediator model: soli ~ zs_class + controls
form.m <- reformulate(c("zs_class", controls), response = "soli")

# Formula for outcome model: support ~ zs_class + soli + controls
form.y <- reformulate(c("zs_class", "soli", controls), response = "support")

# Fit linear models
m.fit <- lm(form.m, data = df_cbzs_elg)        # a-path
y.fit <- lm(form.y, data = df_cbzs_elg)        # b and c'-paths

# Fit outcome model WITHOUT mediator to get c-path (total effect)
y.fit.total <- lm(
  reformulate(c("zs_class", controls), response = "support"),
  data = df_cbzs_elg
)

# Mediation analysis with bootstrapping (10,000 sims)
med.fit <- mediation::mediate(
  model.m   = m.fit,
  model.y   = y.fit,
  treat     = "zs_class",
  mediator  = "soli",
  boot      = TRUE,
  sims      = 10000
)

med_tbl <- tibble(
  Effect   = c("ACME (indirect)", "ADE (direct)",
               "Total Effect", "Prop. Mediated"),
  Estimate = c(med.fit$d0,        med.fit$z0,
               med.fit$tau.coef,  med.fit$n0),
  CI.lower = c(med.fit$d0.ci[1],  med.fit$z0.ci[1],
               med.fit$tau.ci[1], med.fit$n0.ci[1]),
  CI.upper = c(med.fit$d0.ci[2],  med.fit$z0.ci[2],
               med.fit$tau.ci[2], med.fit$n0.ci[2]),
  p.value  = c(med.fit$d0.p,      med.fit$z0.p,
               med.fit$tau.p,     med.fit$n0.p)
)

kbl(med_tbl) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
Effect Estimate CI.lower CI.upper p.value
ACME (indirect) 0.0305636 -0.0069000 0.0701412 0.1044
ADE (direct) 0.3526458 0.2417029 0.4669502 0.0000
Total Effect 0.3832094 0.2780117 0.4903993 0.0000
Prop. Mediated 0.0797570 -0.0180467 0.1962274 0.1044
# Helper to generate significance stars
p_stars <- function(p) {
  if (p < .001) return("***")
  if (p < .01)  return("**")
  if (p < .05)  return("*")
  return("")
}

# Extract coefficients & p-values
a_coef   <- coef(m.fit)["zs_class"]
a_p      <- summary(m.fit)$coefficients["zs_class", "Pr(>|t|)"]

b_coef   <- coef(y.fit)["soli"]
b_p      <- summary(y.fit)$coefficients["soli", "Pr(>|t|)"]

cprime   <- coef(y.fit)["zs_class"]
cprime_p <- summary(y.fit)$coefficients["zs_class", "Pr(>|t|)"]

c_total  <- coef(y.fit.total)["zs_class"]
c_total_p <- summary(y.fit.total)$coefficients["zs_class", "Pr(>|t|)"]

# Paste coefficient + stars
a_label      <- paste0("a = ", round(a_coef, 3), p_stars(a_p))
b_label      <- paste0("b = ", round(b_coef, 3), p_stars(b_p))
cprime_label <- paste0("c' = ", round(cprime, 3), p_stars(cprime_p))
c_label      <- paste0("c = ", round(c_total, 3), p_stars(c_total_p))

# Plot
ggplot() +
  xlim(0, 3) + ylim(0, 2) +

  # Nodes
  annotate("text", x = 0.5, y = 1,   label = "zs_class", fontface = "bold") +
  annotate("text", x = 1.5, y = 1,   label = "soli",     fontface = "bold") +
  annotate("text", x = 2.5, y = 1,   label = "support",  fontface = "bold") +

  # a-path (X → M)
  annotate("segment",
           x = 0.7, xend = 1.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.0, y = 1.15, label = a_label) +

  # b-path (M → Y)
  annotate("segment",
           x = 1.7, xend = 2.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 2.0, y = 1.15, label = b_label) +

  # c'-path (direct effect)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 0.9, yend = 0.9,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 0.75, label = cprime_label) +

  # c-path (total effect, dashed)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 1.1, yend = 1.1,
           linetype = "dashed",
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 1.25, label = c_label) +

  theme_void()

Model 1C

Predictor: CBZS

Mediator: Solidarity

Outcome: Support for Policy

Controls: Conservatism, SDO, system justification

Bootstraps: 10,000

# Vector of control variable names
controls <- c(
  "ideo_con", "SDO", "sj"
)

# Formula for mediator model: soli ~ zs_class + controls
form.m <- reformulate(c("zs_class", controls), response = "soli")

# Formula for outcome model: support ~ zs_class + soli + controls
form.y <- reformulate(c("zs_class", "soli", controls), response = "support")

# Fit linear models
m.fit <- lm(form.m, data = df_cbzs_elg)        # a-path
y.fit <- lm(form.y, data = df_cbzs_elg)        # b and c'-paths

# Fit outcome model WITHOUT mediator to get c-path (total effect)
y.fit.total <- lm(
  reformulate(c("zs_class", controls), response = "support"),
  data = df_cbzs_elg
)

# Mediation analysis with bootstrapping (10,000 sims)
med.fit <- mediation::mediate(
  model.m   = m.fit,
  model.y   = y.fit,
  treat     = "zs_class",
  mediator  = "soli",
  boot      = TRUE,
  sims      = 10000
)

med_tbl <- tibble(
  Effect   = c("ACME (indirect)", "ADE (direct)",
               "Total Effect", "Prop. Mediated"),
  Estimate = c(med.fit$d0,        med.fit$z0,
               med.fit$tau.coef,  med.fit$n0),
  CI.lower = c(med.fit$d0.ci[1],  med.fit$z0.ci[1],
               med.fit$tau.ci[1], med.fit$n0.ci[1]),
  CI.upper = c(med.fit$d0.ci[2],  med.fit$z0.ci[2],
               med.fit$tau.ci[2], med.fit$n0.ci[2]),
  p.value  = c(med.fit$d0.p,      med.fit$z0.p,
               med.fit$tau.p,     med.fit$n0.p)
)

kbl(med_tbl) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
Effect Estimate CI.lower CI.upper p.value
ACME (indirect) 0.0271843 -0.0009831 0.0627197 0.0584
ADE (direct) 0.3633838 0.2378192 0.4846063 0.0000
Total Effect 0.3905681 0.2640509 0.5109390 0.0000
Prop. Mediated 0.0696020 -0.0023610 0.1676861 0.0584
# Helper to generate significance stars
p_stars <- function(p) {
  if (p < .001) return("***")
  if (p < .01)  return("**")
  if (p < .05)  return("*")
  return("")
}

# Extract coefficients & p-values
a_coef   <- coef(m.fit)["zs_class"]
a_p      <- summary(m.fit)$coefficients["zs_class", "Pr(>|t|)"]

b_coef   <- coef(y.fit)["soli"]
b_p      <- summary(y.fit)$coefficients["soli", "Pr(>|t|)"]

cprime   <- coef(y.fit)["zs_class"]
cprime_p <- summary(y.fit)$coefficients["zs_class", "Pr(>|t|)"]

c_total  <- coef(y.fit.total)["zs_class"]
c_total_p <- summary(y.fit.total)$coefficients["zs_class", "Pr(>|t|)"]

# Paste coefficient + stars
a_label      <- paste0("a = ", round(a_coef, 3), p_stars(a_p))
b_label      <- paste0("b = ", round(b_coef, 3), p_stars(b_p))
cprime_label <- paste0("c' = ", round(cprime, 3), p_stars(cprime_p))
c_label      <- paste0("c = ", round(c_total, 3), p_stars(c_total_p))

# Plot
ggplot() +
  xlim(0, 3) + ylim(0, 2) +

  # Nodes
  annotate("text", x = 0.5, y = 1,   label = "zs_class", fontface = "bold") +
  annotate("text", x = 1.5, y = 1,   label = "soli",     fontface = "bold") +
  annotate("text", x = 2.5, y = 1,   label = "support",  fontface = "bold") +

  # a-path (X → M)
  annotate("segment",
           x = 0.7, xend = 1.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.0, y = 1.15, label = a_label) +

  # b-path (M → Y)
  annotate("segment",
           x = 1.7, xend = 2.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 2.0, y = 1.15, label = b_label) +

  # c'-path (direct effect)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 0.9, yend = 0.9,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 0.75, label = cprime_label) +

  # c-path (total effect, dashed)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 1.1, yend = 1.1,
           linetype = "dashed",
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 1.25, label = c_label) +

  theme_void()

Model 1D

Predictor: CBZS

Mediator: Solidarity

Outcome: Support for Policy

Controls: Conservatism, SDO, system justification, relative deprivation, working-class disadvantage, zero-sum mindset, working-class identification

Bootstraps: 10,000

# Vector of control variable names
controls <- c(
  "ideo_con", "SDO", "sj", "reldep", "dis", "zsm","class_id"
)

# Formula for mediator model: soli ~ zs_class + controls
form.m <- reformulate(c("zs_class", controls), response = "soli")

# Formula for outcome model: support ~ zs_class + soli + controls
form.y <- reformulate(c("zs_class", "soli", controls), response = "support")

# Fit linear models
m.fit <- lm(form.m, data = df_cbzs_elg)        # a-path
y.fit <- lm(form.y, data = df_cbzs_elg)        # b and c'-paths

# Fit outcome model WITHOUT mediator to get c-path (total effect)
y.fit.total <- lm(
  reformulate(c("zs_class", controls), response = "support"),
  data = df_cbzs_elg
)

# Mediation analysis with bootstrapping (10,000 sims)
med.fit <- mediation::mediate(
  model.m   = m.fit,
  model.y   = y.fit,
  treat     = "zs_class",
  mediator  = "soli",
  boot      = TRUE,
  sims      = 10000
)

med_tbl <- tibble(
  Effect   = c("ACME (indirect)", "ADE (direct)",
               "Total Effect", "Prop. Mediated"),
  Estimate = c(med.fit$d0,        med.fit$z0,
               med.fit$tau.coef,  med.fit$n0),
  CI.lower = c(med.fit$d0.ci[1],  med.fit$z0.ci[1],
               med.fit$tau.ci[1], med.fit$n0.ci[1]),
  CI.upper = c(med.fit$d0.ci[2],  med.fit$z0.ci[2],
               med.fit$tau.ci[2], med.fit$n0.ci[2]),
  p.value  = c(med.fit$d0.p,      med.fit$z0.p,
               med.fit$tau.p,     med.fit$n0.p)
)

kbl(med_tbl) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
Effect Estimate CI.lower CI.upper p.value
ACME (indirect) 0.0184748 -0.0018239 0.0496931 0.0818
ADE (direct) 0.2806489 0.1410217 0.4150254 0.0000
Total Effect 0.2991236 0.1601699 0.4327983 0.0000
Prop. Mediated 0.0617630 -0.0064893 0.1887597 0.0818
# Helper to generate significance stars
p_stars <- function(p) {
  if (p < .001) return("***")
  if (p < .01)  return("**")
  if (p < .05)  return("*")
  return("")
}

# Extract coefficients & p-values
a_coef   <- coef(m.fit)["zs_class"]
a_p      <- summary(m.fit)$coefficients["zs_class", "Pr(>|t|)"]

b_coef   <- coef(y.fit)["soli"]
b_p      <- summary(y.fit)$coefficients["soli", "Pr(>|t|)"]

cprime   <- coef(y.fit)["zs_class"]
cprime_p <- summary(y.fit)$coefficients["zs_class", "Pr(>|t|)"]

c_total  <- coef(y.fit.total)["zs_class"]
c_total_p <- summary(y.fit.total)$coefficients["zs_class", "Pr(>|t|)"]

# Paste coefficient + stars
a_label      <- paste0("a = ", round(a_coef, 3), p_stars(a_p))
b_label      <- paste0("b = ", round(b_coef, 3), p_stars(b_p))
cprime_label <- paste0("c' = ", round(cprime, 3), p_stars(cprime_p))
c_label      <- paste0("c = ", round(c_total, 3), p_stars(c_total_p))

# Plot
ggplot() +
  xlim(0, 3) + ylim(0, 2) +

  # Nodes
  annotate("text", x = 0.5, y = 1,   label = "zs_class", fontface = "bold") +
  annotate("text", x = 1.5, y = 1,   label = "soli",     fontface = "bold") +
  annotate("text", x = 2.5, y = 1,   label = "support",  fontface = "bold") +

  # a-path (X → M)
  annotate("segment",
           x = 0.7, xend = 1.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.0, y = 1.15, label = a_label) +

  # b-path (M → Y)
  annotate("segment",
           x = 1.7, xend = 2.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 2.0, y = 1.15, label = b_label) +

  # c'-path (direct effect)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 0.9, yend = 0.9,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 0.75, label = cprime_label) +

  # c-path (total effect, dashed)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 1.1, yend = 1.1,
           linetype = "dashed",
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 1.25, label = c_label) +

  theme_void()

Model 1E

Predictor: CBZS

Mediator: Solidarity

Outcome: Support for Policy

Controls: Conservatism, SDO, system justification, relative deprivation, working-class disadvantage, zero-sum mindset, working-class identification, income, education, gender (man = 1, non-man = 0), race (white = 1, non-white = 0), age

Bootstraps: 10,000

# Vector of control variable names
controls <- c(
  "ideo_con", "SDO", "sj", "reldep", "dis", "zsm","class_id",
  "income_num", "edu_num", "man", "white", "age"
)

# Formula for mediator model: soli ~ zs_class + controls
form.m <- reformulate(c("zs_class", controls), response = "soli")

# Formula for outcome model: support ~ zs_class + soli + controls
form.y <- reformulate(c("zs_class", "soli", controls), response = "support")

# Fit linear models
m.fit <- lm(form.m, data = df_cbzs_elg)        # a-path
y.fit <- lm(form.y, data = df_cbzs_elg)        # b and c'-paths

# Fit outcome model WITHOUT mediator to get c-path (total effect)
y.fit.total <- lm(
  reformulate(c("zs_class", controls), response = "support"),
  data = df_cbzs_elg
)

# Mediation analysis with bootstrapping (10,000 sims)
med.fit <- mediation::mediate(
  model.m   = m.fit,
  model.y   = y.fit,
  treat     = "zs_class",
  mediator  = "soli",
  boot      = TRUE,
  sims      = 10000
)

med_tbl <- tibble(
  Effect   = c("ACME (indirect)", "ADE (direct)",
               "Total Effect", "Prop. Mediated"),
  Estimate = c(med.fit$d0,        med.fit$z0,
               med.fit$tau.coef,  med.fit$n0),
  CI.lower = c(med.fit$d0.ci[1],  med.fit$z0.ci[1],
               med.fit$tau.ci[1], med.fit$n0.ci[1]),
  CI.upper = c(med.fit$d0.ci[2],  med.fit$z0.ci[2],
               med.fit$tau.ci[2], med.fit$n0.ci[2]),
  p.value  = c(med.fit$d0.p,      med.fit$z0.p,
               med.fit$tau.p,     med.fit$n0.p)
)

kbl(med_tbl) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
Effect Estimate CI.lower CI.upper p.value
ACME (indirect) 0.0170647 -0.0028456 0.0475594 0.1062
ADE (direct) 0.2820850 0.1421427 0.4157788 0.0002
Total Effect 0.2991498 0.1577548 0.4328861 0.0000
Prop. Mediated 0.0570442 -0.0095370 0.1787894 0.1062
# Helper to generate significance stars
p_stars <- function(p) {
  if (p < .001) return("***")
  if (p < .01)  return("**")
  if (p < .05)  return("*")
  return("")
}

# Extract coefficients & p-values
a_coef   <- coef(m.fit)["zs_class"]
a_p      <- summary(m.fit)$coefficients["zs_class", "Pr(>|t|)"]

b_coef   <- coef(y.fit)["soli"]
b_p      <- summary(y.fit)$coefficients["soli", "Pr(>|t|)"]

cprime   <- coef(y.fit)["zs_class"]
cprime_p <- summary(y.fit)$coefficients["zs_class", "Pr(>|t|)"]

c_total  <- coef(y.fit.total)["zs_class"]
c_total_p <- summary(y.fit.total)$coefficients["zs_class", "Pr(>|t|)"]

# Paste coefficient + stars
a_label      <- paste0("a = ", round(a_coef, 3), p_stars(a_p))
b_label      <- paste0("b = ", round(b_coef, 3), p_stars(b_p))
cprime_label <- paste0("c' = ", round(cprime, 3), p_stars(cprime_p))
c_label      <- paste0("c = ", round(c_total, 3), p_stars(c_total_p))

# Plot
ggplot() +
  xlim(0, 3) + ylim(0, 2) +

  # Nodes
  annotate("text", x = 0.5, y = 1,   label = "zs_class", fontface = "bold") +
  annotate("text", x = 1.5, y = 1,   label = "soli",     fontface = "bold") +
  annotate("text", x = 2.5, y = 1,   label = "support",  fontface = "bold") +

  # a-path (X → M)
  annotate("segment",
           x = 0.7, xend = 1.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.0, y = 1.15, label = a_label) +

  # b-path (M → Y)
  annotate("segment",
           x = 1.7, xend = 2.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 2.0, y = 1.15, label = b_label) +

  # c'-path (direct effect)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 0.9, yend = 0.9,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 0.75, label = cprime_label) +

  # c-path (total effect, dashed)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 1.1, yend = 1.1,
           linetype = "dashed",
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 1.25, label = c_label) +

  theme_void()

Cross-Race Solidarity as DV

Model 1

m1 <- lm(crs ~ zs_class,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 3.76 [3.35, 4.16] 18.28 485 < .001 NA
Zs class 0.21 [0.13, 0.29] 5.14 485 < .001 0.052

Model 2

m1 <- lm(crs ~ zs_class + ideo_con,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2_partial)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 3.71 [3.10, 4.32] 11.90 466 < .001 NA
Zs class 0.22 [0.13, 0.31] 4.73 466 < .001 0.059
Ideo con 0.00 [-0.07, 0.07] -0.05 466 .960 0.000

Model 3

m1 <- lm(crs ~ zs_class + ideo_con + SDO + sj,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2_partial)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 4.45 [3.61, 5.29] 10.43 464 < .001 NA
Zs class 0.16 [0.06, 0.27] 3.13 464 .002 0.064
Ideo con 0.07 [-0.01, 0.14] 1.75 464 .080 0.000
SDO -0.34 [-0.45, -0.24] -6.34 464 < .001 0.078
Sj 0.06 [-0.07, 0.18] 0.91 464 .362 0.002

Model 4

m1 <- lm(crs ~ zs_class + ideo_con + SDO + sj + reldep + dis + zsm + class_id,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2_partial)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 4.07 [2.82, 5.33] 6.37 460 < .001 NA
Zs class 0.20 [0.08, 0.32] 3.33 460 < .001 0.068
Ideo con 0.06 [-0.01, 0.14] 1.65 460 .100 0.000
SDO -0.31 [-0.42, -0.21] -5.84 460 < .001 0.083
Sj 0.05 [-0.08, 0.19] 0.80 460 .427 0.002
Reldep -0.11 [-0.22, 0.00] -1.95 460 .052 0.004
Dis 0.04 [-0.11, 0.18] 0.51 460 .608 0.004
Zsm -0.13 [-0.25, -0.02] -2.24 460 .026 0.012
Class id 0.01 [0.01, 0.02] 4.81 460 < .001 0.048

Model 5

m1 <- lm(crs ~ zs_class + ideo_con + SDO + sj + reldep + dis + zsm + class_id + income_num + edu_num + man + white + age,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2_partial)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 4.15 [2.67, 5.63] 5.52 444 < .001 NA
Zs class 0.19 [0.07, 0.31] 3.05 444 .002 0.067
Ideo con 0.06 [-0.02, 0.13] 1.51 444 .131 0.000
SDO -0.30 [-0.41, -0.19] -5.42 444 < .001 0.073
Sj 0.04 [-0.09, 0.18] 0.62 444 .535 0.001
Reldep -0.10 [-0.22, 0.02] -1.67 444 .095 0.004
Dis 0.04 [-0.11, 0.19] 0.56 444 .575 0.004
Zsm -0.12 [-0.24, 0.00] -1.96 444 .051 0.012
Class id 0.01 [0.01, 0.02] 4.66 444 < .001 0.047
Income num 0.02 [-0.03, 0.07] 0.64 444 .524 0.000
Edu num -0.04 [-0.16, 0.08] -0.61 444 .540 0.001
Man 0.07 [-0.18, 0.31] 0.53 444 .598 0.001
White 0.19 [-0.09, 0.48] 1.35 444 .179 0.003
Age 0.00 [-0.01, 0.01] -0.94 444 .346 0.002

Mediation models

Model 1A

Predictor: CBZS

Mediator: Cross-Race Solidarity

Outcome: Support for Policy

Controls: Conservatism

Bootstraps: 10,000

# Vector of control variable names
controls <- c(
  "ideo_con")

# Formula for mediator model: soli ~ zs_class + controls
form.m <- reformulate(c("zs_class", controls), response = "soli")

# Formula for outcome model: support ~ zs_class + soli + controls
form.y <- reformulate(c("zs_class", "soli", controls), response = "support")

# Fit linear models
m.fit <- lm(form.m, data = df_cbzs_elg)        # a-path
y.fit <- lm(form.y, data = df_cbzs_elg)        # b and c'-paths

# Fit outcome model WITHOUT mediator to get c-path (total effect)
y.fit.total <- lm(
  reformulate(c("zs_class", controls), response = "support"),
  data = df_cbzs_elg
)

# Mediation analysis with bootstrapping (10,000 sims)
med.fit <- mediation::mediate(
  model.m   = m.fit,
  model.y   = y.fit,
  treat     = "zs_class",
  mediator  = "soli",
  boot      = TRUE,
  sims      = 10000
)

med_tbl <- tibble(
  Effect   = c("ACME (indirect)", "ADE (direct)",
               "Total Effect", "Prop. Mediated"),
  Estimate = c(med.fit$d0,        med.fit$z0,
               med.fit$tau.coef,  med.fit$n0),
  CI.lower = c(med.fit$d0.ci[1],  med.fit$z0.ci[1],
               med.fit$tau.ci[1], med.fit$n0.ci[1]),
  CI.upper = c(med.fit$d0.ci[2],  med.fit$z0.ci[2],
               med.fit$tau.ci[2], med.fit$n0.ci[2]),
  p.value  = c(med.fit$d0.p,      med.fit$z0.p,
               med.fit$tau.p,     med.fit$n0.p)
)

kbl(med_tbl) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
Effect Estimate CI.lower CI.upper p.value
ACME (indirect) 0.0988496 0.0456802 0.1566279 0
ADE (direct) 0.3431750 0.2178696 0.4663565 0
Total Effect 0.4420245 0.3272378 0.5563905 0
Prop. Mediated 0.2236291 0.1005454 0.3832946 0
# Helper to generate significance stars
p_stars <- function(p) {
  if (p < .001) return("***")
  if (p < .01)  return("**")
  if (p < .05)  return("*")
  return("")
}

# Extract coefficients & p-values
a_coef   <- coef(m.fit)["zs_class"]
a_p      <- summary(m.fit)$coefficients["zs_class", "Pr(>|t|)"]

b_coef   <- coef(y.fit)["soli"]
b_p      <- summary(y.fit)$coefficients["soli", "Pr(>|t|)"]

cprime   <- coef(y.fit)["zs_class"]
cprime_p <- summary(y.fit)$coefficients["zs_class", "Pr(>|t|)"]

c_total  <- coef(y.fit.total)["zs_class"]
c_total_p <- summary(y.fit.total)$coefficients["zs_class", "Pr(>|t|)"]

# Paste coefficient + stars
a_label      <- paste0("a = ", round(a_coef, 3), p_stars(a_p))
b_label      <- paste0("b = ", round(b_coef, 3), p_stars(b_p))
cprime_label <- paste0("c' = ", round(cprime, 3), p_stars(cprime_p))
c_label      <- paste0("c = ", round(c_total, 3), p_stars(c_total_p))

# Plot
ggplot() +
  xlim(0, 3) + ylim(0, 2) +

  # Nodes
  annotate("text", x = 0.5, y = 1,   label = "zs_class", fontface = "bold") +
  annotate("text", x = 1.5, y = 1,   label = "soli",     fontface = "bold") +
  annotate("text", x = 2.5, y = 1,   label = "support",  fontface = "bold") +

  # a-path (X → M)
  annotate("segment",
           x = 0.7, xend = 1.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.0, y = 1.15, label = a_label) +

  # b-path (M → Y)
  annotate("segment",
           x = 1.7, xend = 2.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 2.0, y = 1.15, label = b_label) +

  # c'-path (direct effect)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 0.9, yend = 0.9,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 0.75, label = cprime_label) +

  # c-path (total effect, dashed)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 1.1, yend = 1.1,
           linetype = "dashed",
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 1.25, label = c_label) +

  theme_void()

Model 2B

Predictor: CBZS

Mediator: Cross-Race Solidarity

Outcome: Support for Policy

Controls: SDO

Bootstraps: 10,000

# Vector of control variable names
controls <- c(
  "SDO")

# Formula for mediator model: soli ~ zs_class + controls
form.m <- reformulate(c("zs_class", controls), response = "soli")

# Formula for outcome model: support ~ zs_class + soli + controls
form.y <- reformulate(c("zs_class", "soli", controls), response = "support")

# Fit linear models
m.fit <- lm(form.m, data = df_cbzs_elg)        # a-path
y.fit <- lm(form.y, data = df_cbzs_elg)        # b and c'-paths

# Fit outcome model WITHOUT mediator to get c-path (total effect)
y.fit.total <- lm(
  reformulate(c("zs_class", controls), response = "support"),
  data = df_cbzs_elg
)

# Mediation analysis with bootstrapping (10,000 sims)
med.fit <- mediation::mediate(
  model.m   = m.fit,
  model.y   = y.fit,
  treat     = "zs_class",
  mediator  = "soli",
  boot      = TRUE,
  sims      = 10000
)

med_tbl <- tibble(
  Effect   = c("ACME (indirect)", "ADE (direct)",
               "Total Effect", "Prop. Mediated"),
  Estimate = c(med.fit$d0,        med.fit$z0,
               med.fit$tau.coef,  med.fit$n0),
  CI.lower = c(med.fit$d0.ci[1],  med.fit$z0.ci[1],
               med.fit$tau.ci[1], med.fit$n0.ci[1]),
  CI.upper = c(med.fit$d0.ci[2],  med.fit$z0.ci[2],
               med.fit$tau.ci[2], med.fit$n0.ci[2]),
  p.value  = c(med.fit$d0.p,      med.fit$z0.p,
               med.fit$tau.p,     med.fit$n0.p)
)

kbl(med_tbl) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
Effect Estimate CI.lower CI.upper p.value
ACME (indirect) 0.0305636 -0.0071155 0.0689741 0.1102
ADE (direct) 0.3526458 0.2387881 0.4681965 0.0000
Total Effect 0.3832094 0.2729769 0.4921009 0.0000
Prop. Mediated 0.0797570 -0.0190748 0.1950716 0.1102
# Helper to generate significance stars
p_stars <- function(p) {
  if (p < .001) return("***")
  if (p < .01)  return("**")
  if (p < .05)  return("*")
  return("")
}

# Extract coefficients & p-values
a_coef   <- coef(m.fit)["zs_class"]
a_p      <- summary(m.fit)$coefficients["zs_class", "Pr(>|t|)"]

b_coef   <- coef(y.fit)["soli"]
b_p      <- summary(y.fit)$coefficients["soli", "Pr(>|t|)"]

cprime   <- coef(y.fit)["zs_class"]
cprime_p <- summary(y.fit)$coefficients["zs_class", "Pr(>|t|)"]

c_total  <- coef(y.fit.total)["zs_class"]
c_total_p <- summary(y.fit.total)$coefficients["zs_class", "Pr(>|t|)"]

# Paste coefficient + stars
a_label      <- paste0("a = ", round(a_coef, 3), p_stars(a_p))
b_label      <- paste0("b = ", round(b_coef, 3), p_stars(b_p))
cprime_label <- paste0("c' = ", round(cprime, 3), p_stars(cprime_p))
c_label      <- paste0("c = ", round(c_total, 3), p_stars(c_total_p))

# Plot
ggplot() +
  xlim(0, 3) + ylim(0, 2) +

  # Nodes
  annotate("text", x = 0.5, y = 1,   label = "zs_class", fontface = "bold") +
  annotate("text", x = 1.5, y = 1,   label = "soli",     fontface = "bold") +
  annotate("text", x = 2.5, y = 1,   label = "support",  fontface = "bold") +

  # a-path (X → M)
  annotate("segment",
           x = 0.7, xend = 1.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.0, y = 1.15, label = a_label) +

  # b-path (M → Y)
  annotate("segment",
           x = 1.7, xend = 2.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 2.0, y = 1.15, label = b_label) +

  # c'-path (direct effect)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 0.9, yend = 0.9,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 0.75, label = cprime_label) +

  # c-path (total effect, dashed)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 1.1, yend = 1.1,
           linetype = "dashed",
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 1.25, label = c_label) +

  theme_void()

Model 2C

Predictor: CBZS

Mediator: Cross-Race Solidarity

Outcome: Support for Policy

Controls: Conservatism, SDO, system justification

Bootstraps: 10,000

# Vector of control variable names
controls <- c(
  "ideo_con", "SDO", "sj"
)

# Formula for mediator model: soli ~ zs_class + controls
form.m <- reformulate(c("zs_class", controls), response = "soli")

# Formula for outcome model: support ~ zs_class + soli + controls
form.y <- reformulate(c("zs_class", "soli", controls), response = "support")

# Fit linear models
m.fit <- lm(form.m, data = df_cbzs_elg)        # a-path
y.fit <- lm(form.y, data = df_cbzs_elg)        # b and c'-paths

# Fit outcome model WITHOUT mediator to get c-path (total effect)
y.fit.total <- lm(
  reformulate(c("zs_class", controls), response = "support"),
  data = df_cbzs_elg
)

# Mediation analysis with bootstrapping (10,000 sims)
med.fit <- mediation::mediate(
  model.m   = m.fit,
  model.y   = y.fit,
  treat     = "zs_class",
  mediator  = "soli",
  boot      = TRUE,
  sims      = 10000
)

med_tbl <- tibble(
  Effect   = c("ACME (indirect)", "ADE (direct)",
               "Total Effect", "Prop. Mediated"),
  Estimate = c(med.fit$d0,        med.fit$z0,
               med.fit$tau.coef,  med.fit$n0),
  CI.lower = c(med.fit$d0.ci[1],  med.fit$z0.ci[1],
               med.fit$tau.ci[1], med.fit$n0.ci[1]),
  CI.upper = c(med.fit$d0.ci[2],  med.fit$z0.ci[2],
               med.fit$tau.ci[2], med.fit$n0.ci[2]),
  p.value  = c(med.fit$d0.p,      med.fit$z0.p,
               med.fit$tau.p,     med.fit$n0.p)
)

kbl(med_tbl) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
Effect Estimate CI.lower CI.upper p.value
ACME (indirect) 0.0271843 -0.0013203 0.0624436 0.0638
ADE (direct) 0.3633838 0.2374156 0.4858094 0.0000
Total Effect 0.3905681 0.2666996 0.5140382 0.0000
Prop. Mediated 0.0696020 -0.0036410 0.1670070 0.0638
# Helper to generate significance stars
p_stars <- function(p) {
  if (p < .001) return("***")
  if (p < .01)  return("**")
  if (p < .05)  return("*")
  return("")
}

# Extract coefficients & p-values
a_coef   <- coef(m.fit)["zs_class"]
a_p      <- summary(m.fit)$coefficients["zs_class", "Pr(>|t|)"]

b_coef   <- coef(y.fit)["soli"]
b_p      <- summary(y.fit)$coefficients["soli", "Pr(>|t|)"]

cprime   <- coef(y.fit)["zs_class"]
cprime_p <- summary(y.fit)$coefficients["zs_class", "Pr(>|t|)"]

c_total  <- coef(y.fit.total)["zs_class"]
c_total_p <- summary(y.fit.total)$coefficients["zs_class", "Pr(>|t|)"]

# Paste coefficient + stars
a_label      <- paste0("a = ", round(a_coef, 3), p_stars(a_p))
b_label      <- paste0("b = ", round(b_coef, 3), p_stars(b_p))
cprime_label <- paste0("c' = ", round(cprime, 3), p_stars(cprime_p))
c_label      <- paste0("c = ", round(c_total, 3), p_stars(c_total_p))

# Plot
ggplot() +
  xlim(0, 3) + ylim(0, 2) +

  # Nodes
  annotate("text", x = 0.5, y = 1,   label = "zs_class", fontface = "bold") +
  annotate("text", x = 1.5, y = 1,   label = "soli",     fontface = "bold") +
  annotate("text", x = 2.5, y = 1,   label = "support",  fontface = "bold") +

  # a-path (X → M)
  annotate("segment",
           x = 0.7, xend = 1.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.0, y = 1.15, label = a_label) +

  # b-path (M → Y)
  annotate("segment",
           x = 1.7, xend = 2.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 2.0, y = 1.15, label = b_label) +

  # c'-path (direct effect)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 0.9, yend = 0.9,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 0.75, label = cprime_label) +

  # c-path (total effect, dashed)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 1.1, yend = 1.1,
           linetype = "dashed",
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 1.25, label = c_label) +

  theme_void()

Model 2D

Predictor: CBZS

Mediator: Cross-Race Solidarity

Outcome: Support for Policy

Controls: Conservatism, SDO, system justification, relative deprivation, working-class disadvantage, zero-sum mindset, working-class identification

Bootstraps: 10,000

# Vector of control variable names
controls <- c(
  "ideo_con", "SDO", "sj", "reldep", "dis", "zsm", "class_id"
)

# Formula for mediator model: soli ~ zs_class + controls
form.m <- reformulate(c("zs_class", controls), response = "soli")

# Formula for outcome model: support ~ zs_class + soli + controls
form.y <- reformulate(c("zs_class", "soli", controls), response = "support")

# Fit linear models
m.fit <- lm(form.m, data = df_cbzs_elg)        # a-path
y.fit <- lm(form.y, data = df_cbzs_elg)        # b and c'-paths

# Fit outcome model WITHOUT mediator to get c-path (total effect)
y.fit.total <- lm(
  reformulate(c("zs_class", controls), response = "support"),
  data = df_cbzs_elg
)

# Mediation analysis with bootstrapping (10,000 sims)
med.fit <- mediation::mediate(
  model.m   = m.fit,
  model.y   = y.fit,
  treat     = "zs_class",
  mediator  = "soli",
  boot      = TRUE,
  sims      = 10000
)

med_tbl <- tibble(
  Effect   = c("ACME (indirect)", "ADE (direct)",
               "Total Effect", "Prop. Mediated"),
  Estimate = c(med.fit$d0,        med.fit$z0,
               med.fit$tau.coef,  med.fit$n0),
  CI.lower = c(med.fit$d0.ci[1],  med.fit$z0.ci[1],
               med.fit$tau.ci[1], med.fit$n0.ci[1]),
  CI.upper = c(med.fit$d0.ci[2],  med.fit$z0.ci[2],
               med.fit$tau.ci[2], med.fit$n0.ci[2]),
  p.value  = c(med.fit$d0.p,      med.fit$z0.p,
               med.fit$tau.p,     med.fit$n0.p)
)

kbl(med_tbl) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
Effect Estimate CI.lower CI.upper p.value
ACME (indirect) 0.0184748 -0.0018851 0.0490046 0.0832
ADE (direct) 0.2806489 0.1438599 0.4178048 0.0000
Total Effect 0.2991236 0.1608911 0.4370884 0.0000
Prop. Mediated 0.0617630 -0.0068717 0.1802324 0.0832
# Helper to generate significance stars
p_stars <- function(p) {
  if (p < .001) return("***")
  if (p < .01)  return("**")
  if (p < .05)  return("*")
  return("")
}

# Extract coefficients & p-values
a_coef   <- coef(m.fit)["zs_class"]
a_p      <- summary(m.fit)$coefficients["zs_class", "Pr(>|t|)"]

b_coef   <- coef(y.fit)["soli"]
b_p      <- summary(y.fit)$coefficients["soli", "Pr(>|t|)"]

cprime   <- coef(y.fit)["zs_class"]
cprime_p <- summary(y.fit)$coefficients["zs_class", "Pr(>|t|)"]

c_total  <- coef(y.fit.total)["zs_class"]
c_total_p <- summary(y.fit.total)$coefficients["zs_class", "Pr(>|t|)"]

# Paste coefficient + stars
a_label      <- paste0("a = ", round(a_coef, 3), p_stars(a_p))
b_label      <- paste0("b = ", round(b_coef, 3), p_stars(b_p))
cprime_label <- paste0("c' = ", round(cprime, 3), p_stars(cprime_p))
c_label      <- paste0("c = ", round(c_total, 3), p_stars(c_total_p))

# Plot
ggplot() +
  xlim(0, 3) + ylim(0, 2) +

  # Nodes
  annotate("text", x = 0.5, y = 1,   label = "zs_class", fontface = "bold") +
  annotate("text", x = 1.5, y = 1,   label = "soli",     fontface = "bold") +
  annotate("text", x = 2.5, y = 1,   label = "support",  fontface = "bold") +

  # a-path (X → M)
  annotate("segment",
           x = 0.7, xend = 1.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.0, y = 1.15, label = a_label) +

  # b-path (M → Y)
  annotate("segment",
           x = 1.7, xend = 2.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 2.0, y = 1.15, label = b_label) +

  # c'-path (direct effect)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 0.9, yend = 0.9,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 0.75, label = cprime_label) +

  # c-path (total effect, dashed)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 1.1, yend = 1.1,
           linetype = "dashed",
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 1.25, label = c_label) +

  theme_void()

Model 2E

Predictor: CBZS

Mediator: Cross-Race Solidarity

Outcome: Support for Policy

Controls: Conservatism, SDO, system justification, relative deprivation, working-class disadvantage, zero-sum mindset, working-class identification, income, education, gender (man = 1, non-man = 0), race (white = 1, non-white = 0), age

Bootstraps: 10,000

# Vector of control variable names
controls <- c(
  "ideo_con", "SDO", "sj", "reldep", "dis", "zsm", "class_id",
  "income_num", "edu_num", "man", "white", "age"
)

# Formula for mediator model: crs ~ zs_class + controls
form.m <- reformulate(c("zs_class", controls), response = "crs")

# Formula for outcome model: support ~ zs_class + crs + controls
form.y <- reformulate(c("zs_class", "crs", controls), response = "support")

# Fit linear models
m.fit <- lm(form.m, data = df_cbzs_elg)        # a-path
y.fit <- lm(form.y, data = df_cbzs_elg)        # b and c'-paths

# Fit outcome model WITHOUT mediator to get c-path (total effect)
y.fit.total <- lm(
  reformulate(c("zs_class", controls), response = "support"),
  data = df_cbzs_elg
)

# Mediation analysis with bootstrapping (10,000 sims)
med.fit <- mediation::mediate(
  model.m   = m.fit,
  model.y   = y.fit,
  treat     = "zs_class",
  mediator  = "crs",
  boot      = TRUE,
  sims      = 10000
)

med_tbl <- tibble(
  Effect   = c("ACME (indirect)", "ADE (direct)",
               "Total Effect", "Prop. Mediated"),
  Estimate = c(med.fit$d0,        med.fit$z0,
               med.fit$tau.coef,  med.fit$n0),
  CI.lower = c(med.fit$d0.ci[1],  med.fit$z0.ci[1],
               med.fit$tau.ci[1], med.fit$n0.ci[1]),
  CI.upper = c(med.fit$d0.ci[2],  med.fit$z0.ci[2],
               med.fit$tau.ci[2], med.fit$n0.ci[2]),
  p.value  = c(med.fit$d0.p,      med.fit$z0.p,
               med.fit$tau.p,     med.fit$n0.p)
)

kbl(med_tbl) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
Effect Estimate CI.lower CI.upper p.value
ACME (indirect) 0.0051590 -0.0140827 0.0293371 0.572
ADE (direct) 0.2963098 0.1569048 0.4336087 0.000
Total Effect 0.3014688 0.1657407 0.4375961 0.000
Prop. Mediated 0.0171128 -0.0507105 0.1115707 0.572
# Helper to generate significance stars
p_stars <- function(p) {
  if (p < .001) return("***")
  if (p < .01)  return("**")
  if (p < .05)  return("*")
  return("")
}

# Extract coefficients & p-values
a_coef   <- coef(m.fit)["zs_class"]
a_p      <- summary(m.fit)$coefficients["zs_class", "Pr(>|t|)"]

b_coef   <- coef(y.fit)["crs"]
b_p      <- summary(y.fit)$coefficients["crs", "Pr(>|t|)"]

cprime   <- coef(y.fit)["zs_class"]
cprime_p <- summary(y.fit)$coefficients["zs_class", "Pr(>|t|)"]

c_total  <- coef(y.fit.total)["zs_class"]
c_total_p <- summary(y.fit.total)$coefficients["zs_class", "Pr(>|t|)"]

# Paste coefficient + stars
a_label      <- paste0("a = ", round(a_coef, 3), p_stars(a_p))
b_label      <- paste0("b = ", round(b_coef, 3), p_stars(b_p))
cprime_label <- paste0("c' = ", round(cprime, 3), p_stars(cprime_p))
c_label      <- paste0("c = ", round(c_total, 3), p_stars(c_total_p))

# Plot
ggplot() +
  xlim(0, 3) + ylim(0, 2) +

  # Nodes
  annotate("text", x = 0.5, y = 1,   label = "zs_class", fontface = "bold") +
  annotate("text", x = 1.5, y = 1,   label = "crs",     fontface = "bold") +
  annotate("text", x = 2.5, y = 1,   label = "support",  fontface = "bold") +

  # a-path (X → M)
  annotate("segment",
           x = 0.7, xend = 1.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.0, y = 1.15, label = a_label) +

  # b-path (M → Y)
  annotate("segment",
           x = 1.7, xend = 2.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 2.0, y = 1.15, label = b_label) +

  # c'-path (direct effect)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 0.9, yend = 0.9,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 0.75, label = cprime_label) +

  # c-path (total effect, dashed)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 1.1, yend = 1.1,
           linetype = "dashed",
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 1.25, label = c_label) +

  theme_void()

Model 3A

Predictor: CBZS

Mediator: Linked Fate

Outcome: Cross-Race Solidarity

Controls: Conservatism

Bootstraps: 10,000

# Vector of control variable names
controls <- c(
  "ideo_con")

# Formula for mediator model: lf ~ zs_class + controls
form.m <- reformulate(c("zs_class", controls), response = "lf")

# Formula for outcome model: crs ~ zs_class + lf + controls
form.y <- reformulate(c("zs_class", "lf", controls), response = "crs")

# Fit linear models
m.fit <- lm(form.m, data = df_cbzs_elg)        # a-path
y.fit <- lm(form.y, data = df_cbzs_elg)        # b and c'-paths

# Fit outcome model WITHOUT mediator to get c-path (total effect)
y.fit.total <- lm(
  reformulate(c("zs_class", controls), response = "crs"),
  data = df_cbzs_elg
)

# Mediation analysis with bootstrapping (10,000 sims)
med.fit <- mediation::mediate(
  model.m   = m.fit,
  model.y   = y.fit,
  treat     = "zs_class",
  mediator  = "lf",
  boot      = TRUE,
  sims      = 10000
)

med_tbl <- tibble(
  Effect   = c("ACME (indirect)", "ADE (direct)",
               "Total Effect", "Prop. Mediated"),
  Estimate = c(med.fit$d0,        med.fit$z0,
               med.fit$tau.coef,  med.fit$n0),
  CI.lower = c(med.fit$d0.ci[1],  med.fit$z0.ci[1],
               med.fit$tau.ci[1], med.fit$n0.ci[1]),
  CI.upper = c(med.fit$d0.ci[2],  med.fit$z0.ci[2],
               med.fit$tau.ci[2], med.fit$n0.ci[2]),
  p.value  = c(med.fit$d0.p,      med.fit$z0.p,
               med.fit$tau.p,     med.fit$n0.p)
)

kbl(med_tbl) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
Effect Estimate CI.lower CI.upper p.value
ACME (indirect) 0.0351306 -0.0148898 0.0889506 0.1694
ADE (direct) 0.1873905 0.0997174 0.2750150 0.0000
Total Effect 0.2225211 0.1287911 0.3171896 0.0000
Prop. Mediated 0.1578753 -0.0864057 0.3903894 0.1694
# Helper to generate significance stars
p_stars <- function(p) {
  if (p < .001) return("***")
  if (p < .01)  return("**")
  if (p < .05)  return("*")
  return("")
}

# Extract coefficients & p-values
a_coef   <- coef(m.fit)["zs_class"]
a_p      <- summary(m.fit)$coefficients["zs_class", "Pr(>|t|)"]

b_coef   <- coef(y.fit)["lf"]
b_p      <- summary(y.fit)$coefficients["lf", "Pr(>|t|)"]

cprime   <- coef(y.fit)["zs_class"]
cprime_p <- summary(y.fit)$coefficients["zs_class", "Pr(>|t|)"]

c_total  <- coef(y.fit.total)["zs_class"]
c_total_p <- summary(y.fit.total)$coefficients["zs_class", "Pr(>|t|)"]

# Paste coefficient + stars
a_label      <- paste0("a = ", round(a_coef, 3), p_stars(a_p))
b_label      <- paste0("b = ", round(b_coef, 3), p_stars(b_p))
cprime_label <- paste0("c' = ", round(cprime, 3), p_stars(cprime_p))
c_label      <- paste0("c = ", round(c_total, 3), p_stars(c_total_p))

# Plot
ggplot() +
  xlim(0, 3) + ylim(0, 2) +

  # Nodes
  annotate("text", x = 0.5, y = 1,   label = "zs_class", fontface = "bold") +
  annotate("text", x = 1.5, y = 1,   label = "lf",     fontface = "bold") +
  annotate("text", x = 2.5, y = 1,   label = "crs",  fontface = "bold") +

  # a-path (X → M)
  annotate("segment",
           x = 0.7, xend = 1.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.0, y = 1.15, label = a_label) +

  # b-path (M → Y)
  annotate("segment",
           x = 1.7, xend = 2.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 2.0, y = 1.15, label = b_label) +

  # c'-path (direct effect)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 0.9, yend = 0.9,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 0.75, label = cprime_label) +

  # c-path (total effect, dashed)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 1.1, yend = 1.1,
           linetype = "dashed",
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 1.25, label = c_label) +

  theme_void()

Model 3B

Predictor: CBZS

Mediator: Linked Fate

Outcome: Cross-Race Solidarity

Controls: SDO

Bootstraps: 10,000

# Vector of control variable names
controls <- c(
  "SDO")

# Formula for mediator model: lf ~ zs_class + controls
form.m <- reformulate(c("zs_class", controls), response = "lf")

# Formula for outcome model: crs ~ zs_class + lf + controls
form.y <- reformulate(c("zs_class", "lf", controls), response = "crs")

# Fit linear models
m.fit <- lm(form.m, data = df_cbzs_elg)        # a-path
y.fit <- lm(form.y, data = df_cbzs_elg)        # b and c'-paths

# Fit outcome model WITHOUT mediator to get c-path (total effect)
y.fit.total <- lm(
  reformulate(c("zs_class", controls), response = "crs"),
  data = df_cbzs_elg
)

# Mediation analysis with bootstrapping (10,000 sims)
med.fit <- mediation::mediate(
  model.m   = m.fit,
  model.y   = y.fit,
  treat     = "zs_class",
  mediator  = "lf",
  boot      = TRUE,
  sims      = 10000
)

med_tbl <- tibble(
  Effect   = c("ACME (indirect)", "ADE (direct)",
               "Total Effect", "Prop. Mediated"),
  Estimate = c(med.fit$d0,        med.fit$z0,
               med.fit$tau.coef,  med.fit$n0),
  CI.lower = c(med.fit$d0.ci[1],  med.fit$z0.ci[1],
               med.fit$tau.ci[1], med.fit$n0.ci[1]),
  CI.upper = c(med.fit$d0.ci[2],  med.fit$z0.ci[2],
               med.fit$tau.ci[2], med.fit$n0.ci[2]),
  p.value  = c(med.fit$d0.p,      med.fit$z0.p,
               med.fit$tau.p,     med.fit$n0.p)
)

kbl(med_tbl) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
Effect Estimate CI.lower CI.upper p.value
ACME (indirect) 0.0055188 -0.0405455 0.0535666 0.8156
ADE (direct) 0.0865134 0.0058302 0.1641741 0.0384
Total Effect 0.0920322 -0.0008905 0.1842315 0.0528
Prop. Mediated 0.0599656 -1.3615440 0.9006288 0.7784
# Helper to generate significance stars
p_stars <- function(p) {
  if (p < .001) return("***")
  if (p < .01)  return("**")
  if (p < .05)  return("*")
  return("")
}

# Extract coefficients & p-values
a_coef   <- coef(m.fit)["zs_class"]
a_p      <- summary(m.fit)$coefficients["zs_class", "Pr(>|t|)"]

b_coef   <- coef(y.fit)["lf"]
b_p      <- summary(y.fit)$coefficients["lf", "Pr(>|t|)"]

cprime   <- coef(y.fit)["zs_class"]
cprime_p <- summary(y.fit)$coefficients["zs_class", "Pr(>|t|)"]

c_total  <- coef(y.fit.total)["zs_class"]
c_total_p <- summary(y.fit.total)$coefficients["zs_class", "Pr(>|t|)"]

# Paste coefficient + stars
a_label      <- paste0("a = ", round(a_coef, 3), p_stars(a_p))
b_label      <- paste0("b = ", round(b_coef, 3), p_stars(b_p))
cprime_label <- paste0("c' = ", round(cprime, 3), p_stars(cprime_p))
c_label      <- paste0("c = ", round(c_total, 3), p_stars(c_total_p))

# Plot
ggplot() +
  xlim(0, 3) + ylim(0, 2) +

  # Nodes
  annotate("text", x = 0.5, y = 1,   label = "zs_class", fontface = "bold") +
  annotate("text", x = 1.5, y = 1,   label = "lf",     fontface = "bold") +
  annotate("text", x = 2.5, y = 1,   label = "crs",  fontface = "bold") +

  # a-path (X → M)
  annotate("segment",
           x = 0.7, xend = 1.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.0, y = 1.15, label = a_label) +

  # b-path (M → Y)
  annotate("segment",
           x = 1.7, xend = 2.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 2.0, y = 1.15, label = b_label) +

  # c'-path (direct effect)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 0.9, yend = 0.9,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 0.75, label = cprime_label) +

  # c-path (total effect, dashed)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 1.1, yend = 1.1,
           linetype = "dashed",
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 1.25, label = c_label) +

  theme_void()

Model 3C

Predictor: CBZS

Mediator: Linked Fate

Outcome: Cross-Race Solidarity

Controls: Conservatism, SDO, system justification

Bootstraps: 10,000

# Vector of control variable names
controls <- c(
  "ideo_con", "SDO", "sj"
)

# Formula for mediator model: lf ~ zs_class + controls
form.m <- reformulate(c("zs_class", controls), response = "lf")

# Formula for outcome model: crs ~ zs_class + lf + controls
form.y <- reformulate(c("zs_class", "lf", controls), response = "crs")

# Fit linear models
m.fit <- lm(form.m, data = df_cbzs_elg)        # a-path
y.fit <- lm(form.y, data = df_cbzs_elg)        # b and c'-paths

# Fit outcome model WITHOUT mediator to get c-path (total effect)
y.fit.total <- lm(
  reformulate(c("zs_class", controls), response = "crs"),
  data = df_cbzs_elg
)

# Mediation analysis with bootstrapping (10,000 sims)
med.fit <- mediation::mediate(
  model.m   = m.fit,
  model.y   = y.fit,
  treat     = "zs_class",
  mediator  = "lf",
  boot      = TRUE,
  sims      = 10000
)

med_tbl <- tibble(
  Effect   = c("ACME (indirect)", "ADE (direct)",
               "Total Effect", "Prop. Mediated"),
  Estimate = c(med.fit$d0,        med.fit$z0,
               med.fit$tau.coef,  med.fit$n0),
  CI.lower = c(med.fit$d0.ci[1],  med.fit$z0.ci[1],
               med.fit$tau.ci[1], med.fit$n0.ci[1]),
  CI.upper = c(med.fit$d0.ci[2],  med.fit$z0.ci[2],
               med.fit$tau.ci[2], med.fit$n0.ci[2]),
  p.value  = c(med.fit$d0.p,      med.fit$z0.p,
               med.fit$tau.p,     med.fit$n0.p)
)

kbl(med_tbl) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
Effect Estimate CI.lower CI.upper p.value
ACME (indirect) 0.0239671 -0.0304190 0.0813894 0.3930
ADE (direct) 0.1406777 0.0448732 0.2327483 0.0046
Total Effect 0.1646447 0.0509629 0.2764176 0.0060
Prop. Mediated 0.1455683 -0.3603776 0.4743540 0.3894
# Helper to generate significance stars
p_stars <- function(p) {
  if (p < .001) return("***")
  if (p < .01)  return("**")
  if (p < .05)  return("*")
  return("")
}

# Extract coefficients & p-values
a_coef   <- coef(m.fit)["zs_class"]
a_p      <- summary(m.fit)$coefficients["zs_class", "Pr(>|t|)"]

b_coef   <- coef(y.fit)["lf"]
b_p      <- summary(y.fit)$coefficients["lf", "Pr(>|t|)"]

cprime   <- coef(y.fit)["zs_class"]
cprime_p <- summary(y.fit)$coefficients["zs_class", "Pr(>|t|)"]

c_total  <- coef(y.fit.total)["zs_class"]
c_total_p <- summary(y.fit.total)$coefficients["zs_class", "Pr(>|t|)"]

# Paste coefficient + stars
a_label      <- paste0("a = ", round(a_coef, 3), p_stars(a_p))
b_label      <- paste0("b = ", round(b_coef, 3), p_stars(b_p))
cprime_label <- paste0("c' = ", round(cprime, 3), p_stars(cprime_p))
c_label      <- paste0("c = ", round(c_total, 3), p_stars(c_total_p))

# Plot
ggplot() +
  xlim(0, 3) + ylim(0, 2) +

  # Nodes
  annotate("text", x = 0.5, y = 1,   label = "zs_class", fontface = "bold") +
  annotate("text", x = 1.5, y = 1,   label = "lf",     fontface = "bold") +
  annotate("text", x = 2.5, y = 1,   label = "crs",  fontface = "bold") +

  # a-path (X → M)
  annotate("segment",
           x = 0.7, xend = 1.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.0, y = 1.15, label = a_label) +

  # b-path (M → Y)
  annotate("segment",
           x = 1.7, xend = 2.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 2.0, y = 1.15, label = b_label) +

  # c'-path (direct effect)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 0.9, yend = 0.9,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 0.75, label = cprime_label) +

  # c-path (total effect, dashed)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 1.1, yend = 1.1,
           linetype = "dashed",
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 1.25, label = c_label) +

  theme_void()

Model 3D

Predictor: CBZS

Mediator: Linked Fate

Outcome: Cross-Race Solidarity

Controls: Conservatism, SDO, system justification, relative deprivation, working-class disadvantage, zero-sum mindset, working-class identificaiton

Bootstraps: 10,000

# Vector of control variable names
controls <- c(
  "ideo_con", "SDO", "sj", "reldep", "dis", "zsm", "class_id"
)

# Formula for mediator model: lf ~ zs_class + controls
form.m <- reformulate(c("zs_class", controls), response = "lf")

# Formula for outcome model: crs ~ zs_class + lf + controls
form.y <- reformulate(c("zs_class", "lf", controls), response = "crs")

# Fit linear models
m.fit <- lm(form.m, data = df_cbzs_elg)        # a-path
y.fit <- lm(form.y, data = df_cbzs_elg)        # b and c'-paths

# Fit outcome model WITHOUT mediator to get c-path (total effect)
y.fit.total <- lm(
  reformulate(c("zs_class", controls), response = "crs"),
  data = df_cbzs_elg
)

# Mediation analysis with bootstrapping (10,000 sims)
med.fit <- mediation::mediate(
  model.m   = m.fit,
  model.y   = y.fit,
  treat     = "zs_class",
  mediator  = "lf",
  boot      = TRUE,
  sims      = 10000
)

med_tbl <- tibble(
  Effect   = c("ACME (indirect)", "ADE (direct)",
               "Total Effect", "Prop. Mediated"),
  Estimate = c(med.fit$d0,        med.fit$z0,
               med.fit$tau.coef,  med.fit$n0),
  CI.lower = c(med.fit$d0.ci[1],  med.fit$z0.ci[1],
               med.fit$tau.ci[1], med.fit$n0.ci[1]),
  CI.upper = c(med.fit$d0.ci[2],  med.fit$z0.ci[2],
               med.fit$tau.ci[2], med.fit$n0.ci[2]),
  p.value  = c(med.fit$d0.p,      med.fit$z0.p,
               med.fit$tau.p,     med.fit$n0.p)
)

kbl(med_tbl) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
Effect Estimate CI.lower CI.upper p.value
ACME (indirect) 0.0232865 -0.0375639 0.0890107 0.4696
ADE (direct) 0.1790387 0.0643493 0.2908054 0.0016
Total Effect 0.2023252 0.0698807 0.3351241 0.0024
Prop. Mediated 0.1150945 -0.3337706 0.4137391 0.4680
# Helper to generate significance stars
p_stars <- function(p) {
  if (p < .001) return("***")
  if (p < .01)  return("**")
  if (p < .05)  return("*")
  return("")
}

# Extract coefficients & p-values
a_coef   <- coef(m.fit)["zs_class"]
a_p      <- summary(m.fit)$coefficients["zs_class", "Pr(>|t|)"]

b_coef   <- coef(y.fit)["lf"]
b_p      <- summary(y.fit)$coefficients["lf", "Pr(>|t|)"]

cprime   <- coef(y.fit)["zs_class"]
cprime_p <- summary(y.fit)$coefficients["zs_class", "Pr(>|t|)"]

c_total  <- coef(y.fit.total)["zs_class"]
c_total_p <- summary(y.fit.total)$coefficients["zs_class", "Pr(>|t|)"]

# Paste coefficient + stars
a_label      <- paste0("a = ", round(a_coef, 3), p_stars(a_p))
b_label      <- paste0("b = ", round(b_coef, 3), p_stars(b_p))
cprime_label <- paste0("c' = ", round(cprime, 3), p_stars(cprime_p))
c_label      <- paste0("c = ", round(c_total, 3), p_stars(c_total_p))

# Plot
ggplot() +
  xlim(0, 3) + ylim(0, 2) +

  # Nodes
  annotate("text", x = 0.5, y = 1,   label = "zs_class", fontface = "bold") +
  annotate("text", x = 1.5, y = 1,   label = "lf",     fontface = "bold") +
  annotate("text", x = 2.5, y = 1,   label = "crs",  fontface = "bold") +

  # a-path (X → M)
  annotate("segment",
           x = 0.7, xend = 1.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.0, y = 1.15, label = a_label) +

  # b-path (M → Y)
  annotate("segment",
           x = 1.7, xend = 2.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 2.0, y = 1.15, label = b_label) +

  # c'-path (direct effect)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 0.9, yend = 0.9,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 0.75, label = cprime_label) +

  # c-path (total effect, dashed)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 1.1, yend = 1.1,
           linetype = "dashed",
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 1.25, label = c_label) +

  theme_void()

Model 3E

Predictor: CBZS

Mediator: Linked Fate

Outcome: Cross-Race Solidarity

Controls: Conservatism, SDO, system justification, relative deprivation, working-class disadvantage, zero-sum mindset, working-class identification, income, education, gender (man = 1, non-man = 0), race (white = 1, non-white = 0), age

Bootstraps: 10,000

# Vector of control variable names
controls <- c(
  "ideo_con", "SDO", "sj", "reldep", "dis", "zsm", "class_id",
  "income_num", "edu_num", "man", "white", "age"
)

# Formula for mediator model: lf ~ zs_class + controls
form.m <- reformulate(c("zs_class", controls), response = "lf")

# Formula for outcome model: crs ~ zs_class + lf + controls
form.y <- reformulate(c("zs_class", "lf", controls), response = "crs")

# Fit linear models
m.fit <- lm(form.m, data = df_cbzs_elg)        # a-path
y.fit <- lm(form.y, data = df_cbzs_elg)        # b and c'-paths

# Fit outcome model WITHOUT mediator to get c-path (total effect)
y.fit.total <- lm(
  reformulate(c("zs_class", controls), response = "crs"),
  data = df_cbzs_elg
)

# Mediation analysis with bootstrapping (10,000 sims)
med.fit <- mediation::mediate(
  model.m   = m.fit,
  model.y   = y.fit,
  treat     = "zs_class",
  mediator  = "lf",
  boot      = TRUE,
  sims      = 10000
)

med_tbl <- tibble(
  Effect   = c("ACME (indirect)", "ADE (direct)",
               "Total Effect", "Prop. Mediated"),
  Estimate = c(med.fit$d0,        med.fit$z0,
               med.fit$tau.coef,  med.fit$n0),
  CI.lower = c(med.fit$d0.ci[1],  med.fit$z0.ci[1],
               med.fit$tau.ci[1], med.fit$n0.ci[1]),
  CI.upper = c(med.fit$d0.ci[2],  med.fit$z0.ci[2],
               med.fit$tau.ci[2], med.fit$n0.ci[2]),
  p.value  = c(med.fit$d0.p,      med.fit$z0.p,
               med.fit$tau.p,     med.fit$n0.p)
)

kbl(med_tbl) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
Effect Estimate CI.lower CI.upper p.value
ACME (indirect) 0.0294014 -0.0355412 0.0968118 0.3716
ADE (direct) 0.1583461 0.0430041 0.2710549 0.0078
Total Effect 0.1877475 0.0489913 0.3223485 0.0080
Prop. Mediated 0.1566006 -0.3994653 0.5134281 0.3668
# Helper to generate significance stars
p_stars <- function(p) {
  if (p < .001) return("***")
  if (p < .01)  return("**")
  if (p < .05)  return("*")
  return("")
}

# Extract coefficients & p-values
a_coef   <- coef(m.fit)["zs_class"]
a_p      <- summary(m.fit)$coefficients["zs_class", "Pr(>|t|)"]

b_coef   <- coef(y.fit)["lf"]
b_p      <- summary(y.fit)$coefficients["lf", "Pr(>|t|)"]

cprime   <- coef(y.fit)["zs_class"]
cprime_p <- summary(y.fit)$coefficients["zs_class", "Pr(>|t|)"]

c_total  <- coef(y.fit.total)["zs_class"]
c_total_p <- summary(y.fit.total)$coefficients["zs_class", "Pr(>|t|)"]

# Paste coefficient + stars
a_label      <- paste0("a = ", round(a_coef, 3), p_stars(a_p))
b_label      <- paste0("b = ", round(b_coef, 3), p_stars(b_p))
cprime_label <- paste0("c' = ", round(cprime, 3), p_stars(cprime_p))
c_label      <- paste0("c = ", round(c_total, 3), p_stars(c_total_p))

# Plot
ggplot() +
  xlim(0, 3) + ylim(0, 2) +

  # Nodes
  annotate("text", x = 0.5, y = 1,   label = "zs_class", fontface = "bold") +
  annotate("text", x = 1.5, y = 1,   label = "lf",     fontface = "bold") +
  annotate("text", x = 2.5, y = 1,   label = "crs",  fontface = "bold") +

  # a-path (X → M)
  annotate("segment",
           x = 0.7, xend = 1.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.0, y = 1.15, label = a_label) +

  # b-path (M → Y)
  annotate("segment",
           x = 1.7, xend = 2.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 2.0, y = 1.15, label = b_label) +

  # c'-path (direct effect)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 0.9, yend = 0.9,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 0.75, label = cprime_label) +

  # c-path (total effect, dashed)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 1.1, yend = 1.1,
           linetype = "dashed",
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 1.25, label = c_label) +

  theme_void()

Model 4A

Predictor: CBZS

Mediator: Linked Fate

Outcome: Solidarity

Controls: Conservatism

Bootstraps: 10,000

# Vector of control variable names
controls <- c(
  "ideo_con")

# Formula for mediator model: soli ~ zs_class + controls
form.m <- reformulate(c("zs_class", controls), response = "lf")

# Formula for outcome model: soli ~ zs_class + lf + controls
form.y <- reformulate(c("zs_class", "lf", controls), response = "soli")

# Fit linear models
m.fit <- lm(form.m, data = df_cbzs_elg)        # a-path
y.fit <- lm(form.y, data = df_cbzs_elg)        # b and c'-paths

# Fit outcome model WITHOUT mediator to get c-path (total effect)
y.fit.total <- lm(
  reformulate(c("zs_class", controls), response = "soli"),
  data = df_cbzs_elg
)

# Mediation analysis with bootstrapping (10,000 sims)
med.fit <- mediation::mediate(
  model.m   = m.fit,
  model.y   = y.fit,
  treat     = "zs_class",
  mediator  = "lf",
  boot      = TRUE,
  sims      = 10000
)

med_tbl <- tibble(
  Effect   = c("ACME (indirect)", "ADE (direct)",
               "Total Effect", "Prop. Mediated"),
  Estimate = c(med.fit$d0,        med.fit$z0,
               med.fit$tau.coef,  med.fit$n0),
  CI.lower = c(med.fit$d0.ci[1],  med.fit$z0.ci[1],
               med.fit$tau.ci[1], med.fit$n0.ci[1]),
  CI.upper = c(med.fit$d0.ci[2],  med.fit$z0.ci[2],
               med.fit$tau.ci[2], med.fit$n0.ci[2]),
  p.value  = c(med.fit$d0.p,      med.fit$z0.p,
               med.fit$tau.p,     med.fit$n0.p)
)

kbl(med_tbl) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
Effect Estimate CI.lower CI.upper p.value
ACME (indirect) 0.0100906 -0.0041370 0.0275924 0.1724
ADE (direct) 0.2471287 0.1899544 0.3063010 0.0000
Total Effect 0.2572192 0.2009405 0.3168749 0.0000
Prop. Mediated 0.0392295 -0.0165528 0.1084615 0.1724
# Helper to generate significance stars
p_stars <- function(p) {
  if (p < .001) return("***")
  if (p < .01)  return("**")
  if (p < .05)  return("*")
  return("")
}

# Extract coefficients & p-values
a_coef   <- coef(m.fit)["zs_class"]
a_p      <- summary(m.fit)$coefficients["zs_class", "Pr(>|t|)"]

b_coef   <- coef(y.fit)["lf"]
b_p      <- summary(y.fit)$coefficients["lf", "Pr(>|t|)"]

cprime   <- coef(y.fit)["zs_class"]
cprime_p <- summary(y.fit)$coefficients["zs_class", "Pr(>|t|)"]

c_total  <- coef(y.fit.total)["zs_class"]
c_total_p <- summary(y.fit.total)$coefficients["zs_class", "Pr(>|t|)"]

# Paste coefficient + stars
a_label      <- paste0("a = ", round(a_coef, 3), p_stars(a_p))
b_label      <- paste0("b = ", round(b_coef, 3), p_stars(b_p))
cprime_label <- paste0("c' = ", round(cprime, 3), p_stars(cprime_p))
c_label      <- paste0("c = ", round(c_total, 3), p_stars(c_total_p))

# Plot
ggplot() +
  xlim(0, 3) + ylim(0, 2) +

  # Nodes
  annotate("text", x = 0.5, y = 1,   label = "zs_class", fontface = "bold") +
  annotate("text", x = 1.5, y = 1,   label = "lf",     fontface = "bold") +
  annotate("text", x = 2.5, y = 1,   label = "soli",  fontface = "bold") +

  # a-path (X → M)
  annotate("segment",
           x = 0.7, xend = 1.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.0, y = 1.15, label = a_label) +

  # b-path (M → Y)
  annotate("segment",
           x = 1.7, xend = 2.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 2.0, y = 1.15, label = b_label) +

  # c'-path (direct effect)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 0.9, yend = 0.9,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 0.75, label = cprime_label) +

  # c-path (total effect, dashed)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 1.1, yend = 1.1,
           linetype = "dashed",
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 1.25, label = c_label) +

  theme_void()

Model 4B

Predictor: CBZS

Mediator: Linked Fate

Outcome: Solidarity

Controls: SDO

Bootstraps: 10,000

# Vector of control variable names
controls <- c(
  "SDO")

# Formula for mediator model: soli ~ zs_class + controls
form.m <- reformulate(c("zs_class", controls), response = "lf")

# Formula for outcome model: soli ~ zs_class + lf + controls
form.y <- reformulate(c("zs_class", "lf", controls), response = "soli")

# Fit linear models
m.fit <- lm(form.m, data = df_cbzs_elg)        # a-path
y.fit <- lm(form.y, data = df_cbzs_elg)        # b and c'-paths

# Fit outcome model WITHOUT mediator to get c-path (total effect)
y.fit.total <- lm(
  reformulate(c("zs_class", controls), response = "soli"),
  data = df_cbzs_elg
)

# Mediation analysis with bootstrapping (10,000 sims)
med.fit <- mediation::mediate(
  model.m   = m.fit,
  model.y   = y.fit,
  treat     = "zs_class",
  mediator  = "lf",
  boot      = TRUE,
  sims      = 10000
)

med_tbl <- tibble(
  Effect   = c("ACME (indirect)", "ADE (direct)",
               "Total Effect", "Prop. Mediated"),
  Estimate = c(med.fit$d0,        med.fit$z0,
               med.fit$tau.coef,  med.fit$n0),
  CI.lower = c(med.fit$d0.ci[1],  med.fit$z0.ci[1],
               med.fit$tau.ci[1], med.fit$n0.ci[1]),
  CI.upper = c(med.fit$d0.ci[2],  med.fit$z0.ci[2],
               med.fit$tau.ci[2], med.fit$n0.ci[2]),
  p.value  = c(med.fit$d0.p,      med.fit$z0.p,
               med.fit$tau.p,     med.fit$n0.p)
)

kbl(med_tbl) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
Effect Estimate CI.lower CI.upper p.value
ACME (indirect) 0.0014281 -0.0112259 0.0135385 0.8108
ADE (direct) 0.1835000 0.1312580 0.2368115 0.0000
Total Effect 0.1849281 0.1330004 0.2368489 0.0000
Prop. Mediated 0.0077223 -0.0644926 0.0777705 0.8108
# Helper to generate significance stars
p_stars <- function(p) {
  if (p < .001) return("***")
  if (p < .01)  return("**")
  if (p < .05)  return("*")
  return("")
}

# Extract coefficients & p-values
a_coef   <- coef(m.fit)["zs_class"]
a_p      <- summary(m.fit)$coefficients["zs_class", "Pr(>|t|)"]

b_coef   <- coef(y.fit)["lf"]
b_p      <- summary(y.fit)$coefficients["lf", "Pr(>|t|)"]

cprime   <- coef(y.fit)["zs_class"]
cprime_p <- summary(y.fit)$coefficients["zs_class", "Pr(>|t|)"]

c_total  <- coef(y.fit.total)["zs_class"]
c_total_p <- summary(y.fit.total)$coefficients["zs_class", "Pr(>|t|)"]

# Paste coefficient + stars
a_label      <- paste0("a = ", round(a_coef, 3), p_stars(a_p))
b_label      <- paste0("b = ", round(b_coef, 3), p_stars(b_p))
cprime_label <- paste0("c' = ", round(cprime, 3), p_stars(cprime_p))
c_label      <- paste0("c = ", round(c_total, 3), p_stars(c_total_p))

# Plot
ggplot() +
  xlim(0, 3) + ylim(0, 2) +

  # Nodes
  annotate("text", x = 0.5, y = 1,   label = "zs_class", fontface = "bold") +
  annotate("text", x = 1.5, y = 1,   label = "lf",     fontface = "bold") +
  annotate("text", x = 2.5, y = 1,   label = "soli",  fontface = "bold") +

  # a-path (X → M)
  annotate("segment",
           x = 0.7, xend = 1.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.0, y = 1.15, label = a_label) +

  # b-path (M → Y)
  annotate("segment",
           x = 1.7, xend = 2.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 2.0, y = 1.15, label = b_label) +

  # c'-path (direct effect)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 0.9, yend = 0.9,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 0.75, label = cprime_label) +

  # c-path (total effect, dashed)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 1.1, yend = 1.1,
           linetype = "dashed",
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 1.25, label = c_label) +

  theme_void()

Model 4C

Predictor: CBZS

Mediator: Linked Fate

Outcome: Solidarity

Controls: Conservatism, SDO, system justification

Bootstraps: 10,000

# Vector of control variable names
controls <- c(
  "ideo_con", "SDO", "sj"
)

# Formula for mediator model: lf ~ zs_class + controls
form.m <- reformulate(c("zs_class", controls), response = "lf")

# Formula for outcome model: soli ~ zs_class + lf + controls
form.y <- reformulate(c("zs_class", "lf", controls), response = "soli")

# Fit linear models
m.fit <- lm(form.m, data = df_cbzs_elg)        # a-path
y.fit <- lm(form.y, data = df_cbzs_elg)        # b and c'-paths

# Fit outcome model WITHOUT mediator to get c-path (total effect)
y.fit.total <- lm(
  reformulate(c("zs_class", controls), response = "soli"),
  data = df_cbzs_elg
)

# Mediation analysis with bootstrapping (10,000 sims)
med.fit <- mediation::mediate(
  model.m   = m.fit,
  model.y   = y.fit,
  treat     = "zs_class",
  mediator  = "lf",
  boot      = TRUE,
  sims      = 10000
)

med_tbl <- tibble(
  Effect   = c("ACME (indirect)", "ADE (direct)",
               "Total Effect", "Prop. Mediated"),
  Estimate = c(med.fit$d0,        med.fit$z0,
               med.fit$tau.coef,  med.fit$n0),
  CI.lower = c(med.fit$d0.ci[1],  med.fit$z0.ci[1],
               med.fit$tau.ci[1], med.fit$n0.ci[1]),
  CI.upper = c(med.fit$d0.ci[2],  med.fit$z0.ci[2],
               med.fit$tau.ci[2], med.fit$n0.ci[2]),
  p.value  = c(med.fit$d0.p,      med.fit$z0.p,
               med.fit$tau.p,     med.fit$n0.p)
)

kbl(med_tbl) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
Effect Estimate CI.lower CI.upper p.value
ACME (indirect) 0.0061417 -0.0082588 0.0216061 0.3972
ADE (direct) 0.1294044 0.0696376 0.1893435 0.0000
Total Effect 0.1355461 0.0743156 0.1972793 0.0000
Prop. Mediated 0.0453110 -0.0754667 0.1701113 0.3972
# Helper to generate significance stars
p_stars <- function(p) {
  if (p < .001) return("***")
  if (p < .01)  return("**")
  if (p < .05)  return("*")
  return("")
}

# Extract coefficients & p-values
a_coef   <- coef(m.fit)["zs_class"]
a_p      <- summary(m.fit)$coefficients["zs_class", "Pr(>|t|)"]

b_coef   <- coef(y.fit)["lf"]
b_p      <- summary(y.fit)$coefficients["lf", "Pr(>|t|)"]

cprime   <- coef(y.fit)["zs_class"]
cprime_p <- summary(y.fit)$coefficients["zs_class", "Pr(>|t|)"]

c_total  <- coef(y.fit.total)["zs_class"]
c_total_p <- summary(y.fit.total)$coefficients["zs_class", "Pr(>|t|)"]

# Paste coefficient + stars
a_label      <- paste0("a = ", round(a_coef, 3), p_stars(a_p))
b_label      <- paste0("b = ", round(b_coef, 3), p_stars(b_p))
cprime_label <- paste0("c' = ", round(cprime, 3), p_stars(cprime_p))
c_label      <- paste0("c = ", round(c_total, 3), p_stars(c_total_p))

# Plot
ggplot() +
  xlim(0, 3) + ylim(0, 2) +

  # Nodes
  annotate("text", x = 0.5, y = 1,   label = "zs_class", fontface = "bold") +
  annotate("text", x = 1.5, y = 1,   label = "lf",     fontface = "bold") +
  annotate("text", x = 2.5, y = 1,   label = "soli",  fontface = "bold") +

  # a-path (X → M)
  annotate("segment",
           x = 0.7, xend = 1.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.0, y = 1.15, label = a_label) +

  # b-path (M → Y)
  annotate("segment",
           x = 1.7, xend = 2.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 2.0, y = 1.15, label = b_label) +

  # c'-path (direct effect)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 0.9, yend = 0.9,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 0.75, label = cprime_label) +

  # c-path (total effect, dashed)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 1.1, yend = 1.1,
           linetype = "dashed",
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 1.25, label = c_label) +

  theme_void()

Model 4D

Predictor: CBZS

Mediator: Linked Fate

Outcome: Solidarity

Controls: Conservatism, SDO, system justification, relative deprivation, working-class disadvantage, zero-sum mindset, working-class identification

Bootstraps: 10,000

# Vector of control variable names
controls <- c(
  "ideo_con", "SDO", "sj", "reldep", "dis", "zsm", "class_id"
)

# Formula for mediator model: lf ~ zs_class + controls
form.m <- reformulate(c("zs_class", controls), response = "lf")

# Formula for outcome model: soli ~ zs_class + lf + controls
form.y <- reformulate(c("zs_class", "lf", controls), response = "soli")

# Fit linear models
m.fit <- lm(form.m, data = df_cbzs_elg)        # a-path
y.fit <- lm(form.y, data = df_cbzs_elg)        # b and c'-paths

# Fit outcome model WITHOUT mediator to get c-path (total effect)
y.fit.total <- lm(
  reformulate(c("zs_class", controls), response = "soli"),
  data = df_cbzs_elg
)

# Mediation analysis with bootstrapping (10,000 sims)
med.fit <- mediation::mediate(
  model.m   = m.fit,
  model.y   = y.fit,
  treat     = "zs_class",
  mediator  = "lf",
  boot      = TRUE,
  sims      = 10000
)

med_tbl <- tibble(
  Effect   = c("ACME (indirect)", "ADE (direct)",
               "Total Effect", "Prop. Mediated"),
  Estimate = c(med.fit$d0,        med.fit$z0,
               med.fit$tau.coef,  med.fit$n0),
  CI.lower = c(med.fit$d0.ci[1],  med.fit$z0.ci[1],
               med.fit$tau.ci[1], med.fit$n0.ci[1]),
  CI.upper = c(med.fit$d0.ci[2],  med.fit$z0.ci[2],
               med.fit$tau.ci[2], med.fit$n0.ci[2]),
  p.value  = c(med.fit$d0.p,      med.fit$z0.p,
               med.fit$tau.p,     med.fit$n0.p)
)

kbl(med_tbl) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
Effect Estimate CI.lower CI.upper p.value
ACME (indirect) 0.0047232 -0.0080719 0.0188565 0.4710
ADE (direct) 0.0868644 0.0192269 0.1523772 0.0106
Total Effect 0.0915876 0.0229675 0.1593608 0.0080
Prop. Mediated 0.0515701 -0.1365552 0.2800105 0.4718
# Helper to generate significance stars
p_stars <- function(p) {
  if (p < .001) return("***")
  if (p < .01)  return("**")
  if (p < .05)  return("*")
  return("")
}

# Extract coefficients & p-values
a_coef   <- coef(m.fit)["zs_class"]
a_p      <- summary(m.fit)$coefficients["zs_class", "Pr(>|t|)"]

b_coef   <- coef(y.fit)["lf"]
b_p      <- summary(y.fit)$coefficients["lf", "Pr(>|t|)"]

cprime   <- coef(y.fit)["zs_class"]
cprime_p <- summary(y.fit)$coefficients["zs_class", "Pr(>|t|)"]

c_total  <- coef(y.fit.total)["zs_class"]
c_total_p <- summary(y.fit.total)$coefficients["zs_class", "Pr(>|t|)"]

# Paste coefficient + stars
a_label      <- paste0("a = ", round(a_coef, 3), p_stars(a_p))
b_label      <- paste0("b = ", round(b_coef, 3), p_stars(b_p))
cprime_label <- paste0("c' = ", round(cprime, 3), p_stars(cprime_p))
c_label      <- paste0("c = ", round(c_total, 3), p_stars(c_total_p))

# Plot
ggplot() +
  xlim(0, 3) + ylim(0, 2) +

  # Nodes
  annotate("text", x = 0.5, y = 1,   label = "zs_class", fontface = "bold") +
  annotate("text", x = 1.5, y = 1,   label = "lf",     fontface = "bold") +
  annotate("text", x = 2.5, y = 1,   label = "soli",  fontface = "bold") +

  # a-path (X → M)
  annotate("segment",
           x = 0.7, xend = 1.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.0, y = 1.15, label = a_label) +

  # b-path (M → Y)
  annotate("segment",
           x = 1.7, xend = 2.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 2.0, y = 1.15, label = b_label) +

  # c'-path (direct effect)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 0.9, yend = 0.9,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 0.75, label = cprime_label) +

  # c-path (total effect, dashed)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 1.1, yend = 1.1,
           linetype = "dashed",
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 1.25, label = c_label) +

  theme_void()

Model 4E

Predictor: CBZS

Mediator: Linked Fate

Outcome: Solidarity

Controls: Conservatism, SDO, system justification, relative deprivation, working-class disadvantage, zero-sum mindset, working-class identification, income, education, gender (man = 1, non-man = 0), race (white = 1, non-white = 0), age

Bootstraps: 10,000

# Vector of control variable names
controls <- c(
  "ideo_con", "SDO", "sj", "reldep", "dis", "zsm", "class_id",
  "income_num", "edu_num", "man", "white", "age"
)

# Formula for mediator model: lf ~ zs_class + controls
form.m <- reformulate(c("zs_class", controls), response = "lf")

# Formula for outcome model: soli ~ zs_class + lf + controls
form.y <- reformulate(c("zs_class", "lf", controls), response = "soli")

# Fit linear models
m.fit <- lm(form.m, data = df_cbzs_elg)        # a-path
y.fit <- lm(form.y, data = df_cbzs_elg)        # b and c'-paths

# Fit outcome model WITHOUT mediator to get c-path (total effect)
y.fit.total <- lm(
  reformulate(c("zs_class", controls), response = "soli"),
  data = df_cbzs_elg
)

# Mediation analysis with bootstrapping (10,000 sims)
med.fit <- mediation::mediate(
  model.m   = m.fit,
  model.y   = y.fit,
  treat     = "zs_class",
  mediator  = "lf",
  boot      = TRUE,
  sims      = 10000
)

med_tbl <- tibble(
  Effect   = c("ACME (indirect)", "ADE (direct)",
               "Total Effect", "Prop. Mediated"),
  Estimate = c(med.fit$d0,        med.fit$z0,
               med.fit$tau.coef,  med.fit$n0),
  CI.lower = c(med.fit$d0.ci[1],  med.fit$z0.ci[1],
               med.fit$tau.ci[1], med.fit$n0.ci[1]),
  CI.upper = c(med.fit$d0.ci[2],  med.fit$z0.ci[2],
               med.fit$tau.ci[2], med.fit$n0.ci[2]),
  p.value  = c(med.fit$d0.p,      med.fit$z0.p,
               med.fit$tau.p,     med.fit$n0.p)
)

kbl(med_tbl) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
Effect Estimate CI.lower CI.upper p.value
ACME (indirect) 0.0063121 -0.0072274 0.0214981 0.3820
ADE (direct) 0.0850680 0.0194878 0.1490030 0.0118
Total Effect 0.0913801 0.0252042 0.1575221 0.0076
Prop. Mediated 0.0690754 -0.1215295 0.3193654 0.3812
# Helper to generate significance stars
p_stars <- function(p) {
  if (p < .001) return("***")
  if (p < .01)  return("**")
  if (p < .05)  return("*")
  return("")
}

# Extract coefficients & p-values
a_coef   <- coef(m.fit)["zs_class"]
a_p      <- summary(m.fit)$coefficients["zs_class", "Pr(>|t|)"]

b_coef   <- coef(y.fit)["lf"]
b_p      <- summary(y.fit)$coefficients["lf", "Pr(>|t|)"]

cprime   <- coef(y.fit)["zs_class"]
cprime_p <- summary(y.fit)$coefficients["zs_class", "Pr(>|t|)"]

c_total  <- coef(y.fit.total)["zs_class"]
c_total_p <- summary(y.fit.total)$coefficients["zs_class", "Pr(>|t|)"]

# Paste coefficient + stars
a_label      <- paste0("a = ", round(a_coef, 3), p_stars(a_p))
b_label      <- paste0("b = ", round(b_coef, 3), p_stars(b_p))
cprime_label <- paste0("c' = ", round(cprime, 3), p_stars(cprime_p))
c_label      <- paste0("c = ", round(c_total, 3), p_stars(c_total_p))

# Plot
ggplot() +
  xlim(0, 3) + ylim(0, 2) +

  # Nodes
  annotate("text", x = 0.5, y = 1,   label = "zs_class", fontface = "bold") +
  annotate("text", x = 1.5, y = 1,   label = "lf",     fontface = "bold") +
  annotate("text", x = 2.5, y = 1,   label = "soli",  fontface = "bold") +

  # a-path (X → M)
  annotate("segment",
           x = 0.7, xend = 1.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.0, y = 1.15, label = a_label) +

  # b-path (M → Y)
  annotate("segment",
           x = 1.7, xend = 2.3, y = 1, yend = 1,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 2.0, y = 1.15, label = b_label) +

  # c'-path (direct effect)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 0.9, yend = 0.9,
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 0.75, label = cprime_label) +

  # c-path (total effect, dashed)
  annotate("segment",
           x = 0.5, xend = 2.5, y = 1.1, yend = 1.1,
           linetype = "dashed",
           arrow = arrow(length = unit(0.20, "cm"))) +
  annotate("text", x = 1.5, y = 1.25, label = c_label) +

  theme_void()

Structural Equation Models

Model 1

library(lavaan)
## This is lavaan 0.6-17
## lavaan is FREE software! Please report any bugs.
## 
## Attaching package: 'lavaan'
## The following object is masked from 'package:psych':
## 
##     cor2cov
model_chain <- '
  # Regressions (paths)
  lf      ~ a * zs_class
  crs     ~ b * lf
  support ~ c * crs + cprime * zs_class   # include direct path zs_class → support

  # Indirect effects
  ind_zs_lf_crs      := a * b           # zs_class → lf → crs
  ind_zs_lf_crs_sup  := a * b * c       # zs_class → lf → crs → support (full chain)

  # Total indirect effect to support (here only the chain)
  ind_total          := a * b * c

  # Total effect of zs_class on support
  total              := cprime + ind_total
'

fit_chain <- sem(
  model_chain,
  data      = df_cbzs_elg,
  se        = "bootstrap",   # bootstrap standard errors
  bootstrap = 10000          # number of bootstrap draws
)

# extract parameter estimates & bootstrap CIs
pe <- parameterEstimates(fit_chain, boot.ci.type = "perc")

# filter for the effects we defined via :=
effects <- pe %>%
  filter(label %in% c(
    "ind_zs_lf_crs",
    "ind_zs_lf_crs_sup",
    "ind_total",
    "cprime",
    "total"
  )) %>%
  transmute(
    Effect = c(
      "Indirect (zs_class → lf → crs)",
      "Indirect (zs_class → lf → crs → support)",
      "Total Indirect",
      "Direct (zs_class → support)",
      "Total Effect"
    ),
    Estimate = est,
    CI.lower = ci.lower,
    CI.upper = ci.upper,
    p.value  = pvalue
  )

kbl(effects) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
Effect Estimate CI.lower CI.upper p.value
Indirect (zs_class → lf → crs) 0.5701976 0.4632013 0.6808573 0.0000000
Indirect (zs_class → lf → crs → support) 0.0543285 0.0084079 0.1038072 0.0252126
Total Indirect 0.0108613 0.0014276 0.0236469 0.0565158
Direct (zs_class → support) 0.0108613 0.0014276 0.0236469 0.0565158
Total Effect 0.5810589 0.4753611 0.6894042 0.0000000
library(semPlot)

semPaths(
  fit_chain,
  whatLabels = "std",    # standardized estimates
  layout     = "circle", # or "tree"
  edge.label.cex = 1.0,
  sizeMan    = 7
)

Model 2

library(lavaan)

model_chain <- '
  # Regressions (paths)
  lf      ~ a * zs_class
  soli     ~ b * lf
  support ~ c * soli + cprime * zs_class   # include direct path zs_class → support

  # Indirect effects
  ind_zs_lf_soli      := a * b           # zs_class → lf → soli
  ind_zs_lf_soli_sup  := a * b * c       # zs_class → lf → soli → support (full chain)

  # Total indirect effect to support (here only the chain)
  ind_total          := a * b * c

  # Total effect of zs_class on support
  total              := cprime + ind_total
'

fit_chain <- sem(
  model_chain,
  data      = df_cbzs_elg,
  se        = "bootstrap",   # bootstrap standard errors
  bootstrap = 10000          # number of bootstrap draws
)

# extract parameter estimates & bootstrap CIs
pe <- parameterEstimates(fit_chain, boot.ci.type = "perc")

# filter for the effects we defined via :=
effects <- pe %>%
  filter(label %in% c(
    "ind_zs_lf_soli",
    "ind_zs_lf_soli_sup",
    "ind_total",
    "cprime",
    "total"
  )) %>%
  transmute(
    Effect = c(
      "Indirect (zs_class → lf → soli)",
      "Indirect (zs_class → lf → soli → support)",
      "Total Indirect",
      "Direct (zs_class → support)",
      "Total Effect"
    ),
    Estimate = est,
    CI.lower = ci.lower,
    CI.upper = ci.upper,
    p.value  = pvalue
  )

kbl(effects) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
Effect Estimate CI.lower CI.upper p.value
Indirect (zs_class → lf → soli) 0.4722501 0.3507958 0.5920686 0.0000000
Indirect (zs_class → lf → soli → support) 0.0201983 0.0023987 0.0439479 0.0581811
Total Indirect 0.0099690 0.0011164 0.0232949 0.0832412
Direct (zs_class → support) 0.0099690 0.0011164 0.0232949 0.0832412
Total Effect 0.4822191 0.3615680 0.5998534 0.0000000
library(semPlot)

semPaths(
  fit_chain,
  whatLabels = "std",    # standardized estimates
  layout     = "circle", # or "tree"
  edge.label.cex = 1.0,
  sizeMan    = 7
)

Parallel Mediations

Model 1

Predictor: CBZS

Mediators: (1) Solidarity; (2) Linked fate

Outcome: Support for policy

Bootstraps: 10,000

label est se ci.lower ci.upper pvalue
a1 0.2833509 0.0255958 0.2340638 0.3344074 0.0000000
a2 0.0897180 0.0382672 0.0139721 0.1639031 0.0190520
b1 0.4382793 0.1102498 0.2244587 0.6563350 0.0000703
b2 0.1247544 0.0731973 -0.0202865 0.2664268 0.0883144
c_prime 0.4767203 0.0628046 0.3554379 0.6018861 0.0000000
ind1 0.1241868 0.0313313 0.0640395 0.1860921 0.0000738
ind2 0.0111927 0.0077211 -0.0024218 0.0278782 0.1471603
ind_total 0.1353796 0.0307996 0.0753247 0.1955648 0.0000111
total 0.6120998 0.0532116 0.5081640 0.7156334 0.0000000

Model 2

Predictor: SDO

Mediators: (1) Solidarity; (2) Linked fate

Outcome: Support for policy

Bootstraps: 10,000

label est se ci.lower ci.upper pvalue
a1 -0.3465604 0.0346794 -0.4163718 -0.2803902 0.0000000
a2 -0.2135161 0.0433613 -0.2977140 -0.1275075 0.0000008
b1 0.3560386 0.1033276 0.1614976 0.5663214 0.0005695
b2 0.0425944 0.0633341 -0.0848050 0.1629275 0.5012430
c_prime -0.6384486 0.0673883 -0.7688629 -0.5000527 0.0000000
ind1 -0.1233889 0.0381134 -0.2062216 -0.0547681 0.0012062
ind2 -0.0090946 0.0138454 -0.0362276 0.0188246 0.5112661
ind_total -0.1324835 0.0380874 -0.2126738 -0.0616368 0.0005044
total -0.7709321 0.0539473 -0.8758705 -0.6641110 0.0000000

Model 3

Predictor: CBZS

Mediators: (1) Working-class identification; (2) Linked fate

Outcome: Solidarity

Bootstraps: 10,000

label est se ci.lower ci.upper pvalue
a1 2.1788811 0.8119571 0.5999725 3.7677138 0.0072858
a2 0.0897180 0.0380747 0.0155674 0.1662724 0.0184546
b1 0.0067664 0.0013975 0.0040565 0.0095220 0.0000013
b2 0.1689446 0.0326872 0.1050918 0.2343836 0.0000002
c_prime 0.2534504 0.0246704 0.2053925 0.3016180 0.0000000
ind1 0.0147432 0.0065501 0.0035648 0.0288325 0.0243966
ind2 0.0151574 0.0073756 0.0024462 0.0311305 0.0398732
ind_total 0.0299005 0.0097335 0.0121814 0.0504648 0.0021270
total 0.2833509 0.0253214 0.2341472 0.3339039 0.0000000

Moderators

SES as Moderator

Model 1

Predictor: CZSB

Moderator: SES

Outcome: Solidarity

m1 <- lm(soli ~ zs_class*ses_num,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 5.31 [4.47, 6.14] 12.54 484 < .001 NA
Zs class 0.19 [0.03, 0.34] 2.34 484 .020 0.229
Ses num -0.27 [-0.55, 0.01] -1.93 484 .054 0.017
Zs class \(\times\) Ses num 0.03 [-0.02, 0.08] 1.10 484 .270 0.003

Model 2

Predictor: CZSB

Moderator: SES

Outcome: Cross-race Solidarity

m1 <- lm(crs ~ zs_class*ses_num,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 5.60 [4.18, 7.02] 7.76 482 < .001 NA
Zs class -0.09 [-0.36, 0.17] -0.70 482 .486 0.055
Ses num -0.64 [-1.12, -0.17] -2.65 482 .008 0.005
Zs class \(\times\) Ses num 0.11 [0.02, 0.20] 2.30 482 .022 0.011
interact_plot(m1,
              pred = "zs_class",
              modx = "ses_num",
              interval = T)

Model 3

Predictor: CZSB

Moderator: SES

Outcome: Support for policy

m1 <- lm(support ~ zs_class*ses_num,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 2.76 [1.03, 4.49] 3.14 484 .002 NA
Zs class 0.47 [0.14, 0.79] 2.83 484 .005 0.240
Ses num -0.15 [-0.73, 0.43] -0.51 484 .608 0.004
Zs class \(\times\) Ses num 0.05 [-0.06, 0.17] 0.96 484 .339 0.002

SDO as Moderator

Model 1

Predictor: CZSB

Moderator: SDO

Outcome: Solidarity

m1 <- lm(soli ~ zs_class*SDO,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 5.76 [5.24, 6.27] 21.91 485 < .001 NA
Zs class 0.16 [0.07, 0.26] 3.38 485 < .001 0.252
SDO -0.29 [-0.44, -0.15] -3.96 485 < .001 0.164
Zs class \(\times\) SDO 0.01 [-0.02, 0.04] 0.42 485 .676 0.000

Model 2

Predictor: CZSB

Moderator: SDO

Outcome: Cross-race Solidarity

m1 <- lm(crs ~ zs_class*SDO,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 4.86 [3.93, 5.80] 10.22 483 < .001 NA
Zs class 0.15 [-0.02, 0.32] 1.71 483 .088 0.056
SDO -0.21 [-0.48, 0.05] -1.62 483 .106 0.077
Zs class \(\times\) SDO -0.02 [-0.07, 0.03] -0.75 483 .451 0.001

Model 3

Predictor: CZSB

Moderator: SDO

Outcome: Support for policy

m1 <- lm(support ~ zs_class*SDO,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 5.57 [4.53, 6.60] 10.57 485 < .001 NA
Zs class 0.27 [0.08, 0.46] 2.81 485 .005 0.282
SDO -0.77 [-1.06, -0.48] -5.24 485 < .001 0.198
Zs class \(\times\) SDO 0.04 [-0.02, 0.10] 1.32 485 .187 0.004

Working-class idendtification as moderator

Model 1

Predictor: CZSB

Moderator: Working-class identification

Outcome: Solidarity

m1 <- lm(soli ~ zs_class*class_id,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 3.78 [3.22, 4.33] 13.42 485 < .001 NA
Zs class 0.33 [0.22, 0.44] 5.80 485 < .001 0.231
Class id 0.01 [0.00, 0.02] 3.02 485 .003 0.059
Zs class \(\times\) Class id 0.00 [0.00, 0.00] -1.26 485 .207 0.003

Model 2

Predictor: CZSB

Moderator: Working-class identification

Outcome: Cross-race Solidarity

m1 <- lm(crs ~ zs_class*class_id,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 2.65 [1.69, 3.61] 5.40 483 < .001 NA
Zs class 0.31 [0.12, 0.50] 3.14 483 .002 0.054
Class id 0.02 [0.00, 0.03] 2.63 483 .009 0.034
Zs class \(\times\) Class id 0.00 [0.00, 0.00] -1.34 483 .180 0.004

Model 3

Predictor: CZSB

Moderator: Working-class identification

Outcome: Support for policy

m1 <- lm(support ~ zs_class*class_id,data = df_cbzs_elg)

eta_table <- eta_squared(m1)
etas_for_table <- c(NA,eta_table$Eta2)
apa_lm <- apa_print(m1)
table_for_print <- apa_lm$table %>% 
  mutate(eta2 = round(etas_for_table,3))
 
kbl(table_for_print) %>% 
  kable_styling(bootstrap_options = "hover",
                full_width = F,
                position = "left")
term estimate conf.int statistic df p.value eta2
Intercept 1.56 [0.39, 2.73] 2.63 485 .009 NA
Zs class 0.74 [0.51, 0.97] 6.21 485 < .001 0.240
Class id 0.01 [0.00, 0.03] 1.53 485 .126 0.002
Zs class \(\times\) Class id 0.00 [-0.01, 0.00] -1.29 485 .199 0.003