This notebook analyses the German PC scale validation dataset.
| Variable | Description |
|---|---|
| SK01 | Hand lowering (0 = Normal severity, 5 = Very severe) |
| SK02 | Moving hands together (0 = No force, 5 = Strong force) |
| SK03 | Mosquito hallucination (0 = No mosquito, 5 = Like a real mosquito) |
| SK04 | Sweet taste (0 = No taste, 5 = Strong taste) |
| SK05 | Sour taste (0 = No taste, 5 = Strong taste) |
| SK04SK05 | Taste mean (calculated in the script) |
| SK06 | Arm rigidity (0 = Normal / no stiffness, 5 = Very stiff) |
| SK07 | Arm immobilization (0 = Normal / no heaviness, 5 = Very heavy) |
| SK08 | Music hallucination (0 = No music, 5 = Clear music) |
| SK09 | Negative visual hallucination (0 = Three balls seen, 5 = Two balls seen) |
| SK10 | Amnesia (0 = Normal memory, 5 = No memory) |
| SK12 | Urge to press space bar (0 = No urge, 5 = Clear urge) |
| SK13 | Space bar amnesia (0 = Normal memory of instructions, 5 = No memory of instructions) |
| SK12SK13 | Space bar mean (calculated in the script) |
| KK02_01 | Items remembered before being asked again |
| KK03_01 | Items remembered after being asked again |
| SO01 | Gender (1 = female, 2 = male, 3 = diverse) |
| SO02_01 | Age |
| SC02 | Answered all questions seriously (0 = yes, 2 = no) |
| TIME005 | Time needed to complete the experiment (seconds) |
knitr::opts_chunk$set(
echo = TRUE,
warning = FALSE,
message = FALSE,
fig.width = 8,
fig.height = 5,
fig.align = "center"
)
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.1 ✔ stringr 1.6.0
## ✔ ggplot2 4.0.2 ✔ tibble 3.2.1
## ✔ lubridate 1.9.4 ✔ tidyr 1.3.2
## ✔ purrr 1.0.4
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(Hmisc)
##
## Attache Paket: 'Hmisc'
##
## Die folgenden Objekte sind maskiert von 'package:dplyr':
##
## src, summarize
##
## Die folgenden Objekte sind maskiert von 'package:base':
##
## format.pval, units
library(psych)
##
## Attache Paket: 'psych'
##
## Das folgende Objekt ist maskiert 'package:Hmisc':
##
## describe
##
## Die folgenden Objekte sind maskiert von 'package:ggplot2':
##
## %+%, alpha
library(ggdist)
library(patchwork)
library(ggthemr)
ggthemr("flat")
## Warning: The `size` argument of `element_line()` is deprecated as of ggplot2 3.4.0.
## ℹ Please use the `linewidth` argument instead.
## ℹ The deprecated feature was likely used in the ggthemr package.
## Please report the issue to the authors.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: The `size` argument of `element_rect()` is deprecated as of ggplot2 3.4.0.
## ℹ Please use the `linewidth` argument instead.
## ℹ The deprecated feature was likely used in the ggthemr package.
## Please report the issue to the authors.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
dat <- read.csv("GitHub/german_pc_scale_validation_data.CSV",
sep = ";")
dat$CASE |> unique() |> length()
## [1] 357
# Participant answered all questions meaningfully
dat |> count(SC02)
outlierMeaningful <- dat[dat$SC02 == 2 ,]$CASE
# Exclude participants how have taken less than 15min for the study (indicating that they skipped parts of the videos)
timeTaken <- dat$TIME005
timeTaken <- timeTaken[!is.na(timeTaken)]
lower_bound <- min(timeTaken, 800) - 1e-8
upper_bound <- max(timeTaken, 1500) + 1e-8
breaks <- c(lower_bound, seq(800, 1500, by = 100), upper_bound)
ggplot(data.frame(x = timeTaken), aes(x = x)) +
geom_histogram(breaks = breaks,
closed = "left") +
geom_vline(xintercept = 900, color = "red", size = 1) +
coord_cartesian(xlim = c(800, 1500)) +
labs(x = "Time taken",
y = "Count")
outlierTime <- dat[dat$TIME005 < 900 ,]$CASE
# remove outlier from dataset
dat <- dat[! dat$CASE %in% outlierMeaningful, ]
dat <- dat[! dat$CASE %in% outlierTime, ]
outlierMeaningful |> unique() |> length()
## [1] 2
outlierTime |> unique() |> length()
## [1] 57
c(outlierMeaningful, outlierTime) |> unique() |> length()
## [1] 58
dat$CASE|> unique() |> length()
## [1] 299
# Refactor columns to factors
cols <- c("SK01","SK02","SK03","SK04","SK05", "SK06","SK07","SK08","SK09","SK10", "SK12","SK13")
dat[cols] <- lapply(dat[cols], function(x) as.numeric(x) - 1)
# Label data
label(dat$SK01) <- "Hand lowering"
label(dat$SK02) <- "Moving hands together"
label(dat$SK03) <- "Mosquito hallucination"
label(dat$SK04) <- "Sweet taste"
label(dat$SK05) <- "Sour taste"
label(dat$SK06) <- "Arm rigidity"
label(dat$SK07) <- "Arm immobilization"
label(dat$SK08) <- "Music hallucination"
label(dat$SK09) <- "Negative visual hallucination"
label(dat$SK10) <- "Amnesia"
label(dat$SK12) <- "Urge to press space bar"
label(dat$SK13) <- "Space bar amnesia"
label(dat$KK02_01) <- "Items remembered before being asked again"
label(dat$KK03_01) <- "Items remembered after being asked again"
label(dat$SO01) <- "Gender"
label(dat$SO02_01) <- "Age"
label(dat$SC02) <- "Answered all questions seriously"
label(dat$TIME005) <- "Completion time (experiment duration)"
# calculate item 4 (arithmetic mean of taste sweet and sour) and 10 (geometric mean of Urge to press space bar and space bar amnesia)
dat <- dat |> mutate(SK04SK05 = (SK04 + SK05) / 2)
label(dat$SK04SK05) <- "Taste Halluctionation"
dat <- dat |> mutate(SK12SK13 = sqrt(SK12 * SK13))
label(dat$SK12SK13) <- "Post-session suggestion"
#function to get bootstrapped drop item cronbach's alpha
alpha_drop_boot_ci <- function(df, B = 2000, conf = 0.95, seed = NULL, warnings = FALSE, ...) {
if (!is.null(seed)) {
set.seed(seed)
}
if (!requireNamespace("psych", quietly = TRUE)) {
stop("Package 'psych' must be installed.")
}
if (!is.data.frame(df) && !is.matrix(df)) {
stop("'df' must be a data frame or matrix containing only the scale items.")
}
df <- as.data.frame(df)
if (ncol(df) < 2) {
stop("At least 2 items are required.")
}
if (!all(vapply(df, is.numeric, logical(1)))) {
stop("All columns in 'df' must be numeric.")
}
if (!is.numeric(B) || length(B) != 1 || B < 1) {
stop("'B' must be a positive integer.")
}
B <- as.integer(B)
if (!is.numeric(conf) || length(conf) != 1 || conf <= 0 || conf >= 1) {
stop("'conf' must be a single number between 0 and 1.")
}
alpha_level <- 1 - conf
probs <- c(alpha_level / 2, 1 - alpha_level / 2)
# Original estimates
a0 <- psych::alpha(df, warnings = warnings, ...)
item_names <- rownames(a0$alpha.drop)
drop_est <- a0$alpha.drop$raw_alpha
names(drop_est) <- item_names
# Bootstrap matrix: rows = resamples, cols = items
boot_drop <- matrix(NA_real_, nrow = B, ncol = length(drop_est))
colnames(boot_drop) <- item_names
for (b in seq_len(B)) {
idx <- sample.int(nrow(df), size = nrow(df), replace = TRUE)
boot_drop[b, ] <- tryCatch({
ab <- psych::alpha(df[idx, , drop = FALSE], warnings = warnings, ...)
vals <- ab$alpha.drop$raw_alpha
names(vals) <- rownames(ab$alpha.drop)
vals <- vals[item_names]
as.numeric(vals)
}, error = function(e) {
rep(NA_real_, length(drop_est))
})
}
# Percentile bootstrap CIs
cis <- t(apply(boot_drop, 2, function(x) {
if (all(is.na(x))) {
c(NA_real_, NA_real_)
} else {
quantile(x, probs = probs, na.rm = TRUE, names = FALSE)
}
}))
out <- data.frame(
item_dropped = item_names,
alpha_if_dropped = as.numeric(drop_est[item_names]),
ci_lower = cis[, 1],
ci_upper = cis[, 2],
row.names = NULL
)
out
}
cit_boot_ci <- function(df, B = 2000, conf = 0.95, seed = NULL) {
if (!is.null(seed)) set.seed(seed)
if (!is.data.frame(df) && !is.matrix(df)) {
stop("'df' must be a data frame or matrix containing only the scale items.")
}
df <- as.data.frame(df)
if (ncol(df) < 2) stop("At least 2 items are required.")
if (!all(vapply(df, is.numeric, logical(1)))) {
stop("All columns in 'df' must be numeric.")
}
if (!is.numeric(B) || length(B) != 1 || B < 1) {
stop("'B' must be a positive integer.")
}
B <- as.integer(B)
if (!is.numeric(conf) || length(conf) != 1 || conf <= 0 || conf >= 1) {
stop("'conf' must be a single number between 0 and 1.")
}
items <- names(df)
cit_fun <- function(dat) {
sapply(items, function(item) {
other_items <- setdiff(items, item)
total_other_mean <- rowMeans(
dat[, other_items, drop = FALSE],
na.rm = TRUE
)
cor(
dat[[item]],
total_other_mean,
use = "pairwise.complete.obs"
)
})
}
# Original estimates
cit0 <- cit_fun(df)
alpha_level <- 1 - conf
probs <- c(alpha_level / 2, 1 - alpha_level / 2)
# Bootstrap estimates
boot_cit <- matrix(NA_real_, nrow = B, ncol = length(items))
colnames(boot_cit) <- items
for (b in seq_len(B)) {
idx <- sample.int(nrow(df), size = nrow(df), replace = TRUE)
boot_cit[b, ] <- tryCatch({
cit_fun(df[idx, , drop = FALSE])
}, error = function(e) {
rep(NA_real_, length(items))
})
}
# Percentile bootstrap CIs
cis <- t(apply(boot_cit, 2, function(x) {
if (all(is.na(x))) {
c(NA_real_, NA_real_)
} else {
quantile(x, probs = probs, na.rm = TRUE, names = FALSE)
}
}))
data.frame(
item = items,
corrected_item_total = as.numeric(cit0[items]),
ci_lower = cis[, 1],
ci_upper = cis[, 2],
row.names = NULL
)
}
# Items to include
items <- c("SK01","SK02","SK03","SK04SK05","SK06","SK07","SK08","SK09","SK10","SK12SK13")
# c("SK01","SK02","SK03","SK04","SK05",
# "SK06","SK07","SK08","SK09","SK10",
# "SK12","SK13","SK04SK05","SK12SK13")
# Subset data
dat_items <- dat[, items]
# Cronbach's alpha
alpha_res <- psych::alpha(dat_items, n.iter=5000)
# 95% C for # Cronbach's alpha
alpha_res$boot.ci
## 2.5% 50% 97.5%
## 0.6697629 0.7246929 0.7682909
# Extract stats
means <- colMeans(dat_items, na.rm = TRUE)
sds <- apply(dat_items, 2, sd, na.rm = TRUE)
# Skewness and Kurtosis
skew <- apply(dat_items, 2, psych::skew, na.rm = TRUE)
kurt <- apply(dat_items, 2, psych::kurtosi, na.rm = TRUE)
# Alpha if item removed
alpha_drop <- alpha_res$alpha.drop
# Bootstrap 95% CI for alpha-if-item-dropped
drop_boot <- alpha_drop_boot_ci(dat_items, B = 5000, seed = 123)
drop_boot
# Calculate PC score per person
PC <- dat |>
mutate(
PC = rowMeans(
across(c(SK01, SK02, SK03,
SK06, SK07, SK08, SK09, SK10,
SK04SK05, SK12SK13))
)
) |>
select(CASE, PC)
# Item-total correlation: each item correlated with PC
item_total <- sapply(items, function(x) {
cor(dat[[x]], PC$PC, use = "pairwise.complete.obs" , method = "pearson")
})
result <- data.frame(
item_code = items,
Item = sapply(items, function(x) Hmisc::label(dat[[x]])),
Mean = round(means, 2),
SD = round(sds, 2),
Skew = round(skew, 2),
Kurt = round(kurt, 2),
Alpha = round(alpha_drop$raw_alpha, 2),
CI_Low = round(drop_boot$ci_lower, 2),
CI_Up = round(drop_boot$ci_upper, 2),
ItemTotal = round(item_total, 2)
)
result
corrected_item_total <- sapply(items, function(item) {
other_items <- setdiff(items, item)
total_other_mean <- rowMeans(dat_items[, other_items], na.rm = TRUE)
cor(dat_items[[item]], total_other_mean, use = "pairwise.complete.obs")
})
corrected_ItemTotal_CIs <- cit_boot_ci(dat_items, B = 2000, conf = 0.95, seed = 123)
result |>
left_join(
data.frame(
item_code = items,
corrected_ItemTotal = round(corrected_item_total, 2),
row.names = NULL
)
) %>%
left_join(corrected_ItemTotal_CIs %>% rename(item_code = item) %>% select(-c(corrected_item_total)) %>% mutate(ci_lower = round(ci_lower, 2), ci_upper = round(ci_upper, 2)))
a0 <- psych::alpha(dat_items, warnings = TRUE)
a0
##
## Reliability analysis
## Call: psych::alpha(x = dat_items, warnings = TRUE)
##
## raw_alpha std.alpha G6(smc) average_r S/N ase mean sd median_r
## 0.73 0.71 0.73 0.2 2.5 0.023 1.8 0.73 0.16
##
## 95% confidence boundaries
## lower alpha upper
## Feldt 0.68 0.73 0.77
## Duhachek 0.68 0.73 0.77
##
## Reliability if an item is dropped:
## raw_alpha std.alpha G6(smc) average_r S/N alpha se var.r med.r
## SK01 0.68 0.67 0.68 0.18 2.0 0.027 0.017 0.15
## SK02 0.70 0.69 0.70 0.20 2.2 0.025 0.019 0.17
## SK03 0.70 0.68 0.71 0.19 2.2 0.025 0.027 0.15
## SK04SK05 0.71 0.69 0.71 0.20 2.2 0.024 0.028 0.15
## SK06 0.66 0.65 0.67 0.17 1.9 0.029 0.017 0.15
## SK07 0.67 0.66 0.68 0.18 2.0 0.028 0.020 0.15
## SK08 0.72 0.71 0.73 0.22 2.5 0.023 0.026 0.18
## SK09 0.73 0.72 0.74 0.22 2.6 0.023 0.026 0.18
## SK10 0.71 0.69 0.71 0.20 2.3 0.024 0.029 0.14
## SK12SK13 0.74 0.73 0.74 0.23 2.7 0.022 0.024 0.18
##
## Item statistics
## n raw.r std.r r.cor r.drop mean sd
## SK01 299 0.65 0.64 0.62 0.53 3.56 1.3
## SK02 299 0.57 0.55 0.50 0.42 3.10 1.5
## SK03 299 0.57 0.56 0.48 0.41 1.54 1.4
## SK04SK05 299 0.54 0.53 0.43 0.37 1.87 1.5
## SK06 299 0.73 0.70 0.70 0.60 2.92 1.6
## SK07 299 0.69 0.66 0.64 0.55 2.52 1.5
## SK08 299 0.38 0.41 0.29 0.23 0.58 1.2
## SK09 299 0.32 0.38 0.23 0.19 0.34 1.0
## SK10 299 0.50 0.52 0.42 0.35 1.23 1.3
## SK12SK13 299 0.32 0.34 0.19 0.14 0.81 1.3
a0$alpha.drop
a0$total
# Calculacte PC Score per person
PC <- dat |> mutate(PC = rowMeans(across(c(SK01,SK02,SK03,
SK06,SK07,SK08,SK09,SK10,
SK04SK05,SK12SK13)))) |>
select(CASE, PC)
PC
percentile_rank <- function(sample, value) {
100 * sum(sample <= value) / length(sample)
}
percentile_rank(PC$PC, 2.8)
## [1] 92.97659
# age
mean(dat$SO02_01)
## [1] 31.65217
range(dat$SO02_01)
## [1] 18 72
sd(dat$SO02_01)
## [1] 12.35083
# Gender (1=female, 2=male, 3=diverse)
dat |> count(SO01)
# Time taken
median(dat$TIME005)/60
## Completion time (experiment duration)
## [1] 16
min(dat$TIME005)/60
## [1] 15.51667
max(dat$TIME005)/60
## [1] 65.35
# Histogram of PC Scores with Density
ggplot(PC, aes(x = PC)) +
geom_histogram(aes(y = after_stat(density)),
color = "black",
fill = "gray",
binwidth = 0.5,
boundary = 0,
alpha = 0.7) +
geom_density(linewidth = 1.2) +
labs(x = "PC-Ger Score",
y = "Density") +
theme(
axis.title = element_text(face = "bold", size = 14)
)
# raincloud plot for PC
my_col <- "#4C72B0"
ggplot(PC, aes(x = PC, y = "")) +
stat_slab(
fill = my_col,
alpha = 0.7,
adjust = .5,
side = "top",
color = NA
) +
stat_dots(
fill = my_col,
alpha = 0.7,
side = "bottom",
justification = 1,
binwidth = 0.1,
dotsize = .7,
stackratio = 0.8
) +
geom_boxplot(
fill = my_col,
alpha = 1,
width = 0.12,
outlier.shape = NA,
color = "black"
) +
labs(
x = "PC-Ger Score",
y = "Density"
) +
theme(
axis.title = element_text(face = "bold", size = 14),
axis.text.y = element_blank(),
axis.ticks.y = element_blank()
)
items <- c("SK01","SK02","SK03","SK04SK05","SK06",
"SK07","SK08","SK09","SK10","SK12SK13")
var_labels <- c(
SK01 = "Hand\nlowering",
SK02 = "Moving\nhands\ntogether",
SK03 = "Mosquito\nhallucination",
SK04 = "Sweet\ntaste",
SK05 = "Sour\ntaste",
SK06 = "Arm\nrigidity",
SK07 = "Arm\nimmobilisation",
SK08 = "Music\nhallucination",
SK09 = "Negative\nvisual\nhallucination",
SK10 = "Amnesia",
SK12 = "Urge to\npress\nspace bar",
SK13 = "Space bar\namnesia",
SK04SK05 = "Taste\nhallucination",
SK12SK13 = "Post-session\nsuggestion"
)
label_numbers <- c(
SK01 = "1",
SK02 = "2",
SK03 = "3",
SK04SK05 = "4",
SK06 = "5",
SK07 = "6",
SK08 = "7",
SK09 = "8",
SK10 = "9",
SK12SK13 = "10"
)
var_labels_num <- setNames(
paste0(label_numbers[names(var_labels)], ". ", var_labels),
names(var_labels)
)
vars_left <- c("SK01","SK02","SK03","SK04SK05", "SK06")
vars_right <- c("SK07","SK08","SK09","SK10","SK12SK13")
singleItemPlot_data <- dat |>
select(CASE, all_of(items)) %>%
mutate(across(everything(), ~ as.numeric(.))) %>%
pivot_longer(cols = -CASE,
names_to = "Item",
values_to = "value")
make_plot <- function(data, vars) {
data_filtered <- data %>%
filter(Item %in% vars) %>%
mutate(Item = factor(Item, levels = rev(vars)))
ggplot() +
# Layer 2: SK12SK13 with different parameters
geom_dots(
data = data_filtered %>% filter(Item == "SK12SK13"),
aes(x = value, y = Item, fill = Item, color = Item),
smooth = smooth_discrete(width = 3.5, size = .2),
binwidth = 0.1,
dotsize = .65
) +
# Layer 1: all items except SK12SK13
geom_dots(
data = data_filtered %>% filter(Item != "SK12SK13"),
aes(x = value, y = Item, fill = Item, color = Item),
smooth = smooth_discrete(width = .8, size = .1),
binwidth = 0.1,
dotsize = .65
) +
scale_y_discrete(labels = var_labels_num, expand = expansion(mult = c(0, 0))) +
scale_x_continuous(breaks = 0:5) +
scale_fill_manual(values = c(
SK01 = "#2ECC71FF",
SK02 = "#F1C40FFF",
SK03 = "#E74C3CFF",
SK04SK05 = "#9B59B6FF",
SK06 = "#1ABC9CFF",
SK07 = "#db00b6",
SK08 = "#D35400FF",
SK09 = "#65f7f4",
SK10 = "#1f78b4",
SK12SK13 = "#666666"
)) +
scale_color_manual(values = c(
SK01 = "#2ECC71FF",
SK02 = "#F1C40FFF",
SK03 = "#E74C3CFF",
SK04SK05 = "#9B59B6FF",
SK06 = "#1ABC9CFF",
SK07 = "#db00b6",
SK08 = "#D35400FF",
SK09 = "#65f7f4",
SK10 = "#1f78b4",
SK12SK13 = "#666666"
)) +
labs(
x = "PC-Ger Score",
y = NULL
) +
theme_minimal() +
theme(
axis.title = element_text(face = "bold", size = 24),
axis.text = element_text(face = "bold", size = 24),
axis.text.y = element_text(
face = "bold",
size = 24,
hjust = 0,
margin = margin(r = 10)
),
legend.position = "none"
)
}
p1 <- make_plot(singleItemPlot_data, vars_left)
p2 <- make_plot(singleItemPlot_data, vars_right)
p1 + p2
Lush_dat <- read.csv("PeteOSF/PCS_norms_first_test_data.csv", sep = ",")
Lush_dat <- Lush_dat %>%
filter(exclude == 0) %>%
filter(Condition == 0)
rename_map <- c(
ParticipantNumber = "CASE",
HandLoweringSubjectiveRating = "SK01",
HandsTogetherSubjectiveRating = "SK02",
MosquitoSubjectiveRating = "SK03",
SweetSubRating = "SK04",
SourSubRating = "SK05",
ArmRigiditySubjectiveRating = "SK06",
ArmImmobilisationSubjectiveRating = "SK07",
MusicSubjectiveRating = "SK08",
VisualHallucinationSubjectiveRating = "SK09",
AmnesiaSubjectiveRating = "SK10",
PostHypnoticSub1 = "SK12",
PostHypnoticSub2 = "SK13",
TasteSubjectiveRating = "SK04SK05",
PostHypnoticSubjectiveRating = "SK12SK13"
)
label_map <- c(
SK01 = "Hand lowering",
SK02 = "Moving hands together",
SK03 = "Mosquito hallucination",
SK04 = "Sweet taste",
SK05 = "Sour taste",
SK06 = "Arm rigidity",
SK07 = "Arm immobilization",
SK08 = "Music hallucination",
SK09 = "Negative visual hallucination",
SK10 = "Amnesia",
SK12 = "Urge to press space bar",
SK13 = "Space bar amnesia",
SK0405 = "Taste Halluctionation",
SK1213 = "Post-session suggestion"
)
matched <- intersect(names(rename_map), names(Lush_dat))
names(Lush_dat)[match(matched, names(Lush_dat))] <- rename_map[matched]
# Items to include
items <- c("SK01","SK02","SK03","SK04SK05","SK06","SK07","SK08","SK09","SK10","SK12SK13")
itemsPC <- c("SK01","SK02","SK03","SK04SK05","SK06","SK07","SK08","SK09","SK10","SK12SK13", "PC")
Lush_dat <- Lush_dat %>%
select(CASE, all_of(items)) %>%
mutate(PC = rowMeans(across(all_of(items), ~ as.numeric(.)))) %>%
mutate(group = "Original") %>%
mutate(across(all_of(items), ~ as.numeric(.)))
Ger_dat <- dat %>%
select(CASE, all_of(items)) %>%
mutate(PC = rowMeans(across(all_of(items), ~ as.numeric(.)))) %>%
mutate(group = "German") %>%
mutate(across(all_of(items), ~ as.numeric(.)))
combined_dat <- bind_rows(Lush_dat, Ger_dat) %>%
select(CASE, group, SK01, SK02, SK03, SK04SK05, SK06, SK07, SK08, SK09, SK10, SK12SK13, PC)
for (nm in intersect(names(label_map), names(combined_dat))) {
Hmisc::label(combined_dat[[nm]]) <- label_map[[nm]]
}
combined_dat_long <- combined_dat %>%
mutate(across(all_of(itemsPC), ~ as.numeric(haven::zap_labels(.x)))) %>%
pivot_longer(
cols = all_of(itemsPC),
names_to = "Item",
values_to = "value"
)
item_labels <- sapply(items, function(x) Hmisc::label(combined_dat[[x]]))
item_desc <- combined_dat %>%
group_by(group) %>%
group_modify(~{
dat_items <- .x[, items, drop = FALSE]
# Cronbach's alpha
alpha_res <- psych::alpha(dat_items, n.iter = 5000)
alpha_drop <- alpha_res$alpha.drop
# Bootstrap CI for alpha if item dropped
drop_boot <- alpha_drop_boot_ci(dat_items, B = 5000, seed = 123)
# Item descriptives
means <- colMeans(dat_items, na.rm = TRUE)
sds <- apply(dat_items, 2, sd, na.rm = TRUE)
skew <- apply(dat_items, 2, psych::skew, na.rm = TRUE)
kurt <- apply(dat_items, 2, psych::kurtosi, na.rm = TRUE)
# Person composite score within group
PC <- .x %>%
mutate(
PC = rowMeans(across(all_of(items)), na.rm = TRUE)
) %>%
pull(PC)
# Item-total correlation within group
item_total <- sapply(items, function(x) {
cor(.x[[x]], PC, use = "pairwise.complete.obs", method = "pearson")
})
data.frame(
item_code = items,
Item = unname(item_labels),
Mean = round(means, 2),
SD = round(sds, 2),
Skew = round(skew, 2),
Kurt = round(kurt, 2),
Alpha = round(alpha_drop$raw_alpha, 2),
CI_Low = round(drop_boot$ci_lower, 2),
CI_Up = round(drop_boot$ci_upper, 2),
ItemTotal = round(item_total, 2)
)
}) %>%
ungroup()
corrected_by_group <- combined_dat %>%
group_by(group) %>%
group_modify(~ {
dat <- .x
vals <- sapply(items, function(item) {
other_items <- setdiff(items, item)
total_other_mean <- rowMeans(dat[, other_items], na.rm = TRUE)
cor(dat[[item]],
total_other_mean,
use = "pairwise.complete.obs")
})
data.frame(
item_code = items,
corrected_item_total = round(vals, 2)
)
}) %>%
ungroup()
corrected_by_group_CIs <- combined_dat %>%
group_by(group) %>%
group_modify(~{
dat_items <- .x[, items, drop = FALSE]
CIs <- cit_boot_ci(dat_items, B = 2000, conf = 0.95, seed = 123)
}) %>%
ungroup()
item_desc |> left_join(
corrected_by_group
) %>%
left_join(corrected_by_group_CIs %>% rename(item_code = item) %>% select(-c(corrected_item_total)) %>% mutate(ci_lower = round(ci_lower, 2), ci_upper = round(ci_upper, 2)))
ks_results <- combined_dat_long |>
group_by(Item) |>
summarise(
ks_stat = ks.test(
value[group == "Original"],
value[group == "German"]
)$statistic,
p_value = ks.test(
value[group == "Original"],
value[group == "German"]
)$p.value,
.groups = "drop"
) |>
mutate(significant = p_value < .05)
ks_results
label_map <- c(
SK01 = "1) Hand lowering",
SK02 = "2) Moving hands together",
SK03 = "3) Mosquito hallucination",
SK04SK05 = "4) Taste Hallucination",
SK06 = "5) Arm rigidity",
SK07 = "6) Arm immobilization",
SK08 = "7) Music hallucination",
SK09 = "8) Negative visual hallucination",
SK10 = "9) Amnesia",
SK12SK13 = "10) Post-session suggestion"
)
create_hist_plot <- function(var_name) {
bins <- if (var_name %in% c("SK04SK05", "SK12SK13")) 11 else 6
ggplot(
combined_dat %>%
filter(group %in% c("German", "Original")),
aes(
x = .data[[var_name]],
color = group,
fill = group
)
) +
geom_histogram(
aes(y = after_stat(density)),
alpha = 0.2,
position = "identity",
bins = bins
) +
labs(
title = label_map[[var_name]],
x = NULL,
y = "Density",
color = "Dataset",
fill = "Dataset"
) +
scale_fill_manual(values = c(
German = "orange",
Original = "royalblue"
)) +
scale_color_manual(values = c(
German = "orange",
Original = "royalblue"
)) +
theme_minimal() +
theme(
plot.title = element_text(face = "bold", size = 20, hjust = 0.5),
axis.title = element_text(face = "bold", size = 24),
axis.text = element_text(face = "bold", size = 20),
axis.text.y = element_text(
face = "bold",
size = 20,
hjust = 0,
margin = margin(r = 10)
),
legend.title = element_text(size = 20),
legend.text = element_text(size = 18)
)
}
plot_order <- c(
"SK01", "SK07",
"SK02", "SK08",
"SK03", "SK09",
"SK04SK05", "SK10",
"SK06", "SK12SK13"
)
hist_plots <- map(plot_order, create_hist_plot)
names(hist_plots) <- plot_order
combined_hist_plot <- wrap_plots(hist_plots, ncol = 2, nrow = 5, guides = "collect") &
theme(legend.position = "bottom")
combined_hist_plot
inner_join(combined_dat %>%
group_by(group) %>%
summarise(
mean = mean(PC, na.rm = TRUE),
sd = sd(PC, na.rm = TRUE),
min = min(PC, na.rm = TRUE),
max = max(PC, na.rm = TRUE),
skew = psych::skew(PC, na.rm = TRUE),
kurt = psych::kurtosi(PC, na.rm = TRUE),
p10 = quantile(PC, probs = 0.10, na.rm = TRUE),
p25 = quantile(PC, probs = 0.25, na.rm = TRUE),
p50 = quantile(PC, probs = 0.50, na.rm = TRUE),
p75 = quantile(PC, probs = 0.75, na.rm = TRUE),
p90 = quantile(PC, probs = 0.90, na.rm = TRUE),
),
item_desc %>%
group_by(group) %>%
summarise(meanItemTotal = mean(ItemTotal))
) |> select(group, p10, p25, p50, p75, p90)
res <- ks.test(
combined_dat_long |>
filter(group == "Original", Item == "PC") |>
pull(value),
combined_dat_long |>
filter(group == "German", Item == "PC") |>
pull(value)
)
res
##
## Asymptotic two-sample Kolmogorov-Smirnov test
##
## data: pull(filter(combined_dat_long, group == "Original", Item == "PC"), value) and pull(filter(combined_dat_long, group == "German", Item == "PC"), value)
## D = 0.089533, p-value = 0.2317
## alternative hypothesis: two-sided
library(transport)
wasserstein1d(
combined_dat_long |>
filter(group == "Original", Item == "PC") |>
pull(value),
combined_dat_long |>
filter(group == "German", Item == "PC") |>
pull(value)
)
## [1] 0.1008361
group_colors <- c(
German = "orange",
Original = "royalblue"
)
percentile_points <- combined_dat |>
dplyr::group_by(group) |>
dplyr::summarise(
`10th percentile` = quantile(PC, 0.10, na.rm = TRUE),
`90th percentile` = quantile(PC, 0.90, na.rm = TRUE),
.groups = "drop"
) |>
tidyr::pivot_longer(
cols = c(`10th percentile`, `90th percentile`),
names_to = "percentile",
values_to = "PC"
)
boxplot_top <- ggplot(
combined_dat,
aes(x = PC, y = group, color = group, fill = group)
) +
geom_boxplot(
alpha = 0.25,
linewidth = 1,
width = 0.6,
outlier.shape = NA
) +
geom_point(
data = percentile_points,
aes(
x = PC,
y = group,
color = group,
shape = percentile
),
inherit.aes = FALSE,
size = 6,
stroke = 1.2
) +
scale_fill_manual(values = group_colors) +
scale_color_manual(values = group_colors) +
scale_shape_manual(
values = c(
"10th percentile" = 17,
"90th percentile" = 16
)
) +
labs(x = NULL, y = NULL) +
theme_minimal() +
theme(
axis.title = element_blank(),
axis.text = element_blank(),
axis.ticks = element_blank(),
panel.grid = element_blank(),
legend.position = "none",
plot.margin = margin(5, 5, 0, 5)
)
histo <- ggplot(
combined_dat,
aes(x = PC, color = group, fill = group)
) +
geom_histogram(
aes(y = after_stat(density)),
alpha = 0.2,
position = "identity",
bins = 30
) +
geom_density(
linewidth = 1.2,
alpha = 0.2
) +
labs(
x = "PC Score",
y = "Density",
color = "Dataset",
fill = "Dataset"
) +
scale_fill_manual(values = group_colors) +
scale_color_manual(values = group_colors) +
theme_minimal() +
theme(
axis.title = element_text(face = "bold", size = 24),
axis.text = element_text(face = "bold", size = 24),
axis.text.y = element_text(
face = "bold",
size = 24,
hjust = 0,
margin = margin(r = 10)
),
legend.title = element_text(size = 24),
legend.text = element_text(size = 24),
plot.margin = margin(0, 5, 5, 5)
)
# Split the two groups
german <- combined_dat %>%
filter(group == "German") %>%
pull(PC)
original <- combined_dat %>%
filter(group == "Original") %>%
pull(PC)
# Create common probability sequence
n <- min(length(german), length(original))
probs <- seq(0, 1, length.out = n)
# Compute quantiles
qq_data <- data.frame(
German = quantile(german, probs = probs, na.rm = TRUE),
Original = quantile(original, probs = probs, na.rm = TRUE)
)
# Plot
qq1 <- ggplot(qq_data, aes(x = Original, y = German)) +
geom_point() +
geom_abline(slope = 1, intercept = 0, linetype = "dashed", color = "red") +
labs(
x = "Original PC quantiles",
y = "German PC quantiles"
) +
coord_flip() +
theme_minimal() +
theme(
axis.title = element_text(face = "bold", size = 24),
axis.text = element_text(face = "bold", size = 24),
axis.text.y = element_text(
face = "bold",
size = 24,
hjust = 0,
margin = margin(r = 10)
),
legend.position = "none"
)
# Q-Q plot against normal distribution
qq2 <- ggplot(data.frame(PC = german), aes(sample = PC)) +
stat_qq() +
stat_qq_line(linetype = "dashed", color = "red") +
labs(
x = "Normal distribution quantiles",
y = "German PC quantiles"
) +
coord_flip() +
theme_minimal() +
theme(
axis.title = element_text(face = "bold", size = 24),
axis.text = element_text(face = "bold", size = 24),
axis.text.y = element_text(
face = "bold",
size = 24,
hjust = 0,
margin = margin(r = 10)
),
legend.position = "none"
)
qq1
qq2
alpha_lush <- psych::alpha(Lush_dat[items], n.iter=5000)
alpha_ger <- psych::alpha(Ger_dat[items], n.iter=5000)
data.frame(
Dataset = c("Original", "German"),
Alpha = c(alpha_lush$total$raw_alpha,
alpha_ger$total$raw_alpha)
)
alpha_lush$boot.ci
## 2.5% 50% 97.5%
## 0.5981377 0.6795979 0.7396602
alpha_ger$boot.ci
## 2.5% 50% 97.5%
## 0.6685916 0.7250248 0.7690652
efa_vars <- combined_dat %>%
select(SK01:SK12SK13) %>%
names()
eigenvalues <- combined_dat %>%
group_by(group) %>%
group_modify(~{
cor_mat <- .x %>%
select(all_of(efa_vars)) %>%
cor(use = "pairwise.complete.obs")
tibble(
Factor = seq_along(eigen(cor_mat)$values),
Eigenvalue = eigen(cor_mat)$values
)
})
scree <- eigenvalues %>%
ggplot(
aes(x = Factor, y = Eigenvalue, color = group)
) +
geom_line() +
geom_point(size = 2) +
scale_x_continuous(breaks = scales::pretty_breaks()) +
labs(
x = "Factor",
y = "Eigenvalue",
color = "Dataset",
) +
scale_color_manual(values = group_colors) +
theme_minimal() +
theme(
axis.title = element_text(face = "bold", size = 24),
axis.text = element_text(face = "bold", size = 24),
axis.text.y = element_text(
face = "bold",
size = 24,
hjust = 0,
margin = margin(r = 10)
),
legend.title = element_text(size = 24),
legend.text = element_text(size = 24),
plot.margin = margin(0, 5, 5, 5)
)
efa_result_german <- fa(
r = combined_dat %>%
filter(group == "German")|>
select(all_of(efa_vars)),
nfactors = 2,
rotate = "oblimin")
efa_result_original <- fa(
r = combined_dat %>%
filter(group == "Original")|>
select(all_of(efa_vars)),
nfactors = 2,
rotate = "oblimin")
factor.congruence(
efa_result_german,
efa_result_original
)
## MR1 MR2
## MR1 0.93 0.15
## MR2 0.28 0.86
efa_result_german$Vaccounted
## MR1 MR2
## SS loadings 2.1720848 1.0842129
## Proportion Var 0.2172085 0.1084213
## Cumulative Var 0.2172085 0.3256298
## Proportion Explained 0.6670412 0.3329588
## Cumulative Proportion 0.6670412 1.0000000
efa_result_original$Vaccounted
## MR1 MR2
## SS loadings 1.6488453 1.0728513
## Proportion Var 0.1648845 0.1072851
## Cumulative Var 0.1648845 0.2721697
## Proportion Explained 0.6058152 0.3941848
## Cumulative Proportion 0.6058152 1.0000000
ggsave(
filename = "EFA_scree.png",
plot = scree,
width = 20,
height = 10,
units = "in",
dpi = 300
)