Probability Distributions
Reviewed & published by Brayan K
R provides every common probability distribution through one consistent system: a distribution name (norm, binom, pois…) prefixed by d, p, q, or r to ask for a density, a cumulative probability, a quantile, or random draws.
Part of the free R course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
In this lesson you'll master the d/p/q/r prefix system with the normal distribution, branch out to runif/rbinom/rpois, lock down reproducibility with set.seed(), and draw from explicit sets with sample().
What You'll Learn in This Lesson
- What the d, p, q, and r prefixes each compute
- How to answer percentile and probability questions with pnorm/qnorm
- How the same system extends to binomial, Poisson, and uniform
- How to generate random draws with rnorm/runif/rbinom/rpois
- Why set.seed() makes random results reproducible
- How sample() draws from an explicit set of values
1️⃣ The d / p / q / r System
Learn one distribution's four functions and you've learned them all. For the normal distribution, the suffix is norm, and the prefix selects the question. The three deterministic ones below give the same answer on every machine.
# Every distribution in R shares a 4-FUNCTION naming system. Take the
# normal distribution ("norm") and bolt one of four prefixes on front:
#
# d -> DENSITY height of the curve at a value
# p -> PROBABILITY cumulative area to the LEFT (a percentile)
# q -> QUANTILE the inverse of p: value at a given percentile
# r -> RANDOM draw random numbers from the distribution
dnorm(0) # density of standard normal at 0
pnorm(0) # P(X <= 0) for standard normal
qnorm(0.975) # the value with 97.5% of area to its leftAdd mean and sd to describe any normal. Here's a worked "heights" example that answers real probability questions exactly.
# d/p/q all accept mean and sd so you can describe ANY normal, not just
# the standard one. Suppose adult heights are Normal(mean=170, sd=10) cm.
# What fraction of people are SHORTER than 180 cm? (a 'p' question)
pnorm(180, mean = 170, sd = 10) # -> 0.8413447
# What height is the 90th percentile? (a 'q' question)
qnorm(0.90, mean = 170, sd = 10) # -> 182.8155
# What fraction are BETWEEN 160 and 180 cm? Subtract two p-values:
pnorm(180, 170, 10) - pnorm(160, 170, 10) # -> 0.68268952️⃣ Beyond the Normal
The same four-letter pattern covers the binomial, Poisson, uniform, exponential, t, chi-squared, and more — just change the suffix and supply that distribution's parameters. These density/probability calls are exact, so their output is stable across machines.
# The same prefix system covers many distributions. Swap the suffix:
#
# unif -> Uniform runif(n, min, max)
# binom -> Binomial rbinom(n, size, prob)
# pois -> Poisson rpois(n, lambda)
# exp, gamma, beta, t, chisq, ... and more.
# Probability of exactly 3 heads in 10 fair coin flips (a 'd' on binom):
dbinom(3, size = 10, prob = 0.5) # -> 0.1171875
# Probability of AT MOST 2 calls/hour when the mean rate is 4 (Poisson):
ppois(2, lambda = 4) # -> 0.2381033
# The d/p/q functions are exact maths, so these numbers are stable
# across machines and R versions — only the r* draws below vary.Reading them: dbinom(3, 10, 0.5) is the probability of exactly 3 successes; ppois(2, 4) is the probability of at most 2 events. Use d for "exactly," p for "at most / less than."
3️⃣ Random Draws & set.seed()
The r* functions simulate data. Because they use R's pseudo-random generator, every run differs — until you fix the starting state with set.seed(). We omit the printed numbers here because exact draws depend on your R version, but the code is reproducible on a given machine.
# The r* functions DRAW random numbers. They use R's RNG, so results
# differ every run UNLESS you fix the seed with set.seed() first.
# NOTE: the exact draws can also differ between R VERSIONS (the default
# RNG changed in R 3.6), so treat printed draws as illustrative.
set.seed(123)
rnorm(5, mean = 0, sd = 1) # 5 standard-normal draws
runif(3, min = 0, max = 10) # 3 uniform draws in [0, 10]
rbinom(4, size = 1, prob = 0.5) # 4 coin flips (0/1)
rpois(3, lambda = 2) # 3 Poisson counts
# Run it: with set.seed(123) the SAME machine reproduces the SAME
# numbers every time. Remove set.seed() and they change each run.sample() is different: it draws from a set you provide — perfect for dice, cards, shuffling, and bootstrapping. Set replace = TRUE to allow repeats.
# sample() draws from a set you supply (not a named distribution).
set.seed(7)
sample(1:6, 1) # one roll of a die
sample(1:6, 10, replace = TRUE) # ten rolls (replace = repeats allowed)
sample(c("H", "T"), 5, replace = TRUE) # five coin flips
sample(letters, 3) # 3 distinct letters, no replacement
# Shuffle a vector by sampling it at full length without replacement:
sample(1:5) # a random permutation of 1..5
# Reproducible because of set.seed(7); exact values are R-version
# dependent, so run it yourself to see your machine's draws.Your turn. Fill in the ___ blank and run it — the deterministic p/q answers are shown.
# 🎯 YOUR TURN — replace each ___ then run it.
# Exam scores are Normal(mean = 75, sd = 8).
# 1) What fraction of students score BELOW 60? (a 'p' question)
below60 <- pnorm(60, mean = 75, sd = 8)
cat("P(score < 60) =", round(below60, 4), "\n") # 0.0304
# 2) What is the 95th-percentile score? (a 'q' question)
top5 <- qnorm(0.95, mean = 75, sd = ___) # 👉 replace ___ with 8
cat("95th percentile =", round(top5, 2), "\n") # 88.16
# 3) Simulate 1000 scores and check the sample mean is near 75.
set.seed(2024)
scores <- rnorm(1000, mean = 75, sd = 8)
cat("Simulated mean =", round(mean(scores), 2), "\n") # close to 75Mini-Challenge: Simulate a Dice Game
Compare simulation against exact theory — the heart of Monte Carlo thinking. Simulate thousands of games, measure the empirical win rate, and check it against the 8/36 truth.
# 🎯 MINI-CHALLENGE: simulate a casino game.
#
# A simple game: roll two fair dice; you WIN if the total is 7 or 11.
#
# 1. Use set.seed() for reproducibility.
# 2. Simulate 10,000 games. For each, sample() two dice (1:6 with
# replacement) and add them — or draw both columns at once.
# 3. Compute the EMPIRICAL win rate (proportion of totals in {7, 11}).
# 4. Compare it to the EXACT theoretical probability: there are 6 ways
# to make 7 and 2 ways to make 11 out of 36 -> 8/36 = 0.2222.
# 5. Print both and confirm the simulation is close.
#
# Write it yourself, then run in RStudio or rdrr.io/snippets.
# your code hereCommon Errors (and the fix)
- ❌ Treating dnorm() as a probability — ✅ that's a density (a curve height), not a probability; use pnorm() for "fraction below."
- ❌ Random results change every run — ✅ call set.seed() just before the r*/sample() line to reproduce them.
- ❌ sample(1:6, 10) errors — ✅ you can't draw 10 from 6 without repeats; add replace = TRUE.
- ❌ qnorm() given a value, not a probability — ✅ q takes a probability in [0, 1]; pass 0.95, not 95.
Pro Tips
- 💡 Upper tail directly: pass lower.tail = FALSE to get P(X > x) without doing 1 - pnorm(x).
- 💡 Weighted draws: sample(x, n, prob = w) lets some outcomes be more likely than others.
- 💡 Bootstrap a statistic: resample your data with sample(x, length(x), replace = TRUE) to estimate uncertainty.
📋 Quick Reference — Distributions
| Task | R Syntax |
|---|---|
| Density at a point | dnorm(x, mean, sd) |
| P(X ≤ x) | pnorm(x, mean, sd) |
| Percentile value | qnorm(p, mean, sd) |
| Random normals | rnorm(n, mean, sd) |
| Reproducible draws | set.seed(123) |
| Draw from a set | sample(1:6, 10, replace = TRUE) |
🎉 Lesson Complete!
- ✅ d = density, p = cumulative probability, q = quantile, r = random
- ✅ pnorm and qnorm are exact inverses
- ✅ The system extends to binom, pois, unif, and more
- ✅ set.seed() makes r* and sample() reproducible
- ✅ sample() draws from a set you supply, with or without replacement
Practice quiz
In R's distribution naming, what does the p prefix compute?
- The density at a point
- A random draw
- The cumulative probability (area to the left)
- The quantile
Answer: The cumulative probability (area to the left). p gives P(X <= x), the cumulative probability up to a value.
What does the q prefix (as in qnorm) return?
- The value at a given percentile
- A probability
- A density height
- A random number
Answer: The value at a given percentile. q is the inverse of p: give it a probability, it returns the value.
What does dnorm() give you?
- A cumulative probability
- A random draw
- A percentile rank
- The density (curve height) at a point
Answer: The density (curve height) at a point. d gives density, not a probability; it can even exceed 1.
Which function draws random numbers from a normal distribution?
- pnorm()
- rnorm()
- qnorm()
- dnorm()
Answer: rnorm(). The r prefix generates random draws from the distribution.
What does set.seed(123) accomplish?
- Makes subsequent random draws reproducible
- Speeds up the r* functions
- Sets the mean to 123
- Draws 123 numbers
Answer: Makes subsequent random draws reproducible. Fixing the RNG seed reproduces the same draws on the same R version.
pnorm and qnorm are related how?
- They are unrelated
- They both return densities
- They are exact inverses of each other
- qnorm always doubles pnorm
Answer: They are exact inverses of each other. qnorm(pnorm(x)) == x; one maps value->prob, the other prob->value.
What does dbinom(3, size = 10, prob = 0.5) compute?
- P(at most 3 successes)
- P(exactly 3 successes in 10 trials)
- The mean number of successes
- A random number of successes
Answer: P(exactly 3 successes in 10 trials). d on a discrete distribution gives the probability of exactly that count.
What does ppois(2, lambda = 4) give?
- P(exactly 2 events)
- The mean 4
- A random Poisson count
- P(at most 2 events) when the mean rate is 4
Answer: P(at most 2 events) when the mean rate is 4. p is cumulative: probability of 2 or fewer events.
Why does sample(1:6, 10) without replace = TRUE error?
- 10 is too large a number
- You can't draw 10 distinct values from only 6
- 1:6 is not a vector
- sample needs a probability
Answer: You can't draw 10 distinct values from only 6. Without replacement you can draw at most 6; add replace = TRUE for repeats.
qnorm() expects its first argument to be...
- A raw data value like 95
- A count of trials
- A probability between 0 and 1
- A standard deviation
Answer: A probability between 0 and 1. q takes a probability in [0, 1]; pass 0.95, not 95.
Continue this course
- Previous: ggplot2: Facets, Themes & Scales
- Next: Hypothesis Testing (t.test, chisq.test) — Run and interpret t-tests, chi-squared tests, and p-values
- Quick reference: R cheat sheet
Frequently asked questions
What do the d, p, q, and r prefixes actually mean?
They pick which question you're asking about a distribution. d (density) gives the height of the probability curve at a point — for continuous variables this is a density, not a probability. p (probability) gives the cumulative probability, the area to the LEFT of a value, i.e. a percentile. q (quantile) is the inverse of p: give it a probability and it returns the value at that percentile. r (random) generates random draws. Memorise these four and you instantly know dnorm/pnorm/qnorm/rnorm, dbinom/pbinom/..., dpois/ppois/..., and dozens more.
Why do I get different random numbers every time, and how do I stop that?
The r* functions and sample() draw from R's pseudo-random number generator, whose state advances on each call — so every run gives fresh numbers. Call set.seed(some_integer) immediately before the random step to fix the starting state; then the same code reproduces the same draws every time on the same R version. This is essential for reproducible analyses, debugging, and teaching. One caveat: R changed its default sampling algorithm in version 3.6, so identical seeds can yield different draws across major R versions.
What's the difference between pnorm and dnorm?
dnorm gives the density — the height of the bell curve at a single point — which on its own isn't a probability and can even exceed 1 for narrow distributions. pnorm gives a genuine probability: the total area under the curve up to that point, i.e. P(X <= value). When you want 'what fraction is below x' use pnorm; when you want the relative likelihood at exactly x (mostly for plotting the curve or likelihood calculations) use dnorm. For the probability of a RANGE, subtract two pnorm values.
How is sample() different from the r* functions?
The r* functions draw from a named theoretical distribution defined by parameters — rnorm needs a mean and sd, rbinom needs size and prob. sample() instead draws from a finite set of values you hand it, with or without replacement, optionally with custom probabilities via the prob argument. Use rbinom/rpois/rnorm to model a known distribution; use sample() to shuffle, bootstrap (resample your data with replacement), draw a random subset, or simulate dice and cards from an explicit set of outcomes.