Skill Tracer
Skills › Scientific Agent Skills › statistical-analysis › effect_sizes_and_power.md

effect_sizes_and_power.md

656 lines · skills/statistical-analysis/references/effect_sizes_and_power.md at 5f2bc03

Open on GitHub
1# Effect Sizes and Power Analysis
2
3This document provides guidance on calculating, interpreting, and reporting effect sizes, as well as conducting power analyses for study planning.
4

Effect Sizes and Power Analysis

This document provides guidance on calculating, interpreting, and reporting effect sizes, as well as conducting power analyses for study planning.

Raw lines 1 to 4
5## Why Effect Sizes Matter
6
71. **Statistical significance ≠ practical significance**: p-values measure incompatibility with a null model under its assumptions, not existence or magnitude
82. **Sample size dependent**: With large samples, trivial effects become "significant"
93. **Interpretation**: Effect sizes provide magnitude and practical importance
104. **Meta-analysis**: Effect sizes enable combining results across studies
115. **Power analysis**: Required for sample size determination
13**Golden rule**: ALWAYS report effect sizes alongside p-values.
15---

Why Effect Sizes Matter

  1. Statistical significance ≠ practical significance: p-values measure incompatibility with a null model under its assumptions, not existence or magnitude
  2. Sample size dependent: With large samples, trivial effects become "significant"
  3. Interpretation: Effect sizes provide magnitude and practical importance
  4. Meta-analysis: Effect sizes enable combining results across studies
  5. Power analysis: Required for sample size determination

Golden rule: ALWAYS report effect sizes alongside p-values.


Raw lines 5 to 16
17## Effect Sizes by Analysis Type

Effect Sizes by Analysis Type

Raw lines 17 to 18
19### T-Tests and Mean Differences
21#### Cohen's d (Standardized Mean Difference)
23**Formula**:
24- Independent groups: d = (M₁ - M₂) / SD_pooled
25- Paired groups: specify the standardizer. d_z = M_diff / SD_diff; d_av = M_diff / sqrt((SD_pre² + SD_post²)/2). These are different effects.
27**Interpretation** (Cohen, 1988):
28- Small: |d| = 0.20
29- Medium: |d| = 0.50
30- Large: |d| = 0.80
32**Context-dependent interpretation**:
33- Justify a smallest scientifically meaningful effect from the outcome, intervention and context; no discipline-wide threshold is universally meaningful.
34- In medicine: Small effect sizes can be clinically important
36**Python calculation**:
37```python
38import pingouin as pg
39import numpy as np
41# Independent t-test with effect size
42result = pg.ttest(group1, group2, correction=False)
43cohens_d = pg.compute_effsize(group1, group2, eftype='cohen') # signed; ttest returns abs(d)
44# (pingouin 0.6.0 renamed columns; on 0.5.x use 'p-val', 'cohen-d', 'CI95%', 'p-unc')
46# Manual calculation
47mean_diff = np.mean(group1) - np.mean(group2)
48n1, n2 = len(group1), len(group2)
49pooled_std = np.sqrt(((n1 - 1) * np.var(group1, ddof=1) + (n2 - 1) * np.var(group2, ddof=1)) / (n1 + n2 - 2))
50cohens_d = mean_diff / pooled_std
52# Paired t-test
53result = pg.ttest(pre, post, paired=True)
54d_av = pg.compute_effsize(pre, post, paired=True, eftype='cohen')
55d_z = pg.compute_effsize(pre, post, paired=True, eftype='cohen_dz')
56```
58**Confidence intervals for independent pooled d** (approximate parametric interval; supply analyzed, non-missing sample sizes; do not reuse this formula for d_av or assume unequal-variance validity):
59```python
60import pingouin as pg
62# compute_effsize_from_t returns only the point estimate;
63# get the CI separately with compute_esci
64d = pg.compute_effsize(group1, group2, eftype='cohen')
65ci = pg.compute_esci(stat=d, nx=len(group1), ny=len(group2),
66 eftype='cohen', confidence=0.95)
67```
69---
71#### Hedges' g (Bias-Corrected d)
73**Why use it**: Cohen's d has slight upward bias with small samples (n < 20)
75**Formula**: g = d × correction_factor, where correction_factor = 1 - 3/(4df - 1)
77**Python calculation**:
78```python
79# pg.ttest output has no Hedges' g column; compute it directly
80hedges_g = pg.compute_effsize(group1, group2, eftype='hedges')
81```
83**Use Hedges' g when**:
84- Sample sizes are small (n < 20 per group)
85- Conducting meta-analyses (standard in meta-analysis)
87---
89#### Glass's Δ (Delta)
91**When to use**: When one group is a control with known variability
93**Formula**: Δ = (M₁ - M₂) / SD_control
95**Use cases**:
96- Clinical trials (use control group SD)
97- When treatment affects variability
99---

T-Tests and Mean Differences

Cohen's d (Standardized Mean Difference)

Formula:

  • Independent groups: d = (M₁ - M₂) / SD_pooled
  • Paired groups: specify the standardizer. d_z = M_diff / SD_diff; d_av = M_diff / sqrt((SD_pre² + SD_post²)/2). These are different effects.

Interpretation (Cohen, 1988):

  • Small: |d| = 0.20
  • Medium: |d| = 0.50
  • Large: |d| = 0.80

Context-dependent interpretation:

  • Justify a smallest scientifically meaningful effect from the outcome, intervention and context; no discipline-wide threshold is universally meaningful.
  • In medicine: Small effect sizes can be clinically important

Python calculation:

import pingouin as pg
import numpy as np

# Independent t-test with effect size
result = pg.ttest(group1, group2, correction=False)
cohens_d = pg.compute_effsize(group1, group2, eftype='cohen')  # signed; ttest returns abs(d)
# (pingouin 0.6.0 renamed columns; on 0.5.x use 'p-val', 'cohen-d', 'CI95%', 'p-unc')

# Manual calculation
mean_diff = np.mean(group1) - np.mean(group2)
n1, n2 = len(group1), len(group2)
pooled_std = np.sqrt(((n1 - 1) * np.var(group1, ddof=1) + (n2 - 1) * np.var(group2, ddof=1)) / (n1 + n2 - 2))
cohens_d = mean_diff / pooled_std

# Paired t-test
result = pg.ttest(pre, post, paired=True)
d_av = pg.compute_effsize(pre, post, paired=True, eftype='cohen')
d_z = pg.compute_effsize(pre, post, paired=True, eftype='cohen_dz')

Confidence intervals for independent pooled d (approximate parametric interval; supply analyzed, non-missing sample sizes; do not reuse this formula for d_av or assume unequal-variance validity):

import pingouin as pg

# compute_effsize_from_t returns only the point estimate;
# get the CI separately with compute_esci
d = pg.compute_effsize(group1, group2, eftype='cohen')
ci = pg.compute_esci(stat=d, nx=len(group1), ny=len(group2),
                     eftype='cohen', confidence=0.95)

Hedges' g (Bias-Corrected d)

Why use it: Cohen's d has slight upward bias with small samples (n < 20)

Formula: g = d × correction_factor, where correction_factor = 1 - 3/(4df - 1)

Python calculation:

# pg.ttest output has no Hedges' g column; compute it directly
hedges_g = pg.compute_effsize(group1, group2, eftype='hedges')

Use Hedges' g when:

  • Sample sizes are small (n < 20 per group)
  • Conducting meta-analyses (standard in meta-analysis)

Glass's Δ (Delta)

When to use: When one group is a control with known variability

Formula: Δ = (M₁ - M₂) / SD_control

Use cases:

  • Clinical trials (use control group SD)
  • When treatment affects variability

Raw lines 19 to 100
101### ANOVA
103#### Eta-squared (η²)
105**What it measures**: Proportion of total variance explained by factor
107**Formula**: η² = SS_effect / SS_total
109**Interpretation**:
110- Small: η² = 0.01 (1% of variance)
111- Medium: η² = 0.06 (6% of variance)
112- Large: η² = 0.14 (14% of variance)
114**Limitation**: In multi-factor designs each effect's η² shrinks as other factors are added (orthogonal decompositions give additive shares; Type II/III sums in unbalanced designs need not add to total SS); it is partial η² that can sum to > 1.0 across factors
116**Python calculation**:
117```python
118import pingouin as pg
120# One-way ANOVA (detailed=True is required for the SS column)
121aov = pg.anova(dv='value', between='group', data=df, detailed=True)
122eta_squared = aov['SS'][0] / aov['SS'].sum()
124# Or read pingouin's np2 column, which is PARTIAL eta-squared:
125partial_eta_sq = aov['np2'][0]
126# np2 coincides with classical eta-squared only for one-way (single-factor) designs
127```
129---
131#### Partial Eta-squared (η²_p)
133**What it measures**: Proportion of variance explained by factor, excluding other factors
135**Formula**: η²_p = SS_effect / (SS_effect + SS_error)
137**Interpretation**: Same benchmarks as η²
139**When to use**: Multi-factor ANOVA (standard in factorial designs)
141**Limitation**: Across factors, partial η² values can sum to > 1.0 — they are not additive shares of total variance
143**Python calculation**:
144```python
145aov = pg.anova(dv='value', between=['factor1', 'factor2'], data=df)
146# pingouin reports partial eta-squared by default
147partial_eta_sq = aov['np2']
148```
150---
152#### Omega-squared (ω²)
154**What it measures**: Less biased estimate of population variance explained
156**Why use it**: η² overestimates effect size; ω² provides better population estimate
158**Formula**: ω² = (SS_effect - df_effect × MS_error) / (SS_total + MS_error)
160**Interpretation**: Same benchmarks as η², but typically smaller values
162**Python calculation** (one-way table with effect row first and residual row last; the estimator can be negative, so state any truncation convention):
163```python
164def omega_squared(aov_table):
165 ss_effect = aov_table.loc[0, 'SS']
166 ss_total = aov_table['SS'].sum()
167 ms_error = aov_table.loc[aov_table.index[-1], 'MS'] # Residual MS
168 df_effect = aov_table.loc[0, 'DF']
170 omega_sq = (ss_effect - df_effect * ms_error) / (ss_total + ms_error)
171 return omega_sq
172```
174---
176#### Cohen's f
178**What it measures**: Effect size for ANOVA (analogous to Cohen's d)
180**Formula**: f = √(η² / (1 - η²))
182**Interpretation**:
183- Small: f = 0.10
184- Medium: f = 0.25
185- Large: f = 0.40
187**Python calculation**:
188```python
189eta_squared = 0.06 # From ANOVA
190cohens_f = np.sqrt(eta_squared / (1 - eta_squared))
191```
193**Use in power analysis**: Required for ANOVA power calculations
195---

ANOVA

Eta-squared (η²)

What it measures: Proportion of total variance explained by factor

Formula: η² = SS_effect / SS_total

Interpretation:

  • Small: η² = 0.01 (1% of variance)
  • Medium: η² = 0.06 (6% of variance)
  • Large: η² = 0.14 (14% of variance)

Limitation: In multi-factor designs each effect's η² shrinks as other factors are added (orthogonal decompositions give additive shares; Type II/III sums in unbalanced designs need not add to total SS); it is partial η² that can sum to > 1.0 across factors

Python calculation:

import pingouin as pg

# One-way ANOVA (detailed=True is required for the SS column)
aov = pg.anova(dv='value', between='group', data=df, detailed=True)
eta_squared = aov['SS'][0] / aov['SS'].sum()

# Or read pingouin's np2 column, which is PARTIAL eta-squared:
partial_eta_sq = aov['np2'][0]
# np2 coincides with classical eta-squared only for one-way (single-factor) designs

Partial Eta-squared (η²_p)

What it measures: Proportion of variance explained by factor, excluding other factors

Formula: η²_p = SS_effect / (SS_effect + SS_error)

Interpretation: Same benchmarks as η²

When to use: Multi-factor ANOVA (standard in factorial designs)

Limitation: Across factors, partial η² values can sum to > 1.0 — they are not additive shares of total variance

Python calculation:

aov = pg.anova(dv='value', between=['factor1', 'factor2'], data=df)
# pingouin reports partial eta-squared by default
partial_eta_sq = aov['np2']

Omega-squared (ω²)

What it measures: Less biased estimate of population variance explained

Why use it: η² overestimates effect size; ω² provides better population estimate

Formula: ω² = (SS_effect - df_effect × MS_error) / (SS_total + MS_error)

Interpretation: Same benchmarks as η², but typically smaller values

Python calculation (one-way table with effect row first and residual row last; the estimator can be negative, so state any truncation convention):

def omega_squared(aov_table):
    ss_effect = aov_table.loc[0, 'SS']
    ss_total = aov_table['SS'].sum()
    ms_error = aov_table.loc[aov_table.index[-1], 'MS']  # Residual MS
    df_effect = aov_table.loc[0, 'DF']

    omega_sq = (ss_effect - df_effect * ms_error) / (ss_total + ms_error)
    return omega_sq

Cohen's f

What it measures: Effect size for ANOVA (analogous to Cohen's d)

Formula: f = √(η² / (1 - η²))

Interpretation:

  • Small: f = 0.10
  • Medium: f = 0.25
  • Large: f = 0.40

Python calculation:

eta_squared = 0.06  # From ANOVA
cohens_f = np.sqrt(eta_squared / (1 - eta_squared))

Use in power analysis: Required for ANOVA power calculations


Raw lines 101 to 196
197### Correlation
199#### Pearson's r / Spearman's ρ
201**Interpretation**:
202- Small: |r| = 0.10
203- Medium: |r| = 0.30
204- Large: |r| = 0.50
206**Important notes**:
207- For Pearson correlation in simple OLS with an intercept, r² equals R². Spearman ρ² concerns ranks and is not raw-outcome variance explained.
208- Pearson r = 0.30 gives r² = 0.09; association is not causal explanation.
209- Consider direction (positive/negative) and context
211**Python calculation**:
212```python
213import pingouin as pg
215# Pearson correlation with CI
216result = pg.corr(x, y, method='pearson')
217r = result['r'].values[0]
218ci = result['CI95'].values[0] # pingouin 0.6.0 renamed CI95% to CI95
220# Spearman correlation
221result = pg.corr(x, y, method='spearman')
222rho = result['r'].values[0]
223```
225---

Correlation

Pearson's r / Spearman's ρ

Interpretation:

  • Small: |r| = 0.10
  • Medium: |r| = 0.30
  • Large: |r| = 0.50

Important notes:

  • For Pearson correlation in simple OLS with an intercept, r² equals R². Spearman ρ² concerns ranks and is not raw-outcome variance explained.
  • Pearson r = 0.30 gives r² = 0.09; association is not causal explanation.
  • Consider direction (positive/negative) and context

Python calculation:

import pingouin as pg

# Pearson correlation with CI
result = pg.corr(x, y, method='pearson')
r = result['r'].values[0]
ci = result['CI95'].values[0]  # pingouin 0.6.0 renamed CI95% to CI95

# Spearman correlation
result = pg.corr(x, y, method='spearman')
rho = result['r'].values[0]

Raw lines 197 to 226
227### Regression
229#### R² (Coefficient of Determination)
231**What it measures**: Proportion of variance in Y explained by model
233**Interpretation**:
234- Small: R² = 0.02
235- Medium: R² = 0.13
236- Large: R² = 0.26
238**Context-dependent**:
239- Interpret R² relative to outcome variability, measurement error and purpose; no field-wide cutoff establishes fit or predictive performance. Evaluate predictions on held-out data when prediction is the goal.
241**Python calculation**:
242```python
243import statsmodels.api as sm
245# Using statsmodels (add_constant adds the intercept column)
246model = sm.OLS(y, sm.add_constant(X)).fit()
247r_squared = model.rsquared
248adjusted_r_squared = model.rsquared_adj
250# Manual
251r_squared = 1 - (SS_residual / SS_total)
252```
254---
256#### Adjusted R²
258**Why use it**: R² artificially increases when adding predictors; adjusted R² penalizes model complexity
260**Formula**: R²_adj = 1 - (1 - R²) × (n - 1) / (n - k - 1)
262**When to use**: Always report alongside R² for multiple regression
264---
266#### Standardized Regression Coefficients (β)
268**What it measures**: Effect of one-SD change in predictor on outcome (in SD units)
270**Interpretation**: Conditional on all included predictors and their coding; β has no universal small/medium/large thresholds and is not Cohen's d.
272**Python calculation**:
273```python
274import statsmodels.api as sm
276# Numeric predictor DataFrame, each column nonconstant; standardize columnwise
277X_std = (X - X.mean()) / X.std()
278y_std = (y - y.mean()) / y.std()
280model = sm.OLS(y_std, sm.add_constant(X_std)).fit()
281beta = model.params
282```
284---
286#### f² (Cohen's f-squared for Regression)
288**What it measures**: Effect size for individual predictors or model comparison
290**Formula**: f² = (R²_AB - R²_A) / (1 - R²_AB)
292Where:
293- R²_AB = R² for full model with predictor
294- R²_A = R² for reduced model without predictor
296**Interpretation**:
297- Small: f² = 0.02
298- Medium: f² = 0.15
299- Large: f² = 0.35
301**Python calculation**:
302```python
303# Compare two nested models
304import statsmodels.api as sm
305# Same observations and outcome in both models; include intercepts
306model_full = sm.OLS(y, sm.add_constant(X_full)).fit()
307model_reduced = sm.OLS(y, sm.add_constant(X_reduced)).fit()
309r2_full = model_full.rsquared
310r2_reduced = model_reduced.rsquared
312f_squared = (r2_full - r2_reduced) / (1 - r2_full)
313```
315---

Regression

R² (Coefficient of Determination)

What it measures: Proportion of variance in Y explained by model

Interpretation:

  • Small: R² = 0.02
  • Medium: R² = 0.13
  • Large: R² = 0.26

Context-dependent:

  • Interpret R² relative to outcome variability, measurement error and purpose; no field-wide cutoff establishes fit or predictive performance. Evaluate predictions on held-out data when prediction is the goal.

Python calculation:

import statsmodels.api as sm

# Using statsmodels (add_constant adds the intercept column)
model = sm.OLS(y, sm.add_constant(X)).fit()
r_squared = model.rsquared
adjusted_r_squared = model.rsquared_adj

# Manual
r_squared = 1 - (SS_residual / SS_total)

Adjusted R²

Why use it: R² artificially increases when adding predictors; adjusted R² penalizes model complexity

Formula: R²_adj = 1 - (1 - R²) × (n - 1) / (n - k - 1)

When to use: Always report alongside R² for multiple regression


Standardized Regression Coefficients (β)

What it measures: Effect of one-SD change in predictor on outcome (in SD units)

Interpretation: Conditional on all included predictors and their coding; β has no universal small/medium/large thresholds and is not Cohen's d.

Python calculation:

import statsmodels.api as sm

# Numeric predictor DataFrame, each column nonconstant; standardize columnwise
X_std = (X - X.mean()) / X.std()
y_std = (y - y.mean()) / y.std()

model = sm.OLS(y_std, sm.add_constant(X_std)).fit()
beta = model.params

f² (Cohen's f-squared for Regression)

What it measures: Effect size for individual predictors or model comparison

Formula: f² = (R²_AB - R²_A) / (1 - R²_AB)

Where:

  • R²_AB = R² for full model with predictor
  • R²_A = R² for reduced model without predictor

Interpretation:

  • Small: f² = 0.02
  • Medium: f² = 0.15
  • Large: f² = 0.35

Python calculation:

# Compare two nested models
import statsmodels.api as sm
# Same observations and outcome in both models; include intercepts
model_full = sm.OLS(y, sm.add_constant(X_full)).fit()
model_reduced = sm.OLS(y, sm.add_constant(X_reduced)).fit()

r2_full = model_full.rsquared
r2_reduced = model_reduced.rsquared

f_squared = (r2_full - r2_reduced) / (1 - r2_full)

Raw lines 227 to 316
317### Categorical Data Analysis
319#### Cramér's V
321**What it measures**: Association strength for χ² test (works for any table size)
323**Formula**: V = √(χ² / (n × (k - 1)))
325Where k = min(rows, columns)
327**Interpretation** (benchmarks depend on df* = min(rows, columns) − 1):
329| df* | Small | Medium | Large |
330|-----|-------|--------|-------|
331| 1 (2×2) | 0.10 | 0.30 | 0.50 |
332| 2 | 0.07 | 0.21 | 0.35 |
333| 3 | 0.06 | 0.17 | 0.29 |
335**For 2×2 tables**: Use phi coefficient (φ)
337**Python calculation**:
338```python
339import numpy as np
340from scipy.stats.contingency import association
342# Cramér's V
343cramers_v = association(contingency_table, method='cramer')
345# Phi coefficient (2x2): |phi| equals Cramér's V for a 2x2 table.
346# Caution: method='pearson' is Pearson's contingency coefficient, NOT phi.
347a, b, c, d = np.asarray(contingency_table, dtype=float).ravel()
348phi = (a * d - b * c) / np.sqrt((a + b) * (c + d) * (a + c) * (b + d)) # signed phi
349```
351---
353#### Odds Ratio (OR) and Risk Ratio (RR)
355**For 2×2 contingency tables**:
357| | Outcome + | Outcome - |
358|-----------|-----------|-----------|
359| Exposed | a | b |
360| Unexposed | c | d |
362**Odds Ratio**: OR = (a/b) / (c/d) = ad / bc
364**Interpretation**:
365- OR = 1: No association
366- OR > 1: Positive association (increased odds)
367- OR < 1: Negative association (decreased odds)
368- OR = 2: Twice the odds
369- OR = 0.5: Half the odds
371**Risk Ratio**: RR = (a/(a+b)) / (c/(c+d))
373**When to use**:
374- Cohort studies: Use RR (more interpretable)
375- Case-control studies: Use OR (RR not available)
376- Logistic regression: OR is natural output
378**Python calculation**:
379```python
380import numpy as np
381from scipy import stats
382import statsmodels.api as sm
384# From contingency table
385odds_ratio = (a * d) / (b * c)
387# For a 2x2 table with method=None, Fisher returns sample OR and p-value (no CI);
388# larger/resampled tables have a different statistic. Require nonnegative integer counts.
389table = np.array([[a, b], [c, d]])
390oddsratio, pvalue = stats.fisher_exact(table)
392# Odds-ratio confidence interval (scipy >= 1.10; conditional MLE estimate)
393or_result = stats.contingency.odds_ratio(table)
394ci = or_result.confidence_interval(confidence_level=0.95)
396# From logistic regression
397model = sm.Logit(y_binary, sm.add_constant(X)).fit()
398odds_ratios = np.exp(model.params) # Exponentiate coefficients
399ci = np.exp(model.conf_int()) # Exponentiate CIs
400```
402---

Categorical Data Analysis

Cramér's V

What it measures: Association strength for χ² test (works for any table size)

Formula: V = √(χ² / (n × (k - 1)))

Where k = min(rows, columns)

Interpretation (benchmarks depend on df* = min(rows, columns) − 1):

df*SmallMediumLarge
1 (2×2)0.100.300.50
20.070.210.35
30.060.170.29

For 2×2 tables: Use phi coefficient (φ)

Python calculation:

import numpy as np
from scipy.stats.contingency import association

# Cramér's V
cramers_v = association(contingency_table, method='cramer')

# Phi coefficient (2x2): |phi| equals Cramér's V for a 2x2 table.
# Caution: method='pearson' is Pearson's contingency coefficient, NOT phi.
a, b, c, d = np.asarray(contingency_table, dtype=float).ravel()
phi = (a * d - b * c) / np.sqrt((a + b) * (c + d) * (a + c) * (b + d))  # signed phi

Odds Ratio (OR) and Risk Ratio (RR)

For 2×2 contingency tables:

Outcome +Outcome -
Exposedab
Unexposedcd

Odds Ratio: OR = (a/b) / (c/d) = ad / bc

Interpretation:

  • OR = 1: No association
  • OR > 1: Positive association (increased odds)
  • OR < 1: Negative association (decreased odds)
  • OR = 2: Twice the odds
  • OR = 0.5: Half the odds

Risk Ratio: RR = (a/(a+b)) / (c/(c+d))

When to use:

  • Cohort studies: Use RR (more interpretable)
  • Case-control studies: Use OR (RR not available)
  • Logistic regression: OR is natural output

Python calculation:

import numpy as np
from scipy import stats
import statsmodels.api as sm

# From contingency table
odds_ratio = (a * d) / (b * c)

# For a 2x2 table with method=None, Fisher returns sample OR and p-value (no CI);
# larger/resampled tables have a different statistic. Require nonnegative integer counts.
table = np.array([[a, b], [c, d]])
oddsratio, pvalue = stats.fisher_exact(table)

# Odds-ratio confidence interval (scipy >= 1.10; conditional MLE estimate)
or_result = stats.contingency.odds_ratio(table)
ci = or_result.confidence_interval(confidence_level=0.95)

# From logistic regression
model = sm.Logit(y_binary, sm.add_constant(X)).fit()
odds_ratios = np.exp(model.params)  # Exponentiate coefficients
ci = np.exp(model.conf_int())  # Exponentiate CIs

Raw lines 317 to 403
404### Nonparametric Effect Sizes
406**Rank-biserial correlation (r_rb)**: Effect size for Mann-Whitney U and Wilcoxon signed-rank tests (range −1 to 1; interpret |r_rb| roughly like r). Returned by `pg.mwu` and `pg.wilcoxon` as the `RBC` column.
408**Common-language effect size (CLES)**: P(X > Y) + 0.5 P(X = Y) for independent group draws (0.5 is no directional superiority, not proof of identical distributions). Returned by `pg.mwu` as `CLES`.
410**r = z / √N**: Classic effect size when a z approximation is reported for Mann-Whitney/Wilcoxon (small 0.10, medium 0.30, large 0.50).
412**Epsilon-squared (ε²)**: Effect size for Kruskal-Wallis: ε² = H × (n + 1) / (n² − 1).
414**Python calculation**:
415```python
416import numpy as np
417import pandas as pd
418import pingouin as pg
420res = pg.mwu(group1, group2)
421print(res[['U_val', 'p_val', 'RBC', 'CLES']])
423# Kruskal-Wallis with epsilon-squared
424df = pd.DataFrame({'value': np.concatenate([group1, group2, group3]),
425 'group': np.repeat(['a', 'b', 'c'],
426 [len(group1), len(group2), len(group3)])})
427kw = pg.kruskal(df, dv='value', between='group')
428H, n = kw['H'].values[0], len(df)
429epsilon_sq = H * (n + 1) / (n**2 - 1)
430```
432---

Nonparametric Effect Sizes

Rank-biserial correlation (r_rb): Effect size for Mann-Whitney U and Wilcoxon signed-rank tests (range −1 to 1; interpret |r_rb| roughly like r). Returned by pg.mwu and pg.wilcoxon as the RBC column.

Common-language effect size (CLES): P(X > Y) + 0.5 P(X = Y) for independent group draws (0.5 is no directional superiority, not proof of identical distributions). Returned by pg.mwu as CLES.

r = z / √N: Classic effect size when a z approximation is reported for Mann-Whitney/Wilcoxon (small 0.10, medium 0.30, large 0.50).

Epsilon-squared (ε²): Effect size for Kruskal-Wallis: ε² = H × (n + 1) / (n² − 1).

Python calculation:

import numpy as np
import pandas as pd
import pingouin as pg

res = pg.mwu(group1, group2)
print(res[['U_val', 'p_val', 'RBC', 'CLES']])

# Kruskal-Wallis with epsilon-squared
df = pd.DataFrame({'value': np.concatenate([group1, group2, group3]),
                   'group': np.repeat(['a', 'b', 'c'],
                                      [len(group1), len(group2), len(group3)])})
kw = pg.kruskal(df, dv='value', between='group')
H, n = kw['H'].values[0], len(df)
epsilon_sq = H * (n + 1) / (n**2 - 1)

Raw lines 404 to 433
434### Bayesian Evidence (Not an Effect Size)
436#### Bayes Factor (BF)
438**What it measures**: Ratio of evidence for alternative vs. null hypothesis
440**Interpretation**:
441- BF₁₀ = 1: Equal evidence for H₁ and H₀
442- BF₁₀ = 3: the data have 3× greater marginal likelihood under H₁ than H₀ (moderate evidence)
443- BF₁₀ = 10: the data have 10× greater marginal likelihood under H₁ than H₀ (strong evidence)
444- BF₁₀ > 100: Decisive evidence for H₁ (30-100 counts as "very strong" on the Jeffreys scale)
445- BF₁₀ = 0.33: the data have about 3× greater marginal likelihood under H₀ than H₁
446- BF₁₀ = 0.10: the data have 10× greater marginal likelihood under H₀ than H₁
448Posterior odds = Bayes factor × prior odds; BF alone is not posterior model probability. For the full Jeffreys interpretation table and BF reporting language, see `bayesian_statistics.md`.
450**Python calculation**:
451```python
452import pingouin as pg
454# Pingouin 0.5+: two-sided BF10 on independent t-tests; use BayesFactor/JASP/PyMC for full inference
455result = pg.ttest(group1, group2, correction=False)
456bf10 = float(result['BF10'].values[0]) # formatted string in default Pingouin output
457```
459---

Bayesian Evidence (Not an Effect Size)

Bayes Factor (BF)

What it measures: Ratio of evidence for alternative vs. null hypothesis

Interpretation:

  • BF₁₀ = 1: Equal evidence for H₁ and H₀
  • BF₁₀ = 3: the data have 3× greater marginal likelihood under H₁ than H₀ (moderate evidence)
  • BF₁₀ = 10: the data have 10× greater marginal likelihood under H₁ than H₀ (strong evidence)
  • BF₁₀ > 100: Decisive evidence for H₁ (30-100 counts as "very strong" on the Jeffreys scale)
  • BF₁₀ = 0.33: the data have about 3× greater marginal likelihood under H₀ than H₁
  • BF₁₀ = 0.10: the data have 10× greater marginal likelihood under H₀ than H₁

Posterior odds = Bayes factor × prior odds; BF alone is not posterior model probability. For the full Jeffreys interpretation table and BF reporting language, see bayesian_statistics.md.

Python calculation:

import pingouin as pg

# Pingouin 0.5+: two-sided BF10 on independent t-tests; use BayesFactor/JASP/PyMC for full inference
result = pg.ttest(group1, group2, correction=False)
bf10 = float(result['BF10'].values[0])  # formatted string in default Pingouin output

Raw lines 434 to 460
461### Bootstrap Confidence Intervals
463When no analytic CI exists for an effect size (or its assumptions are doubtful), bootstrap one:
465```python
466import numpy as np
467from scipy import stats
469def cohen_d(x, y):
470 nx, ny = len(x), len(y)
471 sp = np.sqrt(((nx - 1) * np.var(x, ddof=1) + (ny - 1) * np.var(y, ddof=1))
472 / (nx + ny - 2))
473 return (np.mean(x) - np.mean(y)) / sp
475boot = stats.bootstrap((group1, group2), cohen_d, n_resamples=9999,
476 method='BCa', rng=np.random.default_rng(42))
477print(boot.confidence_interval) # 95% BCa CI by default
478```
480Resample the sampling unit: correlations and paired effects require `paired=True`, and clustered designs require cluster resampling. BCa can be undefined for degenerate/discrete samples; inspect warnings, finite interval endpoints and Monte Carlo stability. More resamples cannot fix the wrong design.
482---

Bootstrap Confidence Intervals

When no analytic CI exists for an effect size (or its assumptions are doubtful), bootstrap one:

import numpy as np
from scipy import stats

def cohen_d(x, y):
    nx, ny = len(x), len(y)
    sp = np.sqrt(((nx - 1) * np.var(x, ddof=1) + (ny - 1) * np.var(y, ddof=1))
                 / (nx + ny - 2))
    return (np.mean(x) - np.mean(y)) / sp

boot = stats.bootstrap((group1, group2), cohen_d, n_resamples=9999,
                       method='BCa', rng=np.random.default_rng(42))
print(boot.confidence_interval)  # 95% BCa CI by default

Resample the sampling unit: correlations and paired effects require paired=True, and clustered designs require cluster resampling. BCa can be undefined for degenerate/discrete samples; inspect warnings, finite interval endpoints and Monte Carlo stability. More resamples cannot fix the wrong design.


Raw lines 461 to 483
484## Power Analysis

Power Analysis

Raw lines 484 to 485
486### Concepts
488**Statistical power**: Probability of rejecting a specified null under a specified alternative effect, variance and design (1 - β).
490**Conventional standards**:
491- Power = 0.80 (80% chance of detecting effect)
492- α = 0.05 (5% Type I error rate)
494**Four interconnected parameters** (given 3, can solve for 4th):
4951. Sample size (n)
4962. Effect size (d, f, etc.)
4973. Significance level (α)
4984. Power (1 - β)
500---

Concepts

Statistical power: Probability of rejecting a specified null under a specified alternative effect, variance and design (1 - β).

Conventional standards:

  • Power = 0.80 (80% chance of detecting effect)
  • α = 0.05 (5% Type I error rate)

Four interconnected parameters (given 3, can solve for 4th):

  1. Sample size (n)
  2. Effect size (d, f, etc.)
  3. Significance level (α)
  4. Power (1 - β)

Raw lines 486 to 501
502### A Priori Power Analysis (Planning)
504**Purpose**: Determine required sample size before study
506**Steps**:
5071. Specify expected effect size (from literature, pilot data, or minimum meaningful effect)
5082. Set α level (typically 0.05)
5093. Set desired power (typically 0.80)
5104. Calculate required n
512**Python implementation**:
513```python
514from statsmodels.stats.power import (
515 tt_ind_solve_power,
516 zt_ind_solve_power,
517 FTestAnovaPower,
518 NormalIndPower
521# T-test power analysis
522n_required = tt_ind_solve_power(
523 effect_size=0.5, # Cohen's d
524 alpha=0.05,
525 power=0.80,
526 ratio=1.0, # Equal group sizes
527 alternative='two-sided'
530# ANOVA power analysis (kwarg is k_groups, not ngroups)
531anova_power = FTestAnovaPower()
532n_total = anova_power.solve_power(
533 effect_size=0.25, # Cohen's f
534 k_groups=3,
535 alpha=0.05,
536 power=0.80
538# Returns the TOTAL sample size across all groups:
539# f = 0.25, k = 3 -> ~158 total; round UP to 53/group, implemented total 159
540import math
541planned_n_per_group = math.ceil(n_required)
542planned_anova_per_group = math.ceil(n_total / 3)
544# Correlation power analysis
545from pingouin import power_corr
546n_required = power_corr(r=0.30, power=0.80, alpha=0.05)
547```
549---

A Priori Power Analysis (Planning)

Purpose: Determine required sample size before study

Steps:

  1. Specify expected effect size (from literature, pilot data, or minimum meaningful effect)
  2. Set α level (typically 0.05)
  3. Set desired power (typically 0.80)
  4. Calculate required n

Python implementation:

from statsmodels.stats.power import (
    tt_ind_solve_power,
    zt_ind_solve_power,
    FTestAnovaPower,
    NormalIndPower
)

# T-test power analysis
n_required = tt_ind_solve_power(
    effect_size=0.5,  # Cohen's d
    alpha=0.05,
    power=0.80,
    ratio=1.0,  # Equal group sizes
    alternative='two-sided'
)

# ANOVA power analysis (kwarg is k_groups, not ngroups)
anova_power = FTestAnovaPower()
n_total = anova_power.solve_power(
    effect_size=0.25,  # Cohen's f
    k_groups=3,
    alpha=0.05,
    power=0.80
)
# Returns the TOTAL sample size across all groups:
# f = 0.25, k = 3 -> ~158 total; round UP to 53/group, implemented total 159
import math
planned_n_per_group = math.ceil(n_required)
planned_anova_per_group = math.ceil(n_total / 3)

# Correlation power analysis
from pingouin import power_corr
n_required = power_corr(r=0.30, power=0.80, alpha=0.05)

Raw lines 502 to 550
551### Post Hoc Power Analysis (After Study)
553**⚠️ CAUTION**: Post hoc power is controversial and often not recommended
555**Why it's problematic**:
556- Observed power is a direct function of p-value
557- If p > 0.05, power is always low
558- Provides no additional information beyond p-value
559- Can be misleading
561**When it might be acceptable**:
562- Study planning for future research
563- Using effect size from multiple studies (not just your own)
564- Explicit goal is sample size for replication
566**Better alternatives**:
567- Report confidence intervals for effect sizes
568- Conduct sensitivity analysis
569- Report minimum detectable effect size
571---

Post Hoc Power Analysis (After Study)

⚠️ CAUTION: Post hoc power is controversial and often not recommended

Why it's problematic:

  • Observed power is a direct function of p-value
  • If p > 0.05, power is always low
  • Provides no additional information beyond p-value
  • Can be misleading

When it might be acceptable:

  • Study planning for future research
  • Using effect size from multiple studies (not just your own)
  • Explicit goal is sample size for replication

Better alternatives:

  • Report confidence intervals for effect sizes
  • Conduct sensitivity analysis
  • Report minimum detectable effect size

Raw lines 551 to 572
573### Sensitivity Analysis
575**Purpose**: Determine minimum detectable effect size given study parameters
577**When to use**: After study is complete, to understand study's capability
579**Python implementation**:
580```python
581# What effect size could we detect with n=50 per group?
582detectable_effect = tt_ind_solve_power(
583 effect_size=None, # Solve for this
584 nobs1=50,
585 alpha=0.05,
586 power=0.80,
587 ratio=1.0,
588 alternative='two-sided'
591print(f"With n=50 per group, we could detect d ≥ {detectable_effect:.2f}")
592```
594---

Sensitivity Analysis

Purpose: Determine minimum detectable effect size given study parameters

When to use: After study is complete, to understand study's capability

Python implementation:

# What effect size could we detect with n=50 per group?
detectable_effect = tt_ind_solve_power(
    effect_size=None,  # Solve for this
    nobs1=50,
    alpha=0.05,
    power=0.80,
    ratio=1.0,
    alternative='two-sided'
)

print(f"With n=50 per group, we could detect d ≥ {detectable_effect:.2f}")

Raw lines 573 to 595
596## Reporting Effect Sizes
598Numerical prose templates below are illustrative, not computed study results; use the actual fitted estimates and interval method.

Reporting Effect Sizes

Numerical prose templates below are illustrative, not computed study results; use the actual fitted estimates and interval method.

Raw lines 596 to 599
600### APA Style Guidelines
602**T-test example**:
603> "Group A (M = 75.2, SD = 8.5) scored significantly higher than Group B (M = 68.3, SD = 9.2), t(98) = 3.82, p < .001, d = 0.77, 95% CI [0.36, 1.18]."
605**ANOVA example**:
606> "There was a significant main effect of treatment condition on test scores, F(2, 87) = 8.45, p < .001, η²p = .16. Post hoc comparisons using Tukey's HSD revealed..."
608**Correlation example**:
609> "There was a moderate positive correlation between study time and exam scores, r(148) = .42, p < .001, 95% CI [.27, .55]."
611**Regression example**:
612> "The regression model significantly predicted exam scores, F(3, 146) = 45.2, p < .001, R² = .48. Study hours (β = .52, p < .001) and prior GPA (β = .31, p < .001) were significant predictors."
614**Bayesian example**: See `bayesian_statistics.md` (Reporting Bayesian Results) for Bayes Factor and posterior reporting templates.
616---

APA Style Guidelines

T-test example:

"Group A (M = 75.2, SD = 8.5) scored significantly higher than Group B (M = 68.3, SD = 9.2), t(98) = 3.82, p < .001, d = 0.77, 95% CI [0.36, 1.18]."

ANOVA example:

"There was a significant main effect of treatment condition on test scores, F(2, 87) = 8.45, p < .001, η²p = .16. Post hoc comparisons using Tukey's HSD revealed..."

Correlation example:

"There was a moderate positive correlation between study time and exam scores, r(148) = .42, p < .001, 95% CI [.27, .55]."

Regression example:

"The regression model significantly predicted exam scores, F(3, 146) = 45.2, p < .001, R² = .48. Study hours (β = .52, p < .001) and prior GPA (β = .31, p < .001) were significant predictors."

Bayesian example: See bayesian_statistics.md (Reporting Bayesian Results) for Bayes Factor and posterior reporting templates.


Raw lines 600 to 617
618## Effect Size Pitfalls
6201. **Don't only rely on benchmarks**: Context matters; small effects can be meaningful
6212. **Report confidence intervals**: CIs show precision of effect size estimate
6223. **Distinguish statistical vs. practical significance**: Large n can make trivial effects "significant"
6234. **Consider cost-benefit**: Even small effects may be valuable if intervention is low-cost
6245. **Multiple outcomes**: Effect sizes vary across outcomes; report all
6256. **Don't cherry-pick**: Report effects for all planned analyses
6267. **Publication bias**: Published effects are often overestimated
628---

Effect Size Pitfalls

  1. Don't only rely on benchmarks: Context matters; small effects can be meaningful
  2. Report confidence intervals: CIs show precision of effect size estimate
  3. Distinguish statistical vs. practical significance: Large n can make trivial effects "significant"
  4. Consider cost-benefit: Even small effects may be valuable if intervention is low-cost
  5. Multiple outcomes: Effect sizes vary across outcomes; report all
  6. Don't cherry-pick: Report effects for all planned analyses
  7. Publication bias: Published effects are often overestimated

Raw lines 618 to 629
630## Quick Reference Table
632| Analysis | Effect Size | Small | Medium | Large |
633|----------|-------------|-------|--------|-------|
634| T-test | Cohen's d | 0.20 | 0.50 | 0.80 |
635| ANOVA | η², ω² | 0.01 | 0.06 | 0.14 |
636| ANOVA | Cohen's f | 0.10 | 0.25 | 0.40 |
637| Correlation | r, ρ | 0.10 | 0.30 | 0.50 |
638| Regression | R² | 0.02 | 0.13 | 0.26 |
639| Regression | f² | 0.02 | 0.15 | 0.35 |
640| Chi-square (df* = 1, 2×2) | Cramér's V, φ | 0.10 | 0.30 | 0.50 |
641| Chi-square (df* = 2) | Cramér's V | 0.07 | 0.21 | 0.35 |
642| Chi-square (df* = 3) | Cramér's V | 0.06 | 0.17 | 0.29 |
644*Note*: df* = min(rows, columns) − 1.
646---

Quick Reference Table

AnalysisEffect SizeSmallMediumLarge
T-testCohen's d0.200.500.80
ANOVAη², ω²0.010.060.14
ANOVACohen's f0.100.250.40
Correlationr, ρ0.100.300.50
RegressionR²0.020.130.26
Regressionf²0.020.150.35
Chi-square (df* = 1, 2×2)Cramér's V, φ0.100.300.50
Chi-square (df* = 2)Cramér's V0.070.210.35
Chi-square (df* = 3)Cramér's V0.060.170.29

Note: df* = min(rows, columns) − 1.


Raw lines 630 to 647
648## Current API Sources
650Reviewed 2026-10-01: [Pingouin effect sizes](https://pingouin-stats.org/generated/pingouin.compute_effsize.html), [approximate effect intervals](https://pingouin-stats.org/generated/pingouin.compute_esci.html), [SciPy bootstrap](https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.bootstrap.html), [statsmodels power](https://www.statsmodels.org/stable/stats.html#power-and-sample-size-calculations).
652## Resources
654- Cohen, J. (1988). *Statistical Power Analysis for the Behavioral Sciences* (2nd ed.)
655- Lakens, D. (2013). Calculating and reporting effect sizes
656- Ellis, P. D. (2010). *The Essential Guide to Effect Sizes*

Resources

  • Cohen, J. (1988). Statistical Power Analysis for the Behavioral Sciences (2nd ed.)
  • Lakens, D. (2013). Calculating and reporting effect sizes
  • Ellis, P. D. (2010). The Essential Guide to Effect Sizes
Raw lines 652 to 656