shurakov2023trainflightretraction
/data/papers/shurakov2023trainflightretraction/analysis/effect_sizes.qmd
---
title: "Effect size computation: shurakov2023trainflightretraction"
format:
  html:
    toc: true
execute:
  echo: true
  warning: true
  message: false
---

```{r}
library(dplyr)
library(esc)
```

## Shared Helpers

```{r}
stop_if_missing <- function(x, name) {
  if (is.na(x)) stop(sprintf("Missing required input: %s", name), call. = FALSE)
}

compute_effect_size <- function(
    paper_key,
    study_id,
    effect_id,
    method_used,
    sign_convention = "d = mean(low) - mean(high)",
    n_high = NA_integer_,
    n_low = NA_integer_,
    mean_high = NA_real_,
    mean_low = NA_real_,
    sd_high = NA_real_,
    sd_low = NA_real_,
    notes_on_assumptions = "",
    imputed_flag = FALSE,
    needs_sensitivity = TRUE
) {
  d <- NA_real_
  v <- NA_real_
  g <- NA_real_
  v_g <- NA_real_
  computed_from_suggested <- NA_character_

  if (method_used == "between_groups") {
    computed_from_suggested <- "groups"
    stop_if_missing(n_high, "n_high")
    stop_if_missing(n_low, "n_low")
    stop_if_missing(mean_high, "mean_high")
    stop_if_missing(mean_low, "mean_low")
    stop_if_missing(sd_high, "sd_high")
    stop_if_missing(sd_low, "sd_low")

    es_d <- esc::esc_mean_sd(
      grp1m = mean_low, grp1sd = sd_low, grp1n = n_low,
      grp2m = mean_high, grp2sd = sd_high, grp2n = n_high,
      es.type = "d"
    )
    es_g <- esc::esc_mean_sd(
      grp1m = mean_low, grp1sd = sd_low, grp1n = n_low,
      grp2m = mean_high, grp2sd = sd_high, grp2n = n_high,
      es.type = "g"
    )

    d <- as.numeric(es_d$es)
    v <- as.numeric(es_d$var)
    g <- as.numeric(es_g$es)
    v_g <- as.numeric(es_g$var)
  } else {
    stop(sprintf("Unknown method_used: %s", method_used), call. = FALSE)
  }

  inputs_used <- paste(
    c(
      sprintf("method=%s", method_used),
      sprintf("sign_convention=%s", sign_convention),
      sprintf("n_low=%s", n_low),
      sprintf("n_high=%s", n_high),
      sprintf("mean_low=%s", mean_low),
      sprintf("mean_high=%s", mean_high),
      sprintf("sd_low=%s", sd_low),
      sprintf("sd_high=%s", sd_high)
    ),
    collapse = ", "
  )

  audit <- data.frame(
    paper_key = paper_key,
    study_id = study_id,
    effect_id = effect_id,
    design = "Between-Subjects",
    method_used = method_used,
    computed_from_suggested = computed_from_suggested,
    inputs_used = inputs_used,
    d = d,
    v = v,
    g = g,
    v_g = v_g,
    notes_on_assumptions = notes_on_assumptions,
    imputed_flag = imputed_flag,
    needs_sensitivity = needs_sensitivity
  )

  yaml_snippet <- sprintf(
    "effect_size:\n  metric: SMD\n  d: %.12f\n  v: %.12f\n  computed_from: %s\n  needs_review: false\n  notes: \"%s\"\n",
    d, v, computed_from_suggested, gsub(pattern = "\"", replacement = "'", x = inputs_used)
  )

  list(audit = audit, yaml_snippet = yaml_snippet)
}
```

## Source Data

The unpublished draft describes Experiment 3 as a Train/Flight extension of the retraction
design. The effect-size computation below follows the author-sent Qualtrics exports
`data/Finished_Responses.csv` and `data/Unfinished_Responses.csv`. Combining these files
reproduces the manuscript's reported Train/Flight Neutral and Stakes group descriptives more
closely than the timestamped export alone. The first two Qualtrics metadata rows are removed
by filtering to consenting responses with usable scenario data.

```{r}
read_qualtrics <- function(file) {
  dat <- read.csv(
    file.path("../data", file),
    na.strings = c("", "NA"),
    check.names = FALSE
  )
  dat$source_file <- file
  dat
}

raw <- bind_rows(
  read_qualtrics("Finished_Responses.csv"),
  read_qualtrics("Unfinished_Responses.csv")
)

dat <- raw %>%
  filter(Consent == "I voluntarily agree to participate in this research study.")

data.frame(
  physical_rows_after_header = nrow(raw),
  consenting_rows = nrow(dat)
)
```

## Composite Scores

Composite scores follow the draft and author analysis script: `I do` is coded as `+1`,
`I don't` as `-1`, multiplied by confidence from 1 to 7.

```{r}
make_composite <- function(response, confidence) {
  response_sign <- case_when(
    response == "I do" ~ 1,
    response == "I don't" ~ -1,
    TRUE ~ NA_real_
  )
  response_sign * as.numeric(confidence)
}

extract_condition <- function(dat, prefix, scenario, condition, condition_label) {
  response_col <- paste0(prefix, "_", condition, "_A")
  confidence_col <- paste0(prefix, "_", condition, "_B_1")

  data.frame(
    scenario = scenario,
    condition = condition_label,
    response = dat[[response_col]],
    confidence = suppressWarnings(as.numeric(dat[[confidence_col]])),
    composite = make_composite(dat[[response_col]], dat[[confidence_col]])
  ) %>%
    filter(!is.na(composite))
}

train_positive <- "The guy knows that the train stops at Kensington"
train_negative <- "The guy does not know that the train stops at Kensington"
flight_positive <- "The woman knows that the flight takes 14 hours"
flight_negative <- "The woman does not know that the flight takes 14 hours"

train_assigned <- dat %>% filter(TR_SU %in% c(train_positive, train_negative))
flight_assigned <- dat %>% filter(FL_SU %in% c(flight_positive, flight_negative))

train_valid <- train_assigned %>%
  filter(TR_SU == train_positive, TR_AT == "Kensington")

flight_valid <- flight_assigned %>%
  filter(FL_SU == flight_positive, FL_AT == "Tokyo")

screening_counts <- data.frame(
  scenario = c("Train", "Flight"),
  assigned = c(nrow(train_assigned), nrow(flight_assigned)),
  initial_knowledge_endorsers = c(nrow(filter(train_assigned, TR_SU == train_positive)), nrow(filter(flight_assigned, FL_SU == flight_positive))),
  initial_knowledge_skeptics = c(nrow(filter(train_assigned, TR_SU == train_negative)), nrow(filter(flight_assigned, FL_SU == flight_negative))),
  valid_knowledge_endorsers = c(nrow(train_valid), nrow(flight_valid)),
  attention_failures_among_endorsers = c(
    nrow(filter(train_assigned, TR_SU == train_positive, is.na(TR_AT) | TR_AT != "Kensington")),
    nrow(filter(flight_assigned, FL_SU == flight_positive, is.na(FL_AT) | FL_AT != "Tokyo"))
  )
)

screening_counts
```

```{r}
composites <- bind_rows(
  extract_condition(train_valid, "TR", "Train", "N", "Neutral"),
  extract_condition(train_valid, "TR", "Train", "S", "Stakes"),
  extract_condition(train_valid, "TR", "Train", "E", "Evidence"),
  extract_condition(flight_valid, "FL", "Flight", "N", "Neutral"),
  extract_condition(flight_valid, "FL", "Flight", "S", "Stakes"),
  extract_condition(flight_valid, "FL", "Flight", "E", "Evidence")
)

group_stats <- composites %>%
  group_by(scenario, condition) %>%
  summarise(
    n = n(),
    retract_n = sum(response == "I don't"),
    retract_rate = retract_n / n,
    mean = mean(composite),
    sd = sd(composite),
    .groups = "drop"
  )

group_stats
```

## Effect Computations

Only the Stakes vs Neutral contrasts are extracted as primary stakes effects. The Evidence
condition is retained above as a manipulation check and contextual comparison, but it is not
the focal low-vs-high stakes comparison.

```{r}
paper_key <- "shurakov2023trainflightretraction"
study_id <- 1

compute_from_stats <- function(effect_id, scenario_name) {
  low <- group_stats[group_stats$scenario == scenario_name & group_stats$condition == "Neutral", ]
  high <- group_stats[group_stats$scenario == scenario_name & group_stats$condition == "Stakes", ]

  compute_effect_size(
    paper_key = paper_key,
    study_id = study_id,
    effect_id = effect_id,
    method_used = "between_groups",
    n_high = high$n,
    n_low = low$n,
    mean_high = high$mean,
    mean_low = low$mean,
    sd_high = high$sd,
    sd_low = low$sd,
    notes_on_assumptions = "Computed from author-sent Finished and Unfinished Qualtrics exports after excluding non-consent, initial knowledge non-endorsers, and attention-check failures; effect size computed with esc::esc_mean_sd."
  )
}

res_s1_e1 <- compute_from_stats("s1_e1", "Train")
res_s1_e2 <- compute_from_stats("s1_e2", "Flight")
```

### Effect s1_e1: Train, Stakes vs Neutral

```{r}
res_s1_e1$audit
cat(res_s1_e1$yaml_snippet)
```

### Effect s1_e2: Flight, Stakes vs Neutral

```{r}
res_s1_e2$audit
cat(res_s1_e2$yaml_snippet)
```

## Audit Table

```{r}
audits <- rbind(res_s1_e1$audit, res_s1_e2$audit)
audits
```