Skip to main content

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.

StatisticR call (one-tailed, upper)
zpnorm(z, lower.tail = FALSE)
tpt(t, df, lower.tail = FALSE)
chi-squarepchisq(x, df, lower.tail = FALSE)
Fpf(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.

Keep reading

Ready to run the numbers?

Our calculator shows the shaded distribution, the exact p-value, and a plain-English reading of what it supports.

Open the P-Value Calculator

Frequently asked questions

How do I calculate a p-value from a t-statistic in R?

Use pt with lower.tail = FALSE. For a one-tailed test, pt(t, df, lower.tail = FALSE). For a two-tailed test the safe idiom is 2 * pt(-abs(t), df), which works from the lower tail and avoids any cancellation. With t = 2.31 and df = 27 that returns 0.02876857.

What is the difference between pt and qt?

pt goes from a statistic to a probability, and qt goes back the other way. pt(2.31, 27, lower.tail = FALSE) gives the tail area beyond t = 2.31. qt(0.975, 27) gives the critical value that cuts off 2.5% in the upper tail, which is 2.051831. The p prefix is the distribution function and the q prefix is the quantile function, and the same convention holds for norm, chisq and f.

Why does 1 - pnorm(x) give zero?

Because pnorm(x) rounds to exactly 1 in double precision once x is large enough, and 1 minus 1 is 0. The subtraction destroys the information you wanted. pnorm(9, lower.tail = FALSE) computes the tail directly and returns 1.128588e-19. Always use lower.tail = FALSE rather than subtracting from one.

Does R use Welch or Student by default?

Welch. R's t.test does not assume equal variances unless you pass var.equal = TRUE. This is why R's degrees of freedom are often fractional — Welch estimates them from the two sample variances rather than using n1 + n2 − 2. It is the safer default and this calculator makes the same choice.

How do I get the p-value out of an R test object?

Assign the test to a variable and read its p.value element: result <- t.test(a, b); result$p.value. Every htest object in R carries p.value, and most also carry conf.int, estimate and statistic. Reading the element gives you the full precision, unlike the printed output which floors at 2.2e-16.

Why does chisq.test disagree with my calculator?

Almost always Yates' continuity correction, which R applies to 2 × 2 tables by default and many calculators do not. Pass correct = FALSE to chisq.test to turn it off, and the two should then agree to floating-point precision. The corrected p-value is the larger, more conservative of the two.