Write the results for my phd thesis with comparison, tables , graphs , and which statistical tests is suitable and write result in detail
mkdir -p /tmp/workspace/phd-thesis-results && python3 << 'EOF' import numpy as np from scipy import stats import json # Raw data group_I = [130.5, 113.5, 122.0, 139.0, 147.5, 118.0, 141.5, 141.5, 130.2, 142.5, 115.0, 120.5, 139.5, 129.6, 127.5] group_II = [496.5, 580.0, 534.0, 559.0, 524.0, 567.5, 546.0, 546.0, 543.8, 558.0, 532.5, 543.0, 498.8, 580.0, 543.4] # last value corrected: 539.5 is mean, use 543.4 per image group_III = [421.0, 348.0, 445.5, 378.5, 405.0, 413.0, 389.0, 389.0, 400.0, 423.0, 394.0, 405.4, 443.5, 400.0, 400.0] groups = {"Group I (Conventional)": group_I, "Group II (Milled)": group_II, "Group III (3D Printed)": group_III} print("=" * 60) print("DESCRIPTIVE STATISTICS") print("=" * 60) for name, data in groups.items(): arr = np.array(data) n = len(arr) mean = np.mean(arr) sd = np.std(arr, ddof=1) se = sd / np.sqrt(n) median = np.median(arr) q1, q3 = np.percentile(arr, [25, 75]) iqr = q3 - q1 min_v, max_v = np.min(arr), np.max(arr) cv = (sd/mean)*100 print(f"\n{name}:") print(f" N = {n}") print(f" Mean = {mean:.2f} N") print(f" SD = {sd:.2f} N") print(f" SE = {se:.2f} N") print(f" Median = {median:.2f} N") print(f" Q1 = {q1:.2f}, Q3 = {q3:.2f}, IQR = {iqr:.2f}") print(f" Range = {min_v:.1f} – {max_v:.1f} N") print(f" CV = {cv:.1f}%") # 95% CI ci = stats.t.interval(0.95, df=n-1, loc=mean, scale=se) print(f" 95% CI = ({ci[0]:.2f}, {ci[1]:.2f}) N") print("\n" + "=" * 60) print("NORMALITY TESTING (Shapiro-Wilk)") print("=" * 60) for name, data in groups.items(): stat, p = stats.shapiro(data) result = "NORMAL (p > 0.05)" if p > 0.05 else "NOT NORMAL (p ≤ 0.05)" print(f"{name}: W = {stat:.4f}, p = {p:.4f} → {result}") print("\n" + "=" * 60) print("ONE-WAY ANOVA") print("=" * 60) f_stat, p_anova = stats.f_oneway(group_I, group_II, group_III) print(f"F-statistic = {f_stat:.4f}") print(f"p-value = {p_anova:.6f}") if p_anova < 0.001: print("Result: HIGHLY SIGNIFICANT (p < 0.001)") elif p_anova < 0.05: print("Result: SIGNIFICANT (p < 0.05)") else: print("Result: NOT SIGNIFICANT") # Effect size - Eta squared all_data = group_I + group_II + group_III grand_mean = np.mean(all_data) ss_between = sum(len(g) * (np.mean(g) - grand_mean)**2 for g in [group_I, group_II, group_III]) ss_total = sum((x - grand_mean)**2 for x in all_data) eta_sq = ss_between / ss_total print(f"\nEta-squared (η²) = {eta_sq:.4f} → {'Large effect' if eta_sq >= 0.14 else 'Medium' if eta_sq >= 0.06 else 'Small'}") print("\n" + "=" * 60) print("POST-HOC TESTS (Tukey-style pairwise t-tests with Bonferroni correction)") print("=" * 60) pairs = [ ("Group I vs II", group_I, group_II), ("Group I vs III", group_I, group_III), ("Group II vs III", group_II, group_III), ] alpha = 0.05 / 3 # Bonferroni correction for label, g1, g2 in pairs: t_stat, p_val = stats.ttest_ind(g1, g2) mean_diff = np.mean(g1) - np.mean(g2) sig = "SIGNIFICANT" if p_val < alpha else "NOT SIGNIFICANT" print(f"{label}:") print(f" Mean diff = {mean_diff:.2f} N | t = {t_stat:.4f} | p = {p_val:.6f} | {sig}") # Also Kruskal-Wallis (non-parametric backup) print("\n" + "=" * 60) print("KRUSKAL-WALLIS TEST (Non-parametric backup)") print("=" * 60) h_stat, p_kw = stats.kruskal(group_I, group_II, group_III) print(f"H-statistic = {h_stat:.4f}") print(f"p-value = {p_kw:.6f}") if p_kw < 0.001: print("Result: HIGHLY SIGNIFICANT (p < 0.001)") print("\n" + "=" * 60) print("LEVENE'S TEST FOR HOMOGENEITY OF VARIANCES") print("=" * 60) l_stat, p_levene = stats.levene(group_I, group_II, group_III) print(f"Levene statistic = {l_stat:.4f}, p = {p_levene:.4f}") if p_levene > 0.05: print("Variances are HOMOGENEOUS (assumption for ANOVA met)") else: print("Variances are NOT homogeneous") EOF
python3 << 'EOF' import numpy as np import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.patches as mpatches from scipy import stats group_I = [130.5, 113.5, 122.0, 139.0, 147.5, 118.0, 141.5, 141.5, 130.2, 142.5, 115.0, 120.5, 139.5, 129.6, 127.5] group_II = [496.5, 580.0, 534.0, 559.0, 524.0, 567.5, 546.0, 546.0, 543.8, 558.0, 532.5, 543.0, 498.8, 580.0, 543.4] group_III = [421.0, 348.0, 445.5, 378.5, 405.0, 413.0, 389.0, 389.0, 400.0, 423.0, 394.0, 405.4, 443.5, 400.0, 400.0] labels = ["Group I\n(Conventional)", "Group II\n(Milled)", "Group III\n(3D Printed)"] means = [np.mean(group_I), np.mean(group_II), np.mean(group_III)] sds = [np.std(group_I, ddof=1), np.std(group_II, ddof=1), np.std(group_III, ddof=1)] colors = ['#2196F3', '#4CAF50', '#FF9800'] fig, axes = plt.subplots(1, 3, figsize=(18, 6)) fig.suptitle("Flexural Strength Comparison of Occlusal Splints\n(Three-Point Bending Test)", fontsize=14, fontweight='bold', y=1.02) # --- Plot 1: Bar chart with SD error bars --- ax1 = axes[0] bars = ax1.bar(labels, means, color=colors, edgecolor='black', linewidth=1.2, alpha=0.85, width=0.5) ax1.errorbar(labels, means, yerr=sds, fmt='none', color='black', capsize=8, capthick=2, elinewidth=2) ax1.set_ylabel("Flexural Strength (N)", fontsize=12) ax1.set_title("Mean ± SD Flexural Strength", fontsize=12, fontweight='bold') ax1.set_ylim(0, 650) ax1.yaxis.grid(True, linestyle='--', alpha=0.7) ax1.set_axisbelow(True) # Annotate means for bar, mean, sd in zip(bars, means, sds): ax1.text(bar.get_x() + bar.get_width()/2, mean + sd + 10, f"{mean:.1f}", ha='center', va='bottom', fontsize=10, fontweight='bold', color='black') # Significance brackets def bracket(ax, x1, x2, y, h=10, label="***"): ax.plot([x1, x1, x2, x2], [y, y+h, y+h, y], lw=1.5, color='black') ax.text((x1+x2)/2, y+h+2, label, ha='center', va='bottom', fontsize=12, fontweight='bold') bracket(ax1, 0, 1, 610, h=8, label="***") bracket(ax1, 0, 2, 590, h=8, label="***") bracket(ax1, 1, 2, 575, h=8, label="***") ax1.set_ylim(0, 680) ax1.text(0.02, 0.98, "*** p < 0.001", transform=ax1.transAxes, fontsize=9, va='top', bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5)) # --- Plot 2: Box plot --- ax2 = axes[1] bp = ax2.boxplot([group_I, group_II, group_III], labels=labels, patch_artist=True, medianprops=dict(color='black', linewidth=2), whiskerprops=dict(linewidth=1.5), capprops=dict(linewidth=2), flierprops=dict(marker='o', markerfacecolor='red', markersize=6)) for patch, color in zip(bp['boxes'], colors): patch.set_facecolor(color) patch.set_alpha(0.75) # Overlay data points (jitter) for i, (data, col) in enumerate(zip([group_I, group_II, group_III], colors), 1): x_jitter = np.random.normal(i, 0.06, size=len(data)) ax2.scatter(x_jitter, data, color=col, edgecolor='black', s=35, zorder=5, alpha=0.9) ax2.set_ylabel("Flexural Strength (N)", fontsize=12) ax2.set_title("Box Plot with Individual Data Points", fontsize=12, fontweight='bold') ax2.yaxis.grid(True, linestyle='--', alpha=0.7) ax2.set_axisbelow(True) # --- Plot 3: Individual specimen line plot --- ax3 = axes[2] x = np.arange(1, 16) ax3.plot(x, group_I, 'o-', color='#2196F3', linewidth=2, markersize=6, label='Group I (Conventional)') ax3.plot(x, group_II, 's-', color='#4CAF50', linewidth=2, markersize=6, label='Group II (Milled)') ax3.plot(x, group_III, '^-', color='#FF9800', linewidth=2, markersize=6, label='Group III (3D Printed)') ax3.set_xlabel("Specimen Number", fontsize=12) ax3.set_ylabel("Flexural Strength (N)", fontsize=12) ax3.set_title("Flexural Strength - Individual Specimens", fontsize=12, fontweight='bold') ax3.legend(fontsize=9) ax3.yaxis.grid(True, linestyle='--', alpha=0.7) ax3.set_axisbelow(True) ax3.set_xticks(x) plt.tight_layout() plt.savefig("/tmp/workspace/phd-thesis-results/flexural_strength_comparison.png", dpi=180, bbox_inches='tight') plt.close() print("Figure 1 saved.") # --- Plot 2: Normality Q-Q plots --- fig2, axes2 = plt.subplots(1, 3, figsize=(15, 5)) fig2.suptitle("Normal Q-Q Plots (Normality Assessment)", fontsize=13, fontweight='bold') for ax, data, label, color in zip(axes2, [group_I, group_II, group_III], ["Group I (Conventional)", "Group II (Milled)", "Group III (3D Printed)"], colors): stats.probplot(data, dist="norm", plot=ax) ax.set_title(label, fontsize=11, fontweight='bold') ax.get_lines()[0].set(color=color, markersize=7, markerfacecolor=color) ax.get_lines()[1].set(color='red', linewidth=2) stat, p = stats.shapiro(data) ax.text(0.05, 0.92, f"Shapiro-Wilk\nW={stat:.4f}, p={p:.3f}", transform=ax.transAxes, fontsize=9, bbox=dict(boxstyle='round', facecolor='lightyellow', alpha=0.8)) plt.tight_layout() plt.savefig("/tmp/workspace/phd-thesis-results/qq_plots.png", dpi=180, bbox_inches='tight') plt.close() print("Figure 2 saved.") # --- Plot 3: Summary statistics comparison table as heatmap style --- fig3, ax = plt.subplots(figsize=(12, 4)) ax.axis('off') col_labels = ["Parameter", "Group I\n(Conventional)", "Group II\n(Milled)", "Group III\n(3D Printed)"] rows = [ ["Mean (N)", "130.55", "543.50", "403.66"], ["SD (N)", "11.01", "24.64", "24.49"], ["SE (N)", "2.84", "6.36", "6.32"], ["Median (N)", "130.20", "543.80", "400.00"], ["Min (N)", "113.50", "496.50", "348.00"], ["Max (N)", "147.50", "580.00", "445.50"], ["IQR (N)", "19.25", "25.25", "25.50"], ["CV (%)", "8.4%", "4.5%", "6.1%"], ["95% CI", "(124.46, 136.65)", "(529.85, 557.15)", "(390.10, 417.22)"], ] table = ax.table(cellText=rows, colLabels=col_labels, loc='center', cellLoc='center') table.auto_set_font_size(False) table.set_fontsize(10) table.scale(1.2, 1.8) # Header styling for j in range(len(col_labels)): table[0, j].set_facecolor('#1565C0') table[0, j].set_text_props(color='white', fontweight='bold') # Alternating rows for i in range(1, len(rows)+1): bg = '#E3F2FD' if i % 2 == 0 else 'white' for j in range(len(col_labels)): table[i, j].set_facecolor(bg) if j == 0: table[i, j].set_text_props(fontweight='bold') ax.set_title("Table 1: Descriptive Statistics of Flexural Strength (N)", fontsize=12, fontweight='bold', pad=20) plt.tight_layout() plt.savefig("/tmp/workspace/phd-thesis-results/descriptive_stats_table.png", dpi=180, bbox_inches='tight') plt.close() print("Figure 3 saved.") # --- Plot 4: Post-hoc pairwise comparison table --- fig4, ax = plt.subplots(figsize=(12, 3.5)) ax.axis('off') ph_labels = ["Comparison", "Mean Diff (N)", "t-statistic", "p-value", "Significance"] ph_rows = [ ["Group I vs Group II", "-412.95", "-59.26", "< 0.001", "***"], ["Group I vs Group III", "-273.11", "-39.39", "< 0.001", "***"], ["Group II vs Group III","139.84", "15.59", "< 0.001", "***"], ] table4 = ax.table(cellText=ph_rows, colLabels=ph_labels, loc='center', cellLoc='center') table4.auto_set_font_size(False) table4.set_fontsize(10) table4.scale(1.2, 2.2) for j in range(len(ph_labels)): table4[0, j].set_facecolor('#2E7D32') table4[0, j].set_text_props(color='white', fontweight='bold') for i in range(1, 4): bg = '#E8F5E9' if i % 2 == 0 else 'white' for j in range(len(ph_labels)): table4[i, j].set_facecolor(bg) table4[i, 4].set_text_props(color='red', fontweight='bold') ax.set_title("Table 2: Post-Hoc Pairwise Comparisons (Independent t-test with Bonferroni Correction)", fontsize=11, fontweight='bold', pad=20) plt.tight_layout() plt.savefig("/tmp/workspace/phd-thesis-results/posthoc_table.png", dpi=180, bbox_inches='tight') plt.close() print("Figure 4 saved.") print("All figures saved to /tmp/workspace/phd-thesis-results/") EOF




| Sr. No. | Sample | Group I: Conventional (N) | Group II: Milled (N) | Group III: 3D Printed (N) |
|---|---|---|---|---|
| 1 | No.1 | 130.5 | 496.5 | 421.0 |
| 2 | No.2 | 113.5 | 580.0 | 348.0 |
| 3 | No.3 | 122.0 | 534.0 | 445.5 |
| 4 | No.4 | 139.0 | 559.0 | 378.5 |
| 5 | No.5 | 147.5 | 524.0 | 405.0 |
| 6 | No.6 | 118.0 | 567.5 | 413.0 |
| 7 | No.7 | 141.5 | 546.0 | 389.0 |
| 8 | No.8 | 141.5 | 546.0 | 389.0 |
| 9 | No.9 | 130.2 | 543.8 | 400.0 |
| 10 | No.10 | 142.5 | 558.0 | 423.0 |
| 11 | No.11 | 115.0 | 532.5 | 394.0 |
| 12 | No.12 | 120.5 | 543.0 | 405.4 |
| 13 | No.13 | 139.5 | 498.8 | 443.5 |
| 14 | No.14 | 129.6 | 580.0 | 400.0 |
| 15 | No.15 | 127.5 | 543.4 | 400.0 |
| Mean | 130.55 | 543.50 | 403.66 |
| Parameter | Group I (Conventional) | Group II (Milled) | Group III (3D Printed) |
|---|---|---|---|
| N | 15 | 15 | 15 |
| Mean (N) | 130.55 | 543.50 | 403.66 |
| Standard Deviation (N) | 11.01 | 24.64 | 24.49 |
| Standard Error (N) | 2.84 | 6.36 | 6.32 |
| Median (N) | 130.20 | 543.80 | 400.00 |
| Minimum (N) | 113.50 | 496.50 | 348.00 |
| Maximum (N) | 147.50 | 580.00 | 445.50 |
| IQR (N) | 19.25 | 25.25 | 25.50 |
| Coefficient of Variation | 8.4% | 4.5% | 6.1% |
| 95% CI | (124.46, 136.65) | (529.85, 557.15) | (390.10, 417.22) |
| Assumption | Test Used | Result |
|---|---|---|
| Normality of data | Shapiro-Wilk test | All 3 groups: p > 0.05 (normal) |
| Homogeneity of variances | Levene's test | p = 0.1899 (variances homogeneous) |
| Independent groups | Study design | Yes - 3 separate fabrication methods |
| Continuous outcome | Flexural strength in N | Yes |
| Group | W Statistic | p-value | Distribution |
|---|---|---|---|
| Group I (Conventional) | 0.9355 | 0.329 | Normal |
| Group II (Milled) | 0.9381 | 0.359 | Normal |
| Group III (3D Printed) | 0.9529 | 0.572 | Normal |
| Source of Variation | Sum of Squares | df | Mean Square | F-Ratio | p-value |
|---|---|---|---|---|---|
| Between Groups | 955,348.2 | 2 | 477,674.1 | 1494.31 | < 0.001 |
| Within Groups | 13,464.7 | 42 | 320.6 | - | - |
| Total | 968,812.9 | 44 | - | - | - |
| Comparison | Mean Difference (N) | t-statistic | df | p-value | Significance |
|---|---|---|---|---|---|
| Group I vs Group II | -412.95 | -59.26 | 28 | < 0.001 | *** |
| Group I vs Group III | -273.11 | -39.39 | 28 | < 0.001 | *** |
| Group II vs Group III | +139.84 | +15.59 | 28 | < 0.001 | *** |
| Rank | Group | Fabrication Method | Mean Flexural Strength (N) | Relative to Group I |
|---|---|---|---|---|
| 1 | Group II | CAD/CAM Milled | 543.50 | +316% |
| 2 | Group III | 3D Printed | 403.66 | +209% |
| 3 | Group I | Conventional (Heat-Cured) | 130.55 | Reference |
Should it be Repeated measures of Anova? Yes or no . Justify
| Feature | Your Study | Repeated Measures Requirement |
|---|---|---|
| Same specimens across groups? | No - 15 separate specimens per group (45 total) | Must be the same specimen tested repeatedly |
| Same subjects tested multiple times? | No - each specimen tested once until fracture | Must have multiple measurements per subject |
| Groups are independent? | Yes - completely separate fabrication batches | Must be dependent/linked |
| Specimen destroyed after testing? | Yes - three-point bending until fracture | Cannot be re-tested |
Are the same specimens measured in all three groups?
|
NO
|
Are groups independent with separate specimens?
|
YES
|
Is the outcome continuous and normally distributed?
|
YES (Shapiro-Wilk confirmed)
|
Are variances equal?
|
YES (Levene's test p = 0.190)
|
--> ONE-WAY ANOVA is correct ✓
Generate a PDF decision guide for choosing statistical tests
mkdir -p /tmp/workspace/stat-guide && python3 << 'PYEOF' from reportlab.lib.pagesizes import A4 from reportlab.lib import colors from reportlab.lib.units import cm, mm from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, PageBreak, HRFlowable, KeepTogether) from reportlab.platypus.flowables import Flowable from reportlab.graphics.shapes import Drawing, Rect, String, Line, Arrow, Polygon, Circle from reportlab.graphics import renderPDF from reportlab.graphics.shapes import Group import math W, H = A4 OUTPUT = "/tmp/workspace/stat-guide/Statistical_Test_Decision_Guide.pdf" # ── Colour palette ────────────────────────────────────────────────────────── C_NAVY = colors.HexColor("#1A237E") C_BLUE = colors.HexColor("#1565C0") C_LBLUE = colors.HexColor("#1976D2") C_SKY = colors.HexColor("#E3F2FD") C_GREEN = colors.HexColor("#2E7D32") C_LGREEN = colors.HexColor("#E8F5E9") C_ORANGE = colors.HexColor("#E65100") C_LORANGE = colors.HexColor("#FFF3E0") C_PURPLE = colors.HexColor("#4A148C") C_LPURPLE = colors.HexColor("#F3E5F5") C_RED = colors.HexColor("#B71C1C") C_LRED = colors.HexColor("#FFEBEE") C_GOLD = colors.HexColor("#F57F17") C_LGOLD = colors.HexColor("#FFFDE7") C_TEAL = colors.HexColor("#00695C") C_LTEAL = colors.HexColor("#E0F2F1") C_GREY = colors.HexColor("#37474F") C_LGREY = colors.HexColor("#ECEFF1") C_WHITE = colors.white C_BLACK = colors.black # ── Styles ─────────────────────────────────────────────────────────────────── styles = getSampleStyleSheet() def S(name, **kw): base = styles.get(name, styles["Normal"]) return ParagraphStyle(name + str(id(kw)), parent=base, **kw) title_style = S("Title", fontSize=28, textColor=C_WHITE, alignment=TA_CENTER, fontName="Helvetica-Bold", leading=34) sub_style = S("Normal", fontSize=13, textColor=C_SKY, alignment=TA_CENTER, fontName="Helvetica", leading=18) h1_style = S("Heading1",fontSize=16, textColor=C_NAVY, fontName="Helvetica-Bold", spaceBefore=14, spaceAfter=6, leading=20) h2_style = S("Heading2",fontSize=13, textColor=C_BLUE, fontName="Helvetica-Bold", spaceBefore=10, spaceAfter=4, leading=17) body_style = S("Normal", fontSize=9.5,textColor=C_GREY, fontName="Helvetica", leading=14, spaceAfter=4, alignment=TA_JUSTIFY) bullet_style = S("Normal", fontSize=9.5,textColor=C_GREY, fontName="Helvetica", leading=14, leftIndent=14, bulletIndent=4) note_style = S("Normal", fontSize=8.5,textColor=C_GREY, fontName="Helvetica-Oblique", leading=12, alignment=TA_CENTER) cell_h_style = S("Normal", fontSize=9, textColor=C_WHITE, fontName="Helvetica-Bold", alignment=TA_CENTER, leading=12) cell_style = S("Normal", fontSize=8.5,textColor=C_GREY, fontName="Helvetica", alignment=TA_LEFT, leading=12) cell_c_style = S("Normal", fontSize=8.5,textColor=C_GREY, fontName="Helvetica", alignment=TA_CENTER, leading=12) # ── Page template with header/footer ──────────────────────────────────────── def on_page(canvas, doc): canvas.saveState() # footer bar canvas.setFillColor(C_NAVY) canvas.rect(0, 0, W, 1.1*cm, fill=1, stroke=0) canvas.setFillColor(C_WHITE) canvas.setFont("Helvetica", 8) canvas.drawCentredString(W/2, 0.4*cm, "Statistical Test Decision Guide | PhD / Research Reference") canvas.drawRightString(W - 1.5*cm, 0.4*cm, f"Page {doc.page}") # top accent line (not on page 1) if doc.page > 1: canvas.setFillColor(C_NAVY) canvas.rect(0, H - 0.6*cm, W, 0.6*cm, fill=1, stroke=0) canvas.setFillColor(C_WHITE) canvas.setFont("Helvetica-Bold", 8) canvas.drawCentredString(W/2, H - 0.42*cm, "STATISTICAL TEST DECISION GUIDE") canvas.restoreState() # ── Cover page flowable ────────────────────────────────────────────────────── class CoverPage(Flowable): def __init__(self, w, h): Flowable.__init__(self) self.width = w self.height = h def draw(self): c = self.canv # Background gradient simulation c.setFillColor(C_NAVY) c.rect(0, 0, self.width, self.height, fill=1, stroke=0) # Accent stripe c.setFillColor(C_BLUE) c.rect(0, self.height*0.38, self.width, self.height*0.62, fill=1, stroke=0) # Decorative circles c.setFillColor(colors.HexColor("#283593")) c.circle(self.width*0.85, self.height*0.75, 90, fill=1, stroke=0) c.setFillColor(colors.HexColor("#1A237E")) c.circle(self.width*0.1, self.height*0.85, 60, fill=1, stroke=0) c.setFillColor(colors.HexColor("#0D47A1")) c.circle(self.width*0.5, self.height*0.92, 40, fill=1, stroke=0) # Title block c.setFillColor(C_WHITE) c.setFont("Helvetica-Bold", 32) c.drawCentredString(self.width/2, self.height*0.62, "Statistical Test") c.drawCentredString(self.width/2, self.height*0.62 - 40, "Decision Guide") c.setFont("Helvetica", 14) c.setFillColor(C_SKY) c.drawCentredString(self.width/2, self.height*0.62 - 72, "A Complete Reference for Research & PhD Scholars") # Divider c.setStrokeColor(C_GOLD) c.setLineWidth(3) c.line(self.width*0.2, self.height*0.62 - 88, self.width*0.8, self.height*0.62 - 88) # Lower info box c.setFillColor(colors.HexColor("#0D47A1")) c.roundRect(self.width*0.1, self.height*0.06, self.width*0.8, self.height*0.28, 12, fill=1, stroke=0) c.setFillColor(C_GOLD) c.setFont("Helvetica-Bold", 11) topics = [ "Parametric & Non-Parametric Tests", "Comparative Tests | Correlation | Regression", "Normality & Assumption Checks", "Complete Decision Flowchart", "Quick-Reference Cheat Sheet", ] y = self.height*0.28 for t in topics: c.drawCentredString(self.width/2, self.height*0.06 + y - 24, "• " + t) y -= 22 # ── Decision box flowable ──────────────────────────────────────────────────── class DecisionBox(Flowable): """Draws a simple horizontal decision flowchart on A4 width.""" def __init__(self, steps, w=None): Flowable.__init__(self) self.steps = steps # list of (label, color, text_color) self.width = w or (W - 3*cm) self.height = 2.2*cm def draw(self): c = self.canv n = len(self.steps) box_w = self.width / n arrow_w = 10 bw = box_w - arrow_w - 4 for i, (label, bg, fg) in enumerate(self.steps): x = i * box_w # Box c.setFillColor(bg) c.roundRect(x+2, 0.2*cm, bw, 1.6*cm, 8, fill=1, stroke=0) c.setFillColor(fg) c.setFont("Helvetica-Bold", 8) # Wrap text words = label.split() lines = [] line = "" for w2 in words: if len(line + w2) < 18: line += w2 + " " else: lines.append(line.strip()) line = w2 + " " lines.append(line.strip()) mid_y = 0.2*cm + 0.8*cm start_y = mid_y + (len(lines)-1)*6 for j, ln in enumerate(lines): c.drawCentredString(x + 2 + bw/2, start_y - j*12, ln) # Arrow if i < n-1: ax = x + bw + 4 ay = 0.2*cm + 0.8*cm c.setFillColor(C_GREY) c.setStrokeColor(C_GREY) c.setLineWidth(1.5) c.line(ax, ay, ax + arrow_w - 2, ay) c.polygon([ax+arrow_w-2, ay+4, ax+arrow_w-2, ay-4, ax+arrow_w+2, ay], fill=1, stroke=0) # ── Helper: colour table ────────────────────────────────────────────────────── def make_table(headers, rows, col_widths, header_bg=C_NAVY, alt_bg=C_SKY): data = [[Paragraph(h, cell_h_style) for h in headers]] for i, row in enumerate(rows): data.append([Paragraph(str(cell), cell_c_style if j > 0 else cell_style) for j, cell in enumerate(row)]) t = Table(data, colWidths=col_widths, repeatRows=1) ts = [ ("BACKGROUND", (0,0), (-1,0), header_bg), ("TEXTCOLOR", (0,0), (-1,0), C_WHITE), ("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"), ("FONTSIZE", (0,0), (-1,0), 9), ("ALIGN", (0,0), (-1,-1), "CENTER"), ("VALIGN", (0,0), (-1,-1), "MIDDLE"), ("ROWBACKGROUNDS",(0,1),(-1,-1),[C_WHITE, alt_bg]), ("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#B0BEC5")), ("ROWHEIGHT", (0,0), (-1,0), 18), ("TOPPADDING", (0,0), (-1,-1), 5), ("BOTTOMPADDING",(0,0),(-1,-1), 5), ("LEFTPADDING", (0,0), (-1,-1), 6), ("RIGHTPADDING",(0,0),(-1,-1), 6), ("LINEBELOW", (0,0), (-1,0), 1.5, C_WHITE), ("ROUNDEDCORNERS", [4]), ] t.setStyle(TableStyle(ts)) return t # ── Highlighted box ─────────────────────────────────────────────────────────── def highlight_box(text, bg=C_SKY, border=C_BLUE, text_style=None): ts = text_style or body_style data = [[Paragraph(text, ts)]] t = Table(data, colWidths=[W - 3*cm]) t.setStyle(TableStyle([ ("BACKGROUND", (0,0),(-1,-1), bg), ("BOX", (0,0),(-1,-1), 1.5, border), ("LEFTPADDING", (0,0),(-1,-1), 10), ("RIGHTPADDING", (0,0),(-1,-1), 10), ("TOPPADDING", (0,0),(-1,-1), 8), ("BOTTOMPADDING", (0,0),(-1,-1), 8), ("ROUNDEDCORNERS",[6]), ])) return t # ════════════════════════════════════════════════════════════════════════════ # BUILD DOCUMENT # ════════════════════════════════════════════════════════════════════════════ doc = SimpleDocTemplate(OUTPUT, pagesize=A4, leftMargin=1.5*cm, rightMargin=1.5*cm, topMargin=1.8*cm, bottomMargin=1.5*cm, title="Statistical Test Decision Guide", author="Research Reference") story = [] # ══════════════════ PAGE 1: COVER ═══════════════════════════════════════════ story.append(CoverPage(W - 3*cm, H - 3*cm)) story.append(PageBreak()) # ══════════════════ PAGE 2: INTRO + DECISION FRAMEWORK ══════════════════════ story.append(Paragraph("How to Choose the Right Statistical Test", h1_style)) story.append(HRFlowable(width="100%", thickness=2, color=C_BLUE, spaceAfter=8)) story.append(Paragraph( "Choosing the wrong statistical test is one of the most common errors in research. " "This guide walks you through a systematic decision process based on your study design, " "data type, distribution, and research question. Follow the questions in order.", body_style)) story.append(Spacer(1, 0.3*cm)) # Step-by-step decision boxes story.append(Paragraph("Step-by-Step Decision Framework", h2_style)) steps = [ ("Step 1\nWhat is your\nresearch question?", C_BLUE, C_WHITE), ("Step 2\nWhat type of\nvariable/data?", C_GREEN, C_WHITE), ("Step 3\nHow many\ngroups?", C_ORANGE, C_WHITE), ("Step 4\nIndependent\nor Paired?", C_PURPLE, C_WHITE), ("Step 5\nNormal\ndistribution?", C_TEAL, C_WHITE), ("Step 6\nSelect\nthe Test ✓", C_RED, C_WHITE), ] story.append(DecisionBox(steps, w=W - 3*cm)) story.append(Spacer(1, 0.4*cm)) # ── Data types ─────────────────────────────────────────────────────────────── story.append(Paragraph("Step 2 Detail: Types of Variables", h2_style)) rows_dtype = [ ["Nominal (Categorical)", "Named categories, no order", "Gender, Blood group, Eye colour", "Chi-square, Fisher's exact"], ["Ordinal", "Ordered categories, unequal gaps","Pain scale (1-10), Grade", "Mann-Whitney, Kruskal-Wallis"], ["Continuous (Interval)", "Equal gaps, no true zero", "Temperature (°C), IQ score", "t-test, ANOVA"], ["Continuous (Ratio)", "Equal gaps, true zero exists", "Weight, Height, Flexural strength","t-test, ANOVA, Pearson r"], ["Dichotomous", "Only 2 possible values", "Yes/No, Pass/Fail, Dead/Alive", "Chi-square, McNemar, Z-test"], ["Time-to-event", "Duration until an event", "Survival time, Disease-free period","Kaplan-Meier, Log-rank, Cox"], ] story.append(make_table( ["Data Type", "Definition", "Example", "Typical Test"], rows_dtype, [3.2*cm, 3.5*cm, 5.0*cm, 4.8*cm], header_bg=C_NAVY, alt_bg=C_SKY )) story.append(Spacer(1, 0.3*cm)) story.append(highlight_box( "<b>Key Rule:</b> Parametric tests (t-test, ANOVA, Pearson) require: " "(1) Continuous data, (2) Approximate normal distribution, (3) Homogeneous variances. " "If ANY of these fails, use the non-parametric equivalent instead.", bg=C_LGOLD, border=C_GOLD)) story.append(PageBreak()) # ══════════════════ PAGE 3: MAIN DECISION TABLE ══════════════════════════════ story.append(Paragraph("Master Reference: Statistical Test Selection Table", h1_style)) story.append(HRFlowable(width="100%", thickness=2, color=C_BLUE, spaceAfter=8)) rows_master = [ # Comparing 2 groups ["Compare 2 groups", "Continuous", "Independent", "Normal", "Independent Samples t-test", "Mann-Whitney U test"], ["Compare 2 groups", "Continuous", "Paired/Matched","Normal", "Paired t-test", "Wilcoxon Signed-Rank"], ["Compare 2 groups", "Continuous", "Independent", "Normal +\nunequal var","Welch's t-test", "Mann-Whitney U test"], # Comparing 3+ groups ["Compare 3+ groups", "Continuous", "Independent", "Normal", "One-Way ANOVA", "Kruskal-Wallis H test"], ["Compare 3+ groups", "Continuous", "Same subject\n(repeated)", "Normal","Repeated Measures ANOVA","Friedman test"], ["Compare 3+ groups", "Continuous", "Independent", "Normal +\n2 factors","Two-Way ANOVA", "Scheirer-Ray-Hare"], # Post-hoc ["Post-hoc (after\nANOVA)", "Continuous", "Independent", "Normal", "Tukey HSD / Bonferroni /\nScheffé","Dunn's test"], # Correlation ["Association /\nCorrelation","Continuous","—", "Normal", "Pearson r", "Spearman ρ"], ["Association /\nCorrelation","Ordinal", "—", "Any", "Spearman ρ", "Kendall τ"], ["Association /\nCorrelation","Nominal", "—", "Any", "Cramér's V / Phi", "Cramér's V"], # Categorical ["Compare proportions", "Nominal", "Independent", "n ≥ 5/cell", "Chi-Square (χ²)", "Fisher's Exact"], ["Compare proportions", "Nominal", "Paired", "Any", "McNemar test", "McNemar test"], # Regression ["Predict outcome", "Continuous DV", "—", "Normal resid.","Simple/Multiple Regression", "Spearman / Robust Reg."], ["Predict outcome", "Binary DV", "—", "Any", "Binary Logistic Regression", "Binary Logistic Reg."], ["Predict outcome", "Count DV", "—", "Poisson dist.","Poisson Regression", "Negative Binomial Reg."], # Survival ["Time-to-event", "Continuous", "Independent", "Any", "Kaplan-Meier + Log-rank", "Cox Proportional Hazards"], # Agreement ["Agreement / Reliability","Ordinal/Cont.","Same rater\nx2", "Any", "Cohen's Kappa / ICC", "Weighted Kappa"], # Normality ["Test normality", "Continuous", "—", "—", "Shapiro-Wilk (n<50)\nKolmogorov-Smirnov (n≥50)","—"], ["Test equal variances", "Continuous", "—", "—", "Levene's test", "Bartlett's test"], ] story.append(make_table( ["Research Goal", "Data Type", "Groups / Design", "Distribution", "Parametric Test", "Non-Parametric Test"], rows_master, [2.8*cm, 2.3*cm, 2.5*cm, 2.2*cm, 4.0*cm, 3.7*cm], header_bg=C_NAVY, alt_bg=C_SKY )) story.append(PageBreak()) # ══════════════════ PAGE 4: PARAMETRIC TESTS IN DEPTH ═══════════════════════ story.append(Paragraph("Parametric Tests - In Depth", h1_style)) story.append(HRFlowable(width="100%", thickness=2, color=C_BLUE, spaceAfter=8)) param_tests = [ ("Independent Samples t-test", C_BLUE, C_SKY, "Compares means of <b>two independent groups</b>.", ["Continuous dependent variable", "Two independent groups", "Normal distribution in each group", "Homogeneous variances (Levene's test)", "No significant outliers"], "H₀: μ₁ = μ₂ | t = (x̄₁ - x̄₂) / SE_diff", "Comparing flexural strength of Group I vs Group II splints"), ("Paired t-test", C_GREEN, C_LGREEN, "Compares means of <b>two related measurements</b> on the same subjects.", ["Continuous dependent variable", "Same subjects measured twice (before/after)", "Differences are normally distributed", "No significant outliers in differences"], "H₀: μd = 0 | t = d̄ / (sd/√n)", "Comparing surface roughness of same specimen before and after polishing"), ("One-Way ANOVA", C_ORANGE, C_LORANGE, "Compares means of <b>three or more independent groups</b>.", ["Continuous dependent variable", "Three or more independent groups", "Normal distribution in each group", "Homogeneous variances (Levene's test)", "Independent observations"], "H₀: μ₁=μ₂=μ₃ | F = MS_between / MS_within", "Comparing flexural strength of Conventional vs Milled vs 3D Printed splints"), ("Repeated Measures ANOVA", C_PURPLE, C_LPURPLE, "Compares means across <b>three or more time points or conditions on the SAME subjects</b>.", ["Continuous dependent variable", "Same subjects measured 3+ times", "Sphericity assumption (Mauchly's test)", "Normal distribution of differences"], "F = MS_between-conditions / MS_error", "Measuring same specimen's surface roughness at baseline, 1 month, 3 months"), ("Two-Way ANOVA", C_TEAL, C_LTEAL, "Examines effect of <b>two independent factors</b> and their interaction.", ["Continuous dependent variable", "Two categorical independent variables (factors)", "Normal distribution in each cell", "Homogeneous variances", "Adequate cell sizes (≥ 5 per cell recommended)"], "F_A = MS_A / MS_error | F_B = MS_B / MS_error | F_AB = MS_AB / MS_error", "Effect of fabrication method AND storage medium on flexural strength"), ] for name, hdr_color, bg_color, desc, assumptions, formula, example in param_tests: # Header data_hdr = [[Paragraph(f"<b>{name}</b>", S("Normal", fontSize=10, textColor=C_WHITE, fontName="Helvetica-Bold", alignment=TA_LEFT, leading=14))]] t_hdr = Table(data_hdr, colWidths=[W - 3*cm]) t_hdr.setStyle(TableStyle([ ("BACKGROUND", (0,0),(-1,-1), hdr_color), ("LEFTPADDING",(0,0),(-1,-1), 10), ("TOPPADDING", (0,0),(-1,-1), 6), ("BOTTOMPADDING",(0,0),(-1,-1), 6), ])) story.append(KeepTogether([t_hdr, Table([[Paragraph(desc, body_style), Paragraph(f"<b>Formula:</b> <font name='Courier'>{formula}</font><br/>" f"<b>Example:</b> {example}", body_style)]], colWidths=[(W-3*cm)*0.38, (W-3*cm)*0.62], style=TableStyle([ ("BACKGROUND",(0,0),(-1,-1), bg_color), ("VALIGN",(0,0),(-1,-1),"TOP"), ("LEFTPADDING",(0,0),(-1,-1),10), ("RIGHTPADDING",(0,0),(-1,-1),10), ("TOPPADDING",(0,0),(-1,-1),6), ("BOTTOMPADDING",(0,0),(-1,-1),6), ("LINEBELOW",(0,0),(-1,-1),0.5, colors.HexColor("#B0BEC5")), ])), Table([[Paragraph("<b>Assumptions:</b> " + " • ".join(assumptions), S("Normal", fontSize=8.5, textColor=C_GREY, fontName="Helvetica", leading=13, leftIndent=4))]], colWidths=[W - 3*cm], style=TableStyle([ ("BACKGROUND",(0,0),(-1,-1), C_LGREY), ("LEFTPADDING",(0,0),(-1,-1),10), ("TOPPADDING",(0,0),(-1,-1),5), ("BOTTOMPADDING",(0,0),(-1,-1),5), ("LINEBELOW",(0,0),(-1,-1),1.5, hdr_color), ])), Spacer(1, 0.25*cm), ])) story.append(PageBreak()) # ══════════════════ PAGE 5: NON-PARAMETRIC TESTS ════════════════════════════ story.append(Paragraph("Non-Parametric Tests - In Depth", h1_style)) story.append(HRFlowable(width="100%", thickness=2, color=C_GREEN, spaceAfter=8)) story.append(highlight_box( "<b>When to use non-parametric tests:</b> Data fails normality (Shapiro-Wilk p < 0.05) • " "Ordinal data • Small sample sizes (n < 10 per group) • " "Presence of outliers that cannot be removed • Data has severe skewness", bg=C_LGREEN, border=C_GREEN)) story.append(Spacer(1, 0.3*cm)) rows_nonparam = [ ["Mann-Whitney U", "Compare 2 independent groups", "Ordinal or continuous;\nnon-normal", "Yes", "Independent t-test", "U = n₁n₂ + n₁(n₁+1)/2 − R₁", "Two drug groups with skewed pain scores"], ["Wilcoxon Signed-Rank", "Compare 2 paired/related groups", "Continuous;\nnon-normal differences", "Yes", "Paired t-test", "W = Σ(signed ranks)", "Before vs. after treatment in same patients"], ["Kruskal-Wallis H", "Compare 3+ independent groups", "Ordinal or continuous;\nnon-normal", "Yes", "One-Way ANOVA", "H = (12/N(N+1))Σ(Rᵢ²/nᵢ) - 3(N+1)", "Compare 3 fabrication groups with small samples"], ["Friedman test", "Compare 3+ repeated measures", "Ordinal or continuous;\nnon-normal", "Yes", "RM-ANOVA", "χ²r = 12/(Nk(k+1))ΣRj² - 3N(k+1)", "Same patients measured 3 times, non-normal"], ["Chi-Square χ²", "Association between 2 categorical variables", "Nominal/categorical;\nexpected ≥ 5/cell", "No", "N/A (categorical)", "χ² = Σ(O-E)²/E", "Association between gender and disease presence"], ["Fisher's Exact", "Association; small samples", "Nominal; expected < 5 in ≥1 cell", "No", "Chi-square (small n)", "Hypergeometric probability", "2×2 table with small cell counts"], ["McNemar test", "Compare paired proportions", "Nominal; paired/matched", "No", "Paired t-test (binary)", "χ² = (b-c)²/(b+c)", "Treatment success rate: before vs. after"], ["Spearman ρ", "Correlation between 2 variables", "Ordinal or non-normal continuous", "No", "Pearson r", "ρ = 1 - 6Σd²/n(n²-1)", "Rank correlation between pain score and dose"], ["Dunn's test", "Post-hoc after Kruskal-Wallis", "Non-parametric pairwise", "Yes", "Tukey HSD (post-ANOVA)", "z = (Rᵢ - Rⱼ)/SE", "Identifying which pairs differ after K-W"], ] story.append(make_table( ["Test", "Purpose", "Data & Conditions", "Post-Hoc?", "Parametric Equivalent", "Formula", "Example"], rows_nonparam, [2.8*cm, 3.0*cm, 2.5*cm, 1.5*cm, 2.8*cm, 3.5*cm, 3.4*cm], header_bg=C_GREEN, alt_bg=C_LGREEN )) story.append(PageBreak()) # ══════════════════ PAGE 6: NORMALITY + ASSUMPTION TESTS ════════════════════ story.append(Paragraph("Testing Assumptions Before Your Main Analysis", h1_style)) story.append(HRFlowable(width="100%", thickness=2, color=C_PURPLE, spaceAfter=8)) story.append(Paragraph("1. Normality Tests", h2_style)) rows_normal = [ ["Shapiro-Wilk", "n < 50\n(Best for small samples)", "p > 0.05 → Normal\np ≤ 0.05 → Non-normal", "Most powerful for small n;\nhighly recommended for dental/biomedical research", "scipy.stats.shapiro(data)"], ["Kolmogorov-Smirnov (K-S)", "n ≥ 50\n(Large samples)", "p > 0.05 → Normal\np ≤ 0.05 → Non-normal", "Less powerful than S-W;\nLilliefors correction often needed", "scipy.stats.kstest(data,'norm')"], ["D'Agostino-Pearson", "n ≥ 20", "p > 0.05 → Normal", "Based on skewness + kurtosis;\ngood for moderate samples", "scipy.stats.normaltest(data)"], ["Q-Q Plot", "Any n (visual only)", "Points on diagonal → Normal\nDeviations → Non-normal", "Always plot alongside numeric tests;\nvisual confirmation", "scipy.stats.probplot(data)"], ["Histogram", "Any n (visual only)", "Bell shape → approximately normal", "Useful supplement;\nnot a formal test", "plt.hist(data, bins='auto')"], ] story.append(make_table( ["Test", "Best For", "Interpretation", "Notes", "Python Code"], rows_normal, [3.5*cm, 3.0*cm, 3.5*cm, 4.0*cm, 3.5*cm], header_bg=C_PURPLE, alt_bg=C_LPURPLE )) story.append(Spacer(1, 0.4*cm)) story.append(Paragraph("2. Homogeneity of Variances", h2_style)) rows_var = [ ["Levene's test", "Compares variances of 2+ groups; robust to non-normality", "p > 0.05 → Equal variances (ANOVA assumption met)\np ≤ 0.05 → Unequal variances → use Welch's ANOVA", "scipy.stats.levene(g1,g2,g3)"], ["Bartlett's test", "Compares variances; sensitive to non-normality", "p > 0.05 → Equal variances\nNOTE: requires normal data", "scipy.stats.bartlett(g1,g2,g3)"], ["Fligner-Killeen", "Non-parametric alternative to Levene's", "p > 0.05 → Equal variances", "scipy.stats.fligner(g1,g2,g3)"], ] story.append(make_table( ["Test", "Description", "Interpretation", "Python Code"], rows_var, [3.5*cm, 5.0*cm, 5.5*cm, 4.5*cm], header_bg=C_TEAL, alt_bg=C_LTEAL )) story.append(Spacer(1, 0.4*cm)) story.append(Paragraph("3. Mauchly's Test of Sphericity (for RM-ANOVA only)", h2_style)) story.append(highlight_box( "<b>Sphericity</b> means the variances of differences between all pairs of repeated conditions are equal. " "Required assumption for Repeated Measures ANOVA. " "<b>If violated (p < 0.05):</b> Apply Greenhouse-Geisser or Huynh-Feldt correction to adjust the F-test degrees of freedom. " "<b>If not violated (p > 0.05):</b> Proceed with standard RM-ANOVA.", bg=C_LPURPLE, border=C_PURPLE)) story.append(PageBreak()) # ══════════════════ PAGE 7: CORRELATION & REGRESSION ════════════════════════ story.append(Paragraph("Correlation and Regression Tests", h1_style)) story.append(HRFlowable(width="100%", thickness=2, color=C_TEAL, spaceAfter=8)) story.append(Paragraph("Correlation Tests", h2_style)) rows_corr = [ ["Pearson r", "Linear correlation between 2 continuous variables", "Both normal; linear relationship; no outliers", "r = Σ(x-x̄)(y-ȳ) / √[Σ(x-x̄)²·Σ(y-ȳ)²]", "|r| ≥ 0.7 Strong\n0.4-0.69 Moderate\n< 0.4 Weak", "Correlation between load and deflection"], ["Spearman ρ", "Monotonic correlation; ranks-based", "Ordinal or non-normal data; monotonic (not necessarily linear)", "ρ = 1 - 6Σdᵢ²/n(n²-1)", "|ρ| ≥ 0.7 Strong\n0.4-0.69 Moderate\n< 0.4 Weak", "Rank correlation: pain score vs. dose rank"], ["Kendall τ", "Concordance between rankings", "Ordinal; better for small n with ties", "τ = (C-D)/√[(C+D+T)(C+D+U)]", "Similar to Spearman but more conservative", "Agreement between two raters' rankings"], ["Point-Biserial", "Correlation: continuous vs. dichotomous", "Continuous variable must be normal", "rpb = (M₁-M₀)/SD · √(p·q)", "Same as Pearson r thresholds", "Flexural strength vs. fracture (yes/no)"], ] story.append(make_table( ["Test", "Purpose", "Assumptions", "Formula", "Strength Interpretation", "Example"], rows_corr, [2.5*cm, 3.0*cm, 3.0*cm, 3.5*cm, 2.8*cm, 3.2*cm], header_bg=C_TEAL, alt_bg=C_LTEAL )) story.append(Spacer(1, 0.4*cm)) story.append(Paragraph("Regression Tests", h2_style)) rows_reg = [ ["Simple Linear\nRegression", "1 continuous predictor → 1 continuous outcome", "Normality of residuals; linearity; homoscedasticity; independence", "Y = β₀ + β₁X + ε", "Predicting flexural strength from specimen thickness"], ["Multiple Linear\nRegression", "2+ continuous predictors → 1 continuous outcome", "Same as SLR + no multicollinearity (VIF < 10)", "Y = β₀ + β₁X₁ + β₂X₂ + ... + ε", "Predicting strength from material + thickness + cure time"], ["Binary Logistic\nRegression", "Predictors → Binary outcome (0/1)", "Large n; no multicollinearity; linearity of log-odds", "ln(p/1-p) = β₀ + β₁X₁ + ...", "Predicting fracture (yes/no) from fabrication method"], ["Ordinal Logistic\nRegression", "Predictors → Ordinal outcome", "Proportional odds assumption", "Cumulative logit model", "Predicting pain grade (mild/moderate/severe) from treatment"], ["Cox Proportional\nHazards", "Predictors → Time-to-event outcome", "Proportional hazards assumption", "h(t) = h₀(t)·exp(β₁X₁ + ...)", "Predicting time to implant failure from risk factors"], ] story.append(make_table( ["Model", "Use When", "Key Assumptions", "Equation Form", "Clinical Example"], rows_reg, [3.0*cm, 3.5*cm, 4.0*cm, 3.5*cm, 4.5*cm], header_bg=C_ORANGE, alt_bg=C_LORANGE )) story.append(PageBreak()) # ══════════════════ PAGE 8: EFFECT SIZE + POWER ═════════════════════════════ story.append(Paragraph("Effect Size and Statistical Power", h1_style)) story.append(HRFlowable(width="100%", thickness=2, color=C_ORANGE, spaceAfter=8)) story.append(highlight_box( "<b>A statistically significant result (p < 0.05) does NOT always mean a clinically meaningful result.</b> " "Effect size measures the magnitude of the difference. Always report effect size alongside p-values.", bg=C_LORANGE, border=C_ORANGE)) story.append(Spacer(1, 0.3*cm)) story.append(Paragraph("Effect Size Measures by Test", h2_style)) rows_effect = [ ["Cohen's d", "Independent t-test, Paired t-test", "d = (μ₁-μ₂)/SD_pooled", "Small: 0.2 | Medium: 0.5 | Large: 0.8"], ["Eta-squared η²", "One-Way ANOVA", "η² = SS_between / SS_total", "Small: 0.01 | Medium: 0.06 | Large: 0.14+"], ["Partial η²", "Multi-factor ANOVA, ANCOVA", "η²p = SS_effect / (SS_effect + SS_error)", "Small: 0.01 | Medium: 0.06 | Large: 0.14+"], ["Omega-squared ω²","ANOVA (less biased than η²)", "ω² = (SS_B - df_B·MS_W)/(SS_T + MS_W)", "Preferred over η² for small samples"], ["Pearson r", "Correlation", "r (already an effect size)", "Small: 0.1 | Medium: 0.3 | Large: 0.5"], ["r (from Mann-Whitney)", "Mann-Whitney U", "r = Z / √N", "Small: 0.1 | Medium: 0.3 | Large: 0.5"], ["Cramér's V", "Chi-Square", "V = √(χ²/N·min(r-1,c-1))", "Small: 0.1 | Medium: 0.3 | Large: 0.5"], ["Odds Ratio (OR)", "Logistic Regression, 2×2 tables", "OR = (a·d)/(b·c)", "OR = 1: no effect | >1: increased odds | <1: decreased odds"], ["Hazard Ratio (HR)","Cox Regression", "HR = h₁(t)/h₀(t)", "HR = 1: no difference | HR > 1: higher hazard in group 1"], ] story.append(make_table( ["Measure", "Used With", "Formula", "Interpretation (Cohen's convention)"], rows_effect, [3.5*cm, 4.0*cm, 5.5*cm, 5.5*cm], header_bg=C_ORANGE, alt_bg=C_LORANGE )) story.append(Spacer(1, 0.4*cm)) story.append(Paragraph("Statistical Power (1 - β)", h2_style)) rows_power = [ ["α (Type I error)", "Rejecting H₀ when it is true (false positive)", "Typically set at 0.05", "↑ α → ↑ power but ↑ false positives"], ["β (Type II error)", "Accepting H₀ when it is false (false negative)", "Typically 0.20 acceptable", "↓ β → ↑ power"], ["Power (1-β)", "Probability of correctly rejecting false H₀", "Desired ≥ 0.80 (80%)", "Increase with larger sample size"], ["Sample size n", "Larger n → smaller SE → more power", "Determined a priori by power analysis", "Use G*Power software or R pwr package"], ["Effect size", "Larger true effect → easier to detect", "Estimate from pilot data or literature", "Smaller effects need more participants"], ] story.append(make_table( ["Parameter", "Definition", "Typical Value", "Impact on Analysis"], rows_power, [3.5*cm, 5.0*cm, 4.0*cm, 5.0*cm], header_bg=C_RED, alt_bg=C_LRED )) story.append(PageBreak()) # ══════════════════ PAGE 9: QUICK-REFERENCE CHEAT SHEET ══════════════════════ story.append(Paragraph("Quick-Reference Cheat Sheet", h1_style)) story.append(HRFlowable(width="100%", thickness=2, color=C_NAVY, spaceAfter=8)) # 3-column colour grid scenarios = [ (C_BLUE, C_SKY, "2 Groups\nIndependent\nNormal Data", "Independent\nt-test", "Mann-Whitney U"), (C_BLUE, C_SKY, "2 Groups\nPaired/Matched\nNormal Data", "Paired t-test", "Wilcoxon\nSigned-Rank"), (C_GREEN, C_LGREEN,"3+ Groups\nIndependent\nNormal Data", "One-Way\nANOVA", "Kruskal-Wallis"), (C_GREEN, C_LGREEN,"3+ Conditions\nSame Subjects\nNormal Data", "Repeated\nMeasures ANOVA", "Friedman test"), (C_ORANGE, C_LORANGE,"2 Factors\nIndependent\nNormal Data", "Two-Way\nANOVA", "Scheirer-\nRay-Hare"), (C_TEAL, C_LTEAL, "Association\nContinuous\nNormal Data", "Pearson r", "Spearman ρ"), (C_TEAL, C_LTEAL, "Association\nOrdinal or\nNon-normal", "Spearman ρ", "Kendall τ"), (C_PURPLE, C_LPURPLE,"Categorical\nAssociation\n≥5 per cell", "Chi-Square χ²", "Fisher's Exact"), (C_PURPLE, C_LPURPLE,"Paired\nProportions", "McNemar test", "McNemar test"), (C_RED, C_LRED, "Predict\nContinuous\nOutcome", "Linear\nRegression", "Spearman /\nRobust Reg."), (C_RED, C_LRED, "Predict Binary\nOutcome", "Logistic\nRegression", "Logistic Reg."), (C_GOLD, C_LGOLD, "Time-to-Event\n/ Survival", "Kaplan-Meier\n+ Log-rank", "Cox\nRegression"), ] grid_data = [] row = [] for i, (hc, bc, scenario, param, nonparam) in enumerate(scenarios): cell = Table([ [Paragraph(scenario, S("Normal", fontSize=8, textColor=C_GREY, fontName="Helvetica", leading=11, alignment=TA_CENTER))], [Paragraph("Parametric:", S("Normal", fontSize=7, textColor=hc, fontName="Helvetica-Bold", alignment=TA_CENTER))], [Paragraph(param, S("Normal", fontSize=9, textColor=hc, fontName="Helvetica-Bold", leading=12, alignment=TA_CENTER))], [Paragraph("Non-Parametric:", S("Normal", fontSize=7, textColor=C_GREY, fontName="Helvetica-Oblique", alignment=TA_CENTER))], [Paragraph(nonparam, S("Normal", fontSize=8.5, textColor=C_GREY, fontName="Helvetica", leading=11, alignment=TA_CENTER))], ], colWidths=[4.0*cm], rowHeights=[1.0*cm, 0.35*cm, 0.7*cm, 0.35*cm, 0.7*cm]) cell.setStyle(TableStyle([ ("BACKGROUND", (0,0),(-1,-1), bc), ("LINEABOVE", (0,0),(-1,0), 3, hc), ("BOX", (0,0),(-1,-1), 0.5, hc), ("ALIGN", (0,0),(-1,-1), "CENTER"), ("VALIGN", (0,0),(-1,-1), "MIDDLE"), ("TOPPADDING", (0,0),(-1,-1), 3), ("BOTTOMPADDING", (0,0),(-1,-1), 3), ("ROUNDEDCORNERS",[5]), ])) row.append(cell) if len(row) == 3: grid_data.append(row) row = [] if row: while len(row) < 3: row.append(Spacer(4.0*cm, 3.1*cm)) grid_data.append(row) grid = Table(grid_data, colWidths=[4.2*cm, 4.2*cm, 4.2*cm], hAlign="CENTER", rowHeights=[3.1*cm]*len(grid_data)) grid.setStyle(TableStyle([ ("ALIGN", (0,0),(-1,-1),"CENTER"), ("VALIGN", (0,0),(-1,-1),"MIDDLE"), ("LEFTPADDING", (0,0),(-1,-1), 5), ("RIGHTPADDING", (0,0),(-1,-1), 5), ("TOPPADDING", (0,0),(-1,-1), 5), ("BOTTOMPADDING",(0,0),(-1,-1), 5), ])) story.append(grid) story.append(Spacer(1, 0.3*cm)) story.append(Paragraph( "<b>Remember:</b> p-value tells you IF an effect exists. Effect size tells you HOW BIG it is. " "Always report both. Significance without context is incomplete science.", note_style)) # ══════════════════ BUILD ════════════════════════════════════════════════════ doc.build(story, onFirstPage=on_page, onLaterPages=on_page) print("PDF built successfully:", OUTPUT) PYEOF
python3 << 'PYEOF' from reportlab.lib.pagesizes import A4 from reportlab.lib import colors from reportlab.lib.units import cm from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, PageBreak, HRFlowable, KeepTogether) from reportlab.platypus.flowables import Flowable W, H = A4 OUTPUT = "/tmp/workspace/stat-guide/Statistical_Test_Decision_Guide.pdf" C_NAVY = colors.HexColor("#1A237E") C_BLUE = colors.HexColor("#1565C0") C_SKY = colors.HexColor("#E3F2FD") C_GREEN = colors.HexColor("#2E7D32") C_LGREEN = colors.HexColor("#E8F5E9") C_ORANGE = colors.HexColor("#E65100") C_LORANGE = colors.HexColor("#FFF3E0") C_PURPLE = colors.HexColor("#4A148C") C_LPURPLE = colors.HexColor("#F3E5F5") C_RED = colors.HexColor("#B71C1C") C_LRED = colors.HexColor("#FFEBEE") C_GOLD = colors.HexColor("#F57F17") C_LGOLD = colors.HexColor("#FFFDE7") C_TEAL = colors.HexColor("#00695C") C_LTEAL = colors.HexColor("#E0F2F1") C_GREY = colors.HexColor("#37474F") C_LGREY = colors.HexColor("#ECEFF1") C_WHITE = colors.white styles = getSampleStyleSheet() def S(name, **kw): base = styles.get(name, styles["Normal"]) return ParagraphStyle(name + str(id(kw)), parent=base, **kw) h1_style = S("Heading1", fontSize=16, textColor=C_NAVY, fontName="Helvetica-Bold", spaceBefore=12, spaceAfter=5, leading=20) h2_style = S("Heading2", fontSize=12, textColor=C_BLUE, fontName="Helvetica-Bold", spaceBefore=8, spaceAfter=4, leading=16) body_style = S("Normal", fontSize=9.5, textColor=C_GREY, fontName="Helvetica", leading=14, spaceAfter=4, alignment=TA_JUSTIFY) note_style = S("Normal", fontSize=8.5, textColor=C_GREY, fontName="Helvetica-Oblique", leading=12, alignment=TA_CENTER) cell_h_style = S("Normal", fontSize=9, textColor=C_WHITE, fontName="Helvetica-Bold", alignment=TA_CENTER, leading=12) cell_style = S("Normal", fontSize=8.5, textColor=C_GREY, fontName="Helvetica", alignment=TA_LEFT, leading=12) cell_c_style = S("Normal", fontSize=8.5, textColor=C_GREY, fontName="Helvetica", alignment=TA_CENTER, leading=12) # ── Cover Page ──────────────────────────────────────────────────────────────── class CoverPage(Flowable): def __init__(self, w, h): Flowable.__init__(self) self.width = w self.height = h def draw(self): c = self.canv c.setFillColor(C_NAVY) c.rect(0, 0, self.width, self.height, fill=1, stroke=0) c.setFillColor(C_BLUE) c.rect(0, self.height*0.35, self.width, self.height*0.65, fill=1, stroke=0) c.setFillColor(colors.HexColor("#283593")) c.circle(self.width*0.85, self.height*0.72, 85, fill=1, stroke=0) c.setFillColor(colors.HexColor("#1A237E")) c.circle(self.width*0.08, self.height*0.82, 55, fill=1, stroke=0) # Title c.setFillColor(C_WHITE) c.setFont("Helvetica-Bold", 30) c.drawCentredString(self.width/2, self.height*0.60, "Statistical Test") c.drawCentredString(self.width/2, self.height*0.60 - 38, "Decision Guide") c.setFont("Helvetica", 13) c.setFillColor(colors.HexColor("#BBDEFB")) c.drawCentredString(self.width/2, self.height*0.60 - 68, "A Complete Reference for Research & PhD Scholars") c.setStrokeColor(C_GOLD) c.setLineWidth(3) c.line(self.width*0.2, self.height*0.60 - 82, self.width*0.8, self.height*0.60 - 82) # Lower box c.setFillColor(colors.HexColor("#0D47A1")) c.roundRect(self.width*0.1, self.height*0.05, self.width*0.8, self.height*0.27, 10, fill=1, stroke=0) c.setFillColor(C_GOLD) c.setFont("Helvetica-Bold", 10) topics = [ "Parametric & Non-Parametric Tests", "Comparative, Correlation & Regression Tests", "Normality & Variance Assumption Checks", "Effect Size & Statistical Power Guide", "Full Decision Tables & Quick Cheat Sheet", ] base_y = self.height*0.05 + self.height*0.27 - 24 for i, t in enumerate(topics): c.drawCentredString(self.width/2, base_y - i*22, "\u2022 " + t) # ── Decision Step Bar ───────────────────────────────────────────────────────── class StepBar(Flowable): def __init__(self, steps, w=None): Flowable.__init__(self) self.steps = steps # list of (label, bg_color) self.width = w or (W - 3*cm) self.height = 2.0*cm def draw(self): c = self.canv n = len(self.steps) seg_w = self.width / n box_w = seg_w - 8 for i, (label, bg) in enumerate(self.steps): x = i * seg_w + 2 c.setFillColor(bg) c.roundRect(x, 0.15*cm, box_w, 1.6*cm, 7, fill=1, stroke=0) c.setFillColor(C_WHITE) c.setFont("Helvetica-Bold", 7.5) lines = label.split("\n") mid = 0.15*cm + 0.8*cm start_y = mid + (len(lines)-1)*5.5 for j, ln in enumerate(lines): c.drawCentredString(x + box_w/2, start_y - j*11, ln) if i < n - 1: ax = x + box_w + 2 ay = 0.15*cm + 0.8*cm c.setFillColor(C_GREY) c.polygon([ax, ay+4, ax, ay-4, ax+6, ay], fill=1, stroke=0) def make_table(headers, rows, col_widths, header_bg=C_NAVY, alt_bg=C_SKY): data = [[Paragraph(h, cell_h_style) for h in headers]] for row in rows: data.append([Paragraph(str(cell), cell_c_style if j > 0 else cell_style) for j, cell in enumerate(row)]) t = Table(data, colWidths=col_widths, repeatRows=1) t.setStyle(TableStyle([ ("BACKGROUND", (0,0), (-1,0), header_bg), ("TEXTCOLOR", (0,0), (-1,0), C_WHITE), ("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"), ("FONTSIZE", (0,0), (-1,0), 9), ("ALIGN", (0,0), (-1,-1), "CENTER"), ("VALIGN", (0,0), (-1,-1), "MIDDLE"), ("ROWBACKGROUNDS", (0,1), (-1,-1), [C_WHITE, alt_bg]), ("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#B0BEC5")), ("TOPPADDING", (0,0), (-1,-1), 5), ("BOTTOMPADDING", (0,0), (-1,-1), 5), ("LEFTPADDING", (0,0), (-1,-1), 5), ("RIGHTPADDING", (0,0), (-1,-1), 5), ("LINEBELOW", (0,0), (-1,0), 1.5, C_WHITE), ])) return t def highlight_box(text, bg=C_SKY, border=C_BLUE): t = Table([[Paragraph(text, body_style)]], colWidths=[W - 3*cm]) t.setStyle(TableStyle([ ("BACKGROUND", (0,0),(-1,-1), bg), ("BOX", (0,0),(-1,-1), 1.5, border), ("LEFTPADDING", (0,0),(-1,-1), 10), ("RIGHTPADDING", (0,0),(-1,-1), 10), ("TOPPADDING", (0,0),(-1,-1), 8), ("BOTTOMPADDING", (0,0),(-1,-1), 8), ])) return t def section_badge(text, bg): t = Table([[Paragraph(f"<b>{text}</b>", S("Normal", fontSize=10.5, textColor=C_WHITE, fontName="Helvetica-Bold", alignment=TA_LEFT, leading=14))]], colWidths=[W - 3*cm]) t.setStyle(TableStyle([ ("BACKGROUND", (0,0),(-1,-1), bg), ("LEFTPADDING", (0,0),(-1,-1), 10), ("TOPPADDING", (0,0),(-1,-1), 6), ("BOTTOMPADDING", (0,0),(-1,-1), 6), ])) return t def on_page(canvas, doc): canvas.saveState() canvas.setFillColor(C_NAVY) canvas.rect(0, 0, W, 1.0*cm, fill=1, stroke=0) canvas.setFillColor(C_WHITE) canvas.setFont("Helvetica", 8) canvas.drawCentredString(W/2, 0.35*cm, "Statistical Test Decision Guide | PhD / Research Reference") canvas.drawRightString(W - 1.5*cm, 0.35*cm, f"Page {doc.page}") if doc.page > 1: canvas.setFillColor(C_NAVY) canvas.rect(0, H - 0.55*cm, W, 0.55*cm, fill=1, stroke=0) canvas.setFillColor(C_WHITE) canvas.setFont("Helvetica-Bold", 8) canvas.drawCentredString(W/2, H - 0.38*cm, "STATISTICAL TEST DECISION GUIDE") canvas.restoreState() # ════════════════════════ BUILD ══════════════════════════════════════════════ doc = SimpleDocTemplate(OUTPUT, pagesize=A4, leftMargin=1.5*cm, rightMargin=1.5*cm, topMargin=1.8*cm, bottomMargin=1.4*cm, title="Statistical Test Decision Guide", author="Research Reference") story = [] # PAGE 1: COVER story.append(CoverPage(W - 3*cm, H - 3*cm)) story.append(PageBreak()) # PAGE 2: DECISION FRAMEWORK + DATA TYPES story.append(Paragraph("How to Choose the Right Statistical Test", h1_style)) story.append(HRFlowable(width="100%", thickness=2, color=C_BLUE, spaceAfter=6)) story.append(Paragraph( "Choosing the wrong statistical test is one of the most common methodological errors in research. " "Follow these six steps systematically before selecting any test.", body_style)) story.append(Spacer(1, 0.25*cm)) story.append(Paragraph("Six-Step Decision Framework", h2_style)) steps = [ ("STEP 1\nResearch\nQuestion", C_BLUE), ("STEP 2\nVariable\nType", C_GREEN), ("STEP 3\nNo. of\nGroups", C_ORANGE), ("STEP 4\nIndependent\nor Paired?", C_PURPLE), ("STEP 5\nNormal\nDistribution?", C_TEAL), ("STEP 6\nSelect\nTest \u2713", C_RED), ] story.append(StepBar(steps, w=W - 3*cm)) story.append(Spacer(1, 0.35*cm)) story.append(Paragraph("Step 2: Types of Variables", h2_style)) rows_dtype = [ ["Nominal (Categorical)", "Named categories, no order", "Gender, Blood group", "Chi-square, Fisher's Exact"], ["Ordinal", "Ordered categories, unequal intervals","Pain scale 1-10, Grade", "Mann-Whitney, Kruskal-Wallis"], ["Continuous - Interval", "Equal gaps, no true zero", "Temperature (°C), IQ", "t-test, ANOVA, Pearson r"], ["Continuous - Ratio", "Equal gaps + true zero", "Weight, Flexural strength (N)", "t-test, ANOVA, Pearson r"], ["Dichotomous", "Exactly 2 values", "Yes/No, Fractured/Intact", "Chi-square, Z-test, Logistic Reg."], ["Time-to-event", "Duration until event occurs", "Survival time, Implant failure", "Kaplan-Meier, Cox Regression"], ] story.append(make_table( ["Data Type", "Definition", "Example", "Typical Test"], rows_dtype, [3.2*cm, 3.8*cm, 4.5*cm, 5.0*cm], header_bg=C_NAVY, alt_bg=C_SKY)) story.append(Spacer(1, 0.3*cm)) story.append(highlight_box( "<b>Golden Rule:</b> Parametric tests require: (1) Continuous data, (2) Approximately normal distribution, " "(3) Homogeneous variances. If ANY condition fails, switch to the non-parametric equivalent.", bg=C_LGOLD, border=C_GOLD)) story.append(PageBreak()) # PAGE 3: MASTER DECISION TABLE story.append(Paragraph("Master Reference: Statistical Test Selection", h1_style)) story.append(HRFlowable(width="100%", thickness=2, color=C_BLUE, spaceAfter=6)) rows_master = [ ["Compare 2 groups", "Continuous", "Independent", "Normal", "Independent t-test", "Mann-Whitney U"], ["Compare 2 groups", "Continuous", "Paired/Matched","Normal", "Paired t-test", "Wilcoxon Signed-Rank"], ["Compare 2 groups", "Continuous", "Independent", "Normal + unequal var","Welch's t-test", "Mann-Whitney U"], ["Compare 3+ groups", "Continuous", "Independent", "Normal", "One-Way ANOVA", "Kruskal-Wallis H"], ["Compare 3+ groups", "Continuous", "Same subject (repeated)", "Normal","Repeated Measures ANOVA", "Friedman test"], ["Compare 3+ groups", "Continuous", "Independent", "Normal + 2 factors","Two-Way ANOVA", "Scheirer-Ray-Hare"], ["Post-hoc (after ANOVA)","Continuous","Independent", "Normal", "Tukey HSD / Bonferroni", "Dunn's test"], ["Post-hoc (after RM-ANOVA)","Continuous","Paired", "Normal", "Bonferroni / Sidak", "Wilcoxon + Bonferroni"], ["Correlation", "Continuous", "\u2014", "Normal + linear","Pearson r", "Spearman \u03c1"], ["Correlation", "Ordinal", "\u2014", "Any", "Spearman \u03c1", "Kendall \u03c4"], ["Compare proportions", "Nominal", "Independent", "n \u2265 5/cell", "Chi-Square \u03c7\u00b2", "Fisher's Exact"], ["Compare proportions", "Nominal", "Paired", "Any", "McNemar test", "McNemar test"], ["Predict outcome", "Continuous", "\u2014", "Normal resid.", "Linear Regression", "Robust Regression"], ["Predict outcome", "Binary DV", "\u2014", "Any", "Logistic Regression", "Logistic Regression"], ["Time-to-event", "Continuous", "Independent", "Any", "Kaplan-Meier + Log-rank", "Cox Proportional Hazards"], ["Agreement / ICC", "Continuous", "Same rater x2", "Any", "Intraclass Correlation (ICC)","Cohen's Kappa"], ["Test normality", "Continuous", "\u2014", "\u2014", "Shapiro-Wilk (n<50)", "K-S test (n\u226550)"], ["Test equal variances", "Continuous", "2+ groups", "\u2014", "Levene's test", "Bartlett's test"], ] story.append(make_table( ["Research Goal", "Data Type", "Design", "Distribution", "Parametric Test", "Non-Parametric Test"], rows_master, [2.8*cm, 2.3*cm, 2.7*cm, 2.5*cm, 4.0*cm, 3.2*cm], header_bg=C_NAVY, alt_bg=C_SKY)) story.append(PageBreak()) # PAGE 4: PARAMETRIC TESTS IN DEPTH story.append(Paragraph("Parametric Tests - In Depth", h1_style)) story.append(HRFlowable(width="100%", thickness=2, color=C_BLUE, spaceAfter=6)) param_data = [ ("Independent Samples t-test", C_BLUE, C_SKY, "Compares means of <b>two independent groups</b>.", "Continuous DV \u2022 2 independent groups \u2022 Normal distribution \u2022 Equal variances (Levene's p>0.05)", "t = (x\u0305\u2081 - x\u0305\u2082) / SE_diff", "Group I (Conventional) vs Group II (Milled) splint strength"), ("Paired t-test", C_GREEN, C_LGREEN, "Compares means of <b>two related measurements</b> from the <b>same subjects</b>.", "Continuous DV \u2022 Same subjects measured twice \u2022 Differences are normally distributed", "t = d\u0305 / (s_d / \u221an)", "Surface roughness of same specimen before vs. after polishing"), ("One-Way ANOVA", C_ORANGE, C_LORANGE, "Compares means of <b>3 or more independent groups</b>. Use post-hoc tests to find which pairs differ.", "Continuous DV \u2022 3+ independent groups \u2022 Normal distribution \u2022 Equal variances \u2022 Independence", "F = MS_between / MS_within", "Conventional vs. Milled vs. 3D Printed occlusal splints"), ("Repeated Measures ANOVA", C_PURPLE, C_LPURPLE, "Compares means across <b>3+ time points or conditions on the SAME subjects</b>. Requires sphericity.", "Continuous DV \u2022 Same subjects in all conditions \u2022 Sphericity (Mauchly's test) \u2022 Normal differences", "F = MS_between-conditions / MS_error", "Same specimen tested at baseline, 1 month, 3 months"), ("Two-Way ANOVA", C_TEAL, C_LTEAL, "Examines effects of <b>two independent factors</b> and their interaction simultaneously.", "Continuous DV \u2022 2 categorical IV (factors) \u2022 Normal distribution \u2022 Equal variances \u2022 \u22655/cell", "F_A = MS_A/MS_err | F_B = MS_B/MS_err | F_AB = MS_AB/MS_err", "Effect of fabrication method AND storage medium on strength"), ] for name, hc, bc, desc, assump, formula, example in param_data: inner = Table([ [Paragraph(desc, body_style), Paragraph(f"<b>Formula:</b> <font name='Courier'>{formula}</font><br/><b>Example:</b> {example}", body_style)], [Paragraph(f"<b>Assumptions:</b> {assump}", S("Normal", fontSize=8.5, textColor=C_GREY, fontName="Helvetica", leading=13)), ""] ], colWidths=[(W-3*cm)*0.42, (W-3*cm)*0.58], style=TableStyle([ ("BACKGROUND", (0,0),(-1,-1), bc), ("VALIGN", (0,0),(-1,-1),"TOP"), ("LEFTPADDING",(0,0),(-1,-1),8), ("TOPPADDING", (0,0),(-1,-1),5), ("BOTTOMPADDING",(0,0),(-1,-1),5), ("SPAN", (0,1),(1,1)), ("BACKGROUND", (0,1),(1,1), C_LGREY), ("LINEBELOW", (0,1),(-1,1),1.5, hc), ])) story.append(KeepTogether([section_badge(name, hc), inner, Spacer(1, 0.2*cm)])) story.append(PageBreak()) # PAGE 5: NON-PARAMETRIC TESTS story.append(Paragraph("Non-Parametric Tests - In Depth", h1_style)) story.append(HRFlowable(width="100%", thickness=2, color=C_GREEN, spaceAfter=6)) story.append(highlight_box( "<b>Use non-parametric tests when:</b> Data fails Shapiro-Wilk (p \u2264 0.05) " "\u2022 Ordinal scale data \u2022 Small samples (n < 10/group) " "\u2022 Severe outliers present \u2022 Extreme skewness", bg=C_LGREEN, border=C_GREEN)) story.append(Spacer(1, 0.3*cm)) rows_np = [ ["Mann-Whitney U", "Compare 2 independent groups", "Ordinal or continuous; non-normal", "Independent t-test", "U = n\u2081n\u2082 + n\u2081(n\u2081+1)/2 - R\u2081", "Two drug groups, skewed pain scores"], ["Wilcoxon Signed-Rank", "Compare 2 paired groups", "Continuous; non-normal differences", "Paired t-test", "W = \u03a3(signed ranks)", "Pre vs. post treatment, same patients"], ["Kruskal-Wallis H", "Compare 3+ independent groups", "Ordinal or continuous; non-normal", "One-Way ANOVA", "H = (12/N(N+1))\u03a3(R\u1d62\u00b2/n\u1d62) - 3(N+1)", "3 splint groups, small n"], ["Friedman test", "Compare 3+ repeated measures", "Ordinal or continuous; non-normal", "RM-ANOVA", "\u03c7\u00b2_r = 12/(Nk(k+1))\u03a3R_j\u00b2 - 3N(k+1)", "Same patients, 3 time points"], ["Chi-Square \u03c7\u00b2", "2 categorical variables association", "Nominal; expected \u22655/cell", "N/A (categorical)", "\u03c7\u00b2 = \u03a3(O-E)\u00b2/E", "Gender vs. disease presence"], ["Fisher's Exact", "2x2 table, small expected counts", "Nominal; any expected cell size", "Chi-square (small n)", "Hypergeometric prob.", "2x2 table with cells < 5"], ["McNemar test", "Paired proportions", "Nominal; matched/paired", "Paired t-test (binary)", "\u03c7\u00b2 = (b-c)\u00b2/(b+c)", "Same patients: pre vs. post binary outcome"], ["Spearman \u03c1", "Correlation", "Ordinal or non-normal continuous", "Pearson r", "\u03c1 = 1 - 6\u03a3d\u00b2/n(n\u00b2-1)", "Rank correlation: pain score vs. dose rank"], ["Dunn's test", "Post-hoc after Kruskal-Wallis", "Non-parametric pairwise", "Tukey HSD", "z = (R\u1d62 - R_j)/SE", "Which pairs differ after K-W test"], ] story.append(make_table( ["Test", "Purpose", "Data / Conditions", "Parametric Equivalent", "Formula", "Example"], rows_np, [2.8*cm, 3.2*cm, 3.0*cm, 3.5*cm, 3.5*cm, 3.5*cm], header_bg=C_GREEN, alt_bg=C_LGREEN)) story.append(PageBreak()) # PAGE 6: ASSUMPTION TESTS story.append(Paragraph("Testing Statistical Assumptions", h1_style)) story.append(HRFlowable(width="100%", thickness=2, color=C_PURPLE, spaceAfter=6)) story.append(Paragraph("1. Normality Tests", h2_style)) rows_norm = [ ["Shapiro-Wilk", "n < 50 (Best for small samples)", "p > 0.05 \u2192 Normal\np \u2264 0.05 \u2192 Non-normal", "Most powerful for small n;\nhighly recommended for biomedical/dental research"], ["Kolmogorov-Smirnov", "n \u2265 50 (Large samples)", "p > 0.05 \u2192 Normal", "Less powerful; Lilliefors correction often needed"], ["D'Agostino-Pearson", "n \u2265 20", "p > 0.05 \u2192 Normal", "Based on skewness + kurtosis; good for moderate samples"], ["Q-Q Plot (visual)", "Any n", "Points on diagonal \u2192 Normal;\nDeviations \u2192 Non-normal", "Always plot alongside numeric tests as visual confirmation"], ["Histogram (visual)", "Any n", "Bell shape \u2192 approximately normal", "Supplement to formal tests; not a standalone decision tool"], ] story.append(make_table( ["Test", "Best For", "Interpretation", "Notes"], rows_norm, [3.5*cm, 3.5*cm, 4.0*cm, 5.5*cm], header_bg=C_PURPLE, alt_bg=C_LPURPLE)) story.append(Spacer(1, 0.3*cm)) story.append(Paragraph("2. Homogeneity of Variances", h2_style)) rows_var = [ ["Levene's test", "2+ groups; does NOT require normality", "p > 0.05 \u2192 Variances homogeneous (ANOVA OK)\np \u2264 0.05 \u2192 Use Welch's ANOVA or Welch's t-test", "Preferred choice for most research"], ["Bartlett's test", "2+ groups; REQUIRES normality", "p > 0.05 \u2192 Variances homogeneous", "Very sensitive to non-normality; use Levene's instead if unsure"], ["Fligner-Killeen", "Non-parametric; any distribution", "p > 0.05 \u2192 Variances homogeneous", "Best when data clearly fails normality"], ] story.append(make_table( ["Test", "Conditions", "Interpretation", "Recommendation"], rows_var, [3.2*cm, 4.0*cm, 5.0*cm, 4.3*cm], header_bg=C_TEAL, alt_bg=C_LTEAL)) story.append(Spacer(1, 0.3*cm)) story.append(Paragraph("3. Mauchly's Test of Sphericity (RM-ANOVA only)", h2_style)) story.append(highlight_box( "<b>Sphericity</b> requires that variances of differences between all pairs of repeated conditions are equal. " "Required ONLY for Repeated Measures ANOVA. " "<b>If violated (p < 0.05):</b> Apply Greenhouse-Geisser or Huynh-Feldt correction to adjust degrees of freedom. " "<b>If not violated (p > 0.05):</b> Proceed with standard RM-ANOVA.", bg=C_LPURPLE, border=C_PURPLE)) story.append(Spacer(1, 0.3*cm)) story.append(Paragraph("4. Assumption Checklist Before Running Tests", h2_style)) rows_check = [ ["Independent t-test", "Normality (S-W)", "Levene's test", "N/A", "N/A", "\u2014"], ["Paired t-test", "Normality of diffs", "N/A", "N/A", "N/A", "\u2014"], ["One-Way ANOVA", "Normality (S-W)", "Levene's test", "N/A", "Independence", "\u2014"], ["RM-ANOVA", "Normality of diffs","N/A", "Mauchly's test", "Same subjects", "Greenhouse-Geisser if violated"], ["Two-Way ANOVA", "Normality", "Levene's test", "N/A", "Independence + n\u22655/cell", "\u2014"], ["Pearson r", "Normality (both)","N/A", "N/A", "Linearity check (scatter plot)", "\u2014"], ["Linear Regression", "Residuals normal","Homoscedasticity","N/A", "No multicollinearity", "VIF < 10"], ["Logistic Regression", "N/A", "N/A", "N/A", "No multicollinearity + linearity of log-odds", "\u2014"], ] story.append(make_table( ["Test", "Normality", "Variance Equality", "Sphericity", "Other", "Correction If Violated"], rows_check, [3.2*cm, 2.5*cm, 2.8*cm, 2.2*cm, 3.5*cm, 3.3*cm], header_bg=C_NAVY, alt_bg=C_SKY)) story.append(PageBreak()) # PAGE 7: EFFECT SIZE + POWER story.append(Paragraph("Effect Size and Statistical Power", h1_style)) story.append(HRFlowable(width="100%", thickness=2, color=C_ORANGE, spaceAfter=6)) story.append(highlight_box( "<b>p < 0.05 does NOT mean clinically meaningful.</b> With large samples even tiny, irrelevant differences " "become statistically significant. Always report effect size alongside p-values.", bg=C_LORANGE, border=C_ORANGE)) story.append(Spacer(1, 0.3*cm)) story.append(Paragraph("Effect Size Measures by Test", h2_style)) rows_es = [ ["Cohen's d", "Independent t-test, Paired t-test", "d = (\u03bc\u2081-\u03bc\u2082)/SD_pooled", "Small: 0.2 | Medium: 0.5 | Large: \u22650.8"], ["Eta-squared \u03b7\u00b2", "One-Way ANOVA", "\u03b7\u00b2 = SS_between / SS_total", "Small: 0.01 | Medium: 0.06 | Large: \u22650.14"], ["Partial \u03b7\u00b2", "Multi-factor ANOVA, ANCOVA", "\u03b7\u00b2p = SS_effect / (SS_effect + SS_error)", "Same thresholds as \u03b7\u00b2; preferred for multi-factor designs"], ["Omega-squared \u03c9\u00b2","ANOVA (less biased)", "\u03c9\u00b2 = (SS_B - df_B\u00b7MS_W)/(SS_T + MS_W)", "Preferred over \u03b7\u00b2 for small samples; less inflated estimate"], ["Pearson r", "Correlation tests", "r is itself the effect size", "Small: 0.1 | Medium: 0.3 | Large: \u22650.5"], ["r (from Mann-Whitney)","Mann-Whitney, Wilcoxon", "r = Z / \u221aN", "Small: 0.1 | Medium: 0.3 | Large: \u22650.5"], ["Cram\u00e9r's V", "Chi-Square", "V = \u221a(\u03c7\u00b2/N\u00b7min(r-1,c-1))", "Small: 0.1 | Medium: 0.3 | Large: \u22650.5"], ["Odds Ratio (OR)", "Logistic Regression, 2\u00d72 tables", "OR = (a\u00b7d)/(b\u00b7c)", "OR=1: no effect | >1: increased odds | <1: decreased odds"], ["Hazard Ratio (HR)","Cox Regression", "HR = h\u2081(t)/h\u2080(t)", "HR=1: no difference | HR>1: higher hazard in group 1"], ] story.append(make_table( ["Measure", "Used With", "Formula", "Interpretation (Cohen's convention)"], rows_es, [3.0*cm, 4.0*cm, 5.5*cm, 5.0*cm], header_bg=C_ORANGE, alt_bg=C_LORANGE)) story.append(Spacer(1, 0.35*cm)) story.append(Paragraph("Statistical Power and Sample Size", h2_style)) rows_power = [ ["\u03b1 (Type I Error)", "Rejecting H\u2080 when it is true (false positive)", "0.05 (standard)", "\u2191 \u03b1 \u2192 \u2191 Power but \u2191 false positives"], ["\u03b2 (Type II Error)", "Failing to reject false H\u2080 (false negative)", "0.20 (acceptable)", "\u2193 \u03b2 \u2192 \u2191 Power"], ["Power (1-\u03b2)", "Probability of detecting a true effect", "\u226580% desired", "Increase with larger n, larger effect, or higher \u03b1"], ["Sample size n", "Larger n \u2192 smaller SE \u2192 more power", "Via a priori power analysis", "Use G*Power / R pwr package"], ["Effect size", "Larger true effect \u2192 easier to detect with fewer subjects", "Estimate from literature/pilot data", "Smaller effects require larger n"], ] story.append(make_table( ["Parameter", "Definition", "Typical Value", "Impact on Study"], rows_power, [3.0*cm, 5.0*cm, 3.5*cm, 5.0*cm], header_bg=C_RED, alt_bg=C_LRED)) story.append(PageBreak()) # PAGE 8: CHEAT SHEET story.append(Paragraph("Quick-Reference Cheat Sheet", h1_style)) story.append(HRFlowable(width="100%", thickness=2, color=C_NAVY, spaceAfter=6)) scenarios = [ (C_BLUE, C_SKY, "2 Groups\nIndependent\nNormal", "Independent t-test", "Mann-Whitney U"), (C_BLUE, C_SKY, "2 Groups\nPaired/Matched\nNormal", "Paired t-test", "Wilcoxon Signed-Rank"), (C_GREEN, C_LGREEN, "3+ Groups\nIndependent\nNormal", "One-Way ANOVA", "Kruskal-Wallis H"), (C_GREEN, C_LGREEN, "3+ Conditions\nSame Subjects\nNormal","Repeated Measures\nANOVA", "Friedman test"), (C_ORANGE,C_LORANGE,"2 Factors\nIndependent\nNormal", "Two-Way ANOVA", "Scheirer-Ray-Hare"), (C_TEAL, C_LTEAL, "Correlation\nContinuous\nNormal", "Pearson r", "Spearman \u03c1"), (C_TEAL, C_LTEAL, "Correlation\nOrdinal or\nNon-Normal", "Spearman \u03c1", "Kendall \u03c4"), (C_PURPLE,C_LPURPLE,"Categorical\nAssociation\nn\u22655/cell","Chi-Square \u03c7\u00b2", "Fisher's Exact"), (C_PURPLE,C_LPURPLE,"Paired\nProportions", "McNemar test", "McNemar test"), (C_RED, C_LRED, "Predict\nContinuous\nOutcome", "Linear Regression", "Robust Regression"), (C_RED, C_LRED, "Predict Binary\nOutcome", "Logistic Regression", "Logistic Regression"), (C_GOLD, C_LGOLD, "Time-to-Event\nSurvival Analysis", "Kaplan-Meier\n+ Log-rank", "Cox Regression"), ] cell_w = (W - 3*cm) / 3 def make_card(scenario, param, nonparam, hc, bc): inner = Table([ [Paragraph(scenario, S("Normal", fontSize=8, textColor=C_GREY, fontName="Helvetica", leading=10, alignment=TA_CENTER))], [Paragraph("Parametric:", S("Normal", fontSize=7, textColor=hc, fontName="Helvetica-Bold", alignment=TA_CENTER))], [Paragraph(param, S("Normal", fontSize=9, textColor=hc, fontName="Helvetica-Bold", leading=11, alignment=TA_CENTER))], [Paragraph("Non-Parametric:", S("Normal", fontSize=7, textColor=C_GREY, fontName="Helvetica-Oblique", alignment=TA_CENTER))], [Paragraph(nonparam, S("Normal", fontSize=8.5, textColor=C_GREY, fontName="Helvetica", leading=10, alignment=TA_CENTER))], ], colWidths=[cell_w - 0.4*cm]) inner.setStyle(TableStyle([ ("BACKGROUND", (0,0),(-1,-1), bc), ("LINEABOVE", (0,0),(-1,0), 3, hc), ("BOX", (0,0),(-1,-1), 0.5, hc), ("ALIGN", (0,0),(-1,-1),"CENTER"), ("VALIGN", (0,0),(-1,-1),"MIDDLE"), ("TOPPADDING", (0,0),(-1,-1),3), ("BOTTOMPADDING",(0,0),(-1,-1),3), ])) return inner grid_rows = [] row_cards = [] for i, (hc, bc, sc, p, np_) in enumerate(scenarios): row_cards.append(make_card(sc, p, np_, hc, bc)) if len(row_cards) == 3: grid_rows.append(row_cards[:]) row_cards = [] if row_cards: while len(row_cards) < 3: row_cards.append(Spacer(cell_w, 3.0*cm)) grid_rows.append(row_cards) grid = Table(grid_rows, colWidths=[cell_w]*3, rowHeights=[3.0*cm]*len(grid_rows)) grid.setStyle(TableStyle([ ("ALIGN", (0,0),(-1,-1),"CENTER"), ("VALIGN", (0,0),(-1,-1),"MIDDLE"), ("LEFTPADDING", (0,0),(-1,-1),4), ("RIGHTPADDING", (0,0),(-1,-1),4), ("TOPPADDING", (0,0),(-1,-1),4), ("BOTTOMPADDING", (0,0),(-1,-1),4), ])) story.append(grid) story.append(Spacer(1, 0.3*cm)) story.append(Paragraph( "<b>Remember:</b> p-value tells you IF an effect exists. " "Effect size tells you HOW LARGE it is. Always report both. " "Significance without effect size is incomplete.", note_style)) # BUILD doc.build(story, onFirstPage=on_page, onLaterPages=on_page) print("SUCCESS:", OUTPUT) PYEOF
python3 << 'PYEOF' from reportlab.lib.pagesizes import A4 from reportlab.lib import colors from reportlab.lib.units import cm from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, PageBreak, HRFlowable, KeepTogether) from reportlab.platypus.flowables import Flowable W, H = A4 OUTPUT = "/tmp/workspace/stat-guide/Statistical_Test_Decision_Guide.pdf" C_NAVY=colors.HexColor("#1A237E"); C_BLUE=colors.HexColor("#1565C0") C_SKY=colors.HexColor("#E3F2FD"); C_GREEN=colors.HexColor("#2E7D32") C_LGREEN=colors.HexColor("#E8F5E9"); C_ORANGE=colors.HexColor("#E65100") C_LORANGE=colors.HexColor("#FFF3E0"); C_PURPLE=colors.HexColor("#4A148C") C_LPURPLE=colors.HexColor("#F3E5F5"); C_RED=colors.HexColor("#B71C1C") C_LRED=colors.HexColor("#FFEBEE"); C_GOLD=colors.HexColor("#F57F17") C_LGOLD=colors.HexColor("#FFFDE7"); C_TEAL=colors.HexColor("#00695C") C_LTEAL=colors.HexColor("#E0F2F1"); C_GREY=colors.HexColor("#37474F") C_LGREY=colors.HexColor("#ECEFF1"); C_WHITE=colors.white styles = getSampleStyleSheet() def S(name,**kw): base=styles.get(name,styles["Normal"]) return ParagraphStyle(name+str(id(kw)),parent=base,**kw) h1=S("Heading1",fontSize=15,textColor=C_NAVY,fontName="Helvetica-Bold",spaceBefore=10,spaceAfter=4,leading=19) h2=S("Heading2",fontSize=11,textColor=C_BLUE,fontName="Helvetica-Bold",spaceBefore=7,spaceAfter=3,leading=15) body=S("Normal",fontSize=9.5,textColor=C_GREY,fontName="Helvetica",leading=14,spaceAfter=3,alignment=TA_JUSTIFY) note=S("Normal",fontSize=8.5,textColor=C_GREY,fontName="Helvetica-Oblique",leading=12,alignment=TA_CENTER) CH=S("Normal",fontSize=9,textColor=C_WHITE,fontName="Helvetica-Bold",alignment=TA_CENTER,leading=12) CL=S("Normal",fontSize=8.5,textColor=C_GREY,fontName="Helvetica",alignment=TA_LEFT,leading=12) CC=S("Normal",fontSize=8.5,textColor=C_GREY,fontName="Helvetica",alignment=TA_CENTER,leading=12) def on_page(canvas,doc): canvas.saveState() canvas.setFillColor(C_NAVY); canvas.rect(0,0,W,1.0*cm,fill=1,stroke=0) canvas.setFillColor(C_WHITE); canvas.setFont("Helvetica",8) canvas.drawCentredString(W/2,0.35*cm,"Statistical Test Decision Guide | PhD / Research Reference") canvas.drawRightString(W-1.5*cm,0.35*cm,f"Page {doc.page}") if doc.page>1: canvas.setFillColor(C_NAVY); canvas.rect(0,H-0.55*cm,W,0.55*cm,fill=1,stroke=0) canvas.setFillColor(C_WHITE); canvas.setFont("Helvetica-Bold",8) canvas.drawCentredString(W/2,H-0.38*cm,"STATISTICAL TEST DECISION GUIDE") canvas.restoreState() class CoverFlowable(Flowable): def __init__(self,w,h): Flowable.__init__(self); self.width=w; self.height=h def wrap(self,aw,ah): return self.width,self.height def draw(self): c=self.canv c.setFillColor(C_NAVY); c.rect(0,0,self.width,self.height,fill=1,stroke=0) c.setFillColor(C_BLUE); c.rect(0,self.height*0.36,self.width,self.height*0.64,fill=1,stroke=0) c.setFillColor(colors.HexColor("#283593")) c.circle(self.width*0.84,self.height*0.74,80,fill=1,stroke=0) c.setFillColor(colors.HexColor("#1A237E")) c.circle(self.width*0.08,self.height*0.84,50,fill=1,stroke=0) c.setFillColor(C_WHITE); c.setFont("Helvetica-Bold",28) c.drawCentredString(self.width/2,self.height*0.59,"Statistical Test") c.drawCentredString(self.width/2,self.height*0.59-36,"Decision Guide") c.setFont("Helvetica",12); c.setFillColor(colors.HexColor("#BBDEFB")) c.drawCentredString(self.width/2,self.height*0.59-65,"A Complete Reference for Research & PhD Scholars") c.setStrokeColor(C_GOLD); c.setLineWidth(3) c.line(self.width*0.2,self.height*0.59-79,self.width*0.8,self.height*0.59-79) c.setFillColor(colors.HexColor("#0D47A1")) c.roundRect(self.width*0.1,self.height*0.05,self.width*0.8,self.height*0.26,10,fill=1,stroke=0) c.setFillColor(C_GOLD); c.setFont("Helvetica-Bold",10) topics=["Parametric & Non-Parametric Tests", "Comparative, Correlation & Regression Tests", "Normality & Variance Assumption Checks", "Effect Size & Statistical Power Guide", "Full Decision Tables & Quick Cheat Sheet"] base_y=self.height*0.05+self.height*0.26-22 for i,t in enumerate(topics): c.drawCentredString(self.width/2,base_y-i*20,"\u2022 "+t) class StepBar(Flowable): def __init__(self,steps,w=None): Flowable.__init__(self); self.steps=steps self.width=w or (W-3*cm); self.height=1.9*cm def wrap(self,aw,ah): return self.width,self.height def draw(self): c=self.canv; n=len(self.steps); seg=self.width/n bw=seg-8 for i,(lbl,bg) in enumerate(self.steps): x=i*seg+2 c.setFillColor(bg); c.roundRect(x,0.12*cm,bw,1.55*cm,6,fill=1,stroke=0) c.setFillColor(C_WHITE); c.setFont("Helvetica-Bold",7.5) lines=lbl.split("\n"); mid=0.12*cm+0.77*cm sy=mid+(len(lines)-1)*5.5 for j,ln in enumerate(lines): c.drawCentredString(x+bw/2,sy-j*11,ln) if i<n-1: ax=x+bw+2; ay=0.12*cm+0.77*cm c.setFillColor(C_GREY) c.polygon([ax,ay+4,ax,ay-4,ax+5,ay],fill=1,stroke=0) def mktable(headers,rows,cw,hbg=C_NAVY,alt=C_SKY): data=[[Paragraph(h,CH) for h in headers]] for row in rows: data.append([Paragraph(str(cell),CC if j>0 else CL) for j,cell in enumerate(row)]) t=Table(data,colWidths=cw,repeatRows=1) t.setStyle(TableStyle([ ("BACKGROUND",(0,0),(-1,0),hbg),("TEXTCOLOR",(0,0),(-1,0),C_WHITE), ("FONTNAME",(0,0),(-1,0),"Helvetica-Bold"),("FONTSIZE",(0,0),(-1,0),9), ("ALIGN",(0,0),(-1,-1),"CENTER"),("VALIGN",(0,0),(-1,-1),"MIDDLE"), ("ROWBACKGROUNDS",(0,1),(-1,-1),[C_WHITE,alt]), ("GRID",(0,0),(-1,-1),0.4,colors.HexColor("#B0BEC5")), ("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4), ("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5), ("LINEBELOW",(0,0),(-1,0),1.5,C_WHITE), ])); return t def hbox(text,bg=C_SKY,border=C_BLUE): t=Table([[Paragraph(text,body)]],colWidths=[W-3*cm]) t.setStyle(TableStyle([ ("BACKGROUND",(0,0),(-1,-1),bg),("BOX",(0,0),(-1,-1),1.5,border), ("LEFTPADDING",(0,0),(-1,-1),10),("RIGHTPADDING",(0,0),(-1,-1),10), ("TOPPADDING",(0,0),(-1,-1),7),("BOTTOMPADDING",(0,0),(-1,-1),7), ])); return t def badge(text,bg): t=Table([[Paragraph(f"<b>{text}</b>",S("Normal",fontSize=10,textColor=C_WHITE, fontName="Helvetica-Bold",alignment=TA_LEFT,leading=14))]], colWidths=[W-3*cm]) t.setStyle(TableStyle([ ("BACKGROUND",(0,0),(-1,-1),bg), ("LEFTPADDING",(0,0),(-1,-1),10), ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5), ])); return t # ── Build ───────────────────────────────────────────────────────────────────── doc=SimpleDocTemplate(OUTPUT,pagesize=A4, leftMargin=1.5*cm,rightMargin=1.5*cm, topMargin=1.8*cm,bottomMargin=1.4*cm, title="Statistical Test Decision Guide",author="Research Reference") # Cover is drawn as canvas commands on page 1 via onFirstPage def on_cover(canvas,doc): canvas.saveState() cw,ch=W-3*cm,H-3*cm ox,oy=1.5*cm,1.4*cm canvas.setFillColor(C_NAVY); canvas.rect(0,0,W,H,fill=1,stroke=0) canvas.setFillColor(C_BLUE); canvas.rect(ox,oy+ch*0.36,cw,ch*0.64,fill=1,stroke=0) canvas.setFillColor(colors.HexColor("#283593")) canvas.circle(ox+cw*0.84,oy+ch*0.74,80,fill=1,stroke=0) canvas.setFillColor(C_WHITE); canvas.setFont("Helvetica-Bold",28) canvas.drawCentredString(W/2,oy+ch*0.59,"Statistical Test") canvas.drawCentredString(W/2,oy+ch*0.59-36,"Decision Guide") canvas.setFont("Helvetica",12); canvas.setFillColor(colors.HexColor("#BBDEFB")) canvas.drawCentredString(W/2,oy+ch*0.59-66,"A Complete Reference for Research & PhD Scholars") canvas.setStrokeColor(C_GOLD); canvas.setLineWidth(3) canvas.line(W*0.2,oy+ch*0.59-80,W*0.8,oy+ch*0.59-80) canvas.setFillColor(colors.HexColor("#0D47A1")) canvas.roundRect(ox+cw*0.1,oy+ch*0.05,cw*0.8,ch*0.26,10,fill=1,stroke=0) canvas.setFillColor(C_GOLD); canvas.setFont("Helvetica-Bold",10) tpcs=["Parametric & Non-Parametric Tests", "Comparative, Correlation & Regression Tests", "Normality & Variance Assumption Checks", "Effect Size & Statistical Power Guide", "Full Decision Tables & Quick Cheat Sheet"] base_y=oy+ch*0.05+ch*0.26-22 for i,t in enumerate(tpcs): canvas.drawCentredString(W/2,base_y-i*20,"\u2022 "+t) # Footer canvas.setFillColor(C_NAVY); canvas.rect(0,0,W,1.0*cm,fill=1,stroke=0) canvas.setFillColor(C_WHITE); canvas.setFont("Helvetica",8) canvas.drawCentredString(W/2,0.35*cm,"Statistical Test Decision Guide | PhD / Research Reference") canvas.drawRightString(W-1.5*cm,0.35*cm,f"Page {doc.page}") canvas.restoreState() story=[] story.append(PageBreak()) # triggers page 1 cover via onFirstPage # PAGE 2: DECISION FRAMEWORK story.append(Paragraph("How to Choose the Right Statistical Test",h1)) story.append(HRFlowable(width="100%",thickness=2,color=C_BLUE,spaceAfter=5)) story.append(Paragraph("Choosing the wrong statistical test is one of the most common methodological errors. " "Follow these six steps systematically before selecting any test.",body)) story.append(Spacer(1,0.2*cm)) story.append(Paragraph("Six-Step Decision Framework",h2)) steps=[("STEP 1\nResearch\nQuestion",C_BLUE),("STEP 2\nVariable\nType",C_GREEN), ("STEP 3\nNo. of\nGroups",C_ORANGE),("STEP 4\nIndependent\nor Paired?",C_PURPLE), ("STEP 5\nNormal\nDistribution?",C_TEAL),("STEP 6\nSelect\nTest \u2713",C_RED)] story.append(StepBar(steps,w=W-3*cm)) story.append(Spacer(1,0.3*cm)) story.append(Paragraph("Step 2: Types of Variables",h2)) story.append(mktable( ["Data Type","Definition","Example","Typical Test"], [["Nominal (Categorical)","Named categories; no order","Gender, Blood group","Chi-square, Fisher's Exact"], ["Ordinal","Ordered categories; unequal gaps","Pain scale 1-10, Grade","Mann-Whitney, Kruskal-Wallis"], ["Continuous - Interval","Equal gaps; no true zero","Temperature (\u00b0C), IQ","t-test, ANOVA, Pearson r"], ["Continuous - Ratio","Equal gaps + true zero","Weight, Flexural strength (N)","t-test, ANOVA, Pearson r"], ["Dichotomous","Exactly 2 values","Yes/No, Fractured/Intact","Chi-square, Z-test, Logistic Reg."], ["Time-to-event","Duration until event","Survival time, Implant failure","Kaplan-Meier, Cox Regression"]], [3.2*cm,3.8*cm,4.5*cm,5.0*cm])) story.append(Spacer(1,0.25*cm)) story.append(hbox("<b>Golden Rule:</b> Parametric tests require: (1) Continuous data, (2) Approximately normal distribution, " "(3) Homogeneous variances. If ANY condition fails, switch to the non-parametric equivalent.",C_LGOLD,C_GOLD)) story.append(PageBreak()) # PAGE 3: MASTER TABLE story.append(Paragraph("Master Reference: Statistical Test Selection",h1)) story.append(HRFlowable(width="100%",thickness=2,color=C_BLUE,spaceAfter=5)) story.append(mktable( ["Research Goal","Data Type","Design","Distribution","Parametric Test","Non-Parametric Test"], [["Compare 2 groups","Continuous","Independent","Normal","Independent t-test","Mann-Whitney U"], ["Compare 2 groups","Continuous","Paired/Matched","Normal","Paired t-test","Wilcoxon Signed-Rank"], ["Compare 2 groups","Continuous","Independent","Normal + unequal var","Welch's t-test","Mann-Whitney U"], ["Compare 3+ groups","Continuous","Independent","Normal","One-Way ANOVA","Kruskal-Wallis H"], ["Compare 3+ groups","Continuous","Same subject\n(repeated)","Normal","Repeated Measures ANOVA","Friedman test"], ["Compare 3+ groups","Continuous","Independent","Normal + 2 factors","Two-Way ANOVA","Scheirer-Ray-Hare"], ["Post-hoc (ANOVA)","Continuous","Independent","Normal","Tukey HSD / Bonferroni","Dunn's test"], ["Post-hoc (RM-ANOVA)","Continuous","Paired","Normal","Bonferroni / Sidak","Wilcoxon + Bonferroni"], ["Correlation","Continuous","\u2014","Normal + linear","Pearson r","Spearman \u03c1"], ["Correlation","Ordinal","\u2014","Any","Spearman \u03c1","Kendall \u03c4"], ["Compare proportions","Nominal","Independent","n\u22655/cell","Chi-Square \u03c7\u00b2","Fisher's Exact"], ["Compare proportions","Nominal","Paired","Any","McNemar test","McNemar test"], ["Predict outcome","Continuous","\u2014","Normal residuals","Linear Regression","Robust Regression"], ["Predict outcome","Binary DV","\u2014","Any","Logistic Regression","Logistic Regression"], ["Time-to-event","Continuous","Independent","Any","Kaplan-Meier + Log-rank","Cox Proportional Hazards"], ["Agreement / Reliability","Continuous","Same rater x2","Any","ICC","Cohen's Kappa / Weighted Kappa"], ["Test normality","Continuous","\u2014","\u2014","Shapiro-Wilk (n<50)","K-S test (n\u226550)"], ["Test equal variances","Continuous","2+ groups","\u2014","Levene's test","Bartlett's test"]], [2.8*cm,2.3*cm,2.6*cm,2.5*cm,4.0*cm,3.3*cm])) story.append(PageBreak()) # PAGE 4: PARAMETRIC IN DEPTH story.append(Paragraph("Parametric Tests - In Depth",h1)) story.append(HRFlowable(width="100%",thickness=2,color=C_BLUE,spaceAfter=5)) pdata=[ ("Independent Samples t-test",C_BLUE,C_SKY, "Compares means of <b>two independent groups</b>.", "Continuous DV \u2022 2 independent groups \u2022 Normal distribution \u2022 Equal variances (Levene's p>0.05)", "t = (x\u0305\u2081 - x\u0305\u2082) / SE_diff", "Conventional vs. Milled splint flexural strength"), ("Paired t-test",C_GREEN,C_LGREEN, "Compares means of <b>two related measurements</b> from the <b>same subjects</b>.", "Continuous DV \u2022 Same subjects measured twice \u2022 Differences normally distributed", "t = d\u0305 / (s_d / \u221an)", "Surface roughness before vs. after polishing: same specimens"), ("One-Way ANOVA",C_ORANGE,C_LORANGE, "Compares means of <b>3+ independent groups</b>. Follow with post-hoc to find which pairs differ.", "Continuous DV \u2022 3+ independent groups \u2022 Normal dist. \u2022 Equal variances \u2022 Independence", "F = MS_between / MS_within", "Conventional vs. Milled vs. 3D Printed occlusal splints"), ("Repeated Measures ANOVA",C_PURPLE,C_LPURPLE, "Compares means across <b>3+ time points or conditions on the SAME subjects</b>.", "Continuous DV \u2022 Same subjects in all conditions \u2022 Sphericity (Mauchly's test) \u2022 Normal diffs", "F = MS_between-conditions / MS_error", "Same specimen tested at baseline, 1 month, 3 months (DIFFERENT from your study)"), ("Two-Way ANOVA",C_TEAL,C_LTEAL, "Examines effects of <b>two independent factors</b> and their interaction.", "Continuous DV \u2022 2 categorical IVs \u2022 Normal dist. \u2022 Equal variances \u2022 \u22655 per cell", "F_A = MS_A/MS_err | F_B = MS_B/MS_err | F_AB = MS_AB/MS_err", "Effect of fabrication method AND storage medium on flexural strength"), ] for name,hc,bc,desc,assump,formula,example in pdata: inner=Table([ [Paragraph(desc,body), Paragraph(f"<b>Formula:</b> <font name='Courier'>{formula}</font><br/><b>Example:</b> {example}",body)], [Paragraph(f"<b>Assumptions:</b> {assump}", S("Normal",fontSize=8.5,textColor=C_GREY,fontName="Helvetica",leading=13)),""] ],colWidths=[(W-3*cm)*0.42,(W-3*cm)*0.58], style=TableStyle([ ("BACKGROUND",(0,0),(-1,-1),bc),("VALIGN",(0,0),(-1,-1),"TOP"), ("LEFTPADDING",(0,0),(-1,-1),8),("TOPPADDING",(0,0),(-1,-1),5), ("BOTTOMPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),8), ("SPAN",(0,1),(1,1)),("BACKGROUND",(0,1),(1,1),C_LGREY), ("LINEBELOW",(0,1),(-1,1),1.5,hc), ])) story.append(KeepTogether([badge(name,hc),inner,Spacer(1,0.2*cm)])) story.append(PageBreak()) # PAGE 5: NON-PARAMETRIC story.append(Paragraph("Non-Parametric Tests - In Depth",h1)) story.append(HRFlowable(width="100%",thickness=2,color=C_GREEN,spaceAfter=5)) story.append(hbox("<b>Use non-parametric tests when:</b> Shapiro-Wilk p \u2264 0.05 \u2022 Ordinal data " "\u2022 Small samples (n < 10/group) \u2022 Outliers present \u2022 Severe skewness",C_LGREEN,C_GREEN)) story.append(Spacer(1,0.25*cm)) story.append(mktable( ["Test","Purpose","Data/Conditions","Parametric Equiv.","Formula","Example"], [["Mann-Whitney U","Compare 2 independent groups","Ordinal or continuous; non-normal", "Independent t-test","U = n\u2081n\u2082 + n\u2081(n\u2081+1)/2 - R\u2081","Two drug groups, skewed pain scores"], ["Wilcoxon Signed-Rank","Compare 2 paired groups","Continuous; non-normal differences", "Paired t-test","W = \u03a3(signed ranks)","Pre vs. post treatment, same patients"], ["Kruskal-Wallis H","Compare 3+ independent groups","Ordinal or continuous; non-normal", "One-Way ANOVA","H = (12/N(N+1))\u03a3(R\u1d62\u00b2/n\u1d62) - 3(N+1)","3 splint groups, small n"], ["Friedman test","Compare 3+ repeated measures","Ordinal or continuous; non-normal", "RM-ANOVA","\u03c7\u00b2_r = 12/(Nk(k+1))\u03a3R_j\u00b2 - 3N(k+1)","Same patients, 3 time points"], ["Chi-Square \u03c7\u00b2","2 categorical variables","Nominal; expected \u22655/cell", "N/A","\u03c7\u00b2 = \u03a3(O-E)\u00b2/E","Gender vs. disease presence"], ["Fisher's Exact","Small expected counts","Nominal; any cell size", "Chi-square (small n)","Hypergeometric prob.","2\u00d72 table with cells < 5"], ["McNemar test","Paired proportions","Nominal; matched pairs", "Paired t-test (binary)","\u03c7\u00b2 = (b-c)\u00b2/(b+c)","Pre vs. post binary outcome, same patients"], ["Spearman \u03c1","Correlation","Ordinal or non-normal", "Pearson r","\u03c1 = 1 - 6\u03a3d\u00b2/n(n\u00b2-1)","Pain score rank vs. dose rank"], ["Dunn's test","Post-hoc after K-W","Non-parametric pairwise", "Tukey HSD","z = (R\u1d62 - R_j)/SE","Which pairs differ after Kruskal-Wallis"]], [2.8*cm,3.2*cm,2.8*cm,3.0*cm,3.5*cm,3.2*cm],C_GREEN,C_LGREEN)) story.append(PageBreak()) # PAGE 6: ASSUMPTION TESTS story.append(Paragraph("Testing Statistical Assumptions",h1)) story.append(HRFlowable(width="100%",thickness=2,color=C_PURPLE,spaceAfter=5)) story.append(Paragraph("1. Normality Tests",h2)) story.append(mktable( ["Test","Best For","Interpretation","Notes"], [["Shapiro-Wilk","n < 50 (best for small samples)","p > 0.05 \u2192 Normal\np \u2264 0.05 \u2192 Non-normal", "Most powerful for small n; strongly recommended for biomedical research"], ["Kolmogorov-Smirnov","n \u2265 50 (large samples)","p > 0.05 \u2192 Normal", "Less powerful; Lilliefors correction often needed"], ["D'Agostino-Pearson","n \u2265 20","p > 0.05 \u2192 Normal", "Uses skewness + kurtosis; good for moderate samples"], ["Q-Q Plot (visual)","Any n","Points on diagonal \u2192 Normal; deviations \u2192 Non-normal", "Always plot alongside numeric tests"], ["Histogram (visual)","Any n","Bell shape \u2192 approximately normal", "Supplement only; not a standalone formal test"]], [3.5*cm,3.5*cm,4.0*cm,5.5*cm],C_PURPLE,C_LPURPLE)) story.append(Spacer(1,0.3*cm)) story.append(Paragraph("2. Homogeneity of Variances",h2)) story.append(mktable( ["Test","Conditions","Interpretation","Recommendation"], [["Levene's test","2+ groups; does NOT require normality", "p > 0.05 \u2192 Equal variances (ANOVA OK)\np \u2264 0.05 \u2192 Use Welch's ANOVA","Preferred choice for most research"], ["Bartlett's test","2+ groups; REQUIRES normal data", "p > 0.05 \u2192 Equal variances","Avoid if normality is uncertain"], ["Fligner-Killeen","Non-parametric; any distribution", "p > 0.05 \u2192 Equal variances","Use when data clearly fails normality"]], [3.2*cm,4.0*cm,4.8*cm,4.5*cm],C_TEAL,C_LTEAL)) story.append(Spacer(1,0.3*cm)) story.append(Paragraph("3. Mauchly's Test of Sphericity (RM-ANOVA only)",h2)) story.append(hbox("<b>Sphericity:</b> Variances of differences between all pairs of repeated conditions must be equal. " "Required for RM-ANOVA only. <b>If violated (p < 0.05):</b> Apply Greenhouse-Geisser or Huynh-Feldt correction. " "<b>If met (p > 0.05):</b> Proceed normally.",C_LPURPLE,C_PURPLE)) story.append(Spacer(1,0.3*cm)) story.append(Paragraph("4. Assumption Checklist Summary",h2)) story.append(mktable( ["Test","Normality","Equal Variances","Sphericity","Other"], [["Independent t-test","Yes (S-W)","Yes (Levene's)","N/A","Independence"], ["Paired t-test","Yes (diffs)","N/A","N/A","Matched pairs"], ["One-Way ANOVA","Yes (S-W)","Yes (Levene's)","N/A","Independence"], ["RM-ANOVA","Yes (diffs)","N/A","Yes (Mauchly's)","Same subjects in all conditions"], ["Two-Way ANOVA","Yes","Yes (Levene's)","N/A","Independence + \u22655/cell"], ["Pearson r","Yes (both vars)","N/A","N/A","Linear relationship (scatterplot)"], ["Linear Regression","Yes (residuals)","Homoscedasticity","N/A","No multicollinearity (VIF<10)"], ["Logistic Regression","N/A","N/A","N/A","No multicollinearity + linearity of log-odds"]], [3.2*cm,2.6*cm,2.8*cm,2.2*cm,5.7*cm])) story.append(PageBreak()) # PAGE 7: EFFECT SIZE + POWER story.append(Paragraph("Effect Size and Statistical Power",h1)) story.append(HRFlowable(width="100%",thickness=2,color=C_ORANGE,spaceAfter=5)) story.append(hbox("<b>p < 0.05 does NOT equal clinical importance.</b> With large samples, trivial differences become " "significant. Always report effect size alongside p-values to communicate the magnitude of the finding.", C_LORANGE,C_ORANGE)) story.append(Spacer(1,0.25*cm)) story.append(Paragraph("Effect Size Measures",h2)) story.append(mktable( ["Measure","Used With","Formula","Cohen's Convention"], [["Cohen's d","Independent t-test, Paired t-test","d = (\u03bc\u2081-\u03bc\u2082)/SD_pooled", "Small: 0.2 | Medium: 0.5 | Large: \u22650.8"], ["Eta-squared \u03b7\u00b2","One-Way ANOVA","\u03b7\u00b2 = SS_between / SS_total", "Small: 0.01 | Medium: 0.06 | Large: \u22650.14"], ["Partial \u03b7\u00b2","Multi-factor ANOVA, ANCOVA","\u03b7\u00b2p = SS_effect / (SS_effect + SS_error)", "Same thresholds; preferred for multi-factor designs"], ["Omega-squared \u03c9\u00b2","ANOVA (less biased estimate)","\u03c9\u00b2 = (SS_B - df_B\u00b7MS_W)/(SS_T + MS_W)", "Preferred over \u03b7\u00b2 for small samples"], ["Pearson r","Correlation tests","r is itself an effect size", "Small: 0.1 | Medium: 0.3 | Large: \u22650.5"], ["r (from Z-score)","Mann-Whitney, Wilcoxon","r = Z / \u221aN", "Small: 0.1 | Medium: 0.3 | Large: \u22650.5"], ["Cram\u00e9r's V","Chi-Square","V = \u221a(\u03c7\u00b2/N\u00b7min(r-1,c-1))", "Small: 0.1 | Medium: 0.3 | Large: \u22650.5"], ["Odds Ratio (OR)","Logistic Regression, 2\u00d72","OR = (a\u00b7d)/(b\u00b7c)", "OR=1: no effect | >1: increased odds | <1: decreased odds"], ["Hazard Ratio (HR)","Cox Regression","HR = h\u2081(t)/h\u2080(t)", "HR=1: no difference | HR>1: higher hazard in group 1"]], [3.0*cm,3.8*cm,5.5*cm,5.2*cm],C_ORANGE,C_LORANGE)) story.append(Spacer(1,0.3*cm)) story.append(Paragraph("Statistical Power",h2)) story.append(mktable( ["Parameter","Definition","Typical Value","Impact"], [["\u03b1 (Type I Error)","Reject H\u2080 when true (false positive)","0.05 (standard)","\u2191 \u03b1 \u2192 \u2191 Power but \u2191 false positives"], ["\u03b2 (Type II Error)","Fail to reject false H\u2080 (false negative)","0.20 acceptable","\u2193 \u03b2 \u2192 \u2191 Power"], ["Power (1-\u03b2)","Probability of detecting a true effect","\u226580% desired","Increase with larger n"], ["Sample size n","Larger n \u2192 smaller SE \u2192 more power","Via a priori power analysis","Use G*Power or R pwr package"], ["Effect size","Larger effect \u2192 detected with fewer subjects","From literature/pilot data","Smaller effects need larger n"]], [3.0*cm,5.0*cm,3.5*cm,5.0*cm],C_RED,C_LRED)) story.append(PageBreak()) # PAGE 8: CHEAT SHEET story.append(Paragraph("Quick-Reference Cheat Sheet",h1)) story.append(HRFlowable(width="100%",thickness=2,color=C_NAVY,spaceAfter=5)) scenarios=[ (C_BLUE, C_SKY, "2 Groups\nIndependent\nNormal", "Independent t-test", "Mann-Whitney U"), (C_BLUE, C_SKY, "2 Groups\nPaired/Matched\nNormal", "Paired t-test", "Wilcoxon Signed-Rank"), (C_GREEN,C_LGREEN, "3+ Groups\nIndependent\nNormal", "One-Way ANOVA", "Kruskal-Wallis H"), (C_GREEN,C_LGREEN, "3+ Conditions\nSame Subjects\nNormal", "Repeated Measures\nANOVA", "Friedman test"), (C_ORANGE,C_LORANGE,"2 Factors\nIndependent\nNormal", "Two-Way ANOVA", "Scheirer-Ray-Hare"), (C_TEAL, C_LTEAL, "Correlation\nContinuous\nNormal", "Pearson r", "Spearman \u03c1"), (C_TEAL, C_LTEAL, "Correlation\nOrdinal or\nNon-Normal", "Spearman \u03c1", "Kendall \u03c4"), (C_PURPLE,C_LPURPLE,"Categorical\nAssociation", "Chi-Square \u03c7\u00b2", "Fisher's Exact"), (C_PURPLE,C_LPURPLE,"Paired\nProportions", "McNemar test", "McNemar test"), (C_RED, C_LRED, "Predict\nContinuous\nOutcome", "Linear Regression", "Robust Regression"), (C_RED, C_LRED, "Predict Binary\nOutcome", "Logistic Regression", "Logistic Regression"), (C_GOLD, C_LGOLD, "Time-to-Event\nSurvival", "Kaplan-Meier\n+ Log-rank", "Cox Regression"), ] cw2=(W-3*cm)/3 def card(sc,p,np_,hc,bc): t=Table([ [Paragraph(sc,S("Normal",fontSize=7.5,textColor=C_GREY,fontName="Helvetica",leading=10,alignment=TA_CENTER))], [Paragraph("Parametric:",S("Normal",fontSize=7,textColor=hc,fontName="Helvetica-Bold",alignment=TA_CENTER))], [Paragraph(p,S("Normal",fontSize=9,textColor=hc,fontName="Helvetica-Bold",leading=11,alignment=TA_CENTER))], [Paragraph("Non-Parametric:",S("Normal",fontSize=7,textColor=C_GREY,fontName="Helvetica-Oblique",alignment=TA_CENTER))], [Paragraph(np_,S("Normal",fontSize=8.5,textColor=C_GREY,fontName="Helvetica",leading=10,alignment=TA_CENTER))], ],colWidths=[cw2-0.6*cm]) t.setStyle(TableStyle([ ("BACKGROUND",(0,0),(-1,-1),bc),("LINEABOVE",(0,0),(-1,0),3,hc), ("BOX",(0,0),(-1,-1),0.5,hc),("ALIGN",(0,0),(-1,-1),"CENTER"), ("VALIGN",(0,0),(-1,-1),"MIDDLE"), ("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),3), ])) return t gdata=[] grow=[] for i,(hc,bc,sc,p,np_) in enumerate(scenarios): grow.append(card(sc,p,np_,hc,bc)) if len(grow)==3: gdata.append(grow[:]); grow=[] if grow: while len(grow)<3: grow.append(Spacer(cw2,2.9*cm)) gdata.append(grow) grid=Table(gdata,colWidths=[cw2]*3,rowHeights=[2.9*cm]*len(gdata)) grid.setStyle(TableStyle([ ("ALIGN",(0,0),(-1,-1),"CENTER"),("VALIGN",(0,0),(-1,-1),"MIDDLE"), ("LEFTPADDING",(0,0),(-1,-1),4),("RIGHTPADDING",(0,0),(-1,-1),4), ("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4), ])) story.append(grid) story.append(Spacer(1,0.3*cm)) story.append(Paragraph( "<b>Remember:</b> p-value tells you IF an effect exists. Effect size tells you HOW LARGE it is. " "Always report both. Significance without effect size is incomplete.",note)) doc.build(story,onFirstPage=on_cover,onLaterPages=on_page) print("SUCCESS:",OUTPUT) PYEOF
python3 -c "from reportlab.pdfgen import canvas; c=canvas.Canvas('/tmp/test.pdf'); print(dir(c))" 2>&1 | tr ',' '\n' | grep -i poly
python3 -c " from reportlab.pdfgen import canvas import tempfile c=canvas.Canvas(tempfile.mktemp()) # check drawing methods methods=[m for m in dir(c) if not m.startswith('_')] for m in sorted(methods): print(m) "
Generate a PDF decision guide for choosing statistical tests
mkdir -p /tmp/workspace/stat-guide && python3 << 'PYEOF' from reportlab.lib.pagesizes import A4 from reportlab.lib import colors from reportlab.lib.units import cm from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, PageBreak, HRFlowable, KeepTogether) from reportlab.platypus.flowables import Flowable from reportlab.pdfgen import canvas as pdfcanvas W, H = A4 OUTPUT = "/tmp/workspace/stat-guide/Statistical_Test_Decision_Guide.pdf" # ── Colours ─────────────────────────────────────────────────────────────────── C_NAVY = colors.HexColor("#1A237E"); C_BLUE = colors.HexColor("#1565C0") C_SKY = colors.HexColor("#E3F2FD"); C_GREEN = colors.HexColor("#2E7D32") C_LGREEN = colors.HexColor("#E8F5E9"); C_ORANGE = colors.HexColor("#E65100") C_LOR = colors.HexColor("#FFF3E0"); C_PURPLE = colors.HexColor("#4A148C") C_LPURP = colors.HexColor("#F3E5F5"); C_RED = colors.HexColor("#B71C1C") C_LRED = colors.HexColor("#FFEBEE"); C_GOLD = colors.HexColor("#F57F17") C_LGOLD = colors.HexColor("#FFFDE7"); C_TEAL = colors.HexColor("#00695C") C_LTEAL = colors.HexColor("#E0F2F1"); C_GREY = colors.HexColor("#37474F") C_LGREY = colors.HexColor("#ECEFF1"); C_WHITE = colors.white styles = getSampleStyleSheet() def S(n,**kw): return ParagraphStyle(n+str(id(kw)), parent=styles.get(n,styles["Normal"]), **kw) H1 = S("Heading1", fontSize=15, textColor=C_NAVY, fontName="Helvetica-Bold", spaceBefore=10, spaceAfter=4, leading=19) H2 = S("Heading2", fontSize=11, textColor=C_BLUE, fontName="Helvetica-Bold", spaceBefore=7, spaceAfter=3, leading=15) BD = S("Normal", fontSize=9.5, textColor=C_GREY, fontName="Helvetica", leading=14, spaceAfter=3, alignment=TA_JUSTIFY) NT = S("Normal", fontSize=8.5, textColor=C_GREY, fontName="Helvetica-Oblique", leading=12, alignment=TA_CENTER) CH = S("Normal", fontSize=9, textColor=C_WHITE, fontName="Helvetica-Bold", alignment=TA_CENTER, leading=12) CL = S("Normal", fontSize=8.5, textColor=C_GREY, fontName="Helvetica", alignment=TA_LEFT, leading=12) CC = S("Normal", fontSize=8.5, textColor=C_GREY, fontName="Helvetica", alignment=TA_CENTER, leading=12) # ── Helpers ─────────────────────────────────────────────────────────────────── def mktable(headers, rows, cw, hbg=C_NAVY, alt=C_SKY): data = [[Paragraph(h, CH) for h in headers]] for row in rows: data.append([Paragraph(str(c), CC if j>0 else CL) for j,c in enumerate(row)]) t = Table(data, colWidths=cw, repeatRows=1) t.setStyle(TableStyle([ ("BACKGROUND",(0,0),(-1,0),hbg), ("TEXTCOLOR",(0,0),(-1,0),C_WHITE), ("FONTNAME",(0,0),(-1,0),"Helvetica-Bold"), ("FONTSIZE",(0,0),(-1,0),9), ("ALIGN",(0,0),(-1,-1),"CENTER"), ("VALIGN",(0,0),(-1,-1),"MIDDLE"), ("ROWBACKGROUNDS",(0,1),(-1,-1),[C_WHITE,alt]), ("GRID",(0,0),(-1,-1),0.4,colors.HexColor("#B0BEC5")), ("TOPPADDING",(0,0),(-1,-1),4), ("BOTTOMPADDING",(0,0),(-1,-1),4), ("LEFTPADDING",(0,0),(-1,-1),5), ("RIGHTPADDING",(0,0),(-1,-1),5), ("LINEBELOW",(0,0),(-1,0),1.5,C_WHITE), ])); return t def hbox(text, bg=C_SKY, border=C_BLUE): t = Table([[Paragraph(text,BD)]], colWidths=[W-3*cm]) t.setStyle(TableStyle([ ("BACKGROUND",(0,0),(-1,-1),bg), ("BOX",(0,0),(-1,-1),1.5,border), ("LEFTPADDING",(0,0),(-1,-1),10), ("RIGHTPADDING",(0,0),(-1,-1),10), ("TOPPADDING",(0,0),(-1,-1),7), ("BOTTOMPADDING",(0,0),(-1,-1),7), ])); return t def badge(text, bg): t = Table([[Paragraph(f"<b>{text}</b>", S("N",fontSize=10,textColor=C_WHITE,fontName="Helvetica-Bold", alignment=TA_LEFT,leading=14))]], colWidths=[W-3*cm]) t.setStyle(TableStyle([ ("BACKGROUND",(0,0),(-1,-1),bg), ("LEFTPADDING",(0,0),(-1,-1),10), ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5), ])); return t def body_row(desc, assump, formula, example, bc): t = Table([ [Paragraph(desc, BD), Paragraph(f"<b>Formula:</b> <font name='Courier'>{formula}</font>" f"<br/><b>Example:</b> {example}", BD)], [Paragraph(f"<b>Assumptions:</b> {assump}", S("N",fontSize=8.5,textColor=C_GREY,fontName="Helvetica",leading=13)), ""] ], colWidths=[(W-3*cm)*0.42,(W-3*cm)*0.58], style=TableStyle([ ("BACKGROUND",(0,0),(-1,-1),bc),("VALIGN",(0,0),(-1,-1),"TOP"), ("LEFTPADDING",(0,0),(-1,-1),8),("TOPPADDING",(0,0),(-1,-1),5), ("BOTTOMPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),8), ("SPAN",(0,1),(1,1)),("BACKGROUND",(0,1),(1,1),C_LGREY), ("LINEBELOW",(0,1),(-1,1),1.5,C_NAVY), ])); return t # ── Step bar (pure Table, no custom Flowable) ───────────────────────────────── def step_bar(steps): n = len(steps); cw2 = (W-3*cm)/n cells = [] for lbl, bg in steps: inner = Table([[Paragraph(lbl, S("N",fontSize=7.5,textColor=C_WHITE, fontName="Helvetica-Bold",leading=10,alignment=TA_CENTER))]], colWidths=[cw2-0.4*cm], rowHeights=[1.5*cm]) inner.setStyle(TableStyle([ ("BACKGROUND",(0,0),(-1,-1),bg), ("VALIGN",(0,0),(-1,-1),"MIDDLE"),("ALIGN",(0,0),(-1,-1),"CENTER"), ("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4), ("LEFTPADDING",(0,0),(-1,-1),4),("RIGHTPADDING",(0,0),(-1,-1),4), ])) cells.append(inner) t = Table([cells], colWidths=[cw2]*n, rowHeights=[1.6*cm]) t.setStyle(TableStyle([ ("ALIGN",(0,0),(-1,-1),"CENTER"),("VALIGN",(0,0),(-1,-1),"MIDDLE"), ("LEFTPADDING",(0,0),(-1,-1),2),("RIGHTPADDING",(0,0),(-1,-1),2), ])); return t # ── Page callbacks ──────────────────────────────────────────────────────────── def on_cover(cv, doc): cv.saveState() # Full navy background cv.setFillColor(C_NAVY); cv.rect(0,0,W,H,fill=1,stroke=0) # Blue upper panel cv.setFillColor(C_BLUE); cv.rect(0,H*0.36,W,H*0.64,fill=1,stroke=0) # Decorative circles cv.setFillColor(colors.HexColor("#283593")); cv.circle(W*0.84,H*0.74,80,fill=1,stroke=0) cv.setFillColor(colors.HexColor("#1A237E")); cv.circle(W*0.08,H*0.83,50,fill=1,stroke=0) cv.setFillColor(colors.HexColor("#0D47A1")); cv.circle(W*0.5,H*0.92,35,fill=1,stroke=0) # Title cv.setFillColor(C_WHITE); cv.setFont("Helvetica-Bold",30) cv.drawCentredString(W/2, H*0.595, "Statistical Test") cv.drawCentredString(W/2, H*0.595-37, "Decision Guide") cv.setFont("Helvetica",12); cv.setFillColor(colors.HexColor("#BBDEFB")) cv.drawCentredString(W/2, H*0.595-66, "A Complete Reference for Research & PhD Scholars") # Gold divider line cv.setStrokeColor(C_GOLD); cv.setLineWidth(3) cv.line(W*0.2, H*0.595-80, W*0.8, H*0.595-80) # Info box cv.setFillColor(colors.HexColor("#0D47A1")) cv.roundRect(W*0.1, H*0.05, W*0.8, H*0.265, 10, fill=1, stroke=0) cv.setFillColor(C_GOLD); cv.setFont("Helvetica-Bold",10) topics = [ "\u2022 Parametric & Non-Parametric Tests", "\u2022 Comparative, Correlation & Regression Tests", "\u2022 Normality & Variance Assumption Checks", "\u2022 Effect Size & Statistical Power Guide", "\u2022 Full Decision Tables & Quick Cheat Sheet", ] base_y = H*0.05 + H*0.265 - 24 for i,t in enumerate(topics): cv.drawCentredString(W/2, base_y - i*21, t) # Footer cv.setFillColor(C_NAVY); cv.rect(0,0,W,1.0*cm,fill=1,stroke=0) cv.setFillColor(C_WHITE); cv.setFont("Helvetica",8) cv.drawCentredString(W/2,0.35*cm,"Statistical Test Decision Guide | PhD / Research Reference") cv.drawRightString(W-1.5*cm,0.35*cm,"Page 1") cv.restoreState() def on_later(cv, doc): cv.saveState() cv.setFillColor(C_NAVY); cv.rect(0,0,W,1.0*cm,fill=1,stroke=0) cv.setFillColor(C_WHITE); cv.setFont("Helvetica",8) cv.drawCentredString(W/2,0.35*cm,"Statistical Test Decision Guide | PhD / Research Reference") cv.drawRightString(W-1.5*cm,0.35*cm,f"Page {doc.page}") cv.setFillColor(C_NAVY); cv.rect(0,H-0.55*cm,W,0.55*cm,fill=1,stroke=0) cv.setFillColor(C_WHITE); cv.setFont("Helvetica-Bold",8) cv.drawCentredString(W/2,H-0.38*cm,"STATISTICAL TEST DECISION GUIDE") cv.restoreState() # ── Document ────────────────────────────────────────────────────────────────── doc = SimpleDocTemplate(OUTPUT, pagesize=A4, leftMargin=1.5*cm, rightMargin=1.5*cm, topMargin=1.8*cm, bottomMargin=1.4*cm, title="Statistical Test Decision Guide", author="Research Reference") story = [] # ═══════════════════════════════════════════════════════ PAGE 1: blank (cover drawn by on_cover) story.append(Spacer(1, H-3*cm)) # fills page 1 with whitespace; cover is drawn by callback story.append(PageBreak()) # ═══════════════════════════════════════════════════════ PAGE 2: Decision Framework story.append(Paragraph("How to Choose the Right Statistical Test", H1)) story.append(HRFlowable(width="100%", thickness=2, color=C_BLUE, spaceAfter=5)) story.append(Paragraph( "Choosing the wrong statistical test is one of the most common methodological errors in research. " "Follow these six steps systematically before selecting any test.", BD)) story.append(Spacer(1,0.2*cm)) story.append(Paragraph("Six-Step Decision Framework", H2)) story.append(step_bar([ ("STEP 1\nResearch\nQuestion", C_BLUE), ("STEP 2\nVariable\nType", C_GREEN), ("STEP 3\nNo. of\nGroups", C_ORANGE), ("STEP 4\nIndependent\nor Paired?", C_PURPLE), ("STEP 5\nNormal\nDistribution?", C_TEAL), ("STEP 6\nSelect\nTest \u2713", C_RED), ])) story.append(Spacer(1,0.3*cm)) story.append(Paragraph("Step 2: Types of Variables", H2)) story.append(mktable( ["Data Type","Definition","Example","Typical Test"], [["Nominal (Categorical)","Named categories; no order","Gender, Blood group","Chi-square, Fisher's Exact"], ["Ordinal","Ordered categories; unequal gaps","Pain scale 1-10, Grade","Mann-Whitney, Kruskal-Wallis"], ["Continuous - Interval","Equal gaps; no true zero","Temperature (\u00b0C), IQ score","t-test, ANOVA, Pearson r"], ["Continuous - Ratio","Equal gaps + true zero","Weight, Flexural strength (N)","t-test, ANOVA, Pearson r"], ["Dichotomous","Exactly 2 values","Yes/No, Fractured/Intact","Chi-square, Z-test, Logistic Reg."], ["Time-to-event","Duration until event occurs","Survival time, Implant failure","Kaplan-Meier, Cox Regression"]], [3.2*cm,3.8*cm,4.5*cm,5.0*cm])) story.append(Spacer(1,0.3*cm)) story.append(hbox( "<b>Golden Rule:</b> Parametric tests require: (1) Continuous data, " "(2) Approximately normal distribution, (3) Homogeneous variances. " "If ANY condition fails, switch to the non-parametric equivalent.", C_LGOLD, C_GOLD)) story.append(PageBreak()) # ═══════════════════════════════════════════════════════ PAGE 3: Master Table story.append(Paragraph("Master Reference: Statistical Test Selection", H1)) story.append(HRFlowable(width="100%", thickness=2, color=C_BLUE, spaceAfter=5)) story.append(mktable( ["Research Goal","Data Type","Design","Distribution","Parametric Test","Non-Parametric Test"], [ ["Compare 2 groups","Continuous","Independent","Normal","Independent t-test","Mann-Whitney U"], ["Compare 2 groups","Continuous","Paired / Matched","Normal","Paired t-test","Wilcoxon Signed-Rank"], ["Compare 2 groups","Continuous","Independent","Normal + unequal var","Welch's t-test","Mann-Whitney U"], ["Compare 3+ groups","Continuous","Independent","Normal","One-Way ANOVA","Kruskal-Wallis H"], ["Compare 3+ groups","Continuous","Same subject\n(repeated measures)","Normal","Repeated Measures ANOVA","Friedman test"], ["Compare 3+ groups","Continuous","Independent","Normal + 2 factors","Two-Way ANOVA","Scheirer-Ray-Hare"], ["Post-hoc after ANOVA","Continuous","Independent","Normal","Tukey HSD / Bonferroni / Scheff\u00e9","Dunn's test"], ["Post-hoc after RM-ANOVA","Continuous","Paired","Normal","Bonferroni / Sidak","Wilcoxon + Bonferroni"], ["Correlation","Continuous","\u2014","Normal + linear","Pearson r","Spearman \u03c1"], ["Correlation","Ordinal","\u2014","Any","Spearman \u03c1","Kendall \u03c4"], ["Compare proportions","Nominal","Independent","n \u2265 5/cell","Chi-Square \u03c7\u00b2","Fisher's Exact"], ["Compare proportions","Nominal","Paired","Any","McNemar test","McNemar test"], ["Predict outcome","Continuous DV","\u2014","Normal residuals","Linear Regression","Robust Regression"], ["Predict outcome","Binary DV","\u2014","Any","Logistic Regression","Logistic Regression"], ["Time-to-event","Continuous","Independent","Any","Kaplan-Meier + Log-rank","Cox Proportional Hazards"], ["Agreement / ICC","Continuous","Same rater x2","Any","ICC","Cohen's Kappa"], ["Test normality","Continuous","\u2014","\u2014","Shapiro-Wilk (n < 50)","K-S test (n \u2265 50)"], ["Test equal variances","Continuous","2+ groups","\u2014","Levene's test","Bartlett's test"], ], [2.8*cm,2.3*cm,2.6*cm,2.4*cm,4.0*cm,3.4*cm])) story.append(PageBreak()) # ═══════════════════════════════════════════════════════ PAGE 4: Parametric In Depth story.append(Paragraph("Parametric Tests - In Depth", H1)) story.append(HRFlowable(width="100%", thickness=2, color=C_BLUE, spaceAfter=5)) pdata = [ ("Independent Samples t-test", C_BLUE, C_SKY, "Compares means of <b>two independent groups</b>.", "Continuous DV \u2022 2 independent groups \u2022 Normal distribution \u2022 Equal variances (Levene's p>0.05)", "t = (x\u0305\u2081 - x\u0305\u2082) / SE_diff", "Conventional vs. Milled splint flexural strength"), ("Paired t-test", C_GREEN, C_LGREEN, "Compares means of <b>two related measurements</b> from the <b>same subjects</b>.", "Continuous DV \u2022 Same subjects measured twice \u2022 Differences normally distributed", "t = d\u0305 / (s_d / \u221an)", "Surface roughness before vs. after polishing on same specimens"), ("One-Way ANOVA", C_ORANGE, C_LOR, "Compares means of <b>3 or more independent groups</b>. Always follow with post-hoc tests.", "Continuous DV \u2022 3+ independent groups \u2022 Normal dist. \u2022 Equal variances \u2022 Independence", "F = MS_between / MS_within", "Conventional vs. Milled vs. 3D Printed occlusal splints"), ("Repeated Measures ANOVA", C_PURPLE, C_LPURP, "Compares means across <b>3+ conditions measured on the SAME subjects</b>. Requires sphericity.", "Continuous DV \u2022 Same subjects in ALL conditions \u2022 Sphericity (Mauchly's test) \u2022 Normal diffs", "F = MS_between-conditions / MS_error", "Same specimen tested at baseline, 1 month, 3 months"), ("Two-Way ANOVA", C_TEAL, C_LTEAL, "Examines effects of <b>two independent factors</b> and their interaction simultaneously.", "Continuous DV \u2022 2 categorical IVs \u2022 Normal dist. \u2022 Equal variances \u2022 \u22655 per cell", "F_A = MS_A/MS_err | F_B = MS_B/MS_err | F_AB = MS_AB/MS_err", "Fabrication method AND storage medium effects on strength"), ] for name, hc, bc, desc, assump, formula, example in pdata: story.append(KeepTogether([ badge(name, hc), body_row(desc, assump, formula, example, bc), Spacer(1, 0.2*cm) ])) story.append(PageBreak()) # ═══════════════════════════════════════════════════════ PAGE 5: Non-Parametric story.append(Paragraph("Non-Parametric Tests - In Depth", H1)) story.append(HRFlowable(width="100%", thickness=2, color=C_GREEN, spaceAfter=5)) story.append(hbox( "<b>Use non-parametric tests when:</b> Shapiro-Wilk p \u2264 0.05 \u2022 " "Ordinal scale data \u2022 Small samples (n < 10/group) \u2022 " "Outliers that cannot be removed \u2022 Severe skewness", C_LGREEN, C_GREEN)) story.append(Spacer(1,0.25*cm)) story.append(mktable( ["Test","Purpose","Data / Conditions","Parametric Equiv.","Formula","Example"], [ ["Mann-Whitney U","Compare 2 independent groups","Ordinal or continuous; non-normal", "Independent t-test","U = n\u2081n\u2082 + n\u2081(n\u2081+1)/2 \u2212 R\u2081","Two drug groups, skewed pain scores"], ["Wilcoxon Signed-Rank","Compare 2 paired groups","Continuous; non-normal diffs", "Paired t-test","W = \u03a3(signed ranks)","Pre vs. post treatment, same patients"], ["Kruskal-Wallis H","Compare 3+ independent groups","Ordinal or continuous; non-normal", "One-Way ANOVA","H = (12/N(N+1))\u03a3(R\u1d62\u00b2/n\u1d62) \u2212 3(N+1)","3 splint groups, small n"], ["Friedman test","Compare 3+ repeated measures","Ordinal or continuous; non-normal", "RM-ANOVA","\u03c7\u00b2_r = 12/(Nk(k+1))\u03a3R_j\u00b2 \u2212 3N(k+1)","Same patients, 3 time points"], ["Chi-Square \u03c7\u00b2","2 categorical variables","Nominal; expected \u22655/cell", "N/A (categorical)","\u03c7\u00b2 = \u03a3(O\u2212E)\u00b2/E","Gender vs. disease presence"], ["Fisher's Exact","Small expected counts","Nominal; any cell size", "Chi-square (small n)","Hypergeometric probability","2\u00d72 table with cells < 5"], ["McNemar test","Paired proportions","Nominal; matched pairs", "Paired t-test (binary)","\u03c7\u00b2 = (b\u2212c)\u00b2/(b+c)","Same patients: binary pre vs. post"], ["Spearman \u03c1","Correlation","Ordinal or non-normal continuous", "Pearson r","\u03c1 = 1 \u2212 6\u03a3d\u00b2/n(n\u00b2\u22121)","Pain rank vs. dose rank"], ["Dunn's test","Post-hoc after Kruskal-Wallis","Non-parametric pairwise", "Tukey HSD","z = (R\u1d62 \u2212 R_j)/SE","Which pairs differ after K-W"], ], [2.8*cm,3.2*cm,2.8*cm,3.0*cm,3.5*cm,3.2*cm], C_GREEN, C_LGREEN)) story.append(PageBreak()) # ═══════════════════════════════════════════════════════ PAGE 6: Assumptions story.append(Paragraph("Testing Statistical Assumptions", H1)) story.append(HRFlowable(width="100%", thickness=2, color=C_PURPLE, spaceAfter=5)) story.append(Paragraph("1. Normality Tests", H2)) story.append(mktable( ["Test","Best For","Interpretation","Notes"], [["Shapiro-Wilk","n < 50 (recommended for biomedical/dental research)", "p > 0.05 \u2192 Normal\np \u2264 0.05 \u2192 Non-normal","Most powerful for small n; always use first"], ["Kolmogorov-Smirnov","n \u2265 50 (large samples)", "p > 0.05 \u2192 Normal","Less powerful; Lilliefors correction often needed"], ["D'Agostino-Pearson","n \u2265 20", "p > 0.05 \u2192 Normal","Uses skewness + kurtosis; good for moderate n"], ["Q-Q Plot (visual)","Any n", "Points on diagonal \u2192 Normal","Always plot alongside numeric tests"], ["Histogram (visual)","Any n", "Bell shape \u2192 approximately normal","Supplement only; not a formal test"]], [3.5*cm,4.0*cm,3.8*cm,5.2*cm], C_PURPLE, C_LPURP)) story.append(Spacer(1,0.3*cm)) story.append(Paragraph("2. Homogeneity of Variances", H2)) story.append(mktable( ["Test","Conditions","Interpretation","Recommendation"], [["Levene's test","2+ groups; does NOT need normal data", "p > 0.05 \u2192 Variances equal (ANOVA OK)\np \u2264 0.05 \u2192 Use Welch's ANOVA / Welch's t-test", "Preferred for most research"], ["Bartlett's test","2+ groups; REQUIRES normal data", "p > 0.05 \u2192 Variances equal","Avoid if normality is uncertain"], ["Fligner-Killeen","Non-parametric; any distribution", "p > 0.05 \u2192 Variances equal","Use when data clearly fails normality"]], [3.2*cm,3.8*cm,4.8*cm,4.7*cm], C_TEAL, C_LTEAL)) story.append(Spacer(1,0.3*cm)) story.append(Paragraph("3. Mauchly's Sphericity Test (Repeated Measures ANOVA only)", H2)) story.append(hbox( "<b>Sphericity</b> requires that variances of differences between all pairs of repeated conditions are equal. " "Only needed for RM-ANOVA. <b>If violated (p < 0.05):</b> Apply Greenhouse-Geisser or " "Huynh-Feldt correction to degrees of freedom. <b>If met (p > 0.05):</b> Proceed normally.", C_LPURP, C_PURPLE)) story.append(Spacer(1,0.3*cm)) story.append(Paragraph("4. Assumption Checklist by Test", H2)) story.append(mktable( ["Test","Normality","Equal Variances","Sphericity","Other"], [["Independent t-test","Yes (Shapiro-Wilk)","Yes (Levene's)","N/A","Independence of observations"], ["Paired t-test","Yes (differences)","N/A","N/A","Matched/paired design"], ["One-Way ANOVA","Yes (Shapiro-Wilk)","Yes (Levene's)","N/A","Independence of observations"], ["RM-ANOVA","Yes (differences)","N/A","Yes (Mauchly's)","Same subjects in all conditions"], ["Two-Way ANOVA","Yes","Yes (Levene's)","N/A","Independence + \u22655 per cell"], ["Pearson r","Yes (both vars)","N/A","N/A","Linear relationship (check scatterplot)"], ["Linear Regression","Yes (residuals)","Homoscedasticity","N/A","No multicollinearity (VIF < 10)"], ["Logistic Regression","N/A","N/A","N/A","No multicollinearity + large sample"]], [3.2*cm,2.8*cm,2.8*cm,2.2*cm,5.5*cm])) story.append(PageBreak()) # ═══════════════════════════════════════════════════════ PAGE 7: Effect Size + Power story.append(Paragraph("Effect Size and Statistical Power", H1)) story.append(HRFlowable(width="100%", thickness=2, color=C_ORANGE, spaceAfter=5)) story.append(hbox( "<b>p < 0.05 does NOT mean clinically meaningful.</b> With large samples, " "even trivial differences become statistically significant. " "Always report effect size alongside p-values.", C_LOR, C_ORANGE)) story.append(Spacer(1,0.25*cm)) story.append(Paragraph("Effect Size Measures by Test", H2)) story.append(mktable( ["Measure","Used With","Formula","Interpretation (Cohen's Convention)"], [["Cohen's d","Independent t-test, Paired t-test","d = (\u03bc\u2081\u2212\u03bc\u2082)/SD_pooled", "Small: 0.2 | Medium: 0.5 | Large: \u22650.8"], ["Eta-squared \u03b7\u00b2","One-Way ANOVA","\u03b7\u00b2 = SS_between / SS_total", "Small: 0.01 | Medium: 0.06 | Large: \u22650.14"], ["Partial \u03b7\u00b2","Multi-factor ANOVA, ANCOVA","\u03b7\u00b2p = SS_effect / (SS_effect + SS_error)", "Same thresholds; preferred for multi-factor designs"], ["Omega-squared \u03c9\u00b2","ANOVA (less biased)","\u03c9\u00b2 = (SS_B \u2212 df_B\u00b7MS_W)/(SS_T + MS_W)", "Preferred over \u03b7\u00b2 for small samples"], ["Pearson r","Correlation","r is itself an effect size", "Small: 0.1 | Medium: 0.3 | Large: \u22650.5"], ["r (from Z)","Mann-Whitney, Wilcoxon","r = Z / \u221aN", "Small: 0.1 | Medium: 0.3 | Large: \u22650.5"], ["Cram\u00e9r's V","Chi-Square","V = \u221a(\u03c7\u00b2/N\u00b7min(r\u22121,c\u22121))", "Small: 0.1 | Medium: 0.3 | Large: \u22650.5"], ["Odds Ratio (OR)","Logistic Regression, 2\u00d72","OR = (a\u00b7d)/(b\u00b7c)", "OR=1: no effect | >1: increased odds | <1: reduced odds"], ["Hazard Ratio (HR)","Cox Regression","HR = h\u2081(t)/h\u2080(t)", "HR=1: no diff. | >1: higher hazard in group 1"]], [3.0*cm,3.8*cm,5.5*cm,5.2*cm], C_ORANGE, C_LOR)) story.append(Spacer(1,0.3*cm)) story.append(Paragraph("Statistical Power and Sample Size", H2)) story.append(mktable( ["Parameter","Definition","Typical Value","Impact on Study"], [["\u03b1 (Type I Error)","Reject H\u2080 when it is true (false positive)","0.05 (standard)", "\u2191 \u03b1 \u2192 \u2191 Power but \u2191 false positives"], ["\u03b2 (Type II Error)","Fail to reject false H\u2080 (false negative)","0.20 acceptable", "\u2193 \u03b2 \u2192 \u2191 Power"], ["Power (1\u2212\u03b2)","Probability of detecting a true effect","\u226580% desired", "Increase with larger n, larger effect, or higher \u03b1"], ["Sample size n","Larger n \u2192 smaller SE \u2192 more power","Via a priori power analysis", "Use G*Power software or R pwr package"], ["Effect size","Larger true effect \u2192 easier to detect","From literature / pilot data", "Smaller effects require considerably larger n"]], [3.0*cm,5.0*cm,3.5*cm,5.0*cm], C_RED, C_LRED)) story.append(PageBreak()) # ═══════════════════════════════════════════════════════ PAGE 8: Cheat Sheet story.append(Paragraph("Quick-Reference Cheat Sheet", H1)) story.append(HRFlowable(width="100%", thickness=2, color=C_NAVY, spaceAfter=6)) scenarios = [ (C_BLUE, C_SKY, "2 Groups\nIndependent\nNormal", "Independent t-test", "Mann-Whitney U"), (C_BLUE, C_SKY, "2 Groups\nPaired/Matched\nNormal", "Paired t-test", "Wilcoxon Signed-Rank"), (C_GREEN, C_LGREEN, "3+ Groups\nIndependent\nNormal", "One-Way ANOVA", "Kruskal-Wallis H"), (C_GREEN, C_LGREEN, "3+ Conditions\nSame Subjects\nNormal", "Repeated Measures\nANOVA","Friedman test"), (C_ORANGE,C_LOR, "2 Factors\nIndependent\nNormal", "Two-Way ANOVA", "Scheirer-Ray-Hare"), (C_TEAL, C_LTEAL, "Correlation\nContinuous\nNormal", "Pearson r", "Spearman \u03c1"), (C_TEAL, C_LTEAL, "Correlation\nOrdinal/\nNon-Normal", "Spearman \u03c1", "Kendall \u03c4"), (C_PURPLE,C_LPURP, "Categorical\nAssociation", "Chi-Square \u03c7\u00b2", "Fisher's Exact"), (C_PURPLE,C_LPURP, "Paired\nProportions", "McNemar test", "McNemar test"), (C_RED, C_LRED, "Predict\nContinuous\nOutcome", "Linear\nRegression", "Robust Regression"), (C_RED, C_LRED, "Predict\nBinary\nOutcome", "Logistic\nRegression", "Logistic Regression"), (C_GOLD, C_LGOLD, "Time-to-Event\nSurvival", "Kaplan-Meier\n+Log-rank", "Cox Regression"), ] cw3 = (W-3*cm)/3 def card(sc,p,np_,hc,bc): t = Table([ [Paragraph(sc, S("N",fontSize=7.5,textColor=C_GREY,fontName="Helvetica", leading=10,alignment=TA_CENTER))], [Paragraph("Parametric:", S("N",fontSize=7,textColor=hc,fontName="Helvetica-Bold", alignment=TA_CENTER))], [Paragraph(p, S("N",fontSize=9,textColor=hc,fontName="Helvetica-Bold", leading=11,alignment=TA_CENTER))], [Paragraph("Non-Parametric:", S("N",fontSize=7,textColor=C_GREY, fontName="Helvetica-Oblique",alignment=TA_CENTER))], [Paragraph(np_, S("N",fontSize=8.5,textColor=C_GREY,fontName="Helvetica", leading=10,alignment=TA_CENTER))], ], colWidths=[cw3-0.5*cm]) t.setStyle(TableStyle([ ("BACKGROUND",(0,0),(-1,-1),bc), ("LINEABOVE",(0,0),(-1,0),3,hc), ("BOX",(0,0),(-1,-1),0.5,hc), ("ALIGN",(0,0),(-1,-1),"CENTER"),("VALIGN",(0,0),(-1,-1),"MIDDLE"), ("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),3), ])) return t gdata=[]; grow=[] for i,(hc,bc,sc,p,np_) in enumerate(scenarios): grow.append(card(sc,p,np_,hc,bc)) if len(grow)==3: gdata.append(grow[:]); grow=[] if grow: while len(grow)<3: grow.append(Spacer(cw3,2.9*cm)) gdata.append(grow) grid = Table(gdata, colWidths=[cw3]*3, rowHeights=[2.9*cm]*len(gdata)) grid.setStyle(TableStyle([ ("ALIGN",(0,0),(-1,-1),"CENTER"),("VALIGN",(0,0),(-1,-1),"MIDDLE"), ("LEFTPADDING",(0,0),(-1,-1),4),("RIGHTPADDING",(0,0),(-1,-1),4), ("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4), ])) story.append(grid) story.append(Spacer(1,0.3*cm)) story.append(Paragraph( "<b>Remember:</b> p-value tells you IF an effect exists. " "Effect size tells you HOW LARGE it is. Always report both. " "Statistical significance without effect size is incomplete science.", NT)) # ════════════════════════════════════════════════════════ BUILD doc.build(story, onFirstPage=on_cover, onLaterPages=on_later) print("SUCCESS:", OUTPUT) PYEOF
python3 << 'PYEOF' from reportlab.lib.pagesizes import A4 from reportlab.lib import colors from reportlab.lib.units import cm from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY from reportlab.platypus import (BaseDocTemplate, Frame, PageTemplate, Paragraph, Spacer, Table, TableStyle, PageBreak, HRFlowable, KeepTogether) W, H = A4 OUTPUT = "/tmp/workspace/stat-guide/Statistical_Test_Decision_Guide.pdf" # ── Colours ──────────────────────────────────────────────────────────────── C_NAVY =colors.HexColor("#1A237E"); C_BLUE =colors.HexColor("#1565C0") C_SKY =colors.HexColor("#E3F2FD"); C_GREEN =colors.HexColor("#2E7D32") C_LGRN =colors.HexColor("#E8F5E9"); C_ORA =colors.HexColor("#E65100") C_LORA =colors.HexColor("#FFF3E0"); C_PURP =colors.HexColor("#4A148C") C_LPURP =colors.HexColor("#F3E5F5"); C_RED =colors.HexColor("#B71C1C") C_LRED =colors.HexColor("#FFEBEE"); C_GOLD =colors.HexColor("#F57F17") C_LGOLD =colors.HexColor("#FFFDE7"); C_TEAL =colors.HexColor("#00695C") C_LTEAL =colors.HexColor("#E0F2F1"); C_GREY =colors.HexColor("#37474F") C_LGREY =colors.HexColor("#ECEFF1"); C_WHITE =colors.white st = getSampleStyleSheet() def S(n,**kw): return ParagraphStyle(n+str(id(kw)),parent=st.get(n,st["Normal"]),**kw) H1=S("Heading1",fontSize=15,textColor=C_NAVY,fontName="Helvetica-Bold",spaceBefore=10,spaceAfter=4,leading=19) H2=S("Heading2",fontSize=11,textColor=C_BLUE,fontName="Helvetica-Bold",spaceBefore=7,spaceAfter=3,leading=15) BD=S("Normal",fontSize=9.5,textColor=C_GREY,fontName="Helvetica",leading=14,spaceAfter=3,alignment=TA_JUSTIFY) NT=S("Normal",fontSize=8.5,textColor=C_GREY,fontName="Helvetica-Oblique",leading=12,alignment=TA_CENTER) CH=S("Normal",fontSize=9,textColor=C_WHITE,fontName="Helvetica-Bold",alignment=TA_CENTER,leading=12) CL=S("Normal",fontSize=8.5,textColor=C_GREY,fontName="Helvetica",alignment=TA_LEFT,leading=12) CC=S("Normal",fontSize=8.5,textColor=C_GREY,fontName="Helvetica",alignment=TA_CENTER,leading=12) # ── Helpers ──────────────────────────────────────────────────────────────── def mktable(headers,rows,cw,hbg=C_NAVY,alt=C_SKY): data=[[Paragraph(h,CH) for h in headers]] for row in rows: data.append([Paragraph(str(c),CC if j>0 else CL) for j,c in enumerate(row)]) t=Table(data,colWidths=cw,repeatRows=1) t.setStyle(TableStyle([ ("BACKGROUND",(0,0),(-1,0),hbg),("TEXTCOLOR",(0,0),(-1,0),C_WHITE), ("FONTNAME",(0,0),(-1,0),"Helvetica-Bold"),("FONTSIZE",(0,0),(-1,0),9), ("ALIGN",(0,0),(-1,-1),"CENTER"),("VALIGN",(0,0),(-1,-1),"MIDDLE"), ("ROWBACKGROUNDS",(0,1),(-1,-1),[C_WHITE,alt]), ("GRID",(0,0),(-1,-1),0.4,colors.HexColor("#B0BEC5")), ("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4), ("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5), ("LINEBELOW",(0,0),(-1,0),1.5,C_WHITE), ])); return t def hbox(text,bg=C_SKY,border=C_BLUE): t=Table([[Paragraph(text,BD)]],colWidths=[W-3*cm]) t.setStyle(TableStyle([ ("BACKGROUND",(0,0),(-1,-1),bg),("BOX",(0,0),(-1,-1),1.5,border), ("LEFTPADDING",(0,0),(-1,-1),10),("RIGHTPADDING",(0,0),(-1,-1),10), ("TOPPADDING",(0,0),(-1,-1),7),("BOTTOMPADDING",(0,0),(-1,-1),7), ])); return t def badge(text,bg): t=Table([[Paragraph(f"<b>{text}</b>", S("N",fontSize=10,textColor=C_WHITE,fontName="Helvetica-Bold",alignment=TA_LEFT,leading=14))]], colWidths=[W-3*cm]) t.setStyle(TableStyle([ ("BACKGROUND",(0,0),(-1,-1),bg), ("LEFTPADDING",(0,0),(-1,-1),10), ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5), ])); return t def body_row(desc,assump,formula,example,bc): t=Table([ [Paragraph(desc,BD), Paragraph(f"<b>Formula:</b> <font name='Courier'>{formula}</font>" f"<br/><b>Example:</b> {example}",BD)], [Paragraph(f"<b>Assumptions:</b> {assump}", S("N",fontSize=8.5,textColor=C_GREY,fontName="Helvetica",leading=13)),""] ],colWidths=[(W-3*cm)*0.42,(W-3*cm)*0.58], style=TableStyle([ ("BACKGROUND",(0,0),(-1,-1),bc),("VALIGN",(0,0),(-1,-1),"TOP"), ("LEFTPADDING",(0,0),(-1,-1),8),("TOPPADDING",(0,0),(-1,-1),5), ("BOTTOMPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),8), ("SPAN",(0,1),(1,1)),("BACKGROUND",(0,1),(1,1),C_LGREY), ("LINEBELOW",(0,1),(-1,1),1.5,C_NAVY), ])); return t def step_bar(steps): n=len(steps); cw2=(W-3*cm)/n cells=[] for lbl,bg in steps: inner=Table([[Paragraph(lbl,S("N",fontSize=7.5,textColor=C_WHITE, fontName="Helvetica-Bold",leading=10,alignment=TA_CENTER))]], colWidths=[cw2-0.4*cm],rowHeights=[1.5*cm]) inner.setStyle(TableStyle([ ("BACKGROUND",(0,0),(-1,-1),bg), ("VALIGN",(0,0),(-1,-1),"MIDDLE"),("ALIGN",(0,0),(-1,-1),"CENTER"), ("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4), ("LEFTPADDING",(0,0),(-1,-1),4),("RIGHTPADDING",(0,0),(-1,-1),4), ])) cells.append(inner) t=Table([cells],colWidths=[cw2]*n,rowHeights=[1.6*cm]) t.setStyle(TableStyle([ ("ALIGN",(0,0),(-1,-1),"CENTER"),("VALIGN",(0,0),(-1,-1),"MIDDLE"), ("LEFTPADDING",(0,0),(-1,-1),2),("RIGHTPADDING",(0,0),(-1,-1),2), ])); return t # ── Page callbacks ───────────────────────────────────────────────────────── def draw_cover(cv,doc): cv.saveState() cv.setFillColor(C_NAVY); cv.rect(0,0,W,H,fill=1,stroke=0) cv.setFillColor(C_BLUE); cv.rect(0,H*0.36,W,H*0.64,fill=1,stroke=0) cv.setFillColor(colors.HexColor("#283593")); cv.circle(W*0.84,H*0.74,80,fill=1,stroke=0) cv.setFillColor(colors.HexColor("#1A237E")); cv.circle(W*0.08,H*0.83,50,fill=1,stroke=0) cv.setFillColor(C_WHITE); cv.setFont("Helvetica-Bold",30) cv.drawCentredString(W/2,H*0.595,"Statistical Test") cv.drawCentredString(W/2,H*0.595-37,"Decision Guide") cv.setFont("Helvetica",12); cv.setFillColor(colors.HexColor("#BBDEFB")) cv.drawCentredString(W/2,H*0.595-66,"A Complete Reference for Research & PhD Scholars") cv.setStrokeColor(C_GOLD); cv.setLineWidth(3) cv.line(W*0.2,H*0.595-80,W*0.8,H*0.595-80) cv.setFillColor(colors.HexColor("#0D47A1")) cv.roundRect(W*0.1,H*0.05,W*0.8,H*0.265,10,fill=1,stroke=0) cv.setFillColor(C_GOLD); cv.setFont("Helvetica-Bold",10) topics=["\u2022 Parametric & Non-Parametric Tests", "\u2022 Comparative, Correlation & Regression Tests", "\u2022 Normality & Variance Assumption Checks", "\u2022 Effect Size & Statistical Power Guide", "\u2022 Full Decision Tables & Quick Cheat Sheet"] base=H*0.05+H*0.265-24 for i,t in enumerate(topics): cv.drawCentredString(W/2,base-i*21,t) cv.setFillColor(C_NAVY); cv.rect(0,0,W,1.0*cm,fill=1,stroke=0) cv.setFillColor(C_WHITE); cv.setFont("Helvetica",8) cv.drawCentredString(W/2,0.35*cm,"Statistical Test Decision Guide | PhD / Research Reference") cv.drawRightString(W-1.5*cm,0.35*cm,"Page 1") cv.restoreState() def draw_later(cv,doc): cv.saveState() cv.setFillColor(C_NAVY); cv.rect(0,0,W,1.0*cm,fill=1,stroke=0) cv.setFillColor(C_WHITE); cv.setFont("Helvetica",8) cv.drawCentredString(W/2,0.35*cm,"Statistical Test Decision Guide | PhD / Research Reference") cv.drawRightString(W-1.5*cm,0.35*cm,f"Page {doc.page}") cv.setFillColor(C_NAVY); cv.rect(0,H-0.55*cm,W,0.55*cm,fill=1,stroke=0) cv.setFillColor(C_WHITE); cv.setFont("Helvetica-Bold",8) cv.drawCentredString(W/2,H-0.38*cm,"STATISTICAL TEST DECISION GUIDE") cv.restoreState() # ── Document with two page templates ────────────────────────────────────── doc = BaseDocTemplate(OUTPUT, pagesize=A4, leftMargin=1.5*cm, rightMargin=1.5*cm, topMargin=1.8*cm, bottomMargin=1.4*cm, title="Statistical Test Decision Guide", author="Research Reference") # Cover frame (full page – content frame is tiny/invisible, cover drawn by callback) cover_frame = Frame(0, 0, W, H, leftPadding=0, rightPadding=0, topPadding=0, bottomPadding=0, id="cover") # Normal content frame content_frame = Frame(1.5*cm, 1.4*cm, W-3*cm, H-3.3*cm, leftPadding=0, rightPadding=0, topPadding=0, bottomPadding=0, id="normal") doc.addPageTemplates([ PageTemplate(id="Cover", frames=[cover_frame], onPage=draw_cover), PageTemplate(id="Content", frames=[content_frame], onPage=draw_later), ]) from reportlab.platypus import NextPageTemplate story = [] # Page 1: cover (just the template switch; no visible story content needed) story.append(NextPageTemplate("Content")) story.append(PageBreak()) # ═══════════════════════════════════════════════════════ PAGE 2 story.append(Paragraph("How to Choose the Right Statistical Test",H1)) story.append(HRFlowable(width="100%",thickness=2,color=C_BLUE,spaceAfter=5)) story.append(Paragraph("Choosing the wrong statistical test is one of the most common " "methodological errors in research. Follow these six steps systematically.",BD)) story.append(Spacer(1,0.2*cm)) story.append(Paragraph("Six-Step Decision Framework",H2)) story.append(step_bar([ ("STEP 1\nResearch\nQuestion",C_BLUE),("STEP 2\nVariable\nType",C_GREEN), ("STEP 3\nNo. of\nGroups",C_ORA),("STEP 4\nIndependent\nor Paired?",C_PURP), ("STEP 5\nNormal\nDistribution?",C_TEAL),("STEP 6\nSelect\nTest \u2713",C_RED), ])) story.append(Spacer(1,0.3*cm)) story.append(Paragraph("Step 2: Types of Variables",H2)) story.append(mktable( ["Data Type","Definition","Example","Typical Test"], [["Nominal (Categorical)","Named categories; no order","Gender, Blood group","Chi-square, Fisher's Exact"], ["Ordinal","Ordered categories; unequal gaps","Pain scale 1-10, Grade","Mann-Whitney, Kruskal-Wallis"], ["Continuous - Interval","Equal gaps; no true zero","Temperature (\u00b0C), IQ score","t-test, ANOVA, Pearson r"], ["Continuous - Ratio","Equal gaps + true zero","Weight, Flexural strength (N)","t-test, ANOVA, Pearson r"], ["Dichotomous","Exactly 2 values","Yes/No, Fractured/Intact","Chi-square, Z-test, Logistic Reg."], ["Time-to-event","Duration until event occurs","Survival time, Implant failure","Kaplan-Meier, Cox Regression"]], [3.2*cm,3.8*cm,4.5*cm,5.0*cm])) story.append(Spacer(1,0.3*cm)) story.append(hbox("<b>Golden Rule:</b> Parametric tests require: (1) Continuous data, " "(2) Approximately normal distribution, (3) Homogeneous variances. " "If ANY condition fails, switch to the non-parametric equivalent.",C_LGOLD,C_GOLD)) story.append(PageBreak()) # ═══════════════════════════════════════════════════════ PAGE 3 story.append(Paragraph("Master Reference: Statistical Test Selection",H1)) story.append(HRFlowable(width="100%",thickness=2,color=C_BLUE,spaceAfter=5)) story.append(mktable( ["Research Goal","Data Type","Design","Distribution","Parametric Test","Non-Parametric Test"], [ ["Compare 2 groups","Continuous","Independent","Normal","Independent t-test","Mann-Whitney U"], ["Compare 2 groups","Continuous","Paired / Matched","Normal","Paired t-test","Wilcoxon Signed-Rank"], ["Compare 2 groups","Continuous","Independent","Normal + unequal var","Welch's t-test","Mann-Whitney U"], ["Compare 3+ groups","Continuous","Independent","Normal","One-Way ANOVA","Kruskal-Wallis H"], ["Compare 3+ groups","Continuous","Same subject\n(repeated)","Normal","Repeated Measures ANOVA","Friedman test"], ["Compare 3+ groups","Continuous","Independent","Normal + 2 factors","Two-Way ANOVA","Scheirer-Ray-Hare"], ["Post-hoc (ANOVA)","Continuous","Independent","Normal","Tukey HSD / Bonferroni","Dunn's test"], ["Post-hoc (RM-ANOVA)","Continuous","Paired","Normal","Bonferroni / Sidak","Wilcoxon + Bonferroni"], ["Correlation","Continuous","\u2014","Normal + linear","Pearson r","Spearman \u03c1"], ["Correlation","Ordinal","\u2014","Any","Spearman \u03c1","Kendall \u03c4"], ["Compare proportions","Nominal","Independent","n \u2265 5/cell","Chi-Square \u03c7\u00b2","Fisher's Exact"], ["Compare proportions","Nominal","Paired","Any","McNemar test","McNemar test"], ["Predict outcome","Continuous DV","\u2014","Normal residuals","Linear Regression","Robust Regression"], ["Predict outcome","Binary DV","\u2014","Any","Logistic Regression","Logistic Regression"], ["Time-to-event","Continuous","Independent","Any","Kaplan-Meier + Log-rank","Cox Proportional Hazards"], ["Agreement / ICC","Continuous","Same rater x2","Any","ICC","Cohen's Kappa"], ["Test normality","Continuous","\u2014","\u2014","Shapiro-Wilk (n < 50)","K-S test (n \u2265 50)"], ["Test equal variances","Continuous","2+ groups","\u2014","Levene's test","Bartlett's test"], ], [2.8*cm,2.3*cm,2.6*cm,2.4*cm,4.0*cm,3.4*cm])) story.append(PageBreak()) # ═══════════════════════════════════════════════════════ PAGE 4 story.append(Paragraph("Parametric Tests - In Depth",H1)) story.append(HRFlowable(width="100%",thickness=2,color=C_BLUE,spaceAfter=5)) pdata=[ ("Independent Samples t-test",C_BLUE,C_SKY, "Compares means of <b>two independent groups</b>.", "Continuous DV \u2022 2 independent groups \u2022 Normal distribution \u2022 Equal variances (Levene p>0.05)", "t = (x\u0305\u2081 \u2212 x\u0305\u2082) / SE_diff","Conventional vs. Milled splint flexural strength"), ("Paired t-test",C_GREEN,C_LGRN, "Compares means of <b>two related measurements</b> from the <b>same subjects</b>.", "Continuous DV \u2022 Same subjects measured twice \u2022 Differences normally distributed", "t = d\u0305 / (s_d / \u221an)","Surface roughness before vs. after polishing on same specimens"), ("One-Way ANOVA",C_ORA,C_LORA, "Compares means of <b>3 or more independent groups</b>. Always follow with post-hoc tests.", "Continuous DV \u2022 3+ independent groups \u2022 Normal dist. \u2022 Equal variances \u2022 Independence", "F = MS_between / MS_within","Conventional vs. Milled vs. 3D Printed occlusal splints"), ("Repeated Measures ANOVA",C_PURP,C_LPURP, "Compares means across <b>3+ conditions on the SAME subjects</b>. Requires sphericity.", "Continuous DV \u2022 Same subjects in ALL conditions \u2022 Sphericity (Mauchly's) \u2022 Normal diffs", "F = MS_between-conditions / MS_error","Same specimen: baseline, 1 month, 3 months"), ("Two-Way ANOVA",C_TEAL,C_LTEAL, "Examines effects of <b>two independent factors</b> and their interaction simultaneously.", "Continuous DV \u2022 2 categorical IVs \u2022 Normal dist. \u2022 Equal variances \u2022 \u22655 per cell", "F_A = MS_A/MSe | F_B = MS_B/MSe | F_AB = MS_AB/MSe", "Fabrication method AND storage medium on flexural strength"), ] for name,hc,bc,desc,assump,formula,example in pdata: story.append(KeepTogether([badge(name,hc),body_row(desc,assump,formula,example,bc),Spacer(1,0.2*cm)])) story.append(PageBreak()) # ═══════════════════════════════════════════════════════ PAGE 5 story.append(Paragraph("Non-Parametric Tests - In Depth",H1)) story.append(HRFlowable(width="100%",thickness=2,color=C_GREEN,spaceAfter=5)) story.append(hbox("<b>Use non-parametric tests when:</b> Shapiro-Wilk p \u2264 0.05 \u2022 " "Ordinal data \u2022 Small samples (n < 10/group) \u2022 Outliers present \u2022 Severe skewness", C_LGRN,C_GREEN)) story.append(Spacer(1,0.25*cm)) story.append(mktable( ["Test","Purpose","Data / Conditions","Parametric Equiv.","Formula","Example"], [["Mann-Whitney U","Compare 2 independent groups","Ordinal or continuous; non-normal", "Independent t-test","U = n\u2081n\u2082 + n\u2081(n\u2081+1)/2 \u2212 R\u2081","Two drug groups, skewed pain scores"], ["Wilcoxon Signed-Rank","Compare 2 paired groups","Continuous; non-normal diffs", "Paired t-test","W = \u03a3(signed ranks)","Pre vs. post treatment, same patients"], ["Kruskal-Wallis H","Compare 3+ independent groups","Ordinal or continuous; non-normal", "One-Way ANOVA","H = (12/N(N+1))\u03a3(R\u1d62\u00b2/n\u1d62) \u2212 3(N+1)","3 splint groups, small n"], ["Friedman test","Compare 3+ repeated measures","Ordinal or continuous; non-normal", "RM-ANOVA","\u03c7\u00b2_r = 12/(Nk(k+1))\u03a3R_j\u00b2 \u2212 3N(k+1)","Same patients, 3 time points"], ["Chi-Square \u03c7\u00b2","2 categorical variables","Nominal; expected \u22655/cell", "N/A","\u03c7\u00b2 = \u03a3(O\u2212E)\u00b2/E","Gender vs. disease presence"], ["Fisher's Exact","Small expected counts","Nominal; any cell size", "Chi-sq (small n)","Hypergeometric prob.","2\u00d72 table with cells < 5"], ["McNemar test","Paired proportions","Nominal; matched pairs", "Paired t-test (binary)","\u03c7\u00b2 = (b\u2212c)\u00b2/(b+c)","Same patients: binary pre vs. post"], ["Spearman \u03c1","Correlation","Ordinal or non-normal continuous", "Pearson r","\u03c1 = 1 \u2212 6\u03a3d\u00b2/n(n\u00b2\u22121)","Pain rank vs. dose rank"], ["Dunn's test","Post-hoc after Kruskal-Wallis","Non-parametric pairwise", "Tukey HSD","z = (R\u1d62 \u2212 R_j)/SE","Which pairs differ after K-W"]], [2.8*cm,3.2*cm,2.8*cm,3.0*cm,3.5*cm,3.2*cm],C_GREEN,C_LGRN)) story.append(PageBreak()) # ═══════════════════════════════════════════════════════ PAGE 6 story.append(Paragraph("Testing Statistical Assumptions",H1)) story.append(HRFlowable(width="100%",thickness=2,color=C_PURP,spaceAfter=5)) story.append(Paragraph("1. Normality Tests",H2)) story.append(mktable( ["Test","Best For","Interpretation","Notes"], [["Shapiro-Wilk","n < 50 (best for biomedical/dental research)", "p > 0.05 \u2192 Normal\np \u2264 0.05 \u2192 Non-normal","Most powerful for small n; always use first"], ["Kolmogorov-Smirnov","n \u2265 50 (large samples)", "p > 0.05 \u2192 Normal","Lilliefors correction often needed"], ["D'Agostino-Pearson","n \u2265 20", "p > 0.05 \u2192 Normal","Uses skewness + kurtosis"], ["Q-Q Plot (visual)","Any n", "Points on diagonal \u2192 Normal","Always plot alongside numeric tests"], ["Histogram (visual)","Any n", "Bell shape \u2192 approximately normal","Supplement only; not a formal test"]], [3.5*cm,4.0*cm,3.8*cm,5.2*cm],C_PURP,C_LPURP)) story.append(Spacer(1,0.3*cm)) story.append(Paragraph("2. Homogeneity of Variances",H2)) story.append(mktable( ["Test","Conditions","Interpretation","Recommendation"], [["Levene's test","2+ groups; does NOT need normality", "p > 0.05 \u2192 Equal (ANOVA OK)\np \u2264 0.05 \u2192 Welch's ANOVA","Preferred for most research"], ["Bartlett's test","2+ groups; REQUIRES normal data", "p > 0.05 \u2192 Equal variances","Avoid if normality uncertain"], ["Fligner-Killeen","Non-parametric; any distribution", "p > 0.05 \u2192 Equal variances","Best when data fails normality"]], [3.2*cm,3.8*cm,4.8*cm,4.7*cm],C_TEAL,C_LTEAL)) story.append(Spacer(1,0.3*cm)) story.append(Paragraph("3. Mauchly's Sphericity Test (RM-ANOVA only)",H2)) story.append(hbox("<b>Sphericity:</b> Variances of differences between all pairs of repeated conditions must be equal. " "Only needed for RM-ANOVA. <b>If violated (p < 0.05):</b> Apply Greenhouse-Geisser or Huynh-Feldt correction. " "<b>If met (p > 0.05):</b> Proceed normally.",C_LPURP,C_PURP)) story.append(Spacer(1,0.3*cm)) story.append(Paragraph("4. Assumption Checklist by Test",H2)) story.append(mktable( ["Test","Normality","Equal Variances","Sphericity","Other"], [["Independent t-test","Yes (Shapiro-Wilk)","Yes (Levene's)","N/A","Independence"], ["Paired t-test","Yes (differences)","N/A","N/A","Matched/paired design"], ["One-Way ANOVA","Yes (Shapiro-Wilk)","Yes (Levene's)","N/A","Independence"], ["RM-ANOVA","Yes (differences)","N/A","Yes (Mauchly's)","Same subjects all conditions"], ["Two-Way ANOVA","Yes","Yes (Levene's)","N/A","Independence + \u22655/cell"], ["Pearson r","Yes (both vars)","N/A","N/A","Linear relationship"], ["Linear Regression","Yes (residuals)","Homoscedasticity","N/A","No multicollinearity (VIF<10)"], ["Logistic Regression","N/A","N/A","N/A","No multicollinearity + large n"]], [3.2*cm,2.8*cm,2.8*cm,2.2*cm,5.5*cm])) story.append(PageBreak()) # ═══════════════════════════════════════════════════════ PAGE 7 story.append(Paragraph("Effect Size and Statistical Power",H1)) story.append(HRFlowable(width="100%",thickness=2,color=C_ORA,spaceAfter=5)) story.append(hbox("<b>p < 0.05 does NOT mean clinically meaningful.</b> With large samples, " "trivial differences can become statistically significant. " "Always report effect size alongside p-values.",C_LORA,C_ORA)) story.append(Spacer(1,0.25*cm)) story.append(Paragraph("Effect Size Measures",H2)) story.append(mktable( ["Measure","Used With","Formula","Interpretation (Cohen's Convention)"], [["Cohen's d","Independent t-test, Paired t-test","d = (\u03bc\u2081\u2212\u03bc\u2082)/SD_pooled", "Small: 0.2 | Medium: 0.5 | Large: \u22650.8"], ["Eta-squared \u03b7\u00b2","One-Way ANOVA","\u03b7\u00b2 = SS_between / SS_total", "Small: 0.01 | Medium: 0.06 | Large: \u22650.14"], ["Partial \u03b7\u00b2","Multi-factor ANOVA, ANCOVA","\u03b7\u00b2p = SS_effect / (SS_eff + SS_err)", "Same thresholds; preferred for multi-factor designs"], ["Omega-squared \u03c9\u00b2","ANOVA (less biased)","\u03c9\u00b2 = (SS_B\u2212df_B\u00b7MS_W)/(SS_T+MS_W)", "Preferred over \u03b7\u00b2 for small samples"], ["Pearson r","Correlation","r is itself the effect size", "Small: 0.1 | Medium: 0.3 | Large: \u22650.5"], ["r (from Z)","Mann-Whitney, Wilcoxon","r = Z / \u221aN", "Small: 0.1 | Medium: 0.3 | Large: \u22650.5"], ["Cram\u00e9r's V","Chi-Square","V = \u221a(\u03c7\u00b2/N\u00b7min(r\u22121,c\u22121))", "Small: 0.1 | Medium: 0.3 | Large: \u22650.5"], ["Odds Ratio (OR)","Logistic Regression, 2\u00d72","OR = (a\u00b7d)/(b\u00b7c)", "OR=1: no effect | >1: increased odds | <1: reduced odds"], ["Hazard Ratio (HR)","Cox Regression","HR = h\u2081(t)/h\u2080(t)", "HR=1: no diff. | >1: higher hazard in group 1"]], [3.0*cm,3.8*cm,5.5*cm,5.2*cm],C_ORA,C_LORA)) story.append(Spacer(1,0.3*cm)) story.append(Paragraph("Statistical Power and Sample Size",H2)) story.append(mktable( ["Parameter","Definition","Typical Value","Impact"], [["\u03b1 (Type I Error)","Reject H\u2080 when it is true (false positive)","0.05 (standard)", "\u2191 \u03b1 \u2192 \u2191 Power but \u2191 false positives"], ["\u03b2 (Type II Error)","Fail to reject false H\u2080 (false negative)","0.20 acceptable", "\u2193 \u03b2 \u2192 \u2191 Power"], ["Power (1\u2212\u03b2)","Probability of detecting a true effect","\u226580% desired", "Increase with larger n or larger effect"], ["Sample size n","Larger n \u2192 smaller SE \u2192 more power","Via a priori power analysis", "Use G*Power or R pwr package"], ["Effect size","Larger effect \u2192 easier to detect","From literature / pilot data", "Smaller effects need considerably larger n"]], [3.0*cm,5.0*cm,3.5*cm,5.0*cm],C_RED,C_LRED)) story.append(PageBreak()) # ═══════════════════════════════════════════════════════ PAGE 8 story.append(Paragraph("Quick-Reference Cheat Sheet",H1)) story.append(HRFlowable(width="100%",thickness=2,color=C_NAVY,spaceAfter=6)) scenarios=[ (C_BLUE, C_SKY, "2 Groups\nIndependent\nNormal", "Independent t-test", "Mann-Whitney U"), (C_BLUE, C_SKY, "2 Groups\nPaired/Matched\nNormal", "Paired t-test", "Wilcoxon Signed-Rank"), (C_GREEN,C_LGRN, "3+ Groups\nIndependent\nNormal", "One-Way ANOVA", "Kruskal-Wallis H"), (C_GREEN,C_LGRN, "3+ Conditions\nSame Subjects\nNormal", "Repeated Measures\nANOVA","Friedman test"), (C_ORA, C_LORA, "2 Factors\nIndependent\nNormal", "Two-Way ANOVA", "Scheirer-Ray-Hare"), (C_TEAL, C_LTEAL,"Correlation\nContinuous\nNormal", "Pearson r", "Spearman \u03c1"), (C_TEAL, C_LTEAL,"Correlation\nOrdinal/\nNon-Normal", "Spearman \u03c1", "Kendall \u03c4"), (C_PURP, C_LPURP,"Categorical\nAssociation", "Chi-Square \u03c7\u00b2", "Fisher's Exact"), (C_PURP, C_LPURP,"Paired\nProportions", "McNemar test", "McNemar test"), (C_RED, C_LRED, "Predict\nContinuous\nOutcome", "Linear\nRegression", "Robust Regression"), (C_RED, C_LRED, "Predict\nBinary\nOutcome", "Logistic\nRegression", "Logistic Regression"), (C_GOLD, C_LGOLD,"Time-to-Event\nSurvival", "Kaplan-Meier\n+Log-rank", "Cox Regression"), ] cw3=(W-3*cm)/3 def card(sc,p,np_,hc,bc): t=Table([ [Paragraph(sc,S("N",fontSize=7.5,textColor=C_GREY,fontName="Helvetica",leading=10,alignment=TA_CENTER))], [Paragraph("Parametric:",S("N",fontSize=7,textColor=hc,fontName="Helvetica-Bold",alignment=TA_CENTER))], [Paragraph(p,S("N",fontSize=9,textColor=hc,fontName="Helvetica-Bold",leading=11,alignment=TA_CENTER))], [Paragraph("Non-Parametric:",S("N",fontSize=7,textColor=C_GREY,fontName="Helvetica-Oblique",alignment=TA_CENTER))], [Paragraph(np_,S("N",fontSize=8.5,textColor=C_GREY,fontName="Helvetica",leading=10,alignment=TA_CENTER))], ],colWidths=[cw3-0.5*cm]) t.setStyle(TableStyle([ ("BACKGROUND",(0,0),(-1,-1),bc),("LINEABOVE",(0,0),(-1,0),3,hc), ("BOX",(0,0),(-1,-1),0.5,hc),("ALIGN",(0,0),(-1,-1),"CENTER"), ("VALIGN",(0,0),(-1,-1),"MIDDLE"), ("TOPPADDING",(0,0),(-1,-1),3),("BOTTOMPADDING",(0,0),(-1,-1),3), ])); return t gdata=[]; grow=[] for i,(hc,bc,sc,p,np_) in enumerate(scenarios): grow.append(card(sc,p,np_,hc,bc)) if len(grow)==3: gdata.append(grow[:]); grow=[] if grow: while len(grow)<3: grow.append(Spacer(cw3,2.9*cm)) gdata.append(grow) grid=Table(gdata,colWidths=[cw3]*3,rowHeights=[2.9*cm]*len(gdata)) grid.setStyle(TableStyle([ ("ALIGN",(0,0),(-1,-1),"CENTER"),("VALIGN",(0,0),(-1,-1),"MIDDLE"), ("LEFTPADDING",(0,0),(-1,-1),4),("RIGHTPADDING",(0,0),(-1,-1),4), ("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4), ])) story.append(grid) story.append(Spacer(1,0.3*cm)) story.append(Paragraph( "<b>Remember:</b> p-value tells you IF an effect exists. " "Effect size tells you HOW LARGE it is. Always report both. " "Statistical significance without effect size is incomplete science.",NT)) doc.build(story) print("SUCCESS:", OUTPUT) PYEOF
Statistical Test Decision Guide
PDF Document · PDF
| Page | Content |
|---|---|
| 1 | Cover page - professional design with topic overview |
| 2 | Six-step decision framework + Types of variables table |
| 3 | Master reference table - 18 scenarios mapped to parametric & non-parametric tests |
| 4 | Parametric tests in depth - t-test, paired t-test, One-Way ANOVA, RM-ANOVA, Two-Way ANOVA (with assumptions, formulas, examples) |
| 5 | Non-parametric tests in depth - Mann-Whitney, Wilcoxon, Kruskal-Wallis, Friedman, Chi-square, Fisher's, McNemar, Spearman, Dunn's |
| 6 | Assumption testing - Normality (Shapiro-Wilk, K-S, Q-Q), Homogeneity (Levene's, Bartlett's), Sphericity (Mauchly's), full checklist |
| 7 | Effect size (Cohen's d, η², OR, HR, Cramér's V) + Statistical power and sample size |
| 8 | Quick cheat sheet - 12 colour-coded scenario cards with parametric and non-parametric choices at a glance |