Menu

T-Test in R: One-Sample, Two-Sample and Paired t.test()

Run one-sample, two-sample and paired t-tests with t.test(), and - the part that actually matters - read every line of the output: t, df, p-value, confidence interval.

This page includes runnable editors - edit, run, and see output instantly.

What a T-Test Asks

Every t-test answers the same underlying question: is this difference in means real, or could it just be noise? Samples wobble - measure ten people's reaction times twice and you'll get two different averages even though nothing changed. The t-test compares the difference you observed against the wobble you'd expect from chance, and reports how surprising your data would be if the true difference were zero.

Three flavors, one function:

  • One-sample - is the mean of this group different from a fixed value?
  • Two-sample - do these two independent groups have different means?
  • Paired - did the same subjects change between two measurements?

One-Sample: t.test(x, mu = ...)

Suppose a process is supposed to average 5.0 seconds, and you time ten runs:

The sample mean is 5.61 - but is 0.61 above target meaningful with only ten runs, or within normal wobble? That's exactly what the output answers.

Reading the Output Line by Line

This is the section that earns its keep - the output of every t.test() variant has the same shape, so learn to read it once. Run the block above and match each line:

  • t = 4.26 - the test statistic: the observed difference (5.61 − 5 = 0.61) divided by the standard error of the mean (about 0.143). It says the sample mean sits a bit more than four standard errors away from the hypothesized value. Bigger |t| = more surprising under the null.
  • df = 9 - degrees of freedom, here n − 1. Small df means small samples, which means the test demands a bigger t before it's impressed.
  • p-value ≈ 0.002 - if the true mean really were 5, the probability of drawing a sample whose mean lands at least this far from 5 (in either direction) is about 0.2%. That's the entire meaning. It is not the probability that the true mean is 5, and a small p-value does not prove your favorite explanation - it only says "hard to blame on chance." At the usual 0.05 threshold, you reject the null here.
  • alternative hypothesis - a restatement of what "reject" would mean. not equal to 5 confirms this was two-sided.
  • 95 percent confidence interval: 5.29 to 5.93 - the range of true means compatible with the data. Notice 5 is outside it - that's the same verdict as p < 0.05, stated in the data's own units, and it also tells you the effect's plausible size, which the p-value never does.
  • sample estimates - the observed mean, so the reader can see the raw fact being tested.

If you need pieces programmatically: result <- t.test(times, mu = 5), then result$p.value, result$conf.int, result$estimate.

Two-Sample: Comparing Independent Groups

For two independent groups, the formula interface reads like the question itself. ToothGrowth records tooth growth in guinea pigs given vitamin C as orange juice (OJ) or ascorbic acid (VC):

Read it as "test len split by supp." The output now shows two sample estimates (mean per group, about 20.7 vs 17.0), and the confidence interval is for the difference between them. Here p ≈ 0.061 and the interval runs from about −0.17 to 7.57: it crosses zero, so at the 0.05 level you can't rule out "no difference" - though an interval stretching to +7.6 also warns you against declaring the difference nonexistent. "Not significant" means not proven, not proven absent.

Welch Is the Default - and That's Good

Look at the output header: Welch Two Sample t-test, with fractional df (about 55.3). The classic Student's t-test assumes both groups have equal variance; Welch's version drops that assumption and adjusts the degrees of freedom to compensate. When variances really are equal, Welch gives essentially identical answers; when they aren't, Student's test can be badly miscalibrated while Welch stays honest. So R's default is the safe one - there is rarely a reason to override it.

The pre-test ritual of "check equal variances first, then choose the test" is outdated advice; just use Welch.

Paired: Before and After

When both measurements come from the same subjects, the groups aren't independent - and treating them as independent throws away the test's power. Eight people's scores before and after a training course:

paired = TRUE tests the mean of the within-person differences (here averaging 2.75 points, p ≈ 0.001). Why pairing changes everything: people differ from each other far more than the training changed anyone - subject-to-subject spread from 65 to 80 would swamp a 2-3 point improvement in an unpaired test. Differencing subtracts each person's baseline away, so only the change remains. The rule: if the data has a natural "same unit measured twice" structure, pair it. (And never use paired = TRUE when the groups are genuinely independent - the pairing would be fiction.)

One-Sided Tests: Handle With Care

By default the test is two-sided: it counts a difference in either direction as evidence. If - before seeing the data - your hypothesis only made sense in one direction, you can say so:

The p-value halves relative to the two-sided test, which is exactly why the temptation exists: switching to one-sided after peeking at the data is p-hacking. Use alternative = "greater" or "less" only with a genuinely pre-registered direction; when in doubt, stay two-sided.

Assumptions, and the Fallback

The t-test assumes the observations are independent and that the sample means are approximately normally distributed - which holds when the data itself is roughly normal or the samples are reasonably large (the central limit theorem does the heavy lifting; by n ≈ 30+ per group, moderate non-normality is a non-issue). Check the shape with a quick histogram or boxplot. Independence, though, no test can rescue - it comes from how the data was collected.

For small samples with clearly skewed data or extreme outliers, the standard non-parametric fallback is one line: wilcox.test(len ~ supp, data = ToothGrowth), which compares distributions via ranks instead of means.

What You Take Away

  • t.test(x, mu = ) for one sample, t.test(y ~ group, data = ) for two, paired = TRUE for before/after.
  • The p-value is "how surprising is this data if the true difference were zero" - nothing more; the confidence interval tells you the effect's plausible size in real units.
  • R's two-sample default is Welch's test (fractional df) - keep it.
  • Pairing removes between-subject noise; use it whenever the same units are measured twice.
  • One-sided alternatives only with a pre-committed direction; wilcox.test() is the rank-based fallback.

Next up: comparing the means of three or more groups at once - ANOVA with aov().

Frequently Asked Questions

How do you run a t-test in R?

With t.test(). One sample against a fixed value: t.test(x, mu = 5). Two independent groups: t.test(value ~ group, data = df). Before/after measurements on the same subjects: t.test(after, before, paired = TRUE).

How do you interpret the p-value of a t-test in R?

It's the probability of seeing a difference at least as large as yours if the true difference were zero. A small p-value (conventionally below 0.05) means the data is hard to explain as noise, so you reject the null hypothesis. It is not the probability that the null is true, and it says nothing about how large or important the difference is - read the confidence interval for that.

Why does R report a Welch t-test by default?

R's two-sample t.test() defaults to Welch's version, which does not assume the two groups have equal variances - that's why the degrees of freedom come out fractional. Welch behaves almost identically to the classic Student's test when variances are equal and is safer when they aren't, so the default is the right choice. Use var.equal = TRUE only if coursework explicitly demands the classic pooled test.

When should you use a paired t-test in R?

When the two sets of measurements come in natural pairs - the same subject measured before and after, the same item rated by two methods. t.test(after, before, paired = TRUE) tests the mean of the within-pair differences, which removes between-subject variation and usually gives far more power than treating the groups as independent.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED