Implementing t-Tests in Python with SciPy — One-Sample, Two-Sample (Student's·Welch's), Paired

The theory is in the previous post — this one turns it into SciPy code for all three t-tests. Checking that the hand-computed t and p match SciPy's output makes the concepts stick.

Testing Means — the t-test (One / Two: Student's·Welch's / Paired)
Is a difference in two group means a real difference or just variability — the rationale for the t-test, its assumptions (normality, equal variance), and worked calculations for each type.
taystudios.com/blog

1. Python statistics libraries

The two main statistics libraries in the Python ecosystem are SciPy and Statsmodels. (For machine learning, it's scikit-learn.)

SciPy

  • Optimized for quickly producing p-values and basic statistics.
  • Good for large datasets or fast decisions.
  • Install: pip install scipy

Statsmodels

  • Provides extensive features for statistical model diagnosis and evaluation.
  • Detailed statistics for each variable (t-statistic), the model (F-statistic), R², and more.
  • Install: pip install statsmodels

This post implements all three t-tests with SciPy.

2. One-sample t-test — stats.ttest_1samp

Example — Does the blood pressure of 5 patients after taking a drug differ from the usual mean (120)?

Patient Blood pressure after dose (mmHg)
P1 118
P2 121
P3 119
P4 117
P5 120
from scipy import stats
import numpy as np

x1 = np.array([118, 121, 119, 117, 120])
mu0 = 120.0  # population mean to compare against

result = stats.ttest_1samp(x1, popmean=mu0, alternative='two-sided', axis=0)
# TtestResult(statistic=-1.4142135623730951, pvalue=0.23019964108049873, df=4)

Key arguments

  • x1: the sample to test (numpy array recommended)
  • popmean: the population mean to compare against
  • alternative: 'two-sided' (default) / 'less' / 'greater'
  • axis: 0 (columns, default) / 1 (rows) / None (flatten)

axis option on a 2D array

arr = np.array([[1.5, 2.3, 3.7],
                [4.1, 5.2, 6.8],
                [7.4, 8.5, 9.1],
                [10.2, 11.4, 12.9],
                [13.3, 14.1, 15.6]])
mu0 = 5

# axis=0 — column-wise (each column as a sample)
t_stat, p_val = stats.ttest_1samp(arr, popmean=mu0, axis=0)
# T-statistic: [1.0946 1.5652 2.1805]
# P-value:     [0.3352 0.1926 0.0947]

# axis=1 — row-wise (each row as a sample)
t_stat, p_val = stats.ttest_1samp(arr, popmean=mu0, axis=1)
# T-statistic: [-3.8886  0.4678  6.6965  8.3224 13.8451]
# P-value:     [ 0.0602  0.6860  0.0216  0.0141  0.0052]

# axis=None — flatten (whole array as one sample)
t_stat, p_val = stats.ttest_1samp(arr, popmean=mu0, axis=None)
# T-statistic: 2.9475
# P-value:     0.0106

3. Two-sample t-test — stats.ttest_ind

Example — Compare blood pressure between drug and no-drug groups.

Patient Group BP after dose
P1 drug 115
P2 drug 118
P3 drug 116
P4 no drug 122
P5 no drug 124

3.1 Student's t-test — equal-variance assumption OK

from scipy import stats
import numpy as np

x1 = np.array([115, 118, 116])  # drug
x2 = np.array([122, 124])       # no drug

t_stat, p_val = stats.ttest_ind(x1, x2, equal_var=True, alternative='two-sided')
# T-statistic: -4.898979485566359
# P-value:      0.016276603459428517

Key arguments

  • x1, x2: the two samples
  • equal_var:
  • True: equal-variance assumption holds → Student's t-test
  • False: equal-variance assumption fails → automatically switches to Welch's t-test
  • alternative and axis are the same as ttest_1samp

axis on 2D arrays

data  = np.array([[115, 118, 116],
                  [122, 124, 123]])
data1 = np.array([[231, 123, 132],
                  [223, 321, 421]])

# axis=0 — column-wise
t_stat, p_val = stats.ttest_ind(data, data1, axis=0, equal_var=True)
# T-statistic: [-20.4136 -1.0197 -1.0862]
# P-value:     [  0.0024  0.4151  0.3909]

# axis=1 — row-wise
t_stat, p_val = stats.ttest_ind(data, data1, axis=1, equal_var=True)
# T-statistic: [-1.3195 -3.4755]
# P-value:     [ 0.2575  0.0255]

3.2 Welch's t-test — unequal variances

t_stat, p_val = stats.ttest_ind(x1, x2, equal_var=False, alternative='two-sided')
# T-statistic: -5.0000
# P-value:      0.02505

Same method, just toggle equal_var=False for Welch's. Use this when the equal-variance assumption doesn't hold.

4. Paired-samples t-test — stats.ttest_rel

Example — Same patients, blood pressure before vs. after. Paired data.

Patient Before After
P1 130 120
P2 128 119
P3 135 125
P4 132 123
P5 129 121
import numpy as np
from scipy import stats

before = np.array([130, 128, 135, 132, 129])
after  = np.array([120, 119, 125, 123, 121])

t_stat, p_val = stats.ttest_rel(before, after, alternative='less')
# T-statistic: 24.58803425594304
# P-value:      0.9999918819424217

alternative='less' here sets "before < after" as the alternative hypothesis. Since before is actually larger, p is near 1. If you want to test "is BP lower after the dose," call it the other way: stats.ttest_rel(after, before, alternative='less') — then p comes out tiny.

Caveat: the two arrays must have the same length. If not:

ValueError: unequal length arrays

Wrap-up

With SciPy's three functions, each t-test is a one-liner:

  • One-sample → stats.ttest_1samp(x, popmean)
  • Two-sample → stats.ttest_ind(x1, x2, equal_var=True/False) — Student's vs. Welch's
  • Paired → stats.ttest_rel(before, after)

Just keep alternative (two-sided / one-sided) and axis (1D/2D) in mind. Compare the output against the hand calculation from the theory post — t and p match.

References


📦 Migrated from my own Korean blog (my own writing). Original: taehyuklee.tistory.com/26

Share𝕏f

Comments