Statistical Simulation in R with Code — Part 1 | by Leihua Ye, PhD | Towards Data Science

This is part 1of a dual post on Statistical Simulation. Please check out part 2 here if you haven’t:
towardsdatascience.com
For the past few months, I’ve received several Data Science interviews from the top tech companies. Almost all of the interviewers asked me to perform statistical simulation for a real business scenario, which I failed to impress.
You know, they are reasonable questions and not super difficult to answer if I had been exposed to the topic before.
Until today, I’ve spent hundreds of hours learning simulations and preparing for the next technical interviews.
Statistical simulation is a rather broad field, and it’s impossible to cover every detail in just one writing. Serving as a starter, I plan to lay out the nuances and applications of simulation through a sequence of posts.
In the real world, things happen randomly, and there is an infinite number of possibilities. We may only be able to observe only a fraction of, but not, the entire possibilities.
Simulation comes in handy and offers a quick overview of the distribution of the possibilities that match real-world outcomes. By studying simulated outcomes, we gain insights into the real world.
Let’s start with the easiest code and move up to more sophisticated scenarios.
The command runif(x, min=a, max=b) generates x number of values within the range between a and b.
library(dplyr)set.seed(2)runif(25,min=0,max=10)
The set.seed() function sets a seed for R’s random number generator, so that we get the same values consistently each time we run the code. Otherwise, we will get different results. Try yourself.
To get integers, we use the round function, as follows:
runif(25,min=0,max=10) %>%   round(.,digits = 0)
Here, the pipe, %>%, comes from magrittr package and allows you to write cleaner and easily understandable code. It can be translated into plain English as ‘then.’
In English, the above two lines of code mean:
generate 25 numbers within the range from 0 and 10then (%>%), round the number up to the nearest integer.
generate 25 numbers within the range from 0 and 10
then (%>%), round the number up to the nearest integer.
Try what happens if you tweak the digits = 1.
Another way of generating a random number is via the sample function:
> sample(x, size, replace=TRUE, prob=NULL)
x: a vector, or a positive integer
size: a non-negative integer giving the number of items to choose
replace: sample with replacement, or not?
prob: a vector of probability weights for obtaining the elements of the vector being sampled
(Please refer to R documentation for more information, link)
A quick example.
set.seed(24)sample(seq(1,10),8)
The set.seed() function makes the result reproducible.
The above code generates a random sample of 8 numbers from the sequence [1,10]. As you can see, we do not set rules for replacement and probability of selection.
By default, R sets replacement to FALSE and adopts equal probabilities of selection.
x = 1:10sample(x,replace=TRUE)
The process of resampling with replacement is called bootstrapping, a more advanced topic that will be covered in the next chapter.
Besides numbers, we can sample words, as follows:
set.seed(1)sample(letters,18)
We generate a random sample of size 10 from the sequence [1,5] with equal probabilities.
equal_prob_dist = sample(5,10000,prob=rep(0.1,5),replace=T)hist(equal_prob_dist)
If we set prob=rep(0.1,5), then numbers 1 to 5 will be equally sampled, as shown in the histogram above.
What does the distribution look like if the probabilities of selection are not equal (e.g., a loaded dice)?
We generate a random sample of size 10 from sequence[1,5] with unequal probabilities
unequal_prob_dist = sample(5,10000,prob =c(0.1,0.25,0.4,0.25,0.1), replace=T)hist(unequal_prob_dist)
We set the following selection probability rules for the sequence:
1 & 5: 0.12 & 4: 0.25 3: 0.4
As it turns out, number 3 is the most selected and 1 & 5 are the least selected.
These equal/unequal scenarios could be tremendously helpful if the question of interest needs differentiated treatments of selection probability.
To sample observations (rows) from a data frame or a list, we don’t sample the rows directly but the indices into an object.
head(mtcars)
# step 1: create an index vector for the elements/rows index <- seq_len(nrow(mtcars)) # step 2: sample the index vectorset.seed(12)#to obtain a random sample of 10sample_index <- sample(index,10)# step 3: to show the sampled elements/rows mtcars[sample_index,]
In this section, let’s get some practice and solve real-life business scenarios.
set.seed(1)die = 1:6# sample 10000 times with replacements for dice 1die1 = sample(die,10000,replace = TRUE,prob=NULL)# sample 10000 times with replacements for dice 2die2= sample(die,10000,replace=TRUE,prob = NULL)# the combined value of die_1 and die_2 outcomes = die1+die2# the probability of getting a 7mean(outcomes == 7)
Similarly, we can use a for loop for the same purpose.
set.seed(1)for (i in 10000){ die_1 = sample(die,prob=NULL,replace=TRUE) die_2 = sample(die,prob=NULL,replace=TRUE) die_sum = die_1+die_2 print(mean(die_sum==7))}
These two outcomes are almost the same.
Instead of simulating each outcome, there is an easier way of getting what we need using sapply.
sapply(2:13,function(x) mean(outcomes==x))
According to R documentation, sapply() function is a “user-friendly version and wrapper of lapply by default returning a vector, matrix or, if simplify = "array", an array if appropriate, by applying simplify2array(). sapply(x, f, simplify = FALSE, USE.NAMES = FALSE) is the same as lapply(x, f).”
Briefly, the function applies the function mean(outcomes) to the sequence [2,13].
Question for you: what is the probability of rolling a 7 if you have 3 dices?
set.seed(1)n=10000included_obs = length(unique(sample(1:n, replace = TRUE)))included_obsmissing_obs = n-included_obsmissing_obs
After sampling, we use unique() to find the distinctive observations and length() to count. In a 10k sample, 6316 unique cases are sampled and 3684 (10k — 6316) cases are not.
As a further note, the reason why we only get 6316 out of 10k is that we sample with replacements, and so some numbers are sampled repetitively. In other words, some numbers are sampled multiple times.
(credits to discussions on R-bloggers, here)
In the original post, the authors introduce multiple methods, and I focus on the first approaches here.
First, we can use a for loop.
m <- 10n <- 10# create an empty matrixm00 <- matrix(0,m,n)for (i in 1:m) {   for (j in 1:n) {     m00[i,j] <- sample(c(0,1),1) }}m00
Here, matrix(0,m,n) creates an empty matrix by m*n, and the two for loops dictate the values of each cell.
Most of the time, a for loop in R may not be the most efficient solution, and we shall use other options if the sample size is big enough.
With that in mind, the authors contribute the second method using apply() function.
m <-10n<-10m0 <- matrix(0,m,n)apply(m0,c(1,2),function(x) sample(c(0,1),1))
So far, everything should be quite familiar except the apply() function. It returns “a vector/array/list of values obtained by applying a function to margins of an array or matrix” (original definition, link).
It has the following form:
> apply(x, margin, fun,...) 
x: an array, or matrix
margin: a vector giving the subscripts in which the function will be applied over. For a matrix, 1 indicates rows, and 2 indicates columns. c(1,2) indicates rows and columns, where x has named dimnames, it can be a character vector selecting dimension names
fun: the function to be applied
# create an empty list total_heads = c()# use a for loop to simulate coin-flipping 10 times # repeat it for 10,000 timesfor (i in 1:10000){ sum_heads = sum(round(runif(10,0,1))) total_heads = c(total_heads, sum_heads) }hist(total_heads)
Here, I’ll explain three lines of code, and the rest is pretty self-explanatory.
for (1 in 1:1000). Unlike other programming languages, you have to specify the range as 1:10000.sum(round(runif(10,0,1))). This is a structured way of coding, which has three separate functionalities: runif() → round() → sum(). It runs the runif() function first and sum() last. Basically, the lines of code generate 10 random numbers between 0 and 1, round it up to the nearest integer (0 or 1), and lastly sum it up.total_heads = c(total_heads, sum_heads). Since total_heads is an empty list in the beginning, we use the line of code to assign the value of sum_heads to total_heads.
for (1 in 1:1000). Unlike other programming languages, you have to specify the range as 1:10000.
sum(round(runif(10,0,1))). This is a structured way of coding, which has three separate functionalities: runif() → round() → sum(). It runs the runif() function first and sum() last. Basically, the lines of code generate 10 random numbers between 0 and 1, round it up to the nearest integer (0 or 1), and lastly sum it up.
total_heads = c(total_heads, sum_heads). Since total_heads is an empty list in the beginning, we use the line of code to assign the value of sum_heads to total_heads.
Thank you for reading!
This is an intro post on statistical simulation, and we will explore more advanced topics in the next posts.
Stay tuned!
Medium recently evolved its Writer Partner Program, which supports ordinary writers like myself. If you are not a subscriber yet and sign up via the following link, I’ll receive a portion of the membership fees.
leihua-ye.medium.com
Please find me on LinkedIn and Youtube.
Also, check my other posts on Artificial Intelligence and Machine Learning.