OSCR

Exposure to false cardiac feedback alters pain perception and anticipatory cardiac frequency.

Code ↔ Paper

The paper beside its authors' code: matches between them have not been computed for this paper yet.

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

R · 947 lines · 36 KB · MIT

  1. # Clear variables
  2. rm(list = ls())
  3. library(readxl)
  4. library(lme4)
  5. library(optimx)
  6. library(lmerTest)
  7. library(effects)
  8. library(emmeans) #
  9. library(brms)
  10. # load data
  11. setwd("C:/Users/Eleonora/OneDrive/MyExperiments/Interoception_exp/2_pain perception/")
  12. data <- read_excel("Data_v3 - 2504.xlsx")
  13. # recode factors
  14. data$Exp <- factor(data$Exp, levels = c("Intero", "Extero"))
  15. data$Feedback <- factor(data$Feedback, levels = c("Congruent", "Slower", "Faster", "No Feedback"))
  16. # remove stim int levels
  17. data = data[data$StimInt != "5", ]
  18. data = data[data$StimInt != "1", ]
  19. # Center Stim intensity (2 = -.5; 3 = 0; 4 = .5)
  20. data$StimInt <- (data$StimInt-3)/2
  21. #################
  22. ### LMM Btw ####
  23. #################
  24. ################### HR BETWEEN ###################
  25. MBtw.HR_N0 = lmer(HR ~ Exp*Feedback*Trial + (1|SSID),data = data, REML = TRUE,
  26. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  27. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  28. MBtw.HR_N = lmer(HR ~ Exp*Feedback*Trial + (Trial|SSID),data = data, REML = TRUE,
  29. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  30. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  31. model_comparison <- anova(MBtw.HR_N0, MBtw.HR_N)
  32. model_comparison
  33. MBtw.HR_N = lmer(HR ~ Exp*Feedback*Trial + (Trial|SSID),data = data, REML = TRUE,
  34. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  35. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  36. # remove outliers (|standardized residuals| > 2 SDs)
  37. MBtw.HRt_N = lmer(HR ~ Exp*Feedback*Trial + (Trial|SSID),data = data,REML = TRUE,
  38. subset = abs(scale(resid(MBtw.HR_N)))<2,
  39. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  40. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  41. # residual analysis
  42. qqnorm(resid(MBtw.HRt_N))
  43. qqline(resid(MBtw.HRt_N))
  44. # show model results
  45. anova(MBtw.HRt_N)
  46. summary(MBtw.HRt_N)
  47. # save model results
  48. F_Btw.HR_N = anova(MBtw.HRt_N)
  49. T_Btw.HR_N = data.frame(summary(MBtw.HRt_N)$coefficients)
  50. posthoc_results <- emmeans(MBtw.HRt_N, pairwise ~ Feedback, adjust="fdr")
  51. posthoc_results
  52. # forziamo il download della versione binaria senza compilare
  53. install.packages("DHARMa", type = "binary", dependencies = TRUE, repos = "https://cran.rstudio.com")
  54. library(DHARMa)library(DHARMa)
  55. sim <- simulateResiduals(fittedModel = MBtw.HRt_N, n = 1000)
  56. plotQQunif(sim)
  57. testNormality(sim)
  58. testDispersion(sim)
  59. ################### HR BETWEEN INTERACTIONS ###################
  60. ### FEEDBACK * EXP
  61. library(emmeans)
  62. emmeans_FE <- emmeans(MBtw.HRt_N, ~ Feedback * Exp)
  63. summary(emmeans_FE) #summary(emmeans_FE, infer = c(TRUE, TRUE)) # HA SENSO?
  64. #Contrasti tra feedback entro ciascun esperimento
  65. contrast(emmeans_FE, method = "pairwise", by = "Exp", adjust = "fdr")
  66. #Contrasti di interazione (cioè: la differenza delle differenze)
  67. contrast(emmeans_FE, interaction = "pairwise")
  68. #Contrasti tra feedback entro ciascun esperimento
  69. contrast(emmeans_FE, method = "pairwise", by = "Feedback", adjust = "fdr")
  70. ###FEEDBACK*TRIAL
  71. library(emmeans)
  72. # Calcola la slope del Trial nei due livelli di Exp
  73. emtrends(MBtw.HRt_N, var = "Trial", specs = "Exp")
  74. emtrends(MBtw.HRt_N, var = "Trial", specs = "Exp", infer = c(TRUE, TRUE), adjust = "none")
  75. contrast(emtrends(MBtw.HRt_N, var = "Trial", specs = "Exp"), method = "pairwise")
  76. ## FEEDBACK * TRIAL * EXPERIMENT
  77. anova(MBtw.HRt_N)
  78. summary(MBtw.HRt_N)
  79. library(emmeans)
  80. # Stima degli slopes per ogni condizione Feedback × Exp
  81. # Estrai le slope di HR su Trial
  82. slopes_HR <- emtrends(MBtw.HRt_N, ~ Feedback * Exp, var = "Trial")
  83. # slope significative (aumento/decremento diverso da zero per ogni cond (exp e feedback))
  84. summary(slopes_HR, infer = c(TRUE, TRUE)) # per avere anche t, p e CI, usa questo, non contrast(slopes_HR)
  85. #dentro a ogni esperimento
  86. contrast(slopes_HR, method = "pairwise", by = "Exp", adjust = "fdr")
  87. # differenze delle differenze TRA esperimenti
  88. interaction_contrast <- contrast(slopes_HR, interaction = "pairwise")
  89. summary(interaction_contrast)
  90. #Tutti i confronti possibili tra condizioni (es. Congruent Intero vs Faster Extero)
  91. contrast(slopes_HR, method = "pairwise", adjust = "fdr")
  92. trial_points <- c(-0.5, 0, 0.5)
  93. # Estimated marginal means at specific trial values
  94. em_trial <- emmeans(MBtw.HRt_N, ~ Feedback * Exp | Trial, at = list(Trial = trial_points))
  95. # View predictions
  96. summary(em_trial)
  97. contrast(em_trial, method = "pairwise", by = c("Trial", "Feedback"), adjust = "fdr")
  98. library(emmeans)
  99. library(ggplot2)
  100. # 1. Definisci i tre livelli di interesse per Trial (già centrato)
  101. trial_levels <- c(-0.5, 0, 0.5)
  102. # 2. Calcola gli emmeans a ciascun livello di Trial
  103. em_trial <- emmeans(MBtw.HRt_N, ~ Feedback | Exp * Trial, at = list(Trial = trial_levels))
  104. # 3. Contrasti tra condizioni di Feedback all’interno di ogni Exp × Trial
  105. contrasts_by_trial <- contrast(em_trial, method = "pairwise", by = c("Exp", "Trial"), adjust = "fdr")
  106. # 4. Visualizza i risultati dei contrasti
  107. summary(contrasts_by_trial)
  108. # 1. Cambiamento nel tempo (slopes) per ogni Feedback: confronto Intero vs Extero
  109. slopes_HR <- emtrends(MBtw.HRt_N, ~ Feedback * Exp, var = "Trial")
  110. # Contrasti di interazione: slope_Intero - slope_Extero per ciascun Feedback
  111. slope_diff <- contrast(slopes_HR, interaction = "pairwise")
  112. summary(slope_diff)
  113. # → Se estimate ≠ 0 e p < .05, significa che quel Feedback cambia con Trial in modo diverso tra Exp
  114. # 2. Faster vs Slower a Trial = -0.5, 0, 0.5 dentro ciascun Exp
  115. trial_levels <- c(-0.5, 0, 0.5)
  116. # Calcola gli emmeans per Feedback in ciascun Exp ai trial prescelti
  117. em_trial <- emmeans(
  118. MBtw.HRt_N,
  119. ~ Feedback | Exp * Trial,
  120. at = list(Trial = trial_levels)
  121. )
  122. # Contrasto custom: Faster vs Slower (vettore su livelli di Feedback: Congruent, Slower, Faster, No Feedback)
  123. fs_contrasts <- contrast(
  124. em_trial,
  125. method = list("Faster vs Slower" = c(0, -1, 1, 0)),
  126. by = c("Exp", "Trial"),
  127. adjust = "fdr"
  128. )
  129. # Risultati
  130. summary(fs_contrasts)
  131. # → Per ogni Exp × Trial ottieni stima, t-ratio e p-value di Faster–Slower
  132. ################### LIKERT BETWEEEN ################### qui ok feedback
  133. MBtw.LIK_N0 = lmer(LIK ~ Exp*Feedback*StimInt*Trial + (1|SSID),data = data,
  134. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  135. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  136. MBtw.LIK_N = lmer(LIK ~ Exp*Feedback*StimInt*Trial + (StimInt+Trial|SSID),data = data,
  137. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  138. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  139. MBtw.LIK_N1 = lmer(LIK ~ Exp*Feedback*StimInt*Trial + (StimInt+Trial+Feedback|SSID),data = data,
  140. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  141. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  142. model_comparison <- anova(MBtw.LIK_N, MBtw.LIK_N1)
  143. model_comparison
  144. MBtw.LIK_N = lmer(LIK ~ Exp*Feedback*StimInt*Trial + (StimInt+Trial+Feedback|SSID),data = data,
  145. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  146. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  147. # remove outliers (|standardized residuals| > 2 SDs)
  148. MBtw.LIKt_N = lmer(LIK ~ Exp*Feedback*StimInt*Trial + (StimInt+Trial+Feedback|SSID),data = data,
  149. subset = abs(scale(resid(MBtw.LIK_N)))<2,
  150. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  151. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  152. # residual analysis
  153. qqnorm(resid(MBtw.LIKt_N))
  154. qqline(resid(MBtw.LIKt_N))
  155. # show model results
  156. anova(MBtw.LIKt_N)
  157. summary(MBtw.LIKt_N)
  158. # save model results
  159. F_Btw.LIK_N = anova(MBtw.LIKt_N)
  160. T_Btw.LIK_N = data.frame(summary(MBtw.LIKt_N)$coefficients)
  161. posthoc_results <- emmeans(MBtw.LIKt_N, pairwise ~ Feedback, adjust="fdr")
  162. posthoc_results
  163. ################### LIKERT BETWEEN INTERACTIONS ###################
  164. # FEEDBACK * EXP
  165. library(emmeans)
  166. emmeans_FE <- emmeans(MBtw.LIKt_N, ~ Feedback * Exp)
  167. summary(emmeans_FE)
  168. #Contrasti tra feedback entro ciascun esperimento
  169. contrast(emmeans_FE, method = "pairwise", by = "Exp", adjust = "fdr")
  170. #Contrasti di interazione (cioè: la differenza delle differenze)
  171. contrast(emmeans_FE, interaction = "pairwise")
  172. # STIMINT*TRIAL
  173. slopes_stimint_at <- emtrends(MBtw.LIKt_N, ~ StimInt, var = "Trial", at = list(StimInt = c(-0.5, 0, 0.5)))
  174. summary(slopes_stimint_at)
  175. test(slopes_stimint_at)
  176. contrast(slopes_stimint_at, method = "pairwise", adjust = "fdr")
  177. #FEEDBACK * STIMINT
  178. em_stimint <- emmeans(MBtw.LIKt_N, ~ Feedback | StimInt, at = list(StimInt = c(-0.5, 0, 0.5)))
  179. summary(em_stimint, infer = c(TRUE, TRUE))
  180. # Pairwise comparisons tra livelli di feedback, per ogni StimInt
  181. contrast(em_stimint, method = "pairwise", adjust = "fdr")
  182. # 1. Calcola le slope (trend) di StimInt per ciascun livello di Feedback
  183. slopes_feedback <- emtrends(MBtw.LIKt_N, ~ Feedback, var = "StimInt")
  184. # 2. Riassunto delle stime: slope, SE, t, p, CI
  185. summary(slopes_feedback)
  186. test(slopes_feedback)
  187. # Contrasti sulle slope di StimInt tra condizioni di feedback
  188. contrast(slopes_feedback, method = "pairwise", adjust = "fdr")
  189. ###FEEDBACK*TRIAL
  190. # Calcolo delle slope dell’effetto di Trial all’interno di ogni livello di Feedback
  191. slopes_trial_by_feedback <- emtrends(MBtw.LIKt_N, ~ Feedback, var = "Trial")
  192. summary(slopes_trial_by_feedback, infer = c(TRUE, TRUE)) # per avere anche t, p e CI
  193. pairs(slopes_trial_by_feedback, adjust = "fdr")
  194. # Contrasti tra le pendenze di Trial tra i diversi livelli di Feedback
  195. trial_slope_contrasts <- pairs(slopes_trial_by_feedback, adjust = "fdr")
  196. summary(trial_slope_contrasts)
  197. plot(slopes_trial_by_feedback)
  198. ## what happens at each trial level
  199. feedback_by_trial_levels <- emmeans(MBtw.LIKt_N, ~ Feedback | Trial,
  200. at = list(Trial = c(-0.5, 0, 0.5)))
  201. pairs(feedback_by_trial_levels, adjust = "fdr")
  202. ################### NPS BETWEEN ###################
  203. MBtw.VAS_NO = lmer(VAS ~ Exp*Feedback*StimInt*Trial +(1|SSID),data = data,
  204. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  205. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  206. MBtw.VAS_N = lmer(VAS ~ Exp*Feedback*StimInt*Trial +(StimInt+Trial|SSID),data = data,
  207. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  208. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  209. model_comparison <- anova(MBtw.VAS_NO, MBtw.VAS_N)
  210. model_comparison
  211. MBtw.VAS_N = lmer(VAS ~ Exp*Feedback*StimInt*Trial +(StimInt+Trial|SSID),data = data,
  212. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  213. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  214. # remove outliers (|standardized residuals| > 2 SDs)
  215. MBtw.VASt_N = lmer(VAS ~ Exp*Feedback*StimInt*Trial +(StimInt+Trial|SSID),data = data,
  216. subset = abs(scale(resid(MBtw.VAS_N)))<2,
  217. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  218. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  219. # residual analysis
  220. qqnorm(resid(MBtw.VASt_N))
  221. qqline(resid(MBtw.VASt_N))
  222. # show model results
  223. anova(MBtw.VASt_N)
  224. summary(MBtw.VASt_N)
  225. # save model results
  226. F_Btw.VAS_N = anova(MBtw.VASt_N)
  227. T_Btw.VAS_N = data.frame(summary(MBtw.VASt_N)$coefficients)
  228. posthoc_results <- emmeans(MBtw.VASt_N, pairwise ~ Feedback, adjust="fdr")
  229. posthoc_results
  230. ################### NPS BETWEEN INTERACTIONS ###################
  231. # FEEDBACK * EXP
  232. library(emmeans)
  233. emmeans_FE <- emmeans(MBtw.VASt_N, ~ Feedback * Exp)
  234. summary(emmeans_FE)
  235. #Contrasti tra feedback entro ciascun esperimento
  236. contrast(emmeans_FE, method = "pairwise", by = "Exp", adjust = "fdr")
  237. #Contrasti di interazione (cioè: la differenza delle differenze)
  238. contrast(emmeans_FE, interaction = "pairwise")
  239. # STIMINT*TRIAL
  240. slopes_stimint_at <- emtrends(MBtw.VASt_N, ~ StimInt, var = "Trial", at = list(StimInt = c(-0.5, 0, 0.5)))
  241. summary(slopes_stimint_at)
  242. test(slopes_stimint_at)
  243. contrast(slopes_stimint_at, method = "pairwise", adjust = "fdr")
  244. #FEEDBACK * STIMINT
  245. em_stimint <- emmeans(MBtw.VASt_N, ~ Feedback | StimInt, at = list(StimInt = c(-0.5, 0, 0.5)))
  246. summary(em_stimint, infer = c(TRUE, TRUE))
  247. # Pairwise comparisons tra livelli di feedback, per ogni StimInt
  248. contrast(em_stimint, method = "pairwise", adjust = "fdr")
  249. # 1. Calcola le slope (trend) di StimInt per ciascun livello di Feedback
  250. slopes_feedback <- emtrends(MBtw.VASt_N, ~ Feedback, var = "StimInt")
  251. # 2. Riassunto delle stime: slope, SE, t, p, CI
  252. summary(slopes_feedback)
  253. test(slopes_feedback)
  254. # Contrasti sulle slope di StimInt tra condizioni di feedback
  255. contrast(slopes_feedback, method = "pairwise", adjust = "fdr")
  256. ###FEEDBACK*TRIAL
  257. # Calcolo delle slope dell’effetto di Trial all’interno di ogni livello di Feedback
  258. slopes_trial_by_feedback <- emtrends(MBtw.VASt_N, ~ Feedback, var = "Trial")
  259. summary(slopes_trial_by_feedback, infer = c(TRUE, TRUE)) # per avere anche t, p e CI
  260. pairs(slopes_trial_by_feedback, adjust = "fdr")
  261. # Contrasti tra le pendenze di Trial tra i diversi livelli di Feedback
  262. trial_slope_contrasts <- pairs(slopes_trial_by_feedback, adjust = "fdr")
  263. summary(trial_slope_contrasts)
  264. plot(slopes_trial_by_feedback)
  265. ## what happens at each trial level
  266. feedback_by_trial_levels <- emmeans(MBtw.VASt_N, ~ Feedback | Trial,
  267. at = list(Trial = c(-0.5, 0, 0.5)))
  268. pairs(feedback_by_trial_levels, adjust = "fdr")
  269. ###################
  270. ### LMM Intero ####
  271. ###################
  272. data1 = data[data$Exp=="Intero", ]
  273. ################### HR Interoceptive ###################
  274. MInt.HRN0 = lmer(HR ~ Feedback*Trial + (1|SSID),data = data1, REML = TRUE,
  275. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  276. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  277. MInt.HRN = lmer(HR ~ Feedback*Trial + (Trial|SSID),data = data1, REML = TRUE,
  278. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  279. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  280. model_comparison <- anova(MInt.HRN0, MInt.HRN)
  281. model_comparison
  282. # remove outliers (|standardized residuals| > 2 SDs)
  283. MInt.HRt = lmer(HR ~ Trial*Feedback + (Trial|SSID),data = data1, REML = TRUE,
  284. subset = abs(scale(resid(MInt.HRN)))<2,
  285. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  286. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  287. # residual analysis
  288. qqnorm(resid(MInt.HRt))
  289. qqline(resid(MInt.HRt))
  290. # show model results
  291. anova(MInt.HRt)
  292. summary(MInt.HRt)
  293. # save model results
  294. F_Int.HR = anova(MInt.HRt)
  295. T_Int.HR = data.frame(summary(MInt.HRt)$coefficients)
  296. posthoc_results <- emmeans(MInt.HRt, pairwise ~ Feedback, adjust="fdr")
  297. posthoc_results
  298. ################### HR Interoceptive INTERACTIONS ###################
  299. ## FEEDBACK * TRIAL
  300. # Calcolo delle slope dell’effetto di Trial all’interno di ogni livello di Feedback
  301. slopes_trial_by_feedback <- emtrends(MInt.HRt, ~ Feedback, var = "Trial")
  302. summary(slopes_trial_by_feedback, infer = c(TRUE, TRUE)) # per avere anche t, p e CI
  303. pairs(slopes_trial_by_feedback, adjust = "fdr")
  304. # Contrasti tra le pendenze di Trial tra i diversi livelli di Feedback
  305. trial_slope_contrasts <- pairs(slopes_trial_by_feedback, adjust = "fdr")
  306. summary(trial_slope_contrasts)
  307. plot(slopes_trial_by_feedback)
  308. ## what happens at each trial level
  309. feedback_by_trial_levels <- emmeans(MInt.HRt, ~ Feedback | Trial,
  310. at = list(Trial = c(-0.5, 0, 0.5)))
  311. feedback_by_trial_levels
  312. pairs(feedback_by_trial_levels, adjust = "fdr")
  313. ################### LIKERT Interoceptive ################### qui ok feedback
  314. MInt.LIK1 = lmer(LIK ~ Trial*Feedback*StimInt + (1|SSID),data = data1, REML = TRUE,
  315. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  316. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  317. MInt.LIK = lmer(LIK ~ Trial*Feedback*StimInt + (StimInt+Trial|SSID),data = data1, REML = TRUE,
  318. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  319. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  320. model_comparison <- anova(MInt.LIK1, MInt.LIK)
  321. model_comparison
  322. # remove outliers (|standardized residuals| > 2 SDs)
  323. MInt.LIKt = lmer(LIK ~ Trial*Feedback*StimInt + (StimInt+Trial|SSID),data = data1, REML = TRUE,
  324. subset = abs(scale(resid(MInt.LIK)))<2,
  325. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  326. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  327. # residual analysis
  328. qqnorm(resid(MInt.LIKt))
  329. qqline(resid(MInt.LIKt))
  330. # show model results
  331. anova(MInt.LIKt)
  332. summary(MInt.LIKt)
  333. # save model results
  334. F_Int.LIK = anova(MInt.LIKt)
  335. T_Int.LIK = data.frame(summary(MInt.LIKt)$coefficients)
  336. posthoc_results <- emmeans(MInt.LIKt, pairwise ~ Feedback, adjust="fdr")
  337. posthoc_results
  338. ################### LIKERT Interoceptive INTERACTIONS ###################
  339. #FEEDBACK * STIMINT
  340. em_stimint <- emmeans(MInt.LIKt, ~ Feedback | StimInt, at = list(StimInt = c(-0.5, 0, 0.5)))
  341. summary(em_stimint, infer = c(TRUE, TRUE))
  342. # Pairwise comparisons tra livelli di feedback, per ogni StimInt
  343. contrast(em_stimint, method = "pairwise", adjust = "fdr")
  344. # 1. Calcola le slope (trend) di StimInt per ciascun livello di Feedback
  345. slopes_feedback <- emtrends(MInt.LIKt, ~ Feedback, var = "StimInt")
  346. # 2. Riassunto delle stime: slope, SE, t, p, CI
  347. summary(slopes_feedback)
  348. test(slopes_feedback)
  349. # Contrasti sulle slope di StimInt tra condizioni di feedback
  350. contrast(slopes_feedback, method = "pairwise", adjust = "fdr")
  351. ## FEEDBACK*TRIAL
  352. # Calcolo delle slope dell’effetto di Trial all’interno di ogni livello di Feedback
  353. slopes_trial_by_feedback <- emtrends(MInt.LIKt, ~ Feedback, var = "Trial")
  354. summary(slopes_trial_by_feedback, infer = c(TRUE, TRUE)) # per avere anche t, p e CI
  355. pairs(slopes_trial_by_feedback, adjust = "fdr")
  356. # Contrasti tra le pendenze di Trial tra i diversi livelli di Feedback
  357. trial_slope_contrasts <- pairs(slopes_trial_by_feedback, adjust = "fdr")
  358. summary(trial_slope_contrasts)
  359. #plot(slopes_trial_by_feedback)
  360. ## what happens at each trial level
  361. feedback_by_trial_levels <- emmeans(MInt.LIKt, ~ Feedback | Trial,
  362. at = list(Trial = c(-0.5, 0, 0.5)))
  363. pairs(feedback_by_trial_levels, adjust = "fdr")
  364. # STIMINT * TRIAL
  365. slopes_stimint_at <- emtrends(MInt.LIKt, ~ StimInt, var = "Trial", at = list(StimInt = c(-0.5, 0, 0.5)))
  366. summary(slopes_stimint_at, infer = c(TRUE, TRUE)) # p values diverso da zero?
  367. #differenze delle differenze
  368. contrast(slopes_stimint_at, method = "pairwise", adjust = "fdr")
  369. ################### NPS Interoceptive ###################
  370. MInt.VAS1 = lmer(VAS ~ Trial*Feedback*StimInt + (1|SSID),data = data1, REML = TRUE,
  371. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  372. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  373. MInt.VAS = lmer(VAS ~ Trial*Feedback*StimInt + (StimInt+Trial|SSID),data = data1, REML = TRUE,
  374. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  375. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  376. model_comparison <- anova(MInt.VAS1, MInt.VAS)
  377. model_comparison
  378. # remove outliers (|standardized residuals| > 2 SDs)
  379. MInt.VASt = lmer(VAS ~ Trial*Feedback*StimInt + (StimInt+Trial|SSID),data = data1, REML = TRUE,
  380. subset = abs(scale(resid(MInt.VAS)))<2,
  381. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  382. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  383. # residual analysis
  384. qqnorm(resid(MInt.VASt))
  385. qqline(resid(MInt.VASt))
  386. # show model results
  387. anova(MInt.VASt)
  388. summary(MInt.VASt)
  389. # save model results
  390. F_Int.VAS = anova(MInt.VASt)
  391. T_Int.VAS = data.frame(summary(MInt.VASt)$coefficients)
  392. posthoc_results <- emmeans(MInt.VASt, pairwise ~ Feedback, adjust="fdr")
  393. posthoc_results
  394. ################### NPS Interoceptive INTERACTIONS ###################
  395. # FEEDBACK*STIMINT
  396. # Estimated marginal means di VAS per ciascun livello di Feedback, ai tre livelli di StimInt
  397. em_stimint <- emmeans(MInt.VASt, ~ Feedback | StimInt, at = list(StimInt = c(-0.5, 0, 0.5)))
  398. summary(em_stimint, infer = c(TRUE, TRUE))
  399. # Pairwise comparisons dentro a ogni StimInt
  400. contrast(em_stimint, method = "pairwise", by = "StimInt", adjust = "fdr")
  401. slopes_feedback <- emtrends(MInt.VASt, ~ Feedback, var = "StimInt")
  402. # 2. Riassunto delle stime: slope, SE, t, p, CI
  403. summary(slopes_feedback, infer = c(TRUE, TRUE))
  404. # Contrasti sulle slope di StimInt tra condizioni di feedback
  405. contrast(slopes_feedback, method = "pairwise", adjust = "fdr")
  406. ###FEEDBACK * TRIAL
  407. # Calcolo delle slope dell’effetto di Trial all’interno di ogni livello di Feedback
  408. slopes_trial_by_feedback <- emtrends(MInt.VASt, ~ Feedback, var = "Trial")
  409. summary(slopes_trial_by_feedback, infer = c(TRUE, TRUE)) # per avere anche t, p e CI
  410. pairs(slopes_trial_by_feedback, adjust = "fdr")
  411. # Contrasti tra le pendenze di Trial tra i diversi livelli di Feedback
  412. trial_slope_contrasts <- pairs(slopes_trial_by_feedback, adjust = "fdr")
  413. summary(trial_slope_contrasts)
  414. #plot(slopes_trial_by_feedback)
  415. ## what happens at each trial level
  416. feedback_by_trial_levels <- emmeans(MInt.VASt, ~ Feedback | Trial,
  417. at = list(Trial = c(-0.5, 0, 0.5)))
  418. pairs(feedback_by_trial_levels, adjust = "fdr")
  419. ###################
  420. ### LMM Extero ####
  421. ###################
  422. data2 = data[data$Exp=="Extero", ]
  423. ################### HR exteroceptive ###################
  424. MExt.HR0 = lmer(HR ~ Trial+Feedback + (1|SSID),data = data2, REML = TRUE,
  425. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  426. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  427. MExt.HR = lmer(HR ~ Trial*Feedback + (Trial|SSID),data = data2, REML = TRUE,
  428. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  429. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  430. model_comparison <- anova(MExt.HR0, MExt.HR)
  431. model_comparison
  432. # remove outliers (|standardized residuals| > 2 SDs)
  433. MExt.HRt = lmer(HR ~ Trial*Feedback + (Trial|SSID),data = data2, REML = TRUE,
  434. subset = abs(scale(resid(MExt.HR)))<2,
  435. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  436. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  437. # residual analysis
  438. qqnorm(resid(MExt.HRt))
  439. qqline(resid(MExt.HRt))
  440. # show model results
  441. anova(MExt.HRt)
  442. summary(MExt.HRt)
  443. # save model results
  444. F_Ext.HR = anova(MExt.HRt)
  445. T_Ext.HR = data.frame(summary(MExt.HRt)$coefficients)
  446. posthoc_results <- emmeans(MExt.HRt, pairwise ~ Feedback, adjust="fdr")
  447. posthoc_results
  448. ################### likert exteroceptive ###################
  449. MExt.LIK0 = lmer(LIK ~ Trial*Feedback*StimInt + (1|SSID),data = data2, REML = TRUE,
  450. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  451. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  452. MExt.LIK = lmer(LIK ~ Trial*Feedback*StimInt + (StimInt+Trial|SSID),data = data2, REML = TRUE,
  453. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  454. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  455. model_comparison <- anova(MExt.LIK0, MExt.LIK)
  456. model_comparison
  457. # remove outliers (|standardized residuals| > 2 SDs)
  458. MExt.LIKt = lmer(LIK ~ Trial*Feedback*StimInt + (StimInt+Trial|SSID),data = data2, REML = TRUE,
  459. subset = abs(scale(resid(MExt.LIK)))<2,
  460. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  461. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  462. # residual analysis
  463. qqnorm(resid(MExt.LIKt))
  464. qqline(resid(MExt.LIKt))
  465. # show model results
  466. anova(MExt.LIKt)
  467. summary(MExt.LIKt)
  468. # save model results
  469. F_Ext.LIK = anova(MExt.LIKt)
  470. T_Ext.LIK = data.frame(summary(MExt.LIKt)$coefficients)
  471. posthoc_results <- emmeans(MExt.LIKt, pairwise ~ Feedback, adjust="fdr")
  472. posthoc_results
  473. ### EXTEROCEPTIVE likert INTERACTIONS
  474. #STIMULUSINTESITY * FEEDBACK
  475. em_stimint <- emmeans(MExt.LIKt, ~ Feedback | StimInt, at = list(StimInt = c(-0.5, 0, 0.5)))
  476. summary(em_stimint)
  477. # Pairwise comparisons tra livelli di feedback, per ogni StimInt
  478. contrast(em_stimint, method = "pairwise", adjust = "fdr")
  479. # 1. Calcola le slope (trend) di StimInt per ciascun livello di Feedback
  480. slopes_feedback <- emtrends(MExt.LIKt, ~ Feedback, var = "StimInt")
  481. # 2. Riassunto delle stime: slope, SE, t, p, CI
  482. summary(slopes_feedback)
  483. test(slopes_feedback)
  484. # Contrasti sulle slope di StimInt tra condizioni di feedback
  485. contrast(slopes_feedback, method = "pairwise", adjust = "fdr")
  486. ### FEEDBACK * TRIAL
  487. # Calcolo delle slope dell’effetto di Trial all’interno di ogni livello di Feedback
  488. slopes_trial_by_feedback <- emtrends(MExt.LIKt, ~ Feedback, var = "Trial")
  489. summary(slopes_trial_by_feedback, infer = c(TRUE, TRUE)) # per avere anche t, p e CI
  490. # Contrasti tra le pendenze di Trial tra i diversi livelli di Feedback
  491. pairs(slopes_trial_by_feedback, adjust = "fdr")
  492. #plot(slopes_trial_by_feedback)
  493. ## what happens at each trial level
  494. feedback_by_trial_levels <- emmeans(MExt.LIKt, ~ Feedback | Trial,
  495. at = list(Trial = c(-0.5, 0, 0.5)))
  496. feedback_by_trial_levels
  497. pairs(feedback_by_trial_levels, adjust = "fdr")
  498. # STIMINT * TRIAL
  499. slopes_stimint_at <- emtrends(MExt.LIKt, ~ StimInt, var = "Trial", at = list(StimInt = c(-0.5, 0, 0.5)))
  500. summary(slopes_stimint_at, infer = c(TRUE, TRUE)) # p values diverso da zero?
  501. #differenze delle differenze
  502. contrast(slopes_stimint_at, method = "pairwise", adjust = "fdr")
  503. ################### VAS exteroceptive ###################
  504. MExt.VAS0 = lmer(VAS ~ Trial*Feedback*StimInt + (1|SSID),data = data2, REML = TRUE,
  505. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  506. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  507. ## VAS
  508. MExt.VAS = lmer(VAS ~ Trial*Feedback*StimInt + (Trial+StimInt|SSID),data = data2, REML = TRUE,
  509. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  510. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  511. model_comparison <- anova(MExt.VAS0, MExt.VAS)
  512. model_comparison
  513. # remove outliers (|standardized residuals| > 2 SDs)
  514. MExt.VASt = lmer(VAS ~ Trial*Feedback*StimInt + (Trial+StimInt|SSID),data = data2, REML = TRUE,
  515. subset = abs(scale(resid(MExt.VAS)))<2,
  516. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  517. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  518. # residual analysis
  519. qqnorm(resid(MExt.VASt))
  520. qqline(resid(MExt.VASt))
  521. # show model results
  522. anova(MExt.VASt)
  523. summary(MExt.VASt)
  524. # save model results
  525. F_Ext.VAS = anova(MExt.VASt)
  526. T_Ext.VAS = data.frame(summary(MExt.VASt)$coefficients)
  527. posthoc_results <- emmeans(MExt.VASt, pairwise ~ Feedback, adjust="fdr")
  528. posthoc_results
  529. ### VAS
  530. # FEEDBACK*STIMINT i
  531. # Estimated marginal means di VAS per ciascun livello di Feedback, ai tre livelli di StimInt
  532. em_stimint <- emmeans(MExt.VASt, ~ Feedback | StimInt, at = list(StimInt = c(-0.5, 0, 0.5)))
  533. summary(em_stimint, infer = c(TRUE, TRUE))
  534. # Pairwise comparisons dentro a ogni StimInt
  535. contrast(em_stimint, method = "pairwise", by = "StimInt", adjust = "fdr")
  536. ## per differenze con zero e contrasti tra feedback (quanto l'increase del main effect stimint e' diverso per ogni feed)
  537. # 1. Calcola le slope (trend) di StimInt per ciascun livello di Feedback
  538. slopes_feedback <- emtrends(MExt.VASt, ~ Feedback, var = "StimInt")
  539. # 2. Riassunto delle stime: slope, SE, t, p, CI
  540. summary(slopes_feedback, infer = c(TRUE, TRUE))
  541. # Contrasti sulle slope di StimInt tra condizioni di feedback
  542. contrast(slopes_feedback, method = "pairwise", adjust = "fdr")
  543. # STIMINT * TRIAL
  544. slopes_stimint_at <- emtrends(MExt.VASt, ~ StimInt, var = "Trial", at = list(StimInt = c(-0.5, 0, 0.5)))
  545. summary(slopes_stimint_at, infer = c(TRUE, TRUE)) # p values diverso da zero?
  546. #differenze delle differenze
  547. contrast(slopes_stimint_at, method = "pairwise", adjust = "fdr")
  548. ## FEEDBACK * TRIAL
  549. # Calcolo delle slope dell’effetto di Trial all’interno di ogni livello di Feedback
  550. slopes_trial_by_feedback <- emtrends(MExt.VASt, ~ Feedback, var = "Trial")
  551. summary(slopes_trial_by_feedback, infer = c(TRUE, TRUE)) # per avere anche t, p e CI
  552. pairs(slopes_trial_by_feedback, adjust = "fdr")
  553. # Contrasti tra le pendenze di Trial tra i diversi livelli di Feedback
  554. trial_slope_contrasts <- pairs(slopes_trial_by_feedback, adjust = "fdr")
  555. summary(trial_slope_contrasts)
  556. plot(slopes_trial_by_feedback)
  557. ## what happens at each trial level
  558. feedback_by_trial_levels <- emmeans(MExt.VASt, ~ Feedback | Trial,
  559. at = list(Trial = c(-0.5, 0, 0.5)))
  560. feedback_by_trial_levels
  561. pairs(feedback_by_trial_levels, adjust = "fdr")
  562. ######################## pain response
  563. model_comparison <- anova(painrespBTW1, painrespBTW)
  564. model_comparison
  565. painrespBTW2 = lmer(HRPR ~ Trial*Feedback*StimInt*Exp + (Trial+StimInt|SSID),data = data, REML = TRUE,
  566. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  567. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  568. painrespBTW1 = lmer(HRPR ~ Trial*Feedback*StimInt*Exp + (Trial|SSID),data = data, REML = TRUE,
  569. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  570. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  571. painrespBTW = lmer(HRPR ~ Trial*Feedback*StimInt*Exp + (1|SSID),data = data, REML = TRUE,
  572. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  573. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  574. # remove outliers (|standardized residuals| > 2 SDs)
  575. painrespBTW_n = lmer(HRPR ~ Trial*Feedback*StimInt*Exp + (1|SSID),data = data, REML = TRUE,
  576. subset = abs(scale(resid(painrespBTW)))<2,
  577. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  578. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  579. # residual analysis
  580. qqnorm(resid(painrespBTW_n))
  581. qqline(resid(painrespBTW_n))
  582. # show model results
  583. anova(painrespBTW_n)
  584. summary(painrespBTW_n)
  585. # save model results
  586. F_Ext.VAS = anova(painrespBTW_n)
  587. T_Ext.VAS = data.frame(summary(painrespBTW_n)$coefficients)
  588. posthoc_results <- emmeans(F_Ext.VAS, pairwise ~ Exp, adjust="fdr")
  589. posthoc_results
  590. # Calcola i simple slopes di StimInt per ciascun livello di Exp
  591. emtrends(painrespBTW_n, specs = ~Exp, var = "StimInt") %>%
  592. summary(infer = c(TRUE, TRUE))
  593. # Calcolo dei simple slopes dell’effetto di Trial a ciascun livello di StimInt e per ciascun Exp
  594. emtrends(painrespBTW_n, specs = ~Exp | StimInt, var = "Trial", at = list(StimInt = c(-0.5, 0, 0.5))) %>%
  595. summary(infer = c(TRUE, TRUE))
  596. emtrends(painrespBTW_n, specs = ~Exp | Feedback, var = "StimInt") %>%
  597. summary(infer = c(TRUE, TRUE))
  598. # plot effects
  599. # If not installed yet
  600. # install.packages("effects")
  601. library(effects)
  602. # Get the effect of the 3-way interaction
  603. eff <- effect("Feedback:StimInt:Exp", painrespBTW_n)
  604. # Plot
  605. plot(eff, multiline = TRUE, ci.style = "bands",
  606. main = "Feedback × StimInt × Exp Interaction (Model-Based)",
  607. xlab = "StimInt", ylab = "Predicted HRPR")
  608. ####
  609. # If not installed yet
  610. # install.packages("ggeffects")
  611. library(ggeffects)
  612. library(ggplot2)
  613. # Get model-based predictions for the 3-way interaction
  614. preds <- ggpredict(painrespBTW_n, terms = c("StimInt", "Feedback", "Exp"))
  615. # Plot
  616. ggplot(preds, aes(x = x, y = predicted, color = group)) +
  617. geom_line(size = 1) +
  618. geom_point(size = 2) +
  619. geom_ribbon(aes(ymin = conf.low, ymax = conf.high, fill = group), alpha = 0.2, color = NA) +
  620. facet_wrap(~ facet) +
  621. labs(title = "Model-Based Interaction Plot: Feedback × StimInt × Exp",
  622. x = "StimInt",
  623. y = "Predicted HRPR",
  624. color = "Feedback",
  625. fill = "Feedback") +
  626. theme_minimal(base_size = 14)
  627. ###############################################
  628. library(ggeffects)
  629. library(ggplot2)
  630. # Get model-based predictions for the interaction between Trial and Exp
  631. preds <- ggpredict(painrespBTW_n, terms = c("Trial", "Exp"))
  632. # Plot
  633. ggplot(preds, aes(x = x, y = predicted, color = group)) +
  634. geom_line(size = 1) +
  635. geom_point(size = 2) +
  636. geom_ribbon(aes(ymin = conf.low, ymax = conf.high, fill = group), alpha = 0.2, color = NA) +
  637. labs(title = "Model-Based Interaction Plot: Trial × Exp",
  638. x = "Trial",
  639. y = "Predicted HRPR",
  640. color = "Exp",
  641. fill = "Exp") +
  642. theme_minimal(base_size = 14)
  643. #####
  644. # FEEDBACK * EXP
  645. library(emmeans)
  646. emmeans_FE <- emmeans(MBtw.VASt_N, ~ Feedback * Exp)
  647. summary(emmeans_FE)
  648. #Contrasti tra feedback entro ciascun esperimento
  649. contrast(emmeans_FE, method = "pairwise", by = "Exp", adjust = "fdr")
  650. #Contrasti di interazione (cioè: la differenza delle differenze)
  651. contrast(emmeans_FE, interaction = "pairwise")
  652. #########################
  653. correl_model = lmer(VAS ~ HR*Exp*Feedback + (1|SSID),data = data, REML = TRUE,
  654. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  655. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  656. # remove outliers (|standardized residuals| > 2 SDs)
  657. correl_model_n = lmer(VAS ~ HR*Exp*Feedback + (1|SSID),data = data, REML = TRUE,
  658. subset = abs(scale(resid(correl_model)))<2,
  659. control = lmerControl(optimizer = "optimx", calc.derivs = FALSE,
  660. optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE)))
  661. # residual analysis
  662. qqnorm(resid(correl_model_n))
  663. qqline(resid(correl_model_n))
  664. # show model results
  665. anova(correl_model_n)
  666. summary(correl_model_n)
  667. # save model results
  668. F_Ext.VAS = anova(correl_model_n)
  669. T_Ext.VAS = data.frame(summary(correl_model_n)$coefficients)
  670. ###FEEDBACK
  671. emmeans_feedback <- emmeans(correl_model_n, ~ Feedback)
  672. # Confronti post hoc a coppie tra le condizioni di Feedback (con correzione Tukey)
  673. pairs(emmeans_feedback, adjust = "fdr")
  674. library(emmeans)
  675. ###FEEDBACK*vas
  676. emtrends(correl_model_n, ~ Feedback, var = "VAS")
  677. pairs(emtrends(correl_model_n, ~ Feedback, var = "VAS"), adjust = "fdr")
  678. # Post hoc: slope di VAS per ciascun tipo di Feedback
  679. em_slopes <- emtrends(correl_model_n, ~ Feedback, var = "VAS")
  680. # Visualizza le slopes
  681. summary(em_slopes)
  682. # Confronti post hoc tra le slopes
  683. pairs(em_slopes, adjust = "fdr")
  684. # slope di VAS → HR per ciascuna combinazione Feedback × Exp
  685. em_triple <- emtrends(correl_model_n, ~ Feedback * Exp, var = "VAS")
  686. # mostra le slopes
  687. summary(em_triple)
  688. # confronti post hoc tra combinazioni
  689. pairs(em_triple, adjust = "fdr")

mixed_models_all.R at commit c1c8721, under MIT · at the source

Overview

Authors: Eleonora Parrotta1,2,3, Patric Bach2, Giovanni Pezzulo4, Andrea Zaccaro3, Mauro Gianni Perrucci3,5,6, Marcello Costantini5,7, Francesca Ferri3,5
  1. Department of Psychology, Sapienza University of Rome, Rome, Italy
  2. School of Psychology, University of Aberdeen, Aberdeen, United Kingdom
  3. Department of Neuroscience, Imaging and Clinical Sciences, “G. d'Annunzio” University of Chieti-Pescara, Chieti, Italy
  4. Institute of Cognitive Sciences and Technologies, National Research Council, Rome, Italy
  5. Institute for Advanced Biomedical Technologies ‑ ITAB, “G. d'Annunzio” University of Chieti-Pescara, Chieti, Italy
  6. UdA-TechLab, Research Center, University “G. d’Annunzio” of Chieti-Pescara, Chieti, Italy
  7. Department of Psychology, “G. d’Annunzio” University of Chieti-Pescara, Chieti, Italy
Journal: eLife, volume 12, article RP90013
Dates: published online 15 June 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.7554/elife.90013 · PMID 42294597 · PMCID PMC13268646 · OpenAlex W4387132705
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: human (organism), pain (population), cognitive (subfield)
Methods: Statistics
Keywords: Human
MeSH: Anticipation, Psychological*, Heart Rate*, Pain Perception*, Adult, Female, Humans, Male, Young Adult (* major topic)
Topic: Psychosomatic Disorders and Their Treatments (Psychiatry and Mental health, Medicine), according to OpenAlex
Funding: European Union's Horizon 2020 (10.3030/945539, 10.3030/952215); European Research Council (10.3030/820213, 820213); the PNRR MUR (PE0000013-FAIR); Leverhulme Trust (RPG-2019-248)
Citations: cited by 1 paper (Europe PMC); 146 references in the paper

Abstract

The experience of pain, like other interoceptive processes, has recently been conceptualized in terms of predictive coding and free energy frameworks. In these views, the brain integrates sensory, proprioceptive, and interoceptive signals to generate probabilistic inferences about upcoming events, which shape both the state and the perception of our inner body. Here, we ask whether it is possible to induce pain expectations by providing false faster (vs. slower) acoustic cardiac feedback before administering electrical cutaneous shocks. We test whether these expectations will shape both the perception of pain and the body’s physiological state toward prior predictions. Results confirmed that faster cardiac feedback elicited pain expectations that affected both perceptual pain judgments and the body’s physiological response. Perceptual pain judgments were biased toward the expected level of pain, such that participants illusorily perceived identical noxious stimuli as more intense and unpleasant. Physiological changes mirrored the predicted level of pain, such that participants’ actual cardiac response in anticipation of pain stimuli showed a deceleration in heart rate, in line with the well-known orienting cardiac response in anticipation of threatening stimuli (Experiment 1). In a control experiment, such perceptual and cardiac modulations were dramatically reduced when the feedback reproduced an exteroceptive, instead of interoceptive, cardiac feedback (Experiment 2). These findings show that cardiac perception can be understood as interoceptive inference that modulates both our perception and the physiological state of the body, thereby actively generating the interoceptive and autonomic consequences that have been predicted.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repository

Its files are read in the Code ↔ Paper reader above.

EP171993/painperception

License: MIT
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: c1c8721f0e3d00fefc602ed32b84e24c218c56d3, 3 June 2026
Languages: R (1)
Size: 5 files, 1 script
Software Heritage: not archived
Found in: “Data availability”
Holds: README, license file
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: brms (1 file), emmeans (1 file), ggplot2 (1 file), lme4 (1 file), lmerTest (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
3 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 1 script, each with its path and the digest of its content;
  • no match between paragraphs and code yet;
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

Datasets cited

Data availability

Anonymized raw ECG recordings underlying the results reported in this study are publicly available on OSF at: https://doi.org/10.17605/OSF.IO/5SW3M. Anonymized trial-level behavioural data, ECG-derived physiological measures, and analysis code are publicly available at: https://github.com/EP171993/painperception (copy archived at Parrotta, 2026).

The following dataset was generated:

Parrotta E. 2026. Exposure to false cardiac feedback alters pain perception and anticipatory cardiac frequency. Open Science Framework.

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, pages, dates, 7 authors, 1 keyword, 8 MeSH terms, 4 funders, 141 references.

Cite

This paper

Parrotta, E., Bach, P., Pezzulo, G., Zaccaro, A., Perrucci, M. G., Costantini, M., & Ferri, F. (2026). Exposure to false cardiac feedback alters pain perception and anticipatory cardiac frequency. eLife, 12, RP90013. https://doi.org/10.7554/elife.90013

BibTeX

@article{parrotta2026exposure,
author = {Parrotta, Eleonora and Bach, Patric and Pezzulo, Giovanni and Zaccaro, Andrea and Perrucci, Mauro Gianni and Costantini, Marcello and Ferri, Francesca},
title = {{Exposure to false cardiac feedback alters pain perception and anticipatory cardiac frequency}},
journal = {eLife},
year = {2026},
month = jun,
volume = {12},
pages = {RP90013},
publisher = {eLife Sciences Publications, Ltd},
issn = {2050-084X},
doi = {10.7554/elife.90013},
url = {https://doi.org/10.7554/elife.90013},
pmid = {42294597},
pmcid = {PMC13268646}
}

RIS

TY - JOUR
AU - Parrotta, Eleonora
AU - Bach, Patric
AU - Pezzulo, Giovanni
AU - Zaccaro, Andrea
AU - Perrucci, Mauro Gianni
AU - Costantini, Marcello
AU - Ferri, Francesca
TI - Exposure to false cardiac feedback alters pain perception and anticipatory cardiac frequency
T2 - eLife
J2 - Elife
PY - 2026
DA - 2026/06/15
VL - 12
SP - RP90013
SN - 2050-084X
PB - eLife Sciences Publications, Ltd
DO - 10.7554/elife.90013
UR - https://doi.org/10.7554/elife.90013
LA - en
ER -

CSL-JSON

{
"id": "10.7554/elife.90013",
"type": "article-journal",
"title": "Exposure to false cardiac feedback alters pain perception and anticipatory cardiac frequency",
"container-title": "eLife",
"author": [
{
"family": "Parrotta",
"given": "Eleonora"
},
{
"family": "Bach",
"given": "Patric"
},
{
"family": "Pezzulo",
"given": "Giovanni"
},
{
"family": "Zaccaro",
"given": "Andrea"
},
{
"family": "Perrucci",
"given": "Mauro Gianni"
},
{
"family": "Costantini",
"given": "Marcello"
},
{
"family": "Ferri",
"given": "Francesca"
}
],
"container-title-short": "Elife",
"volume": "12",
"page": "RP90013",
"DOI": "10.7554/elife.90013",
"PMID": "42294597",
"PMCID": "PMC13268646",
"ISSN": "2050-084X",
"publisher": "eLife Sciences Publications, Ltd",
"URL": "https://doi.org/10.7554/elife.90013",
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
15
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1038/s41467-026-74743-0 [code]
Meta-analytic evidence for distinct neural correlates of conditioned versus verbally induced placebo analgesia.
Journal: Nature communications
In common: lmerTest, lme4, ggplot2, pain, 8 references
[2] doi:10.3389/fnhum.2026.1820376 [code]
The neural dynamics of political socio-pragmatic violations: an ERP study.
Journal: Frontiers in human neuroscience
In common: emmeans, lmerTest, lme4, 1 other tool, 3 references
[3] doi:10.1038/s41598-026-52930-9
Cardiac systole is associated with enhanced go responding in an orthogonalized go/nogo task.
Journal: Scientific reports
In common: cognitive, 6 references
[4] doi:10.1371/journal.pone.0355165 [code]
Pupillary dynamics during hands-off L2 driving and transitions of control under high cognitive load.
Journal: PloS one
In common: brms, emmeans, lmerTest, 2 other tools, cognitive
[5] doi:10.1016/j.isci.2026.116747 [code]
Age and loneliness relate to reduced trust learning and alterations in amygdala function.
Journal: iScience
In common: brms, emmeans, lmerTest, 2 other tools, cognitive
[6] doi:10.1038/s41467-026-73865-9 [code]
Histamine shapes the neurocomputational dynamics of human learning.
Journal: Nature communications
In common: brms, emmeans, lmerTest, 2 other tools, cognitive
[7] doi:10.1523/eneuro.0316-25.2026 [code]
Neural Mechanisms of Self-Generated Action Sequences.
Journal: eNeuro
In common: brms, emmeans, lmerTest, 2 other tools, cognitive
[8] doi:10.1093/nc/niag046 [code]
Awareness of being: a computational neurophenomenological model of mindfulness, mind-wandering, and meta-attentional control.
Journal: Neuroscience of consciousness
In common: 6 references
[9] doi:10.1093/braincomms/fcag120 [code]
The heartbeat evoked potential and the prediction of functional seizure semiology.
Journal: Brain communications
In common: 5 references
[10] doi:10.1038/s41467-026-72916-5 [code]
Precision fMRI reveals that the language network exhibits adult-like left-hemispheric lateralization by 4 years of age.
Journal: Nature communications
In common: brms, emmeans, lmerTest, 2 other tools

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.