sample(1:10, size = 5)Eric Manning
September 1, 2026
sample(), set.seed(), and a couple of named distributionsd/p/q/r familyWe are not teaching statistics today. We are showing you the machinery, so the concepts seem vaguely familiar later.
Additional: Geyer, Distributions in R — a one-page lookup table for every distribution R ships with. Bookmark it.
sample(): draw from a vectorGive it something to draw from and how many draws you want:
[1] 10 4 8 2 9
By default, each element can be drawn once. size can’t exceed the vector:
Error in `sample.int()`:
! cannot take a sample larger than the population when 'replace = FALSE'
The vector doesn’t have to be numbers:
[1] "tails" "heads" "heads" "tails" "tails" "heads" "heads" "heads" "heads"
[10] "heads"
probprob weights the draws. It lines up with the vector you’re sampling from:
[1] "heads" "heads" "heads" "heads" "heads" "heads" "heads" "heads" "heads"
[10] "heads"
prob gets normalized, so c(9, 1) and c(0.9, 0.1) behave identically.
Run the same line twice:
[1] 32 74 6 51
[1] 44 29 63 14
Results are not reproducible between runs.
set.seed()R’s randomness is not random — it’s a deterministic sequence that looks random. set.seed() says where to start:
[1] 93 97 38 45
[1] 93 97 38 45
set.seed() in practiceOne trial, two outcomes, probability p of a 1/T/‘yes’/etc. A (biased?) coin flip.
Every value in a range is equally likely.
rbinom()size = 1 means one coin per draw; prob is the chance of a 1:
[1] 1 1 0 0 1 0 0 1 0 1 0 1 0 1 0 0 1 0 0 0
runif()runif(n) gives n values between 0 and 1, none more likely than any other:
[1] 0.6986735 0.5565305 0.1401400 0.2857233 0.5553690
This is the normal distribution. Note the shape and move on.
rnorm() argumentsmean centers it, sd sets its width. The defaults are 0 and 1:
[1] 0.52058907 -1.07969076 0.13923812 -0.08474878 -0.66663962
[1] 62.25866 88.97280 84.69817 101.70332 92.89314
Every r*() function follows this shape: how many draws, then the knobs for that distribution.
d / p / q / r familyEvery distribution in R comes as four functions sharing one name stem:
| Prefix | Question it answers | Normal |
|---|---|---|
r |
Give me random draws | rnorm() |
d |
How dense/tall is the curve here? | dnorm() |
p |
How much of the distribution’s mass lies below here? | pnorm() |
q |
Which value has this much below it? | qnorm() |
Jason Mercer, “A brief visualization of R’s distribution functions” (2020).
Top row works on the density curve, bottom row on the cumulative curve:
dnorm() — you hand it a value, it hands back the height of the curvernorm() — draws values, more often where the curve is tallpnorm() — you hand it a value, it hands back the probability below itqnorm() — the inverse: hand it a probability, get the valuep and q undo each other.
p and qHalf the curve lies below its center:
[1] 0.5
d is not a probability[1] 0.3989423
0.399 is a height For a continuous distribution the probability of any exact value is zero.
Probability lives in areas under the curve (pnorm()). dnorm() is for drawing the curve.
dstat_function() takes the function itself — no data frame needed:
p is the shaded areaThe blue strip runs from the far left up to qnorm(0.025) = -1.96, and its area is pnorm(-1.96) = 0.025.
“2.5% of the distribution sits below -1.96.”
?Distributions lists every one R ships with. Geyer’s lookup table is friendlier.
Some probability questions have clean formulas. Many do not.
1. Do the random thing once
2. Wrap it in a function that returns one number or TRUE/FALSE
3. Run that function many times
4. Summarize the results
What’s the probability two dice sum to 7?
[1] 5 1
[1] FALSE
[1] TRUE
replicate()replicate(n, expr) runs an expression n times and collects the results:
[1] 10000
[1] FALSE TRUE FALSE FALSE FALSE FALSE
This is just a for loop.
[1] 0.1654
The true answer is 6/36 = 0.1667.
Run the whole simulation at different sizes and watch it settle:
[1] 0.20000 0.15000 0.16300 0.16760 0.16661
More replications == more precision in your estimate.
[1] 62496 57056 50635 57504 69826
Why?
1. You have the population. Draw samples (without replacement) to explore sampling variability. You choose (n), and seeing what changes as (n) changes is the point.
2. You have one sample of a population. Resample its observations with replacement, treating the resample as a stand-in for the population. This is the bootstrap. Variation among the bootstrap iterations helps us quantify uncertainty in the estimate derived from the observed sample.
You have 50 observations. You want to know how much your estimate would have varied if you’d drawn a different 50.
You can’t — there’s only one dataset, but we can treat the sample as if it were the population and redraw from it at the same size:
[1] 64069.1
Do that 10,000 times.
If you don’t bootstrap with the same size, you have to apply a correction factor.
boot::boot() — the classic implementation, several kinds of interval; will apply correction factorsrsample::bootstraps() — tidyverse-flavoredBoth do exactly what we just did – and other stuff.
Meng (2018) examines a 2016 election survey with 2.3 million respondents — about 1% of the American electorate.
Its tiny non-randomness meant it carried the same error as a genuine random sample of about 400 people.
Render as you go. No AI tools.
princeton-ddss.github.io/r-bootcamp/notes/day-3-sim-ex.pdf
| Function | What it does |
|---|---|
sample(x, size, replace, prob) |
Draw from a vector |
set.seed(n) |
Make the draws reproducible |
runif(n, min, max) |
Uniform draws |
rbinom(n, size, prob) |
Coin flips / counts of successes |
rnorm(n, mean, sd) |
Normal draws |
dnorm() / pnorm() / qnorm() |
Height / area below / inverse |
replicate(n, expr) |
Run an expression n times, keep results |
mean(logical_vector) |
Proportion TRUE |
quantile(x, probs) |
Cut points of a set of numbers |