What is meant by "strength of evidence" and "quality of evidence" in a clinical trial? what is the Method of calculation of sample size for a therapeutic trial ?-from standard literature like sabiston/bailey , if possible in an easy memorable format/Table in a downloadable one/two page pdf/image.
mkdir -p /tmp/workspace/evidence-pdf && ls /tmp/workspace/evidence-pdf/
~/evidence-pdf/generate_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.lib import colors
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.lib.colors import HexColor
OUTPUT = "/tmp/workspace/evidence-pdf/Evidence_SampleSize_ClinicalTrials.pdf"
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=1.5*cm,
rightMargin=1.5*cm,
topMargin=1.5*cm,
bottomMargin=1.5*cm
)
# ── Colour palette ────────────────────────────────────────────────────────────
NAVY = HexColor("#1a3a5c")
TEAL = HexColor("#0d7377")
LTBLUE = HexColor("#d6eaf8")
LTGREY = HexColor("#f2f3f4")
RED = HexColor("#c0392b")
GREEN = HexColor("#1e8449")
AMBER = HexColor("#d68910")
WHITE = colors.white
BLACK = colors.black
ORANGE = HexColor("#e67e22")
styles = getSampleStyleSheet()
def h1(text):
return Paragraph(text, ParagraphStyle(
"H1", fontSize=13, textColor=WHITE, backColor=NAVY,
spaceAfter=4, spaceBefore=6, leftIndent=4, rightIndent=4,
leading=16, fontName="Helvetica-Bold", alignment=TA_CENTER
))
def h2(text):
return Paragraph(text, ParagraphStyle(
"H2", fontSize=10.5, textColor=WHITE, backColor=TEAL,
spaceAfter=3, spaceBefore=5, leftIndent=3,
leading=14, fontName="Helvetica-Bold"
))
def body(text, size=8.5):
return Paragraph(text, ParagraphStyle(
"Body", fontSize=size, leading=12, spaceAfter=2,
fontName="Helvetica", textColor=BLACK
))
def small(text):
return Paragraph(text, ParagraphStyle(
"Small", fontSize=7.5, leading=10, spaceAfter=2,
fontName="Helvetica-Oblique", textColor=HexColor("#555555")
))
def bold(text, size=8.5):
return Paragraph(f"<b>{text}</b>", ParagraphStyle(
"Bold", fontSize=size, leading=12, spaceAfter=1,
fontName="Helvetica-Bold", textColor=NAVY
))
def cell(text, bold=False, bg=None, size=8, color=BLACK):
style = ParagraphStyle(
"Cell", fontSize=size, leading=11,
fontName="Helvetica-Bold" if bold else "Helvetica",
textColor=color
)
return Paragraph(text, style)
# ─────────────────────────────────────────────────────────────────────────────
story = []
# ══════════════════════════════════════════════════════════════════
# PAGE 1 — STRENGTH / QUALITY OF EVIDENCE
# ══════════════════════════════════════════════════════════════════
story.append(h1("STRENGTH & QUALITY OF EVIDENCE | SAMPLE SIZE IN CLINICAL TRIALS"))
story.append(small(" Sources: Sabiston Textbook of Surgery 21e (Ch 6) · Schwartz's Principles of Surgery 11e (Ch 51) · Barash's Clinical Anesthesia 9e · Cecil Medicine"))
story.append(Spacer(1, 3))
# ── Section 1 ─────────────────────────────────────────────────────
story.append(h2("1. KEY DEFINITIONS"))
def_data = [
[cell("Term", bold=True, bg=NAVY, color=WHITE),
cell("Definition", bold=True, bg=NAVY, color=WHITE)],
[cell("Quality of Evidence\n(Certainty of Evidence)", bold=True),
cell("Confidence that the true effect lies close to the estimated effect.\nReflects study design, risk of bias, consistency, directness, and precision.")],
[cell("Strength of a\nRecommendation", bold=True),
cell("How confident we are that following the recommendation will do more good than harm.\nDepends on quality of evidence PLUS values, preferences, costs, feasibility.")],
[cell("Internal Validity", bold=True),
cell("Observed outcome is truly due to the intervention (not bias/confounding).")],
[cell("External Validity\n(Generalisability)", bold=True),
cell("Study results apply to real-world clinical practice outside the study.")],
]
def_table = Table(def_data, colWidths=[4.5*cm, 13.5*cm])
def_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("BACKGROUND", (0,1), (-1,1), LTBLUE),
("BACKGROUND", (0,2), (-1,2), LTGREY),
("BACKGROUND", (0,3), (-1,3), LTBLUE),
("BACKGROUND", (0,4), (-1,4), LTGREY),
("VALIGN", (0,0), (-1,-1), "TOP"),
("GRID", (0,0), (-1,-1), 0.5, colors.grey),
("ROWBACKGROUND",(0,0), (-1,0), NAVY),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
]))
story.append(def_table)
story.append(Spacer(1, 5))
# ── Section 2: Evidence Hierarchy ─────────────────────────────────
story.append(h2("2. EVIDENCE HIERARCHY (Oxford CEBM / USPSTF Levels)"))
hier_data = [
[cell("Level", bold=True, color=WHITE), cell("Study Type", bold=True, color=WHITE),
cell("Typical RCT Features", bold=True, color=WHITE)],
[cell("1 ★★★★", bold=True, color=GREEN),
cell("Systematic review / Meta-analysis of RCTs"),
cell("Multiple large RCTs pooled; highest certainty")],
[cell("2 ★★★☆", bold=True, color=GREEN),
cell("Single well-designed RCT (adequate size)"),
cell("Randomisation, allocation concealment, blinding, ITT")],
[cell("3 ★★☆☆", bold=True, color=AMBER),
cell("Cohort study / Non-randomised controlled trial"),
cell("Prospective; large n; propensity matching")],
[cell("4 ★☆☆☆", bold=True, color=ORANGE),
cell("Case-control study / Case series"),
cell("Retrospective; selection bias possible")],
[cell("5 ☆☆☆☆", bold=True, color=RED),
cell("Expert opinion / Mechanism-based reasoning"),
cell("No direct patient-level data")],
]
hier_table = Table(hier_data, colWidths=[2.8*cm, 7.5*cm, 7.7*cm])
hier_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("BACKGROUND", (0,1), (-1,2), HexColor("#d5f5e3")),
("BACKGROUND", (0,3), (-1,3), HexColor("#fef9e7")),
("BACKGROUND", (0,4), (-1,4), HexColor("#fdebd0")),
("BACKGROUND", (0,5), (-1,5), HexColor("#fadbd8")),
("GRID", (0,0), (-1,-1), 0.5, colors.grey),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
]))
story.append(hier_table)
story.append(Spacer(1, 5))
# ── Section 3: GRADE ───────────────────────────────────────────────
story.append(h2("3. GRADE SYSTEM — Quality of Evidence & Strength of Recommendation (Sabiston Table 6.3)"))
grade_data = [
[cell("Quality", bold=True, color=WHITE), cell("Definition (Confidence in Effect Estimate)", bold=True, color=WHITE),
cell("Strength of\nRecommendation", bold=True, color=WHITE), cell("Starting Design", bold=True, color=WHITE)],
[cell("HIGH\n⬆⬆⬆⬆", bold=True, color=GREEN),
cell("Very confident — true effect close to estimated effect; further research unlikely to change confidence."),
cell("STRONG", bold=True, color=GREEN),
cell("RCT")],
[cell("MODERATE\n⬆⬆⬆", bold=True, color=HexColor("#1a9e60")),
cell("Moderately confident — true effect likely close, but possibility of substantial difference."),
cell("CONDITIONAL", bold=True, color=AMBER),
cell("RCT (downgraded)\nor Obs. (upgraded)")],
[cell("LOW\n⬆⬆", bold=True, color=AMBER),
cell("Limited confidence — true effect may be substantially different."),
cell("CONDITIONAL", bold=True, color=AMBER),
cell("Observational")],
[cell("VERY LOW\n⬆", bold=True, color=RED),
cell("Very little confidence — true effect likely substantially different from estimate."),
cell("CONDITIONAL", bold=True, color=RED),
cell("Case series /\nExpert opinion")],
]
grade_table = Table(grade_data, colWidths=[2.5*cm, 8.5*cm, 2.8*cm, 4.2*cm])
grade_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("BACKGROUND", (0,1), (-1,1), HexColor("#d5f5e3")),
("BACKGROUND", (0,2), (-1,2), HexColor("#eafaf1")),
("BACKGROUND", (0,3), (-1,3), HexColor("#fef9e7")),
("BACKGROUND", (0,4), (-1,4), HexColor("#fadbd8")),
("GRID", (0,0), (-1,-1), 0.5, colors.grey),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
]))
story.append(grade_table)
story.append(Spacer(1, 4))
# ── Section 4: Upgrade / Downgrade ────────────────────────────────
story.append(h2("4. FACTORS THAT UPGRADE OR DOWNGRADE QUALITY (Sabiston Table 6.4 / GRADE)"))
updown_data = [
[cell("DOWNGRADE — Lower Quality If:", bold=True, color=WHITE),
cell("UPGRADE — Higher Quality If:", bold=True, color=WHITE)],
[cell("• Risk of bias (poor allocation concealment,\n no blinding, high attrition, ITT violation)"),
cell("• Large magnitude of effect")],
[cell("• Inconsistency (heterogeneous results\n across studies)"),
cell("• Dose-response gradient present")],
[cell("• Indirectness (surrogate endpoints,\n different population)"),
cell("• All plausible confounders would reduce\n the observed effect (conservative estimate)")],
[cell("• Imprecision (wide CIs, small n)"),
cell("• Consistency across multiple independent\n studies")],
[cell("• Publication bias (positive results\n preferentially published)"),
cell("• Representative / generalisable\n patient population")],
]
updown_table = Table(updown_data, colWidths=[9*cm, 9*cm])
updown_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), RED),
("BACKGROUND", (1,0), (1,0), GREEN),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("BACKGROUND", (0,1), (0,-1), HexColor("#fdedec")),
("BACKGROUND", (1,1), (1,-1), HexColor("#e9f7ef")),
("GRID", (0,0), (-1,-1), 0.5, colors.grey),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
]))
story.append(updown_table)
story.append(Spacer(1, 4))
# ── Section 5: Strong vs Weak ──────────────────────────────────────
story.append(h2("5. STRONG vs WEAK RECOMMENDATION (Schwartz Table 51)"))
sw_data = [
[cell("Factor", bold=True, color=WHITE),
cell("STRONG Recommendation Example", bold=True, color=WHITE),
cell("WEAK Recommendation Example", bold=True, color=WHITE)],
[cell("Quality of Evidence"),
cell("Many high-quality RCTs: inhaled steroids in asthma"),
cell("Only case series: pleurodesis in pneumothorax")],
[cell("Balance of Effects"),
cell("Aspirin in MI: reduces mortality, minimal toxicity"),
cell("Warfarin in low-risk AF: small stroke benefit vs bleeding risk")],
[cell("Patient Values"),
cell("Outcomes consistently valued the same by patients"),
cell("Significant patient-to-patient variability in values")],
[cell("Resources / Cost"),
cell("Low cost, widely available"),
cell("High cost, resource-limited settings")],
]
sw_table = Table(sw_data, colWidths=[3.5*cm, 7.5*cm, 7*cm])
sw_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("BACKGROUND", (0,1), (0,-1), LTBLUE),
("ROWBACKGROUND",(0,1), (-1,-1), [LTGREY, LTBLUE, LTGREY, LTBLUE]),
("GRID", (0,0), (-1,-1), 0.5, colors.grey),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
]))
story.append(sw_table)
# ═════════════════ PAGE BREAK ════════════════════════════════════
from reportlab.platypus import PageBreak
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════
# PAGE 2 — SAMPLE SIZE CALCULATION
# ══════════════════════════════════════════════════════════════════
story.append(h1("SAMPLE SIZE CALCULATION FOR A THERAPEUTIC (CLINICAL) TRIAL"))
story.append(small(" Sources: Creasy & Resnik's Maternal-Fetal Medicine 9e · Barash's Clinical Anesthesia 9e · Goldman-Cecil Medicine"))
story.append(Spacer(1, 4))
# ── Error Types ────────────────────────────────────────────────────
story.append(h2("1. ERROR TYPES IN HYPOTHESIS TESTING — The 2×2 Truth Table"))
err_data = [
[cell("", bold=True), cell("H₀ TRUE\n(No real difference)", bold=True, color=WHITE),
cell("H₀ FALSE\n(Real difference exists)", bold=True, color=WHITE)],
[cell("Reject H₀\n(Test says: DIFFERENCE)", bold=True),
cell("❌ TYPE I ERROR (α)\nFalse Positive\n\"We said there was a difference\nbut there wasn't.\""),
cell("✅ CORRECT\nTrue Positive\n(Power = 1 - β)")],
[cell("Fail to Reject H₀\n(Test says: NO DIFFERENCE)", bold=True),
cell("✅ CORRECT\nTrue Negative"),
cell("❌ TYPE II ERROR (β)\nFalse Negative\n\"We missed a real difference.\"\n(Underpowered study)")],
]
err_table = Table(err_data, colWidths=[4.5*cm, 7.25*cm, 6.25*cm])
err_table.setStyle(TableStyle([
("BACKGROUND", (1,0), (-1,0), NAVY),
("TEXTCOLOR", (1,0), (-1,0), WHITE),
("BACKGROUND", (0,0), (0,-1), TEAL),
("TEXTCOLOR", (0,0), (0,-1), WHITE),
("BACKGROUND", (0,0), (0,0), HexColor("#0a2540")),
("BACKGROUND", (1,1), (1,1), HexColor("#fadbd8")),
("BACKGROUND", (2,1), (2,1), HexColor("#d5f5e3")),
("BACKGROUND", (1,2), (1,2), HexColor("#d5f5e3")),
("BACKGROUND", (2,2), (2,2), HexColor("#fadbd8")),
("GRID", (0,0), (-1,-1), 0.5, colors.grey),
("VALIGN", (0,0), (-1,-1), "TOP"),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 5),
]))
story.append(err_table)
story.append(Spacer(1, 5))
# ── Key Parameters ─────────────────────────────────────────────────
story.append(h2("2. KEY PARAMETERS NEEDED BEFORE CALCULATING SAMPLE SIZE"))
param_data = [
[cell("Parameter", bold=True, color=WHITE), cell("Symbol", bold=True, color=WHITE),
cell("Conventional Value", bold=True, color=WHITE), cell("Meaning", bold=True, color=WHITE)],
[cell("Alpha (Type I error)"), cell("α"), cell("0.05 (5%)"),
cell("Chance of a false-positive result. By convention ≤ 5%.")],
[cell("Beta (Type II error)"), cell("β"), cell("0.10–0.20 (10–20%)"),
cell("Chance of a false-negative result. Accepted up to 20%.")],
[cell("Power"), cell("1–β"), cell("80%–90%"),
cell("Probability of detecting a true difference when it exists.")],
[cell("Significance level (p-value)"),cell("p"), cell("< 0.05"),
cell("Threshold below which H₀ is rejected.")],
[cell("Effect size / Minimum\nDetectable Difference"), cell("Δ / MDD"), cell("Clinically meaningful\ndifference"),
cell("Smallest difference that matters clinically. Smaller Δ → larger n.")],
[cell("Baseline event rate\n(Control group incidence)"), cell("P₁"), cell("From literature\nor pilot data"),
cell("Incidence of outcome in unexposed/control group.")],
[cell("Expected event rate\n(Treatment group)"), cell("P₂"), cell("P₁ ± Δ"),
cell("Anticipated incidence in treated group.")],
[cell("Standard Deviation"), cell("σ"), cell("Continuous data only"),
cell("Variability in the outcome measure (from pilot study / literature).")],
[cell("Ratio of groups"), cell("r"), cell("Usually 1:1"),
cell("Ratio of exposed to unexposed (or cases to controls). Affects n per arm.")],
[cell("Side of test\n(Tails)"), cell("—"), cell("Two-tailed (usual)"),
cell("One-tailed only when direction of effect is known a priori.")],
]
param_table = Table(param_data, colWidths=[4.5*cm, 1.5*cm, 3.5*cm, 8.5*cm])
param_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("ROWBACKGROUND",(0,1), (-1,-1), [LTGREY, LTBLUE]*10),
("GRID", (0,0), (-1,-1), 0.5, colors.grey),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
]))
story.append(param_table)
story.append(Spacer(1, 5))
# ── Formulas ───────────────────────────────────────────────────────
story.append(h2("3. SAMPLE SIZE FORMULAS (per group)"))
formula_data = [
[cell("Trial Type", bold=True, color=WHITE),
cell("Formula", bold=True, color=WHITE),
cell("Components / Notes", bold=True, color=WHITE)],
[cell("Comparing TWO MEANS\n(Continuous outcome\ne.g. blood pressure)", bold=True),
cell("n = 2 × [(Zα/2 + Zβ) × σ / Δ]²\n\n= 2 × [(Zα + Zβ) × σ / (μ₁ − μ₂)]²"),
cell("Zα/2 = 1.96 (α=0.05, two-tail)\nZβ = 0.84 (power 80%) or 1.28 (power 90%)\nσ = pooled SD\nΔ = μ₁ − μ₂ (minimum detectable difference)\nLarger σ or smaller Δ → more patients needed")],
[cell("Comparing TWO PROPORTIONS\n(Binary outcome\ne.g. mortality, cure rate)", bold=True),
cell("n = (Zα/2 + Zβ)² × [P₁(1−P₁) + P₂(1−P₂)] / (P₁−P₂)²\n\nOR use:\nn ≈ (Zα/2 + Zβ)² × 2P̄(1−P̄) / (P₁−P₂)²\nwhere P̄ = (P₁+P₂)/2"),
cell("P₁ = event rate in control group\nP₂ = expected event rate in treatment group\nSmaller absolute difference → larger n\nThis is the most common formula in surgical trials")],
[cell("Cohort Study /\nClinical Trial\n(general template)", bold=True),
cell("Components needed:\n① α error (Type I)\n② β error (Type II)\n③ Incidence in unexposed (P₁)\n④ Ratio of exposed:unexposed (r)\n⑤ Minimum detectable RR"),
cell("Use free tools: OpenEpi.com,\nG*Power software,\nor PASS (NCSS)\nAlways consult a biostatistician\nbefore finalising the protocol")],
[cell("Case-Control Study\n(template)", bold=True),
cell("Components needed:\n① α error\n② β error\n③ Prevalence of exposure in controls\n④ Ratio of controls:cases\n⑤ Minimum detectable OR"),
cell("Not a therapeutic trial but listed\nfor completeness\n(Creasy & Resnik 9e, p. 311)")],
]
formula_table = Table(formula_data, colWidths=[4*cm, 7.5*cm, 6.5*cm])
formula_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("BACKGROUND", (0,1), (-1,1), LTBLUE),
("BACKGROUND", (0,2), (-1,2), LTGREY),
("BACKGROUND", (0,3), (-1,3), LTBLUE),
("BACKGROUND", (0,4), (-1,4), LTGREY),
("GRID", (0,0), (-1,-1), 0.5, colors.grey),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 5),
]))
story.append(formula_table)
story.append(Spacer(1, 5))
# ── Z-value table ──────────────────────────────────────────────────
story.append(h2("4. QUICK REFERENCE — Z-VALUES & POWER"))
zval_data = [
[cell("α (significance level)", bold=True, color=WHITE),
cell("Zα/2 (two-tailed)", bold=True, color=WHITE),
cell("Power (1−β)", bold=True, color=WHITE),
cell("Zβ", bold=True, color=WHITE),
cell("Common Combination", bold=True, color=WHITE)],
[cell("0.05 (5%)"), cell("1.96"), cell("80%"), cell("0.84"),
cell("α=0.05, Power=80% → (1.96+0.84)² = 7.84")],
[cell("0.05 (5%)"), cell("1.96"), cell("90%"), cell("1.28"),
cell("α=0.05, Power=90% → (1.96+1.28)² = 10.5")],
[cell("0.01 (1%)"), cell("2.58"), cell("80%"), cell("0.84"),
cell("α=0.01, Power=80% → (2.58+0.84)² = 11.7")],
[cell("0.01 (1%)"), cell("2.58"), cell("90%"), cell("1.28"),
cell("α=0.01, Power=90% → (2.58+1.28)² = 14.9")],
]
zval_table = Table(zval_data, colWidths=[3.5*cm, 3.5*cm, 3*cm, 2.5*cm, 5.5*cm])
zval_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("ROWBACKGROUND",(0,1), (-1,-1), [LTGREY, LTBLUE]*5),
("GRID", (0,0), (-1,-1), 0.5, colors.grey),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0), (-1,-1), 4),
]))
story.append(zval_table)
story.append(Spacer(1, 5))
# ── Memory Aid ─────────────────────────────────────────────────────
story.append(h2("5. MEMORABLE MNEMONIC — \"A BIDE\" (What you need to calculate n)"))
mnemonic_data = [
[cell("A", bold=True, size=14, color=NAVY), cell("Alpha error (α)", bold=True), cell("Type I — False Positive — Set at 0.05")],
[cell("B", bold=True, size=14, color=TEAL), cell("Beta error (β) / Power (1−β)", bold=True), cell("Type II — False Negative — Set at 0.20 → 80% power")],
[cell("I", bold=True, size=14, color=ORANGE), cell("Incidence / Event rate in control (P₁)", bold=True), cell("Baseline rate from literature or pilot study")],
[cell("D", bold=True, size=14, color=RED), cell("Delta (Δ) — Minimum detectable difference", bold=True), cell("Smallest clinically meaningful difference (P₁−P₂ or μ₁−μ₂)")],
[cell("E", bold=True, size=14, color=GREEN), cell("Effect size & SD (σ) for continuous data", bold=True), cell("Variability of measurement; determines width of the formula")],
]
mnem_table = Table(mnemonic_data, colWidths=[1*cm, 7*cm, 10*cm])
mnem_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,-1), LTBLUE),
("ROWBACKGROUND",(0,0), (-1,-1), [LTGREY, LTBLUE]*5),
("GRID", (0,0), (-1,-1), 0.5, colors.grey),
("ALIGN", (0,0), (0,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 5),
]))
story.append(mnem_table)
story.append(Spacer(1, 4))
# ── Footer note ────────────────────────────────────────────────────
story.append(small(
"Key rule: Sample size should always be calculated BEFORE the study begins and reported as part of the study design. "
"In a NEGATIVE study, always check if the study was adequately powered (look for β and power reporting). "
"Practical tools: OpenEpi.com (free, browser-based) · G*Power (free software) · PASS (commercial)."
))
story.append(HRFlowable(width="100%", thickness=0.5, color=NAVY))
story.append(small(
"References: Sabiston Textbook of Surgery 21e — Ch 6 (Outcomes Research) | "
"Schwartz's Principles of Surgery 11e — Ch 51 (Evidence-Based Surgery) | "
"Barash's Clinical Anesthesia 9e — Ch 7 (Statistics) | "
"Creasy & Resnik Maternal-Fetal Medicine 9e — p.311 | "
"Goldman-Cecil Medicine — Ch 8 (Epidemiology)"
))
# ── Build ──────────────────────────────────────────────────────────
doc.build(story)
print(f"PDF saved to {OUTPUT}")
python /tmp/workspace/evidence-pdf/generate_pdf.py 2>&1
python -c "import pdfplumber; pdf=pdfplumber.open('/tmp/workspace/evidence-pdf/Evidence_SampleSize_ClinicalTrials.pdf'); print(f'Pages: {len(pdf.pages)}'); [print(f'Page {i+1}: {len(p.extract_text())} chars') for i,p in enumerate(pdf.pages)]"
Evidence SampleSize ClinicalTrials
PDF Document · PDF
| Quality | Meaning |
|---|---|
| High | Very confident; true effect close to estimated effect; further research very unlikely to change confidence |
| Moderate | Moderately confident; true effect likely close, but possible substantial difference; further research likely to have impact |
| Low | Limited confidence; true effect may be substantially different; further research very likely needed |
| Very Low | Very little confidence; true effect likely substantially different from estimate |
| Factor | Strong Recommendation | Weak Recommendation |
|---|---|---|
| Quality | Many high-quality RCTs (e.g., inhaled steroids in asthma) | Only case series (e.g., pleurodesis in pneumothorax) |
| Balance | Clear benefit, minimal harm (e.g., aspirin in MI) | Small benefit vs significant harm (e.g., warfarin in low-risk AF) |
| Patient values | Uniformly valued outcome | High variability in patient preferences |
[Systematic Review / Meta-analysis] ← Level 1 (top)
[Single well-designed RCT] ← Level 1-2
[Cohort / Controlled study] ← Level 3
[Case-control / Case series] ← Level 4
[Expert opinion] ← Level 5 (base)
| H₀ TRUE (no real difference) | H₀ FALSE (real difference exists) | |
|---|---|---|
| Test says: DIFFERENCE | ❌ Type I error (α) - False Positive | ✅ Correct (Power = 1-β) |
| Test says: NO DIFFERENCE | ✅ Correct | ❌ Type II error (β) - False Negative |
| Letter | Parameter | Conventional Value |
|---|---|---|
| A | Alpha (α) - Type I error | 0.05 (5%) |
| B | Beta (β) / Power (1-β) | β = 0.20 → Power = 80% |
| I | Incidence in control group (P₁) | From literature or pilot study |
| D | Delta (Δ) - Minimum detectable difference | Smallest clinically meaningful difference |
| E | Effect size / SD (σ) | From pilot data; for continuous outcomes |
n per group = 2 × [ (Z_α/2 + Z_β) × σ / Δ ]²
n per group = (Z_α/2 + Z_β)² × [P₁(1-P₁) + P₂(1-P₂)] / (P₁-P₂)²
| α | Z_α/2 | Power | Z_β | Combined (Z_α + Z_β)² |
|---|---|---|---|---|
| 0.05 | 1.96 | 80% | 0.84 | 7.84 |
| 0.05 | 1.96 | 90% | 1.28 | 10.5 |
| 0.01 | 2.58 | 80% | 0.84 | 11.7 |
| 0.01 | 2.58 | 90% | 1.28 | 14.9 |
there is some error in i think. what is Z_alpha.. symbols/sign not given properly in the pdf also
~/evidence-pdf/generate_pdf_v2.py
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.lib import colors
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.lib.colors import HexColor
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
import os
OUTPUT = "/tmp/workspace/evidence-pdf/Evidence_SampleSize_v2.pdf"
doc = SimpleDocTemplate(
OUTPUT, pagesize=A4,
leftMargin=1.5*cm, rightMargin=1.5*cm,
topMargin=1.4*cm, bottomMargin=1.4*cm
)
# ── Colours ───────────────────────────────────────────────────────
NAVY = HexColor("#1a3a5c")
TEAL = HexColor("#0d7377")
LTBLUE = HexColor("#d6eaf8")
LTGREY = HexColor("#f2f3f4")
RED = HexColor("#c0392b")
GREEN = HexColor("#1e8449")
AMBER = HexColor("#d68910")
ORANGE = HexColor("#e67e22")
WHITE = colors.white
BLACK = colors.black
# ── Style helpers ─────────────────────────────────────────────────
def h1(text):
return Paragraph(text, ParagraphStyle(
"H1", fontSize=12, textColor=WHITE, backColor=NAVY,
spaceAfter=3, spaceBefore=4, leftIndent=4,
leading=15, fontName="Helvetica-Bold", alignment=TA_CENTER
))
def h2(text):
return Paragraph(text, ParagraphStyle(
"H2", fontSize=9.5, textColor=WHITE, backColor=TEAL,
spaceAfter=2, spaceBefore=4, leftIndent=3,
leading=13, fontName="Helvetica-Bold"
))
def body(text, size=8):
return Paragraph(text, ParagraphStyle(
"Body", fontSize=size, leading=11, spaceAfter=2,
fontName="Helvetica", textColor=BLACK
))
def small(text):
return Paragraph(text, ParagraphStyle(
"Small", fontSize=7, leading=9, spaceAfter=1,
fontName="Helvetica-Oblique", textColor=HexColor("#555555")
))
def cell(text, bold=False, size=7.5, color=BLACK, align=TA_LEFT):
return Paragraph(text, ParagraphStyle(
"Cell", fontSize=size, leading=10,
fontName="Helvetica-Bold" if bold else "Helvetica",
textColor=color, alignment=align
))
def hdr(text, size=7.5):
return cell(text, bold=True, size=size, color=WHITE)
# Greek letters via Unicode — ReportLab Helvetica supports these
# alpha=\u03b1 beta=\u03b2 sigma=\u03c3 mu=\u03bc
# Delta=\u0394 alpha/2 written as \u03b1/2
A = "\u03b1" # alpha
B = "\u03b2" # beta
S = "\u03c3" # sigma
MU = "\u03bc" # mu
D = "\u0394" # Delta
story = []
# ══════════════════════════════════════════════════════════════════
# PAGE 1 — QUALITY / STRENGTH OF EVIDENCE
# ══════════════════════════════════════════════════════════════════
story.append(h1("STRENGTH & QUALITY OF EVIDENCE | SAMPLE SIZE IN CLINICAL TRIALS"))
story.append(small(
"Sources: Sabiston Textbook of Surgery 21e (Ch 6) | Schwartz's Principles of Surgery 11e (Ch 51) | "
"Barash's Clinical Anesthesia 9e (Ch 7) | Creasy & Resnik 9e | Goldman-Cecil Medicine"
))
story.append(Spacer(1, 3))
# ─ Sec 1: Definitions ────────────────────────────────────────────
story.append(h2("1. KEY DEFINITIONS"))
def_data = [
[hdr("TERM"), hdr("DEFINITION")],
[cell("Quality of Evidence\n(Certainty of Evidence)", bold=True),
cell("How confident we are that the true effect lies close to the estimated effect.\n"
"Reflects: study design + risk of bias + consistency + directness + precision.")],
[cell("Strength of Recommendation", bold=True),
cell("How confident we are that following the recommendation will do more good than harm.\n"
"Depends on: quality of evidence PLUS patient values, costs, and feasibility.")],
[cell("Internal Validity", bold=True),
cell("Observed outcome is truly due to the intervention (not bias or confounding).")],
[cell("External Validity\n(Generalisability)", bold=True),
cell("Study results apply to real-world clinical practice outside the study population.")],
]
t = Table(def_data, colWidths=[4.5*cm, 13.5*cm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), NAVY),
("BACKGROUND", (0,1),(-1,1), LTBLUE),
("BACKGROUND", (0,2),(-1,2), LTGREY),
("BACKGROUND", (0,3),(-1,3), LTBLUE),
("BACKGROUND", (0,4),(-1,4), LTGREY),
("GRID", (0,0),(-1,-1), 0.4, colors.grey),
("VALIGN", (0,0),(-1,-1), "TOP"),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING", (0,0),(-1,-1), 4),
("LEFTPADDING", (0,0),(-1,-1), 5),
]))
story.append(t)
story.append(Spacer(1, 4))
# ─ Sec 2: Evidence Hierarchy ──────────────────────────────────────
story.append(h2("2. EVIDENCE HIERARCHY (Oxford CEBM / USPSTF Levels)"))
hier_data = [
[hdr("Level"), hdr("Study Type"), hdr("Key Features")],
[cell("1 (Highest)", bold=True, color=GREEN),
cell("Systematic review / Meta-analysis of RCTs"),
cell("Multiple RCTs pooled; lowest random error")],
[cell("2", bold=True, color=GREEN),
cell("Single well-designed RCT"),
cell("Randomisation + allocation concealment + blinding + ITT analysis")],
[cell("3", bold=True, color=AMBER),
cell("Cohort study / Non-randomised controlled trial"),
cell("Prospective; large n; propensity matching possible")],
[cell("4", bold=True, color=ORANGE),
cell("Case-control study / Case series"),
cell("Retrospective; susceptible to selection and recall bias")],
[cell("5 (Lowest)", bold=True, color=RED),
cell("Expert opinion / Mechanism-based reasoning"),
cell("No direct patient-level data")],
]
t = Table(hier_data, colWidths=[2.8*cm, 7.5*cm, 7.7*cm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), NAVY),
("BACKGROUND", (0,1),(-1,2), HexColor("#d5f5e3")),
("BACKGROUND", (0,3),(-1,3), HexColor("#fef9e7")),
("BACKGROUND", (0,4),(-1,4), HexColor("#fdebd0")),
("BACKGROUND", (0,5),(-1,5), HexColor("#fadbd8")),
("GRID", (0,0),(-1,-1), 0.4, colors.grey),
("VALIGN", (0,0),(-1,-1), "TOP"),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING", (0,0),(-1,-1), 4),
("LEFTPADDING", (0,0),(-1,-1), 5),
]))
story.append(t)
story.append(Spacer(1, 4))
# ─ Sec 3: GRADE ───────────────────────────────────────────────────
story.append(h2(
"3. GRADE SYSTEM \u2014 Quality of Evidence & Strength of Recommendation "
"(Sabiston 21e, Table 6.3)"
))
grade_data = [
[hdr("Quality"), hdr("Confidence in Effect Estimate"), hdr("Starting\nDesign"), hdr("Strength of\nRecommendation")],
[cell("HIGH", bold=True, color=GREEN),
cell("Very confident \u2014 true effect close to estimate;\nfurther research very unlikely to change confidence."),
cell("RCT"),
cell("STRONG", bold=True, color=GREEN)],
[cell("MODERATE", bold=True, color=HexColor("#1a9e60")),
cell("Moderately confident \u2014 true effect likely close,\nbut possibility of substantial difference."),
cell("RCT (downgraded)\nor Obs. (upgraded)"),
cell("CONDITIONAL", bold=True, color=AMBER)],
[cell("LOW", bold=True, color=AMBER),
cell("Limited confidence \u2014 true effect may be\nsubstantially different."),
cell("Observational"),
cell("CONDITIONAL", bold=True, color=AMBER)],
[cell("VERY LOW", bold=True, color=RED),
cell("Very little confidence \u2014 true effect likely\nsubstantially different from estimate."),
cell("Case series /\nExpert opinion"),
cell("CONDITIONAL", bold=True, color=RED)],
]
t = Table(grade_data, colWidths=[2.4*cm, 8.5*cm, 3.5*cm, 3.6*cm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), NAVY),
("BACKGROUND", (0,1),(-1,1), HexColor("#d5f5e3")),
("BACKGROUND", (0,2),(-1,2), HexColor("#eafaf1")),
("BACKGROUND", (0,3),(-1,3), HexColor("#fef9e7")),
("BACKGROUND", (0,4),(-1,4), HexColor("#fadbd8")),
("GRID", (0,0),(-1,-1), 0.4, colors.grey),
("VALIGN", (0,0),(-1,-1), "TOP"),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING", (0,0),(-1,-1), 4),
("LEFTPADDING", (0,0),(-1,-1), 5),
]))
story.append(t)
story.append(Spacer(1, 4))
# ─ Sec 4: Upgrade / Downgrade ─────────────────────────────────────
story.append(h2("4. FACTORS THAT UPGRADE / DOWNGRADE QUALITY (Sabiston Table 6.4 | GRADE)"))
updown_data = [
[hdr("DOWNGRADE \u2014 Lower Quality If:", size=8), hdr("UPGRADE \u2014 Higher Quality If:", size=8)],
[cell("\u2022 Risk of bias (poor allocation concealment,\n no blinding, high attrition, ITT violation)"),
cell("\u2022 Large magnitude of effect")],
[cell("\u2022 Inconsistency of results across studies"),
cell("\u2022 Dose-response gradient present")],
[cell("\u2022 Indirectness (surrogate endpoints,\n different population studied)"),
cell("\u2022 All plausible confounders would reduce\n the observed effect (conservative estimate)")],
[cell("\u2022 Imprecision (wide confidence intervals, small n)"),
cell("\u2022 Consistent findings across multiple\n independent studies")],
[cell("\u2022 Publication bias (positive results\n preferentially published)"),
cell("\u2022 Representative, generalisable\n patient population")],
]
t = Table(updown_data, colWidths=[9*cm, 9*cm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(0,0), RED),
("BACKGROUND", (1,0),(1,0), GREEN),
("BACKGROUND", (0,1),(0,-1), HexColor("#fdedec")),
("BACKGROUND", (1,1),(1,-1), HexColor("#e9f7ef")),
("GRID", (0,0),(-1,-1),0.4, colors.grey),
("VALIGN", (0,0),(-1,-1),"TOP"),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING", (0,0),(-1,-1), 4),
("LEFTPADDING", (0,0),(-1,-1), 5),
]))
story.append(t)
story.append(Spacer(1, 4))
# ─ Sec 5: Strong vs Weak ──────────────────────────────────────────
story.append(h2("5. STRONG vs WEAK RECOMMENDATION (Schwartz's 11e, Ch 51)"))
sw_data = [
[hdr("Factor"), hdr("STRONG Recommendation Example"), hdr("WEAK Recommendation Example")],
[cell("Quality of\nEvidence"),
cell("Many high-quality RCTs:\ninhaled steroids in asthma"),
cell("Only case series:\npleurodesis in pneumothorax")],
[cell("Balance of\nEffects"),
cell("Aspirin in MI: reduces mortality\nwith minimal toxicity"),
cell("Warfarin in low-risk AF: small stroke\nbenefit vs. bleeding risk + inconvenience")],
[cell("Patient\nValues"),
cell("Outcome consistently valued\nthe same by all patients"),
cell("High patient-to-patient variability\nin values and preferences")],
[cell("Resources /\nCost"),
cell("Low cost, widely available"),
cell("High cost, resource-limited settings")],
]
t = Table(sw_data, colWidths=[3.2*cm, 7.5*cm, 7.3*cm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), NAVY),
("BACKGROUND", (0,1),(-1,1), LTBLUE),
("BACKGROUND", (0,2),(-1,2), LTGREY),
("BACKGROUND", (0,3),(-1,3), LTBLUE),
("BACKGROUND", (0,4),(-1,4), LTGREY),
("GRID", (0,0),(-1,-1), 0.4, colors.grey),
("VALIGN", (0,0),(-1,-1), "TOP"),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING", (0,0),(-1,-1), 4),
("LEFTPADDING", (0,0),(-1,-1), 5),
]))
story.append(t)
# ══════════════════════════════════════════════════════════════════
# PAGE 2 — SAMPLE SIZE
# ══════════════════════════════════════════════════════════════════
story.append(PageBreak())
story.append(h1("SAMPLE SIZE CALCULATION FOR A THERAPEUTIC (CLINICAL) TRIAL"))
story.append(small(
"Sources: Barash's Clinical Anesthesia 9e (Ch 7) | "
"Creasy & Resnik's Maternal-Fetal Medicine 9e (p.311) | Goldman-Cecil Medicine (Ch 8)"
))
story.append(Spacer(1, 4))
# ─ Sec 1: Error Types ─────────────────────────────────────────────
story.append(h2("1. TYPES OF ERROR IN HYPOTHESIS TESTING \u2014 2\u00d72 Truth Table"))
err_data = [
[cell(""),
cell("H\u2080 TRUE\n(No real difference)", bold=True, color=WHITE, align=TA_CENTER),
cell("H\u2080 FALSE\n(Real difference exists)", bold=True, color=WHITE, align=TA_CENTER)],
[cell("Test says:\nDIFFERENCE\n(Reject H\u2080)", bold=True, color=WHITE),
cell("TYPE I ERROR (\u03b1)\n\nFalse Positive\n\"Declared a difference\nthat doesn't exist\"", align=TA_CENTER),
cell("CORRECT\n\nTrue Positive\nPower = 1 \u2212 \u03b2", align=TA_CENTER)],
[cell("Test says:\nNO DIFFERENCE\n(Fail to reject H\u2080)", bold=True, color=WHITE),
cell("CORRECT\n\nTrue Negative", align=TA_CENTER),
cell("TYPE II ERROR (\u03b2)\n\nFalse Negative\n\"Missed a real difference\"\n(Underpowered study)", align=TA_CENTER)],
]
t = Table(err_data, colWidths=[4*cm, 7.5*cm, 6.5*cm])
t.setStyle(TableStyle([
("BACKGROUND", (1,0),(2,0), NAVY),
("TEXTCOLOR", (1,0),(2,0), WHITE),
("BACKGROUND", (0,1),(0,2), TEAL),
("TEXTCOLOR", (0,1),(0,2), WHITE),
("BACKGROUND", (0,0),(0,0), HexColor("#0a2540")),
("BACKGROUND", (1,1),(1,1), HexColor("#fadbd8")),
("BACKGROUND", (2,1),(2,1), HexColor("#d5f5e3")),
("BACKGROUND", (1,2),(1,2), HexColor("#d5f5e3")),
("BACKGROUND", (2,2),(2,2), HexColor("#fadbd8")),
("GRID", (0,0),(-1,-1),0.5, colors.grey),
("VALIGN", (0,0),(-1,-1),"MIDDLE"),
("ALIGN", (1,0),(2,2), "CENTER"),
("TOPPADDING", (0,0),(-1,-1), 6),
("BOTTOMPADDING", (0,0),(-1,-1), 6),
("LEFTPADDING", (0,0),(-1,-1), 5),
]))
story.append(t)
story.append(Spacer(1, 4))
# ─ Sec 2: Parameters ─────────────────────────────────────────────
story.append(h2("2. KEY PARAMETERS NEEDED (Mnemonic: A-BIDE)"))
param_data = [
[hdr("Letter"), hdr("Parameter"), hdr("Symbol"), hdr("Conventional Value"), hdr("Meaning")],
[cell("A", bold=True, color=NAVY, size=9),
cell("\u03b1 Error\n(Type I)", bold=True),
cell("\u03b1"),
cell("0.05 (5%)"),
cell("Risk of a false-positive result.\nConvention: reject H\u2080 if p < 0.05")],
[cell("B", bold=True, color=TEAL, size=9),
cell("\u03b2 Error\n(Type II) / Power", bold=True),
cell("\u03b2 / (1\u2212\u03b2)"),
cell("\u03b2 = 0.10\u20130.20\nPower = 80%\u201390%"),
cell("Risk of a false-negative result.\nPower = probability of detecting a true difference")],
[cell("I", bold=True, color=ORANGE, size=9),
cell("Incidence / Event rate\nin control group", bold=True),
cell("P\u2081"),
cell("From literature\nor pilot study"),
cell("Baseline outcome rate in the unexposed / control arm")],
[cell("D", bold=True, color=RED, size=9),
cell("Delta \u2014 Minimum\nDetectable Difference", bold=True),
cell("\u0394 or MDD"),
cell("Smallest clinically\nmeaningful difference"),
cell("P\u2081 \u2212 P\u2082 (binary) or \u03bc\u2081 \u2212 \u03bc\u2082 (continuous).\nSmaller \u0394 \u2192 larger n required")],
[cell("E", bold=True, color=GREEN, size=9),
cell("Effect size /\nSD (continuous)", bold=True),
cell("\u03c3"),
cell("From pilot data /\nliterature"),
cell("Standard deviation of the outcome measure.\nMore variability \u2192 larger n required")],
]
t = Table(param_data, colWidths=[1.2*cm, 3.5*cm, 2*cm, 3.5*cm, 7.8*cm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), NAVY),
("BACKGROUND", (0,1),(-1,1), LTBLUE),
("BACKGROUND", (0,2),(-1,2), LTGREY),
("BACKGROUND", (0,3),(-1,3), LTBLUE),
("BACKGROUND", (0,4),(-1,4), LTGREY),
("BACKGROUND", (0,5),(-1,5), LTBLUE),
("GRID", (0,0),(-1,-1), 0.4, colors.grey),
("VALIGN", (0,0),(-1,-1), "TOP"),
("ALIGN", (0,0),(0,-1), "CENTER"),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING", (0,0),(-1,-1), 4),
("LEFTPADDING", (0,0),(-1,-1), 5),
]))
story.append(t)
story.append(Spacer(1, 4))
# ─ Sec 3: Formulas ────────────────────────────────────────────────
story.append(h2("3. SAMPLE SIZE FORMULAS (n = per group)"))
formula_data = [
[hdr("Trial Type"), hdr("FORMULA (n per group)"), hdr("Notes / Components")],
[cell("Comparing\nTWO MEANS\n(Continuous outcome\ne.g. BP, Hb, weight)", bold=True),
# Formula uses unicode: n = 2[(Z_a/2 + Z_b) * sigma / Delta]^2
cell(
"n = 2 \u00d7 \u2502 (Z\u03b1/2 + Z\u03b2) \u00d7 \u03c3 / \u0394 \u2502\u00b2\n\n"
"where:\n"
" \u0394 = \u03bc\u2081 \u2212 \u03bc\u2082\n"
" (\u03bc\u2081 = mean in control; \u03bc\u2082 = mean in treatment)\n"
" \u03c3 = pooled standard deviation"
),
cell(
"Z\u03b1/2 = 1.96 (\u03b1=0.05, two-tailed)\n"
"Z\u03b2 = 0.84 (Power 80%)\n"
"Z\u03b2 = 1.28 (Power 90%)\n\n"
"Larger \u03c3 or smaller \u0394\n\u2192 MORE patients needed"
)],
[cell("Comparing\nTWO PROPORTIONS\n(Binary outcome\ne.g. mortality,\ncure rate)", bold=True),
cell(
"n = (Z\u03b1/2 + Z\u03b2)\u00b2 \u00d7 [P\u2081(1\u2212P\u2081) + P\u2082(1\u2212P\u2082)]\n"
" \u00f7 (P\u2081 \u2212 P\u2082)\u00b2\n\n"
"Simplified form using pooled proportion:\n"
" P\u0304 = (P\u2081 + P\u2082) / 2\n"
"n \u2248 (Z\u03b1/2 + Z\u03b2)\u00b2 \u00d7 2P\u0304(1\u2212P\u0304) / (P\u2081\u2212P\u2082)\u00b2"
),
cell(
"P\u2081 = event rate in control group\n"
"P\u2082 = event rate in treatment group\n\n"
"MOST COMMON formula in\nsurgical / therapeutic trials\n\n"
"Smaller (P\u2081 \u2212 P\u2082)\n\u2192 MUCH larger n needed"
)],
[cell("Worked\nExample\n(Binary)", bold=True, color=NAVY),
cell(
"Drug reduces mortality from 20% \u2192 12%\n"
"(\u03b1=0.05 two-tail, Power=80%)\n\n"
"P\u2081=0.20, P\u2082=0.12, Z\u03b1/2=1.96, Z\u03b2=0.84\n"
"Numerator: (1.96+0.84)\u00b2 \u00d7 [0.20\u00d70.80 + 0.12\u00d70.88]\n"
" = 7.84 \u00d7 [0.160 + 0.106] = 7.84 \u00d7 0.266 = 2.086\n"
"Denominator: (0.20\u22120.12)\u00b2 = 0.0064\n"
"n = 2.086 / 0.0064 \u2248 326 per group"
),
cell(
"Total trial size =\n326 \u00d7 2 = 652 patients\n\n"
"Remember:\nalways ADD 10\u201320%\nfor expected dropout/\nloss to follow-up"
)],
]
t = Table(formula_data, colWidths=[3.2*cm, 9.3*cm, 5.5*cm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), NAVY),
("BACKGROUND", (0,1),(-1,1), LTBLUE),
("BACKGROUND", (0,2),(-1,2), LTGREY),
("BACKGROUND", (0,3),(-1,3), HexColor("#eaf4fb")),
("GRID", (0,0),(-1,-1), 0.4, colors.grey),
("VALIGN", (0,0),(-1,-1), "TOP"),
("TOPPADDING", (0,0),(-1,-1), 5),
("BOTTOMPADDING", (0,0),(-1,-1), 5),
("LEFTPADDING", (0,0),(-1,-1), 5),
]))
story.append(t)
story.append(Spacer(1, 4))
# ─ Sec 4: Z-value reference ───────────────────────────────────────
story.append(h2(
"4. Z-VALUE QUICK REFERENCE \u2014 What Z\u03b1/2 and Z\u03b2 Mean"
))
# Explain what Z_alpha/2 IS first
story.append(body(
"<b>Z\u03b1/2</b> is the Z-score from the standard normal distribution that cuts off \u03b1/2 in each tail of the curve. "
"For a two-tailed test at \u03b1=0.05: we split 0.05/2 = 0.025 per tail \u2192 Z = 1.96. "
"<b>Z\u03b2</b> is the Z-score corresponding to the chosen \u03b2 error (the power side of the curve). "
"For 80% power (\u03b2=0.20): Z\u03b2 = 0.84. For 90% power (\u03b2=0.10): Z\u03b2 = 1.28."
))
story.append(Spacer(1, 3))
zval_data = [
[hdr("\u03b1 Level"), hdr("Z\u03b1/2\n(two-tailed)"), hdr("Power\n(1\u2212\u03b2)"), hdr("Z\u03b2"), hdr("(Z\u03b1/2 + Z\u03b2)\u00b2\nUse directly in formula")],
[cell("\u03b1 = 0.05 (5%)"), cell("1.96"), cell("80% (\u03b2=0.20)"), cell("0.84"), cell("(1.96 + 0.84)\u00b2 = <b>7.84</b>")],
[cell("\u03b1 = 0.05 (5%)"), cell("1.96"), cell("90% (\u03b2=0.10)"), cell("1.28"), cell("(1.96 + 1.28)\u00b2 = <b>10.50</b>")],
[cell("\u03b1 = 0.01 (1%)"), cell("2.58"), cell("80% (\u03b2=0.20)"), cell("0.84"), cell("(2.58 + 0.84)\u00b2 = <b>11.70</b>")],
[cell("\u03b1 = 0.01 (1%)"), cell("2.58"), cell("90% (\u03b2=0.10)"), cell("1.28"), cell("(2.58 + 1.28)\u00b2 = <b>14.93</b>")],
]
t = Table(zval_data, colWidths=[3.5*cm, 3.2*cm, 3.2*cm, 2.2*cm, 5.9*cm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), NAVY),
("BACKGROUND", (0,1),(-1,1), LTGREY),
("BACKGROUND", (0,2),(-1,2), HexColor("#d5f5e3")),
("BACKGROUND", (0,3),(-1,3), LTGREY),
("BACKGROUND", (0,4),(-1,4), HexColor("#d5f5e3")),
("GRID", (0,0),(-1,-1), 0.4, colors.grey),
("ALIGN", (0,0),(-1,-1), "CENTER"),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING", (0,0),(-1,-1), 4),
]))
story.append(t)
story.append(Spacer(1, 4))
# ─ Sec 5: Rules + Footer ──────────────────────────────────────────
story.append(h2("5. PRACTICAL RULES (Easy to Remember)"))
rules_data = [
[hdr("#"), hdr("RULE")],
[cell("1"), cell("Always calculate sample size BEFORE starting the study; report it in the Methods section.")],
[cell("2"), cell("\u03b1 = 0.05 (by convention): willing to be wrong about a positive result 1 in 20 times.")],
[cell("3"), cell("\u03b2 = 0.20 (80% power, most common): willing to miss a true difference 1 in 5 times.")],
[cell("4"), cell("Smaller effect to detect \u2192 larger n needed (the formula\u2019s denominator shrinks).")],
[cell("5"), cell("More variability in outcome (\u03c3 \u2191) \u2192 larger n needed (noisier data, harder to detect signal).")],
[cell("6"), cell("Add 10\u201320% to the calculated n to account for dropout and loss to follow-up.")],
[cell("7"), cell("When reading a NEGATIVE trial: always check if it was adequately powered.\n"
"An underpowered study has high \u03b2 error \u2014 it may have missed a real benefit.")],
[cell("8"), cell("Free tools: OpenEpi.com (browser-based) | G*Power software (free download).")],
]
t = Table(rules_data, colWidths=[0.8*cm, 17.2*cm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), NAVY),
("ROWBACKGROUND", (0,1),(-1,-1), [LTGREY, LTBLUE]*5),
("GRID", (0,0),(-1,-1), 0.4, colors.grey),
("VALIGN", (0,0),(-1,-1), "TOP"),
("ALIGN", (0,0),(0,-1), "CENTER"),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING", (0,0),(-1,-1), 4),
("LEFTPADDING", (0,0),(-1,-1), 5),
]))
story.append(t)
story.append(Spacer(1, 3))
story.append(HRFlowable(width="100%", thickness=0.5, color=NAVY))
story.append(small(
"References: Sabiston Textbook of Surgery 21e \u2014 Ch 6 (Outcomes Research) | "
"Schwartz\u2019s Principles of Surgery 11e \u2014 Ch 51 (Evidence-Based Surgery) | "
"Barash\u2019s Clinical Anesthesia 9e \u2014 Ch 7 (Statistics) | "
"Creasy & Resnik\u2019s Maternal-Fetal Medicine 9e \u2014 p.311 | "
"Goldman-Cecil Medicine \u2014 Ch 8 (Epidemiology & Biostatistics)"
))
doc.build(story)
print(f"Done: {OUTPUT}")
python /tmp/workspace/evidence-pdf/generate_pdf_v2.py 2>&1
python -c " import pdfplumber with pdfplumber.open('/tmp/workspace/evidence-pdf/Evidence_SampleSize_v2.pdf') as pdf: print(f'Pages: {len(pdf.pages)}') for i, p in enumerate(pdf.pages): txt = p.extract_text() or '' print(f'Page {i+1}: {len(txt)} chars | preview: {txt[:120].replace(chr(10),\" \")}') "
Evidence SampleSize v2
PDF Document · PDF
Z_α/2 is the Z-score from the standard normal distribution that cuts off α/2 in each tail. For a two-tailed test at α=0.05: split 0.05/2 = 0.025 per tail → Z = 1.96. For α=0.01: 0.005 per tail → Z = 2.58.
n = 2 × |(Z_α/2 + Z_β) × σ / Δ|²n = (Z_α/2 + Z_β)² × [P₁(1−P₁) + P₂(1−P₂)] / (P₁−P₂)²