9  Research log

Append-only. Newest entries at the bottom. What happened, what we learned, and what it changed. Short is fine. This is a hand-written log, so its numbers are typed; the report chapters compute theirs.

2026-10-16 First data delivery: licorice_gargle.csv, emailed by the PI’s fellow, the public medicaldata version (MD5 fe23d586f6bdc92a5db1342f8e41a283). It has 235 rows and 19 columns, matching the codebook names, and every variable the plan uses is present. There are no duplicate rows and no patient ID column. Disagreement with the notes: the codebook says 236 were enrolled, but the file has 235 rows. That is now an open question in Chapter 1 (D-019 covers how the file is handled).

2026-10-16 Structure check at receipt. Outcome values were checked only for validity and missingness, not summarised. The baseline variables and treat have no missing values and contain only codebook codes. All four planned sore-throat scores are integers from 0 to 10. The same 2 patients are missing every post-operative variable (all cough and pain columns), which confirms the client’s “a couple pts no post-op data”. That is under the 5% revisit trigger in D-015, so complete case plus bounds stands. Only 2 patients have a preoperative sore throat, so under the pre-specified rule in D-011 it is left out of the adjusted model. All other covariate categories in that model have at least 10 patients, so D-016 merges nothing. Mallampati class 4 has 1 patient, but Mallampati is not in any model.

2026-10-16 Cleaning function written (clean_raw(), assessments_long() in R/clean.R) with known-answer tests on a hand-built file of 4 patients. Each test was checked by deliberately breaking the code in three ways: swapped treatment labels, sore throat defined as >= 0, and the whole-number check removed. The tests caught all three. The real file passes validation (235 patients; 940 rows in the long table). Two choices the codebook does not settle: patient_id is the raw row number, since the file has no identifier, and age and BMI get only wide plausibility bounds (age 0 to 120, BMI 10 to 80), because the codebook gives no range for them.

2026-10-17 Model functions written in R/models.R: per-assessment logistic regression with risk difference, exchangeable GEE, random-intercept mixed model, adjusted model with sparse-level merging, and missing-outcome bounds. The simulator is in R/simulate.R, and nothing has been fitted to the trial data. Each recovery test uses 200 simulated trials of 235 patients, 2 of them without post-operative data. The marginal truths (per-assessment OR and RD, the GEE’s common OR, and the within-patient correlation) come from integration over the random intercept. The GEE scenario solves for the licorice intercepts that make the marginal OR constant (D-018). Two findings:

2026-10-17 The break harness is now committed as scripts/check_test_breaks.R. D-020 was confirmed by the analyst before any fit to trial data. The final run, against the final tests, lists for each break the tests that failed:

2026-10-17 First fit to the trial data, by make results. All 8 fits ran with no warnings: four per-assessment logistic regressions, the GEE, the mixed model, the adjusted model and the bounds.

2026-10-17 Fixes after the fresh-session analysis review. The review found nothing that changes a conclusion.

  1. The SD 2.75 recovery run had not been committed. It is now scenario 4 in test-recovery.R, with fixed seeds; the earlier run was uncommitted, with other seeds.
    • Mixed model: bias -0.010, coverage 0.950, SE/SD 0.99, mean patient SD 2.777, no warnings.
    • GEE: bias -0.017, coverage 0.955, SE/SD 0.95, working correlation 0.481 against a true 0.486.
    • The review’s concern about GEE coverage of 0.925 looks like Monte Carlo noise: on the committed seeds it is 0.955.
    • make test now takes about two minutes.
  2. The bounds claims now say they cover only patients in the file. A post hoc worst case (D-021) adds the 1 enrolled patient missing from the file to licorice with a sore throat: OR 0.45 (0.25 to 0.81), matching the review’s check. The enrollment count of 236 is now in raw_codebook(), so the number of absent patients is computed.
  3. Every data-dependent claim the review listed now has a computed branch for the other outcome, in Chapters 2, 5, 6 and 7. So does “the arms are similar at baseline”, which the review did not list; it was a judgement on the data and has been removed.
  4. D-020’s date corrected.

The summary (index.qmd) is written from computed results, with the open client questions listed.

2026-10-17 Sped up the simulation-recovery tests from about two minutes to under one, at the analyst’s request. The recovery tests are now split across three files that run in parallel (testthat’s parallel mode, slowest first); almost all the time is the mixed model’s adaptive quadrature. The two checks at the trial’s own clustering (SD 2.75) now use 100 simulated trials instead of 200: they are a robustness check on top of the main recovery tests. Every test still passes, and switching the mixed model back to Laplace still fails both mixed-model recovery files. No analysis changed.