medsci-skills 5.11.0 → 5.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/metadata/distribution_files.json +67 -32
  2. package/metadata/distribution_manifest.json +1 -1
  3. package/package.json +1 -1
  4. package/skills/analyze-stats/SKILL.md +3 -0
  5. package/skills/analyze-stats/references/analysis_guides/agreement_reliability.md +130 -0
  6. package/skills/analyze-stats/references/analysis_guides/diagnostic_accuracy.md +184 -0
  7. package/skills/analyze-stats/references/analysis_guides/survival.md +149 -0
  8. package/skills/peer-review/SKILL.md +11 -0
  9. package/skills/peer-review/references/domain-probes/diagnostic_accuracy.md +1 -0
  10. package/skills/peer-review/references/domain-probes/observational_confounding.md +1 -0
  11. package/skills/peer-review/references/domain-probes/survival_prognostic.md +1 -0
  12. package/skills/self-review/SKILL.md +9 -0
  13. package/skills/self-review/references/domain-probes/diagnostic_accuracy.md +1 -0
  14. package/skills/self-review/references/domain-probes/observational_confounding.md +1 -0
  15. package/skills/self-review/references/domain-probes/survival_prognostic.md +1 -0
  16. package/skills/self-review/scripts/check_claim_artifact.py +52 -1
  17. package/skills/self-review/scripts/check_cv_leakage.py +139 -0
  18. package/skills/self-review/skill.yml +1 -0
  19. package/skills/self-review/tests/fixtures/claim_manuscript_single_primary.md +3 -0
  20. package/skills/self-review/tests/fixtures/claim_scripts_consistent/05_primary_cox.R +3 -0
  21. package/skills/self-review/tests/fixtures/claim_scripts_coprimary/05_primary_cox.R +3 -0
  22. package/skills/self-review/tests/fixtures/cv_leakage_bad.md +2 -0
  23. package/skills/self-review/tests/fixtures/cv_leakage_clean.md +2 -0
  24. package/skills/self-review/tests/test_claim_artifact.sh +15 -0
  25. package/skills/self-review/tests/test_cv_leakage.sh +35 -0
  26. package/skills/write-paper/SKILL.md +3 -3
  27. package/skills/write-paper/references/exemplar_discussion/README.md +3 -0
  28. package/skills/write-paper/references/exemplar_discussion/meta_analysis_prisma.md +52 -0
  29. package/skills/write-paper/references/exemplar_methods/README.md +1 -0
  30. package/skills/write-paper/references/exemplar_methods/meta_analysis_prisma.md +65 -0
  31. package/skills/write-paper/references/exemplar_results/README.md +3 -0
  32. package/skills/write-paper/references/exemplar_results/meta_analysis_prisma.md +51 -0
  33. package/skills/write-paper/references/paper_types/meta_analysis.md +5 -0
@@ -153,8 +153,18 @@
153
153
  },
154
154
  {
155
155
  "path": "skills/analyze-stats/SKILL.md",
156
- "size": 55302,
157
- "sha256": "3809b6a55e8a79b7dc48f617e3be0a7faf2a96a6f0ba03d58977a39f267cce81"
156
+ "size": 56422,
157
+ "sha256": "ddb73598fe60601942e8b44a87771e3ba583a585304effac4ae206d53ab4f154"
158
+ },
159
+ {
160
+ "path": "skills/analyze-stats/references/analysis_guides/agreement_reliability.md",
161
+ "size": 6674,
162
+ "sha256": "599ad2551f547043b2bec79e3139d9c2c3ae33c5025784359b22022f882504c2"
163
+ },
164
+ {
165
+ "path": "skills/analyze-stats/references/analysis_guides/diagnostic_accuracy.md",
166
+ "size": 9303,
167
+ "sha256": "e788d612aa4227500e997ec405ab15a300d28c3b6fb2a1e1df33823764cb9537"
158
168
  },
159
169
  {
160
170
  "path": "skills/analyze-stats/references/analysis_guides/health_economic_evaluation.md",
@@ -216,6 +226,11 @@
216
226
  "size": 12803,
217
227
  "sha256": "1639f4810fb5946b24e782b3ce463c000222dcac75709e1e3dac78ad4a2ad72f"
218
228
  },
229
+ {
230
+ "path": "skills/analyze-stats/references/analysis_guides/survival.md",
231
+ "size": 7191,
232
+ "sha256": "6b7f220f2aa165fd16eda58b0b40ac743c122c03757bc00f61efc884b203f337"
233
+ },
219
234
  {
220
235
  "path": "skills/analyze-stats/references/analysis_guides/test_selection.md",
221
236
  "size": 3439,
@@ -2923,8 +2938,8 @@
2923
2938
  },
2924
2939
  {
2925
2940
  "path": "skills/peer-review/SKILL.md",
2926
- "size": 68302,
2927
- "sha256": "bf56fbc16bb3e43fb43f09982d36e68f53f8b0f666564e2828e692472ad0660f"
2941
+ "size": 69384,
2942
+ "sha256": "94033b84b41304d7dbd7919c84c7e9502bdaff1b7e2ef8f5dd7ec9cd7147c532"
2928
2943
  },
2929
2944
  {
2930
2945
  "path": "skills/peer-review/references/aczel_2021_reviewer2_patterns.md",
@@ -2948,8 +2963,8 @@
2948
2963
  },
2949
2964
  {
2950
2965
  "path": "skills/peer-review/references/domain-probes/diagnostic_accuracy.md",
2951
- "size": 11692,
2952
- "sha256": "afc69e56578babcc1cf7c7952d2df301fdc829329fb480a5cf426ff6ab65d9f5"
2966
+ "size": 11967,
2967
+ "sha256": "8c0a8bd30fe16507e2997ddba1bbcfb4ee7eade7df44f1981e1427cd3a28d233"
2953
2968
  },
2954
2969
  {
2955
2970
  "path": "skills/peer-review/references/domain-probes/equity_fairness.md",
@@ -2993,8 +3008,8 @@
2993
3008
  },
2994
3009
  {
2995
3010
  "path": "skills/peer-review/references/domain-probes/observational_confounding.md",
2996
- "size": 34617,
2997
- "sha256": "ec4a11caed6d604e510437827f2b979fcf43ab1ddea6b298eb95673472b2b459"
3011
+ "size": 34795,
3012
+ "sha256": "ff8cb910c7fb83ec08a04ba88866bdf1e0d2f43d972e2861b825595a85b035ee"
2998
3013
  },
2999
3014
  {
3000
3015
  "path": "skills/peer-review/references/domain-probes/polygenic_risk_score.md",
@@ -3038,8 +3053,8 @@
3038
3053
  },
3039
3054
  {
3040
3055
  "path": "skills/peer-review/references/domain-probes/survival_prognostic.md",
3041
- "size": 13765,
3042
- "sha256": "af0e0323876424eec8ba07be22fe99b3100356e8e4895b5b79635e9a2d76e98e"
3056
+ "size": 14022,
3057
+ "sha256": "96195cd1b575535bc12a133125b9d85e2ca7e756b96a0649e03d4c3696e5312b"
3043
3058
  },
3044
3059
  {
3045
3060
  "path": "skills/peer-review/references/exemplar_reviews/README.md",
@@ -3488,8 +3503,8 @@
3488
3503
  },
3489
3504
  {
3490
3505
  "path": "skills/self-review/SKILL.md",
3491
- "size": 104028,
3492
- "sha256": "22bc51c9adcb1fc9ff9120c2650faaa2f5f3a0482fe1fd781e231e2191e21081"
3506
+ "size": 104731,
3507
+ "sha256": "9c74f317d10c7b491e710debb619dcf536689092a59fe749050a0ad41f4aacd5"
3493
3508
  },
3494
3509
  {
3495
3510
  "path": "skills/self-review/references/domain-probes/ai_overclaiming.md",
@@ -3508,8 +3523,8 @@
3508
3523
  },
3509
3524
  {
3510
3525
  "path": "skills/self-review/references/domain-probes/diagnostic_accuracy.md",
3511
- "size": 11692,
3512
- "sha256": "afc69e56578babcc1cf7c7952d2df301fdc829329fb480a5cf426ff6ab65d9f5"
3526
+ "size": 11967,
3527
+ "sha256": "8c0a8bd30fe16507e2997ddba1bbcfb4ee7eade7df44f1981e1427cd3a28d233"
3513
3528
  },
3514
3529
  {
3515
3530
  "path": "skills/self-review/references/domain-probes/equity_fairness.md",
@@ -3553,8 +3568,8 @@
3553
3568
  },
3554
3569
  {
3555
3570
  "path": "skills/self-review/references/domain-probes/observational_confounding.md",
3556
- "size": 34617,
3557
- "sha256": "ec4a11caed6d604e510437827f2b979fcf43ab1ddea6b298eb95673472b2b459"
3571
+ "size": 34795,
3572
+ "sha256": "ff8cb910c7fb83ec08a04ba88866bdf1e0d2f43d972e2861b825595a85b035ee"
3558
3573
  },
3559
3574
  {
3560
3575
  "path": "skills/self-review/references/domain-probes/polygenic_risk_score.md",
@@ -3598,8 +3613,8 @@
3598
3613
  },
3599
3614
  {
3600
3615
  "path": "skills/self-review/references/domain-probes/survival_prognostic.md",
3601
- "size": 13765,
3602
- "sha256": "af0e0323876424eec8ba07be22fe99b3100356e8e4895b5b79635e9a2d76e98e"
3616
+ "size": 14022,
3617
+ "sha256": "96195cd1b575535bc12a133125b9d85e2ca7e756b96a0649e03d4c3696e5312b"
3603
3618
  },
3604
3619
  {
3605
3620
  "path": "skills/self-review/references/exemplar_findings/README.md",
@@ -3668,8 +3683,8 @@
3668
3683
  },
3669
3684
  {
3670
3685
  "path": "skills/self-review/scripts/check_claim_artifact.py",
3671
- "size": 15879,
3672
- "sha256": "1daa64eaabe493f05ae7e2129a2056b673ec588487d5b6c73f41f10104822935"
3686
+ "size": 18475,
3687
+ "sha256": "c99c8090205734b0eae981550ed09e6275d94add608e66df78f4533c35f20924"
3673
3688
  },
3674
3689
  {
3675
3690
  "path": "skills/self-review/scripts/check_classical_style.py",
@@ -3686,6 +3701,11 @@
3686
3701
  "size": 21506,
3687
3702
  "sha256": "7d3e67074d58a28ffee52ce64b486231f103a3ddcaf6b3b6ee83ba5f89c63bc2"
3688
3703
  },
3704
+ {
3705
+ "path": "skills/self-review/scripts/check_cv_leakage.py",
3706
+ "size": 6349,
3707
+ "sha256": "431c5f14ad2c4f59e85b2a5e86150ac708d476e8f2f1da859d81953f522b2117"
3708
+ },
3689
3709
  {
3690
3710
  "path": "skills/self-review/scripts/check_editorial_impression.py",
3691
3711
  "size": 22032,
@@ -3743,8 +3763,8 @@
3743
3763
  },
3744
3764
  {
3745
3765
  "path": "skills/self-review/skill.yml",
3746
- "size": 2345,
3747
- "sha256": "91883d75f33ee873a6ebfbd4d5f2cf0c292d3b90fe4705d6a35e82a26ecf6523"
3766
+ "size": 2381,
3767
+ "sha256": "9499a87d985d981eac9418d6cd95ed79c1d9724aba8854b7d6713d89679d9f1d"
3748
3768
  },
3749
3769
  {
3750
3770
  "path": "skills/setup-medsci/SKILL.md",
@@ -3898,8 +3918,8 @@
3898
3918
  },
3899
3919
  {
3900
3920
  "path": "skills/write-paper/SKILL.md",
3901
- "size": 66426,
3902
- "sha256": "910808b73103a74c26a3a92acb9e7b99529de1b8d9af8efaf740e7ad737fdd09"
3921
+ "size": 66771,
3922
+ "sha256": "1a50c9ceb040a79feaa60e809c7196f9f04b85cab2f5f3402dcaed3f7aee552c"
3903
3923
  },
3904
3924
  {
3905
3925
  "path": "skills/write-paper/references/exemplar_abstract.md",
@@ -3918,8 +3938,8 @@
3918
3938
  },
3919
3939
  {
3920
3940
  "path": "skills/write-paper/references/exemplar_discussion/README.md",
3921
- "size": 2524,
3922
- "sha256": "46d1b9e874f669608cc2c2abc400ab5efa74e37958f59e0c3b8f842dd4934e7d"
3941
+ "size": 2727,
3942
+ "sha256": "3bfcd4eb4df4ca0309446a606550cb11adc4d88daa676f2217249691d2e6b042"
3923
3943
  },
3924
3944
  {
3925
3945
  "path": "skills/write-paper/references/exemplar_discussion/ai_validation_tripod_claim.md",
@@ -3931,6 +3951,11 @@
3931
3951
  "size": 2500,
3932
3952
  "sha256": "7d49d926eb9aa78c42ce0e6579ca299f528dec77dafc17ed9451ada0c2340b3c"
3933
3953
  },
3954
+ {
3955
+ "path": "skills/write-paper/references/exemplar_discussion/meta_analysis_prisma.md",
3956
+ "size": 3357,
3957
+ "sha256": "3cc8542c754372b19acbc25b3c6d6211479b4bbec361c7638c5d2550db3ecc5f"
3958
+ },
3934
3959
  {
3935
3960
  "path": "skills/write-paper/references/exemplar_discussion/observational_cohort_strobe.md",
3936
3961
  "size": 3652,
@@ -3943,8 +3968,8 @@
3943
3968
  },
3944
3969
  {
3945
3970
  "path": "skills/write-paper/references/exemplar_methods/README.md",
3946
- "size": 2247,
3947
- "sha256": "158c4894b92c1b6c19bda8becd947e455c5050cbef4d7d3606c2c46c3123d006"
3971
+ "size": 2342,
3972
+ "sha256": "6966e8b723d0c4ccc012d54e2b8a22ef59b39c22619e63a8597f9badb380b4e7"
3948
3973
  },
3949
3974
  {
3950
3975
  "path": "skills/write-paper/references/exemplar_methods/ai_validation_tripod_claim.md",
@@ -3956,6 +3981,11 @@
3956
3981
  "size": 2739,
3957
3982
  "sha256": "f403613a0f3625c5c7dab8fa16a21635007821ede2b58d265440db51b55165a6"
3958
3983
  },
3984
+ {
3985
+ "path": "skills/write-paper/references/exemplar_methods/meta_analysis_prisma.md",
3986
+ "size": 4160,
3987
+ "sha256": "a0e696e2f581fa6ae8b1aeb86ff7b9a5aaa6bdb2184fee4352e74d2ee6559d90"
3988
+ },
3959
3989
  {
3960
3990
  "path": "skills/write-paper/references/exemplar_methods/observational_cohort_strobe.md",
3961
3991
  "size": 2364,
@@ -3963,8 +3993,8 @@
3963
3993
  },
3964
3994
  {
3965
3995
  "path": "skills/write-paper/references/exemplar_results/README.md",
3966
- "size": 2588,
3967
- "sha256": "0ececbb937fa44a2d89d56124bc7d5d2fea540a459fd0328b0cf534c463baaa1"
3996
+ "size": 2799,
3997
+ "sha256": "14b588f1f4433be71c39f11627148fff797eb87b3b74be75e61514095d4c38ea"
3968
3998
  },
3969
3999
  {
3970
4000
  "path": "skills/write-paper/references/exemplar_results/ai_validation_tripod_claim.md",
@@ -3976,6 +4006,11 @@
3976
4006
  "size": 2772,
3977
4007
  "sha256": "8e45e0b30b4ab505b4eb8c37007f970bfb5aa81375fd011915b07d85c9419598"
3978
4008
  },
4009
+ {
4010
+ "path": "skills/write-paper/references/exemplar_results/meta_analysis_prisma.md",
4011
+ "size": 3170,
4012
+ "sha256": "8aed3ee5d36c15515843d77b5f69fe76f433c9972827a9e18303b38802fac782"
4013
+ },
3979
4014
  {
3980
4015
  "path": "skills/write-paper/references/exemplar_results/observational_cohort_strobe.md",
3981
4016
  "size": 2452,
@@ -4288,8 +4323,8 @@
4288
4323
  },
4289
4324
  {
4290
4325
  "path": "skills/write-paper/references/paper_types/meta_analysis.md",
4291
- "size": 10163,
4292
- "sha256": "5384224227f54deb1829a400ec6994b45a1b7cb34c167c158804e9d361466b26"
4326
+ "size": 10536,
4327
+ "sha256": "2c237409005214baab63627966d8f2b31498e3c28bea34c571a6d5591a068ed8"
4293
4328
  },
4294
4329
  {
4295
4330
  "path": "skills/write-paper/references/paper_types/nhis_cohort.md",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schema_version": 1,
3
- "version": "5.11.0",
3
+ "version": "5.13.0",
4
4
  "owned_skills": [
5
5
  "academic-aio",
6
6
  "add-journal",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "medsci-skills",
3
- "version": "5.11.0",
3
+ "version": "5.13.0",
4
4
  "description": "MedSci Skills — a medical/scientific research skill suite for AI coding agents (Claude Code, Codex, Cursor, Copilot). The npm package is a terminal-friendly installer shortcut; the canonical distribution remains the GitHub repository and the Claude Code plugin marketplace.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://github.com/Aperivue/medsci-skills#readme",
@@ -393,6 +393,7 @@ tbl %>% as_flex_table() %>% flextable::save_as_docx(path = "table.docx")
393
393
 
394
394
  ### Diagnostic Accuracy
395
395
 
396
+ - **Methodology guide**: `references/analysis_guides/diagnostic_accuracy.md` (**load before generating code** — every metric with a CI on a stated analysis unit; the confidence-weighted trap [unweighted-baseline AUC + monotonic-encoding check, produce-side of probe D9]; paired DeLong vs MRMC for reader-generalising claims; per-stratum admissibility [D10]; one-scale-per-comparison [D11])
396
397
  - Template: `references/templates/diagnostic_accuracy.py`
397
398
  - Always report: sensitivity, specificity, PPV, NPV, accuracy, AUC
398
399
  - CIs: Wilson score for proportions, DeLong for AUC
@@ -411,6 +412,7 @@ tbl %>% as_flex_table() %>% flextable::save_as_docx(path = "table.docx")
411
412
 
412
413
  ### Inter-rater Agreement
413
414
 
415
+ - **Methodology guide**: `references/analysis_guides/agreement_reliability.md` (**load before generating code** — the pseudoreplication trap for clustered/repeated measurements + the pseudoreplication-safe per-subject / mixed-effects code, ICC model/type selection, agreement-vs-reliability distinction; pairs with self-review probe O18)
414
416
  - Table type guide: `references/table-standards/table-types/agreement.md` (ICC with model/type + CI, weighted κ for ordinal, Bland–Altman bias + LoA, reliability-vs-agreement distinction, common errors)
415
417
  - Template: `references/templates/agreement_analysis.py`
416
418
  - 2 raters + categorical: Cohen's kappa
@@ -486,6 +488,7 @@ tbl %>% as_flex_table() %>% flextable::save_as_docx(path = "table.docx")
486
488
 
487
489
  ### Survival Analysis
488
490
 
491
+ - **Methodology guide**: `references/analysis_guides/survival.md` (**load before generating code** — competing risks first [naive 1−KM overestimates → produce the Aalen–Johansen/Fine–Gray CIF; cause-specific vs subdistribution for which question, produce-side of probe S3]; PH check → RMST when violated; reverse-KM follow-up + C-index variant [S6]; estimand provenance [S8])
489
492
  - Table type guide: `references/table-standards/table-types/survival_results.md` (Cox results table: events/person-time, reverse-KM median follow-up, univariable + adjusted HR with CI, PH-assumption footnote, EPV/sparse-stratum and RMST-when-PH-violated rules)
490
493
  - Kaplan-Meier curves with number-at-risk table
491
494
  - Log-rank test for group comparison
@@ -0,0 +1,130 @@
1
+ # Inter-rater Agreement & Reliability Guide
2
+
3
+ Quantifying how well two or more raters (or a rater and a reference, or repeated
4
+ measurements) **agree**. The coefficient is easy to compute; the two ways these
5
+ analyses fail review are (1) treating **clustered** measurements as independent
6
+ (pseudoreplication) and (2) confusing **agreement** with **reliability**.
7
+
8
+ ---
9
+
10
+ ## When to Use
11
+
12
+ - **Cohen's kappa** — 2 raters, categorical (nominal) labels.
13
+ - **Weighted kappa** — 2 raters, **ordinal** labels (linear or quadratic weights; disagreement
14
+ by one category counts less than by three).
15
+ - **Fleiss' kappa** — ≥3 raters, categorical.
16
+ - **Krippendorff's alpha** — any number of raters, any measurement level, tolerates missing data.
17
+ - **ICC (intraclass correlation)** — **continuous** measurements; report the model + type (below).
18
+ - **Bland–Altman** — two continuous methods/raters: bias (mean difference) + 95% limits of agreement.
19
+ - NOT for: a single 2×2 vs a reference standard (that is diagnostic accuracy — see
20
+ `table-types/diagnostic_accuracy.md`); not for a multi-reader AI-vs-human comparison with reader +
21
+ case variance (that is an MRMC reader study — see `table-types/reader_study.md`).
22
+
23
+ ---
24
+
25
+ ## Pseudoreplication comes first (this, not the coefficient, is the issue)
26
+
27
+ If each **subject contributes more than one measurement** — several lesions, aneurysms, nodules,
28
+ slices, or time-points per patient — the rows are **not independent**. Computing agreement on the
29
+ **pooled** rows (or on all pairwise distances) uses an inflated *n*, narrows the CI, and gives an
30
+ **anti-conservative** p-value. This is the single most common reliability-study error a reviewer
31
+ catches (it is flagged by the self-review probe **O18** in `observational_confounding.md`).
32
+
33
+ Two correct paths — pick one and state it:
34
+
35
+ 1. **Aggregate to the independent unit first**, then compute agreement per subject. This is the
36
+ simplest defensible analysis when a per-subject summary is meaningful (e.g. mean measurement,
37
+ majority label, or one index lesion per subject).
38
+ 2. **Model the clustering** — a mixed-effects / variance-components ICC with a **subject random
39
+ effect** (or a GEE with an exchangeable working correlation), so the within-subject correlation
40
+ is estimated rather than ignored.
41
+
42
+ A pooled-pairwise test can *flip* on correction: e.g. Mann–Whitney p = 0.02 on 448 pooled
43
+ pairwise distances became p = 0.59 at the per-aneurysm level (n = 112). **Report the unit of
44
+ analysis explicitly**, and when subjects have multiple measurements report a per-subject
45
+ sensitivity analysis.
46
+
47
+ ### Produce the pseudoreplication-safe version
48
+
49
+ ```python
50
+ import pandas as pd
51
+ import pingouin as pg # ICC with model/type + CI
52
+
53
+ # long format: one row per (subject, measurement); rater columns rater1..raterK
54
+ df = pd.read_csv("ratings.csv")
55
+
56
+ # 1) DETECT clustering: more rows than independent subjects
57
+ n_rows, n_subjects = len(df), df["subject_id"].nunique()
58
+ if n_rows > n_subjects:
59
+ print(f"CLUSTERED: {n_rows} measurements from {n_subjects} subjects "
60
+ f"({n_rows / n_subjects:.1f} per subject) — do NOT pool as independent.")
61
+
62
+ # 2a) PER-SUBJECT AGGREGATION (continuous): mean per subject, then ICC on subject means
63
+ per_subj = df.groupby("subject_id")[["rater1", "rater2"]].mean().reset_index()
64
+ long = per_subj.melt(id_vars="subject_id", var_name="rater", value_name="score")
65
+ icc = pg.intraclass_corr(data=long, targets="subject_id", raters="rater", nan_policy="omit")
66
+ print(icc[["Type", "ICC", "CI95%"]]) # report Type (e.g. ICC2/ICC2k) + CI
67
+
68
+ # 2b) OR MODEL THE CLUSTERING (keep every measurement, subject random effect)
69
+ import statsmodels.formula.api as smf
70
+ df_long = df.melt(id_vars="subject_id", value_vars=["rater1", "rater2"],
71
+ var_name="rater", value_name="score")
72
+ m = smf.mixedlm("score ~ 1", data=df_long, groups=df_long["subject_id"])
73
+ res = m.fit()
74
+ var_between = float(res.cov_re.iloc[0, 0]); var_resid = float(res.scale)
75
+ icc_clustered = var_between / (var_between + var_resid)
76
+ print(f"variance-components ICC (subject random effect) = {icc_clustered:.3f}")
77
+ ```
78
+
79
+ ---
80
+
81
+ ## ICC: state the model and the type (they are not interchangeable)
82
+
83
+ - **Model**: one-way random (raters differ per subject), two-way random (same raters, generalise to
84
+ a rater population), two-way mixed (same raters, these raters only).
85
+ - **Type**: **agreement** vs **consistency** (agreement penalises systematic rater bias; consistency
86
+ does not), and **single** vs **average** measurement (average-of-k is higher — only report it if
87
+ the clinical use averages k raters).
88
+ - Report as e.g. **ICC(2,1) = 0.82 (95% CI 0.74–0.88), two-way random, absolute agreement, single
89
+ rater**. An ICC with no model/type is not interpretable.
90
+
91
+ ---
92
+
93
+ ## Agreement is not reliability
94
+
95
+ - **Agreement** = do raters give the *same value* (absolute; Bland–Altman bias, absolute-agreement ICC).
96
+ - **Reliability** = can raters *rank/discriminate subjects* consistently (relative; consistency ICC,
97
+ Pearson/Spearman). A method can be highly reliable yet have poor agreement (a constant offset).
98
+ State which one the clinical claim needs, and use the matching coefficient.
99
+
100
+ ---
101
+
102
+ ## Reporting
103
+
104
+ - The coefficient **with a 95% CI** (bootstrap or analytic), the model/type (for ICC), and the
105
+ **unit of analysis** (per-subject vs per-lesion, and the clustering handling).
106
+ - The interpretation band used (e.g. Landis–Koch), but do not over-interpret a point estimate whose
107
+ CI spans two bands.
108
+ - For continuous methods: Bland–Altman **bias + 95% limits of agreement**, not just a correlation.
109
+
110
+ ---
111
+
112
+ ## Common failures (flag at review)
113
+
114
+ - **Pooled/pairwise agreement on clustered data** (pseudoreplication) — the headline coefficient's
115
+ CI is too narrow; re-run per-subject or with a subject random effect (probe O18).
116
+ - **ICC reported with no model/type** — uninterpretable; the same data yields different ICCs.
117
+ - **Reliability coefficient used to claim agreement** (or vice versa) — a high consistency ICC does
118
+ not establish that the two methods are interchangeable.
119
+ - **Correlation (r) reported as agreement** for two methods — r ignores a constant/proportional bias;
120
+ Bland–Altman is required.
121
+ - **Kappa on ordinal labels unweighted** — treats a one-category disagreement as a full disagreement.
122
+
123
+ ---
124
+
125
+ ## Anti-Hallucination
126
+
127
+ - Never hand-type a coefficient or CI — compute it from the ratings CSV with a seeded script.
128
+ - Do not quote an ICC without the model/type actually estimated by the code.
129
+ - If subjects have multiple measurements, the per-subject sensitivity analysis is **mandatory** —
130
+ do not report only the pooled number.
@@ -0,0 +1,184 @@
1
+ # Diagnostic Accuracy & Reader-Study Guide
2
+
3
+ Estimating how well an index test (or an AI model / reader) separates disease from
4
+ no-disease against a reference standard. The point estimates are easy to compute; the
5
+ ways these analyses fail review are (1) reporting AUC / sensitivity / specificity **without
6
+ CIs or on the wrong analysis unit**, (2) a **confidence-weighted / rating** score whose
7
+ novelty is never tested against the simpler **unweighted** baseline, and (3) comparing two
8
+ AUCs with a **fixed-reader** test when the claim is meant to **generalise to readers**.
9
+
10
+ ---
11
+
12
+ ## When to Use
13
+
14
+ - **Single index test** vs a reference standard → sensitivity, specificity, PPV, NPV, accuracy
15
+ (each with a 95% CI), and AUC (with a DeLong or bootstrap CI).
16
+ - **Two tests / models on the same cases** → a **paired** AUC comparison (DeLong `roc.test`,
17
+ `paired = TRUE`) — never two independent CIs eyeballed for overlap.
18
+ - **Multi-reader multi-case (MRMC)** reader study (AI-vs-reader, modality comparison) → an
19
+ MRMC method (Obuchowski–Rockette / DBM) that carries **reader + case** variance; see
20
+ `table-standards/table-types/reader_study.md` and `make-figures` `exemplar_plots/mrmc_roc.md`.
21
+ - NOT for: pooling accuracy across studies (that is a DTA meta-analysis — bivariate / HSROC via
22
+ `mada`; see the `/meta-analysis` DTA path); not for rater **agreement** (that is
23
+ `analysis_guides/agreement_reliability.md`).
24
+
25
+ ---
26
+
27
+ ## Every metric with a CI, on a stated analysis unit (the floor)
28
+
29
+ A bare AUC / sensitivity / specificity is not reportable. Compute the CI, and state the unit
30
+ (**per-patient vs per-lesion** — multiple lesions per patient are clustered, exactly as in
31
+ `agreement_reliability.md`; a per-lesion count inflates *n*).
32
+
33
+ ```python
34
+ import numpy as np, pandas as pd
35
+ from sklearn.metrics import roc_auc_score
36
+ from statsmodels.stats.proportion import proportion_confint
37
+
38
+ df = pd.read_csv("reader_calls.csv") # truth (0/1), call (0/1), confidence (1..K), stratum
39
+
40
+ # sensitivity / specificity / PPV / NPV at the operating point, each with a Wilson CI
41
+ tp = int(((df.truth == 1) & (df.call == 1)).sum()); fn = int(((df.truth == 1) & (df.call == 0)).sum())
42
+ tn = int(((df.truth == 0) & (df.call == 0)).sum()); fp = int(((df.truth == 0) & (df.call == 1)).sum())
43
+ for name, num, den in [("sensitivity", tp, tp+fn), ("specificity", tn, tn+fp),
44
+ ("PPV", tp, tp+fp), ("NPV", tn, tn+fn)]:
45
+ lo, hi = proportion_confint(num, den, method="wilson")
46
+ print(f"{name} = {num/den:.3f} (95% CI {lo:.3f}-{hi:.3f}), n={den}")
47
+ ```
48
+
49
+ PPV and NPV are **prevalence-dependent** — do not transport them to a population with a
50
+ different base rate; report sensitivity/specificity (prevalence-invariant) plus the study
51
+ prevalence, and recompute predictive values at the target prevalence with Bayes' rule.
52
+
53
+ ---
54
+
55
+ ## The confidence-weighted trap comes first (this, not the AUC, is the issue)
56
+
57
+ When the novelty is a **confidence-weighted / rating-collapsed** score used as the ROC
58
+ predictor, you must (a) confirm the (call × confidence) encoding is **strictly monotone** — a
59
+ folded encoding silently collides the most-confident-positive with a negative call — and
60
+ (b) report the **unweighted binary-call AUC** beside the weighted one, so the weighting earns
61
+ its place. This is the produce-side of self-review / peer-review probe **D9**.
62
+
63
+ ```python
64
+ K = int(df.confidence.max())
65
+
66
+ # intended strictly-monotone composite: negative calls rank below positive calls, and within
67
+ # a call higher confidence pushes further from the decision boundary.
68
+ def composite(call, conf, K):
69
+ return np.where(call == 1, K + conf, K + 1 - conf) # neg: 1..K, pos: K+1..2K
70
+
71
+ # (1) MONOTONIC-ENCODING CHECK — every distinct (call, confidence) must map to a distinct,
72
+ # correctly-ordered score. A collision is the folded-score bug (e.g. real/1 == ai/1).
73
+ combos = df[["call", "confidence"]].drop_duplicates().copy()
74
+ combos["score"] = composite(combos.call.values, combos.confidence.values, K)
75
+ if combos.score.nunique() != len(combos):
76
+ raise ValueError("ENCODING COLLISION — the confidence weighting is not strictly monotone "
77
+ "(folded-score bug); fix the encoding before computing AUC.")
78
+
79
+ # (2) UNWEIGHTED BASELINE beside the weighted primary
80
+ df["score"] = composite(df.call.values, df.confidence.values, K)
81
+ print(f"AUC (confidence-weighted) = {roc_auc_score(df.truth, df.score):.3f}")
82
+ print(f"AUC (unweighted binary call) = {roc_auc_score(df.truth, df.call):.3f}")
83
+ ```
84
+
85
+ If the weighted AUC materially exceeds the unweighted one, the weighting must be justified;
86
+ if it does not, report the simpler estimator as primary. A weighted score that changed a
87
+ hypothesis's direction versus its unweighted baseline is a **post-lock change to disclose**,
88
+ not a silent primary.
89
+
90
+ ---
91
+
92
+ ## AUC confidence intervals and comparing two AUCs
93
+
94
+ DeLong is the field-standard analytic AUC CI and paired comparison; `pROC` (R) is canonical.
95
+
96
+ ```r
97
+ library(pROC)
98
+ d <- read.csv("reader_calls.csv")
99
+ rw <- roc(d$truth, d$score, quiet = TRUE)
100
+ ci.auc(rw) # DeLong 95% CI for the weighted AUC
101
+ ru <- roc(d$truth, d$call, quiet = TRUE)
102
+ roc.test(rw, ru, method = "delong", paired = TRUE) # paired, SAME cases (fixed-reader)
103
+ ```
104
+
105
+ A portable Python bootstrap CI (use when a DeLong implementation is unavailable):
106
+
107
+ ```python
108
+ def auc_ci_bootstrap(y, s, n_boot=2000, seed=42):
109
+ rng = np.random.default_rng(seed); y = np.asarray(y); s = np.asarray(s); n = len(y)
110
+ boots = [roc_auc_score(y[i], s[i]) for i in (rng.integers(0, n, n) for _ in range(n_boot))
111
+ if len(np.unique(y[i])) == 2]
112
+ return tuple(np.percentile(boots, [2.5, 97.5]))
113
+ ```
114
+
115
+ **Fixed-reader vs MRMC.** A paired DeLong test treats the readers/cases as fixed. If the claim
116
+ is that the result **generalises to readers** (a reader sample), a fixed-reader CI understates
117
+ uncertainty — use an MRMC method (Obuchowski–Rockette / DBM) that carries reader + case
118
+ variance, report per-reader and reader-averaged AUC, and state the unit (per-patient vs
119
+ per-lesion) and the superiority/non-inferiority margin. This is probe D9's MRMC lead.
120
+
121
+ ---
122
+
123
+ ## Per-stratum admissibility: produce the table the claim is checked against (D10)
124
+
125
+ A blanket "no subgroup met AUC ≥ 0.75 with lower bound ≥ 0.70" is falsified by a single tabled
126
+ stratum that meets it. Produce the per-stratum AUC + CI and test each row against the rule
127
+ rather than asserting a global negative.
128
+
129
+ ```python
130
+ rule = lambda auc, lo: (auc >= 0.75) and (lo >= 0.70)
131
+ for name, g in df.groupby("stratum"):
132
+ if g.truth.nunique() < 2: # single-class stratum: not estimable
133
+ print(f"{name}: not estimable (one class)"); continue
134
+ auc = roc_auc_score(g.truth, g.score); lo, hi = auc_ci_bootstrap(g.truth, g.score)
135
+ print(f"{name}: AUC {auc:.3f} (95% CI {lo:.3f}-{hi:.3f}) "
136
+ f"{'QUALIFIES' if rule(auc, lo) else 'below'}")
137
+ ```
138
+
139
+ With *k* strata some crossings are expected — frame qualifying strata as hypothesis-generating
140
+ (note the multiplicity), but do **not** deny a row the paper's own table satisfies.
141
+
142
+ ---
143
+
144
+ ## One scale per comparison (D11)
145
+
146
+ Two values sharing a comparison column (or a row-wise "A vs B") must be computed under the
147
+ **same normalisation / definition** — e.g. both volume errors as a standard relative error, not
148
+ one relative and one range-normalised. If they are not identical, recompute on a common scale
149
+ before any superiority/comparability claim, or footnote "not on the same scale".
150
+
151
+ ---
152
+
153
+ ## Reporting
154
+
155
+ - Sensitivity, specificity, PPV, NPV, accuracy, and AUC each **with a 95% CI**; the operating
156
+ point and how it was chosen (Youden vs a prespecified clinical threshold — a threshold picked
157
+ on the same data is optimistic and needs a held-out or cross-validated estimate).
158
+ - The **analysis unit** (per-patient vs per-lesion) and clustering handling; study prevalence.
159
+ - For a weighted-score primary: the **unweighted-baseline AUC** beside it (D9).
160
+ - AUC alone is insufficient for a clinical claim — pair discrimination with **calibration** and a
161
+ **decision-curve / net-benefit** pass at the relevant threshold (see the incremental-value
162
+ table type and the `make-figures` decision-curve exemplar).
163
+
164
+ ---
165
+
166
+ ## Common failures (flag at review)
167
+
168
+ - **AUC / sensitivity / specificity with no CI**, or a per-lesion count reported as if per-patient.
169
+ - **Confidence-weighted AUC with no unweighted baseline** and no monotonic-encoding check (D9) —
170
+ the weighting may have created the result or hidden a folded-score bug.
171
+ - **Two AUCs compared by CI overlap** instead of a paired DeLong test; a **fixed-reader** CI used
172
+ for a claim that generalises to readers (needs MRMC).
173
+ - **"No stratum met the rule" contradicted by the per-stratum table** (D10).
174
+ - **Mixed-normalisation head-to-head** in one column (D11).
175
+ - **PPV/NPV transported** across a prevalence change.
176
+
177
+ ---
178
+
179
+ ## Anti-Hallucination
180
+
181
+ - Never hand-type an AUC, sensitivity/specificity, or CI — compute it from the calls CSV with a
182
+ seeded script; carry each estimate together with its CI (never a bare AUC).
183
+ - Do not report a weighted-score AUC without the unweighted baseline the code produced.
184
+ - Do not quote a DeLong comparison p-value that a paired test on the same cases did not produce.