T-Test in R: One-Sample, Independent (Student & Welch) & Paired
Compare two means in R — one-sample, independent (Student & Welch) and paired t-tests, rstatix the modern way and base R for the classics, with the p-value drawn on the plot
Learn to run a t-test in R three ways: one-sample (against a known mean), independent two-sample (Student and Welch), and paired. Use rstatix t_test() for a tidy result and base R t.test() for the classic output, check the normality and equal-variance assumptions, report the Cohen’s d effect size, and plot it with a ggpubr boxplot that prints the p-value.
- A t-test compares means — one sample against a known value, or two groups against each other — and tells you whether the difference is statistically significant.
- There are three forms: one-sample (
mu =), independent (two unrelated groups), and paired (the same subjects measured twice). The independent test comes in Student (equal variances) and Welch (unequal — R’s safer default) flavors. - Use
t_test()from rstatix for a tidy result, or base Rt.test()for the classic printout — both wrap the same statistics. - Check the assumptions first: normality (
shapiro_test()+ Q-Q plot) and, for the Student test, equal variances (levene_test()/var.test()). If normality fails, switch to the Wilcoxon test. - Report the p-value (is it significant?) and the Cohen’s d effect size (how big?), and visualize it with
ggpubr::ggboxplot()+stat_pvalue_manual().
Introduction
In a hurry? Jump to the t-test calculator ↓ — paste your own numbers and get the result.
A t-test answers one of the most common questions in data analysis: do two means differ, and is the difference real or just noise? It comes in three forms, picked by how your data is structured:
| Test | Compares | Example |
|---|---|---|
| One-sample | one group’s mean vs. a known value | Is the mean mouse weight different from 25 g? |
| Independent (unpaired) | the means of two unrelated groups | Do men and women differ in weight? |
| Paired | the same subjects measured twice | Did the mice’s weight change before vs. after treatment? |
This lesson runs all three the modern, pipe-friendly rstatix way with t_test(), shows the classic base-R t.test() equivalent, checks the assumptions that decide which test to trust, reports the Cohen’s d effect size, and visualizes the result with a ggpubr boxplot that prints the p-value on the panel. Each test uses its canonical demo dataset from the datarium package — mice (one-sample), genderweight (independent), and mice2 (paired).
The hypotheses, in one line. A t-test pits a null hypothesis (the means are equal) against an alternative (they differ). For two groups: \(H_0: m_A = m_B\) vs. \(H_a: m_A \ne m_B\) (the default two-tailed test). To test a direction instead — \(m_A < m_B\) or \(m_A > m_B\) — pass alternative = "less" or "greater" (a one-tailed test).
t-Test calculator — paste your own data
Have your own numbers? Run a t-test right here — the calculation happens in your browser, so nothing is uploaded to a server. Pick the tab for your design, paste your values, and press Run.
How to use it. (1) Choose the tab: one sample (compare one group to a known value), two independent groups, or paired (two measurements on the same subjects). (2) Paste your numbers between the quotes — one value per line (straight from an Excel column); a decimal point (.) or a French decimal comma (,) both work. (3) Set the options in plain sight. (4) Press Run to get the statistic, degrees of freedom, p-value, confidence interval, the Cohen’s d effect size, and a boxplot. For two-sample, keep welch = TRUE unless the groups truly have equal variances; for one-sample, set mu to the reference value you’re testing against.
Under the hood the calculator calls run_t_test() — a small, dependency-light helper (base R + ggpubr). Prefer the raw t.test() call, step by step? It’s in the walkthrough below. You can also view / download the engine ▸ and reuse it in your own R.
Working with your real dataset? This calculator takes numbers you paste. To run the test on a whole data file — grouped, cleaned, and with the result explained in plain language plus a saved, shareable report — bring your data to Prova: the runtime is the judge.
Independent two-samples t-test
The independent (or unpaired) t-test compares the means of two unrelated groups — each subject belongs to exactly one group, and there’s no relationship between the observations across groups. We start here because it’s the most common case and gives us the hero plot.
We use the genderweight dataset — the weight of 40 individuals, 20 women and 20 men (the group column, F/M). The research question: is the average weight different between the two groups?
library(rstatix)
set.seed(123)
data("genderweight", package = "datarium")
# a couple of random rows per group
genderweight %>% sample_n_by(group, size = 2)# A tibble: 4 × 3
id group weight
<fct> <fct> <dbl>
1 15 F 65.9
2 19 F 62.3
3 34 M 86.2
4 23 M 86.6
Summarize each group with get_summary_stats() — the count, mean and standard deviation you’ll report alongside the test:
library(rstatix)
data("genderweight", package = "datarium")
genderweight %>%
group_by(group) %>%
get_summary_stats(weight, type = "mean_sd")# A tibble: 2 × 5
group variable n mean sd
<fct> <fct> <dbl> <dbl> <dbl>
1 F weight 20 63.5 2.03
2 M weight 20 85.8 4.35
Female weight averages about 63.5 (SD 2.03) and male about 85.8 (SD 4.3) — but always look before you test.
Look at the data first
ggpubr::ggboxplot() draws the boxes and jittered points so you can see the two distributions side by side:
library(ggpubr)
data("genderweight", package = "datarium")
ggboxplot(
genderweight, x = "group", y = "weight",
color = "group", palette = c("#3a86d4", "#8338ec"),
add = "jitter", xlab = "Group", ylab = "Weight"
)
The male group sits well above the female group with little overlap — but the test is what makes the difference defensible.
Student vs. Welch. The Student t-test assumes the two groups have equal variances and pools them; the Welch t-test does not — it uses each group’s own variance and adjusts the degrees of freedom (which is why Welch reports a fractional df). R uses Welch by default because it’s the safer choice, and the two give very similar results unless both the group sizes and the spreads differ a lot.
With rstatix (recommended)
The default t_test(weight ~ group) runs the Welch test. add_significance() appends the familiar significance stars:
library(rstatix)
data("genderweight", package = "datarium")
# Welch t-test (the default — does NOT assume equal variances)
stat.test <- genderweight %>%
t_test(weight ~ group) %>%
add_significance()
stat.test# A tibble: 1 × 9
.y. group1 group2 n1 n2 statistic df p p.signif
<chr> <chr> <chr> <int> <int> <dbl> <dbl> <dbl> <chr>
1 weight F M 20 20 -20.8 26.9 4.30e-18 ****
The tidy tibble names the compared groups in group1/group2, with statistic (the t-value), df, p and the significance flag. The p-value is far below 0.05 — male and female mean weights differ significantly. For the full picture (confidence interval and group estimates) add detailed = TRUE:
library(rstatix)
data("genderweight", package = "datarium")
genderweight %>%
t_test(weight ~ group, detailed = TRUE)# A tibble: 1 × 15
estimate estimate1 estimate2 .y. group1 group2 n1 n2 statistic
* <dbl> <dbl> <dbl> <chr> <chr> <chr> <int> <int> <dbl>
1 -22.3 63.5 85.8 weight F M 20 20 -20.8
# ℹ 6 more variables: p <dbl>, df <dbl>, conf.low <dbl>, conf.high <dbl>,
# method <chr>, alternative <chr>
To run the classic Student test instead — assuming equal variances — add var.equal = TRUE:
library(rstatix)
data("genderweight", package = "datarium")
# Student t-test (assumes equal variances)
genderweight %>% t_test(weight ~ group, var.equal = TRUE)# A tibble: 1 × 8
.y. group1 group2 n1 n2 statistic df p
* <chr> <chr> <chr> <int> <int> <dbl> <dbl> <dbl>
1 weight F M 20 20 -20.8 38 2.33e-22
Run it on your own data. Load your file first — mydata <- read.csv("my-file.csv") — then pipe it in with your own column names: mydata %>% t_test(outcome ~ group). The formula is always outcome ~ grouping_variable.
With base R
t.test() with the outcome ~ group formula computes the same test. It defaults to Welch; set var.equal = TRUE for Student:
data("genderweight", package = "datarium")
# Welch (default)
t.test(weight ~ group, data = genderweight)
Welch Two Sample t-test
data: weight by group
t = -20.791, df = 26.872, p-value < 2.2e-16
alternative hypothesis: true difference in means between group F and group M is not equal to 0
95 percent confidence interval:
-24.53135 -20.12353
sample estimates:
mean in group F mean in group M
63.49867 85.82612
Read the output: t and df are the test statistic and (fractional, for Welch) degrees of freedom, p-value is the significance, the 95 percent confidence interval brackets the difference in means, and the two mean values under sample estimates are the group means. The two functions agree number-for-number — rstatix just returns a tidy tibble. Pull individual values straight off a base-R result with res$p.value, res$statistic, and res$conf.int.
Plot it with the p-value on it
The publication-ready figure puts the test result on the plot — add_xy_position() works out where the bracket sits, stat_pvalue_manual() draws it, and get_test_label() prints the full t, df, p and effect-size line as a subtitle. This is the figure to report:
library(rstatix)
library(ggpubr)
data("genderweight", package = "datarium")
stat.test <- genderweight %>%
t_test(weight ~ group) %>%
add_significance() %>%
add_xy_position(x = "group")
ggboxplot(
genderweight, x = "group", y = "weight",
color = "group", palette = c("#3a86d4", "#8338ec"),
add = c("jitter", "mean"), xlab = "Group", ylab = "Weight"
) +
stat_pvalue_manual(stat.test, tip.length = 0) +
labs(subtitle = get_test_label(stat.test, detailed = TRUE))
The t-statistic is just a signal-to-noise ratio — the difference in means divided by its standard error:
\[t = \frac{\bar{x}_A - \bar{x}_B}{SE_d}\]
A bigger gap (or a smaller, more precise standard error) gives a larger \(t\), and a smaller p-value.
The two flavors differ in how they handle the spread and the degrees of freedom. The Student test pools both groups into one variance and uses \(df = n_A + n_B - 2\). The Welch test uses each group’s own variance and approximates \(df\) with the Satterthwaite formula — a fractional number (here ~26.9) that’s smaller than the pooled \(df\), which is why Welch is more conservative.
Cohen’s d answers a different question — not is the difference real? but how big is it? It’s the standardized mean difference: the gap in means expressed in standard-deviation units, \(d = (\bar{x}_A - \bar{x}_B) / SD\). That makes it comparable across studies and scales, unlike the raw difference or the p-value.
One-sample t-test
The one-sample t-test compares the mean of one group to a known or theoretical value mu. The theoretical mean usually comes from a previous study, or from a “percent of control” baseline of 100.
We use the mice dataset — the weight of 10 mice — and ask: is the average weight different from 25 g?
library(rstatix)
data("mice", package = "datarium")
mice %>% get_summary_stats(weight, type = "mean_sd")# A tibble: 1 × 4
variable n mean sd
<fct> <dbl> <dbl> <dbl>
1 weight 10 20.1 1.90
The mice average about 20.1 g (SD 1.94) — visibly below 25, but the test tells us whether that gap is significant given the small sample.
With rstatix (recommended)
In rstatix, a one-sample test is written t_test(y ~ 1, mu = ...) — the ~ 1 says “compare this one variable against a constant”:
library(rstatix)
data("mice", package = "datarium")
stat.test <- mice %>% t_test(weight ~ 1, mu = 25)
stat.test# A tibble: 1 × 7
.y. group1 group2 n statistic df p
* <chr> <chr> <chr> <int> <dbl> <dbl> <dbl>
1 weight 1 null model 10 -8.10 9 0.0000200
The tidy tibble gives statistic (the t-value), df, and p. Here p is well below 0.05, so the mean mouse weight is significantly different from 25 g. The detailed = TRUE option adds the confidence interval and the observed mean.
With base R
t.test() takes the vector and the mu argument, and prints the classic, fuller report:
data("mice", package = "datarium")
t.test(mice$weight, mu = 25)
One Sample t-test
data: mice$weight
t = -8.1045, df = 9, p-value = 1.995e-05
alternative hypothesis: true mean is not equal to 25
95 percent confidence interval:
18.78346 21.49654
sample estimates:
mean of x
20.14
Read the output: t and df are the test statistic and degrees of freedom (df = n − 1 = 9), p-value is the significance, the 95 percent confidence interval brackets the true mean, and mean of x is the observed mean (~20.1). To test a one-sided hypothesis — “is the mean less than 25?” — add alternative = "less" (or "greater").
A boxplot of the single variable, with the reference value mu = 25 drawn in, shows what the test compares — the observed spread against the hypothesized mean:
library(rstatix)
library(ggpubr)
data("mice", package = "datarium")
stat.test <- mice %>% t_test(weight ~ 1, mu = 25)
ggboxplot(
mice$weight, width = 0.5, add = c("mean", "jitter"),
ylab = "Weight (g)", xlab = FALSE, color = "#3a86d4"
) +
geom_hline(yintercept = 25, linetype = "dashed", color = "#8338ec") +
labs(subtitle = get_test_label(stat.test, detailed = TRUE))
Paired t-test
The paired t-test compares two measurements made on the same subjects — before/after, or two conditions on each unit. It works on the differences: compute each subject’s after − before, then test whether the mean difference is zero.
We use the mice2 dataset — the weight of 10 mice before and after a treatment, in wide format. A paired test wants the two measurements in one column, so reshape it to long with base R (stacking before and after into one weight column with a group factor):
library(rstatix)
data("mice2", package = "datarium")
head(mice2, 3) id before after
1 1 187.2 429.5
2 2 194.2 404.4
3 3 231.7 405.6
# wide → long: gather the before and after values into one column
mice2.long <- data.frame(
id = factor(rep(mice2$id, times = 2)),
group = factor(rep(c("before", "after"), each = nrow(mice2)), levels = c("before", "after")),
weight = c(mice2$before, mice2$after)
)
head(mice2.long, 3) id group weight
1 1 before 187.2
2 2 before 194.2
3 3 before 231.7
library(rstatix)
data("mice2", package = "datarium")
mice2.long <- data.frame(
id = factor(rep(mice2$id, times = 2)),
group = factor(rep(c("before", "after"), each = nrow(mice2)), levels = c("before", "after")),
weight = c(mice2$before, mice2$after)
)
mice2.long %>%
group_by(group) %>%
get_summary_stats(weight, type = "mean_sd")# A tibble: 2 × 5
group variable n mean sd
<fct> <fct> <dbl> <dbl> <dbl>
1 before weight 10 201. 20.0
2 after weight 10 400. 30.1
With rstatix (recommended)
Setting paired = TRUE switches the test to the paired form:
library(rstatix)
data("mice2", package = "datarium")
mice2.long <- data.frame(
id = factor(rep(mice2$id, times = 2)),
group = factor(rep(c("before", "after"), each = nrow(mice2)), levels = c("before", "after")),
weight = c(mice2$before, mice2$after)
)
stat.test <- mice2.long %>%
t_test(weight ~ group, paired = TRUE) %>%
add_significance()
stat.test# A tibble: 1 × 9
.y. group1 group2 n1 n2 statistic df p p.signif
<chr> <chr> <chr> <int> <int> <dbl> <dbl> <dbl> <chr>
1 weight before after 10 10 -25.5 9 0.00000000104 ****
The p-value is far below 0.05 — the average weight after treatment differs significantly from the weight before. (df = n − 1 = 9, one fewer than the number of pairs.)
With base R
The base-R equivalent uses the same formula with paired = TRUE:
data("mice2", package = "datarium")
# base R pairs by position — pass the two columns as vectors (the formula
# interface does not accept paired = TRUE)
t.test(mice2$before, mice2$after, paired = TRUE)
Paired t-test
data: mice2$before and mice2$after
t = -25.546, df = 9, p-value = 1.039e-09
alternative hypothesis: true mean difference is not equal to 0
95 percent confidence interval:
-217.1442 -181.8158
sample estimates:
mean difference
-199.48
Here sample estimates is the mean of the differences, and the confidence interval brackets that difference — both numbers match the rstatix result.
Plot it with the p-value on it
ggpubr::ggpaired() is the natural plot for paired data: it draws the two boxes and connects each subject’s two measurements with a line, so the within-subject change is visible. Add the p-value the same way as the independent plot — add_xy_position() then stat_pvalue_manual():
library(rstatix)
library(ggpubr)
data("mice2", package = "datarium")
mice2.long <- data.frame(
id = factor(rep(mice2$id, times = 2)),
group = factor(rep(c("before", "after"), each = nrow(mice2)), levels = c("before", "after")),
weight = c(mice2$before, mice2$after)
)
stat.test <- mice2.long %>%
t_test(weight ~ group, paired = TRUE) %>%
add_significance() %>%
add_xy_position(x = "group")
ggpaired(
mice2.long, x = "group", y = "weight",
order = c("before", "after"),
color = "group", palette = c("#3a86d4", "#8338ec"),
line.color = "gray", line.size = 0.4,
xlab = "Group", ylab = "Weight"
) +
stat_pvalue_manual(stat.test, tip.length = 0) +
labs(subtitle = get_test_label(stat.test, detailed = TRUE))
The paired test is more powerful than the independent test when the pairing is real, because it removes the subject-to-subject variation — every mouse is its own control.
Pairing is a property of the data, not an option you choose. Use paired = TRUE only when each value in group 1 corresponds to a specific value in group 2 (same subject, matched pair). Applying it to genuinely independent groups is wrong and inflates significance.
Check the assumptions
The t-test is a parametric test. Beyond independent observations, it assumes the data (or, for the paired test, the differences) are approximately normal, the Student version additionally assumes equal variances, and there should be no significant outliers in either group.
With large samples (n > 30) normality matters less — the central limit theorem keeps the test valid even when the raw data stray from normal. The checks below matter most for the small samples (n = 10) in the mice/mice2 examples.
Outliers
rstatix::identify_outliers() flags extreme values per group — an empty result (or is.extreme all FALSE) means no values would distort the means the test compares:
library(rstatix)
data("genderweight", package = "datarium")
genderweight %>%
group_by(group) %>%
identify_outliers(weight)# A tibble: 2 × 5
group id weight is.outlier is.extreme
<fct> <fct> <dbl> <lgl> <lgl>
1 F 20 68.8 TRUE FALSE
2 M 31 95.1 TRUE FALSE
There were no extreme outliers.
Normality — Shapiro-Wilk + Q-Q
Test it with the Shapiro-Wilk test (p > 0.05 → no significant departure from normal). With rstatix, shapiro_test() checks each group at once via group_by():
library(rstatix)
data("genderweight", package = "datarium")
genderweight %>%
group_by(group) %>%
shapiro_test(weight)# A tibble: 2 × 4
group variable statistic p
<fct> <chr> <dbl> <dbl>
1 F weight 0.938 0.224
2 M weight 0.986 0.989
Both p-values are above 0.05, so we cannot reject normality. Back it with ggpubr::ggqqplot(), which draws a Q-Q plot — the points should hug the reference line inside the confidence band:
library(ggpubr)
data("genderweight", package = "datarium")
ggqqplot(genderweight, x = "weight", facet.by = "group")
For a one-sample test, check the variable directly (mice %>% shapiro_test(weight)); for a paired test, check the differences — shapiro_test() on before − after — because it’s the differences that must be normal, not each raw measurement.
Equal variances (for the Student test)
The Student test assumes the two groups have equal variances. Check it with Levene’s test (p > 0.05 → variances are not significantly different, so Student is fine):
library(rstatix)
data("genderweight", package = "datarium")
genderweight %>% levene_test(weight ~ group)# A tibble: 1 × 4
df1 df2 statistic p
<int> <int> <dbl> <dbl>
1 1 38 6.12 0.0180
The base-R equivalent is the F-test var.test(weight ~ group, data = genderweight). Whatever the verdict, Welch (the default) is always the safer choice — it stays valid whether or not the variances match, which is exactly why R defaults to it.
If normality fails (a significant shapiro_test(), or a Q-Q plot that strays from the line), use the non-parametric Wilcoxon test instead — swap t_test() for wilcox_test(), the formula is identical:
genderweight %>% wilcox_test(weight ~ group)See the normality test lesson for the full assumption workflow.
Report the effect size (Cohen’s d)
A significant p-value tells you the difference is real; it does not tell you how big it is. For that, report Cohen’s d — the difference in means expressed in standard-deviation units. rstatix computes it with cohens_d(), which mirrors the t_test() call:
library(rstatix)
data("genderweight", package = "datarium")
# Effect size for the two-group comparison (Welch version)
genderweight %>% cohens_d(weight ~ group, var.equal = FALSE)# A tibble: 1 × 7
.y. group1 group2 effsize n1 n2 magnitude
* <chr> <chr> <chr> <dbl> <int> <int> <ord>
1 weight F M -6.57 20 20 large
cohens_d() mirrors t_test() exactly: pass var.equal = TRUE for the pooled-SD (Student) version, mu = 25 for the one-sample version (cohens_d(weight ~ 1, mu = 25) on mice), or paired = TRUE for the paired version (cohens_d(weight ~ group, paired = TRUE) on mice2.long).
Cohen’s conventional benchmarks are 0.2 (small), 0.5 (moderate), and 0.8 (large) — so the d here is a very large effect. Always pair the p-value with the effect size: a tiny, trivial difference can be “significant” in a large sample, while a large effect can miss significance in a small one. The effect size has its own dedicated lesson — Cohen’s d effect size — covering the one-sample, independent and paired variants in depth.
For small samples (< 50) the Cohen’s d tends to over-inflate. The Hedges’ corrected version (hedges_g() in rstatix) shrinks it by a few percent to compensate.
Interpret the result
Read three things from any t-test, in order:
- The p-value — is the difference statistically significant (typically
p < 0.05)? - The direction & confidence interval — which group is higher, and how precisely (does the CI of the difference exclude 0)?
- The effect size (Cohen’s d) — is the difference practically meaningful, not just detectable?
A complete report reads like: “The mean weight in the female group was 63.5 (SD = 2.03), versus 85.8 (SD = 4.3) in the male group. A Welch two-samples t-test showed the difference was statistically significant, t(26.9) = −20.8, p < 0.0001, d = 6.57.”
Try it live
Run the full workflow on a different comparison — mtcars fuel economy (mpg) by transmission type (am: 0 = automatic, 1 = manual). Edit the variables or the method and re-run; the sandbox boots on first Run.
Ask Prova “should I use a paired or unpaired t-test for my data, and is the difference significant?” — it answers with rstatix code you can run on your own data, then helps you read the p-value and the effect size. The runtime is the judge. Ask Prova →
Common issues
t_test() could not find function / object. Load rstatix first — library(rstatix) — which also provides the %>% pipe used in these examples; and data("genderweight", package = "datarium") to make the dataset available.
My p-value differs from a colleague’s. Check var.equal. R (and rstatix) default to the Welch test, while some textbooks and other software default to the Student test (var.equal = TRUE). The two differ in their degrees of freedom (Welch’s is fractional) and so in the p-value.
A paired test gives a wildly different result from an unpaired one. That’s expected when the pairing is strong — the paired test removes subject-to-subject variation. Make sure paired = TRUE is correct for your design: each value in one group must correspond to a specific value in the other, and the two columns must be in the same subject order after you reshape to long.
Frequently asked questions
Yes — the calculator on this page runs a one-sample, two-sample (Student or Welch), or paired t-test on numbers you paste. It computes entirely in your browser with real R, so your data is never uploaded to any server, and it returns the t statistic, degrees of freedom, p-value, confidence interval, the Cohen’s d effect size and a boxplot — free, no sign-up.
A t-test compares means — one group against a known value, or two groups against each other. It tells you whether an observed difference is real or just the kind of difference you’d expect from random noise in your sample.
Use t_test() from rstatix (tidy output) or base R t.test() (classic printout). For two groups: data %>% t_test(outcome ~ group). For one sample against a known value: data %>% t_test(outcome ~ 1, mu = 25). For paired data add paired = TRUE. Both functions wrap the same statistics — rstatix just returns a tidy tibble you can drop straight onto a ggpubr plot.
An unpaired (independent) t-test compares two separate groups of subjects (e.g. men vs. women). A paired t-test compares two measurements on the same subjects (e.g. mouse weight before vs. after treatment) and tests the mean of the within-subject differences. Use paired only when each value in one group is matched to a specific value in the other; otherwise use unpaired.
Both are independent two-sample t-tests. The Student test assumes the two groups have equal variances and pools them; the Welch test does not — it uses each group’s own variance and adjusts the degrees of freedom (which is why its df is fractional). R uses Welch by default because it’s safer — set var.equal = TRUE to get the Student test. The two give very similar results unless the group sizes and the spreads differ substantially.
A two-tailed test (alternative = "two.sided", the default) asks only do the means differ? — in either direction. A one-tailed test asks a directional question: alternative = "less" tests whether group A’s mean is below group B’s, and alternative = "greater" whether it’s above. Use a one-tailed test only when you have a genuine prior reason to expect one direction (and commit to it before seeing the data); when in doubt, keep the default two-tailed test.
The data (or, for a paired test, the pairwise differences) should be approximately normally distributed — check with shapiro_test() and a Q-Q plot. The independent Student test also assumes equal variances (check with levene_test() or var.test()); the Welch test relaxes that. There should be no extreme outliers (identify_outliers()). With samples larger than ~30 the normality assumption matters less, thanks to the central limit theorem.
Use the non-parametric Wilcoxon test (Mann-Whitney for two independent groups, signed-rank for one-sample or paired) when the normality assumption fails — for example a significant shapiro_test(), a Q-Q plot that strays from the line, a small sample with extreme outliers, or ordinal data. The Wilcoxon test compares ranks (effectively medians) rather than means.
Test your understanding
- Run it. In the live cell below, compute an independent t-test of
weightbygroupingenderweight, then addvar.equal = TRUE. Does the p-value change much? Why? - Conceptual. You measured each mouse’s weight before and after a treatment, in the same animals. Which t-test should you use — and what would you check before trusting it?
The grouping variable is group, so the formula is weight ~ group. The first call (no var.equal) is the Welch test; for the Student test set var.equal = TRUE. For question 2, “same animals measured twice” is the signature of a paired design.
library(rstatix)
library(datarium)
data("genderweight", package = "datarium")
genderweight %>% t_test(weight ~ group) # Welch (default)
genderweight %>% t_test(weight ~ group, var.equal = TRUE) # Student
#> Both are highly significant (p < 0.0001); the df differs (Welch's is fractional, ~26.9).The two p-values are both far below 0.05 — the difference is large and obvious either way. The Welch and Student df differ (Welch’s is fractional) because Welch does not pool the variances.
For question 2: use a paired t-test (paired = TRUE), because each “after” value belongs to the same mouse as its “before” value. Before trusting it, compute the differences (after − before) and check that those are approximately normal with shapiro_test() and a Q-Q plot; if normality fails, use the paired Wilcoxon signed-rank test instead.
Conclusion
You can now run and interpret a t-test in R in all three forms — one-sample (t_test(y ~ 1, mu =)), independent (Student vs. Welch, R’s safer default), and paired (paired = TRUE) — the tidy rstatix way and the classic base-R way. Check normality (and, for Student, equal variances) before you trust the result, read the p-value for significance, and report Cohen’s d for the size of the effect — always with the boxplot, p-value on the panel, in view.
Reuse
Citation
@online{2026,
author = {},
title = {T-Test in {R:} {One-Sample,} {Independent} {(Student} \&
{Welch)} \& {Paired}},
date = {2026-06-23},
url = {https://www.datanovia.com/learn/biostatistics/two-groups/t-test-in-r},
langid = {en}
}