Cortisol profiles, within diagnosis sex differences and power

Version v1.5.0 and scope

Women-versus-men endpoint comparisons are performed within verified controls and within current depression. At each of five saliva sampling occasions, controls are also compared with depression separately within men and within women. There are no overall sex comparisons pooling diagnoses. A new publication chunk adds sex-by-diagnosis interaction contrasts and overall controls-versus-depression comparisons, with numbered main and supplementary outputs. Cohorts are pooled within diagnosis. All P values are unadjusted, with no FDR or other multiplicity correction. Prior versions are retained in Git.

Welch tests compare log waking cortisol, log AUCg and log IC50; effects are women/men geometric-mean ratios. CAR and diurnal log slope use signed values and women-minus-men differences. Report means ± SEM, observed n, effects, 95% CIs and two-sided P values. At least three observations per sex are required. Missing values are not imputed. LIFE current_mdd=0 records remain unverified and outside both diagnostic groups. Current GR measurements come from RESIST only.

Power planning uses two-sided alpha 0.05 and no multiplicity correction. The minimum integer sample above 85% and a 90% design target are calculated per endpoint and diagnosis, retaining pilot sex-specific SDs on the testing scale. Balanced recruitment is primary; the current diagnostic sex ratio is a separate scenario. Counts represent usable results per endpoint, not just enrolled people. The noncentral-t/Satterthwaite Welch approximation is conditional on the assumed true effect. Current pilot effects are uncertain: near-zero effects can yield impractically large counts. Half-pilot-effect sensitivity and standardised d=0.2, 0.3, 0.5 and 0.8 scenarios are supplied. No retrospective observed-power assessment is used to interpret current P values.

Sampling-point tests use positive cortisol values on natural-log scales, reporting women/men or depression/controls geometric-mean ratios and 95% CIs. The two separate diagnostic profiles show arithmetic mean ± SEM of these positive values, with exact unadjusted P values and available n at each occasion. Zero values are excluded, not imputed. Between-diagnosis contrasts are unadjusted for cohort: controls are RESIST-only and depression spans LIFE and RESIST. Pointwise tests do not test a repeated-measures trajectory. Waking tests duplicate the waking endpoint contrasts, not independent evidence.

Salivary cortisol is nmol/L; CAR is +30 minus waking. Slope uses log cortisol at actual waking/afternoon/late times. AUCg integrates all five actual sample times and spans differing LIFE and RESIST observation windows, not a standard 24-hour period. IC50 units and curve-fit quality need verification. DEX and PRED remain separate assays. These within-diagnosis pooled comparisons do not identify sex mechanisms independent of cohort composition, medication or reproductive status. R code has not been executed in the preparation environment.

Publication panels and tables place controls on the left and depression on the right, with men and women within each panel. Matching axes share the same scales. Social, demographic and clinical characteristics use mean ± SD (n) or n/N (%), with unadjusted Welch/Fisher comparisons. A separate colour-coded cohort report compares LIFE and RESIST within source-defined current depression and retains a coded membership roster, including excluded and placeholder records. Study- specific raw symptom scores and education measures are not equated.

Setup and data

packages <- c("knitr","ggplot2","jsonlite","readxl")
missing <- packages[!vapply(packages,requireNamespace,logical(1),quietly=TRUE)]
if(length(missing))stop("Install missing packages in the Console: ",paste(missing,collapse=", "),call.=FALSE)
# No setwd() and no root.dir override: keep the QMD in your cortisol folder.
if(!exists("params"))params<-list(workbook_file="LIFE_RESIST_outputs/LIFE_RESIST_Merged_Baseline.xlsx",
 data_file="LIFE_RESIST_outputs/LIFE_RESIST_Merged_Baseline.csv",output_dir="LIFE_RESIST_Within_Diagnosis_outputs")
if(!file.exists(params$data_file) && !file.exists(params$workbook_file))
 stop("Extract LIFE_RESIST_outputs beside this QMD; merged input data are missing.",call.=FALSE)
D<-if(file.exists(params$workbook_file))as.data.frame(readxl::read_excel(params$workbook_file,sheet="Merged baseline")) else
 read.csv(params$data_file,stringsAsFactors=FALSE,check.names=FALSE,na.strings=c("","NA"))
required<-c("participant_key","study","sex","current_mdd_source","antidepressant_group_source","phq9_raw",
 "age","bmi",paste0("c",1:5,"_nmol_l"),paste0("t",1:5,"_hours"),"dex_ic50","pred_ic50")
if(length(setdiff(required,names(D))))stop("Missing merged fields: ",paste(setdiff(required,names(D)),collapse=", "))
if(anyDuplicated(D$participant_key))stop("Duplicate baseline participant keys.")
dir.create(params$output_dir,recursive=TRUE,showWarnings=FALSE)
out<-function(x)file.path(params$output_dir,x)
as_num<-function(x)suppressWarnings(as.numeric(as.character(x)))
for(v in setdiff(required,c("participant_key","study","sex")))D[[v]]<-as_num(D[[v]])
D$eligible_pooled<-D$sex %in% c("Men","Women")
D$healthy_pooled<-D$eligible_pooled & D$study=="RESIST" & !is.na(D$antidepressant_group_source) & D$antidepressant_group_source==0
D$flag_life_current0_phq_gt7<-D$study=="LIFE" & !is.na(D$current_mdd_source) & D$current_mdd_source==0 & !is.na(D$phq9_raw) & D$phq9_raw>7
stopifnot(!any(D$healthy_pooled & D$study=="LIFE"))
write.csv(D[D$flag_life_current0_phq_gt7,,drop=FALSE],out("LIFE_Diagnostic_Review.csv"),row.names=FALSE,na="")

Recalculate outcomes

C<-as.matrix(D[,paste0("c",1:5,"_nmol_l")]);T<-as.matrix(D[,paste0("t",1:5,"_hours")])
storage.mode(C)<-storage.mode(T)<-"double";C[!is.finite(C)|C<0]<-NA_real_
D[,paste0("c",1:5,"_nmol_l")]<-C
auc<-function(conc,hours)if(any(!is.finite(c(conc,hours)))||any(diff(hours)<=0))NA_real_ else sum((head(conc,-1)+tail(conc,-1))/2*diff(hours))
slope<-function(conc,hours)if(any(!is.finite(c(conc,hours)))||any(conc<=0)||any(diff(hours)<=0))NA_real_ else unname(coef(lm(log(conc)~hours))[2])
D$cort_waking<-C[,1];D$car<-C[,2]-C[,1]
D$slope<-vapply(seq_len(nrow(D)),function(i)slope(C[i,c(1,4,5)],T[i,c(1,4,5)]),numeric(1))
D$aucg_observed<-vapply(seq_len(nrow(D)),function(i)auc(C[i,],T[i,]),numeric(1))
D$observation_hours<-T[,5]-T[,1];D$observation_hours[D$observation_hours<=0]<-NA_real_
D$time_weighted_cortisol<-D$aucg_observed/D$observation_hours;D$wake_hour<-T[,1]
for(v in c("dex_ic50","pred_ic50"))D[[v]][D[[v]]<0 & !is.na(D[[v]])]<-NA_real_
endpoints<-data.frame(variable=c("cort_waking","car","slope","aucg_observed","dex_ic50","pred_ic50","time_weighted_cortisol"),
 measure=c("Waking cortisol","Cortisol awakening response","Diurnal log slope","Observed-window AUCg","Dexamethasone IC50","Prednisolone IC50","Time-weighted cortisol"),
 unit=c("nmol/L","nmol/L","ln(nmol/L)/h","nmol/L × h","source assay units","source assay units","nmol/L"),
 scale=c("log","raw","raw","log","log","log","log"),domain=c(rep("primary",5),rep("supplementary",2)))
smry<-function(x){x<-x[is.finite(x)];list(n=length(x),mean=if(length(x))mean(x)else NA_real_,sem=if(length(x)>1)sd(x)/sqrt(length(x))else NA_real_)}
fmt<-function(x){a<-smry(x);if(a$n<2)paste("n =",a$n)else sprintf("%.3g ± %.3g (%d)",a$mean,a$sem,a$n)}

Uncorrected comparisons

D$depression_group<-D$eligible_pooled & ((D$study=="LIFE" & !is.na(D$current_mdd_source) & D$current_mdd_source==1) | (D$study=="RESIST" & D$antidepressant_group_source %in% c(1,2)))
D$healthy_group<-D$healthy_pooled
H<-D[D$healthy_group,,drop=FALSE];M<-D[D$depression_group,,drop=FALSE]
sx<-function(d,s)d[d$sex==s,,drop=FALSE]
specs<-list(
 list(title="Sex within controls",g1="Men",g2="Women",a=sx(H,"Men"),b=sx(H,"Women")),
 list(title="Sex within depression",g1="Men",g2="Women",a=sx(M,"Men"),b=sx(M,"Women")))
values<-function(d,e){x<-d[[e$variable]];x<-x[is.finite(x)];if(e$scale=="log")x<-x[x>0];x}
compare<-function(spec,e){
 a<-values(spec$a,e);b<-values(spec$b,e)
 r<-data.frame(comparison=spec$title,group1=spec$g1,group2=spec$g2,variable=e$variable,measure=e$measure,unit=e$unit,scale=e$scale,
 n1=length(a),n2=length(b),summary1=fmt(a),summary2=fmt(b),effect=NA_real_,lower=NA_real_,upper=NA_real_,p=NA_real_,status="Not estimable")
 if(min(length(a),length(b))<3)return(r)
 aa<-if(e$scale=="log")log(a)else a;bb<-if(e$scale=="log")log(b)else b
 fit<-tryCatch(t.test(bb,aa,var.equal=FALSE),error=function(e)NULL);if(is.null(fit))return(r)
 z<-c(mean(bb)-mean(aa),fit$conf.int[1],fit$conf.int[2]);if(e$scale=="log")z<-exp(z)
 r$effect<-z[1];r$lower<-z[2];r$upper<-z[3];r$p<-fit$p.value;r$status<-"Estimated";r
}
tables<-setNames(lapply(specs,function(sp)do.call(rbind,lapply(seq_len(nrow(endpoints)),function(j)compare(sp,endpoints[j,])))),vapply(specs,`[[`,character(1),"title"))
for(title in names(tables))print(knitr::kable(tables[[title]],digits=4,caption=paste(title,"— unadjusted P values")))


Table: Sex within controls — unadjusted P values

|comparison          |group1 |group2 |variable               |measure                     |unit               |scale | n1| n2|summary1                |summary2                 | effect|   lower|  upper|      p|status    |
|:-------------------|:------|:------|:----------------------|:---------------------------|:------------------|:-----|--:|--:|:-----------------------|:------------------------|------:|-------:|------:|------:|:---------|
|Sex within controls |Men    |Women  |cort_waking            |Waking cortisol             |nmol/L             |log   | 10| 18|7.47 ± 1.3 (10)         |6.66 ± 0.956 (18)        | 0.7959|  0.3340| 1.8965| 0.5923|Estimated |
|Sex within controls |Men    |Women  |car                    |Cortisol awakening response |nmol/L             |raw   | 10| 18|2.49 ± 1.14 (10)        |3.17 ± 1.12 (18)         | 0.6787| -2.6212| 3.9785| 0.6746|Estimated |
|Sex within controls |Men    |Women  |slope                  |Diurnal log slope           |ln(nmol/L)/h       |raw   |  9| 16|-0.146 ± 0.035 (9)      |-0.14 ± 0.0287 (16)      | 0.0066| -0.0884| 0.1016| 0.8854|Estimated |
|Sex within controls |Men    |Women  |aucg_observed          |Observed-window AUCg        |nmol/L × h         |log   |  9| 15|50.5 ± 6.41 (9)         |59.9 ± 7.3 (15)          | 1.1378|  0.7407| 1.7477| 0.5372|Estimated |
|Sex within controls |Men    |Women  |dex_ic50               |Dexamethasone IC50          |source assay units |log   |  9| 14|2.26e-08 ± 8.11e-09 (9) |1.69e-08 ± 5.61e-09 (14) | 0.8502|  0.3728| 1.9388| 0.6782|Estimated |
|Sex within controls |Men    |Women  |pred_ic50              |Prednisolone IC50           |source assay units |log   |  7|  6|1.26e-07 ± 3.89e-08 (7) |1.32e-07 ± 4.88e-08 (6)  | 0.9985|  0.4277| 2.3309| 0.9969|Estimated |
|Sex within controls |Men    |Women  |time_weighted_cortisol |Time-weighted cortisol      |nmol/L             |log   |  9| 15|3.31 ± 0.412 (9)        |3.75 ± 0.473 (15)        | 1.0786|  0.7073| 1.6449| 0.7121|Estimated |


Table: Sex within depression — unadjusted P values

|comparison            |group1 |group2 |variable               |measure                     |unit               |scale | n1| n2|summary1                 |summary2                 |  effect|   lower|  upper|      p|status    |
|:---------------------|:------|:------|:----------------------|:---------------------------|:------------------|:-----|--:|--:|:------------------------|:------------------------|-------:|-------:|------:|------:|:---------|
|Sex within depression |Men    |Women  |cort_waking            |Waking cortisol             |nmol/L             |log   | 15| 41|5.69 ± 0.94 (15)         |6.87 ± 0.698 (41)        |  1.2693|  0.7730| 2.0843| 0.3290|Estimated |
|Sex within depression |Men    |Women  |car                    |Cortisol awakening response |nmol/L             |raw   | 15| 41|1.35 ± 1.3 (15)          |2.56 ± 0.795 (41)        |  1.2130| -1.9278| 4.3539| 0.4340|Estimated |
|Sex within depression |Men    |Women  |slope                  |Diurnal log slope           |ln(nmol/L)/h       |raw   | 14| 37|-0.139 ± 0.0194 (14)     |-0.141 ± 0.0122 (37)     | -0.0020| -0.0493| 0.0452| 0.9298|Estimated |
|Sex within depression |Men    |Women  |aucg_observed          |Observed-window AUCg        |nmol/L × h         |log   | 14| 37|54.8 ± 7.37 (14)         |52.1 ± 5.77 (37)         |  0.9227|  0.6240| 1.3646| 0.6749|Estimated |
|Sex within depression |Men    |Women  |dex_ic50               |Dexamethasone IC50          |source assay units |log   | 10| 15|1.55e-07 ± 6.34e-08 (10) |5.53e-08 ± 3.45e-08 (15) |  0.2782|  0.0607| 1.2753| 0.0941|Estimated |
|Sex within depression |Men    |Women  |pred_ic50              |Prednisolone IC50           |source assay units |log   |  5| 11|4.35e-07 ± 1.59e-07 (5)  |4.57e-08 ± 9.04e-09 (11) |  0.1192|  0.0228| 0.6224| 0.0189|Estimated |
|Sex within depression |Men    |Women  |time_weighted_cortisol |Time-weighted cortisol      |nmol/L             |log   | 14| 37|3.43 ± 0.421 (14)        |3.96 ± 0.464 (37)        |  1.0792|  0.7471| 1.5591| 0.6737|Estimated |
write.csv(do.call(rbind,tables),out("Unadjusted_Group_Comparisons.csv"),row.names=FALSE,na="")
four<-list("HC men"=sx(H,"Men"),"HC women"=sx(H,"Women"),"MDD men"=sx(M,"Men"),"MDD women"=sx(M,"Women"))

Comparisons at each saliva sampling occasion

categories<-c("Waking","+30 min","+60 min","Afternoon","Late")
timepoint_endpoints<-data.frame(variable=paste0("c",1:5,"_nmol_l"),measure=categories,unit="nmol/L",scale="log")
timepoint_specs<-c(specs,list(
 list(title="Diagnosis within men",g1="Controls",g2="Depression",a=sx(H,"Men"),b=sx(M,"Men")),
 list(title="Diagnosis within women",g1="Controls",g2="Depression",a=sx(H,"Women"),b=sx(M,"Women"))))
timepoint_tables<-setNames(lapply(timepoint_specs,function(sp)do.call(rbind,lapply(1:5,function(j)compare(sp,timepoint_endpoints[j,])))),vapply(timepoint_specs,`[[`,character(1),"title"))
write.csv(do.call(rbind,timepoint_tables),out("Cortisol_Timepoint_Comparisons.csv"),row.names=FALSE,na="")
for(title in names(timepoint_tables))print(knitr::kable(timepoint_tables[[title]],digits=4,caption=paste(title,"— raw two-sided P values")))


Table: Sex within controls — raw two-sided P values

|comparison          |group1 |group2 |variable  |measure   |unit   |scale | n1| n2|summary1         |summary2           | effect|  lower|  upper|      p|status    |
|:-------------------|:------|:------|:---------|:---------|:------|:-----|--:|--:|:----------------|:------------------|------:|------:|------:|------:|:---------|
|Sex within controls |Men    |Women  |c1_nmol_l |Waking    |nmol/L |log   | 10| 18|7.47 ± 1.3 (10)  |6.66 ± 0.956 (18)  | 0.7959| 0.3340| 1.8965| 0.5923|Estimated |
|Sex within controls |Men    |Women  |c2_nmol_l |+30 min   |nmol/L |log   | 10| 18|9.96 ± 1.66 (10) |9.84 ± 1.34 (18)   | 0.6957| 0.2720| 1.7799| 0.4326|Estimated |
|Sex within controls |Men    |Women  |c3_nmol_l |+60 min   |nmol/L |log   | 10| 17|6.03 ± 1.18 (10) |7.83 ± 0.97 (17)   | 1.3050| 0.7325| 2.3249| 0.3477|Estimated |
|Sex within controls |Men    |Women  |c4_nmol_l |Afternoon |nmol/L |log   |  9| 17|1.79 ± 0.401 (9) |1.81 ± 0.53 (17)   | 0.8021| 0.3426| 1.8778| 0.5940|Estimated |
|Sex within controls |Men    |Women  |c5_nmol_l |Late      |nmol/L |log   |  9| 18|0.957 ± 0.29 (9) |0.993 ± 0.388 (18) | 0.7494| 0.3244| 1.7308| 0.4829|Estimated |


Table: Sex within depression — raw two-sided P values

|comparison            |group1 |group2 |variable  |measure   |unit   |scale | n1| n2|summary1          |summary2          | effect|  lower|  upper|      p|status    |
|:---------------------|:------|:------|:---------|:---------|:------|:-----|--:|--:|:-----------------|:-----------------|------:|------:|------:|------:|:---------|
|Sex within depression |Men    |Women  |c1_nmol_l |Waking    |nmol/L |log   | 15| 41|5.69 ± 0.94 (15)  |6.87 ± 0.698 (41) | 1.2693| 0.7730| 2.0843| 0.3290|Estimated |
|Sex within depression |Men    |Women  |c2_nmol_l |+30 min   |nmol/L |log   | 15| 41|7.03 ± 0.948 (15) |9.43 ± 0.898 (41) | 1.2899| 0.8357| 1.9910| 0.2391|Estimated |
|Sex within depression |Men    |Women  |c3_nmol_l |+60 min   |nmol/L |log   | 15| 41|6.07 ± 1.07 (15)  |7.82 ± 1.27 (41)  | 1.2750| 0.7664| 2.1212| 0.3331|Estimated |
|Sex within depression |Men    |Women  |c4_nmol_l |Afternoon |nmol/L |log   | 14| 40|2.4 ± 0.271 (14)  |2.56 ± 0.294 (40) | 0.9195| 0.6410| 1.3190| 0.6391|Estimated |
|Sex within depression |Men    |Women  |c5_nmol_l |Late      |nmol/L |log   | 15| 38|1.06 ± 0.578 (15) |1.14 ± 0.183 (38) | 1.5800| 0.8569| 2.9133| 0.1358|Estimated |


Table: Diagnosis within men — raw two-sided P values

|comparison           |group1   |group2     |variable  |measure   |unit   |scale | n1| n2|summary1         |summary2          | effect|  lower|  upper|      p|status    |
|:--------------------|:--------|:----------|:---------|:---------|:------|:-----|--:|--:|:----------------|:-----------------|------:|------:|------:|------:|:---------|
|Diagnosis within men |Controls |Depression |c1_nmol_l |Waking    |nmol/L |log   | 10| 15|7.47 ± 1.3 (10)  |5.69 ± 0.94 (15)  | 0.7560| 0.3515| 1.6264| 0.4527|Estimated |
|Diagnosis within men |Controls |Depression |c2_nmol_l |+30 min   |nmol/L |log   | 10| 15|9.96 ± 1.66 (10) |7.03 ± 0.948 (15) | 0.6870| 0.3990| 1.1830| 0.1656|Estimated |
|Diagnosis within men |Controls |Depression |c3_nmol_l |+60 min   |nmol/L |log   | 10| 15|6.03 ± 1.18 (10) |6.07 ± 1.07 (15)  | 0.9261| 0.4917| 1.7445| 0.8039|Estimated |
|Diagnosis within men |Controls |Depression |c4_nmol_l |Afternoon |nmol/L |log   |  9| 14|1.79 ± 0.401 (9) |2.4 ± 0.271 (14)  | 1.5882| 0.7626| 3.3075| 0.1929|Estimated |
|Diagnosis within men |Controls |Depression |c5_nmol_l |Late      |nmol/L |log   |  9| 15|0.957 ± 0.29 (9) |1.06 ± 0.578 (15) | 0.7058| 0.3209| 1.5526| 0.3672|Estimated |


Table: Diagnosis within women — raw two-sided P values

|comparison             |group1   |group2     |variable  |measure   |unit   |scale | n1| n2|summary1           |summary2          | effect|  lower|  upper|      p|status    |
|:----------------------|:--------|:----------|:---------|:---------|:------|:-----|--:|--:|:------------------|:-----------------|------:|------:|------:|------:|:---------|
|Diagnosis within women |Controls |Depression |c1_nmol_l |Waking    |nmol/L |log   | 18| 41|6.66 ± 0.956 (18)  |6.87 ± 0.698 (41) | 1.2057| 0.6229| 2.3338| 0.5624|Estimated |
|Diagnosis within women |Controls |Depression |c2_nmol_l |+30 min   |nmol/L |log   | 18| 41|9.84 ± 1.34 (18)   |9.43 ± 0.898 (41) | 1.2737| 0.5218| 3.1091| 0.5777|Estimated |
|Diagnosis within women |Controls |Depression |c3_nmol_l |+60 min   |nmol/L |log   | 17| 41|7.83 ± 0.97 (17)   |7.82 ± 1.27 (41)  | 0.9048| 0.5905| 1.3864| 0.6358|Estimated |
|Diagnosis within women |Controls |Depression |c4_nmol_l |Afternoon |nmol/L |log   | 17| 40|1.81 ± 0.53 (17)   |2.56 ± 0.294 (40) | 1.8206| 0.9924| 3.3400| 0.0527|Estimated |
|Diagnosis within women |Controls |Depression |c5_nmol_l |Late      |nmol/L |log   | 18| 38|0.993 ± 0.388 (18) |1.14 ± 0.183 (38) | 1.4882| 0.7548| 2.9342| 0.2393|Estimated |

Prospective sample size scenarios

welch_power<-function(nm,nw,delta,sm,sw,alpha=.05){
 if(min(nm,nw)<2 || min(sm,sw)<=0)return(NA_real_)
 v1<-sm^2/nm;v2<-sw^2/nw;df<-(v1+v2)^2/(v1^2/(nm-1)+v2^2/(nw-1))
 nc<-abs(delta)/sqrt(v1+v2);critical<-qt(1-alpha/2,df)
 pt(critical,df,ncp=nc,lower.tail=FALSE)+pt(-critical,df,ncp=nc)
}
required_n<-function(delta,sm,sw,target=.90,ratio=1){
 if(any(!is.finite(c(delta,sm,sw,ratio))) || min(sm,sw,ratio)<=0 || abs(delta)<1e-15)
  return(list(men=NA_real_,women=NA_real_,total=NA_real_,power=NA_real_,status="Undefined for zero effect or invalid SD"))
 f<-function(n)welch_power(n,max(2,ceiling(n*ratio)),delta,sm,sw)
 lo<-1;hi<-2
 while(f(hi)<=target && hi<1e9){lo<-hi;hi<-min(1e9,hi*2)}
 if(f(hi)<=target)return(list(men=NA_real_,women=NA_real_,total=NA_real_,power=NA_real_,status="Exceeds search cap"))
 while(hi-lo>1){mid<-floor((lo+hi)/2);if(f(mid)>target)hi<-mid else lo<-mid}
 nw<-max(2,ceiling(hi*ratio))
 stopifnot(f(hi)>target,hi==2 || f(hi-1)<=target)
 list(men=hi,women=nw,total=hi+nw,power=f(hi),status="Conditional pilot-based approximation")
}
power_rows<-list()
for(diagnostic in c("Controls","Depression")){
 d<-if(diagnostic=="Controls")H else M
 ratio<-sum(d$sex=="Women")/sum(d$sex=="Men")
 for(j in seq_len(nrow(endpoints))){
  e<-endpoints[j,];a<-values(sx(d,"Men"),e);b<-values(sx(d,"Women"),e)
  if(min(length(a),length(b))<3)next
  if(e$scale=="log"){a<-log(a);b<-log(b)}
  delta<-mean(b)-mean(a);sm<-sd(a);sw<-sd(b)
  pooled<-sqrt(((length(a)-1)*sm^2+(length(b)-1)*sw^2)/(length(a)+length(b)-2))
  for(allocation in c("Balanced men and women","Current diagnostic sex ratio"))for(target in c(.85,.90)){
   rr<-if(allocation=="Balanced men and women")1 else ratio
   n<-required_n(delta,sm,sw,target,rr);half<-required_n(delta*.5,sm,sw,target,rr)
   power_rows[[length(power_rows)+1L]]<-data.frame(diagnostic_group=diagnostic,variable=e$variable,measure=e$measure,analysis_scale=e$scale,
    current_men=length(a),current_women=length(b),pilot_delta=delta,pilot_sd_men=sm,pilot_sd_women=sw,
    pilot_standardised_difference=delta/pooled,allocation=allocation,women_per_man=rr,target_power=target,alpha=.05,
    required_men=n$men,required_women=n$women,required_total=n$total,achieved_power=n$power,
    additional_men=max(0,n$men-length(a)),additional_women=max(0,n$women-length(b)),status=n$status,
    half_effect_required_men=half$men,half_effect_required_women=half$women,half_effect_required_total=half$total)
  }
 }
}
power_rows<-do.call(rbind,power_rows)
power_scenarios<-do.call(rbind,lapply(c(.2,.3,.5,.8),function(effect)
 do.call(rbind,lapply(c(.85,.90),function(target){n<-required_n(effect,1,1,target)
 data.frame(standardised_difference=effect,target_power=target,required_men=n$men,required_women=n$women,required_total=n$total,achieved_power=n$power,alpha=.05)}))))
write.csv(power_rows,out("Sample_Size_by_Parameter.csv"),row.names=FALSE,na="")
write.csv(power_scenarios,out("Standardised_Effect_Scenarios.csv"),row.names=FALSE,na="")
knitr::kable(power_rows[power_rows$allocation=="Balanced men and women" & power_rows$target_power==.9,],digits=4,caption="90% power conditional on current pilot effects; usable observations required")
90% power conditional on current pilot effects; usable observations required
diagnostic_group variable measure analysis_scale current_men current_women pilot_delta pilot_sd_men pilot_sd_women pilot_standardised_difference allocation women_per_man target_power alpha required_men required_women required_total achieved_power additional_men additional_women status half_effect_required_men half_effect_required_women half_effect_required_total
2 Controls cort_waking Waking cortisol log 10 18 -0.2283 0.9329 1.2710 -0.1959 Balanced men and women 1 0.9 0.05 503 503 1006 0.9003 493 485 Conditional pilot-based approximation 2007 2007 4014
6 Controls car Cortisol awakening response raw 10 18 0.6787 3.5994 4.7481 0.1548 Balanced men and women 1 0.9 0.05 811 811 1622 0.9000 801 793 Conditional pilot-based approximation 3241 3241 6482
10 Controls slope Diurnal log slope raw 9 16 0.0066 0.1050 0.1146 0.0594 Balanced men and women 1 0.9 0.05 5812 5812 11624 0.9000 5803 5796 Conditional pilot-based approximation 23242 23242 46484
14 Controls aucg_observed Observed-window AUCg log 9 15 0.1291 0.4509 0.5425 0.2525 Balanced men and women 1 0.9 0.05 315 315 630 0.9001 306 300 Conditional pilot-based approximation 1257 1257 2514
18 Controls dex_ic50 Dexamethasone IC50 log 9 14 -0.1623 0.9926 0.7194 -0.1946 Balanced men and women 1 0.9 0.05 601 601 1202 0.9002 592 587 Conditional pilot-based approximation 2399 2399 4798
22 Controls pred_ic50 Prednisolone IC50 log 7 6 -0.0015 0.6292 0.7280 -0.0022 Balanced men and women 1 0.9 0.05 4220608 4220608 8441216 0.9000 4220601 4220602 Conditional pilot-based approximation 16882428 16882428 33764856
26 Controls time_weighted_cortisol Time-weighted cortisol log 9 15 0.0757 0.4376 0.5424 0.1494 Balanced men and women 1 0.9 0.05 892 892 1784 0.9001 883 877 Conditional pilot-based approximation 3564 3564 7128
30 Depression cort_waking Waking cortisol log 15 41 0.2385 0.8275 0.6818 0.3301 Balanced men and women 1 0.9 0.05 214 214 428 0.9008 199 173 Conditional pilot-based approximation 851 851 1702
34 Depression car Cortisol awakening response raw 15 41 1.2130 5.0417 5.0936 0.2388 Balanced men and women 1 0.9 0.05 368 368 736 0.9002 353 327 Conditional pilot-based approximation 1469 1469 2938
38 Depression slope Diurnal log slope raw 14 37 -0.0020 0.0725 0.0743 -0.0276 Balanced men and women 1 0.9 0.05 27240 27240 54480 0.9000 27226 27203 Conditional pilot-based approximation 108954 108954 217908
42 Depression aucg_observed Observed-window AUCg log 14 37 -0.0804 0.6052 0.5977 -0.1341 Balanced men and women 1 0.9 0.05 1177 1177 2354 0.9000 1163 1140 Conditional pilot-based approximation 4705 4705 9410
46 Depression dex_ic50 Dexamethasone IC50 log 10 15 -1.2796 1.8910 1.5607 -0.7537 Balanced men and women 1 0.9 0.05 40 40 80 0.9029 30 25 Conditional pilot-based approximation 156 156 312
50 Depression pred_ic50 Prednisolone IC50 log 5 11 -2.1267 1.3439 1.1689 -1.7410 Balanced men and women 1 0.9 0.05 9 9 18 0.9186 4 0 Conditional pilot-based approximation 31 31 62
54 Depression time_weighted_cortisol Time-weighted cortisol log 14 37 0.0763 0.5493 0.6243 0.1260 Balanced men and women 1 0.9 0.05 1251 1251 2502 0.9002 1237 1214 Conditional pilot-based approximation 4998 4998 9996
knitr::kable(power_scenarios,digits=4,caption="Standardised-effect planning scenarios, equal SDs and balanced allocation")
Standardised-effect planning scenarios, equal SDs and balanced allocation
standardised_difference target_power required_men required_women required_total achieved_power alpha
0.2 0.85 450 450 900 0.8501 0.05
0.2 0.90 527 527 1054 0.9004 0.05
0.3 0.85 201 201 402 0.8509 0.05
0.3 0.90 235 235 470 0.9007 0.05
0.5 0.85 73 73 146 0.8510 0.05
0.5 0.90 86 86 172 0.9032 0.05
0.8 0.85 30 30 60 0.8614 0.05
0.8 0.90 34 34 68 0.9015 0.05

Social demographic and clinical characteristics and cohort comparability

if(!file.exists("LIFE_RESIST_Characteristics_Helpers.R"))stop("Extract LIFE_RESIST_Characteristics_Helpers.R beside this QMD.")
source("LIFE_RESIST_Characteristics_Helpers.R",local=TRUE)
cc<-char_build(D,endpoints)
characteristics<-cc$characteristics
jsonlite::write_json(characteristics,out("characteristics_results.json"),auto_unbox=TRUE,pretty=TRUE,na="null")
jsonlite::write_json(cc$cohort,out("cohort_comparability_results.json"),auto_unbox=TRUE,pretty=TRUE,na="null")
rows_data<-function(rows)do.call(rbind,lapply(rows,function(z)as.data.frame(z,stringsAsFactors=FALSE)))
write.csv(rows_data(characteristics$tests),out("Characteristics_All_Comparisons.csv"),row.names=FALSE,na="")
write.csv(rows_data(unlist(cc$cohort$tables,recursive=FALSE)),out("Cohort_Comparability_Tests.csv"),row.names=FALSE,na="")
write.csv(rows_data(cc$cohort$roster),out("Cohort_Colour_Coded_Roster.csv"),row.names=FALSE,na="")
for(title in names(characteristics$sections)){
 d<-as.data.frame(do.call(rbind,characteristics$sections[[title]]),stringsAsFactors=FALSE)
 names(d)<-c("Characteristic","Controls men","Controls women","Controls sex P","Depression men","Depression women","Depression sex P","Diagnosis P men","Diagnosis P women")
 print(knitr::kable(d,caption=paste(title,"— continuous mean ± SD (n), categorical n/N (%)")))
}


Table: Demographic and social characteristics — continuous mean ± SD (n), categorical n/N (%)

|Characteristic                      |Controls men     |Controls women   |Controls sex P |Depression men   |Depression women |Depression sex P |Diagnosis P men |Diagnosis P women |
|:-----------------------------------|:----------------|:----------------|:--------------|:----------------|:----------------|:----------------|:---------------|:-----------------|
|LIFE study, n/N (%)                 |0/11 (0.0%)      |0/19 (0.0%)      |NE             |8/22 (36.4%)     |46/69 (66.7%)    |0.014            |0.031           |<0.001            |
|Age, years                          |27.5 ± 3.14 (11) |31.4 ± 8.4 (19)  |0.087          |38.6 ± 13.7 (22) |31 ± 10.1 (69)   |0.023            |0.001           |0.883             |
|BMI, kg/m²                          |23.2 ± 3.08 (11) |22.3 ± 3.77 (19) |0.477          |25.5 ± 4.38 (22) |25.3 ± 5.34 (69) |0.809            |0.086           |0.008             |
|White ethnic background, n/N (%)    |7/11 (63.6%)     |15/19 (78.9%)    |0.417          |13/22 (59.1%)    |31/66 (47.0%)    |0.461            |1.000           |0.018             |
|Education years (LIFE only)         |n=0              |n=0              |NE             |15.6 ± 3.62 (8)  |15 ± 5.98 (45)   |0.705            |NE              |NE                |
|A-level/higher/degree (RESIST only) |11/11 (100.0%)   |19/19 (100.0%)   |NE             |10/14 (71.4%)    |22/23 (95.7%)    |0.057            |0.105           |1.000             |
|Employed (RESIST only)              |7/11 (63.6%)     |18/19 (94.7%)    |0.047          |10/14 (71.4%)    |15/23 (65.2%)    |1.000            |1.000           |0.027             |
|Married (RESIST only)               |2/11 (18.2%)     |3/19 (15.8%)     |1.000          |4/14 (28.6%)     |3/23 (13.0%)     |0.390            |0.661           |1.000             |
|Smoker (RESIST only)                |0/11 (0.0%)      |1/19 (5.3%)      |1.000          |4/14 (28.6%)     |6/23 (26.1%)     |1.000            |0.105           |0.105             |


Table: Clinical characteristics and assay availability — continuous mean ± SD (n), categorical n/N (%)

|Characteristic                         |Controls men     |Controls women   |Controls sex P |Depression men   |Depression women |Depression sex P |Diagnosis P men |Diagnosis P women |
|:--------------------------------------|:----------------|:----------------|:--------------|:----------------|:----------------|:----------------|:---------------|:-----------------|
|Linked depression PROMIS T             |42.9 ± 5.59 (11) |41.5 ± 5.37 (19) |0.488          |62.6 ± 6.92 (22) |65.8 ± 5.23 (69) |0.056            |<0.001          |<0.001            |
|PHQ-9 total (LIFE only)                |n=0              |n=0              |NE             |15.9 ± 7.24 (8)  |16.2 ± 4.55 (46) |0.900            |NE              |NE                |
|BDI-II total (RESIST only)             |3 ± 2.65 (11)    |2.37 ± 2.89 (19) |0.549          |22.1 ± 7.12 (14) |27.2 ± 6.97 (23) |0.045            |<0.001          |<0.001            |
|Previous depression (RESIST only)      |Not available    |Not available    |NE             |7/14 (50.0%)     |18/23 (78.3%)    |0.146            |NE              |NE                |
|Antidepressant group 2 (RESIST only)   |0/11 (0.0%)      |0/19 (0.0%)      |NE             |3/14 (21.4%)     |8/23 (34.8%)     |0.477            |0.230           |0.005             |
|Any nonnegative saliva result, n/N (%) |10/11 (90.9%)    |18/19 (94.7%)    |1.000          |15/22 (68.2%)    |41/69 (59.4%)    |0.616            |0.218           |0.005             |
|Positive DEX IC50 available, n/N (%)   |9/11 (81.8%)     |14/19 (73.7%)    |1.000          |10/22 (45.5%)    |15/69 (21.7%)    |0.052            |0.067           |<0.001            |
|Positive PRED IC50 available, n/N (%)  |7/11 (63.6%)     |6/19 (31.6%)     |0.132          |5/22 (22.7%)     |11/69 (15.9%)    |0.524            |0.052           |0.186             |
publication_tables<-pub_side_by_side(tables,timepoint_tables,power_rows)

Figures and exports

categories<-c("Waking","+30 min","+60 min","Afternoon","Late")
charts<-list();figures<-character()
save_plot<-function(p,name,w,h){
 if(inherits(p,"ggplot"))print(p)else{grid::grid.newpage();grid::grid.draw(p)}
 for(ext in c("png","pdf","tiff")){
  args<-list(filename=out(paste0(name,".",ext)),plot=p,width=w,height=h,dpi=if(ext=="tiff")600 else 220)
  if(ext=="tiff")args$compression<-"lzw"
  do.call(ggplot2::ggsave,args)
 }
}
profile_specs<-list(Controls=H,Depression=M)
profile_charts<-lapply(names(profile_specs),function(diagnostic){
 d<-profile_specs[[diagnostic]]
 series<-lapply(c("Men","Women"),function(s){
  ss<-lapply(1:5,function(j)smry(values(sx(d,s),timepoint_endpoints[j,])))
  list(name=s,mean=vapply(ss,`[[`,numeric(1),"mean"),sem=vapply(ss,`[[`,numeric(1),"sem"),n=vapply(ss,`[[`,integer(1),"n"))
 })
 list(name=paste0(diagnostic,"_Profile"),title=paste0(diagnostic,": men and women"),categories=categories,series=series,ylabel="Salivary cortisol (nmol/L)",kind="profile",diagnosis=diagnostic)
})
profile_ymax<-max(unlist(lapply(profile_charts,function(ch)unlist(lapply(ch$series,function(z)z$mean+z$sem)))))*1.16
zero_excluded<-sum(vapply(profile_specs,function(d)sum(as.matrix(d[,paste0("c",1:5,"_nmol_l")])==0,na.rm=TRUE),numeric(1)))
format_p<-function(p)ifelse(is.na(p),"NE",ifelse(p<.001,"<0.001",sprintf("%.3f",p)))
profile_grobs<-list()
for(ch in profile_charts){
 ch$ymax<-profile_ymax;charts[[length(charts)+1L]]<-ch
 pd<-do.call(rbind,lapply(ch$series,function(z)data.frame(sample=1:5,group=z$name,mean=z$mean,sem=z$sem)))
 p<-ggplot2::ggplot(pd,ggplot2::aes(sample,mean,colour=group,shape=group,linetype=group,group=group))+
  ggplot2::geom_line(linewidth=.8)+ggplot2::geom_point(size=2.5)+ggplot2::geom_errorbar(ggplot2::aes(ymin=mean-sem,ymax=mean+sem),width=.1)+
  ggplot2::scale_colour_manual(values=c(Men="#1764A0",Women="#C44E16"))+ggplot2::scale_x_continuous(breaks=1:5,labels=categories)+
  ggplot2::coord_cartesian(ylim=c(0,profile_ymax))+ggplot2::labs(title=ch$title,x=NULL,y=ch$ylabel)+
  ggplot2::theme_classic(base_size=12)+ggplot2::theme(legend.position="top",legend.title=ggplot2::element_blank())
 pp<-timepoint_tables[[paste("Sex within",tolower(ch$diagnosis))]]
 tm<-rbind(ch$series[[1]]$n,ch$series[[2]]$n,format_p(pp$p),format_p(timepoint_tables[["Diagnosis within men"]]$p),format_p(timepoint_tables[["Diagnosis within women"]]$p))
 rownames(tm)<-c("Men: n","Women: n","Women vs men: P","MDD vs HC, men: P","MDD vs HC, women: P")
 # Base grid/gtable are used for a readable table under each profile; no extra plot package is needed.
 tg<-gtable::gtable(widths=grid::unit(c(2,rep(1,5)),"null"),heights=grid::unit(rep(1,6),"null"))
 cells<-rbind(c("",categories),cbind(rownames(tm),tm))
 for(i in 1:6)for(j in 1:6)tg<-gtable::gtable_add_grob(tg,grid::textGrob(cells[i,j],gp=grid::gpar(fontsize=9)),t=i,l=j)
 combined<-gtable::gtable(widths=grid::unit(1,"null"),heights=grid::unit(c(6,2.3,1.1),"null"))
 combined<-gtable::gtable_add_grob(combined,ggplot2::ggplotGrob(p),t=1,l=1)
 combined<-gtable::gtable_add_grob(combined,tg,t=2,l=1)
 caption<-paste0("Mean ± SEM; positive values. Unadjusted two-sided Welch tests on log cortisol. HC = controls; MDD = depression.\n",
  "Between-diagnosis P values repeat on both graphs. LIFE/RESIST: afternoon 14:00/16:00; late 20:00/bedtime.\n",
  "Zero concentrations excluded across both groups: ",zero_excluded,". Comparisons are pointwise, not a trajectory test.")
 combined<-gtable::gtable_add_grob(combined,grid::textGrob(caption,gp=grid::gpar(fontsize=9)),t=3,l=1)
 profile_grobs[[ch$diagnosis]]<-combined
 name<-paste0("Figure_Cortisol_Profile_",ch$diagnosis);save_plot(combined,name,10.6,7.4);figures<-c(figures,name)
}

paired<-gtable::gtable(widths=grid::unit(c(1,1),"null"),heights=grid::unit(1,"null"))
paired<-gtable::gtable_add_grob(paired,profile_grobs[["Controls"]],t=1,l=1)
paired<-gtable::gtable_add_grob(paired,profile_grobs[["Depression"]],t=1,l=2)
name<-"Figure_Cortisol_Profile_Side_by_Side";save_plot(paired,name,18,7.4);figures<-c(name,figures)

for(domain in c("Cortisol","GR")){
 selected<-if(domain=="Cortisol")1:4 else 5:6
 points<-means<-annotations<-list()
 for(j in selected){
  e<-endpoints[j,];ss<-lapply(four,function(d)smry(values(d,e)))
  lo<-min(vapply(ss,function(z)z$mean-z$sem,numeric(1)));hi<-max(vapply(ss,function(z)z$mean+z$sem,numeric(1)));pad<-max((hi-lo)*.15,abs(hi)*1e-6,1e-20)
  for(k in 1:2){
   diagnostic<-c("Controls","Depression")[k];selected_ss<-ss[((k-1)*2+1):(k*2)]
   charts[[length(charts)+1L]]<-list(name=paste0(diagnostic,"_",e$variable),title=e$measure,diagnosis=diagnostic,categories=c("Men","Women"),series=list(list(name="Arithmetic mean",mean=vapply(selected_ss,`[[`,numeric(1),"mean"),sem=vapply(selected_ss,`[[`,numeric(1),"sem"),n=vapply(selected_ss,`[[`,integer(1),"n"))),ylabel=e$unit,kind="endpoint",ymin=if(min(vapply(ss,`[[`,numeric(1),"mean"))>0)max(0,lo-pad)else lo-pad,ymax=hi+pad)
  }
  panel<-paste(e$measure,e$unit,if(e$scale=="log")"log10 display"else"",sep="\n")
  transform_y<-function(y)if(e$scale=="log")ifelse(y>0,log10(pmax(y,.Machine$double.xmin)),NA_real_)else y
  for(k in 1:4){
   label<-names(four)[k];x<-values(four[[k]],e);z<-smry(x);diagnostic<-if(k<=2)"Controls"else"Depression";sex<-c("Men","Women")[(k-1)%%2+1]
   points[[length(points)+1L]]<-data.frame(sex=sex,diagnosis=diagnostic,value=transform_y(x),measure=panel)
   means[[length(means)+1L]]<-data.frame(sex=sex,diagnosis=diagnostic,value=transform_y(z$mean),lower=transform_y(z$mean-z$sem),upper=transform_y(z$mean+z$sem),measure=panel)
  }
  for(diagnostic in c("Controls","Depression")){
   rr<-tables[[paste("Sex within",tolower(diagnostic))]];rr<-rr[rr$variable==e$variable,]
   annotations[[length(annotations)+1L]]<-data.frame(diagnosis=diagnostic,measure=panel,label=paste0("P=",format_p(rr$p),"; n=",rr$n1,"/",rr$n2))
  }
 }
 pd<-do.call(rbind,points);md<-do.call(rbind,means);ad<-do.call(rbind,annotations)
 for(z in c("pd","md","ad")){
  q<-get(z);q$diagnosis<-factor(q$diagnosis,levels=c("Controls","Depression"));q$measure<-factor(q$measure,levels=unique(md$measure));assign(z,q)
 }
 pd$sex<-factor(pd$sex,levels=c("Men","Women"));md$sex<-factor(md$sex,levels=c("Men","Women"))
 p<-ggplot2::ggplot(pd,ggplot2::aes(sex,value,colour=sex,shape=sex))+ggplot2::geom_jitter(width=.08,height=0,alpha=.6,size=1.5)+ggplot2::geom_errorbar(data=md,ggplot2::aes(ymin=lower,ymax=upper),colour="black",width=.1)+ggplot2::geom_point(data=md,shape=18,colour="black")+
  ggplot2::geom_text(data=ad,ggplot2::aes(x=1.5,y=Inf,label=label),inherit.aes=FALSE,vjust=1.2,size=3)+
  ggplot2::facet_grid(measure~diagnosis,scales="free_y")+ggplot2::scale_colour_manual(values=c(Men="#1764A0",Women="#C44E16"))+ggplot2::labs(x=NULL,y="Endpoint value",caption="Black diamonds: arithmetic mean ± SEM. Same scale in each parameter row; raw unadjusted within-diagnosis P values.")+ggplot2::theme_classic()+ggplot2::theme(legend.position="none")
 name<-paste0("Figure_",domain,"_Side_by_Side");save_plot(p,name,10.6,if(domain=="Cortisol")11.2 else 5.6);figures<-c(figures,name)
}

estimates<-do.call(rbind,tables);estimates<-estimates[estimates$variable %in% endpoints$variable[1:6],]
estimates$panel<-paste(estimates$measure,ifelse(estimates$scale=="log","log10 ratio","difference"),sep="\n")
for(v in c("effect","lower","upper")){ix<-estimates$scale=="log";estimates[ix,v]<-log10(estimates[ix,v])}
p<-ggplot2::ggplot(estimates,ggplot2::aes(effect,comparison))+ggplot2::geom_vline(xintercept=0,linetype="dashed",colour="grey")+ggplot2::geom_errorbar(ggplot2::aes(xmin=lower,xmax=upper),orientation="y",width=.1)+ggplot2::geom_point()+ggplot2::facet_wrap(~panel,scales="free_x",ncol=3)+ggplot2::labs(x="Group 2 − group 1 difference or log10 ratio",y=NULL)+ggplot2::theme_classic()
name<-"Figure_Within_Diagnosis_Effects";save_plot(p,name,13,8);figures<-c(figures,name)
Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
for 'Group 2 − group 1 difference or log10 ratio' in 'mbcsToSbcs': -
substituted for − (U+2212)

future_n<-unique(ceiling(10^seq(log10(2),7,length.out=160)))
curve_rows<-list()
for(j in 1:6)for(diagnostic in c("Controls","Depression")){
 e<-endpoints[j,];r<-power_rows[power_rows$diagnostic_group==diagnostic & power_rows$variable==e$variable & power_rows$allocation=="Balanced men and women" & power_rows$target_power==.9,]
 curve_rows[[length(curve_rows)+1L]]<-data.frame(per_sex=future_n,power=vapply(future_n,function(n)welch_power(n,n,r$pilot_delta,r$pilot_sd_men,r$pilot_sd_women),numeric(1)),diagnosis=diagnostic,measure=e$measure)
}
curve_data<-do.call(rbind,curve_rows);curve_data<-curve_data[is.finite(curve_data$power),]
p<-ggplot2::ggplot(curve_data,ggplot2::aes(per_sex,power,colour=diagnosis))+ggplot2::geom_line()+ggplot2::geom_hline(yintercept=c(.85,.9),linetype="dashed",colour="grey")+ggplot2::scale_x_log10()+ggplot2::facet_wrap(~measure,ncol=3)+ggplot2::labs(x="Future usable participants per sex",y="Conditional prospective power")+ggplot2::theme_classic()+ggplot2::theme(legend.position="top")
name<-"Figure_Prospective_Power_Curves";save_plot(p,name,12,7);figures<-c(figures,name)

counts<-lapply(list(Controls=H,Depression=M),function(d)list(n=nrow(d),men=sum(d$sex=="Men"),women=sum(d$sex=="Women")))
write.csv(D,out("Group_Analysis_Data.csv"),row.names=FALSE,na="")
jsonlite::write_json(list(analysis_scope="within_diagnosis_timepoints_power_v4",tables=tables,timepoint_tables=timepoint_tables,characteristics=characteristics,publication_tables=publication_tables,power_rows=power_rows,power_scenarios=power_scenarios,charts=charts,figures=figures,counts=counts,version="v1.5.0",multiplicity_correction="none"),out("within_diagnosis_results.json"),dataframe="rows",auto_unbox=TRUE,pretty=TRUE,na="null")
capture.output(sessionInfo(),file=out("sessionInfo.txt"))

Numbered main publication and supplementary results

if(!file.exists("LIFE_RESIST_Publication_Helpers.R"))stop("Extract LIFE_RESIST_Publication_Helpers.R beside this QMD.")
source("LIFE_RESIST_Publication_Helpers.R",local=TRUE)
publication<-pub15_build(D,endpoints,tables,timepoint_tables,charts,counts,power_rows,power_scenarios,characteristics,publication_tables)
jsonlite::write_json(publication,out("publication_results.json"),dataframe="rows",auto_unbox=TRUE,pretty=TRUE,na="null")
for(key in c("characteristics_overall","diagnosis_overall","endpoint_interactions","timepoint_interactions"))
 write.csv(rows_data(publication[[key]]),out(paste0("Publication_",key,".csv")),row.names=FALSE,na="")
write.csv(rows_data(unlist(publication$diagnosis_by_sex,recursive=FALSE)),out("Publication_diagnosis_by_sex.csv"),row.names=FALSE,na="")
knitr::kable(rows_data(publication$endpoint_interactions),digits=4,caption="Sex by diagnosis interaction contrasts, unadjusted P values")
Sex by diagnosis interaction contrasts, unadjusted P values
variable measure unit scale scope hc_men_n hc_women_n mdd_men_n mdd_women_n effect lower upper p contrast se df t status
cort_waking Waking cortisol nmol/L log Pooled 10 18 15 41 1.5948 0.5989 4.2470 0.3406 0.4667 0.4835 37.2393 0.9653 Estimated
cort_waking Waking cortisol nmol/L log RESIST 10 18 13 18 1.1746 0.4302 3.2074 0.7478 0.1609 0.4970 40.0651 0.3238 Estimated
car Cortisol awakening response nmol/L raw Pooled 10 18 15 41 0.5344 -3.9049 4.9736 0.8098 0.5344 2.2080 48.1265 0.2420 Estimated
car Cortisol awakening response nmol/L raw RESIST 10 18 13 18 0.7535 -3.7948 5.3017 0.7407 0.7535 2.2641 49.7443 0.3328 Estimated
slope Diurnal log slope ln(nmol/L)/h raw Pooled 9 16 14 37 -0.0086 -0.1126 0.0953 0.8658 -0.0086 0.0507 27.0952 -0.1706 Estimated
slope Diurnal log slope ln(nmol/L)/h raw RESIST 9 16 12 17 -0.0254 -0.1317 0.0809 0.6287 -0.0254 0.0520 29.4048 -0.4887 Estimated
aucg_observed Observed-window AUCg nmol/L × h log Pooled 9 15 14 37 0.8110 0.4615 1.4254 0.4576 -0.2095 0.2793 41.5508 -0.7499 Estimated
aucg_observed Observed-window AUCg nmol/L × h log RESIST 9 15 12 16 0.6276 0.3308 1.1905 0.1494 -0.4659 0.3172 41.8379 -1.4687 Estimated
dex_ic50 Dexamethasone IC50 source assay units log Pooled 9 14 10 15 0.3272 0.0609 1.7572 0.1832 -1.1173 0.8163 25.0977 -1.3686 Estimated
dex_ic50 Dexamethasone IC50 source assay units log RESIST 9 14 10 15 0.3272 0.0609 1.7572 0.1832 -1.1173 0.8163 25.0977 -1.3686 Estimated
pred_ic50 Prednisolone IC50 source assay units log Pooled 7 6 5 11 0.1194 0.0208 0.6860 0.0216 -2.1252 0.7939 10.9582 -2.6768 Estimated
pred_ic50 Prednisolone IC50 source assay units log RESIST 7 6 5 11 0.1194 0.0208 0.6860 0.0216 -2.1252 0.7939 10.9582 -2.6768 Estimated
time_weighted_cortisol Time-weighted cortisol nmol/L log Pooled 9 15 14 37 1.0006 0.5803 1.7250 0.9984 0.0006 0.2702 43.3354 0.0021 Estimated
time_weighted_cortisol Time-weighted cortisol nmol/L log RESIST 9 15 12 16 0.7084 0.3855 1.3018 0.2596 -0.3447 0.3018 43.6436 -1.1421 Estimated

Render LIFE_RESIST_Publication_Only.qmd to create both numbered Word documents. The main Word file contains controls-versus-depression characteristics and cortisol results. The supplementary file contains sex-stratified comparisons, interaction CIs, RESIST-only interaction sensitivity and the existing power tables.