Visit project site

Visit Project Site

Visit project site

▲ Top

R session Environment

Toggle details regarding my R environment: packages and machine

Loading packages:

options(repos = c(CRAN = "https://cloud.r-project.org"))

if(!require("dplyr")) install.packages("dplyr")
if(!require("tidyr")) install.packages("tidyr")
if(!require("stringr")) install.packages("stringr")
if(!require("ggplot2")) install.packages("ggplot2")
if(!require("emmeans")) install.packages("emmeans")
if(!require("data.table")) install.packages("data.table")
if(!require("PerformanceAnalytics")) install.packages("PerformanceAnalytics")
if(!require("interactions")) install.packages("interactions")
if(!require("car")) install.packages("car")
if(!require("effectsize")) install.packages("effectsize")
if(!require("RColorBrewer")) install.packages("RColorBrewer")
if(!require(" ")) install.packages(" ")
if(!require("robustbase")) install.packages("robustbase") # For Minimum Covariance distance
if(!require("knitr")) install.packages("knitr")
if(!require("gt")) install.packages("gt")
if(!require("lavaan")) install.packages("lavaan")
if(!require("ppcor")) install.packages("ppcor") # For partial correlations
if(!require("sjPlot")) install.packages("sjPlot")
if(!require("MASS")) install.packages("MASS")
if(!require("Matrix")) install.packages("Matrix")
if(!require("ggrepel")) install.packages("ggrepel")
if(!require("corrplot")) install.packages("corrplot")
if(!require("plotly")) install.packages("plotly") # Interactive plots <3
if(!require("performance")) install.packages("performance") 
if(!require("partR2")) install.packages("partR2") 
if(!require("multcomp")) install.packages("multcomp") 
if(!require("parameters")) install.packages("parameters") 
if(!require("lme4")) install.packages("lme4") 
if(!require("lmerTest")) install.packages("lmerTest") 
if(!require("MuMIn")) install.packages("MuMIn") 
if(!require("gridExtra")) install.packages("gridExtra") # Plot layout
if(!require("purrr")) install.packages("purrr") # Really cool 3D scatterplots
if(!require("olsrr")) install.packages("olsrr") # 
if(!require("modelsummary")) install.packages("modelsummary") # 

Session info:

sessionInfo()
## R version 4.5.1 (2025-06-13)
## Platform: aarch64-apple-darwin20
## Running under: macOS Sonoma 14.7.1
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRblas.0.dylib 
## LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1
## 
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
## 
## time zone: Europe/London
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] modelsummary_2.5.0         olsrr_0.6.1               
##  [3] purrr_1.1.0                gridExtra_2.3             
##  [5] MuMIn_1.48.11              lmerTest_3.1-3            
##  [7] lme4_1.1-37                parameters_0.28.2         
##  [9] multcomp_1.4-28            TH.data_1.1-4             
## [11] survival_3.8-3             mvtnorm_1.3-3             
## [13] partR2_0.9.2               performance_0.15.1        
## [15] plotly_4.11.0              corrplot_0.95             
## [17] ggrepel_0.9.6              Matrix_1.7-3              
## [19] sjPlot_2.9.0               ppcor_1.1                 
## [21] MASS_7.3-65                lavaan_0.6-20             
## [23] gt_1.1.0                   knitr_1.50                
## [25] robustbase_0.99-6          RColorBrewer_1.1-3        
## [27] effectsize_1.0.1           car_3.1-3                 
## [29] carData_3.0-5              interactions_1.2.0        
## [31] PerformanceAnalytics_2.0.8 xts_0.14.1                
## [33] zoo_1.8-14                 data.table_1.17.8         
## [35] emmeans_1.11.2-8           ggplot2_4.0.0             
## [37] stringr_1.5.2              tidyr_1.3.1               
## [39] dplyr_1.1.4               
## 
## loaded via a namespace (and not attached):
##  [1] Rdpack_2.6.4        mnormt_2.1.1        sandwich_3.1-1     
##  [4] rlang_1.1.6         magrittr_2.0.4      furrr_0.3.1        
##  [7] compiler_4.5.1      vctrs_0.6.5         quadprog_1.5-8     
## [10] pkgconfig_2.0.3     fastmap_1.2.0       backports_1.5.0    
## [13] pbivnorm_0.6.0      pander_0.6.6        rmarkdown_2.29     
## [16] nloptr_2.2.1        xfun_0.53           cachem_1.1.0       
## [19] jsonlite_2.0.0      goftest_1.2-3       broom_1.0.10       
## [22] parallel_4.5.1      R6_2.6.1            tables_0.9.31      
## [25] bslib_0.9.0         stringi_1.8.7       parallelly_1.45.1  
## [28] boot_1.3-31         numDeriv_2016.8-1.1 jquerylib_0.1.4    
## [31] estimability_1.5.1  Rcpp_1.1.0          splines_4.5.1      
## [34] tidyselect_1.2.1    rstudioapi_0.17.1   abind_1.4-8        
## [37] yaml_2.3.10         codetools_0.2-20    listenv_0.9.1      
## [40] lattice_0.22-7      tibble_3.3.0        withr_3.0.2        
## [43] bayestestR_0.17.0   S7_0.2.0            coda_0.19-4.1      
## [46] evaluate_1.0.5      future_1.67.0       xml2_1.4.0         
## [49] jtools_2.3.0        pillar_1.11.1       nortest_1.0-4      
## [52] stats4_4.5.1        reformulas_0.4.1    insight_1.4.2      
## [55] generics_0.1.4      scales_1.4.0        minqa_1.2.8        
## [58] globals_0.18.0      xtable_1.8-4        glue_1.8.0         
## [61] lazyeval_0.2.2      tools_4.5.1         forcats_1.0.0      
## [64] fs_1.6.6            grid_4.5.1          rbibutils_2.3      
## [67] datawizard_1.2.0    nlme_3.1-168        Formula_1.2-5      
## [70] cli_3.6.5           viridisLite_0.4.2   gtable_0.3.6       
## [73] DEoptimR_1.1-4      broom.mixed_0.2.9.6 sass_0.4.10        
## [76] digest_0.6.37       htmlwidgets_1.6.4   farver_2.1.2       
## [79] htmltools_0.5.8.1   lifecycle_1.0.4     httr_1.4.7

What we know of occupational heroism

Some occupations are more likely than others to be perceived as being heroic. We have consistently shown that these occupations were qualified by high helpfulness perception and high exposure do danger perception. However, a central part of our research project is to identify the consequences (both positive and negative) of having one’s occupation perceived as heroic. In a previous study, we observed that Heroism in Nurses, Firefighters, Soldiers, Psychiatrists, Underwater welders, and Journalists (and Police Officers in the context of scale validation) predicted:

  • H1: Higher gratitude toward workers
  • H2: Reduced criticism acceptability
  • H3: increased support for demands from the workers (In contrast to our initial hypotheses)
  • H4: increased perception of victim-related aspects (e.g., suffering, vulnerability) (In contrast to our initial hypotheses)
  • H5: Increased impunity and motivation to de-regulate the occupation

The present study seeks to replicate this, but also to extend the previous study by analysing the effect of the salience of heroic rhetoric in the media on these outcomes. In order to investigate this heroic rhetoric, we will replicate the findings observed for soldier, during the celebratons of Remembrance day, which celebrates veterans and soldiers.

420 participants (Representative UK residents sample) will complete the same scale but during the week preceding remembrance day (Nov 11). In contrast, the original sample wa collected around September 18th.

The study was registered online: https://osf.io/grjwu/overview

The study gained ethical approval from the University of Kent.

Materials and Data is available online: https://osf.io/grjwu/files

NOTE: this is an rmd associated to the registration of the study. Although the ‘original analyses’ is based on the previously collected sample (September sample), the part corresponding to our reproduction and to the test of the Remembrance day effect is based on simulated data.


Data Wrangling

We have two data frames: the original one (“Data_Correlational_C1.csv”) and the new one – In this document simulated data using the Qualtrics ‘Generate Test Responde’ option.

Both data were collected using very similar surveys – as such the data wrangling process is quite similar.

Toggle details of the data wrangling procedure

Adapt the path of the data file to your local path. We recommend putting all the files in a single folder containing the rmd file.

September sample:

DF <- read.csv("Data_Correlational_C1.csv", comment.char="#")
DF <- subset(DF, DF$age != "")
Demographics <- DF[-c(1:2), c(269:278, 281)]
DF <- DF[-c(1:2), c(23, 25:44, 49:268, 281, 271)]

prolOK <- read.csv("Prolific_Export_C1.csv")

AwaitR<- subset(prolOK, prolOK$Status == "APPROVED")

DF <- subset(DF, DF$prolID != setdiff(DF$prolID, AwaitR$Participant.id))


Journ <- subset(DF, DF$Cond == "Journalist")
Journ <- Journ[, colSums(!is.na(Journ) & Journ != "") > 0 | grepl("SpecCritJ", names(Journ)), drop = FALSE]


Fire <- subset(DF, DF$Cond == "Firefighter")
Fire <- Fire[, colSums(!is.na(Fire) & Fire != "") > 0 | grepl("SpecCritF", names(Fire)), drop = FALSE]

Nurs <- subset(DF, DF$Cond == "Nurse")
Nurs <- Nurs[, colSums(!is.na(Nurs) & Nurs != "") > 0 | grepl("SpecCritN", names(Nurs)), drop = FALSE]

Psych <- subset(DF, DF$Cond == "Psych")
Psych <- Psych[, colSums(!is.na(Psych) & Psych != "") > 0 | grepl("SpecCritP", names(Psych)), drop = FALSE]

Soldier <- subset(DF, DF$Cond == "Soldier")
Soldier <- Soldier[, colSums(!is.na(Soldier) & Soldier != "") > 0 | grepl("SpecCritS", names(Soldier)), drop = FALSE]

Weld <- subset(DF, DF$Cond == "Weld")
Weld <- Weld[, colSums(!is.na(Weld) & Weld != "") > 0 | grepl("SpecCritW", names(Weld)), drop = FALSE]


# 1) Use Weld as the "template" for column names
template_names <- names(Weld)  # 47 names

# 2) Helper that renames a data frame by POSITION to match Weld
rename_like_weld <- function(df, template = template_names) {
  # Sanity checks to avoid silent disasters
  if (ncol(df) != length(template)) {
    stop("Column count mismatch: this df has ", ncol(df),
         " columns but template has ", length(template), ".")
  }
  # Copy names by position
  names(df) <- template
  df
}

# 3) Put all your data frames into a named list
dfs <- list(
  Weld    = Weld,
  Journ   = Journ,
  Nurs    = Nurs,
  Psych   = Psych,
  Soldier = Soldier,
  Fire    = Fire
)

# 4) Harmonise names, then stack vertically with a source column
stacked <- dfs %>%
  purrr::map(rename_like_weld) %>%          # harmonise titles to Weld's names
  bind_rows(.id = "dataset")         # adds the dataset name as the first column

# Result: 48 columns total (1 "dataset" + 47 harmonised vars)
# and nrow(stacked) == sum(nrow(.) for each df)



#------------------------------------------------------------
# 0) Small helpers
#------------------------------------------------------------

# Replace "" and pure whitespace with NA
na_empty <- function(x) {
  x <- if (is.character(x)) str_trim(x) else x
  ifelse(is.character(x) & x == "", NA, x)
}

# Map character -> numeric using a named vector `key`.
# Unknown labels become NA (so you’ll notice and can fix the key).
map_to_num <- function(x, key) {
  x <- na_empty(x)
  # exact match after trimming
  out <- unname(key[ match(x, names(key)) ])
  # if x is already numeric-like (e.g., "5"), keep it
  suppressWarnings({
    out_numlike <- as.numeric(x)
  })
  out[is.na(out) & !is.na(out_numlike)] <- out_numlike[is.na(out) & !is.na(out_numlike)]
  as.numeric(out)
}

# Pull the first integer in a string like "7 - Strongly agree" -> 7
first_int <- function(x) {
  x <- na_empty(x)
  as.numeric(str_extract(x, "\\d+"))
}

# Checkbox columns: any non-empty text -> 1, blank/NA -> 0
checkbox01 <- function(x) as.numeric(!is.na(na_empty(x)))


#------------------------------------------------------------
# 1) Define your scale keys (edit here if wording differs)
#------------------------------------------------------------

agree7 <- c(
  "Strongly disagree"             = 1,
  "Moderately disagree"           = 2,
  "Slightly disagree"             = 3,
  "Neither disagree, nor agree"   = 4,
  "Slightly agree"                = 5,
  "Moderately agree"              = 6,
  "Strongly agree"                = 7
)

likely7 <- c(
  "Very unlikely"                 = 1,
  "Quite unlikely"                = 2,
  "Slightly unlikely"             = 3,
  "Neither likely, nor unlikely"  = 4,
  "Slightly likely"               = 5,
  "Quite likely"                  = 6,
  "Very likely"                   = 7
)

# Frequency (seen in GenSuppDemWeld_*): includes "Always" at the top end
freq7 <- c(
  "Never"            = 1,
  "Very Rarely"      = 2,
  "Rarely"           = 3,  
  "Occasionally"     = 4,
  "Frequently"       = 5,
  "Very frequently"  = 6,
  "Always"           = 7
)

# Intensity (seen in GenVictimWeld_*): ordered least -> most
intensity7 <- c(
  "Not at all"   = 1,
  "Very little"  = 2,
  "A little"     = 3,
  "Somewhat"     = 4,
  "Quite a bit"  = 5,
  "A lot"        = 6,
  "Very much"    = 7 #!!!!!

)

# Valence (Q239). Your data shows: Very/Quite negative, Somewhat/Quite/Very positive.
# Map to 1..5 (negative -> positive). If you have "Neither", add it as 3.
valence5 <- c(
  "Very negative"      = 1,
  "Quite negative"            = 2, 
  "Somewhat negative"     = 3, #!!!!!!
  "Neutral"  = 4,
  "Somewhat positive"     = 5,  
  "Quite positive"      = 6,
  "Very positive" = 7
)

# Gratitude single item (GenGratWeld) appears to use intensity-style words
grat7 <- intensity7


#------------------------------------------------------------
# 2) Identify column groups by name (using your Weld names)
#    We only target cols 2:41, but naming groups is clearer & safer.
#------------------------------------------------------------

agree_cols <- c(
  # Crit accept (general)
  "GenCritW_1","GenCritW_2","GenCritW_3",
  # Specific victims/villains attitudes (agree-type)
  "SpecSuppW_1","SpecSuppW_2","SpecSuppW_3",
  "SpecVictimW_1","SpecVictimW_2","SpecVictimW_3",
  "GenImpW_1","GenImpW_2","GenImpW_3","GenImpW_4",
  "SpecImpW_1","SpecImpW_2","SpecImpW_3","SpecImpW_4"
)

likely_cols <- c("SpecGratW_1","SpecGratW_2","SpecGratW_3")

freq_cols <- c("GenSuppW_1","GenSuppW_2")

intensity_cols <- c("GenVictW_1","GenVictW_2","GenVictW_3","GeneralGratW")

# Checkbox blocks (present/blank -> 1/0)
checkbox_cols <- c(
  "SpecCritW1_1","SpecCritW1_2","SpecCritW1_3","SpecCritW1_9","SpecCritW1_4",
  "SpecCritW2_1","SpecCritW2_2","SpecCritW2_3","SpecCritW2_9","SpecCritW2_4"
)

# Hybrid numeric-label items
hybrid_num_cols <- c("HW_1","DangerHelpW_1","DangerHelpW_2")

# Valence item
valence_cols <- c("AttW")


#------------------------------------------------------------
# 3) Apply mappings to your stacked data (called `stacked`)
#    We leave identifying/meta columns alone: dataset, prolID, gender*, age, Q52, Attentive, Cond
#------------------------------------------------------------

stacked_num <- stacked %>%
  mutate(
    # scales
    across(all_of(agree_cols),    ~ map_to_num(.x, agree7)),
    across(all_of(likely_cols),   ~ map_to_num(.x, likely7)),
    across(all_of(freq_cols),     ~ map_to_num(.x, freq7)),
    across(all_of(intensity_cols),~ map_to_num(.x, intensity7)),
    across(all_of(valence_cols),  ~ map_to_num(.x, valence5)),

    # checkbox-style -> 0/1
    across(all_of(checkbox_cols), checkbox01),

    # hybrid numerics (e.g., "7 - Strongly agree" or just "5")
    across(all_of(hybrid_num_cols), first_int),

    )


#colnames(stacked_num)

# Check results by uncommenting lines below:
#compare_freqs <- function(char_df, num_df, col) {
#    cat("\n\n###", col, "###\n")
#    
#    char_counts <- as.data.frame(table(char_df[[col]]), stringsAsFactors = FALSE)
#    num_counts  <- as.data.frame(table(num_df[[col]]), stringsAsFactors = FALSE)
#    
#    names(char_counts) <- c("label", "char_n")
#    names(num_counts)  <- c("num_value", "num_n")
#    
#    # Make sure numeric column is actually numeric for sorting
#    suppressWarnings(num_counts$num_value <- as.numeric(as.character(num_counts$num_value)))
#    num_counts <- num_counts[order(num_counts$num_value), ]
#    
#    # Match by frequency
#    matched_labels <- character(nrow(num_counts))
#    matched_char_n <- integer(nrow(num_counts))
#    errors <- character(0)
#    
#    for (i in seq_len(nrow(num_counts))) {
#        n <- num_counts$num_n[i]
#        match_rows <- which(char_counts$char_n == n)
#        
#        if (length(match_rows) == 1) {
#            matched_labels[i] <- char_counts$label[match_rows]
#            matched_char_n[i] <- char_counts$char_n[match_rows]
#        } else if (length(match_rows) > 1) {
#            # ambiguous match → keep first
#            matched_labels[i] <- char_counts$label[match_rows[1]]
#            matched_char_n[i] <- char_counts$char_n[match_rows[1]]
#        } else {
#            # no match → flag error
#            matched_labels[i] <- NA
#            matched_char_n[i] <- NA
#            errors <- c(errors, paste0("No matching label for numeric value ", num_counts$num_value[i],
#                                       " (n=", n, ")"))
#        }
#    }
#    
#    comparison <- data.frame(
#        num_value = num_counts$num_value,
#        num_n = num_counts$num_n,
#        label = matched_labels,
#        char_n = matched_char_n,
#        stringsAsFactors = FALSE
#    )
#    
#    print(comparison, row.names = FALSE)
#    
#    if (length(errors) > 0) {
#        cat("⚠️  Errors:\n", paste0(" - ", errors, collapse = "\n"), "\n")
#    }
#}
#
## Columns to check
#cols_to_check <- setdiff(names(stacked), c("dataset", "Q90", "Attentive", "Cond"))
#
## Run it
#for (col in cols_to_check) {
#    compare_freqs(stacked, stacked_num, col)
#}



# Remove ATTENTION CHECKS
stacked_num <- stacked_num[, -c(21, 31, 33)]




# Severity mapping: column suffix -> numeric severity
severity_map <- c("1" = 1, "2" = 2, "3" = 3, "9" = 4, "4" = 5)

# Helper: collapse one multi-response item (five 0/1 columns) into a single 1..5 severity
most_severe <- function(data, stem, map = severity_map) {
  # Build the expected column names for this stem
  suf  <- names(map)                           # c("1","2","3","9","4")
  cols <- paste0(stem, "_", suf)

  # Safety checks: are all expected columns present?
  missing <- setdiff(cols, names(data))
  if (length(missing)) {
    stop("Missing columns for '", stem, "': ", paste(missing, collapse = ", "))
  }

  # Grab the indicators as a numeric matrix (0/1/NA)
  M <- as.matrix(data[cols])

  # Treat 0 as "not selected" -> NA, so they don't affect the max
  M[M == 0] <- NA

  # Weight each column by its severity value (1..5) so max() = most severe chosen
  sev <- as.numeric(map)
  W   <- sweep(M, 2, sev, `*`)   # column j multiplied by sev[j]

  # Row-wise max ignoring NAs; if all NA -> NA
  out <- apply(W, 1, function(x) {
    m <- suppressWarnings(max(x, na.rm = TRUE))
    if (is.infinite(m)) NA_real_ else m
  })

  # Return as integer 1..5 (or NA if nothing ticked)
  as.integer(out)
}

# Apply to your two stems
stems <- c("SpecCritW1", "SpecCritW2")

for (s in stems) {
  newcol <- paste0(s, "_severity")
  stacked_num[[newcol]] <- most_severe(stacked_num, s)
}



# Define items per construct (uncomment 1st line and comment 2nd to include Single item)
#gratitude_items <- grep("SpecGrat|Single|Support_Gov", names(stacked_num), value = TRUE)
gratitude_items <- grep("GeneralGrat|SpecGrat", names(stacked_num), value = TRUE)
criticism_items <- grep("SpecCritW1_severity|SpecCritW2_severity|GenCrit", names(stacked_num), value = TRUE)
demand_items    <- grep("SpecSupp|GenSupp", names(stacked_num), value = TRUE)
victim_items    <- grep("GenVict|SpecVictim", names(stacked_num), value = TRUE)
violation_items <- grep("GenImp|SpecImp", names(stacked_num), value = TRUE)

# Store all subscales in a named list for looping
scales <- list(
  Gratitude = gratitude_items,
  Criticism = criticism_items,
  Demands = demand_items,
  Victimhood = victim_items,
  Violations = violation_items
)


### Reversing items
stacked_num$SpecVictimW_1<- 8 -stacked_num$SpecVictimW_1

stacked_num$GenSuppW_1<- 8 -stacked_num$GenSuppW_1

stacked_num$GenCritW_1<- 8 -stacked_num$GenCritW_1
stacked_num$GenCritW_2<- 8 -stacked_num$GenCritW_2
stacked_num$GenCritW_3<- 8 -stacked_num$GenCritW_3

stacked_num$SpecCritW1_severity<- 6 -stacked_num$SpecCritW1_severity
stacked_num$SpecCritW2_severity<- 6 -stacked_num$SpecCritW2_severity



### Two, perfectly defined subscale: general vs specific
GEN_gratitude_items <- grep("GeneralGratW", names(stacked_num), value = TRUE)
SPEC_gratitude_items <- grep("SpecGratW", names(stacked_num), value = TRUE)



## 3 factors: 1 clear general with only the 3 first that are good:
criticism_items_G <- grep("GenCritW", names(stacked_num), value = TRUE)
# Only the 'chill' posts are good. the two extreme ones measuring something else.
criticism_items_S <- grep("severity", names(stacked_num), value = TRUE)



# Let's keep the General that is distinct from specific (focus on protesting)
DemandSupp_G <- grep("GenSuppW", names(stacked_num), value = TRUE)


DemandSupp_S <- grep("SpecSuppW", names(stacked_num), value = TRUE)


# General works fine
Victim_G  <- grep("GenVictW", names(stacked_num), value = TRUE)
# Same for Specific
Victim_S  <- grep("SpecVictimW", names(stacked_num), value = TRUE)



# Here also: general works fine

Villain_G  <- grep("GenImpW", names(stacked_num), value = TRUE)

## But specific not so much...
Villain_S <- grep("SpecImpW", names(stacked_num), value = TRUE)




scales <- list(
  Gratitude_G = GEN_gratitude_items,
  Gratitude_S = SPEC_gratitude_items,

  criticism_items_G = criticism_items_G,
  criticism_items_S = criticism_items_S,

  DemandSupp_G  = DemandSupp_G,
  DemandSupp_S = DemandSupp_S,# Only 2 items

  Victim_G  = Victim_G ,
  Victim_S = Victim_S,

  Villain_G = Villain_G,
  Villain_S = Villain_S
)


for (name in names(scales)) {
  items <- scales[[name]]
  df_subset <- stacked_num[items]
  stacked_num[[paste0(name, "_mean")]] <- rowMeans(df_subset, na.rm = TRUE)
}


colnames(stacked_num)
##  [1] "dataset"                "prolID"                 "SpecGratW_1"           
##  [4] "SpecGratW_2"            "SpecGratW_3"            "GeneralGratW"          
##  [7] "GenCritW_1"             "GenCritW_2"             "GenCritW_3"            
## [10] "SpecCritW1_1"           "SpecCritW1_2"           "SpecCritW1_3"          
## [13] "SpecCritW1_9"           "SpecCritW1_4"           "SpecCritW2_1"          
## [16] "SpecCritW2_2"           "SpecCritW2_3"           "SpecCritW2_9"          
## [19] "SpecCritW2_4"           "SpecSuppW_1"            "SpecSuppW_3"           
## [22] "GenVictW_1"             "GenVictW_2"             "GenVictW_3"            
## [25] "SpecVictimW_1"          "SpecVictimW_2"          "SpecVictimW_3"         
## [28] "GenImpW_1"              "GenImpW_2"              "GenImpW_4"             
## [31] "SpecImpW_2"             "SpecImpW_3"             "SpecImpW_4"            
## [34] "GenSuppW_1"             "GenSuppW_2"             "HW_1"                  
## [37] "DangerHelpW_1"          "DangerHelpW_2"          "AttW"                  
## [40] "Cond"                   "age"                    "SpecCritW1_severity"   
## [43] "SpecCritW2_severity"    "Gratitude_G_mean"       "Gratitude_S_mean"      
## [46] "criticism_items_G_mean" "criticism_items_S_mean" "DemandSupp_G_mean"     
## [49] "DemandSupp_S_mean"      "Victim_G_mean"          "Victim_S_mean"         
## [52] "Villain_G_mean"         "Villain_S_mean"
#scale_scores <- stacked_num[, c(40, 2, 48:57, 36:39, 34, 35)]
scale_scores <- stacked_num
scale_scores$Cond <- as.factor(scale_scores$Cond)

scale_scores$Heroism <- scale_scores$HW_1
scale_scores$Attitude <- (scale_scores$AttW)
scale_scores$Danger <- (scale_scores$DangerHelpW_1)
scale_scores$Help <- (scale_scores$DangerHelpW_2)

Poppy1_DF <- subset(scale_scores, scale_scores$Cond == "Soldier")

scale_scores <- subset(scale_scores,
                       scale_scores$Cond == "Soldier" | 
                         scale_scores$Cond == "Nurse" |
                         scale_scores$Cond == "Weld")


contrasts(scale_scores$Cond) <- contr.sum(nlevels(scale_scores$Cond))
contrasts(scale_scores$Cond) # 
##             [,1] [,2] [,3] [,4] [,5]
## Firefighter    1    0    0    0    0
## Journalist     0    1    0    0    0
## Nurse          0    0    1    0    0
## Psych          0    0    0    1    0
## Soldier        0    0    0    0    1
## Weld          -1   -1   -1   -1   -1
scale_scores$Cond <- droplevels(scale_scores$Cond)

November sample:

DF <- read.csv("~/Downloads/Poppy+Part+2+revised+-+Hero+Project_November+7,+2025_06.33.csv", comment.char="#")
DF <- subset(DF, DF$age != "")
#colnames(DF)
Demographics2 <- DF[-c(1:2), c(150:160)]
DF <- DF[-c(1:2), c(23, 26:45, 50:149, 162, 163, 152)]

#prolOK <- read.csv("Prolific_Export_C1.csv")

#AwaitR<- subset(prolOK, prolOK$Status == "APPROVED")

#DF <- subset(DF, DF$prolID != setdiff(DF$prolID, AwaitR$Participant.id))



Nurs <- subset(DF, DF$Cond == "Nurse")
Nurs <- Nurs[, colSums(!is.na(Nurs) & Nurs != "") > 0 | grepl("SpecCritN", names(Nurs)), drop = FALSE]


Soldier <- subset(DF, DF$Cond == "Soldier")
Soldier <- Soldier[, colSums(!is.na(Soldier) & Soldier != "") > 0 | grepl("SpecCritS", names(Soldier)), drop = FALSE]

Weld <- subset(DF, DF$Cond == "Weld")
Weld <- Weld[, colSums(!is.na(Weld) & Weld != "") > 0 | grepl("SpecCritW", names(Weld)), drop = FALSE]


# 1) Use Weld as the "template" for column names
template_names <- names(Weld)  # 47 names

# 2) Helper that renames a data frame by POSITION to match Weld
rename_like_weld <- function(df, template = template_names) {
  # Sanity checks to avoid silent disasters
  if (ncol(df) != length(template)) {
    stop("Column count mismatch: this df has ", ncol(df),
         " columns but template has ", length(template), ".")
  }
  # Copy names by position
  names(df) <- template
  df
}

# 3) Put all your data frames into a named list
dfs <- list(
  Weld    = Weld,
  Nurs    = Nurs,
  Soldier = Soldier
)

# 4) Harmonise names, then stack vertically with a source column
stacked <- dfs %>%
  purrr::map(rename_like_weld) %>%          # harmonise titles to Weld's names
  bind_rows(.id = "dataset")         # adds the dataset name as the first column

# Result: 48 columns total (1 "dataset" + 47 harmonised vars)
# and nrow(stacked) == sum(nrow(.) for each df)



#------------------------------------------------------------
# 0) Small helpers
#------------------------------------------------------------

# Replace "" and pure whitespace with NA
na_empty <- function(x) {
  x <- if (is.character(x)) str_trim(x) else x
  ifelse(is.character(x) & x == "", NA, x)
}

# Map character -> numeric using a named vector `key`.
# Unknown labels become NA (so you’ll notice and can fix the key).
map_to_num <- function(x, key) {
  x <- na_empty(x)
  # exact match after trimming
  out <- unname(key[ match(x, names(key)) ])
  # if x is already numeric-like (e.g., "5"), keep it
  suppressWarnings({
    out_numlike <- as.numeric(x)
  })
  out[is.na(out) & !is.na(out_numlike)] <- out_numlike[is.na(out) & !is.na(out_numlike)]
  as.numeric(out)
}

# Pull the first integer in a string like "7 - Strongly agree" -> 7
first_int <- function(x) {
  x <- na_empty(x)
  as.numeric(str_extract(x, "\\d+"))
}

# Checkbox columns: any non-empty text -> 1, blank/NA -> 0
checkbox01 <- function(x) as.numeric(!is.na(na_empty(x)))


#------------------------------------------------------------
# 1) Define your scale keys (edit here if wording differs)
#------------------------------------------------------------

agree7 <- c(
  "Strongly disagree"             = 1,
  "Moderately disagree"           = 2,
  "Slightly disagree"             = 3,
  "Neither disagree, nor agree"   = 4,
  "Slightly agree"                = 5,
  "Moderately agree"              = 6,
  "Strongly agree"                = 7
)

likely7 <- c(
  "Very unlikely"                 = 1,
  "Quite unlikely"                = 2,
  "Slightly unlikely"             = 3,
  "Neither likely, nor unlikely"  = 4,
  "Slightly likely"               = 5,
  "Quite likely"                  = 6,
  "Very likely"                   = 7
)

# Frequency (seen in GenSuppDemWeld_*): includes "Always" at the top end
freq7 <- c(
  "Never"            = 1,
  "Very Rarely"      = 2,
  "Rarely"           = 3,  
  "Occasionally"     = 4,
  "Frequently"       = 5,
  "Very frequently"  = 6,
  "Always"           = 7
)

# Intensity (seen in GenVictimWeld_*): ordered least -> most
intensity7 <- c(
  "Not at all"   = 1,
  "Very little"  = 2,
  "A little"     = 3,
  "Somewhat"     = 4,
  "Quite a bit"  = 5,
  "A lot"        = 6,
  "Very much"    = 7 #!!!!!

)

# Valence (Q239). Your data shows: Very/Quite negative, Somewhat/Quite/Very positive.
# Map to 1..5 (negative -> positive). If you have "Neither", add it as 3.
valence5 <- c(
  "Very negative"      = 1,
  "Quite negative"            = 2, 
  "Somewhat negative"     = 3, #!!!!!!
  "Neutral"  = 4,
  "Somewhat positive"     = 5,  
  "Quite positive"      = 6,
  "Very positive" = 7
)

# Gratitude single item (GenGratWeld) appears to use intensity-style words
grat7 <- intensity7


#------------------------------------------------------------
# 2) Identify column groups by name (using your Weld names)
#    We only target cols 2:41, but naming groups is clearer & safer.
#------------------------------------------------------------

agree_cols <- c(
  # Crit accept (general)
  "GenCritW_1","GenCritW_2","GenCritW_3",
  # Specific victims/villains attitudes (agree-type)
  "SpecSuppW_1","SpecSuppW_2","SpecSuppW_3",
  "SpecVictimW_1","SpecVictimW_2","SpecVictimW_3",
  "GenImpW_1","GenImpW_2","GenImpW_3","GenImpW_4",
  "SpecImpW_1","SpecImpW_2","SpecImpW_3","SpecImpW_4"
)

likely_cols <- c("SpecGratW_1","SpecGratW_2","SpecGratW_3")

freq_cols <- c("GenSuppW_1","GenSuppW_2")

intensity_cols <- c("GenVictW_1","GenVictW_2","GenVictW_3","GeneralGratW")

# Checkbox blocks (present/blank -> 1/0)
checkbox_cols <- c(
  "SpecCritW1_1","SpecCritW1_2","SpecCritW1_3","SpecCritW1_9","SpecCritW1_4",
  "SpecCritW2_1","SpecCritW2_2","SpecCritW2_3","SpecCritW2_9","SpecCritW2_4"
)

# Hybrid numeric-label items
hybrid_num_cols <- c("HW_1","DangerHelpW_1","DangerHelpW_2")

# Valence item
valence_cols <- c("AttW")


#------------------------------------------------------------
# 3) Apply mappings to your stacked data (called `stacked`)
#    We leave identifying/meta columns alone: dataset, prolID, gender*, age, Q52, Attentive, Cond
#------------------------------------------------------------

stacked_num <- stacked %>%
  mutate(
    # scales
    across(all_of(agree_cols),    ~ map_to_num(.x, agree7)),
    across(all_of(likely_cols),   ~ map_to_num(.x, likely7)),
    across(all_of(freq_cols),     ~ map_to_num(.x, freq7)),
    across(all_of(intensity_cols),~ map_to_num(.x, intensity7)),
    across(all_of(valence_cols),  ~ map_to_num(.x, valence5)),

    # checkbox-style -> 0/1
    across(all_of(checkbox_cols), checkbox01),

    # hybrid numerics (e.g., "7 - Strongly agree" or just "5")
    across(all_of(hybrid_num_cols), first_int),

    )


colnames(stacked_num)
##  [1] "dataset"       "Q90"           "SpecGratW_1"   "SpecGratW_2"  
##  [5] "SpecGratW_3"   "GeneralGratW"  "GenCritW_1"    "GenCritW_2"   
##  [9] "GenCritW_3"    "SpecCritW1_1"  "SpecCritW1_2"  "SpecCritW1_3" 
## [13] "SpecCritW1_9"  "SpecCritW1_4"  "SpecCritW2_1"  "SpecCritW2_2" 
## [17] "SpecCritW2_3"  "SpecCritW2_9"  "SpecCritW2_4"  "SpecSuppW_1"  
## [21] "SpecSuppW_2"   "SpecSuppW_3"   "GenVictW_1"    "GenVictW_2"   
## [25] "GenVictW_3"    "SpecVictimW_1" "SpecVictimW_2" "SpecVictimW_3"
## [29] "GenImpW_1"     "GenImpW_2"     "GenImpW_3"     "GenImpW_4"    
## [33] "SpecImpW_1"    "SpecImpW_2"    "SpecImpW_3"    "SpecImpW_4"   
## [37] "GenSuppW_1"    "GenSuppW_2"    "HW_1"          "DangerHelpW_1"
## [41] "DangerHelpW_2" "AttW"          "Attentive"     "Cond"         
## [45] "age"
# Remove ATTENTION CHECKS: GenImpW_3 ; SpecSuppW_2; SpecImpW_1
stacked_num <- stacked_num[, -c(21, 31, 33)]




# Severity mapping: column suffix -> numeric severity
severity_map <- c("1" = 1, "2" = 2, "3" = 3, "9" = 4, "4" = 5)

# Helper: collapse one multi-response item (five 0/1 columns) into a single 1..5 severity
most_severe <- function(data, stem, map = severity_map) {
  # Build the expected column names for this stem
  suf  <- names(map)                           # c("1","2","3","9","4")
  cols <- paste0(stem, "_", suf)

  # Safety checks: are all expected columns present?
  missing <- setdiff(cols, names(data))
  if (length(missing)) {
    stop("Missing columns for '", stem, "': ", paste(missing, collapse = ", "))
  }

  # Grab the indicators as a numeric matrix (0/1/NA)
  M <- as.matrix(data[cols])

  # Treat 0 as "not selected" -> NA, so they don't affect the max
  M[M == 0] <- NA

  # Weight each column by its severity value (1..5) so max() = most severe chosen
  sev <- as.numeric(map)
  W   <- sweep(M, 2, sev, `*`)   # column j multiplied by sev[j]

  # Row-wise max ignoring NAs; if all NA -> NA
  out <- apply(W, 1, function(x) {
    m <- suppressWarnings(max(x, na.rm = TRUE))
    if (is.infinite(m)) NA_real_ else m
  })

  # Return as integer 1..5 (or NA if nothing ticked)
  as.integer(out)
}

# Apply to your two stems
stems <- c("SpecCritW1", "SpecCritW2")

for (s in stems) {
  newcol <- paste0(s, "_severity")
  stacked_num[[newcol]] <- most_severe(stacked_num, s)
}

gratitude_items <- grep("GeneralGrat|SpecGrat", names(stacked_num), value = TRUE)
criticism_items <- grep("SpecCritW1_severity|SpecCritW2_severity|GenCrit", names(stacked_num), value = TRUE)
demand_items    <- grep("SpecSupp|GenSupp", names(stacked_num), value = TRUE)
victim_items    <- grep("GenVict|SpecVictim", names(stacked_num), value = TRUE)
violation_items <- grep("GenImp|SpecImp", names(stacked_num), value = TRUE)

# Store all subscales in a named list for looping
scales <- list(
  Gratitude = gratitude_items,
  Criticism = criticism_items,
  Demands = demand_items,
  Victimhood = victim_items,
  Violations = violation_items
)


### Reversing items
stacked_num$SpecVictimW_1<- 8 -stacked_num$SpecVictimW_1

stacked_num$GenSuppW_1<- 8 -stacked_num$GenSuppW_1

stacked_num$GenCritW_1<- 8 -stacked_num$GenCritW_1
stacked_num$GenCritW_2<- 8 -stacked_num$GenCritW_2
stacked_num$GenCritW_3<- 8 -stacked_num$GenCritW_3

stacked_num$SpecCritW1_severity<- 6 -stacked_num$SpecCritW1_severity
stacked_num$SpecCritW2_severity<- 6 -stacked_num$SpecCritW2_severity



### Two, perfectly defined subscale: general vs specific
GEN_gratitude_items <- grep("GeneralGratW", names(stacked_num), value = TRUE)
SPEC_gratitude_items <- grep("SpecGratW", names(stacked_num), value = TRUE)



## 3 factors: 1 clear general with only the 3 first that are good:
criticism_items_G <- grep("GenCritW", names(stacked_num), value = TRUE)
# Only the 'chill' posts are good. the two extreme ones measuring something else.
criticism_items_S <- grep("severity", names(stacked_num), value = TRUE)



# Let's keep the General that is distinct from specific (focus on protesting)
DemandSupp_G <- grep("GenSuppW", names(stacked_num), value = TRUE)


DemandSupp_S <- grep("SpecSuppW", names(stacked_num), value = TRUE)


# General works fine
Victim_G  <- grep("GenVictW", names(stacked_num), value = TRUE)
# Same for Specific
Victim_S  <- grep("SpecVictimW", names(stacked_num), value = TRUE)



# Here also: general works fine

Villain_G  <- grep("GenImpW", names(stacked_num), value = TRUE)

## But specific not so much...
Villain_S <- grep("SpecImpW", names(stacked_num), value = TRUE)




scales <- list(
  Gratitude_G = GEN_gratitude_items,
  Gratitude_S = SPEC_gratitude_items,

  criticism_items_G = criticism_items_G,
  criticism_items_S = criticism_items_S,

  DemandSupp_G  = DemandSupp_G,
  DemandSupp_S = DemandSupp_S,# Only 2 items

  Victim_G  = Victim_G ,
  Victim_S = Victim_S,

  Villain_G = Villain_G,
  Villain_S = Villain_S
)


for (name in names(scales)) {
  items <- scales[[name]]
  df_subset <- stacked_num[items]
  stacked_num[[paste0(name, "_mean")]] <- rowMeans(df_subset, na.rm = TRUE)
}


#colnames(stacked_num)

#scale_scores <- stacked_num[, c(40, 2, 48:57, 36:39, 34, 35)]
scale_scores2 <- stacked_num
scale_scores2$Cond <- as.factor(scale_scores2$Cond)
contrasts(scale_scores2$Cond) <- contr.sum(nlevels(scale_scores2$Cond))
contrasts(scale_scores2$Cond) # Deviations from registration!! We need to use sum to zero contrasts to make coef interpretable
##         [,1] [,2]
## Nurse      1    0
## Soldier    0    1
## Weld      -1   -1
scale_scores2$Heroism <- scale_scores2$HW_1
scale_scores2$Attitude <- (scale_scores2$AttW)
scale_scores2$Danger <- (scale_scores2$DangerHelpW_1)
scale_scores2$Help <- (scale_scores2$DangerHelpW_2)

Poppy2_DF <- subset(scale_scores2, scale_scores2$Cond == "Soldier")

contrasts(scale_scores2$Cond) <- contr.sum(nlevels(scale_scores2$Cond))
contrasts(scale_scores2$Cond)
##         [,1] [,2]
## Nurse      1    0
## Soldier    0    1
## Weld      -1   -1
Toggle Demographic From both studies

September sample

Demographics <- subset(Demographics, Demographics$Cond == "Soldier" | Demographics$Cond == "Nurse" | Demographics$Cond == "Weld")
Demographics$flag_inconsistent <- (Demographics$Job_match_6 == "None of the above") & 
  (Demographics$Job_match_1 != "" | Demographics$Job_match_2 != "" | Demographics$Job_match_3 != "" | 
   Demographics$Job_match_4 != "" | Demographics$Job_match_5 != "")


DFsoldier <- subset(scale_scores, scale_scores$Cond == "Soldier")

paste0(nrow(DFsoldier)," participants took part in the study in the SOLDIER condition. Mean age in the sample is ", mean(as.numeric(DFsoldier$Age)), ", SD = ", sd(as.numeric(DFsoldier$Age)))
## [1] "138 participants took part in the study in the SOLDIER condition. Mean age in the sample is NaN, SD = NA"
## Gender

Demographics %>% group_by(gender) %>% summarise(N=n()) %>%
  ggplot(aes(x=gender,y=N,fill=gender))+
  geom_bar(stat = 'identity',color='black')+
  scale_y_continuous(labels = scales::comma_format(accuracy = 2))+
  geom_text(aes(label=N),vjust=-0.25,fontface='bold')+
  theme_bw()+
  theme(axis.text = element_text(color='black',face='bold'),
        axis.title = element_text(color='black',face='bold'),
        legend.text = element_text(color='black',face='bold'),
        legend.title = element_text(color='black',face='bold')) +
  ggtitle("Gender distribution")

## Occupations
#colnames(Set)
#jobs <- unlist(Demographics[-which(Demographics$flag_inconsistent == T), 4:10])           # Make a long list of all jobs that were named
# Columns 4:9 hold the six job-matches
job_mat <- Demographics[, 4:9]

# 1) Long list of named jobs (ignore blanks here)
jobs <- unlist(job_mat, use.names = FALSE)
jobs <- jobs[jobs != "" & !is.na(jobs)]

# 2) Count named jobs
job_df <- as.data.frame(table(jobs), stringsAsFactors = FALSE)
colnames(job_df) <- c("Job", "Count")

# 3) Count "None of the above" = rows where ALL six entries are blank (or NA)
none_count <- sum(apply(job_mat, 1, function(x) all(is.na(x) | x == "")))

# 4) Append that category
job_df <- rbind(job_df, data.frame(Job = "None of the above", Count = none_count))

# (optional) make Job a factor so it shows up nicely; here we keep your original plotting as-is
# job_df$Job <- factor(job_df$Job)

# 5) Plot (your code)
ggplot(job_df, aes(x = Job, y = Count, fill = Job)) +
  geom_bar(stat = 'identity', color = 'black') +
  scale_y_continuous(labels = scales::comma_format(accuracy = 2)) +
  geom_text(aes(label = Count), vjust = -0.25, fontface = 'bold') +
  theme_bw() +
  theme(axis.text = element_text(color = 'black', face = 'bold'),
        axis.title = element_text(color = 'black', face = 'bold'),
        legend.text = element_text(color = 'black', face = 'bold'),
        legend.title = element_text(color = 'black', face = 'bold')) +
  ggtitle("Job distribution")

November sample

Demographics <- Demographics2

Demographics$flag_inconsistent <- (Demographics$Job_match_6 == "None of the above") & 
  (Demographics$Job_match_1 != "" | Demographics$Job_match_2 != "" | Demographics$Job_match_3 != "" | 
   Demographics$Job_match_4 != "" | Demographics$Job_match_5 != "")


DFsoldier <- subset(scale_scores2, scale_scores2$Cond == "Soldier")

paste0(nrow(DFsoldier), " participants took part in the study in the SOLDIER condition. Mean age in the sample is ", mean(as.numeric(DFsoldier$Age)), ", SD = ", sd(as.numeric(DFsoldier$Age)))
## [1] "140 participants took part in the study in the SOLDIER condition. Mean age in the sample is NaN, SD = NA"
## Gender

Demographics %>% group_by(gender) %>% summarise(N=n()) %>%
  ggplot(aes(x=gender,y=N,fill=gender))+
  geom_bar(stat = 'identity',color='black')+
  scale_y_continuous(labels = scales::comma_format(accuracy = 2))+
  geom_text(aes(label=N),vjust=-0.25,fontface='bold')+
  theme_bw()+
  theme(axis.text = element_text(color='black',face='bold'),
        axis.title = element_text(color='black',face='bold'),
        legend.text = element_text(color='black',face='bold'),
        legend.title = element_text(color='black',face='bold')) +
  ggtitle("Gender distribution")

## Occupations
#colnames(Set)
#jobs <- unlist(Demographics[-which(Demographics$flag_inconsistent == T), 4:10])           # Make a long list of all jobs that were named
# Columns 4:9 hold the six job-matches
job_mat <- Demographics[, 4:9]

# 1) Long list of named jobs (ignore blanks here)
jobs <- unlist(job_mat, use.names = FALSE)
jobs <- jobs[jobs != "" & !is.na(jobs)]

# 2) Count named jobs
job_df <- as.data.frame(table(jobs), stringsAsFactors = FALSE)
colnames(job_df) <- c("Job", "Count")

# 3) Count "None of the above" = rows where ALL six entries are blank (or NA)
none_count <- sum(apply(job_mat, 1, function(x) all(is.na(x) | x == "")))

# 4) Append that category
job_df <- rbind(job_df, data.frame(Job = "None of the above", Count = none_count))

# (optional) make Job a factor so it shows up nicely; here we keep your original plotting as-is
# job_df$Job <- factor(job_df$Job)

# 5) Plot (your code)
ggplot(job_df, aes(x = Job, y = Count, fill = Job)) +
  geom_bar(stat = 'identity', color = 'black') +
  scale_y_continuous(labels = scales::comma_format(accuracy = 2)) +
  geom_text(aes(label = Count), vjust = -0.25, fontface = 'bold') +
  theme_bw() +
  theme(axis.text = element_text(color = 'black', face = 'bold'),
        axis.title = element_text(color = 'black', face = 'bold'),
        legend.text = element_text(color = 'black', face = 'bold'),
        legend.title = element_text(color = 'black', face = 'bold')) +
  ggtitle("Job distribution")

Main Findings

A model comparison approach was used to assess our main hypotheses and qualify the part of variance explained by general attitude (i.e., Halo effect).

For each step of our model‐comparison procedure, we evaluated two models: 1) one based on general-level items, 2) one based on specific-level items,

If an hypothesis is supported at both the general and specific levels, we interpret this as full support for the hypothesis. If only one type of measure supports the hypothesis, we interpret this as partial support for the hypothesis.

We performed independent OLS regression models predicting each of our target outcomes (i.e., gratitude, criticism acceptability, support for workers demands, suffering assessment, and acceptability of regulations violation) using heroism score as predictor.

We established two models assessing the effect of heroism while accounting for possible interactions with occupation types and possible halo effects (see subsection Variable roles for details on each model).

Model 1 (Heroism effect across occupations): Target construct (gratitude, criticism acceptability, support for demands, suffering assessment, or acceptability of regulation violations): predicted variable Heroism: Main predictor Model: Target outcome ~ Heroism

Model 2 (Heroism effect across occupations and Halo effect): Target construct (gratitude, criticism acceptability, support for demands, suffering assessment, or acceptability of regulation violations): predicted variable Attitude: Covariate Heroism: Main predictor and moderator Model: Target outcome ~ Heroism + Attitude

AS REGISTERED: our main conclusions will be based on Model 1 – not accounting for attitude.

H1: Heroism is positively associated to gratitude

We are grateful for our heroes. As such, at a general level, people might declare openly gratefulness toward workers, and at the specific level, they are likely to display public support for the workers that are heroised. Sharing supportive post online, donating to campaigns, volunteering their time… people want to give back to heroes.

SEPTEMBER DF

General level

To what extent do you feel grateful for XXXs’ work?

Toggle details of the models diagnostics and outlier analyses

Below, you’ll find model diagnostics (QQ plot, fitted vs residuals, linearity), Outliers analysis, and more details on the output

[NOTE THAT EFFECT SIZES ED IN THE COMMANDS BELOW SHOULD NOT BE TRUSTED]

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 1: DV ~ Heroism + Occupation")
## [1] "Diagnostics for Model 1: DV ~ Heroism + Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Gratitude_G_mean ~ Heroism  + Cond, data = scale_scores)
mod1r <- lmrob(Gratitude_G_mean ~ Heroism  + Cond, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 2.435 2.218
(0.209) (0.328)
11.638 6.770
p = <0.001 p = <0.001
Heroism 0.680 0.709
(0.034) (0.050)
20.181 14.157
p = <0.001 p = <0.001
CondSoldier −0.533 −0.359
(0.127) (0.116)
−4.188 −3.091
p = <0.001 p = 0.002
CondWeld −0.478 −0.294
(0.129) (0.119)
−3.716 −2.463
p = <0.001 p = 0.014
Num.Obs. 417 417
R2 0.541 0.625
R2 Adj. 0.538 0.623
RMSE 1.05 1.05
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Gratitude_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 2: DV ~ Heroism * Occupation")
## [1] "Diagnostics for Model 2: DV ~ Heroism * Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Gratitude_G_mean ~ Heroism  * Cond, data = scale_scores)
mod1r <- lmrob(Gratitude_G_mean ~ Heroism  * Cond, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 2.975 3.322
(0.322) (0.672)
9.251 4.947
p = <0.001 p = <0.001
Heroism 0.584 0.527
(0.055) (0.103)
10.615 5.128
p = <0.001 p = <0.001
CondSoldier −2.011 −2.534
(0.427) (0.756)
−4.716 −3.353
p = <0.001 p = <0.001
CondWeld −0.264 −0.637
(0.461) (0.917)
−0.572 −0.695
p = 0.568 p = 0.487
Heroism × CondSoldier 0.281 0.374
(0.076) (0.115)
3.695 3.244
p = <0.001 p = 0.001
Heroism × CondWeld −0.061 0.036
(0.086) (0.153)
−0.705 0.236
p = 0.481 p = 0.813
Num.Obs. 417 417
R2 0.564 0.657
R2 Adj. 0.558 0.653
RMSE 1.02 1.03
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Gratitude_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude")
## [1] "Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Gratitude_G_mean ~ Heroism  + Cond + Attitude, data = scale_scores)
mod1r <- lmrob(Gratitude_G_mean ~ Heroism  + Cond + Attitude, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 0.107 0.000
(0.236) (0.000)
0.451
p = 0.652
Heroism 0.263 0.000
(0.040) (0.000)
6.562
p = <0.001
CondSoldier −0.196 0.000
(0.107) (0.000)
−1.839
p = 0.067
CondWeld −0.315 −1.000
(0.106) (0.000)
−2.976
p = 0.003
Attitude 0.743 1.000
(0.052) (0.000)
14.314
p = <0.001
Num.Obs. 417 417
R2 0.694 1.000
R2 Adj. 0.691 1.000
RMSE 0.85 0.97
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Gratitude_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude")
## [1] "Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Gratitude_G_mean ~ Heroism*Cond + Attitude, data = scale_scores)
mod1r <- lmrob(Gratitude_G_mean ~ Heroism*Cond + Attitude, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 0.464 0.000
(0.326) (0.000)
1.424
p = 0.155
Heroism 0.231 0.000
(0.053) (0.000)
4.384
p = <0.001
CondSoldier −0.918 0.000
(0.364) (0.000)
−2.520
p = 0.012
CondWeld −0.258 −6.000
(0.384) (0.000)
−0.671
p = 0.502
Attitude 0.715 1.000
(0.053) (0.000)
13.525
p = <0.001
Heroism × CondSoldier 0.134 0.000
(0.064) (0.000)
2.094
p = 0.037
Heroism × CondWeld −0.021 1.000
(0.072) (0.000)
−0.296
p = 0.767
Num.Obs. 417 417
R2 0.698 1.000
R2 Adj. 0.694 1.000
RMSE 0.85 1.23
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Gratitude_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0(" S for each model (please ignore the eta squares, they are way off")
## [1] " S for each model (please ignore the eta squares, they are way off"
paste0("####################################################")
## [1] "####################################################"
paste0("MODEL 1: Gratitude_G_mean ~ Heroism")
## [1] "MODEL 1: Gratitude_G_mean ~ Heroism"
 (Anova(mod1 <- lm(Gratitude_G_mean ~ Heroism  + Cond, data = scale_scores), type = "III"))
paste0("MODEL 2: Gratitude_G_mean ~ Heroism * Cond")
## [1] "MODEL 2: Gratitude_G_mean ~ Heroism * Cond"
 (Anova(mod2 <- lm(Gratitude_G_mean ~ Heroism * Cond, data = scale_scores), type = "III"))
paste0("####")
## [1] "####"
paste0("MODEL 3: Gratitude_G_mean ~ Heroism +scale(Attitude)")
## [1] "MODEL 3: Gratitude_G_mean ~ Heroism +scale(Attitude)"
 (Anova(mod3 <- lm(Gratitude_G_mean ~ Heroism   + Cond +scale(Attitude) , data = scale_scores), type = "III"))
paste0("MODEL 4: Gratitude_G_mean ~ Heroism * Cond + scale(Attitude)")
## [1] "MODEL 4: Gratitude_G_mean ~ Heroism * Cond + scale(Attitude)"
 (Anova(mod4 <-lm(Gratitude_G_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores), type = "III"))
paste0("Model Comparison: assessing the importance of attitude - not accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - not accounting for occupations"
anova(mod1, mod3)
paste0("Model Comparison: assessing the importance of attitude - accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - accounting for occupations"
anova(mod2, mod4)
ggplot(scale_scores, aes(y = Gratitude_G_mean, x = Heroism)) +
  # 1) points colored by Cond
  geom_point(aes(color = Cond),
             size = 2.7, alpha = 0.7) +
  
  # 2) ONE global lm line (group = 1 prevents one line per Cond)
  stat_smooth(method = "lm", se = TRUE,
              aes(group = 1),
              color = "black", linewidth = 1) +
  
  # Nice, accessible palette (works well with >3 groups)
  scale_color_brewer(palette = "Set2") +
  
  labs(
    y = "Gratitude (G)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Gratitude (G), colored by Occupation",
    subtitle = "Points are participants; black line is overall linear fit with 95% CI"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "right",
    panel.grid.minor = element_blank()
  )
## `geom_smooth()` using formula = 'y ~ x'

ggplot(scale_scores, aes(y = Gratitude_G_mean, x = Heroism, color = Cond)) +
  # points colored by condition
  geom_point(size = 2.7, alpha = 0.7) +
  # ONE lm line PER condition (because color is mapped here)
  stat_smooth(method = "lm", se = FALSE, linewidth = 1, fullrange = TRUE) +
  scale_color_brewer(palette = "Set2") +
  labs(
    y = "Gratitude (G)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Gratitude (G) with per-occupation linear fits"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "right", panel.grid.minor = element_blank())
## `geom_smooth()` using formula = 'y ~ x'

Overall: Heroism predict general gratitude. This is true above and beyond attitude (see Model 3). It is true when controlling for normative effects of occupations (Model 2), and when controlling for both occupations and attitude (Model 4). See Summary tables:

Model 1: “~Heroism + Occupation”

# Make sure Cond is a factor
scale_scores <- scale_scores %>% mutate(Cond = as.factor(Cond))
scale_scores$Occupation <- scale_scores$Cond
scale_scores$Occupation <- as.factor(scale_scores$Occupation)
contrasts(scale_scores$Occupation) <- contr.sum(nlevels(scale_scores$Occupation))


# Partial eta^2 from F and dfs (works for any df1)
eta_p2_fromF <- function(Fval, df1, df2) (Fval * df1) / (Fval * df1 + df2)

tidy_type3 <- function(mod, caption) {
  a <- car::Anova(mod, type = "III")
  tab <- as.data.frame(a)
  tab$Term <- rownames(tab)
  names(tab) <- sub(" ", "_", names(tab))

  resid_df2 <- tab$Df[tab$Term == "Residuals"]

  out <- tab %>%
    dplyr::filter(!(Term %in% c("(Intercept)", "Residuals"))) %>%
    dplyr::transmute(
      F     = round(F_value, 2),
      p     = ifelse(`Pr(>F)` < .001, "< .001", sprintf("= %.3f", `Pr(>F)`)),
      eta2p = round((F_value * Df) / (F_value * Df + resid_df2), 3)
    )

  kbl <- knitr::kable(
    out,
    format  = "html",
    align   = "rrc",      # 3 columns → 3 characters
    caption = caption
  )

  if ("column_spec" %in% getNamespaceExports("kableExtra")) {
    kbl <- kbl %>%
      kableExtra::kable_styling(
        full_width        = FALSE,
        bootstrap_options = c("striped", "hover")
      ) %>%
      # p is column 2 now
      kableExtra::column_spec(
        2,
        border_right = "1px solid #ddd",
        extra_css    = "padding-right: 10px;"
      ) %>%
      # eta2p is column 3
      kableExtra::column_spec(
        3,
        extra_css = "padding-left: 10px;"
      )
  }

  kbl
}


# =========================
# OLD MODELS (Heroism)
# =========================
m1_H <- lm(Gratitude_G_mean ~ Heroism + Occupation, data = scale_scores)
m2_H <- lm(Gratitude_G_mean ~ Heroism * Occupation, data = scale_scores)
m3_H <- lm(Gratitude_G_mean ~ Heroism + Occupation + Attitude, data = scale_scores)
m4_H <- lm(Gratitude_G_mean ~ Heroism * Occupation + Attitude, data = scale_scores)

tidy_type3(m1_H, "~Heroism + Cond")
~Heroism + Cond
F p eta2p
Heroism 407.29 < .001 0.497
Occupation 10.46 < .001 0.048
# =========================
# NEW MODELS (Danger & Help)
# =========================

Model 2: “~Heroism * Occupation”

tidy_type3(m2_H, "~Heroism * Cond")
~Heroism * Cond
F p eta2p
Heroism 382.57 < .001 0.482
Occupation 13.63 < .001 0.062
Heroism:Occupation 10.53 < .001 0.049

Model 3: “~Heroism + Occupation + Attitude”

tidy_type3(m3_H, "~Heroism + Cond + scale(Attitude)")
~Heroism + Cond + scale(Attitude)
F p eta2p
Heroism 43.06 < .001 0.095
Occupation 4.49 = 0.012 0.021
Attitude 204.88 < .001 0.332

Model 4: “~Heroism * Occupation + Attitude”

tidy_type3(m4_H, "~Heroism * Cond + scale(Attitude)")
~Heroism * Cond + scale(Attitude)
F p eta2p
Heroism 44.93 < .001 0.099
Occupation 3.44 = 0.033 0.016
Attitude 182.92 < .001 0.309
Heroism:Occupation 3.15 = 0.044 0.015

Comparison of main effect sizes in each model

# Use orthogonal contrasts; keeps your "Heroism main effect" interpretable with Cond in the model
old_contr <- options(contrasts = c("contr.sum", "contr.poly"))

# Helper to extract the main effect of "Heroism" from a car::Anova table
extract_heroism <- function(mod, model_label) {
  a <- car::Anova(mod, type = "III")

  # Coerce to data.frame with rownames preserved
  tab <- as.data.frame(a)
  tab$Term <- rownames(tab)

  # Standard column names across R versions
  # (car::Anova uses these names for lm/glm type=III)
  # Columns: "Sum Sq", "Df", "F value", "Pr(>F)"
  names(tab) <- sub(" ", "_", names(tab))  # make names safe: "Sum_Sq", "F_value", etc.

  # Pull rows we need
  hero <- tab %>% filter(Term == "Heroism")
  resid <- tab %>% filter(Term == "Residuals")

  # Safety checks (in case of name variants)
  stopifnot(nrow(hero) == 1, nrow(resid) == 1)

  # Partial eta^2 = SS_effect / (SS_effect + SS_residual)
  eta_p2 <- hero$Sum_Sq / (hero$Sum_Sq + resid$Sum_Sq)

  # Format p nicely (APA-ish)
  p_fmt <- ifelse(hero$`Pr(>F)` < .001, "< .001",
                  sprintf("= %.3f", hero$`Pr(>F)`))

  dplyr::tibble(
    Model = model_label,
    F = hero$F_value,
    p = p_fmt,
    eta2p = eta_p2
  )
}

# ----- Fit the four models -----
mod1 <- lm(Gratitude_G_mean ~ Heroism   + Cond, data = scale_scores)
mod2 <- lm(Gratitude_G_mean ~ Heroism * Cond, data = scale_scores)
mod3 <- lm(Gratitude_G_mean ~ Heroism    + Cond + scale(Attitude), data = scale_scores)
mod4 <- lm(Gratitude_G_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores)

# ----- Build the summary table -----
tbl <- bind_rows(
  extract_heroism(mod1, "~ Heroism   + Cond"),
  extract_heroism(mod2, "~ Heroism * Occupation"),
  extract_heroism(mod3, "~ Heroism + Cond + Attitude"),
  extract_heroism(mod4, "~ Heroism * Occupation + Attitude")
  ) %>%
  # Nice number formatting for F and eta^2p
  mutate(
    F = round(F, 2),
    eta2p = round(eta2p, 3)
  )

# ----- Print as HTML-friendly table -----
# Build the table first
tbl_kbl <- tbl %>%
  kable("html",
        caption = "Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²",
        align = "lllrrrcr")
# Add a footnote in a version-robust way
if ("footnote" %in% getNamespaceExports("kableExtra")) {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::footnote(
      general = "Type-III sums of squares with sum contrasts.",
      general_title = ""
    )
} else {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::add_footnote(
      label = "Type-III sums of squares with sum contrasts.",
      notation = "none"
    )
}

tbl_kbl
Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²
Model F p eta2p
~ Heroism + Cond 407.29 < .001 0.497
~ Heroism * Occupation 382.57 < .001 0.482
~ Heroism + Cond + Attitude 43.06 < .001 0.095
~ Heroism * Occupation + Attitude 44.93 < .001 0.099
Type-III sums of squares with sum contrasts.

Specific level

[If there were a public campaign in support of journalists, how likely would you be to do each of these things in response?] Sharing a supportive post about journalists on my social media

Toggle details of the models diagnostics and outlier analyses

Below, you’ll find model diagnostics (QQ plot, fitted vs residuals, linearity), Outliers analysis, and more details on the output

[NOTE THAT EFFECT SIZES ED IN THE COMMANDS BELOW SHOULD NOT BE TRUSTED]

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 1: DV ~ Heroism + Occupation")
## [1] "Diagnostics for Model 1: DV ~ Heroism + Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Gratitude_S_mean ~ Heroism  + Cond, data = scale_scores)
mod1r <- lmrob(Gratitude_S_mean ~ Heroism  + Cond, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 0.448 0.247
(0.252) (0.225)
1.780 1.099
p = 0.076 p = 0.273
Heroism 0.585 0.626
(0.047) (0.045)
12.544 13.944
p = <0.001 p = <0.001
Cond1 0.234 0.253
(0.103) (0.107)
2.281 2.369
p = 0.023 p = 0.018
Cond2 −0.129 −0.137
(0.101) (0.108)
−1.280 −1.267
p = 0.201 p = 0.206
Num.Obs. 417 417
R2 0.307 0.332
R2 Adj. 0.302 0.327
RMSE 1.45 1.45
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Gratitude_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 2: DV ~ Heroism * Occupation")
## [1] "Diagnostics for Model 2: DV ~ Heroism * Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Gratitude_S_mean ~ Heroism  * Cond, data = scale_scores)
mod1r <- lmrob(Gratitude_S_mean ~ Heroism  * Cond, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 0.467 0.254
(0.256) (0.257)
1.826 0.990
p = 0.069 p = 0.323
Heroism 0.583 0.627
(0.048) (0.052)
12.221 12.049
p = <0.001 p = <0.001
Cond1 0.431 0.478
(0.367) (0.326)
1.173 1.467
p = 0.242 p = 0.143
Cond2 −0.354 −0.325
(0.344) (0.300)
−1.030 −1.085
p = 0.303 p = 0.279
Heroism × Cond1 −0.036 −0.042
(0.066) (0.062)
−0.544 −0.672
p = 0.587 p = 0.502
Heroism × Cond2 0.043 0.036
(0.064) (0.062)
0.670 0.584
p = 0.503 p = 0.559
Num.Obs. 417 417
R2 0.308 0.333
R2 Adj. 0.299 0.325
RMSE 1.45 1.45
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Gratitude_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude")
## [1] "Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Gratitude_S_mean ~ Heroism  + Cond + Attitude, data = scale_scores)
mod1r <- lmrob(Gratitude_S_mean ~ Heroism  + Cond + Attitude, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) −0.657 −0.748
(0.351) (0.283)
−1.871 −2.642
p = 0.062 p = 0.009
Heroism 0.372 0.402
(0.066) (0.076)
5.608 5.267
p = <0.001 p = <0.001
Cond1 0.149 0.167
(0.102) (0.110)
1.458 1.526
p = 0.146 p = 0.128
Cond2 −0.043 −0.040
(0.101) (0.110)
−0.422 −0.364
p = 0.673 p = 0.716
Attitude 0.380 0.373
(0.086) (0.090)
4.416 4.132
p = <0.001 p = <0.001
Num.Obs. 417 417
R2 0.338 0.352
R2 Adj. 0.332 0.346
RMSE 1.42 1.42
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Gratitude_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude")
## [1] "Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Gratitude_S_mean ~ Heroism*Cond + Attitude, data = scale_scores)
mod1r <- lmrob(Gratitude_S_mean ~ Heroism*Cond + Attitude, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) −0.689 −0.775
(0.364) (0.295)
−1.892 −2.632
p = 0.059 p = 0.009
Heroism 0.374 0.405
(0.067) (0.079)
5.585 5.155
p = <0.001 p = <0.001
Cond1 0.233 0.279
(0.362) (0.332)
0.644 0.841
p = 0.520 p = 0.401
Cond2 0.038 0.016
(0.348) (0.300)
0.108 0.052
p = 0.914 p = 0.958
Attitude 0.385 0.376
(0.088) (0.091)
4.366 4.130
p = <0.001 p = <0.001
Heroism × Cond1 −0.017 −0.022
(0.064) (0.062)
−0.256 −0.354
p = 0.798 p = 0.724
Heroism × Cond2 −0.017 −0.012
(0.064) (0.061)
−0.259 −0.202
p = 0.796 p = 0.840
Num.Obs. 417 417
R2 0.338 0.352
R2 Adj. 0.329 0.343
RMSE 1.42 1.42
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Gratitude_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0(" S for each model (please ignore the eta squares, they are way off")
## [1] " S for each model (please ignore the eta squares, they are way off"
paste0("####################################################")
## [1] "####################################################"
paste0("MODEL 1: Gratitude_S_mean ~ Heroism")
## [1] "MODEL 1: Gratitude_S_mean ~ Heroism"
 (Anova(mod1 <- lm(Gratitude_S_mean ~ Heroism  + Cond, data = scale_scores), type = "III"))
paste0("MODEL 2: Gratitude_S_mean ~ Heroism * Cond")
## [1] "MODEL 2: Gratitude_S_mean ~ Heroism * Cond"
 (Anova(mod2 <- lm(Gratitude_S_mean ~ Heroism * Cond, data = scale_scores), type = "III"))
paste0("MODEL 3: Gratitude_S_mean ~ Heroism +scale(Attitude)")
## [1] "MODEL 3: Gratitude_S_mean ~ Heroism +scale(Attitude)"
 (Anova(mod3 <- lm(Gratitude_S_mean ~ Heroism   + Cond +scale(Attitude) , data = scale_scores), type = "III"))
paste0("MODEL 4: Gratitude_S_mean ~ Heroism * Cond + scale(Attitude)")
## [1] "MODEL 4: Gratitude_S_mean ~ Heroism * Cond + scale(Attitude)"
 (Anova(mod4 <-lm(Gratitude_S_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores), type = "III"))
paste0("Model Comparison: assessing the importance of attitude - not accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - not accounting for occupations"
anova(mod1, mod3)
paste0("Model Comparison: assessing the importance of attitude - accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - accounting for occupations"
anova(mod2, mod4)
ggplot(scale_scores, aes(y = Gratitude_S_mean, x = Heroism)) +
  # 1) points colored by Cond
  geom_point(aes(color = Cond),
             size = 2.7, alpha = 0.7) +
  
  # 2) ONE global lm line (group = 1 prevents one line per Cond)
  stat_smooth(method = "lm", se = TRUE,
              aes(group = 1),
              color = "black", linewidth = 1) +
  
  # Nice, accessible palette (works well with >3 groups)
  scale_color_brewer(palette = "Set2") +
  
  labs(
    y = "Gratitude (S)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Gratitude (S), colored by Occupation",
    subtitle = "Points are participants; black line is overall linear fit with 95% CI"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "right",
    panel.grid.minor = element_blank()
  )
## `geom_smooth()` using formula = 'y ~ x'

ggplot(scale_scores, aes(y = Gratitude_S_mean, x = Heroism, color = Cond)) +
  # points colored by condition
  geom_point(size = 2.7, alpha = 0.7) +
  # ONE lm line PER condition (because color is mapped here)
  stat_smooth(method = "lm", se = FALSE, linewidth = 1, fullrange = TRUE) +
  scale_color_brewer(palette = "Set2") +
  labs(
    y = "Gratitude (S)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Gratitude (S) with per-Occupation linear fits"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "right", panel.grid.minor = element_blank())
## `geom_smooth()` using formula = 'y ~ x'

Overall: Heroism predict specific gratitude. This is true above and beyond attitude (see Model 3). It is true when controlling for normative effects of occupations (Model 2), and when controlling for both occupations and attitude (Model 4). See Summary table:

Model 1: “~Heroism + Occupation”

# =========================
# OLD MODELS (Heroism)
# =========================
m1_H <- lm(Gratitude_S_mean ~ Heroism + Occupation, data = scale_scores)
m2_H <- lm(Gratitude_S_mean ~ Heroism * Occupation, data = scale_scores)
m3_H <- lm(Gratitude_S_mean ~ Heroism + Occupation + Attitude, data = scale_scores)
m4_H <- lm(Gratitude_S_mean ~ Heroism * Occupation + Attitude, data = scale_scores)

tidy_type3(m1_H, "~Heroism + Cond")
~Heroism + Cond
F p eta2p
Heroism 157.36 < .001 0.276
Occupation 2.62 = 0.074 0.013
# =========================
# NEW MODELS (Danger & Help)
# =========================

Model 2: “~Heroism * Occupation”

tidy_type3(m2_H, "~Heroism * Cond")
~Heroism * Cond
F p eta2p
Heroism 149.35 < .001 0.267
Occupation 0.85 = 0.430 0.004
Heroism:Occupation 0.27 = 0.761 0.001

Model 3: “~Heroism + Occupation + Attitude”

tidy_type3(m3_H, "~Heroism + Occupation + Attitude")
~Heroism + Occupation + Attitude
F p eta2p
Heroism 31.45 < .001 0.071
Occupation 1.13 = 0.322 0.005
Attitude 19.50 < .001 0.045

Model 4: “~Heroism * Occupation + Attitude”

tidy_type3(m4_H, "~Heroism * Occupation + Attitude")
~Heroism * Occupation + Attitude
F p eta2p
Heroism 31.19 < .001 0.071
Occupation 0.31 = 0.731 0.002
Attitude 19.06 < .001 0.044
Heroism:Occupation 0.11 = 0.898 0.001

==> Full support for our hypotheses. Across all occupations Heroism predict general- and specific-level gratitude, with or without controlling for attitude.

Comparison of main effects across models

# Use orthogonal contrasts; keeps your "Heroism main effect" interpretable with Cond in the model
old_contr <- options(contrasts = c("contr.sum", "contr.poly"))

# Helper to extract the main effect of "Heroism" from a car::Anova table
extract_heroism <- function(mod, model_label) {
  a <- car::Anova(mod, type = "III")

  # Coerce to data.frame with rownames preserved
  tab <- as.data.frame(a)
  tab$Term <- rownames(tab)

  # Standard column names across R versions
  # (car::Anova uses these names for lm/glm type=III)
  # Columns: "Sum Sq", "Df", "F value", "Pr(>F)"
  names(tab) <- sub(" ", "_", names(tab))  # make names safe: "Sum_Sq", "F_value", etc.

  # Pull rows we need
  hero <- tab %>% filter(Term == "Heroism")
  resid <- tab %>% filter(Term == "Residuals")

  # Safety checks (in case of name variants)
  stopifnot(nrow(hero) == 1, nrow(resid) == 1)

  # Partial eta^2 = SS_effect / (SS_effect + SS_residual)
  eta_p2 <- hero$Sum_Sq / (hero$Sum_Sq + resid$Sum_Sq)

  # Format p nicely (APA-ish)
  p_fmt <- ifelse(hero$`Pr(>F)` < .001, "< .001",
                  sprintf("= %.3f", hero$`Pr(>F)`))

  dplyr::tibble(
    Model = model_label,
    F = hero$F_value,
    p = p_fmt,
    eta2p = eta_p2
  )
}

# ----- Fit the four models -----
mod1 <- lm(Gratitude_S_mean ~ Heroism   + Cond, data = scale_scores)
mod2 <- lm(Gratitude_S_mean ~ Heroism * Cond, data = scale_scores)
mod3 <- lm(Gratitude_S_mean ~ Heroism   + Cond + scale(Attitude), data = scale_scores)
mod4 <- lm(Gratitude_S_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores)

# ----- Build the summary table -----
tbl <- bind_rows(
  extract_heroism(mod1, "~ Heroism   + Cond"),
  extract_heroism(mod2, "~ Heroism * Occupation"),
  extract_heroism(mod3, "~ Heroism   + Cond + Attitude"),
  extract_heroism(mod4, "~ Heroism * Occupation + Attitude")
  ) %>%
  # Nice number formatting for F and eta^2p
  mutate(
    F = round(F, 2),
    eta2p = round(eta2p, 3)
  )

# ----- Print as HTML-friendly table -----
# Build the table first
tbl_kbl <- tbl %>%
  kable("html",
        caption = "Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²",
        align = "lllrrrcr")
# Add a footnote in a version-robust way
if ("footnote" %in% getNamespaceExports("kableExtra")) {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::footnote(
      general = "Type-III sums of squares with sum contrasts.",
      general_title = ""
    )
} else {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::add_footnote(
      general = "Type-III sums of squares with sum contrasts.",
      notation = "none"
    )
}

tbl_kbl
Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²
Model F p eta2p
~ Heroism + Cond 157.36 < .001 0.276
~ Heroism * Occupation 149.35 < .001 0.267
~ Heroism + Cond + Attitude 31.45 < .001 0.071
~ Heroism * Occupation + Attitude 31.19 < .001 0.071
Type-III sums of squares with sum contrasts.

NOVEMBER DF

General level

To what extent do you feel grateful for XXXs’ work?

Toggle details of the models diagnostics and outlier analyses

Below, you’ll find model diagnostics (QQ plot, fitted vs residuals, linearity), Outliers analysis, and more details on the output

[NOTE THAT EFFECT SIZES ED IN THE COMMANDS BELOW SHOULD NOT BE TRUSTED]

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 1: DV ~ Heroism + Occupation")
## [1] "Diagnostics for Model 1: DV ~ Heroism + Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Gratitude_G_mean ~ Heroism  + Cond, data = scale_scores2)
mod1r <- lmrob(Gratitude_G_mean ~ Heroism  + Cond, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 2.586 2.481
(0.173) (0.296)
14.909 8.386
p = <0.001 p = <0.001
Heroism 0.604 0.634
(0.033) (0.050)
18.297 12.752
p = <0.001 p = <0.001
Cond1 0.443 0.349
(0.074) (0.066)
5.955 5.273
p = <0.001 p = <0.001
Cond2 −0.274 −0.274
(0.073) (0.069)
−3.771 −3.981
p = <0.001 p = <0.001
Num.Obs. 421 421
R2 0.518 0.589
R2 Adj. 0.515 0.586
RMSE 1.05 1.05
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Gratitude_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 2: DV ~ Heroism * Occupation")
## [1] "Diagnostics for Model 2: DV ~ Heroism * Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Gratitude_G_mean ~ Heroism  * Cond, data = scale_scores2)
mod1r <- lmrob(Gratitude_G_mean ~ Heroism  * Cond, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 2.811 2.869
(0.171) (0.251)
16.419 11.424
p = <0.001 p = <0.001
Heroism 0.569 0.573
(0.032) (0.043)
17.522 13.189
p = <0.001 p = <0.001
Cond1 1.676 1.940
(0.262) (0.339)
6.401 5.729
p = <0.001 p = <0.001
Cond2 −1.435 −1.717
(0.224) (0.309)
−6.401 −5.557
p = <0.001 p = <0.001
Heroism × Cond1 −0.227 −0.275
(0.047) (0.057)
−4.793 −4.862
p = <0.001 p = <0.001
Heroism × Cond2 0.226 0.259
(0.043) (0.052)
5.287 4.959
p = <0.001 p = <0.001
Num.Obs. 421 421
R2 0.556 0.654
R2 Adj. 0.551 0.649
RMSE 1.00 1.01
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Gratitude_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude")
## [1] "Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Gratitude_G_mean ~ Heroism  + Cond + Attitude, data = scale_scores2)
mod1r <- lmrob(Gratitude_G_mean ~ Heroism  + Cond + Attitude, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 0.439 0.000
(0.211) (0.000)
2.079
p = 0.038
Heroism 0.236 0.000
(0.038) (0.000)
6.193
p = <0.001
Cond1 0.145 0.000
(0.065) (0.000)
2.215
p = 0.027
Cond2 −0.009 0.000
(0.063) (0.000)
−0.141
p = 0.888
Attitude 0.697 1.000
(0.050) (0.000)
13.861
p = <0.001
Num.Obs. 421 421
R2 0.671 1.000
R2 Adj. 0.667 1.000
RMSE 0.87 0.92
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Gratitude_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude")
## [1] "Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Gratitude_G_mean ~ Heroism*Cond + Attitude, data = scale_scores2)
mod1r <- lmrob(Gratitude_G_mean ~ Heroism*Cond + Attitude, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 0.650 0.000
(0.226) (0.000)
2.876
p = 0.004
Heroism 0.245 0.000
(0.038) (0.000)
6.469
p = <0.001
Cond1 0.829 0.000
(0.233) (0.000)
3.553
p = <0.001
Cond2 −0.427 0.000
(0.207) (0.000)
−2.059
p = 0.040
Attitude 0.658 1.000
(0.053) (0.000)
12.511
p = <0.001
Heroism × Cond1 −0.125 0.000
(0.041) (0.000)
−3.025
p = 0.003
Heroism × Cond2 0.075 0.000
(0.038) (0.000)
1.965
p = 0.050
Num.Obs. 421 421
R2 0.678 1.000
R2 Adj. 0.673 1.000
RMSE 0.86 0.92
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Gratitude_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0(" S for each model (please ignore the eta squares, they are way off")
## [1] " S for each model (please ignore the eta squares, they are way off"
paste0("####################################################")
## [1] "####################################################"
paste0("MODEL 1: Gratitude_G_mean ~ Heroism")
## [1] "MODEL 1: Gratitude_G_mean ~ Heroism"
 (Anova(mod1 <- lm(Gratitude_G_mean ~ Heroism  + Cond, data = scale_scores2), type = "III"))
paste0("MODEL 2: Gratitude_G_mean ~ Heroism * Cond")
## [1] "MODEL 2: Gratitude_G_mean ~ Heroism * Cond"
 (Anova(mod2 <- lm(Gratitude_G_mean ~ Heroism * Cond, data = scale_scores2), type = "III"))
paste0("MODEL 3: Gratitude_G_mean ~ Heroism +scale(Attitude)")
## [1] "MODEL 3: Gratitude_G_mean ~ Heroism +scale(Attitude)"
 (Anova(mod3 <- lm(Gratitude_G_mean ~ Heroism   + Cond +scale(Attitude) , data = scale_scores2), type = "III"))
paste0("MODEL 4: Gratitude_G_mean ~ Heroism * Cond + scale(Attitude)")
## [1] "MODEL 4: Gratitude_G_mean ~ Heroism * Cond + scale(Attitude)"
 (Anova(mod4 <-lm(Gratitude_G_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores2), type = "III"))
paste0("Model Comparison: assessing the importance of attitude - not accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - not accounting for occupations"
anova(mod1, mod3)
paste0("Model Comparison: assessing the importance of attitude - accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - accounting for occupations"
anova(mod2, mod4)
ggplot(scale_scores2, aes(y = Gratitude_G_mean, x = Heroism)) +
  # 1) points colored by Cond
  geom_point(aes(color = Cond),
             size = 2.7, alpha = 0.7) +
  
  # 2) ONE global lm line (group = 1 prevents one line per Cond)
  stat_smooth(method = "lm", se = TRUE,
              aes(group = 1),
              color = "black", linewidth = 1) +
  
  # Nice, accessible palette (works well with >3 groups)
  scale_color_brewer(palette = "Set2") +
  
  labs(
    y = "Gratitude (G)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Gratitude (G), colored by Occupation",
    subtitle = "Points are participants; black line is overall linear fit with 95% CI"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "right",
    panel.grid.minor = element_blank()
  )
## `geom_smooth()` using formula = 'y ~ x'

ggplot(scale_scores2, aes(y = Gratitude_G_mean, x = Heroism, color = Cond)) +
  # points colored by condition
  geom_point(size = 2.7, alpha = 0.7) +
  # ONE lm line PER condition (because color is mapped here)
  stat_smooth(method = "lm", se = FALSE, linewidth = 1, fullrange = TRUE) +
  scale_color_brewer(palette = "Set2") +
  labs(
    y = "Gratitude (G)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Gratitude (G) with per-occupation linear fits"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "right", panel.grid.minor = element_blank())
## `geom_smooth()` using formula = 'y ~ x'

Overall: Heroism predict general gratitude. This is true above and beyond attitude (see Model 3). It is true when controlling for normative effects of occupations (Model 2), and when controlling for both occupations and attitude (Model 4). See Summary tables:

Model 1: “~Heroism + Occupation”

# Make sure Cond is a factor
scale_scores2 <- scale_scores2 %>% mutate(Cond = as.factor(Cond))
scale_scores2$Occupation <- scale_scores2$Cond
scale_scores2$Occupation <- as.factor(scale_scores2$Occupation)
contrasts(scale_scores2$Occupation) <- contr.sum(nlevels(scale_scores2$Occupation))


# Partial eta^2 from F and dfs (works for any df1)
eta_p2_fromF <- function(Fval, df1, df2) (Fval * df1) / (Fval * df1 + df2)

tidy_type3 <- function(mod, caption) {
  a <- car::Anova(mod, type = "III")
  tab <- as.data.frame(a)
  tab$Term <- rownames(tab)
  names(tab) <- sub(" ", "_", names(tab))

  resid_df2 <- tab$Df[tab$Term == "Residuals"]

  out <- tab %>%
    dplyr::filter(!(Term %in% c("(Intercept)", "Residuals"))) %>%
    dplyr::transmute(
      F     = round(F_value, 2),
      p     = ifelse(`Pr(>F)` < .001, "< .001", sprintf("= %.3f", `Pr(>F)`)),
      eta2p = round((F_value * Df) / (F_value * Df + resid_df2), 3)
    )

  kbl <- knitr::kable(
    out,
    format  = "html",
    align   = "rrc",      # 3 columns → 3 characters
    caption = caption
  )

  if ("column_spec" %in% getNamespaceExports("kableExtra")) {
    kbl <- kbl %>%
      kableExtra::kable_styling(
        full_width        = FALSE,
        bootstrap_options = c("striped", "hover")
      ) %>%
      # p is column 2 now
      kableExtra::column_spec(
        2,
        border_right = "1px solid #ddd",
        extra_css    = "padding-right: 10px;"
      ) %>%
      # eta2p is column 3
      kableExtra::column_spec(
        3,
        extra_css = "padding-left: 10px;"
      )
  }

  kbl
}


# =========================
# OLD MODELS (Heroism)
# =========================
m1_H <- lm(Gratitude_G_mean ~ Heroism + Occupation, data = scale_scores2)
m2_H <- lm(Gratitude_G_mean ~ Heroism * Occupation, data = scale_scores2)
m3_H <- lm(Gratitude_G_mean ~ Heroism + Occupation + Attitude, data = scale_scores2)
m4_H <- lm(Gratitude_G_mean ~ Heroism * Occupation + Attitude, data = scale_scores2)

tidy_type3(m1_H, "~Heroism + Cond")
~Heroism + Cond
F p eta2p
Heroism 334.78 < .001 0.445
Occupation 18.19 < .001 0.080
# =========================
# NEW MODELS (Danger & Help)
# =========================

Model 2: “~Heroism * Occupation”

tidy_type3(m2_H, "~Heroism * Cond")
~Heroism * Cond
F p eta2p
Heroism 307.01 < .001 0.425
Occupation 26.86 < .001 0.115
Heroism:Occupation 17.67 < .001 0.078

Model 3: “~Heroism + Occupation + Attitude”

tidy_type3(m3_H, "~Heroism + Cond + scale(Attitude)")
~Heroism + Cond + scale(Attitude)
F p eta2p
Heroism 38.35 < .001 0.084
Occupation 3.26 = 0.040 0.015
Attitude 192.14 < .001 0.316

Model 4: “~Heroism * Occupation + Attitude”

tidy_type3(m4_H, "~Heroism * Cond + scale(Attitude)")
~Heroism * Cond + scale(Attitude)
F p eta2p
Heroism 41.85 < .001 0.092
Occupation 6.31 = 0.002 0.030
Attitude 156.53 < .001 0.274
Heroism:Occupation 4.75 = 0.009 0.022

Comparison of main effect sizes in each model

# Use orthogonal contrasts; keeps your "Heroism main effect" interpretable with Cond in the model
old_contr <- options(contrasts = c("contr.sum", "contr.poly"))

# Helper to extract the main effect of "Heroism" from a car::Anova table
extract_heroism <- function(mod, model_label) {
  a <- car::Anova(mod, type = "III")

  # Coerce to data.frame with rownames preserved
  tab <- as.data.frame(a)
  tab$Term <- rownames(tab)

  # Standard column names across R versions
  # (car::Anova uses these names for lm/glm type=III)
  # Columns: "Sum Sq", "Df", "F value", "Pr(>F)"
  names(tab) <- sub(" ", "_", names(tab))  # make names safe: "Sum_Sq", "F_value", etc.

  # Pull rows we need
  hero <- tab %>% filter(Term == "Heroism")
  resid <- tab %>% filter(Term == "Residuals")

  # Safety checks (in case of name variants)
  stopifnot(nrow(hero) == 1, nrow(resid) == 1)

  # Partial eta^2 = SS_effect / (SS_effect + SS_residual)
  eta_p2 <- hero$Sum_Sq / (hero$Sum_Sq + resid$Sum_Sq)

  # Format p nicely (APA-ish)
  p_fmt <- ifelse(hero$`Pr(>F)` < .001, "< .001",
                  sprintf("= %.3f", hero$`Pr(>F)`))

  dplyr::tibble(
    Model = model_label,
    F = hero$F_value,
    p = p_fmt,
    eta2p = eta_p2
  )
}

# ----- Fit the four models -----
mod1 <- lm(Gratitude_G_mean ~ Heroism   + Cond, data = scale_scores2)
mod2 <- lm(Gratitude_G_mean ~ Heroism * Cond, data = scale_scores2)
mod3 <- lm(Gratitude_G_mean ~ Heroism    + Cond + scale(Attitude), data = scale_scores2)
mod4 <- lm(Gratitude_G_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores2)

# ----- Build the summary table -----
tbl <- bind_rows(
  extract_heroism(mod1, "~ Heroism   + Cond"),
  extract_heroism(mod2, "~ Heroism * Occupation"),
  extract_heroism(mod3, "~ Heroism + Cond + Attitude"),
  extract_heroism(mod4, "~ Heroism * Occupation + Attitude")
  ) %>%
  # Nice number formatting for F and eta^2p
  mutate(
    F = round(F, 2),
    eta2p = round(eta2p, 3)
  )

# ----- Print as HTML-friendly table -----
# Build the table first
tbl_kbl <- tbl %>%
  kable("html",
        caption = "Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²",
        align = "lllrrrcr")
# Add a footnote in a version-robust way
if ("footnote" %in% getNamespaceExports("kableExtra")) {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::footnote(
      general = "Type-III sums of squares with sum contrasts.",
      general_title = ""
    )
} else {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::add_footnote(
      general = "Type-III sums of squares with sum contrasts.",
      notation = "none"
    )
}

tbl_kbl
Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²
Model F p eta2p
~ Heroism + Cond 334.78 < .001 0.445
~ Heroism * Occupation 307.01 < .001 0.425
~ Heroism + Cond + Attitude 38.35 < .001 0.084
~ Heroism * Occupation + Attitude 41.85 < .001 0.092
Type-III sums of squares with sum contrasts.

Specific level

[If there were a public campaign in support of journalists, how likely would you be to do each of these things in response?] Sharing a supportive post about journalists on my social media

Toggle details of the models diagnostics and outlier analyses

Below, you’ll find model diagnostics (QQ plot, fitted vs residuals, linearity), Outliers analysis, and more details on the output

[NOTE THAT EFFECT SIZES ED IN THE COMMANDS BELOW SHOULD NOT BE TRUSTED]

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 1: DV ~ Heroism + Occupation")
## [1] "Diagnostics for Model 1: DV ~ Heroism + Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Gratitude_S_mean ~ Heroism  + Cond, data = scale_scores2)
mod1r <- lmrob(Gratitude_S_mean ~ Heroism  + Cond, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 0.359 0.205
(0.232) (0.202)
1.549 1.015
p = 0.122 p = 0.311
Heroism 0.555 0.587
(0.044) (0.041)
12.608 14.161
p = <0.001 p = <0.001
Cond1 0.349 0.399
(0.099) (0.115)
3.514 3.467
p = <0.001 p = <0.001
Cond2 −0.081 −0.090
(0.097) (0.101)
−0.838 −0.888
p = 0.402 p = 0.375
Num.Obs. 421 421
R2 0.334 0.366
R2 Adj. 0.329 0.361
RMSE 1.40 1.40
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Gratitude_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 2: DV ~ Heroism * Occupation")
## [1] "Diagnostics for Model 2: DV ~ Heroism * Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Gratitude_S_mean ~ Heroism  * Cond, data = scale_scores2)
mod1r <- lmrob(Gratitude_S_mean ~ Heroism  * Cond, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 0.408 0.249
(0.238) (0.211)
1.716 1.177
p = 0.087 p = 0.240
Heroism 0.546 0.578
(0.045) (0.042)
12.119 13.881
p = <0.001 p = <0.001
Cond1 0.524 0.451
(0.364) (0.353)
1.440 1.280
p = 0.151 p = 0.201
Cond2 −0.369 −0.393
(0.312) (0.267)
−1.185 −1.473
p = 0.237 p = 0.142
Heroism × Cond1 −0.031 −0.008
(0.066) (0.062)
−0.475 −0.122
p = 0.635 p = 0.903
Heroism × Cond2 0.058 0.064
(0.059) (0.055)
0.972 1.157
p = 0.332 p = 0.248
Num.Obs. 421 421
R2 0.335 0.369
R2 Adj. 0.327 0.362
RMSE 1.40 1.40
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Gratitude_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude")
## [1] "Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Gratitude_S_mean ~ Heroism  + Cond + Attitude, data = scale_scores2)
mod1r <- lmrob(Gratitude_S_mean ~ Heroism  + Cond + Attitude, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) −0.660 −0.749
(0.334) (0.304)
−1.974 −2.467
p = 0.049 p = 0.014
Heroism 0.381 0.402
(0.060) (0.067)
6.323 6.043
p = <0.001 p = <0.001
Cond1 0.208 0.257
(0.103) (0.120)
2.010 2.144
p = 0.045 p = 0.033
Cond2 0.044 0.054
(0.100) (0.111)
0.445 0.483
p = 0.657 p = 0.629
Attitude 0.331 0.332
(0.080) (0.090)
4.157 3.684
p = <0.001 p = <0.001
Num.Obs. 421 421
R2 0.360 0.386
R2 Adj. 0.354 0.380
RMSE 1.37 1.37
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Gratitude_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude")
## [1] "Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Gratitude_S_mean ~ Heroism*Cond + Attitude, data = scale_scores2)
mod1r <- lmrob(Gratitude_S_mean ~ Heroism*Cond + Attitude, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) −0.710 −0.814
(0.362) (0.351)
−1.961 −2.319
p = 0.051 p = 0.021
Heroism 0.379 0.399
(0.061) (0.067)
6.255 5.919
p = <0.001 p = <0.001
Cond1 0.086 0.004
(0.373) (0.372)
0.230 0.010
p = 0.818 p = 0.992
Cond2 0.152 0.165
(0.332) (0.323)
0.459 0.512
p = 0.647 p = 0.609
Attitude 0.340 0.344
(0.084) (0.099)
4.046 3.480
p = <0.001 p = <0.001
Heroism × Cond1 0.022 0.047
(0.066) (0.064)
0.329 0.729
p = 0.742 p = 0.466
Heroism × Cond2 −0.020 −0.019
(0.061) (0.061)
−0.329 −0.311
p = 0.743 p = 0.756
Num.Obs. 421 421
R2 0.361 0.386
R2 Adj. 0.351 0.378
RMSE 1.37 1.37
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Gratitude_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0(" S for each model (please ignore the eta squares, they are way off")
## [1] " S for each model (please ignore the eta squares, they are way off"
paste0("####################################################")
## [1] "####################################################"
paste0("MODEL 1: Gratitude_S_mean ~ Heroism")
## [1] "MODEL 1: Gratitude_S_mean ~ Heroism"
 (Anova(mod1 <- lm(Gratitude_S_mean ~ Heroism  + Cond, data = scale_scores2), type = "III"))
paste0("MODEL 2: Gratitude_S_mean ~ Heroism * Cond")
## [1] "MODEL 2: Gratitude_S_mean ~ Heroism * Cond"
 (Anova(mod2 <- lm(Gratitude_S_mean ~ Heroism * Cond, data = scale_scores2), type = "III"))
paste0("MODEL 3: Gratitude_S_mean ~ Heroism +scale(Attitude)")
## [1] "MODEL 3: Gratitude_S_mean ~ Heroism +scale(Attitude)"
 (Anova(mod3 <- lm(Gratitude_S_mean ~ Heroism   + Cond +scale(Attitude) , data = scale_scores2), type = "III"))
paste0("MODEL 4: Gratitude_S_mean ~ Heroism * Cond + scale(Attitude)")
## [1] "MODEL 4: Gratitude_S_mean ~ Heroism * Cond + scale(Attitude)"
 (Anova(mod4 <-lm(Gratitude_S_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores2), type = "III"))
paste0("Model Comparison: assessing the importance of attitude - not accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - not accounting for occupations"
anova(mod1, mod3)
paste0("Model Comparison: assessing the importance of attitude - accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - accounting for occupations"
anova(mod2, mod4)
ggplot(scale_scores2, aes(y = Gratitude_S_mean, x = Heroism)) +
  # 1) points colored by Cond
  geom_point(aes(color = Cond),
             size = 2.7, alpha = 0.7) +
  
  # 2) ONE global lm line (group = 1 prevents one line per Cond)
  stat_smooth(method = "lm", se = TRUE,
              aes(group = 1),
              color = "black", linewidth = 1) +
  
  # Nice, accessible palette (works well with >3 groups)
  scale_color_brewer(palette = "Set2") +
  
  labs(
    y = "Gratitude (S)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Gratitude (S), colored by Occupation",
    subtitle = "Points are participants; black line is overall linear fit with 95% CI"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "right",
    panel.grid.minor = element_blank()
  )
## `geom_smooth()` using formula = 'y ~ x'

ggplot(scale_scores2, aes(y = Gratitude_S_mean, x = Heroism, color = Cond)) +
  # points colored by condition
  geom_point(size = 2.7, alpha = 0.7) +
  # ONE lm line PER condition (because color is mapped here)
  stat_smooth(method = "lm", se = FALSE, linewidth = 1, fullrange = TRUE) +
  scale_color_brewer(palette = "Set2") +
  labs(
    y = "Gratitude (S)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Gratitude (S) with per-Occupation linear fits"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "right", panel.grid.minor = element_blank())
## `geom_smooth()` using formula = 'y ~ x'

Overall: Heroism predict specific gratitude. This is true above and beyond attitude (see Model 3). It is true when controlling for normative effects of occupations (Model 2), and when controlling for both occupations and attitude (Model 4). See Summary table:

Model 1: “~Heroism + Occupation”

# =========================
# OLD MODELS (Heroism)
# =========================
m1_H <- lm(Gratitude_S_mean ~ Heroism + Occupation, data = scale_scores2)
m2_H <- lm(Gratitude_S_mean ~ Heroism * Occupation, data = scale_scores2)
m3_H <- lm(Gratitude_S_mean ~ Heroism + Occupation + Attitude, data = scale_scores2)
m4_H <- lm(Gratitude_S_mean ~ Heroism * Occupation + Attitude, data = scale_scores2)

tidy_type3(m1_H, "~Heroism + Cond")
~Heroism + Cond
F p eta2p
Heroism 158.96 < .001 0.276
Occupation 6.70 = 0.001 0.031
# =========================
# NEW MODELS (Danger & Help)
# =========================

Model 2: “~Heroism * Occupation”

tidy_type3(m2_H, "~Heroism * Cond")
~Heroism * Cond
F p eta2p
Heroism 146.87 < .001 0.261
Occupation 1.16 = 0.313 0.006
Heroism:Occupation 0.47 = 0.624 0.002

Model 3: “~Heroism + Occupation + Attitude”

tidy_type3(m3_H, "~Heroism + Occupation + Attitude")
~Heroism + Occupation + Attitude
F p eta2p
Heroism 39.98 < .001 0.088
Occupation 3.70 = 0.026 0.017
Attitude 17.28 < .001 0.040

Model 4: “~Heroism * Occupation + Attitude”

tidy_type3(m4_H, "~Heroism * Occupation + Attitude")
~Heroism * Occupation + Attitude
F p eta2p
Heroism 39.13 < .001 0.086
Occupation 0.29 = 0.750 0.001
Attitude 16.37 < .001 0.038
Heroism:Occupation 0.07 = 0.929 0.000

==> Full support for our hypotheses. Across all occupations Heroism predict general- and specific-level gratitude, with or without controlling for attitude.

Comparison of main effects across models

# Use orthogonal contrasts; keeps your "Heroism main effect" interpretable with Cond in the model
old_contr <- options(contrasts = c("contr.sum", "contr.poly"))

# Helper to extract the main effect of "Heroism" from a car::Anova table
extract_heroism <- function(mod, model_label) {
  a <- car::Anova(mod, type = "III")

  # Coerce to data.frame with rownames preserved
  tab <- as.data.frame(a)
  tab$Term <- rownames(tab)

  # Standard column names across R versions
  # (car::Anova uses these names for lm/glm type=III)
  # Columns: "Sum Sq", "Df", "F value", "Pr(>F)"
  names(tab) <- sub(" ", "_", names(tab))  # make names safe: "Sum_Sq", "F_value", etc.

  # Pull rows we need
  hero <- tab %>% filter(Term == "Heroism")
  resid <- tab %>% filter(Term == "Residuals")

  # Safety checks (in case of name variants)
  stopifnot(nrow(hero) == 1, nrow(resid) == 1)

  # Partial eta^2 = SS_effect / (SS_effect + SS_residual)
  eta_p2 <- hero$Sum_Sq / (hero$Sum_Sq + resid$Sum_Sq)

  # Format p nicely (APA-ish)
  p_fmt <- ifelse(hero$`Pr(>F)` < .001, "< .001",
                  sprintf("= %.3f", hero$`Pr(>F)`))

  dplyr::tibble(
    Model = model_label,
    F = hero$F_value,
    p = p_fmt,
    eta2p = eta_p2
  )
}

# ----- Fit the four models -----
mod1 <- lm(Gratitude_S_mean ~ Heroism   + Cond, data = scale_scores2)
mod2 <- lm(Gratitude_S_mean ~ Heroism * Cond, data = scale_scores2)
mod3 <- lm(Gratitude_S_mean ~ Heroism   + Cond + scale(Attitude), data = scale_scores2)
mod4 <- lm(Gratitude_S_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores2)

# ----- Build the summary table -----
tbl <- bind_rows(
  extract_heroism(mod1, "~ Heroism   + Cond"),
  extract_heroism(mod2, "~ Heroism * Occupation"),
  extract_heroism(mod3, "~ Heroism   + Cond + Attitude"),
  extract_heroism(mod4, "~ Heroism * Occupation + Attitude")
  ) %>%
  # Nice number formatting for F and eta^2p
  mutate(
    F = round(F, 2),
    eta2p = round(eta2p, 3)
  )

# ----- Print as HTML-friendly table -----
# Build the table first
tbl_kbl <- tbl %>%
  kable("html",
        caption = "Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²",
        align = "lllrrrcr")
# Add a footnote in a version-robust way
if ("footnote" %in% getNamespaceExports("kableExtra")) {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::footnote(
      general = "Type-III sums of squares with sum contrasts.",
      general_title = ""
    )
} else {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::add_footnote(
      general = "Type-III sums of squares with sum contrasts.",
      notation = "none"
    )
}

tbl_kbl
Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²
Model F p eta2p
~ Heroism + Cond 158.96 < .001 0.276
~ Heroism * Occupation 146.87 < .001 0.261
~ Heroism + Cond + Attitude 39.98 < .001 0.088
~ Heroism * Occupation + Attitude 39.13 < .001 0.086
Type-III sums of squares with sum contrasts.

H2: Heroism is associated with reduced criticism acceptability

Criticism of those granted moral goodness through the ‘hero’ status might be seen as a violation of sacred values (Tetlock, 2003). As such, people should that people should not criticise the heroised workers at the general level. At the specific level, they should be more likely to approve for prosecutions and bans imposed to people who openly criticised the target workers.

SEPTEMBER DF

General level

People should think twice before they criticize journalists

Toggle details of the models diagnostics and outlier analyses

Below, you’ll find model diagnostics (QQ plot, fitted vs residuals, linearity), Outliers analysis, and more details on the output

[NOTE THAT EFFECT SIZES ED IN THE COMMANDS BELOW SHOULD NOT BE TRUSTED]

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 1: DV ~ Heroism + Occupation")
## [1] "Diagnostics for Model 1: DV ~ Heroism + Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(criticism_items_G_mean ~ Heroism  + Cond, data = scale_scores)
mod1r <- lmrob(criticism_items_G_mean ~ Heroism  + Cond, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 5.008 5.161
(0.179) (0.281)
28.025 18.380
p = <0.001 p = <0.001
Heroism −0.473 −0.508
(0.033) (0.049)
−14.297 −10.426
p = <0.001 p = <0.001
Cond1 −0.059 −0.053
(0.073) (0.073)
−0.805 −0.731
p = 0.421 p = 0.465
Cond2 0.032 0.026
(0.072) (0.073)
0.445 0.357
p = 0.657 p = 0.721
Num.Obs. 417 417
R2 0.348 0.386
R2 Adj. 0.343 0.382
RMSE 1.03 1.03
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$criticism_items_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 2: DV ~ Heroism * Occupation")
## [1] "Diagnostics for Model 2: DV ~ Heroism * Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(criticism_items_G_mean ~ Heroism  * Cond, data = scale_scores)
mod1r <- lmrob(criticism_items_G_mean ~ Heroism  * Cond, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 4.947 5.133
(0.180) (0.261)
27.427 19.688
p = <0.001 p = <0.001
Heroism −0.461 −0.502
(0.034) (0.045)
−13.675 −11.123
p = <0.001 p = <0.001
Cond1 −0.120 −0.291
(0.259) (0.391)
−0.462 −0.744
p = 0.644 p = 0.457
Cond2 0.500 0.649
(0.243) (0.358)
2.062 1.813
p = 0.040 p = 0.071
Heroism × Cond1 0.009 0.039
(0.046) (0.064)
0.190 0.612
p = 0.849 p = 0.541
Heroism × Cond2 −0.093 −0.117
(0.045) (0.061)
−2.053 −1.909
p = 0.041 p = 0.057
Num.Obs. 417 417
R2 0.355 0.406
R2 Adj. 0.347 0.399
RMSE 1.02 1.03
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$criticism_items_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude")
## [1] "Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(criticism_items_G_mean ~ Heroism  + Cond + Attitude, data = scale_scores)
mod1r <- lmrob(criticism_items_G_mean ~ Heroism  + Cond + Attitude, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 6.473 6.672
(0.234) (0.262)
27.661 25.425
p = <0.001 p = <0.001
Heroism −0.191 −0.216
(0.044) (0.051)
−4.315 −4.220
p = <0.001 p = <0.001
Cond1 0.054 0.046
(0.068) (0.065)
0.795 0.713
p = 0.427 p = 0.476
Cond2 −0.083 −0.081
(0.067) (0.066)
−1.239 −1.219
p = 0.216 p = 0.223
Attitude −0.504 −0.519
(0.057) (0.062)
−8.789 −8.433
p = <0.001 p = <0.001
Num.Obs. 417 417
R2 0.451 0.505
R2 Adj. 0.445 0.500
RMSE 0.94 0.95
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$criticism_items_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude")
## [1] "Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(criticism_items_G_mean ~ Heroism*Cond + Attitude, data = scale_scores)
mod1r <- lmrob(criticism_items_G_mean ~ Heroism*Cond + Attitude, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 6.443 6.644
(0.243) (0.271)
26.565 24.530
p = <0.001 p = <0.001
Heroism −0.190 −0.217
(0.045) (0.051)
−4.255 −4.219
p = <0.001 p = <0.001
Cond1 0.136 0.041
(0.241) (0.299)
0.563 0.138
p = 0.574 p = 0.890
Cond2 −0.007 0.001
(0.232) (0.275)
−0.030 0.004
p = 0.976 p = 0.997
Attitude −0.499 −0.514
(0.059) (0.064)
−8.484 −8.048
p = <0.001 p = <0.001
Heroism × Cond1 −0.016 0.000
(0.043) (0.050)
−0.374 0.005
p = 0.708 p = 0.996
Heroism × Cond2 −0.016 −0.016
(0.043) (0.047)
−0.370 −0.332
p = 0.712 p = 0.740
Num.Obs. 417 417
R2 0.451 0.505
R2 Adj. 0.443 0.497
RMSE 0.94 0.94
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$criticism_items_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0(" S for each model (please ignore the eta squares, they are way off")
## [1] " S for each model (please ignore the eta squares, they are way off"
paste0("####################################################")
## [1] "####################################################"
paste0("MODEL 1: criticism_items_G_mean ~ Heroism")
## [1] "MODEL 1: criticism_items_G_mean ~ Heroism"
 (Anova(mod1 <- lm(criticism_items_G_mean ~ Heroism  + Cond, data = scale_scores), type = "III"))
paste0("MODEL 2: criticism_items_G_mean ~ Heroism * Cond")
## [1] "MODEL 2: criticism_items_G_mean ~ Heroism * Cond"
 (Anova(mod2 <- lm(criticism_items_G_mean ~ Heroism * Cond, data = scale_scores), type = "III"))
paste0("MODEL 3: criticism_items_G_mean ~ Heroism +scale(Attitude)")
## [1] "MODEL 3: criticism_items_G_mean ~ Heroism +scale(Attitude)"
 (Anova(mod3 <- lm(criticism_items_G_mean ~ Heroism   + Cond +scale(Attitude) , data = scale_scores), type = "III"))
paste0("MODEL 4: criticism_items_G_mean ~ Heroism * Cond + scale(Attitude)")
## [1] "MODEL 4: criticism_items_G_mean ~ Heroism * Cond + scale(Attitude)"
 (Anova(mod4 <-lm(criticism_items_G_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores), type = "III"))
paste0("Model Comparison: assessing the importance of attitude - not accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - not accounting for occupations"
anova(mod1, mod3)
paste0("Model Comparison: assessing the importance of attitude - accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - accounting for occupations"
anova(mod2, mod4)
ggplot(scale_scores, aes(y = criticism_items_G_mean, x = Heroism)) +
  # 1) points colored by Cond
  geom_point(aes(color = Cond),
             size = 2.7, alpha = 0.7) +
  
  # 2) ONE global lm line (group = 1 prevents one line per Cond)
  stat_smooth(method = "lm", se = TRUE,
              aes(group = 1),
              color = "black", linewidth = 1) +
  
  # Nice, accessible palette (works well with >3 groups)
  scale_color_brewer(palette = "Set2") +
  
  labs(
    y = "Criticism accept. (G)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Criticism accept. (G), colored by Occupation",
    subtitle = "Points are participants; black line is overall linear fit with 95% CI"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "right",
    panel.grid.minor = element_blank()
  )
## `geom_smooth()` using formula = 'y ~ x'

ggplot(scale_scores, aes(y = criticism_items_G_mean, x = Heroism, color = Cond)) +
  # points colored by condition
  geom_point(size = 2.7, alpha = 0.7) +
  # ONE lm line PER condition (because color is mapped here)
  stat_smooth(method = "lm", se = FALSE, linewidth = 1, fullrange = TRUE) +
  scale_color_brewer(palette = "Set2") +
  labs(
    y = "Criticism accept. (G)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Criticism accept. (G) with per-Occupation linear fits"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "right", panel.grid.minor = element_blank())
## `geom_smooth()` using formula = 'y ~ x'

Overall: Heroism predict decreased general acceptability of criticism. This is true above and beyond attitude (see Model 3). It is true when controlling for normative effects of occupations (Model 2), and when controlling for both occupations and attitude (Model 4). See Summary table:

Model 1: “~Heroism + Occupation”

# =========================
# OLD MODELS (Heroism)
# =========================
m1_H <- lm(criticism_items_G_mean ~ Heroism + Occupation, data = scale_scores)
m2_H <- lm(criticism_items_G_mean ~ Heroism * Occupation, data = scale_scores)
m3_H <- lm(criticism_items_G_mean ~ Heroism + Occupation + Attitude, data = scale_scores)
m4_H <- lm(criticism_items_G_mean ~ Heroism * Occupation + Attitude, data = scale_scores)

tidy_type3(m1_H, "~Heroism + Cond")
~Heroism + Cond
F p eta2p
Heroism 204.40 < .001 0.331
Occupation 0.33 = 0.722 0.002
# =========================
# NEW MODELS (Danger & Help)
# =========================

Model 2: “~Heroism * Occupation”

tidy_type3(m2_H, "~Heroism * Cond")
~Heroism * Cond
F p eta2p
Heroism 187.02 < .001 0.313
Occupation 2.26 = 0.105 0.011
Heroism:Occupation 2.31 = 0.100 0.011

Model 3: “~Heroism + Occupation + Attitude”

tidy_type3(m3_H, "~Heroism + Occupation + Attitude")
~Heroism + Occupation + Attitude
F p eta2p
Heroism 18.62 < .001 0.043
Occupation 0.78 = 0.457 0.004
Attitude 77.24 < .001 0.158

Model 4: “~Heroism * Occupation + Attitude”

tidy_type3(m4_H, "~Heroism * Occupation + Attitude")
~Heroism * Occupation + Attitude
F p eta2p
Heroism 18.11 < .001 0.042
Occupation 0.19 = 0.825 0.001
Attitude 71.98 < .001 0.149
Heroism:Occupation 0.23 = 0.798 0.001

Comparison of main predictors across models

# Use orthogonal contrasts; keeps your "Heroism main effect" interpretable with Cond in the model
old_contr <- options(contrasts = c("contr.sum", "contr.poly"))

# Helper to extract the main effect of "Heroism" from a car::Anova table
extract_heroism <- function(mod, model_label) {
  a <- car::Anova(mod, type = "III")

  # Coerce to data.frame with rownames preserved
  tab <- as.data.frame(a)
  tab$Term <- rownames(tab)

  # Standard column names across R versions
  # (car::Anova uses these names for lm/glm type=III)
  # Columns: "Sum Sq", "Df", "F value", "Pr(>F)"
  names(tab) <- sub(" ", "_", names(tab))  # make names safe: "Sum_Sq", "F_value", etc.

  # Pull rows we need
  hero <- tab %>% filter(Term == "Heroism")
  resid <- tab %>% filter(Term == "Residuals")

  # Safety checks (in case of name variants)
  stopifnot(nrow(hero) == 1, nrow(resid) == 1)

  # Partial eta^2 = SS_effect / (SS_effect + SS_residual)
  eta_p2 <- hero$Sum_Sq / (hero$Sum_Sq + resid$Sum_Sq)

  # Format p nicely (APA-ish)
  p_fmt <- ifelse(hero$`Pr(>F)` < .001, "< .001",
                  sprintf("= %.3f", hero$`Pr(>F)`))

  dplyr::tibble(
    Model = model_label,
    F = hero$F_value,
    p = p_fmt,
    eta2p = eta_p2
  )
}

# ----- Fit the four models -----
mod1 <- lm(criticism_items_G_mean ~ Heroism   + Cond, data = scale_scores)
mod2 <- lm(criticism_items_G_mean ~ Heroism * Cond, data = scale_scores)
mod3 <- lm(criticism_items_G_mean ~ Heroism   + Cond + scale(Attitude), data = scale_scores)
mod4 <- lm(criticism_items_G_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores)

# ----- Build the summary table -----
tbl <- bind_rows(
  extract_heroism(mod1, "~ Heroism   + Cond"),
  extract_heroism(mod2, "~ Heroism * Occupation"),
  extract_heroism(mod3, "~ Heroism   + Cond + Attitude"),
  extract_heroism(mod4, "~ Heroism * Occupation + Attitude")
  ) %>%
  # Nice number formatting for F and eta^2p
  mutate(
    F = round(F, 2),
    eta2p = round(eta2p, 3)
  )

# ----- Print as HTML-friendly table -----
# Build the table first
tbl_kbl <- tbl %>%
  kable("html",
        caption = "Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²",
        align = "lllrrrcr")
# Add a footnote in a version-robust way
if ("footnote" %in% getNamespaceExports("kableExtra")) {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::footnote(
      general = "Type-III sums of squares with sum contrasts.",
      general_title = ""
    )
} else {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::add_footnote(
      general = "Type-III sums of squares with sum contrasts.",
      notation = "none"
    )
}

tbl_kbl
Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²
Model F p eta2p
~ Heroism + Cond 204.40 < .001 0.331
~ Heroism * Occupation 187.02 < .001 0.313
~ Heroism + Cond + Attitude 18.62 < .001 0.043
~ Heroism * Occupation + Attitude 18.11 < .001 0.042
Type-III sums of squares with sum contrasts.

Specific level

[An online post that says: “Journalists are evil and wish harm on other people”] The person making this post should be prosecuted using the UK laws against “grossly offensive” public messaging

Toggle details of the models diagnostics and outlier analyses

Below, you’ll find model diagnostics (QQ plot, fitted vs residuals, linearity), Outliers analysis, and more details on the output

[NOTE THAT EFFECT SIZES ED IN THE COMMANDS BELOW SHOULD NOT BE TRUSTED]

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 1: DV ~ Heroism + Occupation")
## [1] "Diagnostics for Model 1: DV ~ Heroism + Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(criticism_items_S_mean ~ Heroism  + Cond, data = scale_scores, na.action = na.exclude)
mod1r <- lmrob(criticism_items_S_mean ~ Heroism  + Cond, data = scale_scores, na.action = na.exclude)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.389 3.536
(0.157) (0.181)
21.608 19.527
p = <0.001 p = <0.001
Heroism −0.211 −0.249
(0.029) (0.034)
−7.271 −7.340
p = <0.001 p = <0.001
Cond1 0.017 0.050
(0.064) (0.065)
0.263 0.774
p = 0.792 p = 0.439
Cond2 −0.019 −0.031
(0.063) (0.069)
−0.300 −0.449
p = 0.764 p = 0.653
Num.Obs. 417 417
R2 0.117 0.152
R2 Adj. 0.111 0.146
RMSE 0.90 0.91
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$criticism_items_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 2: DV ~ Heroism * Occupation")
## [1] "Diagnostics for Model 2: DV ~ Heroism * Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(criticism_items_S_mean ~ Heroism  * Cond, data = scale_scores, na.action = na.exclude)
mod1r <- lmrob(criticism_items_S_mean ~ Heroism  * Cond, data = scale_scores, na.action = na.exclude)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.368 3.503
(0.159) (0.189)
21.199 18.544
p = <0.001 p = <0.001
Heroism −0.205 −0.241
(0.030) (0.037)
−6.899 −6.607
p = <0.001 p = <0.001
Cond1 0.257 0.150
(0.228) (0.247)
1.124 0.609
p = 0.261 p = 0.543
Cond2 0.023 0.092
(0.214) (0.245)
0.110 0.377
p = 0.913 p = 0.707
Heroism × Cond1 −0.045 −0.020
(0.041) (0.044)
−1.114 −0.445
p = 0.266 p = 0.657
Heroism × Cond2 −0.011 −0.026
(0.040) (0.046)
−0.271 −0.562
p = 0.786 p = 0.574
Num.Obs. 417 417
R2 0.121 0.153
R2 Adj. 0.111 0.142
RMSE 0.90 0.90
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$criticism_items_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude")
## [1] "Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(criticism_items_S_mean ~ Heroism  + Cond + Attitude, data = scale_scores, na.action = na.exclude)
mod1r <- lmrob(criticism_items_S_mean ~ Heroism  + Cond + Attitude, data = scale_scores, na.action = na.exclude)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.776 3.946
(0.222) (0.225)
16.991 17.517
p = <0.001 p = <0.001
Heroism −0.137 −0.163
(0.042) (0.054)
−3.254 −3.018
p = 0.001 p = 0.003
Cond1 0.047 0.082
(0.065) (0.065)
0.721 1.267
p = 0.472 p = 0.206
Cond2 −0.049 −0.067
(0.064) (0.070)
−0.773 −0.961
p = 0.440 p = 0.337
Attitude −0.133 −0.148
(0.054) (0.065)
−2.444 −2.259
p = 0.015 p = 0.024
Num.Obs. 417 417
R2 0.130 0.165
R2 Adj. 0.122 0.157
RMSE 0.90 0.90
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$criticism_items_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude")
## [1] "Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(criticism_items_S_mean ~ Heroism*Cond + Attitude, data = scale_scores, na.action = na.exclude)
mod1r <- lmrob(criticism_items_S_mean ~ Heroism*Cond + Attitude, data = scale_scores, na.action = na.exclude)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.773 3.922
(0.230) (0.240)
16.404 16.336
p = <0.001 p = <0.001
Heroism −0.131 −0.158
(0.042) (0.056)
−3.108 −2.810
p = 0.002 p = 0.005
Cond1 0.326 0.221
(0.229) (0.243)
1.425 0.910
p = 0.155 p = 0.363
Cond2 −0.114 −0.058
(0.220) (0.237)
−0.517 −0.243
p = 0.605 p = 0.808
Attitude −0.135 −0.147
(0.056) (0.066)
−2.420 −2.209
p = 0.016 p = 0.028
Heroism × Cond1 −0.052 −0.026
(0.041) (0.044)
−1.284 −0.606
p = 0.200 p = 0.545
Heroism × Cond2 0.010 −0.003
(0.041) (0.044)
0.248 −0.071
p = 0.804 p = 0.944
Num.Obs. 417 417
R2 0.134 0.164
R2 Adj. 0.121 0.152
RMSE 0.89 0.90
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$criticism_items_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0(" S for each model (please ignore the eta squares, they are way off")
## [1] " S for each model (please ignore the eta squares, they are way off"
paste0("####################################################")
## [1] "####################################################"
paste0("MODEL 1: criticism_items_S_mean ~ Heroism")
## [1] "MODEL 1: criticism_items_S_mean ~ Heroism"
 (Anova(mod1 <- lm(criticism_items_S_mean ~ Heroism  + Cond, data = scale_scores), type = "III"))
paste0("MODEL 2: criticism_items_S_mean ~ Heroism * Cond")
## [1] "MODEL 2: criticism_items_S_mean ~ Heroism * Cond"
 (Anova(mod2 <- lm(criticism_items_S_mean ~ Heroism * Cond, data = scale_scores), type = "III"))
paste0("MODEL 3: criticism_items_S_mean ~ Heroism +scale(Attitude)")
## [1] "MODEL 3: criticism_items_S_mean ~ Heroism +scale(Attitude)"
 (Anova(mod3 <- lm(criticism_items_S_mean ~ Heroism   + Cond +scale(Attitude) , data = scale_scores), type = "III"))
paste0("MODEL 4: criticism_items_S_mean ~ Heroism * Cond + scale(Attitude)")
## [1] "MODEL 4: criticism_items_S_mean ~ Heroism * Cond + scale(Attitude)"
 (Anova(mod4 <-lm(criticism_items_S_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores), type = "III"))
paste0("Model Comparison: assessing the importance of attitude - not accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - not accounting for occupations"
anova(mod1, mod3)
paste0("Model Comparison: assessing the importance of attitude - accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - accounting for occupations"
anova(mod2, mod4)
ggplot(scale_scores, aes(y = criticism_items_S_mean, x = Heroism)) +
  # 1) points colored by Cond
  geom_point(aes(color = Cond),
             size = 2.7, alpha = 0.7) +
  
  # 2) ONE global lm line (group = 1 prevents one line per Cond)
  stat_smooth(method = "lm", se = TRUE,
              aes(group = 1),
              color = "black", linewidth = 1) +
  
  # Nice, accessible palette (works well with >3 groups)
  scale_color_brewer(palette = "Set2") +
  
  labs(
    y = "Criticism accept. (S)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Criticism accept. (S), colored by Occupation",
    subtitle = "Points are participants; black line is overall linear fit with 95% CI"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "right",
    panel.grid.minor = element_blank()
  )
## `geom_smooth()` using formula = 'y ~ x'

ggplot(scale_scores, aes(y = criticism_items_S_mean, x = Heroism, color = Cond)) +
  # points colored by condition
  geom_point(size = 2.7, alpha = 0.7) +
  # ONE lm line PER condition (because color is mapped here)
  stat_smooth(method = "lm", se = FALSE, linewidth = 1, fullrange = TRUE) +
  scale_color_brewer(palette = "Set2") +
  labs(
    y = "Criticism accept. (S)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Criticism accept. (S) with per-Occupation linear fits"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "right", panel.grid.minor = element_blank())
## `geom_smooth()` using formula = 'y ~ x'

Overall: Heroism predict decreased specific acceptability of criticism. This is true above and beyond attitude (see Model 3)– Although effect sizes are drastically reduced when accounting for attitude in the case of specific acceptability of criticism. It is true when controlling for normative effects of occupations (Model 2), and when controlling for both occupations and attitude (Model 4). See Summary table:

Model 1: “~Heroism + Occupation”

# =========================
# OLD MODELS (Heroism)
# =========================
m1_H <- lm(criticism_items_S_mean ~ Heroism + Occupation, data = scale_scores)
m2_H <- lm(criticism_items_S_mean ~ Heroism * Occupation, data = scale_scores)
m3_H <- lm(criticism_items_S_mean ~ Heroism + Occupation + Attitude, data = scale_scores)
m4_H <- lm(criticism_items_S_mean ~ Heroism * Occupation + Attitude, data = scale_scores)

tidy_type3(m1_H, "~Heroism + Cond")
~Heroism + Cond
F p eta2p
Heroism 52.87 < .001 0.113
Occupation 0.05 = 0.948 0.000
# =========================
# NEW MODELS (Danger & Help)
# =========================

Model 2: “~Heroism * Occupation”

tidy_type3(m2_H, "~Heroism * Cond")
~Heroism * Cond
F p eta2p
Heroism 47.59 < .001 0.104
Occupation 0.87 = 0.419 0.004
Heroism:Occupation 0.90 = 0.406 0.004

Model 3: “~Heroism + Occupation + Attitude”

tidy_type3(m3_H, "~Heroism + Occupation + Attitude")
~Heroism + Occupation + Attitude
F p eta2p
Heroism 10.59 = 0.001 0.025
Occupation 0.37 = 0.691 0.002
Attitude 5.97 = 0.015 0.014

Model 4: “~Heroism * Occupation + Attitude”

tidy_type3(m4_H, "~Heroism * Occupation + Attitude")
~Heroism * Occupation + Attitude
F p eta2p
Heroism 9.66 = 0.002 0.023
Occupation 1.03 = 0.359 0.005
Attitude 5.86 = 0.016 0.014
Heroism:Occupation 0.86 = 0.424 0.004

Comparison of main predictors across models

# Use orthogonal contrasts; keeps your "Heroism main effect" interpretable with Cond in the model
old_contr <- options(contrasts = c("contr.sum", "contr.poly"))

# Helper to extract the main effect of "Heroism" from a car::Anova table
extract_heroism <- function(mod, model_label) {
  a <- car::Anova(mod, type = "III")

  # Coerce to data.frame with rownames preserved
  tab <- as.data.frame(a)
  tab$Term <- rownames(tab)

  # Standard column names across R versions
  # (car::Anova uses these names for lm/glm type=III)
  # Columns: "Sum Sq", "Df", "F value", "Pr(>F)"
  names(tab) <- sub(" ", "_", names(tab))  # make names safe: "Sum_Sq", "F_value", etc.

  # Pull rows we need
  hero <- tab %>% filter(Term == "Heroism")
  resid <- tab %>% filter(Term == "Residuals")

  # Safety checks (in case of name variants)
  stopifnot(nrow(hero) == 1, nrow(resid) == 1)

  # Partial eta^2 = SS_effect / (SS_effect + SS_residual)
  eta_p2 <- hero$Sum_Sq / (hero$Sum_Sq + resid$Sum_Sq)

  # Format p nicely (APA-ish)
  p_fmt <- ifelse(hero$`Pr(>F)` < .001, "< .001",
                  sprintf("= %.3f", hero$`Pr(>F)`))

  dplyr::tibble(
    Model = model_label,
    F = hero$F_value,
    p = p_fmt,
    eta2p = eta_p2
  )
}

# ----- Fit the four models -----
mod1 <- lm(criticism_items_S_mean ~ Heroism   + Cond, data = scale_scores)
mod2 <- lm(criticism_items_S_mean ~ Heroism * Cond, data = scale_scores)
mod3 <- lm(criticism_items_S_mean ~ Heroism  + Cond+ scale(Attitude), data = scale_scores)
mod4 <- lm(criticism_items_S_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores)

# ----- Build the summary table -----
tbl <- bind_rows(
  extract_heroism(mod1, "~ Heroism + Cond"),
  extract_heroism(mod2, "~ Heroism * Occupation"),
  extract_heroism(mod3, "~ Heroism + Cond + Attitude"),
  extract_heroism(mod4, "~ Heroism * Occupation + Attitude")
  ) %>%
  # Nice number formatting for F and eta^2p
  mutate(
    F = round(F, 2),
    eta2p = round(eta2p, 3)
  )

# ----- Print as HTML-friendly table -----
# Build the table first
tbl_kbl <- tbl %>%
  kable("html",
        caption = "Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²",
        align = "lllrrrcr")
# Add a footnote in a version-robust way
if ("footnote" %in% getNamespaceExports("kableExtra")) {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::footnote(
      general = "Type-III sums of squares with sum contrasts.",
      general_title = ""
    )
} else {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::add_footnote(
      general = "Type-III sums of squares with sum contrasts.",
      notation = "none"
    )
}

tbl_kbl
Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²
Model F p eta2p
~ Heroism + Cond 52.87 < .001 0.113
~ Heroism * Occupation 47.59 < .001 0.104
~ Heroism + Cond + Attitude 10.59 = 0.001 0.025
~ Heroism * Occupation + Attitude 9.66 = 0.002 0.023
Type-III sums of squares with sum contrasts.

==> Full support for our hypotheses. Across all occupations Heroism predict general- and specific-level acceptability of criticism, with or without controlling for attitude.

NOVEMBER DF

General level

People should think twice before they criticize journalists

Toggle details of the models diagnostics and outlier analyses

Below, you’ll find model diagnostics (QQ plot, fitted vs residuals, linearity), Outliers analysis, and more details on the output

[NOTE THAT EFFECT SIZES ED IN THE COMMANDS BELOW SHOULD NOT BE TRUSTED]

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 1: DV ~ Heroism + Occupation")
## [1] "Diagnostics for Model 1: DV ~ Heroism + Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(criticism_items_G_mean ~ Heroism  + Cond, data = scale_scores2)
mod1r <- lmrob(criticism_items_G_mean ~ Heroism  + Cond, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 5.111 5.267
(0.180) (0.295)
28.446 17.862
p = <0.001 p = <0.001
Heroism −0.472 −0.507
(0.034) (0.053)
−13.825 −9.546
p = <0.001 p = <0.001
Cond1 −0.161 −0.191
(0.077) (0.073)
−2.087 −2.623
p = 0.038 p = 0.009
Cond2 0.158 0.153
(0.075) (0.077)
2.107 1.982
p = 0.036 p = 0.048
Num.Obs. 421 421
R2 0.347 0.401
R2 Adj. 0.343 0.397
RMSE 1.08 1.09
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$criticism_items_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 2: DV ~ Heroism * Occupation")
## [1] "Diagnostics for Model 2: DV ~ Heroism * Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(criticism_items_G_mean ~ Heroism  * Cond, data = scale_scores2)
mod1r <- lmrob(criticism_items_G_mean ~ Heroism  * Cond, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 4.914 5.059
(0.179) (0.195)
27.487 25.909
p = <0.001 p = <0.001
Heroism −0.435 −0.469
(0.034) (0.036)
−12.846 −12.996
p = <0.001 p = <0.001
Cond1 −0.776 −1.047
(0.273) (0.285)
−2.837 −3.673
p = 0.005 p = <0.001
Cond2 1.335 1.708
(0.234) (0.247)
5.702 6.916
p = <0.001 p = <0.001
Heroism × Cond1 0.109 0.147
(0.049) (0.049)
2.197 2.964
p = 0.029 p = 0.003
Heroism × Cond2 −0.237 −0.301
(0.045) (0.044)
−5.312 −6.782
p = <0.001 p = <0.001
Num.Obs. 421 421
R2 0.389 0.497
R2 Adj. 0.382 0.491
RMSE 1.05 1.06
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$criticism_items_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude")
## [1] "Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(criticism_items_G_mean ~ Heroism  + Cond + Attitude, data = scale_scores2)
mod1r <- lmrob(criticism_items_G_mean ~ Heroism  + Cond + Attitude, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 6.599 6.655
(0.245) (0.252)
26.918 26.395
p = <0.001 p = <0.001
Heroism −0.218 −0.193
(0.044) (0.063)
−4.923 −3.091
p = <0.001 p = 0.002
Cond1 0.046 0.030
(0.076) (0.071)
0.608 0.417
p = 0.544 p = 0.677
Cond2 −0.025 −0.041
(0.073) (0.071)
−0.342 −0.576
p = 0.732 p = 0.565
Attitude −0.483 −0.526
(0.058) (0.069)
−8.276 −7.642
p = <0.001 p = <0.001
Num.Obs. 421 421
R2 0.440 0.494
R2 Adj. 0.434 0.489
RMSE 1.00 1.01
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$criticism_items_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude")
## [1] "Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(criticism_items_G_mean ~ Heroism*Cond + Attitude, data = scale_scores2)
mod1r <- lmrob(criticism_items_G_mean ~ Heroism*Cond + Attitude, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 6.312 6.272
(0.262) (0.241)
24.068 26.059
p = <0.001 p = <0.001
Heroism −0.226 −0.228
(0.044) (0.066)
−5.146 −3.445
p = <0.001 p = <0.001
Cond1 −0.228 −0.390
(0.270) (0.304)
−0.843 −1.285
p = 0.400 p = 0.199
Cond2 0.683 0.946
(0.241) (0.317)
2.839 2.979
p = 0.005 p = 0.003
Attitude −0.425 −0.428
(0.061) (0.082)
−6.979 −5.187
p = <0.001 p = <0.001
Heroism × Cond1 0.042 0.064
(0.048) (0.050)
0.888 1.260
p = 0.375 p = 0.208
Heroism × Cond2 −0.139 −0.187
(0.044) (0.054)
−3.139 −3.443
p = 0.002 p = <0.001
Num.Obs. 421 421
R2 0.453 0.530
R2 Adj. 0.445 0.523
RMSE 0.99 1.00
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$criticism_items_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0(" S for each model (please ignore the eta squares, they are way off")
## [1] " S for each model (please ignore the eta squares, they are way off"
paste0("####################################################")
## [1] "####################################################"
paste0("MODEL 1: criticism_items_G_mean ~ Heroism")
## [1] "MODEL 1: criticism_items_G_mean ~ Heroism"
 (Anova(mod1 <- lm(criticism_items_G_mean ~ Heroism  + Cond, data = scale_scores2), type = "III"))
paste0("MODEL 2: criticism_items_G_mean ~ Heroism * Cond")
## [1] "MODEL 2: criticism_items_G_mean ~ Heroism * Cond"
 (Anova(mod2 <- lm(criticism_items_G_mean ~ Heroism * Cond, data = scale_scores2), type = "III"))
paste0("MODEL 3: criticism_items_G_mean ~ Heroism +scale(Attitude)")
## [1] "MODEL 3: criticism_items_G_mean ~ Heroism +scale(Attitude)"
 (Anova(mod3 <- lm(criticism_items_G_mean ~ Heroism   + Cond +scale(Attitude) , data = scale_scores2), type = "III"))
paste0("MODEL 4: criticism_items_G_mean ~ Heroism * Cond + scale(Attitude)")
## [1] "MODEL 4: criticism_items_G_mean ~ Heroism * Cond + scale(Attitude)"
 (Anova(mod4 <-lm(criticism_items_G_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores2), type = "III"))
paste0("Model Comparison: assessing the importance of attitude - not accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - not accounting for occupations"
anova(mod1, mod3)
paste0("Model Comparison: assessing the importance of attitude - accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - accounting for occupations"
anova(mod2, mod4)
ggplot(scale_scores2, aes(y = criticism_items_G_mean, x = Heroism)) +
  # 1) points colored by Cond
  geom_point(aes(color = Cond),
             size = 2.7, alpha = 0.7) +
  
  # 2) ONE global lm line (group = 1 prevents one line per Cond)
  stat_smooth(method = "lm", se = TRUE,
              aes(group = 1),
              color = "black", linewidth = 1) +
  
  # Nice, accessible palette (works well with >3 groups)
  scale_color_brewer(palette = "Set2") +
  
  labs(
    y = "Criticism accept. (G)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Criticism accept. (G), colored by Occupation",
    subtitle = "Points are participants; black line is overall linear fit with 95% CI"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "right",
    panel.grid.minor = element_blank()
  )
## `geom_smooth()` using formula = 'y ~ x'

ggplot(scale_scores2, aes(y = criticism_items_G_mean, x = Heroism, color = Cond)) +
  # points colored by condition
  geom_point(size = 2.7, alpha = 0.7) +
  # ONE lm line PER condition (because color is mapped here)
  stat_smooth(method = "lm", se = FALSE, linewidth = 1, fullrange = TRUE) +
  scale_color_brewer(palette = "Set2") +
  labs(
    y = "Criticism accept. (G)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Criticism accept. (G) with per-Occupation linear fits"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "right", panel.grid.minor = element_blank())
## `geom_smooth()` using formula = 'y ~ x'

Overall: Heroism predict decreased general acceptability of criticism. This is true above and beyond attitude (see Model 3). It is true when controlling for normative effects of occupations (Model 2), and when controlling for both occupations and attitude (Model 4). See Summary table:

Model 1: “~Heroism + Occupation”

# =========================
# OLD MODELS (Heroism)
# =========================
m1_H <- lm(criticism_items_G_mean ~ Heroism + Occupation, data = scale_scores2)
m2_H <- lm(criticism_items_G_mean ~ Heroism * Occupation, data = scale_scores2)
m3_H <- lm(criticism_items_G_mean ~ Heroism + Occupation + Attitude, data = scale_scores2)
m4_H <- lm(criticism_items_G_mean ~ Heroism * Occupation + Attitude, data = scale_scores2)

tidy_type3(m1_H, "~Heroism + Cond")
~Heroism + Cond
F p eta2p
Heroism 191.12 < .001 0.314
Occupation 2.94 = 0.054 0.014
# =========================
# NEW MODELS (Danger & Help)
# =========================

Model 2: “~Heroism * Occupation”

tidy_type3(m2_H, "~Heroism * Cond")
~Heroism * Cond
F p eta2p
Heroism 165.02 < .001 0.285
Occupation 16.27 < .001 0.073
Heroism:Occupation 14.13 < .001 0.064

Model 3: “~Heroism + Occupation + Attitude”

tidy_type3(m3_H, "~Heroism + Occupation + Attitude")
~Heroism + Occupation + Attitude
F p eta2p
Heroism 24.24 < .001 0.055
Occupation 0.18 = 0.831 0.001
Attitude 68.49 < .001 0.141

Model 4: “~Heroism * Occupation + Attitude”

tidy_type3(m4_H, "~Heroism * Occupation + Attitude")
~Heroism * Occupation + Attitude
F p eta2p
Heroism 26.48 < .001 0.060
Occupation 4.50 = 0.012 0.021
Attitude 48.70 < .001 0.105
Heroism:Occupation 5.17 = 0.006 0.024

Comparison of main predictors across models

# Use orthogonal contrasts; keeps your "Heroism main effect" interpretable with Cond in the model
old_contr <- options(contrasts = c("contr.sum", "contr.poly"))

# Helper to extract the main effect of "Heroism" from a car::Anova table
extract_heroism <- function(mod, model_label) {
  a <- car::Anova(mod, type = "III")

  # Coerce to data.frame with rownames preserved
  tab <- as.data.frame(a)
  tab$Term <- rownames(tab)

  # Standard column names across R versions
  # (car::Anova uses these names for lm/glm type=III)
  # Columns: "Sum Sq", "Df", "F value", "Pr(>F)"
  names(tab) <- sub(" ", "_", names(tab))  # make names safe: "Sum_Sq", "F_value", etc.

  # Pull rows we need
  hero <- tab %>% filter(Term == "Heroism")
  resid <- tab %>% filter(Term == "Residuals")

  # Safety checks (in case of name variants)
  stopifnot(nrow(hero) == 1, nrow(resid) == 1)

  # Partial eta^2 = SS_effect / (SS_effect + SS_residual)
  eta_p2 <- hero$Sum_Sq / (hero$Sum_Sq + resid$Sum_Sq)

  # Format p nicely (APA-ish)
  p_fmt <- ifelse(hero$`Pr(>F)` < .001, "< .001",
                  sprintf("= %.3f", hero$`Pr(>F)`))

    dplyr::tibble(
    Model = model_label,
    F = hero$F_value,
    p = p_fmt,
    eta2p = eta_p2
  )
}

# ----- Fit the four models -----
mod1 <- lm(criticism_items_G_mean ~ Heroism   + Cond, data = scale_scores2)
mod2 <- lm(criticism_items_G_mean ~ Heroism * Cond, data = scale_scores2)
mod3 <- lm(criticism_items_G_mean ~ Heroism   + Cond + scale(Attitude), data = scale_scores2)
mod4 <- lm(criticism_items_G_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores2)

# ----- Build the summary table -----
tbl <- bind_rows(
  extract_heroism(mod1, "~ Heroism   + Cond"),
  extract_heroism(mod2, "~ Heroism * Occupation"),
  extract_heroism(mod3, "~ Heroism   + Cond + Attitude"),
  extract_heroism(mod4, "~ Heroism * Occupation + Attitude")
  ) %>%
  # Nice number formatting for F and eta^2p
  mutate(
    F = round(F, 2),
    eta2p = round(eta2p, 3)
  )

# ----- Print as HTML-friendly table -----
# Build the table first
tbl_kbl <- tbl %>%
  kable("html",
        caption = "Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²",
        align = "lllrrrcr")
# Add a footnote in a version-robust way
if ("footnote" %in% getNamespaceExports("kableExtra")) {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::footnote(
      general = "Type-III sums of squares with sum contrasts.",
      general_title = ""
    )
} else {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::add_footnote(
      general = "Type-III sums of squares with sum contrasts.",
      notation = "none"
    )
}

tbl_kbl
Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²
Model F p eta2p
~ Heroism + Cond 191.12 < .001 0.314
~ Heroism * Occupation 165.02 < .001 0.285
~ Heroism + Cond + Attitude 24.24 < .001 0.055
~ Heroism * Occupation + Attitude 26.48 < .001 0.060
Type-III sums of squares with sum contrasts.

Specific level

[An online post that says: “Journalists are evil and wish harm on other people”] The person making this post should be prosecuted using the UK laws against “grossly offensive” public messaging

Toggle details of the models diagnostics and outlier analyses

Below, you’ll find model diagnostics (QQ plot, fitted vs residuals, linearity), Outliers analysis, and more details on the output

[NOTE THAT EFFECT SIZES ED IN THE COMMANDS BELOW SHOULD NOT BE TRUSTED]

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 1: DV ~ Heroism + Occupation")
## [1] "Diagnostics for Model 1: DV ~ Heroism + Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(criticism_items_S_mean ~ Heroism  + Cond, data = scale_scores2, na.action = na.exclude)
mod1r <- lmrob(criticism_items_S_mean ~ Heroism  + Cond, data = scale_scores2, na.action = na.exclude)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.111 3.164
(0.146) (0.154)
21.286 20.513
p = <0.001 p = <0.001
Heroism −0.147 −0.162
(0.028) (0.031)
−5.279 −5.228
p = <0.001 p = <0.001
Cond1 −0.164 −0.176
(0.063) (0.068)
−2.622 −2.591
p = 0.009 p = 0.010
Cond2 0.203 0.224
(0.061) (0.067)
3.315 3.372
p = <0.001 p = <0.001
Num.Obs. 421 421
R2 0.102 0.115
R2 Adj. 0.095 0.108
RMSE 0.88 0.88
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$criticism_items_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 2: DV ~ Heroism * Occupation")
## [1] "Diagnostics for Model 2: DV ~ Heroism * Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(criticism_items_S_mean ~ Heroism  * Cond, data = scale_scores2, na.action = na.exclude)
mod1r <- lmrob(criticism_items_S_mean ~ Heroism  * Cond, data = scale_scores2, na.action = na.exclude)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.021 3.051
(0.148) (0.146)
20.371 20.853
p = <0.001 p = <0.001
Heroism −0.128 −0.139
(0.028) (0.030)
−4.546 −4.701
p = <0.001 p = <0.001
Cond1 −0.320 −0.364
(0.227) (0.219)
−1.411 −1.663
p = 0.159 p = 0.097
Cond2 0.791 0.826
(0.194) (0.179)
4.076 4.626
p = <0.001 p = <0.001
Heroism × Cond1 0.025 0.031
(0.041) (0.041)
0.614 0.770
p = 0.539 p = 0.441
Heroism × Cond2 −0.120 −0.124
(0.037) (0.037)
−3.258 −3.399
p = 0.001 p = <0.001
Num.Obs. 421 421
R2 0.126 0.136
R2 Adj. 0.115 0.126
RMSE 0.87 0.87
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$criticism_items_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude")
## [1] "Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(criticism_items_S_mean ~ Heroism  + Cond + Attitude, data = scale_scores2, na.action = na.exclude)
mod1r <- lmrob(criticism_items_S_mean ~ Heroism  + Cond + Attitude, data = scale_scores2, na.action = na.exclude)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.615 3.677
(0.213) (0.220)
17.007 16.745
p = <0.001 p = <0.001
Heroism −0.060 −0.074
(0.038) (0.040)
−1.578 −1.832
p = 0.115 p = 0.068
Cond1 −0.094 −0.098
(0.066) (0.075)
−1.437 −1.309
p = 0.151 p = 0.191
Cond2 0.141 0.156
(0.063) (0.071)
2.216 2.200
p = 0.027 p = 0.028
Attitude −0.164 −0.166
(0.051) (0.052)
−3.232 −3.176
p = 0.001 p = 0.002
Num.Obs. 421 421
R2 0.124 0.135
R2 Adj. 0.115 0.126
RMSE 0.87 0.87
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$criticism_items_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude")
## [1] "Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(criticism_items_S_mean ~ Heroism*Cond + Attitude, data = scale_scores2, na.action = na.exclude)
mod1r <- lmrob(criticism_items_S_mean ~ Heroism*Cond + Attitude, data = scale_scores2, na.action = na.exclude)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.440 3.485
(0.228) (0.253)
15.066 13.762
p = <0.001 p = <0.001
Heroism −0.065 −0.075
(0.038) (0.040)
−1.698 −1.871
p = 0.090 p = 0.062
Cond1 −0.155 −0.162
(0.236) (0.261)
−0.660 −0.621
p = 0.510 p = 0.535
Cond2 0.596 0.615
(0.209) (0.212)
2.844 2.900
p = 0.005 p = 0.004
Attitude −0.128 −0.130
(0.053) (0.058)
−2.406 −2.236
p = 0.017 p = 0.026
Heroism × Cond1 0.005 0.005
(0.042) (0.045)
0.128 0.117
p = 0.898 p = 0.907
Heroism × Cond2 −0.091 −0.093
(0.039) (0.040)
−2.357 −2.325
p = 0.019 p = 0.021
Num.Obs. 421 421
R2 0.138 0.146
R2 Adj. 0.126 0.134
RMSE 0.86 0.86
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$criticism_items_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0(" S for each model (please ignore the eta squares, they are way off")
## [1] " S for each model (please ignore the eta squares, they are way off"
paste0("####################################################")
## [1] "####################################################"
paste0("MODEL 1: criticism_items_S_mean ~ Heroism")
## [1] "MODEL 1: criticism_items_S_mean ~ Heroism"
 (Anova(mod1 <- lm(criticism_items_S_mean ~ Heroism  + Cond, data = scale_scores2), type = "III"))
paste0("MODEL 2: criticism_items_S_mean ~ Heroism * Cond")
## [1] "MODEL 2: criticism_items_S_mean ~ Heroism * Cond"
 (Anova(mod2 <- lm(criticism_items_S_mean ~ Heroism * Cond, data = scale_scores2), type = "III"))
paste0("MODEL 3: criticism_items_S_mean ~ Heroism +scale(Attitude)")
## [1] "MODEL 3: criticism_items_S_mean ~ Heroism +scale(Attitude)"
 (Anova(mod3 <- lm(criticism_items_S_mean ~ Heroism   + Cond +scale(Attitude) , data = scale_scores2), type = "III"))
paste0("MODEL 4: criticism_items_S_mean ~ Heroism * Cond + scale(Attitude)")
## [1] "MODEL 4: criticism_items_S_mean ~ Heroism * Cond + scale(Attitude)"
 (Anova(mod4 <-lm(criticism_items_S_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores2), type = "III"))
paste0("Model Comparison: assessing the importance of attitude - not accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - not accounting for occupations"
anova(mod1, mod3)
paste0("Model Comparison: assessing the importance of attitude - accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - accounting for occupations"
anova(mod2, mod4)
ggplot(scale_scores2, aes(y = criticism_items_S_mean, x = Heroism)) +
  # 1) points colored by Cond
  geom_point(aes(color = Cond),
             size = 2.7, alpha = 0.7) +
  
  # 2) ONE global lm line (group = 1 prevents one line per Cond)
  stat_smooth(method = "lm", se = TRUE,
              aes(group = 1),
              color = "black", linewidth = 1) +
  
  # Nice, accessible palette (works well with >3 groups)
  scale_color_brewer(palette = "Set2") +
  
  labs(
    y = "Criticism accept. (S)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Criticism accept. (S), colored by Occupation",
    subtitle = "Points are participants; black line is overall linear fit with 95% CI"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "right",
    panel.grid.minor = element_blank()
  )
## `geom_smooth()` using formula = 'y ~ x'

ggplot(scale_scores2, aes(y = criticism_items_S_mean, x = Heroism, color = Cond)) +
  # points colored by condition
  geom_point(size = 2.7, alpha = 0.7) +
  # ONE lm line PER condition (because color is mapped here)
  stat_smooth(method = "lm", se = FALSE, linewidth = 1, fullrange = TRUE) +
  scale_color_brewer(palette = "Set2") +
  labs(
    y = "Criticism accept. (S)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Criticism accept. (S) with per-Occupation linear fits"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "right", panel.grid.minor = element_blank())
## `geom_smooth()` using formula = 'y ~ x'

Overall: Heroism predict decreased specific acceptability of criticism. This is true above and beyond attitude (see Model 3)– Although effect sizes are drastically reduced when accounting for attitude in the case of specific acceptability of criticism. It is true when controlling for normative effects of occupations (Model 2), and when controlling for both occupations and attitude (Model 4). See Summary table:

Model 1: “~Heroism + Occupation”

# =========================
# OLD MODELS (Heroism)
# =========================
m1_H <- lm(criticism_items_S_mean ~ Heroism + Occupation, data = scale_scores2)
m2_H <- lm(criticism_items_S_mean ~ Heroism * Occupation, data = scale_scores2)
m3_H <- lm(criticism_items_S_mean ~ Heroism + Occupation + Attitude, data = scale_scores2)
m4_H <- lm(criticism_items_S_mean ~ Heroism * Occupation + Attitude, data = scale_scores2)

tidy_type3(m1_H, "~Heroism + Cond")
~Heroism + Cond
F p eta2p
Heroism 27.87 < .001 0.063
Occupation 6.14 = 0.002 0.029
# =========================
# NEW MODELS (Danger & Help)
# =========================

Model 2: “~Heroism * Occupation”

tidy_type3(m2_H, "~Heroism * Cond")
~Heroism * Cond
F p eta2p
Heroism 20.66 < .001 0.047
Occupation 8.68 < .001 0.040
Heroism:Occupation 5.75 = 0.003 0.027

Model 3: “~Heroism + Occupation + Attitude”

tidy_type3(m3_H, "~Heroism + Occupation + Attitude")
~Heroism + Occupation + Attitude
F p eta2p
Heroism 2.49 = 0.115 0.006
Occupation 2.49 = 0.084 0.012
Attitude 10.44 = 0.001 0.024

Model 4: “~Heroism * Occupation + Attitude”

tidy_type3(m4_H, "~Heroism * Occupation + Attitude")
~Heroism * Occupation + Attitude
F p eta2p
Heroism 2.88 = 0.090 0.007
Occupation 4.76 = 0.009 0.022
Attitude 5.79 = 0.017 0.014
Heroism:Occupation 3.43 = 0.033 0.016

Comparison of main predictors across models

# Use orthogonal contrasts; keeps your "Heroism main effect" interpretable with Cond in the model
old_contr <- options(contrasts = c("contr.sum", "contr.poly"))

# Helper to extract the main effect of "Heroism" from a car::Anova table
extract_heroism <- function(mod, model_label) {
  a <- car::Anova(mod, type = "III")

  # Coerce to data.frame with rownames preserved
  tab <- as.data.frame(a)
  tab$Term <- rownames(tab)

  # Standard column names across R versions
  # (car::Anova uses these names for lm/glm type=III)
  # Columns: "Sum Sq", "Df", "F value", "Pr(>F)"
  names(tab) <- sub(" ", "_", names(tab))  # make names safe: "Sum_Sq", "F_value", etc.

  # Pull rows we need
  hero <- tab %>% filter(Term == "Heroism")
  resid <- tab %>% filter(Term == "Residuals")

  # Safety checks (in case of name variants)
  stopifnot(nrow(hero) == 1, nrow(resid) == 1)

  # Partial eta^2 = SS_effect / (SS_effect + SS_residual)
  eta_p2 <- hero$Sum_Sq / (hero$Sum_Sq + resid$Sum_Sq)

  # Format p nicely (APA-ish)
  p_fmt <- ifelse(hero$`Pr(>F)` < .001, "< .001",
                  sprintf("= %.3f", hero$`Pr(>F)`))

  dplyr::tibble(
    Model = model_label,
    F = hero$F_value,
    p = p_fmt,
    eta2p = eta_p2
  )
}

# ----- Fit the four models -----
mod1 <- lm(criticism_items_S_mean ~ Heroism   + Cond, data = scale_scores2)
mod2 <- lm(criticism_items_S_mean ~ Heroism * Cond, data = scale_scores2)
mod3 <- lm(criticism_items_S_mean ~ Heroism  + Cond+ scale(Attitude), data = scale_scores2)
mod4 <- lm(criticism_items_S_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores2)

# ----- Build the summary table -----
tbl <- bind_rows(
  extract_heroism(mod1, "~ Heroism + Cond"),
  extract_heroism(mod2, "~ Heroism * Occupation"),
  extract_heroism(mod3, "~ Heroism + Cond + Attitude"),
  extract_heroism(mod4, "~ Heroism * Occupation + Attitude")
  ) %>%
  # Nice number formatting for F and eta^2p
  mutate(
    F = round(F, 2),
    eta2p = round(eta2p, 3)
  )

# ----- Print as HTML-friendly table -----
# Build the table first
tbl_kbl <- tbl %>%
  kable("html",
        caption = "Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²",
        align = "lllrrrcr")
# Add a footnote in a version-robust way
if ("footnote" %in% getNamespaceExports("kableExtra")) {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::footnote(
      general = "Type-III sums of squares with sum contrasts.",
      general_title = ""
    )
} else {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::add_footnote(
      general = "Type-III sums of squares with sum contrasts.",
      notation = "none"
    )
}

tbl_kbl
Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²
Model F p eta2p
~ Heroism + Cond 27.87 < .001 0.063
~ Heroism * Occupation 20.66 < .001 0.047
~ Heroism + Cond + Attitude 2.49 = 0.115 0.006
~ Heroism * Occupation + Attitude 2.88 = 0.090 0.007
Type-III sums of squares with sum contrasts.

==> Full support for our hypotheses. Across all occupations Heroism predict general- and specific-level acceptability of criticism, with or without controlling for attitude.

H3: Heroism is associated with reduced support for demands from workers

Because it is expected from heroes to be selfless, demands from workers are incompatible with the hero status and should be evaluated negatively. This is a backlash from the heroic status that parallels previous findings on the exploitation of heroes (see Stanley & Kay, 2024). At the general level, it means ing that it is justified for workers to take the lead (vs the government to take the lead) on pushing demands to improve their situation. At the specific level, it means supporting the right for workers to protest and make demands to improve their working situation.

SEPTEMBER DF

General level

[it is justified for] XXX, and not the government, to take the lead on improvements that benefit the profession

Toggle details of the models diagnostics and outlier analyses

Below, you’ll find model diagnostics (QQ plot, fitted vs residuals, linearity), Outliers analysis, and more details on the output

[NOTE THAT EFFECT SIZES ED IN THE COMMANDS BELOW SHOULD NOT BE TRUSTED]

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 1: DV ~ Heroism + Occupation")
## [1] "Diagnostics for Model 1: DV ~ Heroism + Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(DemandSupp_G_mean ~ Heroism  + Cond, data = scale_scores)
mod1r <- lmrob(DemandSupp_G_mean ~ Heroism  + Cond, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.530 3.519
(0.184) (0.177)
19.137 19.912
p = <0.001 p = <0.001
Heroism 0.096 0.096
(0.034) (0.035)
2.796 2.721
p = 0.005 p = 0.007
Cond1 0.060 0.082
(0.075) (0.075)
0.795 1.088
p = 0.427 p = 0.277
Cond2 −0.160 −0.173
(0.074) (0.071)
−2.162 −2.445
p = 0.031 p = 0.015
Num.Obs. 417 417
R2 0.031 0.039
R2 Adj. 0.024 0.032
RMSE 1.06 1.06
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$DemandSupp_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 2: DV ~ Heroism * Occupation")
## [1] "Diagnostics for Model 2: DV ~ Heroism * Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(DemandSupp_G_mean ~ Heroism  * Cond, data = scale_scores)
mod1r <- lmrob(DemandSupp_G_mean ~ Heroism  * Cond, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.555 3.464
(0.187)
19.008
p = <0.001
Heroism 0.089 0.118
(0.035)
2.543
p = 0.011
Cond1 −0.083 −0.149
(0.269)
−0.310
p = 0.756
Cond2 −0.276 0.158
(0.252)
−1.099
p = 0.272
Heroism × Cond1 0.028 0.047
(0.048)
0.576
p = 0.565
Heroism × Cond2 0.025 −0.064
(0.047)
0.525
p = 0.600
Num.Obs. 417 417
R2 0.033 0.184
R2 Adj. 0.021 0.174
RMSE 1.06 1.07
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$DemandSupp_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude")
## [1] "Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(DemandSupp_G_mean ~ Heroism  + Cond + Attitude, data = scale_scores)
mod1r <- lmrob(DemandSupp_G_mean ~ Heroism  + Cond + Attitude, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.355 3.304
(0.263) (0.243)
12.757 13.581
p = <0.001 p = <0.001
Heroism 0.062 0.051
(0.050) (0.056)
1.243 0.912
p = 0.215 p = 0.362
Cond1 0.046 0.068
(0.077) (0.076)
0.605 0.897
p = 0.546 p = 0.370
Cond2 −0.146 −0.152
(0.076) (0.074)
−1.938 −2.062
p = 0.053 p = 0.040
Attitude 0.060 0.078
(0.064) (0.069)
0.935 1.121
p = 0.350 p = 0.263
Num.Obs. 417 417
R2 0.033 0.042
R2 Adj. 0.024 0.033
RMSE 1.06 1.06
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$DemandSupp_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude")
## [1] "Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(DemandSupp_G_mean ~ Heroism*Cond + Attitude, data = scale_scores)
mod1r <- lmrob(DemandSupp_G_mean ~ Heroism*Cond + Attitude, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.391 3.325
(0.272) (0.236)
12.448 14.096
p = <0.001 p = <0.001
Heroism 0.059 0.050
(0.050) (0.055)
1.180 0.912
p = 0.239 p = 0.362
Cond1 −0.111 −0.233
(0.271) (0.247)
−0.411 −0.943
p = 0.681 p = 0.346
Cond2 −0.221 −0.132
(0.260) (0.238)
−0.848 −0.556
p = 0.397 p = 0.579
Attitude 0.055 0.072
(0.066) (0.068)
0.829 1.064
p = 0.408 p = 0.288
Heroism × Cond1 0.030 0.058
(0.048) (0.048)
0.631 1.203
p = 0.528 p = 0.230
Heroism × Cond2 0.016 −0.001
(0.048) (0.048)
0.337 −0.029
p = 0.736 p = 0.977
Num.Obs. 417 417
R2 0.035 0.048
R2 Adj. 0.021 0.034
RMSE 1.06 1.06
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$DemandSupp_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0(" S for each model (please ignore the eta squares, they are way off")
## [1] " S for each model (please ignore the eta squares, they are way off"
paste0("####################################################")
## [1] "####################################################"
paste0("MODEL 1: DemandSupp_G_mean ~ Heroism")
## [1] "MODEL 1: DemandSupp_G_mean ~ Heroism"
 (Anova(mod1 <- lm(DemandSupp_G_mean ~ Heroism  + Cond, data = scale_scores), type = "III"))
paste0("MODEL 2: DemandSupp_G_mean ~ Heroism * Cond")
## [1] "MODEL 2: DemandSupp_G_mean ~ Heroism * Cond"
 (Anova(mod2 <- lm(DemandSupp_G_mean ~ Heroism * Cond, data = scale_scores), type = "III"))
paste0("MODEL 3: DemandSupp_G_mean ~ Heroism +scale(Attitude)")
## [1] "MODEL 3: DemandSupp_G_mean ~ Heroism +scale(Attitude)"
 (Anova(mod3 <- lm(DemandSupp_G_mean ~ Heroism   + Cond +scale(Attitude) , data = scale_scores), type = "III"))
paste0("MODEL 4: DemandSupp_G_mean ~ Heroism * Cond + scale(Attitude)")
## [1] "MODEL 4: DemandSupp_G_mean ~ Heroism * Cond + scale(Attitude)"
 (Anova(mod4 <-lm(DemandSupp_G_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores), type = "III"))
paste0("Model Comparison: assessing the importance of attitude - not accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - not accounting for occupations"
anova(mod1, mod3)
paste0("Model Comparison: assessing the importance of attitude - accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - accounting for occupations"
anova(mod2, mod4)
ggplot(scale_scores, aes(y = DemandSupp_G_mean, x = Heroism)) +
  # 1) points colored by Cond
  geom_point(aes(color = Cond),
             size = 2.7, alpha = 0.7) +
  
  # 2) ONE global lm line (group = 1 prevents one line per Cond)
  stat_smooth(method = "lm", se = TRUE,
              aes(group = 1),
              color = "black", linewidth = 1) +
  
  # Nice, accessible palette (works well with >3 groups)
  scale_color_brewer(palette = "Set2") +
  
  labs(
    y = "Support for demands (G)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Support for demands (G), colored by Occupation",
    subtitle = "Points are participants; black line is overall linear fit with 95% CI"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "right",
    panel.grid.minor = element_blank()
  )
## `geom_smooth()` using formula = 'y ~ x'

ggplot(scale_scores, aes(x = Heroism, y = DemandSupp_G_mean, color = Cond)) +
  # points colored by condition
  geom_point(size = 2.7, alpha = 0.7) +
  # ONE lm line PER condition (because color is mapped here)
  stat_smooth(method = "lm", se = FALSE, linewidth = 1, fullrange = TRUE) +
  scale_color_brewer(palette = "Set2") +
  labs(
    y = "Support for demands (G)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Support for demands (G) with per-Occupation linear fits"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "right", panel.grid.minor = element_blank())
## `geom_smooth()` using formula = 'y ~ x'

Overall, very small effect sizes for this measure of general support for workers demands - all eta2 < .01. It is not of interest, despite significance. Moreover, effects run in the opposite direction to our predictions, see table:

Model 1: “~Heroism + Occupation”

# =========================
# OLD MODELS (Heroism)
# =========================
m1_H <- lm(DemandSupp_G_mean ~ Heroism + Occupation, data = scale_scores)
m2_H <- lm(DemandSupp_G_mean ~ Heroism * Occupation, data = scale_scores)
m3_H <- lm(DemandSupp_G_mean ~ Heroism + Occupation + Attitude, data = scale_scores)
m4_H <- lm(DemandSupp_G_mean ~ Heroism * Occupation + Attitude, data = scale_scores)

tidy_type3(m1_H, "~Heroism + Cond")
~Heroism + Cond
F p eta2p
Heroism 7.81 = 0.005 0.019
Occupation 2.39 = 0.093 0.011
# =========================
# NEW MODELS (Danger & Help)
# =========================

Model 2: “~Heroism * Occupation”

tidy_type3(m2_H, "~Heroism * Cond")
~Heroism * Cond
F p eta2p
Heroism 6.47 = 0.011 0.015
Occupation 1.01 = 0.364 0.005
Heroism:Occupation 0.49 = 0.613 0.002

Model 3: “~Heroism + Occupation + Attitude”

tidy_type3(m3_H, "~Heroism + Occupation + Attitude")
~Heroism + Occupation + Attitude
F p eta2p
Heroism 1.54 = 0.215 0.004
Occupation 1.98 = 0.139 0.010
Attitude 0.87 = 0.350 0.002

Model 4: “~Heroism * Occupation + Attitude”

tidy_type3(m4_H, "~Heroism * Occupation + Attitude")
~Heroism * Occupation + Attitude
F p eta2p
Heroism 1.39 = 0.239 0.003
Occupation 0.77 = 0.462 0.004
Attitude 0.69 = 0.408 0.002
Heroism:Occupation 0.40 = 0.673 0.002

Comparison of main predictors across models

# Use orthogonal contrasts; keeps your "Heroism main effect" interpretable with Cond in the model
old_contr <- options(contrasts = c("contr.sum", "contr.poly"))

# Helper to extract the main effect of "Heroism" from a car::Anova table
extract_heroism <- function(mod, model_label) {
  a <- car::Anova(mod, type = "III")

  # Coerce to data.frame with rownames preserved
  tab <- as.data.frame(a)
  tab$Term <- rownames(tab)

  # Standard column names across R versions
  # (car::Anova uses these names for lm/glm type=III)
  # Columns: "Sum Sq", "Df", "F value", "Pr(>F)"
  names(tab) <- sub(" ", "_", names(tab))  # make names safe: "Sum_Sq", "F_value", etc.

  # Pull rows we need
  hero <- tab %>% filter(Term == "Heroism")
  resid <- tab %>% filter(Term == "Residuals")

  # Safety checks (in case of name variants)
  stopifnot(nrow(hero) == 1, nrow(resid) == 1)

  # Partial eta^2 = SS_effect / (SS_effect + SS_residual)
  eta_p2 <- hero$Sum_Sq / (hero$Sum_Sq + resid$Sum_Sq)

  # Format p nicely (APA-ish)
  p_fmt <- ifelse(hero$`Pr(>F)` < .001, "< .001",
                  sprintf("= %.3f", hero$`Pr(>F)`))

  dplyr::tibble(
    Model = model_label,
    F = hero$F_value,
    p = p_fmt,
    eta2p = eta_p2
  )
}

# ----- Fit the four models -----
mod1 <- lm(DemandSupp_G_mean ~ Heroism + Cond, data = scale_scores)
mod2 <- lm(DemandSupp_G_mean ~ Heroism * Cond, data = scale_scores)
mod3 <- lm(DemandSupp_G_mean ~ Heroism + Cond + scale(Attitude), data = scale_scores)
mod4 <- lm(DemandSupp_G_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores)

# ----- Build the summary table -----
tbl <- bind_rows(
  extract_heroism(mod1, "~ Heroism + Cond"),
  extract_heroism(mod2, "~ Heroism * Occupation"),
  extract_heroism(mod3, "~ Heroism + Cond + Attitude"),
  extract_heroism(mod4, "~ Heroism * Occupation + Attitude")
  ) %>%
  # Nice number formatting for F and eta^2p
  mutate(
    F = round(F, 2),
    eta2p = round(eta2p, 3)
  )

# ----- Print as HTML-friendly table -----
# Build the table first
tbl_kbl <- tbl %>%
  kable("html",
        caption = "Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²",
        align = "lllrrrcr")
# Add a footnote in a version-robust way
if ("footnote" %in% getNamespaceExports("kableExtra")) {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::footnote(
      general = "Type-III sums of squares with sum contrasts.",
      general_title = ""
    )
} else {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::add_footnote(
      general = "Type-III sums of squares with sum contrasts.",
      notation = "none"
    )
}

tbl_kbl
Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²
Model F p eta2p
~ Heroism + Cond 7.81 = 0.005 0.019
~ Heroism * Occupation 6.47 = 0.011 0.015
~ Heroism + Cond + Attitude 1.54 = 0.215 0.004
~ Heroism * Occupation + Attitude 1.39 = 0.239 0.003
Type-III sums of squares with sum contrasts.

Specific level

Journalists should protest more for the rights they deserve

Toggle details of the models diagnostics and outlier analyses

Below, you’ll find model diagnostics (QQ plot, fitted vs residuals, linearity), Outliers analysis, and more details on the output

[NOTE THAT EFFECT SIZES ED IN THE COMMANDS BELOW SHOULD NOT BE TRUSTED]

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 1: DV ~ Heroism + Occupation")
## [1] "Diagnostics for Model 1: DV ~ Heroism + Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(DemandSupp_S_mean ~ Heroism  + Cond, data = scale_scores)
mod1r <- lmrob(DemandSupp_S_mean ~ Heroism  + Cond, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.384 3.228
(0.240) (0.334)
14.122 9.671
p = <0.001 p = <0.001
Heroism 0.294 0.348
(0.044) (0.060)
6.620 5.777
p = <0.001 p = <0.001
Cond1 0.127 0.065
(0.098) (0.105)
1.302 0.619
p = 0.194 p = 0.536
Cond2 −0.341 −0.259
(0.096) (0.101)
−3.545 −2.562
p = <0.001 p = 0.011
Num.Obs. 417 417
R2 0.126 0.164
R2 Adj. 0.120 0.158
RMSE 1.38 1.39
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$DemandSupp_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 2: DV ~ Heroism * Occupation")
## [1] "Diagnostics for Model 2: DV ~ Heroism * Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(DemandSupp_S_mean ~ Heroism  * Cond, data = scale_scores)
mod1r <- lmrob(DemandSupp_S_mean ~ Heroism  * Cond, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.324 3.138
(0.241) (0.279)
13.819 11.236
p = <0.001 p = <0.001
Heroism 0.300 0.358
(0.045) (0.051)
6.685 6.970
p = <0.001 p = <0.001
Cond1 −0.711 −0.977
(0.346) (0.427)
−2.056 −2.288
p = 0.040 p = 0.023
Cond2 0.490 0.697
(0.323) (0.399)
1.516 1.749
p = 0.130 p = 0.081
Heroism × Cond1 0.153 0.186
(0.062) (0.072)
2.480 2.585
p = 0.014 p = 0.010
Heroism × Cond2 −0.158 −0.178
(0.060) (0.074)
−2.616 −2.400
p = 0.009 p = 0.017
Num.Obs. 417 417
R2 0.146 0.200
R2 Adj. 0.135 0.190
RMSE 1.36 1.37
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$DemandSupp_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude")
## [1] "Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(DemandSupp_S_mean ~ Heroism  + Cond + Attitude, data = scale_scores)
mod1r <- lmrob(DemandSupp_S_mean ~ Heroism  + Cond + Attitude, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.232 2.934
(0.342) (0.428)
9.453 6.860
p = <0.001 p = <0.001
Heroism 0.265 0.286
(0.065) (0.093)
4.093 3.083
p = <0.001 p = 0.002
Cond1 0.115 0.039
(0.100) (0.106)
1.160 0.365
p = 0.247 p = 0.716
Cond2 −0.329 −0.225
(0.098) (0.111)
−3.353 −2.028
p = <0.001 p = 0.043
Attitude 0.052 0.107
(0.084) (0.116)
0.627 0.922
p = 0.531 p = 0.357
Num.Obs. 417 417
R2 0.127 0.167
R2 Adj. 0.118 0.159
RMSE 1.38 1.39
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$DemandSupp_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude")
## [1] "Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(DemandSupp_S_mean ~ Heroism*Cond + Attitude, data = scale_scores)
mod1r <- lmrob(DemandSupp_S_mean ~ Heroism*Cond + Attitude, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.021 2.714
(0.350) (0.351)
8.629 7.721
p = <0.001 p = <0.001
Heroism 0.245 0.260
(0.064) (0.091)
3.813 2.866
p = <0.001 p = 0.004
Cond1 −0.762 −1.044
(0.348) (0.402)
−2.190 −2.595
p = 0.029 p = 0.010
Cond2 0.593 0.842
(0.335) (0.396)
1.772 2.125
p = 0.077 p = 0.034
Attitude 0.101 0.163
(0.085) (0.114)
1.192 1.432
p = 0.234 p = 0.153
Heroism × Cond1 0.158 0.192
(0.062) (0.068)
2.556 2.813
p = 0.011 p = 0.005
Heroism × Cond2 −0.174 −0.197
(0.062) (0.072)
−2.810 −2.714
p = 0.005 p = 0.007
Num.Obs. 417 417
R2 0.148 0.204
R2 Adj. 0.136 0.192
RMSE 1.36 1.37
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$DemandSupp_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0(" S for each model (please ignore the eta squares, they are way off")
## [1] " S for each model (please ignore the eta squares, they are way off"
paste0("####################################################")
## [1] "####################################################"
paste0("MODEL 1: DemandSupp_S_mean ~ Heroism")
## [1] "MODEL 1: DemandSupp_S_mean ~ Heroism"
 (Anova(mod1 <- lm(DemandSupp_S_mean ~ Heroism  + Cond, data = scale_scores), type = "III"))
paste0("MODEL 2: DemandSupp_S_mean ~ Heroism * Cond")
## [1] "MODEL 2: DemandSupp_S_mean ~ Heroism * Cond"
 (Anova(mod2 <- lm(DemandSupp_S_mean ~ Heroism * Cond, data = scale_scores), type = "III"))
paste0("MODEL 3: DemandSupp_S_mean ~ Heroism +scale(Attitude)")
## [1] "MODEL 3: DemandSupp_S_mean ~ Heroism +scale(Attitude)"
 (Anova(mod3 <- lm(DemandSupp_S_mean ~ Heroism   + Cond +scale(Attitude) , data = scale_scores), type = "III"))
paste0("MODEL 4: DemandSupp_S_mean ~ Heroism * Cond + scale(Attitude)")
## [1] "MODEL 4: DemandSupp_S_mean ~ Heroism * Cond + scale(Attitude)"
 (Anova(mod4 <-lm(DemandSupp_S_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores), type = "III"))
paste0("Model Comparison: assessing the importance of attitude - not accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - not accounting for occupations"
anova(mod1, mod3)
paste0("Model Comparison: assessing the importance of attitude - accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - accounting for occupations"
anova(mod2, mod4)
ggplot(scale_scores, aes(y = DemandSupp_S_mean, x = Heroism)) +
  # 1) points colored by Cond
  geom_point(aes(color = Cond),
             size = 2.7, alpha = 0.7) +
  
  # 2) ONE global lm line (group = 1 prevents one line per Cond)
  stat_smooth(method = "lm", se = TRUE,
              aes(group = 1),
              color = "black", linewidth = 1) +
  
  # Nice, accessible palette (works well with >3 groups)
  scale_color_brewer(palette = "Set2") +
  
  labs(
    y = "Support for demands (S)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Support for demands (S), colored by Occupation",
    subtitle = "Points are participants; black line is overall linear fit with 95% CI"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "right",
    panel.grid.minor = element_blank()
  )
## `geom_smooth()` using formula = 'y ~ x'

ggplot(scale_scores, aes(y = DemandSupp_S_mean, x = Heroism, color = Cond)) +
  # points colored by condition
  geom_point(size = 2.7, alpha = 0.7) +
  # ONE lm line PER condition (because color is mapped here)
  stat_smooth(method = "lm", se = FALSE, linewidth = 1, fullrange = TRUE) +
  scale_color_brewer(palette = "Set2") +
  labs(
    y = "Support for demands (S) ",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Support for demands (S) with per-Occupation linear fits"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "right", panel.grid.minor = element_blank())
## `geom_smooth()` using formula = 'y ~ x'

==> No support for our hypotheses. Across all occupations Heroism predicted SUPPORT for general- and specific-level workers’ demands, with or without controlling for attitude. Although effects were drastically reduced when accounting for attitude, heroism might increase our support for workers’ demands. Our hypotheses should therefore be revised.

Model 1: “~Heroism + Occupation”

# =========================
# OLD MODELS (Heroism)
# =========================
m1_H <- lm(DemandSupp_S_mean ~ Heroism + Occupation, data = scale_scores)
m2_H <- lm(DemandSupp_S_mean ~ Heroism * Occupation, data = scale_scores)
m3_H <- lm(DemandSupp_S_mean ~ Heroism + Occupation + Attitude, data = scale_scores)
m4_H <- lm(DemandSupp_S_mean ~ Heroism * Occupation + Attitude, data = scale_scores)

tidy_type3(m1_H, "~Heroism + Cond")
~Heroism + Cond
F p eta2p
Heroism 43.83 < .001 0.096
Occupation 6.42 = 0.002 0.030
# =========================
# NEW MODELS (Danger & Help)
# =========================

Model 2: “~Heroism * Occupation”

tidy_type3(m2_H, "~Heroism * Cond")
~Heroism * Cond
F p eta2p
Heroism 44.69 < .001 0.098
Occupation 2.33 = 0.099 0.011
Heroism:Occupation 4.71 = 0.010 0.022

Model 3: “~Heroism + Occupation + Attitude”

tidy_type3(m3_H, "~Heroism + Occupation + Attitude")
~Heroism + Occupation + Attitude
F p eta2p
Heroism 16.75 < .001 0.039
Occupation 5.84 = 0.003 0.028
Attitude 0.39 = 0.531 0.001

Model 4: “~Heroism * Occupation + Attitude”

tidy_type3(m4_H, "~Heroism * Occupation + Attitude")
~Heroism * Occupation + Attitude
F p eta2p
Heroism 14.54 < .001 0.034
Occupation 2.76 = 0.064 0.013
Attitude 1.42 = 0.234 0.003
Heroism:Occupation 5.22 = 0.006 0.025

Comparison of main predictors across models

# Use orthogonal contrasts; keeps your "Heroism main effect" interpretable with Cond in the model
old_contr <- options(contrasts = c("contr.sum", "contr.poly"))

# Helper to extract the main effect of "Heroism" from a car::Anova table
extract_heroism <- function(mod, model_label) {
  a <- car::Anova(mod, type = "III")

  # Coerce to data.frame with rownames preserved
  tab <- as.data.frame(a)
  tab$Term <- rownames(tab)

  # Standard column names across R versions
  # (car::Anova uses these names for lm/glm type=III)
  # Columns: "Sum Sq", "Df", "F value", "Pr(>F)"
  names(tab) <- sub(" ", "_", names(tab))  # make names safe: "Sum_Sq", "F_value", etc.

  # Pull rows we need
  hero <- tab %>% filter(Term == "Heroism")
  resid <- tab %>% filter(Term == "Residuals")

  # Safety checks (in case of name variants)
  stopifnot(nrow(hero) == 1, nrow(resid) == 1)

  # Partial eta^2 = SS_effect / (SS_effect + SS_residual)
  eta_p2 <- hero$Sum_Sq / (hero$Sum_Sq + resid$Sum_Sq)

  # Format p nicely (APA-ish)
  p_fmt <- ifelse(hero$`Pr(>F)` < .001, "< .001",
                  sprintf("= %.3f", hero$`Pr(>F)`))

  dplyr::tibble(
    Model = model_label,
    F = hero$F_value,
    p = p_fmt,
    eta2p = eta_p2
  )
}

# ----- Fit the four models -----
mod1 <- lm(DemandSupp_S_mean ~ Heroism + Cond, data = scale_scores)
mod2 <- lm(DemandSupp_S_mean ~ Heroism * Cond, data = scale_scores)
mod3 <- lm(DemandSupp_S_mean ~ Heroism + Cond + scale(Attitude), data = scale_scores)
mod4 <- lm(DemandSupp_S_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores)

# ----- Build the summary table -----
tbl <- bind_rows(
  extract_heroism(mod1, "~ Heroism + Cond"),
  extract_heroism(mod2, "~ Heroism * Occupation"),
  extract_heroism(mod3, "~ Heroism + Cond + Attitude"),
  extract_heroism(mod4, "~ Heroism * Occupation + Attitude")
  ) %>%
  # Nice number formatting for F and eta^2p
  mutate(
    F = round(F, 2),
    eta2p = round(eta2p, 3)
  )

# ----- Print as HTML-friendly table -----
# Build the table first
tbl_kbl <- tbl %>%
  kable("html",
        caption = "Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²",
        align = "lllrrrcr")
# Add a footnote in a version-robust way
if ("footnote" %in% getNamespaceExports("kableExtra")) {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::footnote(
      general = "Type-III sums of squares with sum contrasts.",
      general_title = ""
    )
} else {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::add_footnote(
      general = "Type-III sums of squares with sum contrasts.",
      notation = "none"
    )
}

tbl_kbl
Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²
Model F p eta2p
~ Heroism + Cond 43.83 < .001 0.096
~ Heroism * Occupation 44.69 < .001 0.098
~ Heroism + Cond + Attitude 16.75 < .001 0.039
~ Heroism * Occupation + Attitude 14.54 < .001 0.034
Type-III sums of squares with sum contrasts.

NOVEMBER DF

General level

[it is justified for] XXX, and not the government, to take the lead on improvements that benefit the profession

Toggle details of the models diagnostics and outlier analyses

Below, you’ll find model diagnostics (QQ plot, fitted vs residuals, linearity), Outliers analysis, and more details on the output

[NOTE THAT EFFECT SIZES ED IN THE COMMANDS BELOW SHOULD NOT BE TRUSTED]

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 1: DV ~ Heroism + Occupation")
## [1] "Diagnostics for Model 1: DV ~ Heroism + Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(DemandSupp_G_mean ~ Heroism  + Cond, data = scale_scores2)
mod1r <- lmrob(DemandSupp_G_mean ~ Heroism  + Cond, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.879 3.959
(0.155) (0.135)
25.040 29.325
p = <0.001 p = <0.001
Heroism 0.019 0.003
(0.029) (0.027)
0.638 0.131
p = 0.524 p = 0.896
Cond1 −0.029 −0.033
(0.066) (0.065)
−0.437 −0.507
p = 0.662 p = 0.612
Cond2 −0.114 −0.131
(0.065) (0.059)
−1.757 −2.208
p = 0.080 p = 0.028
Num.Obs. 421 421
R2 0.013 0.021
R2 Adj. 0.006 0.014
RMSE 0.93 0.93
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$DemandSupp_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 2: DV ~ Heroism * Occupation")
## [1] "Diagnostics for Model 2: DV ~ Heroism * Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(DemandSupp_G_mean ~ Heroism  * Cond, data = scale_scores2)
mod1r <- lmrob(DemandSupp_G_mean ~ Heroism  * Cond, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.892 3.982
(0.159) (0.146)
24.535 27.354
p = <0.001 p = <0.001
Heroism 0.013 −0.003
(0.030) (0.029)
0.417 −0.115
p = 0.677 p = 0.909
Cond1 −0.242 −0.166
(0.243) (0.236)
−0.997 −0.703
p = 0.319 p = 0.483
Cond2 −0.287 −0.312
(0.208) (0.175)
−1.381 −1.785
p = 0.168 p = 0.075
Heroism × Cond1 0.042 0.027
(0.044) (0.045)
0.959 0.602
p = 0.338 p = 0.548
Heroism × Cond2 0.039 0.040
(0.040) (0.034)
0.979 1.150
p = 0.328 p = 0.251
Num.Obs. 421 421
R2 0.021 0.027
R2 Adj. 0.009 0.015
RMSE 0.93 0.93
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$DemandSupp_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude")
## [1] "Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(DemandSupp_G_mean ~ Heroism  + Cond + Attitude, data = scale_scores2)
mod1r <- lmrob(DemandSupp_G_mean ~ Heroism  + Cond + Attitude, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.700 3.783
(0.228) (0.193)
16.247 19.639
p = <0.001 p = <0.001
Heroism −0.012 −0.027
(0.041) (0.035)
−0.286 −0.775
p = 0.775 p = 0.439
Cond1 −0.054 −0.058
(0.070) (0.069)
−0.765 −0.842
p = 0.445 p = 0.400
Cond2 −0.092 −0.108
(0.068) (0.063)
−1.351 −1.732
p = 0.177 p = 0.084
Attitude 0.058 0.057
(0.054) (0.044)
1.068 1.305
p = 0.286 p = 0.193
Num.Obs. 421 421
R2 0.016 0.024
R2 Adj. 0.006 0.014
RMSE 0.93 0.93
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$DemandSupp_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude")
## [1] "Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(DemandSupp_G_mean ~ Heroism*Cond + Attitude, data = scale_scores2)
mod1r <- lmrob(DemandSupp_G_mean ~ Heroism*Cond + Attitude, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.723 3.816
(0.246) (0.219)
15.150 17.396
p = <0.001 p = <0.001
Heroism −0.013 −0.027
(0.041) (0.034)
−0.311 −0.792
p = 0.756 p = 0.429
Cond1 −0.308 −0.233
(0.253) (0.242)
−1.215 −0.961
p = 0.225 p = 0.337
Cond2 −0.208 −0.235
(0.225) (0.186)
−0.922 −1.265
p = 0.357 p = 0.207
Attitude 0.051 0.050
(0.057) (0.046)
0.901 1.086
p = 0.368 p = 0.278
Heroism × Cond1 0.050 0.035
(0.045) (0.045)
1.118 0.783
p = 0.264 p = 0.434
Heroism × Cond2 0.027 0.028
(0.042) (0.035)
0.647 0.793
p = 0.518 p = 0.428
Num.Obs. 421 421
R2 0.023 0.029
R2 Adj. 0.009 0.015
RMSE 0.93 0.93
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$DemandSupp_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0(" S for each model (please ignore the eta squares, they are way off")
## [1] " S for each model (please ignore the eta squares, they are way off"
paste0("####################################################")
## [1] "####################################################"
paste0("MODEL 1: DemandSupp_G_mean ~ Heroism")
## [1] "MODEL 1: DemandSupp_G_mean ~ Heroism"
 (Anova(mod1 <- lm(DemandSupp_G_mean ~ Heroism  + Cond, data = scale_scores2), type = "III"))
paste0("MODEL 2: DemandSupp_G_mean ~ Heroism * Cond")
## [1] "MODEL 2: DemandSupp_G_mean ~ Heroism * Cond"
 (Anova(mod2 <- lm(DemandSupp_G_mean ~ Heroism * Cond, data = scale_scores2), type = "III"))
paste0("MODEL 3: DemandSupp_G_mean ~ Heroism +scale(Attitude)")
## [1] "MODEL 3: DemandSupp_G_mean ~ Heroism +scale(Attitude)"
 (Anova(mod3 <- lm(DemandSupp_G_mean ~ Heroism   + Cond +scale(Attitude) , data = scale_scores2), type = "III"))
paste0("MODEL 4: DemandSupp_G_mean ~ Heroism * Cond + scale(Attitude)")
## [1] "MODEL 4: DemandSupp_G_mean ~ Heroism * Cond + scale(Attitude)"
 (Anova(mod4 <-lm(DemandSupp_G_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores2), type = "III"))
paste0("Model Comparison: assessing the importance of attitude - not accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - not accounting for occupations"
anova(mod1, mod3)
paste0("Model Comparison: assessing the importance of attitude - accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - accounting for occupations"
anova(mod2, mod4)
ggplot(scale_scores2, aes(y = DemandSupp_G_mean, x = Heroism)) +
  # 1) points colored by Cond
  geom_point(aes(color = Cond),
             size = 2.7, alpha = 0.7) +
  
  # 2) ONE global lm line (group = 1 prevents one line per Cond)
  stat_smooth(method = "lm", se = TRUE,
              aes(group = 1),
              color = "black", linewidth = 1) +
  
  # Nice, accessible palette (works well with >3 groups)
  scale_color_brewer(palette = "Set2") +
  
  labs(
    y = "Support for demands (G)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Support for demands (G), colored by Occupation",
    subtitle = "Points are participants; black line is overall linear fit with 95% CI"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "right",
    panel.grid.minor = element_blank()
  )
## `geom_smooth()` using formula = 'y ~ x'

ggplot(scale_scores2, aes(x = Heroism, y = DemandSupp_G_mean, color = Cond)) +
  # points colored by condition
  geom_point(size = 2.7, alpha = 0.7) +
  # ONE lm line PER condition (because color is mapped here)
  stat_smooth(method = "lm", se = FALSE, linewidth = 1, fullrange = TRUE) +
  scale_color_brewer(palette = "Set2") +
  labs(
    y = "Support for demands (G)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Support for demands (G) with per-Occupation linear fits"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "right", panel.grid.minor = element_blank())
## `geom_smooth()` using formula = 'y ~ x'

Overall, very small effect sizes for this measure of general support for workers demands - all eta2 < .01. It is not of interest, despite significance. Moreover, effects run in the opposite direction to our predictions, see table:

Model 1: “~Heroism + Occupation”

# =========================
# OLD MODELS (Heroism)
# =========================
m1_H <- lm(DemandSupp_G_mean ~ Heroism + Occupation, data = scale_scores2)
m2_H <- lm(DemandSupp_G_mean ~ Heroism * Occupation, data = scale_scores2)
m3_H <- lm(DemandSupp_G_mean ~ Heroism + Occupation + Attitude, data = scale_scores2)
m4_H <- lm(DemandSupp_G_mean ~ Heroism * Occupation + Attitude, data = scale_scores2)

tidy_type3(m1_H, "~Heroism + Cond")
~Heroism + Cond
F p eta2p
Heroism 0.41 = 0.524 0.001
Occupation 2.67 = 0.071 0.013
# =========================
# NEW MODELS (Danger & Help)
# =========================

Model 2: “~Heroism * Occupation”

tidy_type3(m2_H, "~Heroism * Cond")
~Heroism * Cond
F p eta2p
Heroism 0.17 = 0.677 0.000
Occupation 3.00 = 0.051 0.014
Heroism:Occupation 1.70 = 0.184 0.008

Model 3: “~Heroism + Occupation + Attitude”

tidy_type3(m3_H, "~Heroism + Occupation + Attitude")
~Heroism + Occupation + Attitude
F p eta2p
Heroism 0.08 = 0.775 0.000
Occupation 2.51 = 0.083 0.012
Attitude 1.14 = 0.286 0.003

Model 4: “~Heroism * Occupation + Attitude”

tidy_type3(m4_H, "~Heroism * Occupation + Attitude")
~Heroism * Occupation + Attitude
F p eta2p
Heroism 0.10 = 0.756 0.000
Occupation 2.71 = 0.068 0.013
Attitude 0.81 = 0.368 0.002
Heroism:Occupation 1.53 = 0.218 0.007

Comparison of main predictors across models

# Use orthogonal contrasts; keeps your "Heroism main effect" interpretable with Cond in the model
old_contr <- options(contrasts = c("contr.sum", "contr.poly"))

# Helper to extract the main effect of "Heroism" from a car::Anova table
extract_heroism <- function(mod, model_label) {
  a <- car::Anova(mod, type = "III")

  # Coerce to data.frame with rownames preserved
  tab <- as.data.frame(a)
  tab$Term <- rownames(tab)

  # Standard column names across R versions
  # (car::Anova uses these names for lm/glm type=III)
  # Columns: "Sum Sq", "Df", "F value", "Pr(>F)"
  names(tab) <- sub(" ", "_", names(tab))  # make names safe: "Sum_Sq", "F_value", etc.

  # Pull rows we need
  hero <- tab %>% filter(Term == "Heroism")
  resid <- tab %>% filter(Term == "Residuals")

  # Safety checks (in case of name variants)
  stopifnot(nrow(hero) == 1, nrow(resid) == 1)

  # Partial eta^2 = SS_effect / (SS_effect + SS_residual)
  eta_p2 <- hero$Sum_Sq / (hero$Sum_Sq + resid$Sum_Sq)

  # Format p nicely (APA-ish)
  p_fmt <- ifelse(hero$`Pr(>F)` < .001, "< .001",
                  sprintf("= %.3f", hero$`Pr(>F)`))

    dplyr::tibble(
    Model = model_label,
    F = hero$F_value,
    p = p_fmt,
    eta2p = eta_p2
  )
}

# ----- Fit the four models -----
mod1 <- lm(DemandSupp_G_mean ~ Heroism + Cond, data = scale_scores2)
mod2 <- lm(DemandSupp_G_mean ~ Heroism * Cond, data = scale_scores2)
mod3 <- lm(DemandSupp_G_mean ~ Heroism + Cond + scale(Attitude), data = scale_scores2)
mod4 <- lm(DemandSupp_G_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores2)

# ----- Build the summary table -----
tbl <- bind_rows(
  extract_heroism(mod1, "~ Heroism + Cond"),
  extract_heroism(mod2, "~ Heroism * Occupation"),
  extract_heroism(mod3, "~ Heroism + Cond + Attitude"),
  extract_heroism(mod4, "~ Heroism * Occupation + Attitude")
  ) %>%
  # Nice number formatting for F and eta^2p
  mutate(
    F = round(F, 2),
    eta2p = round(eta2p, 3)
  )

# ----- Print as HTML-friendly table -----
# Build the table first
tbl_kbl <- tbl %>%
  kable("html",
        caption = "Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²",
        align = "lllrrrcr")
# Add a footnote in a version-robust way
if ("footnote" %in% getNamespaceExports("kableExtra")) {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::footnote(
      general = "Type-III sums of squares with sum contrasts.",
      general_title = ""
    )
} else {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::add_footnote(
      general = "Type-III sums of squares with sum contrasts.",
      notation = "none"
    )
}

tbl_kbl
Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²
Model F p eta2p
~ Heroism + Cond 0.41 = 0.524 0.001
~ Heroism * Occupation 0.17 = 0.677 0.000
~ Heroism + Cond + Attitude 0.08 = 0.775 0.000
~ Heroism * Occupation + Attitude 0.10 = 0.756 0.000
Type-III sums of squares with sum contrasts.

Specific level

Journalists should protest more for the rights they deserve

Toggle details of the models diagnostics and outlier analyses

Below, you’ll find model diagnostics (QQ plot, fitted vs residuals, linearity), Outliers analysis, and more details on the output

[NOTE THAT EFFECT SIZES ED IN THE COMMANDS BELOW SHOULD NOT BE TRUSTED]

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 1: DV ~ Heroism + Occupation")
## [1] "Diagnostics for Model 1: DV ~ Heroism + Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(DemandSupp_S_mean ~ Heroism  + Cond, data = scale_scores2)
mod1r <- lmrob(DemandSupp_S_mean ~ Heroism  + Cond, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.811 3.917
(0.217) (0.245)
17.600 15.999
p = <0.001 p = <0.001
Heroism 0.220 0.216
(0.041) (0.046)
5.345 4.719
p = <0.001 p = <0.001
Cond1 0.158 0.212
(0.093) (0.098)
1.698 2.169
p = 0.090 p = 0.031
Cond2 −0.289 −0.293
(0.091) (0.097)
−3.193 −3.020
p = 0.002 p = 0.003
Num.Obs. 421 421
R2 0.093 0.105
R2 Adj. 0.087 0.098
RMSE 1.31 1.31
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$DemandSupp_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 2: DV ~ Heroism * Occupation")
## [1] "Diagnostics for Model 2: DV ~ Heroism * Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(DemandSupp_S_mean ~ Heroism  * Cond, data = scale_scores2)
mod1r <- lmrob(DemandSupp_S_mean ~ Heroism  * Cond, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.732 3.298
(0.222)
16.812
p = <0.001
Heroism 0.234 0.327
(0.042)
5.558
p = <0.001
Cond1 −0.177 −0.479
(0.339)
−0.522
p = 0.602
Cond2 0.151 −0.197
(0.291)
0.519
p = 0.604
Heroism × Cond1 0.061 0.147
(0.061)
0.989
p = 0.323
Heroism × Cond2 −0.087 −0.030
(0.055)
−1.577
p = 0.116
Num.Obs. 421 421
R2 0.099 0.464
R2 Adj. 0.088 0.457
RMSE 1.30 1.33
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$DemandSupp_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude")
## [1] "Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(DemandSupp_S_mean ~ Heroism  + Cond + Attitude, data = scale_scores2)
mod1r <- lmrob(DemandSupp_S_mean ~ Heroism  + Cond + Attitude, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.369 3.405
(0.317) (0.400)
10.613 8.519
p = <0.001 p = <0.001
Heroism 0.144 0.137
(0.057) (0.061)
2.523 2.235
p = 0.012 p = 0.026
Cond1 0.096 0.134
(0.098) (0.109)
0.981 1.223
p = 0.327 p = 0.222
Cond2 −0.235 −0.229
(0.095) (0.106)
−2.478 −2.164
p = 0.014 p = 0.031
Attitude 0.144 0.159
(0.076) (0.093)
1.899 1.705
p = 0.058 p = 0.089
Num.Obs. 421 421
R2 0.101 0.115
R2 Adj. 0.093 0.107
RMSE 1.30 1.30
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$DemandSupp_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude")
## [1] "Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(DemandSupp_S_mean ~ Heroism*Cond + Attitude, data = scale_scores2)
mod1r <- lmrob(DemandSupp_S_mean ~ Heroism*Cond + Attitude, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.066 3.132
(0.342) (0.427)
8.976 7.342
p = <0.001 p = <0.001
Heroism 0.134 0.122
(0.057) (0.066)
2.344 1.840
p = 0.020 p = 0.066
Cond1 −0.438 −0.259
(0.352) (0.445)
−1.244 −0.583
p = 0.214 p = 0.561
Cond2 0.462 0.410
(0.313) (0.378)
1.473 1.085
p = 0.141 p = 0.279
Attitude 0.203 0.218
(0.079) (0.104)
2.555 2.103
p = 0.011 p = 0.036
Heroism × Cond1 0.092 0.064
(0.062) (0.073)
1.483 0.889
p = 0.139 p = 0.374
Heroism × Cond2 −0.134 −0.123
(0.058) (0.067)
−2.308 −1.840
p = 0.021 p = 0.067
Num.Obs. 421 421
R2 0.113 0.124
R2 Adj. 0.100 0.111
RMSE 1.29 1.30
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$DemandSupp_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0(" S for each model (please ignore the eta squares, they are way off")
## [1] " S for each model (please ignore the eta squares, they are way off"
paste0("####################################################")
## [1] "####################################################"
paste0("MODEL 1: DemandSupp_S_mean ~ Heroism")
## [1] "MODEL 1: DemandSupp_S_mean ~ Heroism"
 (Anova(mod1 <- lm(DemandSupp_S_mean ~ Heroism  + Cond, data = scale_scores2), type = "III"))
paste0("MODEL 2: DemandSupp_S_mean ~ Heroism * Cond")
## [1] "MODEL 2: DemandSupp_S_mean ~ Heroism * Cond"
 (Anova(mod2 <- lm(DemandSupp_S_mean ~ Heroism * Cond, data = scale_scores2), type = "III"))
paste0("MODEL 3: DemandSupp_S_mean ~ Heroism +scale(Attitude)")
## [1] "MODEL 3: DemandSupp_S_mean ~ Heroism +scale(Attitude)"
 (Anova(mod3 <- lm(DemandSupp_S_mean ~ Heroism   + Cond +scale(Attitude) , data = scale_scores2), type = "III"))
paste0("MODEL 4: DemandSupp_S_mean ~ Heroism * Cond + scale(Attitude)")
## [1] "MODEL 4: DemandSupp_S_mean ~ Heroism * Cond + scale(Attitude)"
 (Anova(mod4 <-lm(DemandSupp_S_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores2), type = "III"))
paste0("Model Comparison: assessing the importance of attitude - not accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - not accounting for occupations"
anova(mod1, mod3)
paste0("Model Comparison: assessing the importance of attitude - accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - accounting for occupations"
anova(mod2, mod4)
ggplot(scale_scores2, aes(y = DemandSupp_S_mean, x = Heroism)) +
  # 1) points colored by Cond
  geom_point(aes(color = Cond),
             size = 2.7, alpha = 0.7) +
  
  # 2) ONE global lm line (group = 1 prevents one line per Cond)
  stat_smooth(method = "lm", se = TRUE,
              aes(group = 1),
              color = "black", linewidth = 1) +
  
  # Nice, accessible palette (works well with >3 groups)
  scale_color_brewer(palette = "Set2") +
  
  labs(
    y = "Support for demands (S)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Support for demands (S), colored by Occupation",
    subtitle = "Points are participants; black line is overall linear fit with 95% CI"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "right",
    panel.grid.minor = element_blank()
  )
## `geom_smooth()` using formula = 'y ~ x'

ggplot(scale_scores2, aes(y = DemandSupp_S_mean, x = Heroism, color = Cond)) +
  # points colored by condition
  geom_point(size = 2.7, alpha = 0.7) +
  # ONE lm line PER condition (because color is mapped here)
  stat_smooth(method = "lm", se = FALSE, linewidth = 1, fullrange = TRUE) +
  scale_color_brewer(palette = "Set2") +
  labs(
    y = "Support for demands (S) ",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Support for demands (S) with per-Occupation linear fits"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "right", panel.grid.minor = element_blank())
## `geom_smooth()` using formula = 'y ~ x'

==> No support for our hypotheses. Across all occupations Heroism predicted SUPPORT for general- and specific-level workers’ demands, with or without controlling for attitude. Although effects were drastically reduced when accounting for attitude, heroism might increase our support for workers’ demands. Our hypotheses should therefore be revised.

Model 1: “~Heroism + Occupation”

# =========================
# OLD MODELS (Heroism)
# =========================
m1_H <- lm(DemandSupp_S_mean ~ Heroism + Occupation, data = scale_scores2)
m2_H <- lm(DemandSupp_S_mean ~ Heroism * Occupation, data = scale_scores2)
m3_H <- lm(DemandSupp_S_mean ~ Heroism + Occupation + Attitude, data = scale_scores2)
m4_H <- lm(DemandSupp_S_mean ~ Heroism * Occupation + Attitude, data = scale_scores2)

tidy_type3(m1_H, "~Heroism + Cond")
~Heroism + Cond
F p eta2p
Heroism 28.57 < .001 0.064
Occupation 5.11 = 0.006 0.024
# =========================
# NEW MODELS (Danger & Help)
# =========================

Model 2: “~Heroism * Occupation”

tidy_type3(m2_H, "~Heroism * Cond")
~Heroism * Cond
F p eta2p
Heroism 30.89 < .001 0.069
Occupation 0.18 = 0.837 0.001
Heroism:Occupation 1.29 = 0.275 0.006

Model 3: “~Heroism + Occupation + Attitude”

tidy_type3(m3_H, "~Heroism + Occupation + Attitude")
~Heroism + Occupation + Attitude
F p eta2p
Heroism 6.37 = 0.012 0.015
Occupation 3.16 = 0.043 0.015
Attitude 3.61 = 0.058 0.009

Model 4: “~Heroism * Occupation + Attitude”

tidy_type3(m4_H, "~Heroism * Occupation + Attitude")
~Heroism * Occupation + Attitude
F p eta2p
Heroism 5.49 = 0.020 0.013
Occupation 1.20 = 0.302 0.006
Attitude 6.53 = 0.011 0.016
Heroism:Occupation 2.76 = 0.065 0.013

Comparison of main predictors across models

# Use orthogonal contrasts; keeps your "Heroism main effect" interpretable with Cond in the model
old_contr <- options(contrasts = c("contr.sum", "contr.poly"))

# Helper to extract the main effect of "Heroism" from a car::Anova table
extract_heroism <- function(mod, model_label) {
  a <- car::Anova(mod, type = "III")

  # Coerce to data.frame with rownames preserved
  tab <- as.data.frame(a)
  tab$Term <- rownames(tab)

  # Standard column names across R versions
  # (car::Anova uses these names for lm/glm type=III)
  # Columns: "Sum Sq", "Df", "F value", "Pr(>F)"
  names(tab) <- sub(" ", "_", names(tab))  # make names safe: "Sum_Sq", "F_value", etc.

  # Pull rows we need
  hero <- tab %>% filter(Term == "Heroism")
  resid <- tab %>% filter(Term == "Residuals")

  # Safety checks (in case of name variants)
  stopifnot(nrow(hero) == 1, nrow(resid) == 1)

  # Partial eta^2 = SS_effect / (SS_effect + SS_residual)
  eta_p2 <- hero$Sum_Sq / (hero$Sum_Sq + resid$Sum_Sq)

  # Format p nicely (APA-ish)
  p_fmt <- ifelse(hero$`Pr(>F)` < .001, "< .001",
                  sprintf("= %.3f", hero$`Pr(>F)`))

    dplyr::tibble(
    Model = model_label,
    F = hero$F_value,
    p = p_fmt,
    eta2p = eta_p2
  )
}

# ----- Fit the four models -----
mod1 <- lm(DemandSupp_S_mean ~ Heroism + Cond, data = scale_scores2)
mod2 <- lm(DemandSupp_S_mean ~ Heroism * Cond, data = scale_scores2)
mod3 <- lm(DemandSupp_S_mean ~ Heroism + Cond + scale(Attitude), data = scale_scores2)
mod4 <- lm(DemandSupp_S_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores2)

# ----- Build the summary table -----
tbl <- bind_rows(
  extract_heroism(mod1, "~ Heroism + Cond"),
  extract_heroism(mod2, "~ Heroism * Occupation"),
  extract_heroism(mod3, "~ Heroism + Cond + Attitude"),
  extract_heroism(mod4, "~ Heroism * Occupation + Attitude")
  ) %>%
  # Nice number formatting for F and eta^2p
  mutate(
    F = round(F, 2),
    eta2p = round(eta2p, 3)
  )

# ----- Print as HTML-friendly table -----
# Build the table first
tbl_kbl <- tbl %>%
  kable("html",
        caption = "Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²",
        align = "lllrrrcr")
# Add a footnote in a version-robust way
if ("footnote" %in% getNamespaceExports("kableExtra")) {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::footnote(
      general = "Type-III sums of squares with sum contrasts.",
      general_title = ""
    )
} else {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::add_footnote(
      general = "Type-III sums of squares with sum contrasts.",
      notation = "none"
    )
}

tbl_kbl
Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²
Model F p eta2p
~ Heroism + Cond 28.57 < .001 0.064
~ Heroism * Occupation 30.89 < .001 0.069
~ Heroism + Cond + Attitude 6.37 = 0.012 0.015
~ Heroism * Occupation + Attitude 5.49 = 0.020 0.013
Type-III sums of squares with sum contrasts.

H4: Heroism is associated with decreased perception of victim-hood

The heroic status might be incompatible with the victim status (see Hartman et al., 2022) - heroes are agentic, whereas victims are passive. Heroes are there to defend us - and we might overestimate their resilience to hardship and downplay their vulnerability and suffering. At the general level, it means that we might heroes to be less victimised, unfairly treated, exploited. At the specific level, it means we should indicate that, upon reading that 60% of the workers intense migraines, we would feel that workers are strong enough to endure it.

SEPTEMBER DF

General level

[How much do you see journalists as:] Victimised

Toggle details of the models diagnostics and outlier analyses

Below, you’ll find model diagnostics (QQ plot, fitted vs residuals, linearity), Outliers analysis, and more details on the output

[NOTE THAT EFFECT SIZES ED IN THE COMMANDS BELOW SHOULD NOT BE TRUSTED]

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 1: DV ~ Heroism + Occupation")
## [1] "Diagnostics for Model 1: DV ~ Heroism + Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Victim_G_mean ~ Heroism  + Cond, data = scale_scores)
mod1r <- lmrob(Victim_G_mean ~ Heroism  + Cond, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 1.665 1.600
(0.244) (0.246)
6.815 6.505
p = <0.001 p = <0.001
Heroism 0.339 0.350
(0.045) (0.048)
7.485 7.290
p = <0.001 p = <0.001
Cond1 0.548 0.586
(0.100) (0.109)
5.501 5.395
p = <0.001 p = <0.001
Cond2 −0.094 −0.101
(0.098) (0.103)
−0.954 −0.975
p = 0.341 p = 0.330
Num.Obs. 417 417
R2 0.218 0.226
R2 Adj. 0.212 0.220
RMSE 1.41 1.41
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Victim_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 2: DV ~ Heroism * Occupation")
## [1] "Diagnostics for Model 2: DV ~ Heroism * Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Victim_G_mean ~ Heroism  * Cond, data = scale_scores)
mod1r <- lmrob(Victim_G_mean ~ Heroism  * Cond, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 1.619 1.552
(0.247) (0.245)
6.562 6.341
p = <0.001 p = <0.001
Heroism 0.344 0.354
(0.046) (0.049)
7.474 7.248
p = <0.001 p = <0.001
Cond1 0.011 −0.125
(0.355) (0.359)
0.031 −0.347
p = 0.975 p = 0.729
Cond2 0.490 0.513
(0.332) (0.319)
1.477 1.606
p = 0.140 p = 0.109
Heroism × Cond1 0.098 0.131
(0.063) (0.066)
1.544 1.976
p = 0.123 p = 0.049
Heroism × Cond2 −0.111 −0.117
(0.062) (0.064)
−1.798 −1.823
p = 0.073 p = 0.069
Num.Obs. 417 417
R2 0.225 0.238
R2 Adj. 0.216 0.229
RMSE 1.40 1.40
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Victim_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude")
## [1] "Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Victim_G_mean ~ Heroism  + Cond + Attitude, data = scale_scores)
mod1r <- lmrob(Victim_G_mean ~ Heroism  + Cond + Attitude, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 1.560 1.464
(0.349) (0.328)
4.476 4.469
p = <0.001 p = <0.001
Heroism 0.319 0.323
(0.066) (0.065)
4.836 4.946
p = <0.001 p = <0.001
Cond1 0.540 0.577
(0.102) (0.109)
5.318 5.313
p = <0.001 p = <0.001
Cond2 −0.085 −0.090
(0.100) (0.105)
−0.852 −0.856
p = 0.395 p = 0.392
Attitude 0.036 0.047
(0.085) (0.079)
0.421 0.593
p = 0.674 p = 0.554
Num.Obs. 417 417
R2 0.218 0.227
R2 Adj. 0.210 0.219
RMSE 1.41 1.41
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Victim_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude")
## [1] "Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Victim_G_mean ~ Heroism*Cond + Attitude, data = scale_scores)
mod1r <- lmrob(Victim_G_mean ~ Heroism*Cond + Attitude, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 1.408 1.337
(0.359) (0.325)
3.917 4.115
p = <0.001 p = <0.001
Heroism 0.306 0.314
(0.066) (0.063)
4.634 5.000
p = <0.001 p = <0.001
Cond1 −0.025 −0.145
(0.358) (0.349)
−0.070 −0.416
p = 0.944 p = 0.677
Cond2 0.562 0.577
(0.344) (0.312)
1.635 1.850
p = 0.103 p = 0.065
Attitude 0.070 0.073
(0.087) (0.076)
0.809 0.957
p = 0.419 p = 0.339
Heroism × Cond1 0.101 0.132
(0.064) (0.065)
1.595 2.026
p = 0.111 p = 0.043
Heroism × Cond2 −0.122 −0.127
(0.063) (0.063)
−1.928 −2.011
p = 0.055 p = 0.045
Num.Obs. 417 417
R2 0.227 0.239
R2 Adj. 0.215 0.228
RMSE 1.40 1.40
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Victim_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0(" S for each model (please ignore the eta squares, they are way off")
## [1] " S for each model (please ignore the eta squares, they are way off"
paste0("####################################################")
## [1] "####################################################"
paste0("MODEL 1: Victim_G_mean ~ Heroism")
## [1] "MODEL 1: Victim_G_mean ~ Heroism"
 (Anova(mod1 <- lm(Victim_G_mean ~ Heroism  + Cond, data = scale_scores), type = "III"))
paste0("MODEL 2: Victim_G_mean ~ Heroism * Cond")
## [1] "MODEL 2: Victim_G_mean ~ Heroism * Cond"
 (Anova(mod2 <- lm(Victim_G_mean ~ Heroism * Cond, data = scale_scores), type = "III"))
paste0("MODEL 3: Victim_G_mean ~ Heroism +scale(Attitude)")
## [1] "MODEL 3: Victim_G_mean ~ Heroism +scale(Attitude)"
 (Anova(mod3 <- lm(Victim_G_mean ~ Heroism   + Cond +scale(Attitude) , data = scale_scores), type = "III"))
paste0("MODEL 4: Victim_G_mean ~ Heroism * Cond + scale(Attitude)")
## [1] "MODEL 4: Victim_G_mean ~ Heroism * Cond + scale(Attitude)"
 (Anova(mod4 <-lm(Victim_G_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores), type = "III"))
paste0("Model Comparison: assessing the importance of attitude - not accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - not accounting for occupations"
anova(mod1, mod3)
paste0("Model Comparison: assessing the importance of attitude - accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - accounting for occupations"
anova(mod2, mod4)
ggplot(scale_scores, aes(y = Victim_G_mean, x = Heroism)) +
  # 1) points colored by Cond
  geom_point(aes(color = Cond),
             size = 2.7, alpha = 0.7) +
  
  # 2) ONE global lm line (group = 1 prevents one line per Cond)
  stat_smooth(method = "lm", se = TRUE,
              aes(group = 1),
              color = "black", linewidth = 1) +
  
  # Nice, accessible palette (works well with >3 groups)
  scale_color_brewer(palette = "Set2") +
  
  labs(
    y = "Victimisation (G)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Victimisation (G), colored by Occupation",
    subtitle = "Points are participants; black line is overall linear fit with 95% CI"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "right",
    panel.grid.minor = element_blank()
  )
## `geom_smooth()` using formula = 'y ~ x'

ggplot(scale_scores, aes(y = Victim_G_mean, x = Heroism, color = Cond)) +
  # points colored by condition
  geom_point(size = 2.7, alpha = 0.7) +
  # ONE lm line PER condition (because color is mapped here)
  stat_smooth(method = "lm", se = FALSE, linewidth = 1, fullrange = TRUE) +
  scale_color_brewer(palette = "Set2") +
  labs(
    y = "Victimisation (G)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Victimisation (G) with per-Occupation linear fits"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "right", panel.grid.minor = element_blank())
## `geom_smooth()` using formula = 'y ~ x'

A significant, yet small effect, in the opposite direction.

Model 1: “~Heroism + Occupation”

# =========================
# OLD MODELS (Heroism)
# =========================
m1_H <- lm(Victim_G_mean ~ Heroism + Occupation, data = scale_scores)
m2_H <- lm(Victim_G_mean ~ Heroism * Occupation, data = scale_scores)
m3_H <- lm(Victim_G_mean ~ Heroism + Occupation + Attitude, data = scale_scores)
m4_H <- lm(Victim_G_mean ~ Heroism * Occupation + Attitude, data = scale_scores)

tidy_type3(m1_H, "~Heroism + Cond")
~Heroism + Cond
F p eta2p
Heroism 56.03 < .001 0.119
Occupation 17.21 < .001 0.077
# =========================
# NEW MODELS (Danger & Help)
# =========================

Model 2: “~Heroism * Occupation”

tidy_type3(m2_H, "~Heroism * Cond")
~Heroism * Cond
F p eta2p
Heroism 55.87 < .001 0.120
Occupation 1.40 = 0.249 0.007
Heroism:Occupation 2.05 = 0.130 0.010

Model 3: “~Heroism + Occupation + Attitude”

tidy_type3(m3_H, "~Heroism + Occupation + Attitude")
~Heroism + Occupation + Attitude
F p eta2p
Heroism 23.38 < .001 0.054
Occupation 16.54 < .001 0.074
Attitude 0.18 = 0.674 0.000

Model 4: “~Heroism * Occupation + Attitude”

tidy_type3(m4_H, "~Heroism * Occupation + Attitude")
~Heroism * Occupation + Attitude
F p eta2p
Heroism 21.47 < .001 0.050
Occupation 1.64 = 0.195 0.008
Attitude 0.65 = 0.419 0.002
Heroism:Occupation 2.29 = 0.103 0.011

Comparison of main predictors across models

# Use orthogonal contrasts; keeps your "Heroism main effect" interpretable with Cond in the model
old_contr <- options(contrasts = c("contr.sum", "contr.poly"))

# Helper to extract the main effect of "Heroism" from a car::Anova table
extract_heroism <- function(mod, model_label) {
  a <- car::Anova(mod, type = "III")

  # Coerce to data.frame with rownames preserved
  tab <- as.data.frame(a)
  tab$Term <- rownames(tab)

  # Standard column names across R versions
  # (car::Anova uses these names for lm/glm type=III)
  # Columns: "Sum Sq", "Df", "F value", "Pr(>F)"
  names(tab) <- sub(" ", "_", names(tab))  # make names safe: "Sum_Sq", "F_value", etc.

  # Pull rows we need
  hero <- tab %>% filter(Term == "Heroism")
  resid <- tab %>% filter(Term == "Residuals")

  # Safety checks (in case of name variants)
  stopifnot(nrow(hero) == 1, nrow(resid) == 1)

  # Partial eta^2 = SS_effect / (SS_effect + SS_residual)
  eta_p2 <- hero$Sum_Sq / (hero$Sum_Sq + resid$Sum_Sq)

  # Format p nicely (APA-ish)
  p_fmt <- ifelse(hero$`Pr(>F)` < .001, "< .001",
                  sprintf("= %.3f", hero$`Pr(>F)`))

  dplyr::tibble(
    Model = model_label,
    F = hero$F_value,
    p = p_fmt,
    eta2p = eta_p2
  )
}

# ----- Fit the four models -----
mod1 <- lm(Victim_G_mean ~ Heroism + Cond, data = scale_scores)
mod2 <- lm(Victim_G_mean ~ Heroism * Cond, data = scale_scores)
mod3 <- lm(Victim_G_mean ~ Heroism + Cond + scale(Attitude), data = scale_scores)
mod4 <- lm(Victim_G_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores)

# ----- Build the summary table -----
tbl <- bind_rows(
  extract_heroism(mod1, "~ Heroism + Occupation"),
  extract_heroism(mod2, "~ Heroism * Occupation"),
  extract_heroism(mod3, "~ Heroism + Occupation + Attitude"),
  extract_heroism(mod4, "~ Heroism * Occupation + Attitude")
  ) %>%
  # Nice number formatting for F and eta^2p
  mutate(
    F = round(F, 2),
    eta2p = round(eta2p, 3)
  )

# ----- Print as HTML-friendly table -----
# Build the table first
tbl_kbl <- tbl %>%
  kable("html",
        caption = "Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²",
        align = "lllrrrcr")
# Add a footnote in a version-robust way
if ("footnote" %in% getNamespaceExports("kableExtra")) {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::footnote(
      general = "Type-III sums of squares with sum contrasts.",
      general_title = ""
    )
} else {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::add_footnote(
      general = "Type-III sums of squares with sum contrasts.",
      notation = "none"
    )
}

tbl_kbl
Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²
Model F p eta2p
~ Heroism + Occupation 56.03 < .001 0.119
~ Heroism * Occupation 55.87 < .001 0.120
~ Heroism + Occupation + Attitude 23.38 < .001 0.054
~ Heroism * Occupation + Attitude 21.47 < .001 0.050
Type-III sums of squares with sum contrasts.

Specific level

[Consider the following observation from a recent : In their professional life, more than 60% of journalists have ed intense migraines from working long hours. How much would you agree or disagree with the following statements:] I believe journalists are strong enough to face this condition

Toggle details of the models diagnostics and outlier analyses

Below, you’ll find model diagnostics (QQ plot, fitted vs residuals, linearity), Outliers analysis, and more details on the output

[NOTE THAT EFFECT SIZES ED IN THE COMMANDS BELOW SHOULD NOT BE TRUSTED]

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 1: DV ~ Heroism + Occupation")
## [1] "Diagnostics for Model 1: DV ~ Heroism + Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Victim_S_mean ~ Heroism  + Cond, data = scale_scores)
mod1r <- lmrob(Victim_S_mean ~ Heroism  + Cond, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.919 3.910
(0.183) (0.228)
21.368 17.147
p = <0.001 p = <0.001
Heroism 0.227 0.235
(0.034) (0.041)
6.694 5.791
p = <0.001 p = <0.001
Cond1 0.194 0.190
(0.075) (0.074)
2.600 2.569
p = 0.010 p = 0.011
Cond2 −0.413 −0.435
(0.074) (0.081)
−5.610 −5.358
p = <0.001 p = <0.001
Num.Obs. 417 417
R2 0.165 0.174
R2 Adj. 0.159 0.168
RMSE 1.06 1.06
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Victim_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 2: DV ~ Heroism * Occupation")
## [1] "Diagnostics for Model 2: DV ~ Heroism * Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Victim_S_mean ~ Heroism  * Cond, data = scale_scores)
mod1r <- lmrob(Victim_S_mean ~ Heroism  * Cond, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.897 3.913
(0.185) (0.214)
21.039 18.248
p = <0.001 p = <0.001
Heroism 0.228 0.232
(0.035) (0.038)
6.606 6.048
p = <0.001 p = <0.001
Cond1 −0.292 −0.338
(0.266) (0.295)
−1.096 −1.146
p = 0.274 p = 0.252
Cond2 −0.020 0.030
(0.249) (0.335)
−0.081 0.088
p = 0.935 p = 0.930
Heroism × Cond1 0.089 0.096
(0.048) (0.051)
1.879 1.893
p = 0.061 p = 0.059
Heroism × Cond2 −0.074 −0.086
(0.047) (0.060)
−1.587 −1.440
p = 0.113 p = 0.151
Num.Obs. 417 417
R2 0.174 0.181
R2 Adj. 0.164 0.171
RMSE 1.05 1.05
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Victim_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude")
## [1] "Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Victim_S_mean ~ Heroism  + Cond + Attitude, data = scale_scores)
mod1r <- lmrob(Victim_S_mean ~ Heroism  + Cond + Attitude, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.770 3.619
(0.262) (0.344)
14.411 10.524
p = <0.001 p = <0.001
Heroism 0.199 0.182
(0.049) (0.058)
4.017 3.114
p = <0.001 p = 0.002
Cond1 0.183 0.167
(0.076) (0.075)
2.401 2.227
p = 0.017 p = 0.026
Cond2 −0.401 −0.417
(0.075) (0.083)
−5.343 −5.007
p = <0.001 p = <0.001
Attitude 0.051 0.099
(0.064) (0.082)
0.803 1.205
p = 0.422 p = 0.229
Num.Obs. 417 417
R2 0.166 0.183
R2 Adj. 0.158 0.175
RMSE 1.05 1.06
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Victim_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude")
## [1] "Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Victim_S_mean ~ Heroism*Cond + Attitude, data = scale_scores)
mod1r <- lmrob(Victim_S_mean ~ Heroism*Cond + Attitude, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.672 3.562
(0.270) (0.296)
13.620 12.034
p = <0.001 p = <0.001
Heroism 0.188 0.163
(0.050) (0.063)
3.788 2.567
p = <0.001 p = 0.011
Cond1 −0.330 −0.385
(0.268) (0.284)
−1.231 −1.357
p = 0.219 p = 0.175
Cond2 0.056 0.162
(0.258) (0.361)
0.218 0.449
p = 0.828 p = 0.654
Attitude 0.075 0.123
(0.065) (0.082)
1.149 1.503
p = 0.251 p = 0.134
Heroism × Cond1 0.093 0.099
(0.048) (0.049)
1.954 2.023
p = 0.051 p = 0.044
Heroism × Cond2 −0.085 −0.107
(0.048) (0.063)
−1.795 −1.688
p = 0.073 p = 0.092
Num.Obs. 417 417
R2 0.176 0.188
R2 Adj. 0.164 0.176
RMSE 1.05 1.05
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Victim_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0(" S for each model (please ignore the eta squares, they are way off")
## [1] " S for each model (please ignore the eta squares, they are way off"
paste0("####################################################")
## [1] "####################################################"
paste0("MODEL 1: Victim_S_mean ~ Heroism")
## [1] "MODEL 1: Victim_S_mean ~ Heroism"
 (Anova(mod1 <- lm(Victim_S_mean ~ Heroism  + Cond, data = scale_scores), type = "III"))
paste0("MODEL 2: Victim_S_mean ~ Heroism * Cond")
## [1] "MODEL 2: Victim_S_mean ~ Heroism * Cond"
 (Anova(mod2 <- lm(Victim_S_mean ~ Heroism * Cond, data = scale_scores), type = "III"))
paste0("MODEL 3: Victim_S_mean ~ Heroism +scale(Attitude)")
## [1] "MODEL 3: Victim_S_mean ~ Heroism +scale(Attitude)"
 (Anova(mod3 <- lm(Victim_S_mean ~ Heroism   + Cond +scale(Attitude) , data = scale_scores), type = "III"))
paste0("MODEL 4: Victim_S_mean ~ Heroism * Cond + scale(Attitude)")
## [1] "MODEL 4: Victim_S_mean ~ Heroism * Cond + scale(Attitude)"
 (Anova(mod4 <-lm(Victim_S_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores), type = "III"))
paste0("Model Comparison: assessing the importance of attitude - not accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - not accounting for occupations"
anova(mod1, mod3)
paste0("Model Comparison: assessing the importance of attitude - accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - accounting for occupations"
anova(mod2, mod4)
ggplot(scale_scores, aes(y = Victim_S_mean, x = Heroism)) +
  # 1) points colored by Cond
  geom_point(aes(color = Cond),
             size = 2.7, alpha = 0.7) +
  
  # 2) ONE global lm line (group = 1 prevents one line per Cond)
  stat_smooth(method = "lm", se = TRUE,
              aes(group = 1),
              color = "black", linewidth = 1) +
  
  # Nice, accessible palette (works well with >3 groups)
  scale_color_brewer(palette = "Set2") +
  
  labs(
    y = "Victimisation (S)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Victimisation (S), colored by Occupation",
    subtitle = "Points are participants; black line is overall linear fit with 95% CI"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "right",
    panel.grid.minor = element_blank()
  )
## `geom_smooth()` using formula = 'y ~ x'

ggplot(scale_scores, aes(y = Victim_S_mean, x = Heroism, color = Cond)) +
  # points colored by condition
  geom_point(size = 2.7, alpha = 0.7) +
  # ONE lm line PER condition (because color is mapped here)
  stat_smooth(method = "lm", se = FALSE, linewidth = 1, fullrange = TRUE) +
  scale_color_brewer(palette = "Set2") +
  labs(
    y = "Victimisation (S)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Victimisation (S) with per-Occupation linear fits"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "right", panel.grid.minor = element_blank())
## `geom_smooth()` using formula = 'y ~ x'

==> No support for our hypotheses. Across all occupations Heroism predicted POSITIVE general- and specific-level workers’ victimization, with or without controlling for attitude. Although effects were drastically reduced when accounting for attitude, heroism might increase our perception that workers need help and support, and suffer and are exploited. Our hypotheses should therefore be revised.

Model 1: “~Heroism + Occupation”

# =========================
# OLD MODELS (Heroism)
# =========================
m1_H <- lm(Victim_S_mean ~ Heroism + Occupation, data = scale_scores)
m2_H <- lm(Victim_S_mean ~ Heroism * Occupation, data = scale_scores)
m3_H <- lm(Victim_S_mean ~ Heroism + Occupation + Attitude, data = scale_scores)
m4_H <- lm(Victim_S_mean ~ Heroism * Occupation + Attitude, data = scale_scores)

tidy_type3(m1_H, "~Heroism + Cond")
~Heroism + Cond
F p eta2p
Heroism 44.81 < .001 0.098
Occupation 15.76 < .001 0.071
# =========================
# NEW MODELS (Danger & Help)
# =========================

Model 2: “~Heroism * Occupation”

tidy_type3(m2_H, "~Heroism * Cond")
~Heroism * Cond
F p eta2p
Heroism 43.64 < .001 0.096
Occupation 0.81 = 0.446 0.004
Heroism:Occupation 2.21 = 0.111 0.011

Model 3: “~Heroism + Occupation + Attitude”

tidy_type3(m3_H, "~Heroism + Occupation + Attitude")
~Heroism + Occupation + Attitude
F p eta2p
Heroism 16.13 < .001 0.038
Occupation 14.36 < .001 0.065
Attitude 0.65 = 0.422 0.002

Model 4: “~Heroism * Occupation + Attitude”

tidy_type3(m4_H, "~Heroism * Occupation + Attitude")
~Heroism * Occupation + Attitude
F p eta2p
Heroism 14.35 < .001 0.034
Occupation 0.84 = 0.433 0.004
Attitude 1.32 = 0.251 0.003
Heroism:Occupation 2.55 = 0.080 0.012

Comparison of main predictors across models

# Use orthogonal contrasts; keeps your "Heroism main effect" interpretable with Cond in the model
old_contr <- options(contrasts = c("contr.sum", "contr.poly"))

# Helper to extract the main effect of "Heroism" from a car::Anova table
extract_heroism <- function(mod, model_label) {
  a <- car::Anova(mod, type = "III")

  # Coerce to data.frame with rownames preserved
  tab <- as.data.frame(a)
  tab$Term <- rownames(tab)

  # Standard column names across R versions
  # (car::Anova uses these names for lm/glm type=III)
  # Columns: "Sum Sq", "Df", "F value", "Pr(>F)"
  names(tab) <- sub(" ", "_", names(tab))  # make names safe: "Sum_Sq", "F_value", etc.

  # Pull rows we need
  hero <- tab %>% filter(Term == "Heroism")
  resid <- tab %>% filter(Term == "Residuals")

  # Safety checks (in case of name variants)
  stopifnot(nrow(hero) == 1, nrow(resid) == 1)

  # Partial eta^2 = SS_effect / (SS_effect + SS_residual)
  eta_p2 <- hero$Sum_Sq / (hero$Sum_Sq + resid$Sum_Sq)

  # Format p nicely (APA-ish)
  p_fmt <- ifelse(hero$`Pr(>F)` < .001, "< .001",
                  sprintf("= %.3f", hero$`Pr(>F)`))

  dplyr::tibble(
    Model = model_label,
    F = hero$F_value,
    p = p_fmt,
    eta2p = eta_p2
  )
}

# ----- Fit the four models -----
mod1 <- lm(Victim_S_mean ~ Heroism + Cond, data = scale_scores)
mod2 <- lm(Victim_S_mean ~ Heroism * Cond, data = scale_scores)
mod3 <- lm(Victim_S_mean ~ Heroism + Cond + scale(Attitude), data = scale_scores)
mod4 <- lm(Victim_S_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores)

# ----- Build the summary table -----
tbl <- bind_rows(
  extract_heroism(mod1, "~ Heroism + Cond"),
  extract_heroism(mod2, "~ Heroism * Occupation"),
  extract_heroism(mod3, "~ Heroism + Cond + Attitude"),
  extract_heroism(mod4, "~ Heroism * Occupation + Attitude")
  ) %>%
  # Nice number formatting for F and eta^2p
  mutate(
    F = round(F, 2),
    eta2p = round(eta2p, 3)
  )

# ----- Print as HTML-friendly table -----
# Build the table first
tbl_kbl <- tbl %>%
  kable("html",
        caption = "Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²",
        align = "lllrrrcr")
# Add a footnote in a version-robust way
if ("footnote" %in% getNamespaceExports("kableExtra")) {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::footnote(
      general = "Type-III sums of squares with sum contrasts.",
      general_title = ""
    )
} else {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::add_footnote(
      general = "Type-III sums of squares with sum contrasts.",
      notation = "none"
    )
}

tbl_kbl
Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²
Model F p eta2p
~ Heroism + Cond 44.81 < .001 0.098
~ Heroism * Occupation 43.64 < .001 0.096
~ Heroism + Cond + Attitude 16.13 < .001 0.038
~ Heroism * Occupation + Attitude 14.35 < .001 0.034
Type-III sums of squares with sum contrasts.

NOVEMBER DF

General level

[How much do you see journalists as:] Victimised

Toggle details of the models diagnostics and outlier analyses

Below, you’ll find model diagnostics (QQ plot, fitted vs residuals, linearity), Outliers analysis, and more details on the output

[NOTE THAT EFFECT SIZES ED IN THE COMMANDS BELOW SHOULD NOT BE TRUSTED]

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 1: DV ~ Heroism + Occupation")
## [1] "Diagnostics for Model 1: DV ~ Heroism + Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Victim_G_mean ~ Heroism  + Cond, data = scale_scores2)
mod1r <- lmrob(Victim_G_mean ~ Heroism  + Cond, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 2.113 1.971
(0.226) (0.218)
9.359 9.022
p = <0.001 p = <0.001
Heroism 0.241 0.264
(0.043) (0.044)
5.621 5.956
p = <0.001 p = <0.001
Cond1 0.688 0.734
(0.097) (0.102)
7.103 7.166
p = <0.001 p = <0.001
Cond2 −0.118 −0.113
(0.094) (0.095)
−1.245 −1.190
p = 0.214 p = 0.235
Num.Obs. 421 421
R2 0.220 0.243
R2 Adj. 0.215 0.238
RMSE 1.36 1.36
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Victim_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 2: DV ~ Heroism * Occupation")
## [1] "Diagnostics for Model 2: DV ~ Heroism * Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Victim_G_mean ~ Heroism  * Cond, data = scale_scores2)
mod1r <- lmrob(Victim_G_mean ~ Heroism  * Cond, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 2.052 1.922
(0.232) (0.219)
8.854 8.794
p = <0.001 p = <0.001
Heroism 0.251 0.271
(0.044) (0.045)
5.715 5.992
p = <0.001 p = <0.001
Cond1 0.368 0.408
(0.354) (0.312)
1.039 1.307
p = 0.299 p = 0.192
Cond2 0.200 0.185
(0.303) (0.307)
0.658 0.605
p = 0.511 p = 0.546
Heroism × Cond1 0.059 0.061
(0.064) (0.060)
0.917 1.009
p = 0.360 p = 0.314
Heroism × Cond2 −0.062 −0.058
(0.058) (0.062)
−1.071 −0.941
p = 0.285 p = 0.347
Num.Obs. 421 421
R2 0.223 0.246
R2 Adj. 0.213 0.237
RMSE 1.36 1.36
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Victim_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude")
## [1] "Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Victim_G_mean ~ Heroism  + Cond + Attitude, data = scale_scores2)
mod1r <- lmrob(Victim_G_mean ~ Heroism  + Cond + Attitude, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 2.264 2.138
(0.332)
6.814
p = <0.001
Heroism 0.267 0.314
(0.060)
4.462
p = <0.001
Cond1 0.709 1.209
(0.103)
6.905
p = <0.001
Cond2 −0.136 −0.299
(0.099)
−1.373
p = 0.170
Attitude −0.049 −0.095
(0.079)
−0.620
p = 0.535
Num.Obs. 421 421
R2 0.221 0.616
R2 Adj. 0.213 0.613
RMSE 1.36 1.42
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Victim_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude")
## [1] "Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Victim_G_mean ~ Heroism*Cond + Attitude, data = scale_scores2)
mod1r <- lmrob(Victim_G_mean ~ Heroism*Cond + Attitude, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 2.126 2.080
(0.359) (0.374)
5.917 5.556
p = <0.001 p = <0.001
Heroism 0.262 0.295
(0.060) (0.063)
4.356 4.716
p = <0.001 p = <0.001
Cond1 0.397 0.470
(0.371) (0.326)
1.072 1.440
p = 0.284 p = 0.151
Cond2 0.165 0.119
(0.330) (0.320)
0.500 0.373
p = 0.617 p = 0.709
Attitude −0.023 −0.049
(0.084) (0.092)
−0.271 −0.531
p = 0.787 p = 0.595
Heroism × Cond1 0.055 0.053
(0.065) (0.061)
0.844 0.872
p = 0.399 p = 0.383
Heroism × Cond2 −0.057 −0.049
(0.061) (0.063)
−0.931 −0.772
p = 0.353 p = 0.440
Num.Obs. 421 421
R2 0.223 0.247
R2 Adj. 0.212 0.236
RMSE 1.36 1.36
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Victim_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0(" S for each model (please ignore the eta squares, they are way off")
## [1] " S for each model (please ignore the eta squares, they are way off"
paste0("####################################################")
## [1] "####################################################"
paste0("MODEL 1: Victim_G_mean ~ Heroism")
## [1] "MODEL 1: Victim_G_mean ~ Heroism"
 (Anova(mod1 <- lm(Victim_G_mean ~ Heroism  + Cond, data = scale_scores2), type = "III"))
paste0("MODEL 2: Victim_G_mean ~ Heroism * Cond")
## [1] "MODEL 2: Victim_G_mean ~ Heroism * Cond"
 (Anova(mod2 <- lm(Victim_G_mean ~ Heroism * Cond, data = scale_scores2), type = "III"))
paste0("MODEL 3: Victim_G_mean ~ Heroism +scale(Attitude)")
## [1] "MODEL 3: Victim_G_mean ~ Heroism +scale(Attitude)"
 (Anova(mod3 <- lm(Victim_G_mean ~ Heroism   + Cond +scale(Attitude) , data = scale_scores2), type = "III"))
paste0("MODEL 4: Victim_G_mean ~ Heroism * Cond + scale(Attitude)")
## [1] "MODEL 4: Victim_G_mean ~ Heroism * Cond + scale(Attitude)"
 (Anova(mod4 <-lm(Victim_G_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores2), type = "III"))
paste0("Model Comparison: assessing the importance of attitude - not accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - not accounting for occupations"
anova(mod1, mod3)
paste0("Model Comparison: assessing the importance of attitude - accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - accounting for occupations"
anova(mod2, mod4)
ggplot(scale_scores2, aes(y = Victim_G_mean, x = Heroism)) +
  # 1) points colored by Cond
  geom_point(aes(color = Cond),
             size = 2.7, alpha = 0.7) +
  
  # 2) ONE global lm line (group = 1 prevents one line per Cond)
  stat_smooth(method = "lm", se = TRUE,
              aes(group = 1),
              color = "black", linewidth = 1) +
  
  # Nice, accessible palette (works well with >3 groups)
  scale_color_brewer(palette = "Set2") +
  
  labs(
    y = "Victimisation (G)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Victimisation (G), colored by Occupation",
    subtitle = "Points are participants; black line is overall linear fit with 95% CI"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "right",
    panel.grid.minor = element_blank()
  )
## `geom_smooth()` using formula = 'y ~ x'

ggplot(scale_scores2, aes(y = Victim_G_mean, x = Heroism, color = Cond)) +
  # points colored by condition
  geom_point(size = 2.7, alpha = 0.7) +
  # ONE lm line PER condition (because color is mapped here)
  stat_smooth(method = "lm", se = FALSE, linewidth = 1, fullrange = TRUE) +
  scale_color_brewer(palette = "Set2") +
  labs(
    y = "Victimisation (G)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Victimisation (G) with per-Occupation linear fits"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "right", panel.grid.minor = element_blank())
## `geom_smooth()` using formula = 'y ~ x'

A significant, yet small effect, in the opposite direction.

Model 1: “~Heroism + Occupation”

# =========================
# OLD MODELS (Heroism)
# =========================
m1_H <- lm(Victim_G_mean ~ Heroism + Occupation, data = scale_scores2)
m2_H <- lm(Victim_G_mean ~ Heroism * Occupation, data = scale_scores2)
m3_H <- lm(Victim_G_mean ~ Heroism + Occupation + Attitude, data = scale_scores2)
m4_H <- lm(Victim_G_mean ~ Heroism * Occupation + Attitude, data = scale_scores2)

tidy_type3(m1_H, "~Heroism + Cond")
~Heroism + Cond
F p eta2p
Heroism 31.6 < .001 0.070
Occupation 28.6 < .001 0.121
# =========================
# NEW MODELS (Danger & Help)
# =========================

Model 2: “~Heroism * Occupation”

tidy_type3(m2_H, "~Heroism * Cond")
~Heroism * Cond
F p eta2p
Heroism 32.66 < .001 0.073
Occupation 1.54 = 0.215 0.007
Heroism:Occupation 0.69 = 0.500 0.003

Model 3: “~Heroism + Occupation + Attitude”

tidy_type3(m3_H, "~Heroism + Occupation + Attitude")
~Heroism + Occupation + Attitude
F p eta2p
Heroism 19.91 < .001 0.046
Occupation 27.86 < .001 0.118
Attitude 0.38 = 0.535 0.001

Model 4: “~Heroism * Occupation + Attitude”

tidy_type3(m4_H, "~Heroism * Occupation + Attitude")
~Heroism * Occupation + Attitude
F p eta2p
Heroism 18.97 < .001 0.044
Occupation 1.51 = 0.222 0.007
Attitude 0.07 = 0.787 0.000
Heroism:Occupation 0.54 = 0.585 0.003

Comparison of main predictors across models

# Use orthogonal contrasts; keeps your "Heroism main effect" interpretable with Cond in the model
old_contr <- options(contrasts = c("contr.sum", "contr.poly"))

# Helper to extract the main effect of "Heroism" from a car::Anova table
extract_heroism <- function(mod, model_label) {
  a <- car::Anova(mod, type = "III")

  # Coerce to data.frame with rownames preserved
  tab <- as.data.frame(a)
  tab$Term <- rownames(tab)

  # Standard column names across R versions
  # (car::Anova uses these names for lm/glm type=III)
  # Columns: "Sum Sq", "Df", "F value", "Pr(>F)"
  names(tab) <- sub(" ", "_", names(tab))  # make names safe: "Sum_Sq", "F_value", etc.

  # Pull rows we need
  hero <- tab %>% filter(Term == "Heroism")
  resid <- tab %>% filter(Term == "Residuals")

  # Safety checks (in case of name variants)
  stopifnot(nrow(hero) == 1, nrow(resid) == 1)

  # Partial eta^2 = SS_effect / (SS_effect + SS_residual)
  eta_p2 <- hero$Sum_Sq / (hero$Sum_Sq + resid$Sum_Sq)

  # Format p nicely (APA-ish)
  p_fmt <- ifelse(hero$`Pr(>F)` < .001, "< .001",
                  sprintf("= %.3f", hero$`Pr(>F)`))

    dplyr::tibble(
    Model = model_label,
    F = hero$F_value,
    p = p_fmt,
    eta2p = eta_p2
  )
}

# ----- Fit the four models -----
mod1 <- lm(Victim_G_mean ~ Heroism + Cond, data = scale_scores2)
mod2 <- lm(Victim_G_mean ~ Heroism * Cond, data = scale_scores2)
mod3 <- lm(Victim_G_mean ~ Heroism + Cond + scale(Attitude), data = scale_scores2)
mod4 <- lm(Victim_G_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores2)

# ----- Build the summary table -----
tbl <- bind_rows(
  extract_heroism(mod1, "~ Heroism + Occupation"),
  extract_heroism(mod2, "~ Heroism * Occupation"),
  extract_heroism(mod3, "~ Heroism + Occupation + Attitude"),
  extract_heroism(mod4, "~ Heroism * Occupation + Attitude")
  ) %>%
  # Nice number formatting for F and eta^2p
  mutate(
    F = round(F, 2),
    eta2p = round(eta2p, 3)
  )

# ----- Print as HTML-friendly table -----
# Build the table first
tbl_kbl <- tbl %>%
  kable("html",
        caption = "Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²",
        align = "lllrrrcr")
# Add a footnote in a version-robust way
if ("footnote" %in% getNamespaceExports("kableExtra")) {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::footnote(
      general = "Type-III sums of squares with sum contrasts.",
      general_title = ""
    )
} else {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::add_footnote(
      general = "Type-III sums of squares with sum contrasts.",
      notation = "none"
    )
}

tbl_kbl
Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²
Model F p eta2p
~ Heroism + Occupation 31.60 < .001 0.070
~ Heroism * Occupation 32.66 < .001 0.073
~ Heroism + Occupation + Attitude 19.91 < .001 0.046
~ Heroism * Occupation + Attitude 18.97 < .001 0.044
Type-III sums of squares with sum contrasts.

Specific level

[Consider the following observation from a recent : In their professional life, more than 60% of journalists have ed intense migraines from working long hours. How much would you agree or disagree with the following statements:] I believe journalists are strong enough to face this condition

Toggle details of the models diagnostics and outlier analyses

Below, you’ll find model diagnostics (QQ plot, fitted vs residuals, linearity), Outliers analysis, and more details on the output

[NOTE THAT EFFECT SIZES ED IN THE COMMANDS BELOW SHOULD NOT BE TRUSTED]

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 1: DV ~ Heroism + Occupation")
## [1] "Diagnostics for Model 1: DV ~ Heroism + Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Victim_S_mean ~ Heroism  + Cond, data = scale_scores2)
mod1r <- lmrob(Victim_S_mean ~ Heroism  + Cond, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 4.408 4.361
(0.175) (0.217)
25.256 20.075
p = <0.001 p = <0.001
Heroism 0.162 0.179
(0.033) (0.039)
4.885 4.562
p = <0.001 p = <0.001
Cond1 0.204 0.221
(0.075) (0.076)
2.729 2.908
p = 0.007 p = 0.004
Cond2 −0.326 −0.344
(0.073) (0.076)
−4.461 −4.511
p = <0.001 p = <0.001
Num.Obs. 421 421
R2 0.107 0.128
R2 Adj. 0.100 0.121
RMSE 1.05 1.05
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Victim_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 2: DV ~ Heroism * Occupation")
## [1] "Diagnostics for Model 2: DV ~ Heroism * Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Victim_S_mean ~ Heroism  * Cond, data = scale_scores2)
mod1r <- lmrob(Victim_S_mean ~ Heroism  * Cond, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 4.342 4.318
(0.179) (0.221)
24.273 19.559
p = <0.001 p = <0.001
Heroism 0.173 0.185
(0.034) (0.039)
5.092 4.750
p = <0.001 p = <0.001
Cond1 −0.131 −0.098
(0.273) (0.345)
−0.480 −0.285
p = 0.632 p = 0.776
Cond2 0.019 −0.047
(0.234) (0.312)
0.080 −0.149
p = 0.937 p = 0.881
Heroism × Cond1 0.062 0.057
(0.049) (0.058)
1.245 0.992
p = 0.214 p = 0.322
Heroism × Cond2 −0.067 −0.056
(0.045) (0.056)
−1.510 −1.010
p = 0.132 p = 0.313
Num.Obs. 421 421
R2 0.112 0.129
R2 Adj. 0.102 0.118
RMSE 1.05 1.05
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Victim_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude")
## [1] "Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Victim_S_mean ~ Heroism  + Cond + Attitude, data = scale_scores2)
mod1r <- lmrob(Victim_S_mean ~ Heroism  + Cond + Attitude, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.981 3.693
(0.255) (0.357)
15.591 10.348
p = <0.001 p = <0.001
Heroism 0.089 0.083
(0.046) (0.046)
1.936 1.804
p = 0.054 p = 0.072
Cond1 0.145 0.132
(0.079) (0.083)
1.838 1.592
p = 0.067 p = 0.112
Cond2 −0.273 −0.280
(0.076) (0.078)
−3.584 −3.607
p = <0.001 p = <0.001
Attitude 0.138 0.200
(0.061) (0.075)
2.276 2.679
p = 0.023 p = 0.008
Num.Obs. 421 421
R2 0.118 0.158
R2 Adj. 0.109 0.150
RMSE 1.05 1.05
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Victim_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude")
## [1] "Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Victim_S_mean ~ Heroism*Cond + Attitude, data = scale_scores2)
mod1r <- lmrob(Victim_S_mean ~ Heroism*Cond + Attitude, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.723 3.522
(0.275) (0.336)
13.560 10.483
p = <0.001 p = <0.001
Heroism 0.080 0.074
(0.046) (0.047)
1.737 1.553
p = 0.083 p = 0.121
Cond1 −0.374 −0.378
(0.283) (0.324)
−1.321 −1.168
p = 0.187 p = 0.243
Cond2 0.308 0.239
(0.252) (0.309)
1.221 0.774
p = 0.223 p = 0.440
Attitude 0.189 0.236
(0.064) (0.072)
2.956 3.281
p = 0.003 p = 0.001
Heroism × Cond1 0.091 0.089
(0.050) (0.054)
1.818 1.665
p = 0.070 p = 0.097
Heroism × Cond2 −0.110 −0.096
(0.047) (0.055)
−2.374 −1.761
p = 0.018 p = 0.079
Num.Obs. 421 421
R2 0.131 0.163
R2 Adj. 0.118 0.150
RMSE 1.04 1.04
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Victim_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0(" S for each model (please ignore the eta squares, they are way off")
## [1] " S for each model (please ignore the eta squares, they are way off"
paste0("####################################################")
## [1] "####################################################"
paste0("MODEL 1: Victim_S_mean ~ Heroism")
## [1] "MODEL 1: Victim_S_mean ~ Heroism"
 (Anova(mod1 <- lm(Victim_S_mean ~ Heroism  + Cond, data = scale_scores2), type = "III"))
paste0("MODEL 2: Victim_S_mean ~ Heroism * Cond")
## [1] "MODEL 2: Victim_S_mean ~ Heroism * Cond"
 (Anova(mod2 <- lm(Victim_S_mean ~ Heroism * Cond, data = scale_scores2), type = "III"))
paste0("MODEL 3: Victim_S_mean ~ Heroism +scale(Attitude)")
## [1] "MODEL 3: Victim_S_mean ~ Heroism +scale(Attitude)"
 (Anova(mod3 <- lm(Victim_S_mean ~ Heroism   + Cond +scale(Attitude) , data = scale_scores2), type = "III"))
paste0("MODEL 4: Victim_S_mean ~ Heroism * Cond + scale(Attitude)")
## [1] "MODEL 4: Victim_S_mean ~ Heroism * Cond + scale(Attitude)"
 (Anova(mod4 <-lm(Victim_S_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores2), type = "III"))
paste0("Model Comparison: assessing the importance of attitude - not accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - not accounting for occupations"
anova(mod1, mod3)
paste0("Model Comparison: assessing the importance of attitude - accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - accounting for occupations"
anova(mod2, mod4)
ggplot(scale_scores2, aes(y = Victim_S_mean, x = Heroism)) +
  # 1) points colored by Cond
  geom_point(aes(color = Cond),
             size = 2.7, alpha = 0.7) +
  
  # 2) ONE global lm line (group = 1 prevents one line per Cond)
  stat_smooth(method = "lm", se = TRUE,
              aes(group = 1),
              color = "black", linewidth = 1) +
  
  # Nice, accessible palette (works well with >3 groups)
  scale_color_brewer(palette = "Set2") +
  
  labs(
    y = "Victimisation (S)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Victimisation (S), colored by Occupation",
    subtitle = "Points are participants; black line is overall linear fit with 95% CI"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "right",
    panel.grid.minor = element_blank()
  )
## `geom_smooth()` using formula = 'y ~ x'

ggplot(scale_scores2, aes(y = Victim_S_mean, x = Heroism, color = Cond)) +
  # points colored by condition
  geom_point(size = 2.7, alpha = 0.7) +
  # ONE lm line PER condition (because color is mapped here)
  stat_smooth(method = "lm", se = FALSE, linewidth = 1, fullrange = TRUE) +
  scale_color_brewer(palette = "Set2") +
  labs(
    y = "Victimisation (S)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Victimisation (S) with per-Occupation linear fits"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "right", panel.grid.minor = element_blank())
## `geom_smooth()` using formula = 'y ~ x'

==> No support for our hypotheses. Across all occupations Heroism predicted POSITIVE general- and specific-level workers’ victimization, with or without controlling for attitude. Although effects were drastically reduced when accounting for attitude, heroism might increase our perception that workers need help and support, and suffer and are exploited. Our hypotheses should therefore be revised.

Model 1: “~Heroism + Occupation”

# =========================
# OLD MODELS (Heroism)
# =========================
m1_H <- lm(Victim_S_mean ~ Heroism + Occupation, data = scale_scores2)
m2_H <- lm(Victim_S_mean ~ Heroism * Occupation, data = scale_scores2)
m3_H <- lm(Victim_S_mean ~ Heroism + Occupation + Attitude, data = scale_scores2)
m4_H <- lm(Victim_S_mean ~ Heroism * Occupation + Attitude, data = scale_scores2)

tidy_type3(m1_H, "~Heroism + Cond")
~Heroism + Cond
F p eta2p
Heroism 23.86 < .001 0.054
Occupation 10.14 < .001 0.046
# =========================
# NEW MODELS (Danger & Help)
# =========================

Model 2: “~Heroism * Occupation”

tidy_type3(m2_H, "~Heroism * Cond")
~Heroism * Cond
F p eta2p
Heroism 25.93 < .001 0.059
Occupation 0.14 = 0.873 0.001
Heroism:Occupation 1.34 = 0.262 0.006

Model 3: “~Heroism + Occupation + Attitude”

tidy_type3(m3_H, "~Heroism + Occupation + Attitude")
~Heroism + Occupation + Attitude
F p eta2p
Heroism 3.75 = 0.054 0.009
Occupation 6.43 = 0.002 0.030
Attitude 5.18 = 0.023 0.012

Model 4: “~Heroism * Occupation + Attitude”

tidy_type3(m4_H, "~Heroism * Occupation + Attitude")
~Heroism * Occupation + Attitude
F p eta2p
Heroism 3.02 = 0.083 0.007
Occupation 1.03 = 0.358 0.005
Attitude 8.74 = 0.003 0.021
Heroism:Occupation 3.12 = 0.045 0.015

Comparison of main predictors across models

# Use orthogonal contrasts; keeps your "Heroism main effect" interpretable with Cond in the model
old_contr <- options(contrasts = c("contr.sum", "contr.poly"))

# Helper to extract the main effect of "Heroism" from a car::Anova table
extract_heroism <- function(mod, model_label) {
  a <- car::Anova(mod, type = "III")

  # Coerce to data.frame with rownames preserved
  tab <- as.data.frame(a)
  tab$Term <- rownames(tab)

  # Standard column names across R versions
  # (car::Anova uses these names for lm/glm type=III)
  # Columns: "Sum Sq", "Df", "F value", "Pr(>F)"
  names(tab) <- sub(" ", "_", names(tab))  # make names safe: "Sum_Sq", "F_value", etc.

  # Pull rows we need
  hero <- tab %>% filter(Term == "Heroism")
  resid <- tab %>% filter(Term == "Residuals")

  # Safety checks (in case of name variants)
  stopifnot(nrow(hero) == 1, nrow(resid) == 1)

  # Partial eta^2 = SS_effect / (SS_effect + SS_residual)
  eta_p2 <- hero$Sum_Sq / (hero$Sum_Sq + resid$Sum_Sq)

  # Format p nicely (APA-ish)
  p_fmt <- ifelse(hero$`Pr(>F)` < .001, "< .001",
                  sprintf("= %.3f", hero$`Pr(>F)`))

    dplyr::tibble(
    Model = model_label,
    F = hero$F_value,
    p = p_fmt,
    eta2p = eta_p2
  )
}

# ----- Fit the four models -----
mod1 <- lm(Victim_S_mean ~ Heroism + Cond, data = scale_scores2)
mod2 <- lm(Victim_S_mean ~ Heroism * Cond, data = scale_scores2)
mod3 <- lm(Victim_S_mean ~ Heroism + Cond + scale(Attitude), data = scale_scores2)
mod4 <- lm(Victim_S_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores2)

# ----- Build the summary table -----
tbl <- bind_rows(
  extract_heroism(mod1, "~ Heroism + Cond"),
  extract_heroism(mod2, "~ Heroism * Occupation"),
  extract_heroism(mod3, "~ Heroism + Cond + Attitude"),
  extract_heroism(mod4, "~ Heroism * Occupation + Attitude")
  ) %>%
  # Nice number formatting for F and eta^2p
  mutate(
    F = round(F, 2),
    eta2p = round(eta2p, 3)
  )

# ----- Print as HTML-friendly table -----
# Build the table first
tbl_kbl <- tbl %>%
  kable("html",
        caption = "Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²",
        align = "lllrrrcr")
# Add a footnote in a version-robust way
if ("footnote" %in% getNamespaceExports("kableExtra")) {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::footnote(
      general = "Type-III sums of squares with sum contrasts.",
      general_title = ""
    )
} else {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::add_footnote(
      general = "Type-III sums of squares with sum contrasts.",
      notation = "none"
    )
}

tbl_kbl
Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²
Model F p eta2p
~ Heroism + Cond 23.86 < .001 0.054
~ Heroism * Occupation 25.93 < .001 0.059
~ Heroism + Cond + Attitude 3.75 = 0.054 0.009
~ Heroism * Occupation + Attitude 3.02 = 0.083 0.007
Type-III sums of squares with sum contrasts.

H5: Heroism is associated to greater impunity

Because Heroic status might also be incompatible with the villain status (Hartman et al., 2022), we should grant greater impunity to heroes, perceived as moral instances. At the general level, it means that we would support de-regulating the occupations. At the specific level, it means that in the context of a moral dilemma contrasting respecting the rules vs doing one’s job, we would be in favour of protecting rule-breaking heroes.

SEPTEMBER DF

General level

Journalists should be given more freedom in the way they do their work

Toggle details of the models diagnostics and outlier analyses

Below, you’ll find model diagnostics (QQ plot, fitted vs residuals, linearity), Outliers analysis, and more details on the output

[NOTE THAT EFFECT SIZES ED IN THE COMMANDS BELOW SHOULD NOT BE TRUSTED]

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 1: DV ~ Heroism + Occupation")
## [1] "Diagnostics for Model 1: DV ~ Heroism + Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Villain_G_mean ~ Heroism  + Cond, data = scale_scores)
mod1r <- lmrob(Villain_G_mean ~ Heroism  + Cond, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 2.177 2.120
(0.228) (0.227)
9.548 9.357
p = <0.001 p = <0.001
Heroism 0.324 0.337
(0.042) (0.043)
7.677 7.824
p = <0.001 p = <0.001
Cond1 −0.087 −0.068
(0.093) (0.097)
−0.932 −0.697
p = 0.352 p = 0.486
Cond2 −0.141 −0.175
(0.092) (0.102)
−1.545 −1.717
p = 0.123 p = 0.087
Num.Obs. 417 417
R2 0.132 0.143
R2 Adj. 0.125 0.136
RMSE 1.31 1.31
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Villain_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 2: DV ~ Heroism * Occupation")
## [1] "Diagnostics for Model 2: DV ~ Heroism * Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Villain_G_mean ~ Heroism  * Cond, data = scale_scores)
mod1r <- lmrob(Villain_G_mean ~ Heroism  * Cond, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 2.265 2.214
(0.229) (0.221)
9.907 10.029
p = <0.001 p = <0.001
Heroism 0.310 0.322
(0.043) (0.042)
7.272 7.649
p = <0.001 p = <0.001
Cond1 0.543 0.486
(0.328) (0.326)
1.654 1.491
p = 0.099 p = 0.137
Cond2 −1.074 −1.089
(0.307) (0.295)
−3.493 −3.697
p = <0.001 p = <0.001
Heroism × Cond1 −0.113 −0.101
(0.059) (0.060)
−1.931 −1.687
p = 0.054 p = 0.092
Heroism × Cond2 0.180 0.180
(0.057) (0.057)
3.139 3.133
p = 0.002 p = 0.002
Num.Obs. 417 417
R2 0.153 0.163
R2 Adj. 0.143 0.153
RMSE 1.30 1.30
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Villain_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude")
## [1] "Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Villain_G_mean ~ Heroism  + Cond + Attitude, data = scale_scores)
mod1r <- lmrob(Villain_G_mean ~ Heroism  + Cond + Attitude, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 1.429 1.411
(0.321) (0.280)
4.448 5.032
p = <0.001 p = <0.001
Heroism 0.180 0.197
(0.061) (0.055)
2.966 3.551
p = 0.003 p = <0.001
Cond1 −0.144 −0.125
(0.094) (0.100)
−1.542 −1.258
p = 0.124 p = 0.209
Cond2 −0.083 −0.120
(0.092) (0.102)
−0.896 −1.179
p = 0.371 p = 0.239
Attitude 0.257 0.248
(0.079) (0.069)
3.267 3.596
p = 0.001 p = <0.001
Num.Obs. 417 417
R2 0.154 0.163
R2 Adj. 0.145 0.155
RMSE 1.30 1.30
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Villain_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude")
## [1] "Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Villain_G_mean ~ Heroism*Cond + Attitude, data = scale_scores)
mod1r <- lmrob(Villain_G_mean ~ Heroism*Cond + Attitude, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 1.619 1.608
(0.330) (0.285)
4.901 5.652
p = <0.001 p = <0.001
Heroism 0.193 0.208
(0.061) (0.057)
3.186 3.616
p = 0.002 p = <0.001
Cond1 0.433 0.370
(0.329) (0.313)
1.318 1.184
p = 0.188 p = 0.237
Cond2 −0.855 −0.863
(0.316) (0.284)
−2.707 −3.034
p = 0.007 p = 0.003
Attitude 0.215 0.207
(0.080) (0.072)
2.689 2.859
p = 0.007 p = 0.004
Heroism × Cond1 −0.103 −0.089
(0.058) (0.057)
−1.758 −1.555
p = 0.080 p = 0.121
Heroism × Cond2 0.147 0.145
(0.058) (0.055)
2.519 2.608
p = 0.012 p = 0.009
Num.Obs. 417 417
R2 0.168 0.176
R2 Adj. 0.156 0.164
RMSE 1.28 1.28
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Villain_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0(" S for each model (please ignore the eta squares, they are way off")
## [1] " S for each model (please ignore the eta squares, they are way off"
paste0("####################################################")
## [1] "####################################################"
paste0("MODEL 1: Villain_G_mean ~ Heroism")
## [1] "MODEL 1: Villain_G_mean ~ Heroism"
 (Anova(mod1 <- lm(Villain_G_mean ~ Heroism  + Cond, data = scale_scores), type = "III"))
paste0("MODEL 2: Villain_G_mean ~ Heroism * Cond")
## [1] "MODEL 2: Villain_G_mean ~ Heroism * Cond"
 (Anova(mod2 <- lm(Villain_G_mean ~ Heroism * Cond, data = scale_scores), type = "III"))
paste0("MODEL 3: Villain_G_mean ~ Heroism +scale(Attitude)")
## [1] "MODEL 3: Villain_G_mean ~ Heroism +scale(Attitude)"
 (Anova(mod3 <- lm(Villain_G_mean ~ Heroism   + Cond +scale(Attitude) , data = scale_scores), type = "III"))
paste0("MODEL 4: Villain_G_mean ~ Heroism * Cond + scale(Attitude)")
## [1] "MODEL 4: Villain_G_mean ~ Heroism * Cond + scale(Attitude)"
 (Anova(mod4 <-lm(Villain_G_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores), type = "III"))
paste0("Model Comparison: assessing the importance of attitude - not accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - not accounting for occupations"
anova(mod1, mod3)
paste0("Model Comparison: assessing the importance of attitude - accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - accounting for occupations"
anova(mod2, mod4)
ggplot(scale_scores, aes(y = Villain_G_mean, x = Heroism)) +
  # 1) points colored by Cond
  geom_point(aes(color = Cond),
             size = 2.7, alpha = 0.7) +
  
  # 2) ONE global lm line (group = 1 prevents one line per Cond)
  stat_smooth(method = "lm", se = TRUE,
              aes(group = 1),
              color = "black", linewidth = 1) +
  
  # Nice, accessible palette (works well with >3 groups)
  scale_color_brewer(palette = "Set2") +
  
  labs(
    y = "Impunity (G)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Impunity (G), colored by Occupation",
    subtitle = "Points are participants; black line is overall linear fit with 95% CI"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "right",
    panel.grid.minor = element_blank()
  )
## `geom_smooth()` using formula = 'y ~ x'

ggplot(scale_scores, aes(y = Villain_G_mean, x = Heroism, color = Cond)) +
  # points colored by condition
  geom_point(size = 2.7, alpha = 0.7) +
  # ONE lm line PER condition (because color is mapped here)
  stat_smooth(method = "lm", se = FALSE, linewidth = 1, fullrange = TRUE) +
  scale_color_brewer(palette = "Set2") +
  labs(
    y = "Impunity (G) ",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Impunity (G) with per-Occupation linear fits"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "right", panel.grid.minor = element_blank())
## `geom_smooth()` using formula = 'y ~ x'

Model 1: “~Heroism + Occupation”

# =========================
# OLD MODELS (Heroism)
# =========================
m1_H <- lm(Villain_G_mean ~ Heroism + Occupation, data = scale_scores)
m2_H <- lm(Villain_G_mean ~ Heroism * Occupation, data = scale_scores)
m3_H <- lm(Villain_G_mean ~ Heroism + Occupation + Attitude, data = scale_scores)
m4_H <- lm(Villain_G_mean ~ Heroism * Occupation + Attitude, data = scale_scores)

tidy_type3(m1_H, "~Heroism + Cond")
~Heroism + Cond
F p eta2p
Heroism 58.93 < .001 0.125
Occupation 3.10 = 0.046 0.015
# =========================
# NEW MODELS (Danger & Help)
# =========================

Model 2: “~Heroism * Occupation”

tidy_type3(m2_H, "~Heroism * Cond")
~Heroism * Cond
F p eta2p
Heroism 52.88 < .001 0.114
Occupation 6.10 = 0.002 0.029
Heroism:Occupation 5.25 = 0.006 0.025

Model 3: “~Heroism + Occupation + Attitude”

tidy_type3(m3_H, "~Heroism + Occupation + Attitude")
~Heroism + Occupation + Attitude
F p eta2p
Heroism 8.80 = 0.003 0.021
Occupation 3.13 = 0.045 0.015
Attitude 10.68 = 0.001 0.025

Model 4: “~Heroism * Occupation + Attitude”

tidy_type3(m4_H, "~Heroism * Occupation + Attitude")
~Heroism * Occupation + Attitude
F p eta2p
Heroism 10.15 = 0.002 0.024
Occupation 3.67 = 0.026 0.018
Attitude 7.23 = 0.007 0.017
Heroism:Occupation 3.54 = 0.030 0.017

Comparison of main predictors across models

# Use orthogonal contrasts; keeps your "Heroism main effect" interpretable with Cond in the model
old_contr <- options(contrasts = c("contr.sum", "contr.poly"))

# Helper to extract the main effect of "Heroism" from a car::Anova table
extract_heroism <- function(mod, model_label) {
  a <- car::Anova(mod, type = "III")

  # Coerce to data.frame with rownames preserved
  tab <- as.data.frame(a)
  tab$Term <- rownames(tab)

  # Standard column names across R versions
  # (car::Anova uses these names for lm/glm type=III)
  # Columns: "Sum Sq", "Df", "F value", "Pr(>F)"
  names(tab) <- sub(" ", "_", names(tab))  # make names safe: "Sum_Sq", "F_value", etc.

  # Pull rows we need
  hero <- tab %>% filter(Term == "Heroism")
  resid <- tab %>% filter(Term == "Residuals")

  # Safety checks (in case of name variants)
  stopifnot(nrow(hero) == 1, nrow(resid) == 1)

  # Partial eta^2 = SS_effect / (SS_effect + SS_residual)
  eta_p2 <- hero$Sum_Sq / (hero$Sum_Sq + resid$Sum_Sq)

  # Format p nicely (APA-ish)
  p_fmt <- ifelse(hero$`Pr(>F)` < .001, "< .001",
                  sprintf("= %.3f", hero$`Pr(>F)`))

  dplyr::tibble(
    Model = model_label,
    F = hero$F_value,
    p = p_fmt,
    eta2p = eta_p2
  )
}

# ----- Fit the four models -----
mod1 <- lm(Villain_G_mean ~ Heroism + Cond, data = scale_scores)
mod2 <- lm(Villain_G_mean ~ Heroism * Cond, data = scale_scores)
mod3 <- lm(Villain_G_mean ~ Heroism + Cond + scale(Attitude), data = scale_scores)
mod4 <- lm(Villain_G_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores)

# ----- Build the summary table -----
tbl <- bind_rows(
  extract_heroism(mod1, "~ Heroism + Cond"),
  extract_heroism(mod2, "~ Heroism * Occupation"),
  extract_heroism(mod3, "~ Heroism + Cond + Attitude"),
  extract_heroism(mod4, "~ Heroism * Occupation + Attitude")
  ) %>%
  # Nice number formatting for F and eta^2p
  mutate(
    F = round(F, 2),
    eta2p = round(eta2p, 3)
  )

# ----- Print as HTML-friendly table -----
# Build the table first
tbl_kbl <- tbl %>%
  kable("html",
        caption = "Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²",
        align = "lllrrrcr")
# Add a footnote in a version-robust way
if ("footnote" %in% getNamespaceExports("kableExtra")) {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::footnote(
      general = "Type-III sums of squares with sum contrasts.",
      general_title = ""
    )
} else {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::add_footnote(
      general = "Type-III sums of squares with sum contrasts.",
      notation = "none"
    )
}

tbl_kbl
Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²
Model F p eta2p
~ Heroism + Cond 58.93 < .001 0.125
~ Heroism * Occupation 52.88 < .001 0.114
~ Heroism + Cond + Attitude 8.80 = 0.003 0.021
~ Heroism * Occupation + Attitude 10.15 = 0.002 0.024
Type-III sums of squares with sum contrasts.

Specific level

UK journalists must follow strict codes of ethics, such as IPSO rules and their employers’ policies. These ban phone hacking or wiretapping except in rare, extreme cases. The rules stress respect for privacy, following the law, and avoiding unjustified intrusion, even for the public interest. A national journalist suspects a top official of abusing their position by steering public contracts to companies owned by close associates, raising concerns of corruption and misuse of public money. With no evidence and official investigations blocked, the journalist believes normal ing won’t work fast enough. They secretly tap the phones of the official, his wife, and two daughters for several weeks, hoping to find proof.

Toggle details of the models diagnostics and outlier analyses

Below, you’ll find model diagnostics (QQ plot, fitted vs residuals, linearity), Outliers analysis, and more details on the output

[NOTE THAT EFFECT SIZES ED IN THE COMMANDS BELOW SHOULD NOT BE TRUSTED]

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 1: DV ~ Heroism + Occupation")
## [1] "Diagnostics for Model 1: DV ~ Heroism + Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Villain_S_mean ~ Heroism  + Cond, data = scale_scores)
mod1r <- lmrob(Villain_S_mean ~ Heroism  + Cond, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.443 3.342
(0.276) (0.328)
12.486 10.180
p = <0.001 p = <0.001
Heroism 0.330 0.381
(0.051) (0.059)
6.455 6.482
p = <0.001 p = <0.001
Cond1 −0.641 −0.653
(0.112) (0.146)
−5.701 −4.462
p = <0.001 p = <0.001
Cond2 0.355 0.319
(0.111) (0.106)
3.204 3.012
p = 0.001 p = 0.003
Num.Obs. 417 417
R2 0.131 0.172
R2 Adj. 0.125 0.166
RMSE 1.59 1.60
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Villain_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 2: DV ~ Heroism * Occupation")
## [1] "Diagnostics for Model 2: DV ~ Heroism * Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Villain_S_mean ~ Heroism  * Cond, data = scale_scores)
mod1r <- lmrob(Villain_S_mean ~ Heroism  * Cond, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.478 3.405
(0.280) (0.338)
12.441 10.074
p = <0.001 p = <0.001
Heroism 0.325 0.370
(0.052) (0.060)
6.223 6.200
p = <0.001 p = <0.001
Cond1 −0.346 −0.464
(0.402) (0.555)
−0.861 −0.835
p = 0.390 p = 0.404
Cond2 −0.033 −0.188
(0.376) (0.433)
−0.089 −0.434
p = 0.929 p = 0.665
Heroism × Cond1 −0.053 −0.032
(0.072) (0.094)
−0.745 −0.338
p = 0.457 p = 0.736
Heroism × Cond2 0.075 0.097
(0.070) (0.074)
1.064 1.308
p = 0.288 p = 0.192
Num.Obs. 417 417
R2 0.134 0.177
R2 Adj. 0.123 0.167
RMSE 1.58 1.60
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Villain_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude")
## [1] "Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Villain_S_mean ~ Heroism  + Cond + Attitude, data = scale_scores)
mod1r <- lmrob(Villain_S_mean ~ Heroism  + Cond + Attitude, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 2.499 2.485
(0.388) (0.385)
6.439 6.459
p = <0.001 p = <0.001
Heroism 0.148 0.206
(0.073) (0.082)
2.016 2.509
p = 0.044 p = 0.012
Cond1 −0.714 −0.706
(0.113) (0.143)
−6.314 −4.931
p = <0.001 p = <0.001
Cond2 0.429 0.390
(0.111) (0.106)
3.849 3.673
p = <0.001 p = <0.001
Attitude 0.325 0.303
(0.095) (0.098)
3.414 3.100
p = <0.001 p = 0.002
Num.Obs. 417 417
R2 0.155 0.192
R2 Adj. 0.147 0.184
RMSE 1.56 1.57
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Villain_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude")
## [1] "Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Villain_S_mean ~ Heroism*Cond + Attitude, data = scale_scores)
mod1r <- lmrob(Villain_S_mean ~ Heroism*Cond + Attitude, data = scale_scores)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 2.525 2.557
(0.402) (0.424)
6.276 6.029
p = <0.001 p = <0.001
Heroism 0.152 0.209
(0.074) (0.083)
2.058 2.518
p = 0.040 p = 0.012
Cond1 −0.508 −0.633
(0.400) (0.491)
−1.271 −1.288
p = 0.205 p = 0.198
Cond2 0.290 0.180
(0.385) (0.420)
0.753 0.428
p = 0.452 p = 0.669
Attitude 0.318 0.288
(0.097) (0.105)
3.259 2.747
p = 0.001 p = 0.006
Heroism × Cond1 −0.038 −0.012
(0.071) (0.084)
−0.529 −0.142
p = 0.597 p = 0.887
Heroism × Cond2 0.026 0.040
(0.071) (0.072)
0.360 0.553
p = 0.719 p = 0.581
Num.Obs. 417 417
R2 0.156 0.193
R2 Adj. 0.143 0.181
RMSE 1.56 1.57
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores$Villain_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0(" S for each model (please ignore the eta squares, they are way off")
## [1] " S for each model (please ignore the eta squares, they are way off"
paste0("####################################################")
## [1] "####################################################"
paste0("MODEL 1: Villain_S_mean ~ Heroism")
## [1] "MODEL 1: Villain_S_mean ~ Heroism"
 (Anova(mod1 <- lm(Villain_S_mean ~ Heroism  + Cond, data = scale_scores), type = "III"))
paste0("MODEL 2: Villain_S_mean ~ Heroism * Cond")
## [1] "MODEL 2: Villain_S_mean ~ Heroism * Cond"
 (Anova(mod2 <- lm(Villain_S_mean ~ Heroism * Cond, data = scale_scores), type = "III"))
paste0("MODEL 3: Villain_S_mean ~ Heroism +scale(Attitude)")
## [1] "MODEL 3: Villain_S_mean ~ Heroism +scale(Attitude)"
 (Anova(mod3 <- lm(Villain_S_mean ~ Heroism   + Cond +scale(Attitude) , data = scale_scores), type = "III"))
paste0("MODEL 4: Villain_S_mean ~ Heroism * Cond + scale(Attitude)")
## [1] "MODEL 4: Villain_S_mean ~ Heroism * Cond + scale(Attitude)"
 (Anova(mod4 <-lm(Villain_S_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores), type = "III"))
paste0("Model Comparison: assessing the importance of attitude - not accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - not accounting for occupations"
anova(mod1, mod3)
paste0("Model Comparison: assessing the importance of attitude - accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - accounting for occupations"
anova(mod2, mod4)
ggplot(scale_scores, aes(y = Villain_S_mean, x = Heroism)) +
  # 1) points colored by Cond
  geom_point(aes(color = Cond),
             size = 2.7, alpha = 0.7) +
  
  # 2) ONE global lm line (group = 1 prevents one line per Cond)
  stat_smooth(method = "lm", se = TRUE,
              aes(group = 1),
              color = "black", linewidth = 1) +
  
  # Nice, accessible palette (works well with >3 groups)
  scale_color_brewer(palette = "Set2") +
  
  labs(
    y = "Impunity (S)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Impunity (S), colored by Occupation",
    subtitle = "Points are participants; black line is overall linear fit with 95% CI"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "right",
    panel.grid.minor = element_blank()
  )
## `geom_smooth()` using formula = 'y ~ x'

ggplot(scale_scores, aes(y = Villain_S_mean, x = Heroism, color = Cond)) +
  # points colored by condition
  geom_point(size = 2.7, alpha = 0.7) +
  # ONE lm line PER condition (because color is mapped here)
  stat_smooth(method = "lm", se = FALSE, linewidth = 1, fullrange = TRUE) +
  scale_color_brewer(palette = "Set2") +
  labs(
    y = "Impunity (S) ",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Impunity (S) with per-Occupation linear fits"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "right", panel.grid.minor = element_blank())
## `geom_smooth()` using formula = 'y ~ x'

==> Full support for our hypotheses. Across all occupations Heroism predict general- and specific-level gratitude, with or without controlling for attitude.

Model 1: “~Heroism + Occupation”

# =========================
# OLD MODELS (Heroism)
# =========================
m1_H <- lm(Villain_S_mean ~ Heroism + Occupation, data = scale_scores)
m2_H <- lm(Villain_S_mean ~ Heroism * Occupation, data = scale_scores)
m3_H <- lm(Villain_S_mean ~ Heroism + Occupation + Attitude, data = scale_scores)
m4_H <- lm(Villain_S_mean ~ Heroism * Occupation + Attitude, data = scale_scores)

tidy_type3(m1_H, "~Heroism + Cond")
~Heroism + Cond
F p eta2p
Heroism 41.67 < .001 0.092
Occupation 16.35 < .001 0.073
# =========================
# NEW MODELS (Danger & Help)
# =========================

Model 2: “~Heroism * Occupation”

tidy_type3(m2_H, "~Heroism * Cond")
~Heroism * Cond
F p eta2p
Heroism 38.72 < .001 0.086
Occupation 0.51 = 0.599 0.002
Heroism:Occupation 0.63 = 0.531 0.003

Model 3: “~Heroism + Occupation + Attitude”

tidy_type3(m3_H, "~Heroism + Occupation + Attitude")
~Heroism + Occupation + Attitude
F p eta2p
Heroism 4.06 = 0.044 0.010
Occupation 20.18 < .001 0.089
Attitude 11.66 < .001 0.028

Model 4: “~Heroism * Occupation + Attitude”

tidy_type3(m4_H, "~Heroism * Occupation + Attitude")
~Heroism * Occupation + Attitude
F p eta2p
Heroism 4.24 = 0.040 0.010
Occupation 0.82 = 0.439 0.004
Attitude 10.62 = 0.001 0.025
Heroism:Occupation 0.15 = 0.857 0.001

Comparison of main predictors across models

# Use orthogonal contrasts; keeps your "Heroism main effect" interpretable with Cond in the model
old_contr <- options(contrasts = c("contr.sum", "contr.poly"))

# Helper to extract the main effect of "Heroism" from a car::Anova table
extract_heroism <- function(mod, model_label) {
  a <- car::Anova(mod, type = "III")

  # Coerce to data.frame with rownames preserved
  tab <- as.data.frame(a)
  tab$Term <- rownames(tab)

  # Standard column names across R versions
  # (car::Anova uses these names for lm/glm type=III)
  # Columns: "Sum Sq", "Df", "F value", "Pr(>F)"
  names(tab) <- sub(" ", "_", names(tab))  # make names safe: "Sum_Sq", "F_value", etc.

  # Pull rows we need
  hero <- tab %>% filter(Term == "Heroism")
  resid <- tab %>% filter(Term == "Residuals")

  # Safety checks (in case of name variants)
  stopifnot(nrow(hero) == 1, nrow(resid) == 1)

  # Partial eta^2 = SS_effect / (SS_effect + SS_residual)
  eta_p2 <- hero$Sum_Sq / (hero$Sum_Sq + resid$Sum_Sq)

  # Format p nicely (APA-ish)
  p_fmt <- ifelse(hero$`Pr(>F)` < .001, "< .001",
                  sprintf("= %.3f", hero$`Pr(>F)`))

  dplyr::tibble(
    Model = model_label,
    F = hero$F_value,
    p = p_fmt,
    eta2p = eta_p2
  )
}

# ----- Fit the four models -----
mod1 <- lm(Villain_S_mean ~ Heroism + Cond, data = scale_scores)
mod2 <- lm(Villain_S_mean ~ Heroism * Cond, data = scale_scores)
mod3 <- lm(Villain_S_mean ~ Heroism + Cond + scale(Attitude), data = scale_scores)
mod4 <- lm(Villain_S_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores)

# ----- Build the summary table -----
tbl <- bind_rows(
  extract_heroism(mod1, "~ Heroism + Cond"),
  extract_heroism(mod2, "~ Heroism * Occupation"),
  extract_heroism(mod3, "~ Heroism + Cond + Attitude"),
  extract_heroism(mod4, "~ Heroism * Occupation + Attitude")
  ) %>%
  # Nice number formatting for F and eta^2p
  mutate(
    F = round(F, 2),
    eta2p = round(eta2p, 3)
  )

# ----- Print as HTML-friendly table -----
# Build the table first
tbl_kbl <- tbl %>%
  kable("html",
        caption = "Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²",
        align = "lllrrrcr")
# Add a footnote in a version-robust way
if ("footnote" %in% getNamespaceExports("kableExtra")) {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::footnote(
      general = "Type-III sums of squares with sum contrasts.",
      general_title = ""
    )
} else {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::add_footnote(
      general = "Type-III sums of squares with sum contrasts.",
      notation = "none"
    )
}

tbl_kbl
Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²
Model F p eta2p
~ Heroism + Cond 41.67 < .001 0.092
~ Heroism * Occupation 38.72 < .001 0.086
~ Heroism + Cond + Attitude 4.06 = 0.044 0.010
~ Heroism * Occupation + Attitude 4.24 = 0.040 0.010
Type-III sums of squares with sum contrasts.

NOVEMBER DF

General level

Journalists should be given more freedom in the way they do their work

Toggle details of the models diagnostics and outlier analyses

Below, you’ll find model diagnostics (QQ plot, fitted vs residuals, linearity), Outliers analysis, and more details on the output

[NOTE THAT EFFECT SIZES ED IN THE COMMANDS BELOW SHOULD NOT BE TRUSTED]

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 1: DV ~ Heroism + Occupation")
## [1] "Diagnostics for Model 1: DV ~ Heroism + Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Villain_G_mean ~ Heroism  + Cond, data = scale_scores2)
mod1r <- lmrob(Villain_G_mean ~ Heroism  + Cond, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 2.088 2.000
(0.213) (0.212)
9.788 9.450
p = <0.001 p = <0.001
Heroism 0.304 0.321
(0.041) (0.043)
7.500 7.388
p = <0.001 p = <0.001
Cond1 0.038 0.019
(0.092) (0.097)
0.410 0.202
p = 0.682 p = 0.840
Cond2 −0.351 −0.365
(0.089) (0.093)
−3.928 −3.904
p = <0.001 p = <0.001
Num.Obs. 421 421
R2 0.152 0.164
R2 Adj. 0.146 0.158
RMSE 1.29 1.29
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Villain_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 2: DV ~ Heroism * Occupation")
## [1] "Diagnostics for Model 2: DV ~ Heroism * Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Villain_G_mean ~ Heroism  * Cond, data = scale_scores2)
mod1r <- lmrob(Villain_G_mean ~ Heroism  * Cond, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 2.152 2.061
(0.219) (0.213)
9.844 9.689
p = <0.001 p = <0.001
Heroism 0.291 0.308
(0.041) (0.044)
7.012 6.996
p = <0.001 p = <0.001
Cond1 0.120 0.072
(0.334) (0.315)
0.358 0.228
p = 0.720 p = 0.820
Cond2 −0.776 −0.715
(0.286) (0.271)
−2.710 −2.641
p = 0.007 p = 0.009
Heroism × Cond1 −0.012 −0.007
(0.060) (0.060)
−0.206 −0.121
p = 0.837 p = 0.904
Heroism × Cond2 0.087 0.074
(0.054) (0.057)
1.603 1.289
p = 0.110 p = 0.198
Num.Obs. 421 421
R2 0.158 0.166
R2 Adj. 0.148 0.156
RMSE 1.28 1.28
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Villain_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude")
## [1] "Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Villain_G_mean ~ Heroism  + Cond + Attitude, data = scale_scores2)
mod1r <- lmrob(Villain_G_mean ~ Heroism  + Cond + Attitude, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 1.807 1.826
(0.314) (0.328)
5.762 5.574
p = <0.001 p = <0.001
Heroism 0.256 0.290
(0.057) (0.061)
4.530 4.783
p = <0.001 p = <0.001
Cond1 −0.002 −0.006
(0.097) (0.105)
−0.016 −0.056
p = 0.987 p = 0.955
Cond2 −0.316 −0.343
(0.094) (0.099)
−3.375 −3.451
p = <0.001 p = <0.001
Attitude 0.091 0.058
(0.075) (0.082)
1.225 0.706
p = 0.221 p = 0.481
Num.Obs. 421 421
R2 0.155 0.165
R2 Adj. 0.147 0.157
RMSE 1.28 1.29
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Villain_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude")
## [1] "Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Villain_G_mean ~ Heroism*Cond + Attitude, data = scale_scores2)
mod1r <- lmrob(Villain_G_mean ~ Heroism*Cond + Attitude, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 1.946 1.959
(0.339) (0.355)
5.744 5.511
p = <0.001 p = <0.001
Heroism 0.260 0.292
(0.057) (0.061)
4.576 4.815
p = <0.001 p = <0.001
Cond1 0.039 0.031
(0.349) (0.338)
0.111 0.093
p = 0.911 p = 0.926
Cond2 −0.680 −0.669
(0.311) (0.302)
−2.186 −2.216
p = 0.029 p = 0.027
Attitude 0.063 0.032
(0.079) (0.087)
0.798 0.367
p = 0.425 p = 0.713
Heroism × Cond1 −0.003 −0.002
(0.062) (0.062)
−0.044 −0.039
p = 0.965 p = 0.969
Heroism × Cond2 0.073 0.067
(0.057) (0.061)
1.271 1.097
p = 0.204 p = 0.273
Num.Obs. 421 421
R2 0.159 0.166
R2 Adj. 0.147 0.154
RMSE 1.28 1.28
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Villain_G_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0(" S for each model (please ignore the eta squares, they are way off")
## [1] " S for each model (please ignore the eta squares, they are way off"
paste0("####################################################")
## [1] "####################################################"
paste0("MODEL 1: Villain_G_mean ~ Heroism")
## [1] "MODEL 1: Villain_G_mean ~ Heroism"
 (Anova(mod1 <- lm(Villain_G_mean ~ Heroism  + Cond, data = scale_scores2), type = "III"))
paste0("MODEL 2: Villain_G_mean ~ Heroism * Cond")
## [1] "MODEL 2: Villain_G_mean ~ Heroism * Cond"
 (Anova(mod2 <- lm(Villain_G_mean ~ Heroism * Cond, data = scale_scores2), type = "III"))
paste0("MODEL 3: Villain_G_mean ~ Heroism +scale(Attitude)")
## [1] "MODEL 3: Villain_G_mean ~ Heroism +scale(Attitude)"
 (Anova(mod3 <- lm(Villain_G_mean ~ Heroism   + Cond +scale(Attitude) , data = scale_scores2), type = "III"))
paste0("MODEL 4: Villain_G_mean ~ Heroism * Cond + scale(Attitude)")
## [1] "MODEL 4: Villain_G_mean ~ Heroism * Cond + scale(Attitude)"
 (Anova(mod4 <-lm(Villain_G_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores2), type = "III"))
paste0("Model Comparison: assessing the importance of attitude - not accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - not accounting for occupations"
anova(mod1, mod3)
paste0("Model Comparison: assessing the importance of attitude - accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - accounting for occupations"
anova(mod2, mod4)
ggplot(scale_scores2, aes(y = Villain_G_mean, x = Heroism)) +
  # 1) points colored by Cond
  geom_point(aes(color = Cond),
             size = 2.7, alpha = 0.7) +
  
  # 2) ONE global lm line (group = 1 prevents one line per Cond)
  stat_smooth(method = "lm", se = TRUE,
              aes(group = 1),
              color = "black", linewidth = 1) +
  
  # Nice, accessible palette (works well with >3 groups)
  scale_color_brewer(palette = "Set2") +
  
  labs(
    y = "Impunity (G)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Impunity (G), colored by Occupation",
    subtitle = "Points are participants; black line is overall linear fit with 95% CI"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "right",
    panel.grid.minor = element_blank()
  )
## `geom_smooth()` using formula = 'y ~ x'

ggplot(scale_scores2, aes(y = Villain_G_mean, x = Heroism, color = Cond)) +
  # points colored by condition
  geom_point(size = 2.7, alpha = 0.7) +
  # ONE lm line PER condition (because color is mapped here)
  stat_smooth(method = "lm", se = FALSE, linewidth = 1, fullrange = TRUE) +
  scale_color_brewer(palette = "Set2") +
  labs(
    y = "Impunity (G) ",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Impunity (G) with per-Occupation linear fits"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "right", panel.grid.minor = element_blank())
## `geom_smooth()` using formula = 'y ~ x'

Model 1: “~Heroism + Occupation”

# =========================
# OLD MODELS (Heroism)
# =========================
m1_H <- lm(Villain_G_mean ~ Heroism + Occupation, data = scale_scores2)
m2_H <- lm(Villain_G_mean ~ Heroism * Occupation, data = scale_scores2)
m3_H <- lm(Villain_G_mean ~ Heroism + Occupation + Attitude, data = scale_scores2)
m4_H <- lm(Villain_G_mean ~ Heroism * Occupation + Attitude, data = scale_scores2)

tidy_type3(m1_H, "~Heroism + Cond")
~Heroism + Cond
F p eta2p
Heroism 56.25 < .001 0.119
Occupation 9.25 < .001 0.042
# =========================
# NEW MODELS (Danger & Help)
# =========================

Model 2: “~Heroism * Occupation”

tidy_type3(m2_H, "~Heroism * Cond")
~Heroism * Cond
F p eta2p
Heroism 49.17 < .001 0.106
Occupation 4.46 = 0.012 0.021
Heroism:Occupation 1.45 = 0.236 0.007

Model 3: “~Heroism + Occupation + Attitude”

tidy_type3(m3_H, "~Heroism + Occupation + Attitude")
~Heroism + Occupation + Attitude
F p eta2p
Heroism 20.52 < .001 0.047
Occupation 8.13 < .001 0.038
Attitude 1.50 = 0.221 0.004

Model 4: “~Heroism * Occupation + Attitude”

tidy_type3(m4_H, "~Heroism * Occupation + Attitude")
~Heroism * Occupation + Attitude
F p eta2p
Heroism 20.94 < .001 0.048
Occupation 3.38 = 0.035 0.016
Attitude 0.64 = 0.425 0.002
Heroism:Occupation 1.01 = 0.364 0.005

Comparison of main predictors across models

# Use orthogonal contrasts; keeps your "Heroism main effect" interpretable with Cond in the model
old_contr <- options(contrasts = c("contr.sum", "contr.poly"))

# Helper to extract the main effect of "Heroism" from a car::Anova table
extract_heroism <- function(mod, model_label) {
  a <- car::Anova(mod, type = "III")

  # Coerce to data.frame with rownames preserved
  tab <- as.data.frame(a)
  tab$Term <- rownames(tab)

  # Standard column names across R versions
  # (car::Anova uses these names for lm/glm type=III)
  # Columns: "Sum Sq", "Df", "F value", "Pr(>F)"
  names(tab) <- sub(" ", "_", names(tab))  # make names safe: "Sum_Sq", "F_value", etc.

  # Pull rows we need
  hero <- tab %>% filter(Term == "Heroism")
  resid <- tab %>% filter(Term == "Residuals")

  # Safety checks (in case of name variants)
  stopifnot(nrow(hero) == 1, nrow(resid) == 1)

  # Partial eta^2 = SS_effect / (SS_effect + SS_residual)
  eta_p2 <- hero$Sum_Sq / (hero$Sum_Sq + resid$Sum_Sq)

  # Format p nicely (APA-ish)
  p_fmt <- ifelse(hero$`Pr(>F)` < .001, "< .001",
                  sprintf("= %.3f", hero$`Pr(>F)`))

    dplyr::tibble(
    Model = model_label,
    F = hero$F_value,
    p = p_fmt,
    eta2p = eta_p2
  )
}

# ----- Fit the four models -----
mod1 <- lm(Villain_G_mean ~ Heroism + Cond, data = scale_scores2)
mod2 <- lm(Villain_G_mean ~ Heroism * Cond, data = scale_scores2)
mod3 <- lm(Villain_G_mean ~ Heroism + Cond + scale(Attitude), data = scale_scores2)
mod4 <- lm(Villain_G_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores2)

# ----- Build the summary table -----
tbl <- bind_rows(
  extract_heroism(mod1, "~ Heroism + Cond"),
  extract_heroism(mod2, "~ Heroism * Occupation"),
  extract_heroism(mod3, "~ Heroism + Cond + Attitude"),
  extract_heroism(mod4, "~ Heroism * Occupation + Attitude")
  ) %>%
  # Nice number formatting for F and eta^2p
  mutate(
    F = round(F, 2),
    eta2p = round(eta2p, 3)
  )

# ----- Print as HTML-friendly table -----
# Build the table first
tbl_kbl <- tbl %>%
  kable("html",
        caption = "Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²",
        align = "lllrrrcr")
# Add a footnote in a version-robust way
if ("footnote" %in% getNamespaceExports("kableExtra")) {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::footnote(
      general = "Type-III sums of squares with sum contrasts.",
      general_title = ""
    )
} else {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::add_footnote(
      general = "Type-III sums of squares with sum contrasts.",
      notation = "none"
    )
}

tbl_kbl
Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²
Model F p eta2p
~ Heroism + Cond 56.25 < .001 0.119
~ Heroism * Occupation 49.17 < .001 0.106
~ Heroism + Cond + Attitude 20.52 < .001 0.047
~ Heroism * Occupation + Attitude 20.94 < .001 0.048
Type-III sums of squares with sum contrasts.

Specific level

UK journalists must follow strict codes of ethics, such as IPSO rules and their employers’ policies. These ban phone hacking or wiretapping except in rare, extreme cases. The rules stress respect for privacy, following the law, and avoiding unjustified intrusion, even for the public interest. A national journalist suspects a top official of abusing their position by steering public contracts to companies owned by close associates, raising concerns of corruption and misuse of public money. With no evidence and official investigations blocked, the journalist believes normal ing won’t work fast enough. They secretly tap the phones of the official, his wife, and two daughters for several weeks, hoping to find proof.

Toggle details of the models diagnostics and outlier analyses

Below, you’ll find model diagnostics (QQ plot, fitted vs residuals, linearity), Outliers analysis, and more details on the output

[NOTE THAT EFFECT SIZES ED IN THE COMMANDS BELOW SHOULD NOT BE TRUSTED]

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 1: DV ~ Heroism + Occupation")
## [1] "Diagnostics for Model 1: DV ~ Heroism + Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Villain_S_mean ~ Heroism  + Cond, data = scale_scores2)
mod1r <- lmrob(Villain_S_mean ~ Heroism  + Cond, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.310 3.236
(0.259) (0.304)
12.781 10.638
p = <0.001 p = <0.001
Heroism 0.338 0.366
(0.049) (0.054)
6.851 6.729
p = <0.001 p = <0.001
Cond1 −0.627 −0.655
(0.111) (0.127)
−5.641 −5.164
p = <0.001 p = <0.001
Cond2 0.119 0.093
(0.108) (0.107)
1.100 0.877
p = 0.272 p = 0.381
Num.Obs. 421 421
R2 0.137 0.152
R2 Adj. 0.131 0.146
RMSE 1.56 1.56
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Villain_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 2: DV ~ Heroism * Occupation")
## [1] "Diagnostics for Model 2: DV ~ Heroism * Occupation"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Villain_S_mean ~ Heroism  * Cond, data = scale_scores2)
mod1r <- lmrob(Villain_S_mean ~ Heroism  * Cond, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 3.474 3.430
(0.264) (0.325)
13.171 10.549
p = <0.001 p = <0.001
Heroism 0.312 0.335
(0.050) (0.057)
6.251 5.865
p = <0.001 p = <0.001
Cond1 0.309 0.273
(0.403) (0.542)
0.765 0.504
p = 0.444 p = 0.615
Cond2 −0.714 −0.756
(0.345) (0.408)
−2.066 −1.852
p = 0.039 p = 0.065
Heroism × Cond1 −0.173 −0.169
(0.073) (0.093)
−2.367 −1.819
p = 0.018 p = 0.070
Heroism × Cond2 0.161 0.162
(0.066) (0.072)
2.451 2.258
p = 0.015 p = 0.024
Num.Obs. 421 421
R2 0.153 0.164
R2 Adj. 0.143 0.154
RMSE 1.55 1.55
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Villain_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude")
## [1] "Diagnostics for Model 3: DV ~ Heroism + Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Villain_S_mean ~ Heroism  + Cond + Attitude, data = scale_scores2)
mod1r <- lmrob(Villain_S_mean ~ Heroism  + Cond + Attitude, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 2.573 2.356
(0.378)
6.805
p = <0.001
Heroism 0.211 0.189
(0.068)
3.098
p = 0.002
Cond1 −0.729 −1.131
(0.117)
−6.241
p = <0.001
Cond2 0.210 0.419
(0.113)
1.862
p = 0.063
Attitude 0.239 0.363
(0.090)
2.658
p = 0.008
Num.Obs. 421 421
R2 0.151 0.512
R2 Adj. 0.143 0.508
RMSE 1.55 1.62
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Villain_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0("Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude")
## [1] "Diagnostics for Model 4: DV ~ Heroism * Occupation + Attitude"
paste0("####################################################")
## [1] "####################################################"
mod1 <- lm(Villain_S_mean ~ Heroism*Cond + Attitude, data = scale_scores2)
mod1r <- lmrob(Villain_S_mean ~ Heroism*Cond + Attitude, data = scale_scores2)
plot(mod1)

ols_plot_cooksd_bar(mod1, type = 1)

paste0("Comparison with Robust model")
## [1] "Comparison with Robust model"
models <- list("OLS (lm)" = mod1, "Robust (lmrob)" = mod1r)
modelsummary(
  models,
  statistic = c("({std.error})", "{statistic}", "p = {p.value}"),
  gof_omit  = "IC|Log.Lik",   # robust AIC comparability is iffy; omit by default
  output    = "html"
)
OLS (lm) Robust (lmrob)
(Intercept) 2.879 2.716
(0.407) (0.427)
7.070 6.365
p = <0.001 p = <0.001
Heroism 0.223 0.220
(0.068) (0.083)
3.275 2.663
p = 0.001 p = 0.008
Cond1 0.075 0.035
(0.420) (0.530)
0.180 0.067
p = 0.858 p = 0.947
Cond2 −0.436 −0.458
(0.374) (0.392)
−1.167 −1.168
p = 0.244 p = 0.243
Attitude 0.181 0.225
(0.095) (0.104)
1.914 2.170
p = 0.056 p = 0.031
Heroism × Cond1 −0.144 −0.142
(0.074) (0.091)
−1.948 −1.562
p = 0.052 p = 0.119
Heroism × Cond2 0.120 0.118
(0.069) (0.069)
1.735 1.720
p = 0.083 p = 0.086
Num.Obs. 421 421
R2 0.161 0.176
R2 Adj. 0.148 0.164
RMSE 1.54 1.54
fitted_vals <- fitted(mod1)

# Plot observed values against fitted values
plot(fitted_vals, scale_scores2$Villain_S_mean,
     xlab = "Fitted Values",
     ylab = "Observed DV",
     main = "Observed vs Fitted Values")
abline(0, 1, col = "blue", lty = 2)

paste0("####################################################")
## [1] "####################################################"
paste0(" S for each model (please ignore the eta squares, they are way off")
## [1] " S for each model (please ignore the eta squares, they are way off"
paste0("####################################################")
## [1] "####################################################"
paste0("MODEL 1: Villain_S_mean ~ Heroism")
## [1] "MODEL 1: Villain_S_mean ~ Heroism"
 (Anova(mod1 <- lm(Villain_S_mean ~ Heroism  + Cond, data = scale_scores2), type = "III"))
paste0("MODEL 2: Villain_S_mean ~ Heroism * Cond")
## [1] "MODEL 2: Villain_S_mean ~ Heroism * Cond"
 (Anova(mod2 <- lm(Villain_S_mean ~ Heroism * Cond, data = scale_scores2), type = "III"))
paste0("MODEL 3: Villain_S_mean ~ Heroism +scale(Attitude)")
## [1] "MODEL 3: Villain_S_mean ~ Heroism +scale(Attitude)"
 (Anova(mod3 <- lm(Villain_S_mean ~ Heroism   + Cond +scale(Attitude) , data = scale_scores2), type = "III"))
paste0("MODEL 4: Villain_S_mean ~ Heroism * Cond + scale(Attitude)")
## [1] "MODEL 4: Villain_S_mean ~ Heroism * Cond + scale(Attitude)"
 (Anova(mod4 <-lm(Villain_S_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores2), type = "III"))
paste0("Model Comparison: assessing the importance of attitude - not accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - not accounting for occupations"
anova(mod1, mod3)
paste0("Model Comparison: assessing the importance of attitude - accounting for occupations")
## [1] "Model Comparison: assessing the importance of attitude - accounting for occupations"
anova(mod2, mod4)
ggplot(scale_scores2, aes(y = Villain_S_mean, x = Heroism)) +
  # 1) points colored by Cond
  geom_point(aes(color = Cond),
             size = 2.7, alpha = 0.7) +
  
  # 2) ONE global lm line (group = 1 prevents one line per Cond)
  stat_smooth(method = "lm", se = TRUE,
              aes(group = 1),
              color = "black", linewidth = 1) +
  
  # Nice, accessible palette (works well with >3 groups)
  scale_color_brewer(palette = "Set2") +
  
  labs(
    y = "Impunity (S)",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Impunity (S), colored by Occupation",
    subtitle = "Points are participants; black line is overall linear fit with 95% CI"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    legend.position = "right",
    panel.grid.minor = element_blank()
  )
## `geom_smooth()` using formula = 'y ~ x'

ggplot(scale_scores2, aes(y = Villain_S_mean, x = Heroism, color = Cond)) +
  # points colored by condition
  geom_point(size = 2.7, alpha = 0.7) +
  # ONE lm line PER condition (because color is mapped here)
  stat_smooth(method = "lm", se = FALSE, linewidth = 1, fullrange = TRUE) +
  scale_color_brewer(palette = "Set2") +
  labs(
    y = "Impunity (S) ",
    x = "Heroism",
    color = "Occupation",
    title = "Heroism vs. Impunity (S) with per-Occupation linear fits"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "right", panel.grid.minor = element_blank())
## `geom_smooth()` using formula = 'y ~ x'

==> Full support for our hypotheses. Across all occupations Heroism predict general- and specific-level gratitude, with or without controlling for attitude.

Model 1: “~Heroism + Occupation”

# =========================
# OLD MODELS (Heroism)
# =========================
m1_H <- lm(Villain_S_mean ~ Heroism + Occupation, data = scale_scores2)
m2_H <- lm(Villain_S_mean ~ Heroism * Occupation, data = scale_scores2)
m3_H <- lm(Villain_S_mean ~ Heroism + Occupation + Attitude, data = scale_scores2)
m4_H <- lm(Villain_S_mean ~ Heroism * Occupation + Attitude, data = scale_scores2)

tidy_type3(m1_H, "~Heroism + Cond")
~Heroism + Cond
F p eta2p
Heroism 46.94 < .001 0.101
Occupation 17.78 < .001 0.079
# =========================
# NEW MODELS (Danger & Help)
# =========================

Model 2: “~Heroism * Occupation”

tidy_type3(m2_H, "~Heroism * Cond")
~Heroism * Cond
F p eta2p
Heroism 39.07 < .001 0.086
Occupation 2.20 = 0.112 0.011
Heroism:Occupation 4.01 = 0.019 0.019

Model 3: “~Heroism + Occupation + Attitude”

tidy_type3(m3_H, "~Heroism + Occupation + Attitude")
~Heroism + Occupation + Attitude
F p eta2p
Heroism 9.60 = 0.002 0.023
Occupation 21.14 < .001 0.092
Attitude 7.07 = 0.008 0.017

Model 4: “~Heroism * Occupation + Attitude”

tidy_type3(m4_H, "~Heroism * Occupation + Attitude")
~Heroism * Occupation + Attitude
F p eta2p
Heroism 10.73 = 0.001 0.025
Occupation 0.86 = 0.423 0.004
Attitude 3.66 = 0.056 0.009
Heroism:Occupation 2.32 = 0.100 0.011

Comparison of main predictors across models

# Use orthogonal contrasts; keeps your "Heroism main effect" interpretable with Cond in the model
old_contr <- options(contrasts = c("contr.sum", "contr.poly"))

# Helper to extract the main effect of "Heroism" from a car::Anova table
extract_heroism <- function(mod, model_label) {
  a <- car::Anova(mod, type = "III")

  # Coerce to data.frame with rownames preserved
  tab <- as.data.frame(a)
  tab$Term <- rownames(tab)

  # Standard column names across R versions
  # (car::Anova uses these names for lm/glm type=III)
  # Columns: "Sum Sq", "Df", "F value", "Pr(>F)"
  names(tab) <- sub(" ", "_", names(tab))  # make names safe: "Sum_Sq", "F_value", etc.

  # Pull rows we need
  hero <- tab %>% filter(Term == "Heroism")
  resid <- tab %>% filter(Term == "Residuals")

  # Safety checks (in case of name variants)
  stopifnot(nrow(hero) == 1, nrow(resid) == 1)

  # Partial eta^2 = SS_effect / (SS_effect + SS_residual)
  eta_p2 <- hero$Sum_Sq / (hero$Sum_Sq + resid$Sum_Sq)

  # Format p nicely (APA-ish)
  p_fmt <- ifelse(hero$`Pr(>F)` < .001, "< .001",
                  sprintf("= %.3f", hero$`Pr(>F)`))

    dplyr::tibble(
    Model = model_label,
    F = hero$F_value,
    p = p_fmt,
    eta2p = eta_p2
  )
}

# ----- Fit the four models -----
mod1 <- lm(Villain_S_mean ~ Heroism + Cond, data = scale_scores2)
mod2 <- lm(Villain_S_mean ~ Heroism * Cond, data = scale_scores2)
mod3 <- lm(Villain_S_mean ~ Heroism + Cond + scale(Attitude), data = scale_scores2)
mod4 <- lm(Villain_S_mean ~ Heroism * Cond + scale(Attitude), data = scale_scores2)

# ----- Build the summary table -----
tbl <- bind_rows(
  extract_heroism(mod1, "~ Heroism + Cond"),
  extract_heroism(mod2, "~ Heroism * Occupation"),
  extract_heroism(mod3, "~ Heroism + Cond + Attitude"),
  extract_heroism(mod4, "~ Heroism * Occupation + Attitude")
  ) %>%
  # Nice number formatting for F and eta^2p
  mutate(
    F = round(F, 2),
    eta2p = round(eta2p, 3)
  )

# ----- Print as HTML-friendly table -----
# Build the table first
tbl_kbl <- tbl %>%
  kable("html",
        caption = "Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²",
        align = "lllrrrcr")
# Add a footnote in a version-robust way
if ("footnote" %in% getNamespaceExports("kableExtra")) {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::footnote(
      general = "Type-III sums of squares with sum contrasts.",
      general_title = ""
    )
} else {
  tbl_kbl <- tbl_kbl %>%
    kableExtra::add_footnote(
      general = "Type-III sums of squares with sum contrasts.",
      notation = "none"
    )
}

tbl_kbl
Main effect of Heroism across models (Type-III ANOVA): F, p, partial η²
Model F p eta2p
~ Heroism + Cond 46.94 < .001 0.101
~ Heroism * Occupation 39.07 < .001 0.086
~ Heroism + Cond + Attitude 9.60 = 0.002 0.023
~ Heroism * Occupation + Attitude 10.73 = 0.001 0.025
Type-III sums of squares with sum contrasts.

Conclusion on the replication

The study was successful in replicating our previous correlations. Some minor differences can be noted, for instance occupation interacted with heroism on both cricitism acceptability in the new sample, but not in the previous one. You can toggle an exploration of this three way interaction below.

Toggle exploration of the 3-way interaction on general criticism acceptability

We test the models for the november data set, to assess the interaction between occupation and heroism on the target outcome Criticism acceptability (general level) (note that results are comparable for the specific level).

(m2_H <- lm(criticism_items_G_mean ~ Heroism * Cond, data = scale_scores2))
## 
## Call:
## lm(formula = criticism_items_G_mean ~ Heroism * Cond, data = scale_scores2)
## 
## Coefficients:
##   (Intercept)        Heroism          Cond1          Cond2  Heroism:Cond1  
##        4.9145        -0.4353        -0.7755         1.3349         0.1086  
## Heroism:Cond2  
##       -0.2366
(m4_H <- lm(criticism_items_G_mean ~ Heroism * Cond + Attitude, data = scale_scores2))
## 
## Call:
## lm(formula = criticism_items_G_mean ~ Heroism * Cond + Attitude, 
##     data = scale_scores2)
## 
## Coefficients:
##   (Intercept)        Heroism          Cond1          Cond2       Attitude  
##       6.31201       -0.22601       -0.22795        0.68313       -0.42540  
## Heroism:Cond1  Heroism:Cond2  
##       0.04244       -0.13944
anova(m2_H)  # includes F-test for the interaction term
anova(m4_H)  # same for the model with Attitude

The interaction is significant: the effect of heroism on criticism acceptability differs between each condition. For soldiers, the effect of heroism as a moral shield is stronger than for nurses or welders. But to claim that, we must decompose the interaction and assess if the difference in the effect of heroism is statistically different between soldiers on the one hand, and nurses or welders on the other hand.

We then decompose the significant interaction:

emtrends(m2_H, pairwise ~ Cond, var = "Heroism")
## $emtrends
##  Cond    Heroism.trend     SE  df lower.CL upper.CL
##  Nurse          -0.327 0.0624 415   -0.449   -0.204
##  Soldier        -0.672 0.0501 415   -0.770   -0.573
##  Weld           -0.307 0.0627 415   -0.431   -0.184
## 
## Confidence level used: 0.95 
## 
## $contrasts
##  contrast        estimate     SE  df t.ratio p.value
##  Nurse - Soldier   0.3452 0.0800 415   4.316  0.0001
##  Nurse - Weld     -0.0194 0.0885 415  -0.219  0.9738
##  Soldier - Weld   -0.3646 0.0803 415  -4.543  <.0001
## 
## P value adjustment: tukey method for comparing a family of 3 estimates
emtrends(m4_H, pairwise ~ Cond, var = "Heroism")
## $emtrends
##  Cond    Heroism.trend     SE  df lower.CL upper.CL
##  Nurse          -0.184 0.0625 414   -0.306  -0.0607
##  Soldier        -0.365 0.0646 414   -0.492  -0.2384
##  Weld           -0.129 0.0647 414   -0.256  -0.0019
## 
## Confidence level used: 0.95 
## 
## $contrasts
##  contrast        estimate     SE  df t.ratio p.value
##  Nurse - Soldier   0.1819 0.0793 414   2.294  0.0577
##  Nurse - Weld     -0.0546 0.0839 414  -0.650  0.7924
##  Soldier - Weld   -0.2364 0.0782 414  -3.023  0.0075
## 
## P value adjustment: tukey method for comparing a family of 3 estimates

So, for the November data set, regarding the general acceptability of criticism: the soldier slope appears to be significantly steeper than the nurse slope (although only marginally when evacuating attitude) and welder – this is vaguely consistent with our registered hypotheses. However —— this does not mean that this difference of difference… is different between november and september sample.

Let’s see if that significant difference in heroism effect between occupation differs between data sets:

scale_scores$SampleType <- "September"
scale_scores2$SampleType <- "November"

#colnames(scale_scores)
#colnames(scale_scores2)

scale_scores2 <- scale_scores2[, -c(2, 40)]
scale_scores <- scale_scores[, -2]
combined_scales <- rbind(scale_scores, scale_scores2)


anova(mexpl1<-lm(criticism_items_G_mean ~ Heroism * SampleType * Cond, data = combined_scales))

This 3-way interaction does not reach significance – I will not bother decomposing it. This lack of 3-way interaction states that the effect of heroism on criticism acceptability, which was shown to differ accross occupation in the november set does not differ from the one observed in the september set.


Poppy Effect

Hypotheses 6 to 11 addressed a conceptual extension of the previous findings: The effect of remembrace commemorations on the heroisation of soldiers. Because the replication was conducted the week preceding remembrance day (11th of November), we predicted increased heroisation of soldiers in the new sample, and increased outcomes scores previously associated with heroism in the new sample.

We know assemble the two datasets together and code for sample type - we code the sample type September (-0.5) vs October (+0.5), and only keep the soldier condition. See exploratory section for analyses regarding the other conditions (welders and nurses).

We can now test our Poppy effect by comparing the two samples regarding their scores of heroism, gratitude, support for workers demands, victim-related traits, impunity, criticism acceptability. As registered, two models are computed: one model only accounting for sample type on the target outcome, and a second evacuating variance explained by attitude (as the effect of remembrance day could indirectly influence the outcomes through increased positive attitude towards soldiers).

In all the analyses below, we are interested in the effect of the variable SampleType on target outcomes. We compute two models: one simple, and one using Attitude as a covariate.

H6: Perceived Heroism of soldiers will be greater for data collected around remembrance day vs data collected in September.

Soldiers <- subset(combined_scales, combined_scales$Cond == "Soldier")

Soldiers$SampleType_Dummy<-ifelse(Soldiers$SampleType == "September", -0.5, 0.5)
# t.test(Soldiers$Heroism ~ Soldiers$SampleType)
summary(m1 <- lm(Heroism ~ SampleType_Dummy, data = Soldiers))
## 
## Call:
## lm(formula = Heroism ~ SampleType_Dummy, data = Soldiers)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -4.0797 -1.0797  0.0857  1.0857  2.0857 
## 
## Coefficients:
##                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)        4.9970     0.1040  48.070   <2e-16 ***
## SampleType_Dummy  -0.1654     0.2079  -0.796    0.427    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.733 on 276 degrees of freedom
## Multiple R-squared:  0.002289,   Adjusted R-squared:  -0.001326 
## F-statistic: 0.6331 on 1 and 276 DF,  p-value: 0.4269
summary(m2 <- lm(Heroism ~ SampleType + scale(Attitude), data = Soldiers))
## 
## Call:
## lm(formula = Heroism ~ SampleType + scale(Attitude), data = Soldiers)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -4.5231 -0.5231  0.3614  0.4787  2.3614 
## 
## Coefficients:
##                 Estimate Std. Error t value Pr(>|t|)    
## (Intercept)      4.99620    0.05997  83.312   <2e-16 ***
## SampleType1      0.02843    0.06016   0.473    0.637    
## scale(Attitude)  1.41880    0.06026  23.544   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.9999 on 275 degrees of freedom
## Multiple R-squared:  0.6692, Adjusted R-squared:  0.6668 
## F-statistic: 278.1 on 2 and 275 DF,  p-value: < 2.2e-16
anova(m1, m2)

In none of our model, did SampleType reached significance. Given the importance of attitude in explaining heroism (see model 2) and the lack of effect of our sample type (model 1) - it is obvious that model 2 significantly explains heroism above and beyond model 1.

Data does not support H6: November sample is not significantly more heroised than September sample.

H7: Gratefulness of soldiers will be greater for data collected around remembrance day vs data collected in September.

cat("GENERAL")
## GENERAL
summary(m1 <- lm(Gratitude_G_mean ~ SampleType_Dummy, data = Soldiers))
## 
## Call:
## lm(formula = Gratitude_G_mean ~ SampleType_Dummy, data = Soldiers)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -4.3551 -1.2786  0.6449  1.6449  1.7214 
## 
## Coefficients:
##                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)        5.3168     0.1051  50.581   <2e-16 ***
## SampleType_Dummy  -0.0765     0.2102  -0.364    0.716    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.753 on 276 degrees of freedom
## Multiple R-squared:  0.0004795,  Adjusted R-squared:  -0.003142 
## F-statistic: 0.1324 on 1 and 276 DF,  p-value: 0.7162
summary(m2 <- lm(Gratitude_G_mean ~ SampleType + scale(Attitude), data = Soldiers))
## 
## Call:
## lm(formula = Gratitude_G_mean ~ SampleType + scale(Attitude), 
##     data = Soldiers)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -2.99133 -0.00438  0.02171  0.18031  2.17161 
## 
## Coefficients:
##                 Estimate Std. Error t value Pr(>|t|)    
## (Intercept)      5.31598    0.05459  97.386   <2e-16 ***
## SampleType1      0.07930    0.05476   1.448    0.149    
## scale(Attitude)  1.50065    0.05485  27.358   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.9101 on 275 degrees of freedom
## Multiple R-squared:  0.7314, Adjusted R-squared:  0.7295 
## F-statistic: 374.5 on 2 and 275 DF,  p-value: < 2.2e-16
anova(m1, m2)
cat("SPECIFIC")
## SPECIFIC
summary(m1 <- lm(Gratitude_S_mean ~ SampleType_Dummy, data = Soldiers))
## 
## Call:
## lm(formula = Gratitude_S_mean ~ SampleType_Dummy, data = Soldiers)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -2.2923 -1.9589 -0.2923  1.3623  3.9929 
## 
## Coefficients:
##                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)        3.1497     0.1071  29.410   <2e-16 ***
## SampleType_Dummy  -0.2851     0.2142  -1.331    0.184    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.786 on 276 degrees of freedom
## Multiple R-squared:  0.00638,    Adjusted R-squared:  0.002779 
## F-statistic: 1.772 on 1 and 276 DF,  p-value: 0.1842
summary(m2 <- lm(Gratitude_S_mean ~ SampleType + scale(Attitude), data = Soldiers))
## 
## Call:
## lm(formula = Gratitude_S_mean ~ SampleType + scale(Attitude), 
##     data = Soldiers)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -3.3156 -1.1078  0.0467  1.0178  3.3801 
## 
## Coefficients:
##                 Estimate Std. Error t value Pr(>|t|)    
## (Intercept)      3.14912    0.08690  36.236   <2e-16 ***
## SampleType1     -0.06043    0.08717  -0.693    0.489    
## scale(Attitude)  1.04847    0.08733  12.006   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.449 on 275 degrees of freedom
## Multiple R-squared:  0.3481, Adjusted R-squared:  0.3433 
## F-statistic: 73.42 on 2 and 275 DF,  p-value: < 2.2e-16
anova(m1, m2)

The effect of sample type does not reach significance in any of our model. Attitude significantly predicts gratitude.

Data does not support H7: regardless of the specificity of the outcome, the sample did not influence ed gratitude toward soldiers – to be honest, this is quite interesting considering that the poppy season is all about commemorating veterans, and money donated to buy poppy is often given to the Royal British Legion - which uses the money for social and emotional support for veterans of the british army. Because veterans are former soldiers, it is surprising that this self-displayed gratitude for veterans does not extend to present soldiers.

H8: Cricitism acceptability of soldiers will be greater for data collected around in September vs remembrance data.

cat("GENERAL")
## GENERAL
summary(m1 <- lm(criticism_items_G_mean ~ SampleType_Dummy, data = Soldiers))
## 
## Call:
## lm(formula = criticism_items_G_mean ~ SampleType_Dummy, data = Soldiers)
## 
## Residuals:
##    Min     1Q Median     3Q    Max 
## -1.948 -1.302 -0.281  1.031  4.365 
## 
## Coefficients:
##                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       2.79144    0.09206  30.322   <2e-16 ***
## SampleType_Dummy  0.31235    0.18412   1.696   0.0909 .  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.535 on 276 degrees of freedom
## Multiple R-squared:  0.01032,    Adjusted R-squared:  0.006734 
## F-statistic: 2.878 on 1 and 276 DF,  p-value: 0.09093
summary(m2 <- lm(criticism_items_G_mean ~ SampleType + scale(Attitude), data = Soldiers))
## 
## Call:
## lm(formula = criticism_items_G_mean ~ SampleType + scale(Attitude), 
##     data = Soldiers)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -2.8102 -0.6358 -0.1435  0.6246  3.0589 
## 
## Coefficients:
##                 Estimate Std. Error t value Pr(>|t|)    
## (Intercept)      2.79210    0.06063  46.054   <2e-16 ***
## SampleType1      0.06545    0.06081   1.076    0.283    
## scale(Attitude) -1.15819    0.06092 -19.011   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.011 on 275 degrees of freedom
## Multiple R-squared:  0.5724, Adjusted R-squared:  0.5692 
## F-statistic:   184 on 2 and 275 DF,  p-value: < 2.2e-16
anova(m1, m2)
cat("SPECIFIC")
## SPECIFIC
summary(m1 <- lm(criticism_items_S_mean ~ SampleType_Dummy, data = Soldiers))
## 
## Call:
## lm(formula = criticism_items_S_mean ~ SampleType_Dummy, data = Soldiers)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1.59286 -0.79710 -0.09286  0.70290  2.20290 
## 
## Coefficients:
##                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       2.44498    0.06048  40.429   <2e-16 ***
## SampleType_Dummy  0.29576    0.12095   2.445   0.0151 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.008 on 276 degrees of freedom
## Multiple R-squared:  0.0212, Adjusted R-squared:  0.01766 
## F-statistic: 5.979 on 1 and 276 DF,  p-value: 0.0151
summary(m2 <- lm(criticism_items_S_mean ~ SampleType + scale(Attitude), data = Soldiers))
## 
## Call:
## lm(formula = criticism_items_S_mean ~ SampleType + scale(Attitude), 
##     data = Soldiers)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -2.2366 -0.6717 -0.1384  0.5954  2.0945 
## 
## Coefficients:
##                 Estimate Std. Error t value Pr(>|t|)    
## (Intercept)      2.44521    0.05560  43.982  < 2e-16 ***
## SampleType1      0.11645    0.05577   2.088   0.0377 *  
## scale(Attitude) -0.40127    0.05587  -7.183 6.42e-12 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.9269 on 275 degrees of freedom
## Multiple R-squared:  0.1758, Adjusted R-squared:  0.1698 
## F-statistic: 29.33 on 2 and 275 DF,  p-value: 2.839e-12
anova(m1, m2)

Although the general measure of criticism acceptability (‘People should think twice before criticising soldiers’) does not reach significance here, the specific measure (condamnation of public social media messages criticising in hateful speech soldiers: ‘UK Soldiers should die and burn in hell’ and ‘UK soldiers are evil and wish harm on other people’) did reach significance: however —- this was in the opposite direction as the one we predicted. In other words: people are less likely to wish the authors of the hate speech to be prosecuted during the week of remembrance day… This is in contradiction with real experiences (e.g., Yassmin Abdel-Magied controvery on Anzac day in Australia - https://en.wikipedia.org/wiki/Yassmin_Abdel-Magied#Anzac_Day_post). This is really surprising.

Data does not support H8: regardless of the specificity of the outcome, the sample did influence criticism acceptability, but in the opposite direction as the one we predicted

Let’s see some breakdown details cause that’s so surprising:

Sept<- subset(Soldiers, Soldiers$SampleType == "September")
Nov<- subset(Soldiers, Soldiers$SampleType == "November")

# Compute proportions
sept_props <- colMeans(Sept[, 9:18], na.rm = TRUE)
nov_props  <- colMeans(Nov[, 9:18], na.rm = TRUE)
# Combine into one matrix
compare_mat <- rbind(September = sept_props, November = nov_props)

# Get suffix (the part after the last "_")
suffixes <- sub(".*_", "", colnames(compare_mat))

# Map suffix to nice labels
label_map <- c(
  "1" = "be Liked",
  "2" = "be Ignored",
  "3" = "be deleted",
  "9" = "be banned",
  "4" = "be prosecuted"
)

nice_labels <- label_map[suffixes]

# Logical indices for W1 and W2 items
is_w1 <- grepl("W1_", colnames(compare_mat))
is_w2 <- grepl("W2_", colnames(compare_mat))

# Subsets for W1
compare_w1      <- compare_mat[, is_w1, drop = FALSE]
nice_labels_w1  <- nice_labels[is_w1]

# Subsets for W2
compare_w2      <- compare_mat[, is_w2, drop = FALSE]
nice_labels_w2  <- nice_labels[is_w2]



barplot(compare_w1,
        beside = TRUE,
        col = c("skyblue", "orange"),
        ylim = c(0, 1),
        names.arg = nice_labels_w1,
        ylab = "Proportion of 'Would'",
        main = "Acceptability of Tweet 1",
        sub  = "... should die and burn in hell",
        legend.text = TRUE)

barplot(compare_w2,
        beside = TRUE,
        col = c("skyblue", "orange"),
        ylim = c(0, 1),
        names.arg = nice_labels_w2,
        ylab = "Proportion of 'Would'",
        main = "Acceptability of Tweet 2",
        sub  = "... are evil and wish harm on other people",
        legend.text = TRUE)

Above, we can note that the proportion of british who want to prosecute hate speech against soldier was larger for the September set than November set. Likewise, proportions of British people reporting that hate speech should be liked or ignored appears larger for the November sample than the September sample. This is really unexpected.

H9: Demands support of soldiers will be greater for data collected around remembrance day vs data collected in September.

cat("GENERAL")
## GENERAL
summary(m1 <- lm(DemandSupp_G_mean ~ SampleType_Dummy, data = Soldiers))
## 
## Call:
## lm(formula = DemandSupp_G_mean ~ SampleType_Dummy, data = Soldiers)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -2.8571 -0.3571  0.1429  0.6429  3.1449 
## 
## Coefficients:
##                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       3.85611    0.06055  63.690   <2e-16 ***
## SampleType_Dummy  0.00207    0.12109   0.017    0.986    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.009 on 276 degrees of freedom
## Multiple R-squared:  1.059e-06,  Adjusted R-squared:  -0.003622 
## F-statistic: 0.0002923 on 1 and 276 DF,  p-value: 0.9864
summary(m2 <- lm(DemandSupp_G_mean ~ SampleType + scale(Attitude), data = Soldiers))
## 
## Call:
## lm(formula = DemandSupp_G_mean ~ SampleType + scale(Attitude), 
##     data = Soldiers)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -3.01257 -0.51257  0.07848  0.51100  3.01100 
## 
## Coefficients:
##                 Estimate Std. Error t value Pr(>|t|)    
## (Intercept)      3.85603    0.06009  64.168   <2e-16 ***
## SampleType1      0.01178    0.06028   0.195   0.8451    
## scale(Attitude)  0.13722    0.06039   2.272   0.0238 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.002 on 275 degrees of freedom
## Multiple R-squared:  0.01843,    Adjusted R-squared:  0.01129 
## F-statistic: 2.582 on 2 and 275 DF,  p-value: 0.07745
anova(m1, m2)
cat("SPECIFIC")
## SPECIFIC
summary(m1 <- lm(DemandSupp_S_mean ~ SampleType_Dummy, data = Soldiers))
## 
## Call:
## lm(formula = DemandSupp_S_mean ~ SampleType_Dummy, data = Soldiers)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -3.6036 -0.6036  0.1801  0.9638  2.4638 
## 
## Coefficients:
##                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       4.56990    0.08902  51.333   <2e-16 ***
## SampleType_Dummy  0.06734    0.17805   0.378    0.706    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.484 on 276 degrees of freedom
## Multiple R-squared:  0.000518,   Adjusted R-squared:  -0.003103 
## F-statistic: 0.143 on 1 and 276 DF,  p-value: 0.7056
summary(m2 <- lm(DemandSupp_S_mean ~ SampleType + scale(Attitude), data = Soldiers))
## 
## Call:
## lm(formula = DemandSupp_S_mean ~ SampleType + scale(Attitude), 
##     data = Soldiers)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -3.6953 -0.6953  0.2559  1.0128  2.5997 
## 
## Coefficients:
##                 Estimate Std. Error t value Pr(>|t|)    
## (Intercept)      4.56986    0.08905  51.316   <2e-16 ***
## SampleType1      0.04001    0.08933   0.448    0.655    
## scale(Attitude)  0.08100    0.08949   0.905    0.366    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.485 on 275 degrees of freedom
## Multiple R-squared:  0.003487,   Adjusted R-squared:  -0.003761 
## F-statistic: 0.4811 on 2 and 275 DF,  p-value: 0.6186
anova(m1, m2)

Here again - regardless of the measure (general or specific), SampleType does not influence support for demands from workers. This is perhaps less surprising than the others null effects of the study as - even in the original study (September sample), effect sizes were quite small - and even close to null for the general measure of the construct.

Data does not support H9: regardless of the specificity of the outcome, the sample did not influence support for demands from workers

H10: Victimisation of soldiers will be greater for data collected around remembrance day vs data collected in September.

cat("GENERAL")
## GENERAL
summary(m1 <- lm(Victim_G_mean ~ SampleType_Dummy, data = Soldiers))
## 
## Call:
## lm(formula = Victim_G_mean ~ SampleType_Dummy, data = Soldiers)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -2.2923 -1.1810  0.0411  0.8190  3.8190 
## 
## Coefficients:
##                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       3.23661    0.08463  38.244   <2e-16 ***
## SampleType_Dummy -0.11132    0.16926  -0.658    0.511    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.411 on 276 degrees of freedom
## Multiple R-squared:  0.001565,   Adjusted R-squared:  -0.002053 
## F-statistic: 0.4325 on 1 and 276 DF,  p-value: 0.5113
summary(m2 <- lm(Victim_G_mean ~ SampleType + scale(Attitude), data = Soldiers))
## 
## Call:
## lm(formula = Victim_G_mean ~ SampleType + scale(Attitude), data = Soldiers)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -2.5328 -1.1267  0.0304  1.0304  4.3574 
## 
## Coefficients:
##                 Estimate Std. Error t value Pr(>|t|)    
## (Intercept)      3.23647    0.08348  38.767  < 2e-16 ***
## SampleType1     -0.03636    0.08374  -0.434  0.66452    
## scale(Attitude)  0.24642    0.08389   2.937  0.00359 ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.392 on 275 degrees of freedom
## Multiple R-squared:  0.03194,    Adjusted R-squared:  0.0249 
## F-statistic: 4.536 on 2 and 275 DF,  p-value: 0.01153
anova(m1, m2)
cat("SPECIFIC")
## SPECIFIC
summary(m1 <- lm(Victim_S_mean ~ SampleType_Dummy, data = Soldiers))
## 
## Call:
## lm(formula = Victim_S_mean ~ SampleType_Dummy, data = Soldiers)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -2.99517 -0.66184  0.00483  0.78810  2.33816 
## 
## Coefficients:
##                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       4.77020    0.06986  68.278   <2e-16 ***
## SampleType_Dummy  0.21674    0.13973   1.551    0.122    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.165 on 276 degrees of freedom
## Multiple R-squared:  0.008642,   Adjusted R-squared:  0.00505 
## F-statistic: 2.406 on 1 and 276 DF,  p-value: 0.122
summary(m2 <- lm(Victim_S_mean ~ SampleType + scale(Attitude), data = Soldiers))
## 
## Call:
## lm(formula = Victim_S_mean ~ SampleType + scale(Attitude), data = Soldiers)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -3.0396 -0.7063 -0.0396  0.8487  2.5264 
## 
## Coefficients:
##                 Estimate Std. Error t value Pr(>|t|)    
## (Intercept)      4.77012    0.06947  68.666   <2e-16 ***
## SampleType1      0.11951    0.06968   1.715   0.0875 .  
## scale(Attitude)  0.14219    0.06981   2.037   0.0426 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.158 on 275 degrees of freedom
## Multiple R-squared:  0.02338,    Adjusted R-squared:  0.01627 
## F-statistic: 3.291 on 2 and 275 DF,  p-value: 0.03868
anova(m1, m2)

No effect of SampleType.

Data does not support H10: regardless of the specificity of the outcome, the sample did not influence victimisation of workers

H11: Impunity of soldiers will be greater for data collected around remembrance day vs data collected in September.

cat("GENERAL")
## GENERAL
summary(m1 <- lm(Villain_G_mean ~ SampleType_Dummy, data = Soldiers))
## 
## Call:
## lm(formula = Villain_G_mean ~ SampleType_Dummy, data = Soldiers)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -2.6836 -1.0169 -0.2333  1.1000  3.7667 
## 
## Coefficients:
##                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       3.45845    0.09097  38.019   <2e-16 ***
## SampleType_Dummy -0.45024    0.18193  -2.475   0.0139 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.517 on 276 degrees of freedom
## Multiple R-squared:  0.02171,    Adjusted R-squared:  0.01816 
## F-statistic: 6.124 on 1 and 276 DF,  p-value: 0.01393
summary(m2 <- lm(Villain_G_mean ~ SampleType + scale(Attitude), data = Soldiers))
## 
## Call:
## lm(formula = Villain_G_mean ~ SampleType + scale(Attitude), data = Soldiers)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -3.0273 -0.9026 -0.1007  0.9709  3.4378 
## 
## Coefficients:
##                 Estimate Std. Error t value Pr(>|t|)    
## (Intercept)      3.45806    0.08084  42.775  < 2e-16 ***
## SampleType1     -0.17021    0.08109  -2.099   0.0367 *  
## scale(Attitude)  0.70101    0.08124   8.629 5.04e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.348 on 275 degrees of freedom
## Multiple R-squared:  0.2302, Adjusted R-squared:  0.2246 
## F-statistic: 41.11 on 2 and 275 DF,  p-value: 2.398e-16
anova(m1, m2)
cat("SPECIFIC")
## SPECIFIC
summary(m1 <- lm(Villain_S_mean ~ SampleType_Dummy, data = Soldiers))
## 
## Call:
## lm(formula = Villain_S_mean ~ SampleType_Dummy, data = Soldiers)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -4.4734 -1.0881  0.5266  1.5266  1.9119 
## 
## Coefficients:
##                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       5.28076    0.09335  56.572   <2e-16 ***
## SampleType_Dummy -0.38533    0.18669  -2.064     0.04 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.556 on 276 degrees of freedom
## Multiple R-squared:  0.0152, Adjusted R-squared:  0.01163 
## F-statistic:  4.26 on 1 and 276 DF,  p-value: 0.03995
summary(m2 <- lm(Villain_S_mean ~ SampleType + scale(Attitude), data = Soldiers))
## 
## Call:
## lm(formula = Villain_S_mean ~ SampleType + scale(Attitude), data = Soldiers)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -4.7301 -0.8068  0.2699  0.9365  3.7065 
## 
## Coefficients:
##                 Estimate Std. Error t value Pr(>|t|)    
## (Intercept)      5.28030    0.07947   66.44   <2e-16 ***
## SampleType1     -0.12832    0.07972   -1.61    0.109    
## scale(Attitude)  0.82142    0.07986   10.29   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.325 on 275 degrees of freedom
## Multiple R-squared:  0.2888, Adjusted R-squared:  0.2836 
## F-statistic: 55.84 on 2 and 275 DF,  p-value: < 2.2e-16
anova(m1, m2)
# Compute group means and SDs
summ_stats <- Soldiers %>%
  group_by(SampleType) %>%
  summarise(
    mean = mean(Villain_G_mean, na.rm = TRUE),
    sd   = sd(Villain_G_mean, na.rm = TRUE)
  )

# Boxplot + mean dots + text
ggplot(Soldiers, aes(x = SampleType, y = Villain_G_mean, fill = SampleType)) +
  geom_boxplot(alpha = 0.7, width = 0.6, outlier.shape = 21, outlier.fill = "white") +
  stat_summary(fun = mean, geom = "point", shape = 21, size = 3, fill = "red") +
  geom_text(
    data = summ_stats,
    aes(
      x = SampleType,
      y = mean,
      label = sprintf("M=%.2f\nSD=%.2f", mean, sd)
    ),
    vjust = -1.5,        # position text above boxes
    size = 3.5,
    color = "black"
  ) +
  labs(
    title = "Impunity (General) by Sample Type",
    subtitle = "Higher scores = agreement with deregulating soldiers",
    x = "Sample Type",
    y = "Impunity (general)"
  ) +
  theme_minimal(base_size = 13) +
  theme(legend.position = "none")

# Compute group means and SDs
summ_stats <- Soldiers %>%
  group_by(SampleType) %>%
  summarise(
    mean = mean(Villain_S_mean, na.rm = TRUE),
    sd   = sd(Villain_S_mean, na.rm = TRUE)
  )

# Boxplot + mean dots + text
ggplot(Soldiers, aes(x = SampleType, y = Villain_G_mean, fill = SampleType)) +
  geom_boxplot(alpha = 0.7, width = 0.6, outlier.shape = 21, outlier.fill = "white") +
  stat_summary(fun = mean, geom = "point", shape = 21, size = 3, fill = "red") +
  geom_text(
    data = summ_stats,
    aes(
      x = SampleType,
      y = mean,
      label = sprintf("M=%.2f\nSD=%.2f", mean, sd)
    ),
    vjust = -1.5,        # position text above boxes
    size = 3.5,
    color = "black"
  ) +
  labs(
    title = "Impunity (Specific) by Sample Type",
    subtitle = "Higher scores = agreement with shielding soldiers from prosecution when breaking the rules",
    x = "Sample Type",
    y = "Impunity (Specific)"
  ) +
  theme_minimal(base_size = 13) +
  theme(legend.position = "none")

Again, as for the other pendent of moral shielding - criticism acceptability - we observe that participants are less likely to want to shield soldiers from prosecutions or agree with the idea that regulations are a problem for soldiers in the November sample. This runs against our hypotheses and points to the idea that the Poppy season might weaken the moral shield of soldiers.

Registered Exploratory analyses

In the initial registration document, We did not expect any effect of Sample type on target outcomes for other occupations (i.e., Nurses and Welders). We registered exploratory analyses assessing how occupation (sum-to-zero contrast) moderates the effect of sample type on each target outcome (Heroism, Gratitude, Criticism acceptability, etc.). We decompose the interaction to directly compare the effect of Sample type in the Soldier condition to 1) the effect of sample type in the Nurse condition, and 2) the effect of sample type in the Welder condition. Note that the power to detect this interaction given our planned sample size is around 65%.

In addition - because most of our previous analyses on the poppy effect did not reach significance - there is no reason to assess how target occupation qualifies the poppy effects on soldiers (because they were not found). Nevertheless - it is interesting to see if the two unexpected results (Poppy season appears to increase criticism acceptability and decrease impunity of soldiers) is specific to soldiers by comparing them with welders and nurses. So let us follow the registered exploratory plan for these two outcomes.

Criticism acceptability General

summary(mexpl1<-lm(criticism_items_G_mean ~ SampleType * Cond, data = combined_scales))
## 
## Call:
## lm(formula = criticism_items_G_mean ~ SampleType * Cond, data = combined_scales)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -1.9476 -0.9504 -0.1826  0.7190  4.7163 
## 
## Coefficients:
##                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)        2.64777    0.04449  59.509  < 2e-16 ***
## SampleType1        0.09025    0.04449   2.028   0.0428 *  
## Cond1             -0.34513    0.06272  -5.503 4.97e-08 ***
## Cond2              0.14367    0.06300   2.281   0.0228 *  
## SampleType1:Cond1 -0.07129    0.06272  -1.137   0.2560    
## SampleType1:Cond2  0.06593    0.06300   1.047   0.2956    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.288 on 832 degrees of freedom
## Multiple R-squared:  0.04181,    Adjusted R-squared:  0.03605 
## F-statistic: 7.261 on 5 and 832 DF,  p-value: 1.144e-06
summary(mexpl2<-lm(criticism_items_G_mean ~ SampleType * Cond + scale(Attitude), data = combined_scales))
## 
## Call:
## lm(formula = criticism_items_G_mean ~ SampleType * Cond + scale(Attitude), 
##     data = combined_scales)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -2.8578 -0.6718 -0.1324  0.5342  5.0585 
## 
## Coefficients:
##                    Estimate Std. Error t value Pr(>|t|)    
## (Intercept)        2.645346   0.034678  76.284   <2e-16 ***
## SampleType1        0.062239   0.034699   1.794   0.0732 .  
## Cond1              0.062311   0.051938   1.200   0.2306    
## Cond2             -0.102301   0.050230  -2.037   0.0420 *  
## scale(Attitude)   -0.858831   0.037004 -23.209   <2e-16 ***
## SampleType1:Cond1  0.008946   0.049003   0.183   0.8552    
## SampleType1:Cond2  0.013268   0.049152   0.270   0.7873    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.004 on 831 degrees of freedom
## Multiple R-squared:  0.4187, Adjusted R-squared:  0.4145 
## F-statistic: 99.74 on 6 and 831 DF,  p-value: < 2.2e-16
# Estimated marginal means for SampleType within each Cond
emm <- emmeans(mexpl1, ~ SampleType | Cond)

# Pairwise comparisons of SampleType inside each Cond
pairs(emm)
## Cond = Nurse:
##  contrast             estimate    SE  df t.ratio p.value
##  November - September   0.0379 0.153 832   0.248  0.8045
## 
## Cond = Soldier:
##  contrast             estimate    SE  df t.ratio p.value
##  November - September   0.3124 0.154 832   2.022  0.0435
## 
## Cond = Weld:
##  contrast             estimate    SE  df t.ratio p.value
##  November - September   0.1912 0.155 832   1.236  0.2170
# Now let's compare contrasts:
contrast(emm, method = "pairwise", interaction = TRUE, by = NULL)
##  SampleType_pairwise  Cond_pairwise   estimate    SE  df t.ratio p.value
##  November - September Nurse - Soldier   -0.274 0.218 832  -1.262  0.2074
##  November - September Nurse - Weld      -0.153 0.218 832  -0.704  0.4815
##  November - September Soldier - Weld     0.121 0.219 832   0.554  0.5798

The effect of SampleType observed in the soldier condition (p = .04) was not significantly different than the effect of SampleType observed in Welders or nurses.

Criticism acceptability Specific

summary(mexpl1<-lm(criticism_items_S_mean ~ SampleType * Cond, data = combined_scales))
## 
## Call:
## lm(formula = criticism_items_S_mean ~ SampleType * Cond, data = combined_scales)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -1.5929 -0.7971 -0.1303  0.7029  2.2837 
## 
## Coefficients:
##                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)        2.33484    0.03241  72.043  < 2e-16 ***
## SampleType1        0.03930    0.03241   1.213 0.225580    
## Cond1             -0.16154    0.04568  -3.536 0.000429 ***
## Cond2              0.11014    0.04589   2.400 0.016601 *  
## SampleType1:Cond1 -0.08232    0.04568  -1.802 0.071917 .  
## SampleType1:Cond2  0.10857    0.04589   2.366 0.018204 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.9381 on 832 degrees of freedom
## Multiple R-squared:  0.02428,    Adjusted R-squared:  0.01841 
## F-statistic:  4.14 on 5 and 832 DF,  p-value: 0.001013
summary(mexpl2<-lm(criticism_items_S_mean ~ SampleType * Cond + scale(Attitude), data = combined_scales))
## 
## Call:
## lm(formula = criticism_items_S_mean ~ SampleType * Cond + scale(Attitude), 
##     data = combined_scales)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -2.14535 -0.68389 -0.04584  0.57712  2.23214 
## 
## Coefficients:
##                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)        2.33398    0.03091  75.520   <2e-16 ***
## SampleType1        0.02945    0.03092   0.952   0.3412    
## Cond1             -0.01821    0.04629  -0.393   0.6942    
## Cond2              0.02361    0.04477   0.527   0.5980    
## scale(Attitude)   -0.30212    0.03298  -9.161   <2e-16 ***
## SampleType1:Cond1 -0.05409    0.04367  -1.239   0.2159    
## SampleType1:Cond2  0.09005    0.04380   2.056   0.0401 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.8946 on 831 degrees of freedom
## Multiple R-squared:  0.1138, Adjusted R-squared:  0.1074 
## F-statistic: 17.78 on 6 and 831 DF,  p-value: < 2.2e-16
# Estimated marginal means for SampleType within each Cond
emm <- emmeans(mexpl1, ~ SampleType | Cond)

# Pairwise comparisons of SampleType inside each Cond
pairs(emm)
## Cond = Nurse:
##  contrast             estimate    SE  df t.ratio p.value
##  November - September  -0.0860 0.112 832  -0.771  0.4407
## 
## Cond = Soldier:
##  contrast             estimate    SE  df t.ratio p.value
##  November - September   0.2958 0.113 832   2.628  0.0087
## 
## Cond = Weld:
##  contrast             estimate    SE  df t.ratio p.value
##  November - September   0.0261 0.113 832   0.231  0.8170
# Now let's compare contrasts:
contrast(emm, method = "pairwise", interaction = TRUE, by = NULL)
##  SampleType_pairwise  Cond_pairwise   estimate    SE  df t.ratio p.value
##  November - September Nurse - Soldier   -0.382 0.158 832  -2.410  0.0162
##  November - September Nurse - Weld      -0.112 0.159 832  -0.707  0.4797
##  November - September Soldier - Weld     0.270 0.159 832   1.693  0.0908

Here, the effect of SampleType in Soldiers was statistically different from the effect of SampleType in Nurses (p = .016), and marginally different from the effect observed in Welders (p = .09). This provides mixed support for a specific effect of the Poppy season on Soldiers.

Impunity General

summary(mexpl1<-lm(Villain_G_mean ~ SampleType * Cond, data = combined_scales))
## 
## Call:
## lm(formula = Villain_G_mean ~ SampleType * Cond, data = combined_scales)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -2.9686 -0.9173  0.0314  0.8681  3.7667 
## 
## Coefficients:
##                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)        3.73676    0.04808  77.718  < 2e-16 ***
## SampleType1       -0.11971    0.04808  -2.490   0.0130 *  
## Cond1              0.13149    0.06777   1.940   0.0527 .  
## Cond2             -0.27831    0.06808  -4.088 4.77e-05 ***
## SampleType1:Cond1  0.07071    0.06777   1.043   0.2971    
## SampleType1:Cond2 -0.10541    0.06808  -1.548   0.1219    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.392 on 832 degrees of freedom
## Multiple R-squared:  0.02973,    Adjusted R-squared:  0.0239 
## F-statistic: 5.098 on 5 and 832 DF,  p-value: 0.0001301
summary(mexpl2<-lm(Villain_G_mean ~ SampleType * Cond + scale(Attitude), data = combined_scales))
## 
## Call:
## lm(formula = Villain_G_mean ~ SampleType * Cond + scale(Attitude), 
##     data = combined_scales)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -3.4900 -0.9030 -0.0288  0.9480  3.5003 
## 
## Coefficients:
##                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)        3.73810    0.04558  82.011   <2e-16 ***
## SampleType1       -0.10427    0.04561  -2.286   0.0225 *  
## Cond1             -0.09317    0.06827  -1.365   0.1727    
## Cond2             -0.14268    0.06602  -2.161   0.0310 *  
## scale(Attitude)    0.47355    0.04864   9.736   <2e-16 ***
## SampleType1:Cond1  0.02647    0.06441   0.411   0.6812    
## SampleType1:Cond2 -0.07637    0.06461  -1.182   0.2375    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.319 on 831 degrees of freedom
## Multiple R-squared:  0.1291, Adjusted R-squared:  0.1228 
## F-statistic: 20.53 on 6 and 831 DF,  p-value: < 2.2e-16
# Estimated marginal means for SampleType within each Cond
emm <- emmeans(mexpl1, ~ SampleType | Cond)

# Pairwise comparisons of SampleType inside each Cond
pairs(emm)
## Cond = Nurse:
##  contrast             estimate    SE  df t.ratio p.value
##  November - September   -0.098 0.165 832  -0.592  0.5538
## 
## Cond = Soldier:
##  contrast             estimate    SE  df t.ratio p.value
##  November - September   -0.450 0.167 832  -2.697  0.0071
## 
## Cond = Weld:
##  contrast             estimate    SE  df t.ratio p.value
##  November - September   -0.170 0.167 832  -1.017  0.3096
# Now let's compare contrasts:
contrast(emm, method = "pairwise", interaction = TRUE, by = NULL)
##  SampleType_pairwise  Cond_pairwise   estimate    SE  df t.ratio p.value
##  November - September Nurse - Soldier    0.352 0.235 832   1.498  0.1344
##  November - September Nurse - Weld       0.072 0.235 832   0.306  0.7596
##  November - September Soldier - Weld    -0.280 0.236 832  -1.186  0.2361

There is no evidence for a specific Poppy effect in soldiers, compared with other occupations (all planned comparisons are non-significant).

Impunity Specific

summary(mexpl1<-lm(Villain_S_mean ~ SampleType * Cond, data = combined_scales))
## 
## Call:
## lm(formula = Villain_S_mean ~ SampleType * Cond, data = combined_scales)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -4.4734 -1.1401  0.3478  1.4390  2.4390 
## 
## Coefficients:
##                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)        5.07798    0.05742  88.431  < 2e-16 ***
## SampleType1       -0.07264    0.05742  -1.265   0.2063    
## Cond1             -0.46768    0.08094  -5.778 1.07e-08 ***
## Cond2              0.20278    0.08130   2.494   0.0128 *  
## SampleType1:Cond1  0.02336    0.08094   0.289   0.7729    
## SampleType1:Cond2 -0.12003    0.08130  -1.476   0.1402    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.662 on 832 degrees of freedom
## Multiple R-squared:  0.04321,    Adjusted R-squared:  0.03746 
## F-statistic: 7.516 on 5 and 832 DF,  p-value: 6.509e-07
summary(mexpl2<-lm(Villain_S_mean ~ SampleType * Cond + scale(Attitude), data = combined_scales))
## 
## Call:
## lm(formula = Villain_S_mean ~ SampleType * Cond + scale(Attitude), 
##     data = combined_scales)
## 
## Residuals:
##    Min     1Q Median     3Q    Max 
## -4.685 -1.042  0.285  1.146  3.389 
## 
## Coefficients:
##                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)        5.07957    0.05446  93.279  < 2e-16 ***
## SampleType1       -0.05425    0.05449  -0.996    0.320    
## Cond1             -0.73519    0.08156  -9.014  < 2e-16 ***
## Cond2              0.36428    0.07888   4.618 4.48e-06 ***
## scale(Attitude)    0.56388    0.05811   9.704  < 2e-16 ***
## SampleType1:Cond1 -0.02932    0.07695  -0.381    0.703    
## SampleType1:Cond2 -0.08546    0.07718  -1.107    0.269    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.576 on 831 degrees of freedom
## Multiple R-squared:  0.1406, Adjusted R-squared:  0.1344 
## F-statistic: 22.66 on 6 and 831 DF,  p-value: < 2.2e-16
# Estimated marginal means for SampleType within each Cond
emm <- emmeans(mexpl1, ~ SampleType | Cond)

# Pairwise comparisons of SampleType inside each Cond
pairs(emm)
## Cond = Nurse:
##  contrast             estimate    SE  df t.ratio p.value
##  November - September  -0.0985 0.198 832  -0.499  0.6182
## 
## Cond = Soldier:
##  contrast             estimate    SE  df t.ratio p.value
##  November - September  -0.3853 0.199 832  -1.933  0.0536
## 
## Cond = Weld:
##  contrast             estimate    SE  df t.ratio p.value
##  November - September   0.0481 0.200 832   0.241  0.8099
# Now let's compare contrasts:
contrast(emm, method = "pairwise", interaction = TRUE, by = NULL)
##  SampleType_pairwise  Cond_pairwise   estimate    SE  df t.ratio p.value
##  November - September Nurse - Soldier    0.287 0.281 832   1.022  0.3073
##  November - September Nurse - Weld      -0.147 0.281 832  -0.522  0.6020
##  November - September Soldier - Weld    -0.433 0.282 832  -1.536  0.1250

Similarly, there is nothing pointing to a specific effect of the season on Soldiers for specific measure of impunity.


HISTOGRAMS

Let’s get some more descriptives in the picture:

The following table show the difference between the two samples for each outcome, in the Soldier condition:

library(ggplot2)
library(dplyr)
library(tidyr)

# ensure Attitude isn't a matrix
combined_scales$Attitude <- as.numeric(combined_scales$Attitude)

vars <- c(
  "Danger",
  "Help",
  "Gratitude_G_mean",
  "Gratitude_S_mean",
  "criticism_items_G_mean",
  "criticism_items_S_mean",
  "DemandSupp_G_mean",
  "DemandSupp_S_mean",
  "Victim_G_mean",
  "Victim_S_mean",
  "Villain_G_mean",
  "Villain_S_mean",
  "Heroism",
  "Attitude"
)

# Filter data
df_filtered <- combined_scales %>%
  filter(
    Cond %in% c("Soldier", "Nurse", "Weld"),
    SampleType %in% c("September", "November")
  ) %>%
  mutate(
    SampleType = factor(SampleType, levels = c("September", "November"))
  )

# Loop through variables
for (var in vars) {

  p <- ggplot(df_filtered, aes_string(x = var, fill = "SampleType")) +
    geom_histogram(
      position = "identity",
      alpha = 0.4,
      color = "black",
      binwidth = 1,
      na.rm = TRUE
    ) +
    facet_wrap(~Cond, scales = "free_y") +
    labs(
      title = paste0("Distribution of ", var, " by Occupation and Sample Type"),
      x = var,
      y = "Count",
      fill = "Sample Type"
    ) +
    theme_classic(base_size = 12) +
    theme(
      panel.grid.major.y = element_line(linewidth = 0.3, colour = "grey80"),
      panel.grid.minor.y = element_line(linewidth = 0.2, colour = "grey90"),
      plot.title = element_text(face = "bold", hjust = 0.5)
    )

  print(p)
}
## Warning: `aes_string()` was deprecated in ggplot2 3.0.0.
## ℹ Please use tidy evaluation idioms with `aes()`.
## ℹ See also `vignette("ggplot2-in-packages")` for more information.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

The following table does just the same, but also considering nurses and welders

# Define variables of interest
vars <- c("Gratitude_G_mean", "Gratitude_S_mean", 
           "criticism_items_G_mean", "criticism_items_S_mean",
           "DemandSupp_G_mean", "DemandSupp_S_mean", 
           "Victim_G_mean", "Victim_S_mean", 
           "Villain_G_mean", "Villain_S_mean",
           "Heroism", "Attitude", "Danger", "Help")

# Function to compute everything for one variable
compare_groups <- function(var, data) {
  x <- data[[var]]
  group <- data$SampleType
  
  sep <- x[group == "September"]
  nov <- x[group == "November"]
  
  t_res <- t.test(sep, nov)
  
  # Compute Cohen's d manually
  pooled_sd <- sqrt(((length(sep)-1)*sd(sep)^2 + (length(nov)-1)*sd(nov)^2) / 
                    (length(sep) + length(nov) - 2))
  d <- (mean(sep) - mean(nov)) / pooled_sd
  
  data.frame(
    Variable = var,
    September_M_SD = sprintf("%.2f (%.2f)", mean(sep, na.rm=TRUE), sd(sep, na.rm=TRUE)),
    November_M_SD  = sprintf("%.2f (%.2f)", mean(nov, na.rm=TRUE), sd(nov, na.rm=TRUE)),
    t_value = round(t_res$statistic, 2),
    p_value = round(t_res$p.value, 3),
    Cohens_d = round(d, 2)
  )
}

# Apply function to all variables and bind results
results <- do.call(rbind, lapply(vars, compare_groups, data = Soldiers))

# Print nicely
results
# Variables of interest
vars <- c("Gratitude_G_mean", "Gratitude_S_mean", 
          "criticism_items_G_mean", "criticism_items_S_mean",
          "DemandSupp_G_mean", "DemandSupp_S_mean", 
          "Victim_G_mean", "Victim_S_mean", 
          "Villain_G_mean", "Villain_S_mean",
          "Heroism", "Attitude", "Danger", "Help")

# Conditions
conditions <- levels(combined_scales$Cond)

# Function: comparison within each condition for one variable
compare_by_condition <- function(var, data) {
  result_list <- list()
  
  for (cond in conditions) {
    subset_data <- data[data$Cond == cond, ]
    
    x <- subset_data[[var]]
    group <- subset_data$SampleType
    
    sep <- x[group == "September"]
    nov <- x[group == "November"]
    
    # drop NAs
    sep <- sep[!is.na(sep)]
    nov <- nov[!is.na(nov)]
    
    # if too few observations, skip or return NA row
    if (length(sep) < 2 || length(nov) < 2) {
      next
      # or, if you prefer explicit NAs instead of skipping:
      # result_list[[cond]] <- data.frame(
      #   Variable = var,
      #   Condition = cond,
      #   September_M_SD = NA,
      #   November_M_SD = NA,
      #   t_value = NA,
      #   p_value = NA,
      #   Cohens_d = NA,
      #   stringsAsFactors = FALSE
      # )
      # next
    }
    
    t_res <- t.test(sep, nov)
    
    pooled_sd <- sqrt(((length(sep) - 1) * sd(sep)^2 +
                       (length(nov) - 1) * sd(nov)^2) /
                      (length(sep) + length(nov) - 2))
    
    d <- (mean(sep) - mean(nov)) / pooled_sd
    
    result_list[[cond]] <- data.frame(
      Variable        = var,
      Condition       = cond,
      September_M_SD  = sprintf("%.2f (%.2f)",
                                mean(sep, na.rm = TRUE),
                                sd(sep, na.rm = TRUE)),
      November_M_SD   = sprintf("%.2f (%.2f)",
                                mean(nov, na.rm = TRUE),
                                sd(nov, na.rm = TRUE)),
      p_value         = round(t_res$p.value, 3),
      Cohens_d        = round(d, 2),
      stringsAsFactors = FALSE
    )
  }
  
  do.call(rbind, result_list)
}

# Apply function to all variables and bind everything
results_cond <- do.call(rbind,
                        lapply(vars, compare_by_condition, data = combined_scales))

# Optional: order condition factor
results_cond$Condition <- factor(results_cond$Condition,
                                 levels = c("Nurse", "Soldier", "Weld"))

# Inspect
results_cond

Finally, we can nail the coffin in these exploratory analyses assessing the effect of the Heroic moral shield on target outcomes for Soldiers – as moderated by the season (Outcome ~ Heroism * SampleType, data = Soldiers). This effect would support a vaguely related hypothesis: “Remembrance commemorations will strenghten the moral shield previously observed” (which is different from our registered effect stating that remembrance commemorations will directly influence the outcomes – acting as a manipulation of heroism)

anova(mexpl1<-lm(Gratitude_G_mean ~ Heroism * SampleType, data = Soldiers))
anova(mexpl1<-lm(Gratitude_S_mean ~ Heroism * SampleType, data = Soldiers))
anova(mexpl1<-lm(criticism_items_G_mean ~ Heroism * SampleType, data = Soldiers))
anova(mexpl1<-lm(criticism_items_S_mean ~ Heroism * SampleType, data = Soldiers))
anova(mexpl1<-lm(DemandSupp_G_mean ~ Heroism * SampleType, data = Soldiers))
anova(mexpl1<-lm(DemandSupp_S_mean ~ Heroism * SampleType, data = Soldiers))
anova(mexpl1<-lm(Victim_G_mean ~ Heroism * SampleType, data = Soldiers))
anova(mexpl1<-lm(Victim_S_mean ~ Heroism * SampleType, data = Soldiers))
anova(mexpl1<-lm(Villain_G_mean ~ Heroism * SampleType, data = Soldiers))
anova(mexpl1<-lm(Villain_S_mean ~ Heroism * SampleType, data = Soldiers))

None of these analyses return a significant interaction. It really points to an absence of moderation of the season on the moral shield effect associated with heroism.

Conclusion

This study aimed at assessing the impact of remembrance day commemorations on the heroisation of soldiers and it’s consequences. In previous study, we found that heroism could act as a moral shield (decreasing criticism acceptability and increasing impunity/support for deregulation of the occupations). We wanted to see if these effects would be magnified during a season where veterans and fallen soldiers are commemorated accross the country.

Comparing to samples of equal sizes, we failed to observe evidence that soldiers are more heroised around remembrance day (11th of November) than mid-September. If anything, participants responding during the poppy season reported more negative attitudes toward soldiers, they were also more acceptant of hate speech and criticism of soldiers - and were slightly less likely to want to deregulate or protect soldiers breaking the rules from prosecutions.

These results are unexpected. Maybe prolific participants are tired of poppies? Maybe for a subgroup of the population, over-representated on Prolific, does the poppy season create a negative reaction?