Overview

This document runs the preregistered analyses on the Study 1 pilot data. Sections are labeled to match the preregistration’s section numbers. An additional Exploratory: Interaction Patterns section is included at the end, covering the in-person/online/alone analysis that is not part of the confirmatory preregistered analyses but is still of interest for the pilot check.

# install.packages(c("qualtRics", "dplyr", "tidyr", "purrr", "broom"))
library(qualtRics)
library(dplyr)
library(tidyr)
library(purrr)
library(broom)

0. Import data

qualtRics::read_survey() expects the standard 3-row Qualtrics CSV export (variable names / question text / ImportId JSON) and returns one row per response with columns already appropriately typed.

raw <- read_survey("~/Google drive/My Drive/YEAR 2/PROJECTS/SANDRA/AI Companionship/Cold Turkey Study/Expectations/Data/small_pilot.csv")
cat("Raw N (all rows, including previews/tests):", nrow(raw), "\n")
## Raw N (all rows, including previews/tests): 50

1. Remove preview / test responses

DistributionChannel == "preview" flags responses generated by researchers testing the survey (e.g., via the Qualtrics preview link), not real participants, and is dropped before applying the preregistered exclusion criteria.

raw <- raw %>% filter(DistributionChannel != "preview")
cat("N after removing preview responses:", nrow(raw), "\n")
## N after removing preview responses: 48

2. Preregistered exclusion criteria (Section 6)

Participants are excluded if they do not pass a standard attention-check, Qualtrics’ bot detection software, and/or Qualtrics’ duplicate-response detection, and/or if they complete the survey in less than 1 minute. Operationalized as:

Respondents screened out as ineligible before reaching the substantive survey blocks (and therefore missing all well-being data) are dropped as part of this step as well, since they were never part of the analytic sample to begin with.

clean <- raw %>%
  mutate(
    attn_bots_clean  = tolower(trimws(attn_bots)),
    fails_bot_trap   = grepl("14285733", attn_bots_clean, fixed = TRUE),
    reached_survey   = !is.na(r_depression_1),
    is_duplicate     = ifelse(is.na(Q_DuplicateRespondent), FALSE, Q_DuplicateRespondent),
    fails_recaptcha  = Q_RecaptchaScore < 0.5
  ) %>%
  filter(
    reached_survey,
    `Duration (in seconds)` >= 60,
    attn == 2,
    !fails_bot_trap,
    !is.na(attn_bots),
    !is_duplicate,
    !fails_recaptcha
  )

cat("Final analytic N after exclusions:", nrow(clean), "\n")
## Final analytic N after exclusions: 12

3. Construct scores (Section 3)

Reverse-score the two “(R)” items, then average the two items per construct to form a construct-level score (range 1-7), separately for the Retrospective (Block A, r_) and Forecasted (Block B, f_) item sets.

reverse_7pt <- function(x) 8 - x

clean <- clean %>%
  mutate(
    r_rse_1R      = reverse_7pt(r_rse_1R),
    f_rse_1R      = reverse_7pt(f_rse_1R),
    r_connect_1R  = reverse_7pt(r_connect_1R),
    f_connect_1R  = reverse_7pt(f_connect_1R)
  )

construct_items <- list(
  loneliness        = list(r = c("r_loneliness_1", "r_loneliness_2"),
                            f = c("f_loneliness_1", "f_loneliness_2")),
  depression        = list(r = c("r_depression_1", "r_depression_2"),
                            f = c("f_depression_1", "f_depression_2")),
  anxiety           = list(r = c("r_anxiety_1", "r_anxiety_2"),
                            f = c("f_anxiety_1", "f_anxiety_2")),
  stress            = list(r = c("r_stress_1", "r_stress_2"),
                            f = c("f_stress_1", "f_stress_2")),
  happiness         = list(r = c("r_happiness_1", "r_happiness_2"),
                            f = c("f_happiness_1", "f_happiness_2")),
  life_satisfaction = list(r = c("r_swl_1", "r_swl_2"),
                            f = c("f_swl_1", "f_swl_2")),
  self_esteem       = list(r = c("r_rse_1R", "r_rse_2"),
                            f = c("f_rse_1R", "f_rse_2")),
  irq               = list(r = c("r_IRQ_1", "r_IRQ_2"),
                            f = c("f_IRQ_1", "f_IRQ_2")),
  connectedness     = list(r = c("r_connect_1R", "r_connect_2"),
                            f = c("f_connect_1R", "f_connect_2"))
)

for (construct in names(construct_items)) {
  r_cols <- construct_items[[construct]]$r
  f_cols <- construct_items[[construct]]$f
  clean[[paste0(construct, "_retro")]]    <- rowMeans(clean[, r_cols], na.rm = FALSE)
  clean[[paste0(construct, "_forecast")]] <- rowMeans(clean[, f_cols], na.rm = FALSE)
  clean[[paste0(construct, "_diff")]]     <- clean[[paste0(construct, "_forecast")]] -
                                              clean[[paste0(construct, "_retro")]]
}

4. Primary analysis (Section 5)

For each of the nine outcomes, we compare forecasted scores (Block B) with retrospective scores (Block A) using paired-samples t-tests, calculate difference scores as Forecasted − Retrospective, and report mean differences and effect sizes. Benjamini-Hochberg FDR correction (q = .05) is applied across the nine tests as a single family.

cohens_dz <- function(diff) mean(diff, na.rm = TRUE) / sd(diff, na.rm = TRUE)

primary_results <- map_dfr(names(construct_items), function(construct) {
  retro    <- clean[[paste0(construct, "_retro")]]
  forecast <- clean[[paste0(construct, "_forecast")]]
  diff     <- clean[[paste0(construct, "_diff")]]

  test <- t.test(forecast, retro, paired = TRUE)

  tibble(
    construct = construct,
    n         = sum(!is.na(diff)),
    mean_diff = mean(diff, na.rm = TRUE),
    t         = unname(test$statistic),
    df        = unname(test$parameter),
    p         = test$p.value,
    dz        = cohens_dz(diff)
  )
}) %>%
  mutate(q_BH = p.adjust(p, method = "BH"))

knitr::kable(primary_results, digits = 3)
construct n mean_diff t df p dz q_BH
loneliness 12 1.167 2.461 11 0.032 0.710 0.142
depression 12 0.958 2.130 11 0.057 0.615 0.168
anxiety 12 0.750 1.358 11 0.202 0.392 0.227
stress 12 1.000 1.970 11 0.074 0.569 0.168
happiness 12 -1.417 -2.976 11 0.013 -0.859 0.114
life_satisfaction 12 -0.708 -1.468 11 0.170 -0.424 0.219
self_esteem 12 -0.375 -1.567 11 0.145 -0.452 0.219
irq 12 0.333 0.763 11 0.461 0.220 0.461
connectedness 12 -0.417 -1.520 11 0.157 -0.439 0.219

5. Secondary analysis (Section 8, part 1)

We test whether participants’ expectations about abstaining from their AI companion — forecasted difficulty and forecasted missing — predict their forecasted-minus-retrospective difference scores across the nine constructs. Run as separate simple regressions (one predictor per model) for each construct; unadjusted p-values, no FDR correction for this secondary section.

secondary_predictors <- c("f_difficult", "f_miss")

secondary_results <- map_dfr(names(construct_items), function(construct) {
  diff_col <- paste0(construct, "_diff")

  map_dfr(secondary_predictors, function(predictor) {
    model <- lm(clean[[diff_col]] ~ clean[[predictor]])
    coefs <- tidy(model)
    slope_row <- coefs[coefs$term == "clean[[predictor]]", ]

    tibble(
      construct = construct,
      predictor = predictor,
      n         = sum(!is.na(clean[[diff_col]]) & !is.na(clean[[predictor]])),
      estimate  = slope_row$estimate,
      se        = slope_row$std.error,
      p         = slope_row$p.value
    )
  })
})

knitr::kable(secondary_results, digits = 3)
construct predictor n estimate se p
loneliness f_difficult 12 -0.211 0.479 0.670
loneliness f_miss 12 0.610 0.375 0.135
depression f_difficult 12 0.882 0.365 0.036
depression f_miss 12 0.890 0.285 0.011
anxiety f_difficult 12 0.829 0.499 0.128
anxiety f_miss 12 0.870 0.407 0.058
stress f_difficult 12 0.789 0.454 0.113
stress f_miss 12 0.900 0.351 0.028
happiness f_difficult 12 -0.697 0.433 0.138
happiness f_miss 12 -0.790 0.342 0.044
life_satisfaction f_difficult 12 -0.526 0.464 0.283
life_satisfaction f_miss 12 -0.620 0.382 0.136
self_esteem f_difficult 12 -0.355 0.217 0.133
self_esteem f_miss 12 -0.450 0.159 0.018
irq f_difficult 12 0.487 0.418 0.272
irq f_miss 12 0.050 0.388 0.900
connectedness f_difficult 12 -0.224 0.271 0.428
connectedness f_miss 12 -0.250 0.231 0.304

6. Exploratory: Interaction Patterns

Not part of the confirmatory preregistered analyses, but included here for the pilot check. For each of the three interaction variables (in-person, online, alone), we compare forecasted scores with retrospective scores using paired-samples t-tests and report mean differences and effect sizes. No FDR correction applied.

interaction_vars <- c(interaction = "interaction", online = "online", alone = "alone")

interaction_results <- map_dfr(names(interaction_vars), function(label) {
  var   <- interaction_vars[[label]]
  r_col <- paste0("r_", var)
  f_col <- paste0("f_", var)

  retro    <- clean[[r_col]]
  forecast <- clean[[f_col]]
  diff     <- forecast - retro

  test <- t.test(forecast, retro, paired = TRUE)

  tibble(
    variable  = label,
    n         = sum(!is.na(diff)),
    mean_diff = mean(diff, na.rm = TRUE),
    t         = unname(test$statistic),
    df        = unname(test$parameter),
    p         = test$p.value,
    dz        = cohens_dz(diff)
  )
})

knitr::kable(interaction_results, digits = 3)
variable n mean_diff t df p dz
interaction 12 -0.083 -0.432 11 0.674 -0.125
online 12 0.167 1.000 11 0.339 0.289
alone 12 0.083 0.290 11 0.777 0.084

7. Save cleaned data + results (optional)

write.csv(clean, "clean_pilot_data.csv", row.names = FALSE)
write.csv(primary_results, "primary_results.csv", row.names = FALSE)
write.csv(secondary_results, "secondary_results.csv", row.names = FALSE)
write.csv(interaction_results, "interaction_results.csv", row.names = FALSE)