ZRH --:--:--

How sample size affects variable selection

A simulation of how often lm() and stepwise selection pick the true predictors as the sample grows.

Simulation20223 min readSimulationRegressionR
On this page

With this exercise we want to inspect how lm function and step function vary when it comes to selection of correct variables depending on sample size.

Foto von Jackson Jost auf Unsplash

Generate Data

Show code (113 lines)
# Load packages
library(lme4)
library(dplyr)
library(ggplot2)

options(scipen=0)
set.seed(101)

# Variable and Dataset preparation
N <- c(25, 30, 40, 60, 100 ,500, 1000)
# create a data frame to later add which variables were selected
predi_selection_lm <- as.data.frame(matrix(NA, nrow = 19, ncol = length(N)))
predi_selection_step <- as.data.frame(matrix(NA, nrow = 19, ncol = length(N)))

names(predi_selection_lm ) <- c("N25", "N30", "N40", "N60", "N100", "N500", "N1000")
names(predi_selection_step ) <- c("N25", "N30", "N40", "N60", "N100", "N500", "N1000")

#counter to use as index for table predi_selection (loop)
a = 1

#loop over different N (sample size)
for(i in N){
  # Simulation dataset
  x1 <- runif(i, 0, 10)
  x2 <- runif(i, 0, 10)
  x3 <- runif(i, 0, 10)
  x4 <- runif(i, 0, 10)
  x5 <- runif(i, 0, 10)
  x6 <- runif(i, 0, 10)
  x7 <- runif(i, 0, 10)
  x8 <- runif(i, 0, 10)
  x9 <- runif(i, 0, 10)
  x10 <- runif(i, 0, 10)
  x11 <- runif(i, 0, 10)
  x12 <- runif(i, 0, 10)
  x13 <- runif(i, 0, 10)
  x14 <- runif(i, 0, 10)
  x15 <- runif(i, 0, 10)
  x16 <- runif(i, 0, 10)
  x17 <- runif(i, 0, 10)
  x18 <- runif(i, 0, 10)
  x19 <- runif(i, 0, 10)
  err <- runif(i, 0, 5)

  data <- cbind(x1,x2,x3,x4,x5,x6, x7, x8, x9 ,x10, x11, x12, x13, x14, x15, x16, x17, x18, x19,  err)
  data <- as.data.frame(data)

  # generate y values.
  y <- 30 + 0.4*x1 + 1*x2 + 2.3*x3 + 0.7*x4 + 0.2*x5  + 1*x6 + 2*x7 + 3*x8 + 1.5 *x9 + 0.5*x10 + err

  # add generated y values to data frame
  data_2 <- cbind(data, y)

  # Modelling with all variables as predi.
  mod <- lm(y~ x1 + x2 + x3 + x4 + x5 + x5 +x6 + x7 + x8 + x9 + x10 + x11 + x12 + x13 + x14 + x15 + x16 + x17 + x18 + x19, data =  data_2)
  summary(mod)

  ########################
  #lets try step function#
  ########################


  # use step function and save final model as stepwise_model
  stepwise_model <-  step(mod)


  # Extract p-val: lm
  p_val_lm <- (summary(mod)$coefficients[,4])
  p_val_lm <- p_val_lm[2:20] #take out intercept

  # Extract p-val: step
  p_val_step <- summary(stepwise_model)$coefficients[,4]
  p_val_step <- p_val_step[2:20] #take out intercept

  # Store value: lm
  predi_selection_lm[a] <- ifelse(p_val_lm < 0.05, 1, 0)

  #store values for step different, because summary output dosen't include all variable like in lm
  p_val_step <-  na.omit(ifelse(p_val_step < 0.05, 1, 0))

   #remove selected pred. without 0.05
  p_val_step <- p_val_step[! p_val_step %in% 0]

  # save the names of all selected coefficients as coes
  coes <- names(p_val_step)

  # create dummy var with all possible variables
  dummy_var <- c('x1','x2','x3','x4','x5','x6','x7','x8','x9','x10','x11','x12','x13','x14','x15','x16','x17','x18','x19')

  # which out of all possible variable have been selected?
  selected_coe <- ifelse(dummy_var %in% coes, 1,0)

  predi_selection_step[a] <- selected_coe

  #increase counter by one
  a = a + 1
}

# Summary accuracy predictors selection
true_pred_dummy <- c(1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0) #dummy code for the existing predictor

# new data frame to compare true predi. to selected predi.
predi_accuracy_lm <- data.frame(matrix(NA, nrow = 19, ncol = length(N)))
names(predi_accuracy_lm) <- c("N25", "N30", "N40", "N60", "N100", "N500", "N1000")

predi_accuracy_step <- data.frame(matrix(NA, nrow = 19, ncol = length(N)))
names(predi_accuracy_step) <- c("N25", "N30", "N40", "N60", "N100", "N500", "N1000")

#if all correct than coloumn all 1.
for (j in 1:length(N)){
  predi_accuracy_lm[j] <- ifelse(predi_selection_lm[j] == true_pred_dummy, 1,0)
  predi_accuracy_step[j] <- ifelse(predi_selection_step[j] == true_pred_dummy, 1,0)
}

Now lets visualize these results

Show code (18 lines)
# final data frame used for visualization
sample_s <- c("N25", "N30", "N40", "N60", "N100", "N500", "N1000")
summary_accuracy <- data.frame(cbind(sample_s,rep(NA, times = length(sample_s)),rep(NA, times = length(sample_s))))
names(summary_accuracy) <- c("sample_s", "accuracy_lm", "accuracy_step")

for( i in 1: nrow(summary_accuracy)){
  summary_accuracy[i,2] <-  mean(predi_accuracy_lm[,i])
  summary_accuracy[i,3] <-  mean(predi_accuracy_step[,i])

}
summary_accuracy$accuracy_lm <- as.numeric(summary_accuracy$accuracy_lm)
summary_accuracy$accuracy_step <- as.numeric(summary_accuracy$accuracy_step)
ggplot(data = summary_accuracy,aes(x=factor(sample_s,
                                           level = c("N25", "N30", "N40", "N60", "N100", "N500", "N1000")) ,
                                            y = accuracy_lm)) +
  geom_bar(stat= "identity")+
  xlab("sample size")+
  labs(title = "Accuracy of selected predictor per sample size")

This is the accuracy of the normal lm function with all predictors used.

ggplot(data = summary_accuracy,aes(x=factor(sample_s,
                                           level = c("N25", "N30", "N40", "N60", "N100", "N500", "N1000")) ,
                                            y = accuracy_step)) +
  geom_bar(stat= "identity")+
  xlab("sample size")+
  labs(title = "Accuracy of selected predictor per sample size")

This is the accuracy of the step function default = backwards selection (starting with full model: all predictors) .

Summary:

  • especially with low sample size N25 step function seems to perform less well than normal lm function
  • with increasing sample size both perform well and use all relevant predictors

Back to top

Esc

Type at least two letters. Search covers every project and knowledge-base note.

oz@zrh: ~
QUICK DOSSIER · 30 SEC READ

OLIVER ZINGG

Psychologist and data scientist in Zürich. I design the study, model the data and explain what it means.

STATUS
Open to full-time data science roles from January 2027
NOW
Working student, data science & project management at Zurich Insurance (since Nov 2025)
THESIS
M.Sc. thesis (HSLU): leveraging double machine learning for robust decision-making, due December 2026
EDUCATION
M.Sc. Psychology (UZH, 5.6) · M.Sc. Applied Information & Data Science (HSLU, Dec 2026)
TOOLS
R, Python, SQL, Matlab · Power BI, Tableau · mixed models, ML, BERTopic

TOP CASE FILES

  1. Learning and memory formation in children and adolescents with psychiatric disordersMaster's thesis, UZH · 2023
  2. Support vector machines vs. CNNs for Alzheimer's disease classificationBachelor's thesis, UZH · 2021
  3. Simpson's paradox and mixed-effects modelsStatistics · 2023