Answer
What do p values and t values actually mean?
The short answer
A t value is a signal-to-noise ratio: the estimated effect divided by its standard error. The p value is the probability of getting a t at least that far from zero if the null hypothesis were true, so a small p says the data would be surprising under the null. It is not the probability that the null is true, and a large p value never proves that there is no effect.
The short version
Every hypothesis test starts from a null hypothesis, usually the boring claim that there is no difference or no effect. The test then asks one question: if the null were true, how unusual would data like ours be?
The t value answers the first half. It measures how far the estimate is from what the null predicts, in units of standard error. A t of 0.5 means the estimate is half a standard error from zero, well within ordinary sampling noise. A t of 3 means it is three standard errors away, which chance alone rarely produces.
The p value turns that distance into a probability. It is the chance, computed as if the null were true, of getting a t at least as far from zero as the one you observed. Small p values mean your result would be rare under the null, which counts as evidence against it.
How the t value and the p value are connected
For a comparison of two means, t is the difference between the sample means divided by the standard error of that difference. The numerator is the signal; the denominator is how much that signal would bounce around from sample to sample. More data or less variable data shrink the standard error, so the same difference produces a larger t.
If the null is true and the assumptions hold, t follows a t distribution with a known number of degrees of freedom. The two-sided p value is simply the area in both tails of that distribution beyond the observed t. So p is a one-to-one translation of t, given the df: bigger |t|, smaller p.
The df matter. With few df the t distribution has heavy tails, so the same t is less surprising. In the example below, t = 2.1 gives p = .090 with 5 df, p = .049 with 20 df and p = .038 with 100 df.
See it in R and Python
The code compares two simulated groups of 20 whose true means differ by 6 points (SD 10), computes t and p by hand to show they match t.test(), and then repeats the experiment 10,000 times with and without a real difference to show how p values behave.
R
set.seed(31)
# 1. Two groups of 20; the true difference in means is 6 points (SD 10)
a <- rnorm(20, mean = 56, sd = 10)
b <- rnorm(20, mean = 50, sd = 10)
tt <- t.test(a, b, var.equal = TRUE)
# The t value is the difference divided by its standard error
diff <- mean(a) - mean(b)
sp <- sqrt(((20 - 1) * var(a) + (20 - 1) * var(b)) / (20 + 20 - 2))
se <- sp * sqrt(1 / 20 + 1 / 20)
round(c(diff = diff, se = se, t_by_hand = diff / se,
t_from_test = unname(tt$statistic), df = unname(tt$parameter)), 3)
# The p value is the tail area of the t distribution beyond |t|, both sides
round(c(p_by_hand = 2 * pt(-abs(diff / se), df = 38), p_from_test = tt$p.value), 4)
round(c(mean_a = mean(a), sd_a = sd(a), mean_b = mean(b), sd_b = sd(b)), 2)
round(tt$conf.int, 2)
# 2. The same t value gives different p values with different df
round(sapply(c(df5 = 5, df20 = 20, df100 = 100), function(k) 2 * pt(-2.1, k)), 4)
# 3. When the null is true, p values are spread evenly between 0 and 1
p_null <- replicate(10000, t.test(rnorm(20, 50, 10), rnorm(20, 50, 10),
var.equal = TRUE)$p.value)
round(mean(p_null < 0.05), 3) # about 0.05
round(table(cut(p_null, seq(0, 1, 0.2))) / 10000, 3) # about 0.2 in each bin
# 4. When there is a real 6-point difference, small p values pile up
p_alt <- replicate(10000, t.test(rnorm(20, 56, 10), rnorm(20, 50, 10),
var.equal = TRUE)$p.value)
round(mean(p_alt < 0.05), 3) # power of this design
round(mean(p_alt > 0.5), 3) # a large p is still commonPython
import numpy as np
from scipy import stats
rng = np.random.default_rng(31)
# 1. Two groups of 20; the true difference in means is 6 points (SD 10)
a = rng.normal(56, 10, 20)
b = rng.normal(50, 10, 20)
tt = stats.ttest_ind(a, b) # equal variances assumed, like var.equal = TRUE
# The t value is the difference divided by its standard error
diff = a.mean() - b.mean()
sp = np.sqrt(((20 - 1) * a.var(ddof=1) + (20 - 1) * b.var(ddof=1)) / (20 + 20 - 2))
se = sp * np.sqrt(1 / 20 + 1 / 20)
print("diff", round(diff, 3), "se", round(se, 3), "t_by_hand", round(diff / se, 3),
"t_from_test", round(tt.statistic, 3), "df", tt.df)
# The p value is the tail area of the t distribution beyond |t|, both sides
print("p_by_hand", round(2 * stats.t.cdf(-abs(diff / se), 38), 4), "p_from_test", round(tt.pvalue, 4))
ci = tt.confidence_interval(0.95)
print("95% CI", round(ci.low, 2), round(ci.high, 2))
# 2. The same t value gives different p values with different df
print({k: round(2 * stats.t.cdf(-2.1, k), 4) for k in [5, 20, 100]})
# 3. When the null is true, p values are spread evenly between 0 and 1
p_null = stats.ttest_ind(rng.normal(50, 10, (10000, 20)),
rng.normal(50, 10, (10000, 20)), axis=1).pvalue
print(round(np.mean(p_null < 0.05), 3)) # about 0.05
print(np.round(np.histogram(p_null, bins=np.linspace(0, 1, 6))[0] / 10000, 3))
# 4. When there is a real 6-point difference, small p values pile up
p_alt = stats.ttest_ind(rng.normal(56, 10, (10000, 20)),
rng.normal(50, 10, (10000, 20)), axis=1).pvalue
print(round(np.mean(p_alt < 0.05), 3)) # power of this design
print(round(np.mean(p_alt > 0.5), 3)) # a large p is still commonThe figures below come from one seeded run of the R code. Python uses a different random number generator, so its simulated figures differ slightly, but they show the same pattern; the df comparison, which involves no randomness, matches exactly.
- t is signal over noise. Group A had M = 55.26 (SD = 8.83) and group B had M = 48.02 (SD = 9.88). The difference of 7.245 divided by its standard error of 2.963 gives t = 2.445, exactly what
t.test()reports, with 38 df. - p is a tail area. Twice the area of the t(38) distribution beyond 2.445 is 0.0192, again matching the test. The 95% CI for the difference, [1.25, 13.24], excludes zero, which agrees with p < .05.
- Under the null, p is uniform. Across 10,000 experiments with no true difference, 5.1% of p values fell below .05, and each fifth of the 0-1 range held close to 20% of them (19.8% to 20.2%). A p value near .05 is exactly as likely as one near .95 when nothing is going on.
- A real effect can still give a large p. With a true 6-point difference and 20 per group, only 44.7% of experiments reached p < .05, and 10.4% gave p > .5. A single non-significant result from a small study says little.
Should you hope for a high or a low p value?
A low p value is what you want when your goal is to show that an effect exists, because it is evidence against the no-effect null. People often hope for a high p value when checking an assumption, such as a normality test or a goodness-of-fit test, since there the null is the thing they want to be true.
That second use is weaker than it looks. A high p value means the data are compatible with the null, not that the null has been confirmed. The simulation above shows why: a real difference produced p > .5 about one time in ten. If you need to show that two groups are practically the same, use an equivalence test (such as two one-sided tests) with a pre-set margin, or report a confidence interval and show it contains only negligible differences.
Common misreadings
- "p is the probability the null is true." No. It is computed assuming the null is true, so it cannot also be the probability of the null. Getting that would need prior information, which is what Bayesian methods add.
- "p = .03 means a 97% chance the finding is real." Same error in reverse. How often small p values reflect real effects depends on the power of the studies and how plausible the hypotheses were to begin with.
- "A smaller p means a bigger effect." p mixes effect size with sample size. A trivial difference in a huge sample can give a tiny p; always report the effect and its confidence interval.
- "p = .06 and p = .04 are completely different results." The .05 line is a convention. Two studies on either side of it can show nearly the same evidence.
- "Not significant means no effect." As the simulation shows, underpowered studies often miss real effects.
The American Statistical Association's 2016 statement on p-values makes these same points (Wasserstein & Lazar, 2016). For the role of df in the t distribution, see What are degrees of freedom in statistics, really?. For more plain-language guides to planning and reporting analyses, see the DASS blog.
How to report a t test and p value in APA style (7th edition)
Give the t value with its df in parentheses, the exact p value to two or three decimals without a leading zero, and the confidence interval for the effect. Using the example above:
"Group A scored higher (M = 55.26, SD = 8.83) than group B (M = 48.02, SD = 9.88), t(38) = 2.45, p = .019, 95% CI [1.25, 13.24]."
Report exact p values rather than "p < .05" or "n.s."; for very small values write p < .001. Add an effect size such as Cohen's d (d = x.xx) when you can. In the method section, say which test you used and whether equal variances were assumed.
Related tools and guides
- Check a reported p-value
- t-test power and sample size calculator
- APA 7 formatter for t tests
- Which statistical test should I use?
- What are degrees of freedom in statistics, really?
- What does 95% confidence mean?
- p-value (Wikipedia)
- Student's t-test (Wikipedia)
More answered questions
- What are degrees of freedom in statistics, really?
- Is there a 95% probability that your confidence interval covers the true mean?
- Is a normality test worth running on your data?
Working with your own data?
General answers only go so far. Send us your situation and we'll reply by email within two business days.
Ask your questionWritten by AskStats with AI assistance. This is general information, not advice for your specific data or study. When the results matter, check your approach with a qualified statistician.