Code
sudo dnf install 'dnf-command(copr)'
sudo dnf copr enable iucar/RStudio
sudo dnf install RStudio-desktopThis chapter asks three questions.
<- stores a value under a name, so later lines can use it.TRUE or FALSE for each entry.The pace is gentle on purpose so that the rest of the book can move quickly.
You will program your statistical analysis in R. My main sell to you is that “R programming” is in your own self-interest, not mine.
Next quarter’s sales report, or the same table with one more year of data, is the same analysis run again. Done by hand, it costs another afternoon. If it is written as a script, you simply swap the data file and rerun it. The same code that summarizes 100 observations summarizes one million. Asking an AI assistant to simply do the analysis may get you different results when you simply want to repeat an analysis. Instead, you can use AI to help you generate and check your reproducible scripts.
Most of this book is spent on methods that don’t fit into a cell formula. We build confidence intervals by resampling the data thousands of times. We test hypotheses by simulating what the data would look like if there were no effect. We also follow good visualization conventions, such as showing 2D data without unnecessary 3D effects. See Tufte (2001) and https://clauswilke.com/dataviz/no-3d.html.
Every calculation is recorded, rather than hidden in a sequence of clicks that nobody wrote down. Anyone who reads the script, including you, can then find the exact step where a number went wrong. A spreadsheet can display a plausible number even when the calculation behind it is wrong.
None of these were hard problems. They were ordinary mistakes that went undetected. Recording and rerunning the steps makes mistakes easier to find. Published research often comes with a replication package of data and scripts, so that others can rerun the analysis (Peng 2011). That makes the research reproducible: the same data and code give the same numbers. Research is replicable if a new study with new data reaches the same conclusion (Peng 2011). See https://retractionwatch.com/ and https://econjwatch.org/ for more examples of what happens when academics don’t check their work. Search for the “worst Excel errors” and note the frequency they arise from copy/paste via the “point-and-click” approach.
The underlying skill is turning a vague question, such as “are our west-coast customers different?”, into a sequence of explicit and checkable steps. That skill carries over to any job where somebody has to defend a number.
We program with R because it is particularly good for both introductory statistics and later use in academia and industry. It is free on any machine, and it stays free after you graduate and after you leave whatever employer paid for your other software.
We will work in R through a second piece of software, RStudio. R performs the analysis while RStudio provides the workspace for writing code, inspecting data, and viewing results. RStudio is a popular choice in academia and business. R can also do more than analysis. For example, R Shiny builds interactive web apps (Chang et al. 2026).
Excel is useful for data entry and a quick inspection, while R is stronger for work that must be updated, checked, explained, or scaled. Think about what your future employers want and do some of your own research to best understand how much to invest.
Install in two steps
For Fedora (linux) users, note that you need to first enable the repo and then install
sudo dnf install 'dnf-command(copr)'
sudo dnf copr enable iucar/RStudio
sudo dnf install RStudio-desktopMake sure you have the latest version of R and RStudio for class. If not, then reinstall.
RStudio is perhaps the easiest to get going with. (There are other GUI’s.)
In RStudio, there are 4 panes. If you do not see 4, click “file > new file > R script” on the top left of the toolbar.
The top left pane is where you write your code. For example, type
1+1The pane below is where your code is executed. Keep your mouse on the same line as your code, and then click “Run”. You should see
> 1+1
[1] 2
If you click “Run” again, you should see that same output printed again.
As we proceed, you can see both my source code and output like this:
1 + 1
## [1] 2You can use + to add any numbers. Try \(2+2\) and \(3+3\). First execute each one-at-a-time. Then highlight/run them both together.
Throughout this textbook, there are special boxes for especially important statistical examples.
To understand each chunk of code:
E.g., Use the following code to see if the empty space matters
(2 + 7)^3 / 10
## [1] 72.9You can create “variables” that store values. For example,
## MAIN EXAMPLE
x <- 1 # <- stores the value 1 in a variable named x
x + 1 # R replaces x with its stored value
## [1] 2x <- 23 #Another example
x + 1
## [1] 24y <- x + 1 #Another example
y
## [1] 24Also note that your variable names do not matter technically, but they should be informative to help avoid common mistakes.
x_plus_1 <- x + 1 #less confusing
y_times_2 <- x + 1 #more confusingYou will make many mistakes. Here are some of the most common ones to look out for.
# Mistake 1: spelling and spacing
X + 1 # notice that R is sensitive to capitalization (x is not X)
## Error:
## ! object 'X' not found
Y < - 43 # notice that < - is not <-
## Error:
## ! object 'Y' not found
Y + 1 # notice that Y was not actually created above, even if you thought it was
## Error:
## ! object 'Y' not found
# Mistake 2: half-completed code
x + y +
x_plus_y_plus_z <- x + y + z +
x
## Error:
## ! object 'z' not found
# Seeing '+' in the bottom console? press 'Escape' and try againAsk your peers or AI to check your code and error messages if you cannot figure out the issue.
Note that there are often many ways to accomplish the same goal
x + 2 + 3
## [1] 28
sum(x,2,3) # sum() is a built-in function that adds its inputs
## [1] 28You can always ask R for help
?sum # put ? before a function name to open its help pageDesktop/ECON_XXXX/CodeAs you work through the material, make sure to both execute and save your scripts.
Add lots of commentary to your scripts. Name your scripts systematically.
R already comes with many datasets, such as USArrests. They live in a package called datasets, which R loads every time it starts. A package is a bundle of functions and datasets that someone has written and shared, so you can use them without writing them yourself.
Many more datasets live in packages that are not part of R when you first install it. You install such a package once, and then load its data in each script that uses it. This book uses two such packages, Ecdat and wooldridge, which both contain many economics datasets.
install.packages('Ecdat') # only once, typed in the console
install.packages('wooldridge')It is best to keep install.packages() out of your script, so that re-running the script does not reinstall the package every time.
If the text in RStudio is hard to read, click “View > Zoom In” on the top toolbar. Each click makes the text in every pane larger. Click “View > Zoom Out” to make it smaller again, or “View > Actual Size” to return to the default. The menu also lists a keyboard shortcut next to each option.
Before you can analyze data in R, you need to know what shapes the data can take.
Each shape suits a different kind of dataset. A scalar holds a single number, such as your total income for the year. A vector holds one variable across observations, such as the income of each person in class. A matrix holds two or more variables across observations, such as the income and education level of every person in class. Vectors are probably your most common object in R, but we will start with scalars.
Make your first scalar
## MAIN EXAMPLE
x <- 2 # a single number is a scalar
x # typing an object's name prints its value
## [1] 2Perform simple calculations and see how R is doing the math for you
x + 2
## [1] 4
x*2 # Perform and print a simple calculation
## [1] 4
(x+1)^2 # Perform and print a simple calculation
## [1] 9
x + NA # often used for missing values
## [1] NANow change x, predict what will happen, then re-run the code.
Make your first vector. The name x_hat will identify sample values throughout the book.
## MAIN EXAMPLE
x_hat <- c(0, 1, 3, 10, 6) # c() combines values into a vector
x_hat
## [1] 0 1 3 10 6
x_hat[2] # square brackets extract the 2nd element
## [1] 1
# Arithmetic applies to each entry of x_hat
x_hat + 2
## [1] 2 3 5 12 8
x_hat*2
## [1] 0 2 6 20 12
x_hat^2
## [1] 0 1 9 100 36Apply mathematical calculations elementwise
x_hat+x_hat
## [1] 0 2 6 20 12
x_hat*x_hat
## [1] 0 1 9 100 36
x_hat^x_hat
## [1] 1.0000e+00 1.0000e+00 2.7000e+01 1.0000e+10 4.6656e+04In R, scalars are treated as a vector with one element.
c(1)
## [1] 1Matrices are also common objects
## MAIN EXAMPLE
# Two vectors of the same length
x1_hat <- c(1, 4, 9)
x2_hat <- c(3, 0, 2)
# Stack them as rows (cbind() would stack them as columns)
x_mat <- rbind(x1_hat, x2_hat)
x_mat
## [,1] [,2] [,3]
## x1_hat 1 4 9
## x2_hat 3 0 2
x_mat[2, ] # row 2: leave the column slot empty
## [1] 3 0 2
x_mat[ , 2] # column 2: leave the row slot empty
## x1_hat x2_hat
## 4 0
x_mat[2, 2] # one entry: give both row and column
## x2_hat
## 0There are elementwise calculations
x_mat+2
## [,1] [,2] [,3]
## x1_hat 3 6 11
## x2_hat 5 2 4
x_mat*2
## [,1] [,2] [,3]
## x1_hat 2 8 18
## x2_hat 6 0 4
x_mat^2
## [,1] [,2] [,3]
## x1_hat 1 16 81
## x2_hat 9 0 4
x_mat + x_mat
## [,1] [,2] [,3]
## x1_hat 2 8 18
## x2_hat 6 0 4
x_mat * x_mat
## [,1] [,2] [,3]
## x1_hat 1 16 81
## x2_hat 9 0 4A function is a named recipe that takes input objects, performs a calculation, and returns an output.
Perhaps the most common function we will use is summation. You can see exactly what a function does with ?.
## MAIN EXAMPLE
x_hat
## [1] 0 1 3 10 6
sum(x_hat) # adds the entries of a vector
## [1] 20
# ?sum # delete the first # to open the help page
mean(x_hat)
## [1] 4
max(x_hat)
## [1] 10Summing an indicator counts how many observations satisfy the condition. For example, \(\sum_{i=1}^{n}\mathbf{1}\left(\hat{x}_{i}=x\right)\) counts the number of observations equal to \(x\). In R, 1*(x_hat == 3) computes \(\mathbf{1}(\hat{x}_{i}=3)\) for each entry: the comparison gives TRUE or FALSE, and multiplying by 1 turns these into \(1\) and \(0\).
1*(x_hat == 3) # 1 if the entry equals 3, 0 otherwise
## [1] 0 0 1 0 0
sum(x_hat == 3) # how many entries equal 3
## [1] 1
mean(x_hat == 3) # proportion of entries that equal 3
## [1] 0.2Sometimes, we will use vectors that are entirely ordered.
x_hat
## [1] 0 1 3 10 6
order(x_hat)
## [1] 1 2 3 5 4
x_hat[order(x_hat)]
## [1] 0 1 3 6 10
sort(x_hat)
## [1] 0 1 3 6 10We can also make ordered vectors with functions.
seq(1, 7, by=1) #same as 1:7
## [1] 1 2 3 4 5 6 7
seq(1, 7, by=0.5)
## [1] 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0 6.5 7.0Sometimes the same calculation must be performed on many inputs, with each result depending on the previous one or stored alongside the others.
Loops are useful when each iteration depends on the previous one (recursion) or when the order of operations matters. For purely elementwise calculations, vectorized arithmetic on a single vector is usually faster and easier to read.
Applying the same function over and over again
## MAIN EXAMPLE
# Example 1: simple division
x_hat <- rep(NA, 3) # pre-allocate NAs to hold the results
x_hat
## [1] NA NA NA
for(i in seq(1, 3)){ # i takes the values 1, 2, 3 in turn
x_hat[i] <- i/2 # fill the i-th entry on each pass
}
x_hat # now filled in
## [1] 0.5 1.0 1.5
# Example 2: using existing data
x_hat <- c(1, 3, 9, 2)
y_hat <- rep(NA, length(x_hat))
for(i in seq(1, 4) ){
y_hat[i] <- x_hat[i] + 1
}
y_hat
## [1] 2 4 10 3
#Example 3: recursion
x_hat <- rep(NA, 4)
x_hat[1] <- 1
for(i in seq(2, 4) ){
x_hat[i] <- x_hat[i-1]^2
}
x_hat
## [1] 1 1 1 1Functions are useful for any calculation you intend to reuse: define the recipe once, and a single edit to the recipe propagates to every call. Naming the calculation also makes scripts easier to read, since a well-chosen function name documents what the code is doing.
## MAIN EXAMPLE
add_two <- function(input) { # input is a placeholder name
output <- input + 2 # output exists only inside the function
return(output) # hand the result back to the caller
}
x_hat <- c(0, 1, 3, 10, 6)
add_two(input=x_hat) # add_two(x_hat) works the same
## [1] 2 3 5 12 8Common mistakes:
print(input)
print(output)
# These are not available globally, only locally (inside of the function)
# Double check typos
y_hat < - add_two(Input=X_hat)
# Seeing '+' in the bottom console
# often means you forgot to close the function with '}'
# click the bottom left panel, press 'Escape', and try again in the top left panel
add_two <- function(input_vector) {
output_vector <- input_vector + 2
return(output_vector)
x_hat <- c(0, 1, 3, 10, 6)
add_two(x_hat)There are many different functions. Many of these functions have defaults.
add_scalar <- function(input_vector1, input_scalar2) {
output_vector <- input_vector1 + input_scalar2
return(output_vector)
}
add_scalar(x_hat, 3)
## [1] 3 4 6 13 9
add_scalar(x_hat, 4)
## [1] 4 5 7 14 10
add_scalar3 <- function(input_vector1, input_scalar2=3) {
output_vector <- input_vector1 + input_scalar2
return(output_vector)
}
add_scalar3(x_hat)
## [1] 3 4 6 13 9
add_scalar3(x_hat, 4)
## [1] 4 5 7 14 10Calculations that are either TRUE or FALSE
## MAIN EXAMPLE
x_hat <- c(1, 2, 3, NA) # NA marks a missing value
x_hat == 2 # TRUE or FALSE for each entry
## [1] FALSE TRUE FALSE NA
any(x_hat==2) # TRUE if at least one entry is TRUE
## [1] TRUE
all(x_hat==2) # TRUE only if every entry is TRUE
## [1] FALSE
2 %in% x_hat # does 2 appear anywhere in x_hat?
## [1] TRUE
is.na(x_hat) # flags the missing entries
## [1] FALSE FALSE FALSE TRUEThe & and | commands are logical calculations that compare vectors to the left and right.
x_hat <- c(1, 2, 3, NA)
x_hat < 2
## [1] TRUE FALSE FALSE NA
x_hat >= 1
## [1] TRUE TRUE TRUE NA
(x_hat >= 1) & (x_hat < 2)
## [1] TRUE FALSE FALSE NA
(x_hat >= 1) | (x_hat < 2)
## [1] TRUE TRUE TRUE NAYou can apply functions to all matrix elements
x1_hat <- c(1, 4, 10)
x2_hat <- c(3, 0, 2)
x_mat <- rbind(x1_hat, x2_hat)
x_mat
## [,1] [,2] [,3]
## x1_hat 1 4 10
## x2_hat 3 0 2
sum(x_mat) # adds every entry, not just one row or column
## [1] 20You can apply functions to each row or column of a matrix
# Row sums
y_row <- apply(x_mat, 1, sum) # the 1 applies sum() to each row
y_row
## x1_hat x2_hat
## 15 5
#check row sums are correct
sum(x_mat[1, ])
## [1] 15
sum(x_mat[2, ])
## [1] 5# Column sums
y_col <- apply(x_mat, 2, sum)
y_col
## [1] 4 4 12
#check column sums are correct: DIYAn AI assistant is a normal part of writing code now, and this book suggests one order for using one: you attempt the task first, and the assistant then evaluates what you produced. Working in that order keeps you able to tell whether the output is right. Most of this book teaches you how to tell. Exercise 1 of every chapter, including this one, walks a script you already wrote through an eight-step review.
The full workflow, along with the prompts, the notes on what to upload, and a worked example of catching an assistant in a confident mistake, is in Working with AI.
Comment the script you wrote for this chapter, then restart R and check that it runs from a clean session, then check the script with AI as explained in Working with AI. Write three sentences from memory on the main statistical idea of this chapter, and ask the assistant what is wrong, vague, or missing. Finish with your own questions about whatever you found hardest.
Explain the difference between replicable and reproducible research. Why is programming your analysis in R better for reproducibility than a point-and-click approach?
Let x_hat <- c(2, 5, 8, 3, 7). Without running the code, predict the output of sum(x_hat) / length(x_hat), then verify in R. Next, use sort(x_hat) and extract the second element of the sorted vector with bracket indexing.
Write a function called square_plus that takes a vector and a scalar as inputs, squares each element of the vector, and then adds the scalar. Test it on x_hat <- c(1, 4, 9) with a scalar of 5. Confirm your result by computing the answer manually.
This chapter introduced the R environment and the three basic objects, scalars, vectors, and matrices. You manipulate these objects with functions and loops. The p_hat loop in the Must Know box computes the proportion of x_hat <- c(0, 1, 3, 10, 6) equal to each \(x~\text{in}~\{1,2,3\}\). You install R and RStudio, write your code in a script with comments, and save it so you can rerun and check it. You install a package such as Ecdat once, and then load its data in each script that uses it.
Comments
You should add comments to your codes, and you do this with hashtags. For example
Code
The comments in this chapter say what each line does, because the syntax is new to you. In your own scripts, comments are most useful when they say why a line is there; see Code Conventions for examples.
Now try some other mathematical operations
Code
Now try some more complex examples
Code