How to find a p-value in R
R has a p-value function for every distribution and a test function for every common design. Here is the full map — plus the argument that quietly decides whether your answer is accurate in the far tail.
8 min read · Last reviewed 7 August 2026
The short answer
If you already have a test statistic, R converts it with a distribution function. Each one
takes lower.tail = FALSE to give you the upper-tail area directly.
| Statistic | R call (one-tailed, upper) |
|---|---|
| z | pnorm(z, lower.tail = FALSE) |
| t | pt(t, df, lower.tail = FALSE) |
| chi-square | pchisq(x, df, lower.tail = FALSE) |
| F | pf(f, df1, df2, lower.tail = FALSE) |
For a two-tailed test on a symmetric distribution, double the tail of the absolute value:
2 * pt(-abs(2.31), 27)
#> 0.02876857
Chi-square and F are the exception. They are already right-tailed, and doubling them is not a conservative choice — it is meaningless. The chi-square converter explains why.
The argument that decides your accuracy
There are two ways to write an upper tail in R, and they are not equally good:
# Correct
pnorm(9, lower.tail = FALSE)
#> 1.128588e-19
# Wrong in the far tail
1 - pnorm(9)
#> 0
The second form computes a number very close to 1, then subtracts it from 1. Double precision cannot represent the difference, so the answer collapses to exactly zero — and R reports that a result which should be 1.13 × 10⁻¹⁹ is impossible.
lower.tail = FALSE computes the tail directly and never forms the difference.
Use it every time. This calculator is built on the same principle, which is why it does not
print p = 0.000; the methodology page shows the
published comparisons.
From raw data
If you have the observations rather than the statistic, the test functions do both steps:
# Welch's t-test — R's default, and the safer one
t.test(group_a, group_b)
# Student's t-test, assuming equal variances
t.test(group_a, group_b, var.equal = TRUE)
# Paired
t.test(before, after, paired = TRUE)
# One sample against a target
t.test(x, mu = 100)
# Chi-square on a contingency table
chisq.test(matrix(c(31, 19, 22, 28), nrow = 2))
# Pearson correlation
cor.test(x, y)
# One-way ANOVA
summary(aov(value ~ group, data = df))
Two defaults are worth knowing. t.test runs Welch's version
unless you ask otherwise, which is the right default because it does not assume equal
variances. And chisq.test applies Yates' continuity correction
to 2 × 2 tables by default, so its p-value will be slightly larger than an uncorrected one.
Pass correct = FALSE to turn it off if you are comparing against a calculator
that does not apply it.
Pulling the number out
Every htest object carries the p-value as a named element, so you never need to
read it off the printout:
result <- t.test(group_a, group_b)
result$p.value
#> 0.02876857
result$conf.int
result$estimate
For a regression, the coefficient table holds them:
coef(summary(model))[, "Pr(>|t|)"]
Why R prints "< 2.2e-16"
R's print methods floor very small p-values at 2.2e-16, which is machine
epsilon for double precision. It is a display convention, not the actual value — the number
in result$p.value is usually far smaller and perfectly usable. If you need to
report it, print the element directly rather than the object.
A p-value that small is rarely the interesting part of a result anyway. It says the null is implausible; it says nothing about whether the effect is large enough to matter. Report the effect size and interval alongside it.
Checking R against something else
R, SciPy and this calculator agree to within floating-point noise, because they implement the same underlying functions. If you want to verify a specific result, paste your statistic and degrees of freedom into the calculator and compare. A disagreement in the third significant figure almost always means a different test was run — Welch versus Student, corrected versus uncorrected, one tail versus two — rather than a numerical error.