Randomness & Statistical Inference

What a random sample can — and cannot — tell us

Merlin Schaeffer · Department of Sociology

2026-07-23

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.

One part of today’s lecture per goal. Our running question: do better-educated people generally believe more in the political system’s responsiveness?

Random samples & weights

Part 1 of 3

All our data are samples. To generalise from them safely, we lean on 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 like them 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?

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(across(c(psppsgva, trstlgl, trstplc, pspwght),
                zap_labels),                # Labelled -> numeric
         eduyrs = pmin(pmax(eduyrs, 9), 21), # Censor at 9 & 21 years
         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 Danes 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.

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.

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
((1*2 + 1 + 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.

Unweighted 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("Unweighted" = mod_unw, "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)
ESS_sample <- ESS %>% sample_n(50)

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 not seeing everyone.

… 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 — visible, and (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 <- replicate(1000, {          # 1000 repetitions of …
  s <- ESS %>% sample_n(50)                # … draw 50 people
  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\).

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%

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

The catch — and 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\).

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

&nbsp;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 — wide where data are thin, narrow where they are dense.

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 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\) — I bet it’s raining.

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

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

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

In our 50-person sample the estimate is positive but not significant — with so few people, sampling error is simply too large to rule out \(H_0\).

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,
  • standard error \(0.008\),
  • \(t = 3.2\), \(p = 0.0015\).

Answer: yes — better-educated Danes believe significantly more in the political system’s responsiveness. The effect is small per year, but clearly not zero.

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 — using 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.