Randomness & Statistical Inference

What a random sample can, and cannot, tell us

Merlin Schaeffer · Department of Sociology

2026-10-26

By the end of today you can …

  1. explain why we rely on random samples, and how weights repair imperfect randomness;

  2. see that every sample carries sampling error, and estimate it with a standard error;

  3. use confidence intervals and hypothesis tests to say what a sample can, and cannot, generalise.

Random samples & weights

Part 1 of 3

All our data are samples. To generalise from them, we use one idea: randomness.

The research question of the day

Do better-educated people generally believe more strongly that the political system is responsive, that people have a say?

Political efficacy: the belief that one’s voice counts in politics.

Discuss: we only ever have data on some people, in some year. What has to be true about those people for us to trust a general answer?

Pupils protest the school reform outside Christiansborg, May 2016. Did it change anything? Photo: Ole Ryhl Olsson, CC BY-SA 4.0.

Preparation

pacman::p_load(
  tidyverse,    # Data manipulation and visualization
  haven,        # Read SPSS/Stata files & handle labelled data
  estimatr,     # OLS with robust standard errors (for weights)
  modelsummary  # Nicely formatted regression tables
)

Download ESS9e03_1.sav from Absalon into your course project folder, then:

ESS <- read_spss("ESS9e03_1.sav") %>%
  filter(cntry == "DK") %>% # Keep only the Danish respondents
  # A minimal set of variables for today
  select(idno, pspwght, gndr, eduyrs, agea,
         psppsgva, trstlgl, trstplc) %>%
  mutate(psppsgva = zap_labels(psppsgva), # Make numeric
         trstlgl = zap_labels(trstlgl),   # Make numeric
         trstplc = zap_labels(trstplc),   # Make numeric
         pspwght = zap_labels(pspwght),   # Make numeric
         eduyrs = case_when(              # Censor education at 9 and 21 years
           eduyrs > 21 ~ 21,
           eduyrs < 9 ~ 9,
           TRUE ~ as.numeric(eduyrs)),
         gndr = as_factor(gndr)) %>%
  drop_na() # Drop cases with missing values

Why do we study samples at all?

(1) Populations are too large to survey everyone.

(2) Even with data on everyone, we care about general patterns and social mechanisms, not just this year’s people in DK or today’s countries.

So we treat any data set (even a full register) as one sample drawn from an unobservable “super-population”.

Source: Wikipedia

Convenience samples mislead

A good sample is representative, it mirrors the super-population.

Discuss: you snowball-sample starting from your best friend. What bias creeps in, and who gets left out?

Your friends resemble you (age, education, city, politics). Whole groups are systematically missing; the sample is biased before you compute anything.

Randomness = fairness

Why do so many games use dice, coins, shuffled cards?

Fairness: a random draw gives everyone the same chance, regardless of who they are. No player, and no social group, can be systematically forgotten.

A random sample is the equivalent of stirring the soup before you taste it: one spoonful then represents the whole pot.

Soup: Michael W. Kolton, CC BY-SA 3.0.

But participation is not random

Who answers a survey is never a perfect coin flip:
some groups are harder to reach, or more willing to respond.

We repair this after the fact with post-stratification weights.

Denmark, 2015. The last polls put Dansk Folkeparti at ~18% — it won 21.1%. On the other side, SF and Enhedslisten polled about a point above what they got.

Part of that gap is late deciders, not only refusals, but either way: who ends up in the data is not random.

Cartoon: Adam Zyglis, The Buffalo News / Cagle Cartoons

What is a weight?

A weight reflects how much a case should count, to undo over- or under-sampling.

In a perfect random sample every case has weight 1. If older people are over-represented, we give them a weight below 1; under-represented groups get a weight above 1.

Discuss: the plot shows the ESS weights by age. Why might survey researchers down-weight older respondents here?

How weights work: a worked example

Imagine a patriarchal ballot on women’s driving rights, where a man’s vote counts double a woman’s.

Gender Vote Voted yes Weight
man No 0 2
man Yes 1 2
woman Yes 1 1
woman Yes 1 1
woman No 0 1

Discuss: ignoring weights, what share voted “Yes”?

Three equivalent ways to get the weighted share of “Yes”:

# (yes × weight) summed, over total weight
((0*2 + 1*2 + 1 + 1 + 0*1) / (2 + 2 + 1 + 1 + 1)) * 100
# [1] 57.1
yes    <- c(0, 1, 1, 1, 0)
weight <- c(2, 2, 1, 1, 1)
sum(yes * weight) / sum(weight) * 100
# [1] 57.1
weighted.mean(x = yes, w = weight) * 100
# [1] 57.1

Weights can be powerful

With the right weights, even a wildly non-representative sample can forecast well.

Wang, Rothschild, Goel, and Gelman (2015) predicted the 2012 US election from Xbox gamers (young, male, unrepresentative) reweighted to the electorate.

Source: Wang, Rothschild, Goel et al. (2015)

Using weights in regression

Most R functions take a weights argument. For weighted OLS use estimatr::lm_robust().

Weights make the residuals heteroscedastic, which breaks a standard OLS assumption. lm_robust() fixes the standard errors for us.

Pol. Efficacy (unweighted) Pol. Efficacy (weighted)
(Intercept) 2.199 2.378
eduyrs 0.039 0.025
Num.Obs. 1511 1511
R2 0.026 0.010
mod_unw <- lm_robust(psppsgva ~ eduyrs, data = ESS)
mod_wt  <- lm_robust(psppsgva ~ eduyrs, data = ESS, weights = pspwght)

modelsummary(
  list("Pol. Efficacy \n (unweighted)" = mod_unw, 
       "Pol. Efficacy \n (weighted)" = mod_wt),
  statistic = NULL,
  gof_map = c("nobs", "r.squared"),
  output = "kableExtra"
)

Sampling error & the standard error

Part 2 of 3

Randomness removes bias, but a random sample is still noisy. How noisy, exactly?

A thought experiment: pretend we see everyone

Suppose the Danish ESS (1511 people) were our whole super-population.

Then we could calculate the true slope of political efficacy on education (blue line): \[\beta = 0.039\]

No weights here; this is a make-believe population.

Now draw one small sample

We rarely see the population, usually just a sample. Take 50 people at random:

set.seed(1261990) # Make randomness reproducible
ESS_sample <- ESS %>% sample_n(50) # Sample 50 cases at random

The sample’s own regression line (red) gives \(\hat\beta = 0.042\); not the true 0.039.

Another sample, another answer

Draw a different 50 people and the line moves again: \(\hat\beta = -0.008\).

Beware: each sample gives a different estimate. This scatter of estimates around the truth is sampling error; not a mistake, but the price of randomness and seeing only a sample.

… and again, and again

Keep drawing fresh samples of 50. Every red line is a new random sample; the blue line is the unchanging truth.

The lines fan out around the true line. That fan is the sampling error: in this thought experiment visible, but normally (as we’ll see) predictable.

Do it 1000 times: the sampling distribution

Repeat “draw 50, estimate \(\hat\beta\)” 1000 times and collect all the estimates. Their spread is the sampling error; and it has a beautiful, predictable shape.

set.seed(1261990)
sample_betas <- c() # An empty vector to collect the 1000 slopes
for (draw in 1:1000) {                     # 1000 repetitions of …
  s <- ESS %>% sample_n(50)                # … draw 50 people
  sample_betas[draw] <- coef(lm(psppsgva ~ eduyrs, data = s))["eduyrs"] # … store the slope
}
sd(sample_betas) # The true sampling error
# [1] 0.0367

Each dot is one sample’s slope. As they pile up, a bell curve emerges around the true \(\beta\).

true_beta <- coef(lm(psppsgva ~ eduyrs, data = ESS))["eduyrs"] %>% unname()
sd_b <- sd(sample_betas)

ggplot(tibble(b = sample_betas), aes(x = b)) + # One row per sample's slope
  geom_histogram(binwidth = 0.008, fill = "#901A1E", alpha = 0.85) +
  geom_vline(xintercept = true_beta, color = "#425570", linewidth = 1.2) +
  geom_vline(xintercept = true_beta + c(-1.96, 1.96) * sd_b,
             color = "#b5892c", linetype = "longdash", linewidth = 1) +
  labs(x = expression("1000 sample estimates of "*hat(beta)),
       y = "Count",
       caption = "Blue = true β · gold dashed = ±1.96 × sampling error")

The estimates pile up in a Normal (bell) curve around the truth. We know its areas exactly:

  • \(\pm 1.65 \times\) SD covers the middle 90%
  • \(\pm 1.96 \times\) SD covers the middle 95%

Those multipliers, 1.65 and 1.96, are the critical values: how far out from the centre you must go, counted in standard deviations, to cover a given share of the curve. Want more coverage, walk further out.

We will soon multiply a standard error by a critical value to build confidence intervals and tests.

Source: Veaux, Velleman, and Bock (2021)

With small \(n\), the curve has slightly fatter tails: the \(t\)-distribution. Its critical value depends on the degrees of freedom (\(n\) − parameters):

# 95% critical value, df = 50 − 2
qt(p = 0.975, df = 48)
# [1] 2.01

Source: Wikipedia

Gauss’s gift

Discuss: in real research we get one sample. We can’t draw 1000. So how could we possibly know the sampling error?

Thanks to Carl Friedrich Gauss, we can estimate the sampling error from a single sample. That estimate is the standard error \(\hat\sigma\).

Carl Friedrich Gauss · 1777 – 1855

For an OLS slope: \[\hat{\sigma}(\hat\beta) = \sqrt{\frac{1}{n-2}\,\frac{\text{SD}(e)}{\text{SD}(x)}}\]

Pol. Efficacy (50-person sample)
(Intercept) 1.965
(0.530)
eduyrs 0.042
(0.036)
Num.Obs. 50
R2 0.027

The estimated SE from this one sample (0.036) is remarkably close to the true sampling error from 1000 samples (0.037).

Your turn: samples & weights

Break

Confidence intervals & hypothesis tests

Part 3 of 3

The standard error turns a single estimate into an honest statement of uncertainty.

95% confidence intervals

Combine the estimate with its standard error:

\[\hat\beta \pm \text{critical value} \times \hat\sigma\]

What “95%” means: if we drew samples over and over and built this interval each time, 95% of those intervals would contain the true \(\beta\) (the blue line). It is a statement about the procedure, not any single interval.

Watch the intervals accumulate: only about 1 in 20 (the red ones) misses the truth.

Confidence intervals, hands-on

Play with samples, coverage, and interval width yourself:

Uncertainty, drawn

geom_smooth(method = "lm") shades the 95% CI around the line.

ggplot(ESS_sample, aes(y = psppsgva, x = eduyrs)) +
  geom_jitter(alpha = 1/3,
              width = 0.1, height = 0.1) +
  geom_smooth(method = "lm", color = "#901A1E") + # 95% CI band
  ylim(1, 5) +
  labs(y = "Political efficacy",
       x = "Years of education")

The band is exactly the region where those sampled regression lines plausibly lie. Watch them fall inside it.

Hypothesis tests: argue with an opponent

The debate of Socrates and Aspasia

Research is a debate with a sceptic who dismisses your finding:

“Sure, your educated respondents scored higher. But that’s just a fluke of your 5-person sample. Really there’s no difference at all.”

\[\underbrace{H_{0}}_{\text{the } H_{\text{opponent}}}:\ \beta_{\text{education}} = 0\]

We test whether the data can overturn this null.

The (weird) logic of a test

  • \(H_0\): the sun is shining.

  • I see 5 kids walking to school with umbrellas. How likely is that if the sun is really shining?

  • If that probability is below 5%, I stop believing \(H_0\), and bet it’s raining.

Discuss: can you phrase your own everyday \(H_0\)-and-evidence example?

We never prove our \(H_1\)! We show the sceptic’s \(H_0\) makes the data too surprising to keep believing in \(H_0\).

A test, step by step

1 The sceptic’s claim: \(H_0: \beta = 0\).

2 The ruler: our sample’s standard error, \(\hat\sigma = 0.043\).

3 The sampling distribution: If \(H_0\) were true, samples of 50 would scatter around 0 like this (\(t\)-distribution, df = 48).

4 The test interval: 95% of those chance estimates land within \(0 \pm 2.01 \times \hat\sigma\).

5 The \(t\)-value: Our estimate** \(\hat\beta = 0.042\) is \(t = 0.98\) standard errors from 0; here inside the test interval.

6 The \(p\)-value: 67% of chance estimates are closer to 0 than ours. And the other \(p = 0.33\) are at least as far out → the probability \(p\) to draw a sample like ours if $H_0# is true, is \(p = 0.33\). Not surprising → we can’t reject \(H_0\).

Put it into practice

\(t\)-value \(= \dfrac{\hat\beta}{\hat\sigma}\): how many standard errors the estimate sits from 0.

\(p\)-value: if \(H_0\) were true, the probability of an estimate this far from 0 (or further).

A small \(p\) (by convention \(< 0.05\)) means the data are hard to reconcile with \(H_0\); evidence against it, not proof of \(H_1\).

ols <- lm_robust(psppsgva ~ eduyrs,
                 data = ESS_sample)
tidy(ols) %>%                    # Tidy result table
  filter(term == "eduyrs") %>%
  select(estimate, std.error,
         statistic, p.value)
#   estimate std.error statistic p.value
# 1    0.042     0.043     0.976   0.334

50 people: the estimate lands inside the test interval → \(p = 0.33\), can’t reject \(H_0\).

All 1,511 people in DK: a smaller estimate, but far outside a much narrower interval → \(p = 0.0015\), reject \(H_0\).

Same question, same axis. Only the standard error shrank.

Political efficacy
(Intercept) 2.378***
(0.114)
eduyrs 0.025**
(0.008)
Num.Obs. 1511
R2 0.010
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

Learning goal achieved

Using the whole weighted sample, education predicts political efficacy:

  • \(\hat\beta = 0.025\) per year of education,
  • \(\hat\sigma =0.008\),
  • \(t = 3.2\), \(p = 0.0015\).

Answer: Yes, better-educated people believe significantly more in the responsiveness of Denmark’s political system.

Your turn: inference

Check yourself: today’s goals

Look back at the goals from the start of the lecture. Can you tick all three?

  • Explain to your neighbour why a random sample beats a convenience sample, and what a weight repairs.
  • Say, in one sentence, what a standard error estimates; use the word sampling distribution.
  • Read a regression’s \(t\)- and \(p\)-value and state what they do (and don’t) let you conclude about \(H_0\).

Anything feel shaky? That is what this week’s Absalon quiz and the Friday exercise class are for.

Today’s important functions

  1. haven::read_spss() + zap_labels(): read SPSS data and turn labelled columns numeric.
  2. dplyr::sample_n(): draw a random sample of rows.
  3. weighted.mean(), and the weights = argument: analyses that respect survey weights.
  4. estimatr::lm_robust(..., weights =): weighted OLS with correct standard errors.
  5. qt(): critical values from the \(t\)-distribution.
  6. geom_smooth(method = "lm"): an OLS line with its 95% confidence band.
  7. broom::tidy(): pull estimates, standard errors, \(t\)- and \(p\)-values into a tibble.

References

Veaux, D., Velleman, and Bock (2021). Stats: Data and Models, Global Edition. Pearson Higher Ed.

Wang, W., D. Rothschild, S. Goel, et al. (2015). “Forecasting elections with non-representative polls”. In: International Journal of Forecasting, pp. 980-991.