Skip to course content
Free Python statistics course

Statistical Data Analytics with Python

Unit 06.00: equal_var=False is the option you want

This is the single most consequential default in scipy.stats, and it is the opposite of R's.

equal_var=False is the option you almost always want

A one-sample test compares a mean to a fixed value from outside the data — a specification, a target, a previous period. A two-sample test compares two independent groups.

For the two-sample case scipy defaults to Student's t-test, which assumes both groups share a variance. When that assumption fails — and especially when the group sizes also differ — it pools the variances in a way that credits the small noisy group with the precision of the large tight one. The result is a confident p-value computed on degrees of freedom the data does not support.

Welch's test makes no such assumption and costs essentially nothing when the variances happen to match.

This block runs a one-sample test, then compares both two-sample methods on groups with unequal spread and size.

import numpy as np
from scipy import stats

rng = np.random.default_rng(301)

# One-sample: is this mean different from a fixed target?
weights = rng.normal(502, 5, 40)
one = stats.ttest_1samp(weights, popmean=500)
ci = one.confidence_interval()
print("One-sample against a 500 g target")
print(f"  mean {weights.mean():.2f}   p = {one.pvalue:.4f}"
      f"   CI [{ci.low:.2f}, {ci.high:.2f}]\n")

# Two-sample, with deliberately unequal spread and unequal group sizes.
line_a = rng.normal(502, 4, 60)
line_b = rng.normal(496, 14, 15)

student = stats.ttest_ind(line_a, line_b)                  # scipy's DEFAULT
welch = stats.ttest_ind(line_a, line_b, equal_var=False)

print(f"SDs: {line_a.std(ddof=1):.2f} and {line_b.std(ddof=1):.2f}"
      f"   ns: {line_a.size} and {line_b.size}")
print(f"  equal_var=True  (scipy default): p = {student.pvalue:.4f}"
      f"   df = {student.df:.1f}")
print(f"  equal_var=False (Welch)        : p = {welch.pvalue:.4f}"
      f"   df = {welch.df:.1f}")
print()
print("scipy defaults to the EQUAL-VARIANCE test. R defaults to Welch.")
print("With unequal spreads and unequal group sizes the two disagree, and the")
print("equal-variance version is the one that is wrong. Pass equal_var=False.")

The one-sample test puts the mean at 500.52 against a 500 g target, p = 0.5486 — no evidence of drift. The two-sample comparison is the important one: SDs of 3.60 and 16.89 from groups of 60 and 15 give p = 0.0019 on 73 degrees of freedom under scipy's default, and p = 0.1086 on 14.3 under Welch. One says a clear difference, the other says nothing — and Welch's much smaller df reflects that the noisy group of 15 carries most of the uncertainty.

The mistake this prevents

The mistake is calling stats.ttest_ind(a, b) and taking the p-value. On unbalanced, unequal-variance data that is the wrong test, and it errs towards finding effects.

Takeaway

Pass equal_var=False to ttest_ind as a matter of habit. Report both group SDs and both sample sizes so a reader can see whether the assumption ever mattered.