Unit 05.05: Read the result object, and check the default
scipy's ttest_ind assumes equal variances unless you tell it otherwise. R's t.test does the opposite.
Read the result object, and check the default
A scipy test result carries the statistic, the degrees of freedom, the p-value and — on recent versions — a confidence interval via .confidence_interval(). The interval is the part that says how large the effect might be; the p-value only says whether zero is inside it.
The default worth knowing is equal_var=True. That is Student's t-test, which assumes both groups have the same variance. Welch's test, which does not, is equal_var=False — and it is R's default, so code translated between the two languages silently changes method.
When the variances are similar the two agree closely. When they are not, they diverge, and Welch is the safer answer.
This block runs both versions on the same two groups and annotates the output.
import numpy as np
from scipy import stats
rng = np.random.default_rng(206)
control = rng.normal(50, 9, 35)
treated = rng.normal(55, 9, 35)
# scipy's DEFAULT is equal_var=True. R's default is Welch. This matters.
student = stats.ttest_ind(treated, control) # equal_var=True
welch = stats.ttest_ind(treated, control, equal_var=False)
for name, res in [("equal_var=True (scipy default)", student),
("equal_var=False (Welch)", welch)]:
ci = res.confidence_interval()
print(f"{name:32s} t = {res.statistic:6.3f} df = {res.df:6.2f}"
f" p = {res.pvalue:.5f} CI [{ci.low:5.2f}, {ci.high:5.2f}]")
print()
print("--- what each part is ---")
print(f"statistic : {welch.statistic:.3f} -- difference in standard-error units")
print(f"df : {welch.df:.2f} -- fractional, because Welch adjusts it")
print(f"pvalue : {welch.pvalue:.5f}")
ci = welch.confidence_interval()
print(f"CI : [{ci.low:.2f}, {ci.high:.2f}] -- for the DIFFERENCE")
print(f"group SDs : {control.std(ddof=1):.2f} and {treated.std(ddof=1):.2f}")
print()
print("Here the two agree because the SDs are similar. When they are not,")
print("they diverge -- and scipy will not choose Welch for you.")
Both give t = 1.510 and p ≈ 0.1356, with degrees of freedom of 68.00 and 67.42 — Welch's is fractional because it adjusts them. They agree here because the group SDs are 8.24 and 9.05, close enough for the assumption to be harmless. The interval for the difference, [-1.00, 7.25], contains zero and is the line worth quoting: the data is consistent with anything from a small decrease to a useful increase.
The mistake this prevents
The mistake is copying a t-test from R documentation into scipy and assuming it is the same test. It is not, and nothing warns you.
Takeaway
Pass equal_var=False unless you have a reason not to, and read .confidence_interval() before the p-value. Report both group standard deviations so a reader can see whether the assumption mattered.
