AEM 6850

Empirical Methods for Applied Economists

Prof. Ariel Ortiz-Bobea

2 · R essentials I

Thursday, August 27, 2026

Cornell University

Objectives

By the end of this session you can:

  • Build vectors and do arithmetic on them.
  • Say what R does when a vector mixes classes, and why that turns a column of numbers into text.
  • Handle missing values instead of being surprised by them.
  • Subset a vector or a data frame with a logical condition.
  • Say what a factor is, and repair the trap it sets.

R is a language and a program

  • R is a language. A grammar for saying what to do with data.
  • R is also a program. The thing you installed, which reads that grammar and runs it.
  • RStudio is neither. It is an editor with an R console attached.
  • Delete RStudio and R still works. Delete R and nothing works.

The R logo

Objects and calls

Everything that exists is an object. Everything that happens is a function call.

John Chambers, one of R’s designers

x <- c(1, 2, 3)
f <- function(v) v * 2

class(f)        # a function is an object, exactly like a vector
#> [1] "function"
class(`+`)      # so is the plus sign
#> [1] "function"
`+`(x, 1)       # so x + 1 is a call to that object
#> [1] 2 3 4

What “object-oriented” means

Code organised around objects: values that carry their own class, where the class decides what a function does with them.

Polymorphism means that a developer can consider a function’s interface separately from its implementation, making it possible to use the same function form for different types of input.

Hadley Wickham, Advanced R

Plainly: one name, many behaviours. You write summary(x) without knowing what x is. R reads the class and runs the version built for it.

One name, many behaviours

summary(c(1, 2, 3, 100))
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#>    1.00    1.75    2.50   26.50   27.25  100.00
summary(c("a", "b", "a"))
#>    Length     Class      Mode 
#>         3 character character

Same call, two results. R read the class and picked the method. That is polymorphism, and it is why you learn summary(), print() and plot() once and they keep working on things you have not met yet.

R next to Stata and Python

Stata R Python
Built for data analysis data analysis general programming
Data in memory one active dataset; more via frames many named objects many named objects
Naming the data implicit: regress y x explicit: lm(y ~ x, data = d) explicit
Where tables come from the dataset is the workspace objects you create and name from libraries
Strongest at a clean panel to a published table statistical methods and graphics pipelines, APIs, ML, production code
Who uses it economists, social scientists statisticians, many fields programmers, CS, machine learning

Economics is a Stata field. That is measured, not folklore.

Objects

Everything in R is an object with a name you give it and a class R assigns.

x <- c(1, 2, 3, 4, 5)   # the arrow points from the value into the name
x = c(1, 2, 3, 4, 5)    # also assigns; you will meet it in other code
c(1, 2, 3, 4, 5) -> x   # legal, and points the other way

x
#> [1] 1 2 3 4 5
class(x)
#> [1] "numeric"
length(x)
#> [1] 5

Your environment

ls()            # everything you have named, right now
#> [1] "f" "x"
5 + 3           # this prints, and is gone
#> [1] 8
ls()            # nothing new: it was never given a name
#> [1] "f" "x"

Asking R for help

Every function ships with a help page. Both of these open it, from the console:

?seq             # the help page for seq()
help(seq)        # the same thing
??regression     # search the help system when you do not know the name

Vectors

A vector is an ordered sequence of values that are all the same class: all numbers, or all text, or all TRUE/FALSE. It is the basic unit of R: the single number 5 is a vector of length one.

1:10
#>  [1]  1  2  3  4  5  6  7  8  9 10
seq(0, 100, by = 25)
#> [1]   0  25  50  75 100
seq(0, 1, length.out = 5)
#> [1] 0.00 0.25 0.50 0.75 1.00
rep(c("a", "b"), times = 3)
#> [1] "a" "b" "a" "b" "a" "b"
rep(c("a", "b"), each = 3)
#> [1] "a" "a" "a" "b" "b" "b"

Vector arithmetic

Arithmetic applies to every element at once. You almost never write a loop for this:

x * 2
#> [1]  2  4  6  8 10
x + c(10, 20, 30, 40, 50)
#> [1] 11 22 33 44 55
x + c(0, 100)      # lengths do not match -- R recycles, and warns
#> [1]   1 102   3 104   5

Summaries

One number out of a whole vector.

sum(x)
#> [1] 15
mean(x)
#> [1] 3
min(x)
#> [1] 1
max(x)
#> [1] 5
range(x)         # both ends at once
#> [1] 1 5
round(mean(x), 2)   # trim the decimals before you report a number
#> [1] 3

Exercise 1

# One line, two commands: what is the mean of 0, 25, 50, 75, 100?
mean(seq(0, 100, by = 25))
#> [1] 50

Classes

class(1)
#> [1] "numeric"
class("a")
#> [1] "character"
class(TRUE)
#> [1] "logical"
class(as.Date("2025-01-07"))
#> [1] "Date"

Silent conversion

A vector can only hold one class. So when you mix classes, R does not stop or warn. It silently converts everything to a class that can hold them all.

c(1, 2, 3)
#> [1] 1 2 3
c(1, 2, "three")          # one text value converts the whole vector
#> [1] "1"     "2"     "three"
class(c(1, 2, "three"))
#> [1] "character"

TRUE is 1

sum(c(TRUE, FALSE, TRUE))   # TRUE becomes 1, FALSE becomes 0
#> [1] 2
mean(c(TRUE, FALSE, TRUE))  # so the mean of a logical is a proportion
#> [1] 0.6666667

Text sorts like text

sort(c(10, 9, 100))
#> [1]   9  10 100
sort(c("10", "9", "100"))   # character by character
#> [1] "10"  "100" "9"

What it costs you

The convenience has a cost. Here is what one stray text entry does to a column of numbers:

readings <- c(12.4, 18.1, 9.7, "n/a", 22.3)
class(readings)
#> [1] "character"
mean(readings)
#> [1] NA

Repair it, then check the repair:

readings <- as.numeric(readings)   # "n/a" cannot convert -- it becomes NA
readings
#> [1] 12.4 18.1  9.7   NA 22.3
mean(readings, na.rm = TRUE)
#> [1] 15.625

NA: not available

NA stands for not available. It is not zero, and it is not the text "NA". It marks a value R does not have, and R enforces that: any computation touching an unknown returns an unknown.

temps <- c(21.0, NA, 19.4, 23.8)
mean(temps)
#> [1] NA
mean(temps, na.rm = TRUE)
#> [1] 21.4
is.na(temps)
#> [1] FALSE  TRUE FALSE FALSE
sum(is.na(temps))
#> [1] 1
temps == NA        # == tests for equality; this is not how you test for NA
#> [1] NA NA NA NA

Indexing

Square brackets pull elements out by position.

x <- c(10, 20, 30, 40, 50)
x[1]
#> [1] 10
x[c(1, 3)]
#> [1] 10 30
x[-1]              # everything except the first
#> [1] 20 30 40 50
x[length(x)]       # the last, however long it is
#> [1] 50

Logical subsetting

Get comfortable with this one: every filter you write for the rest of the semester works this way.

x > 25             # a TRUE/FALSE for every element
#> [1] FALSE FALSE  TRUE  TRUE  TRUE
x[x > 25]          # keep the elements where it is TRUE
#> [1] 30 40 50
sum(x > 25)        # count them -- TRUE is 1
#> [1] 3
x[x > 25 & x < 45] # & is and, | is or
#> [1] 30 40

Exercise 2

# w <- c(12, 45, 7, 33, 88, 21)
#
# One line each: how many values are above 20?
#                what is the mean of just those?
w <- c(12, 45, 7, 33, 88, 21)
sum(w > 20)
#> [1] 4
mean(w[w > 20])
#> [1] 46.75

Data frames

A data frame is a set of equal-length vectors standing side by side: columns of possibly different classes, rows that line up. Every dataset in this course is one.

d <- data.frame(
  site = c("Compton", "Compton", "Reseda"),
  date = c("2025-01-01", "2025-01-02", "2025-01-01"),
  pm25 = c(53.2, 33.6, 47.0)
)
d
#>      site       date pm25
#> 1 Compton 2025-01-01 53.2
#> 2 Compton 2025-01-02 33.6
#> 3  Reseda 2025-01-01 47.0
str(d)
#> 'data.frame':    3 obs. of  3 variables:
#>  $ site: chr  "Compton" "Compton" "Reseda"
#>  $ date: chr  "2025-01-01" "2025-01-02" "2025-01-01"
#>  $ pm25: num  53.2 33.6 47

Stacking two frames

e <- data.frame(site = "Reseda", date = "2025-01-02", pm25 = 41.5)

identical(names(d), names(e))   # same columns, same order?
#> [1] TRUE
both <- rbind(d, e)             # stack the rows
nrow(both)
#> [1] 4

Rows and columns

Subsetting takes two coordinates: d[rows, columns]. Leave one blank to mean “all of them.”

d$pm25            # one column, by name
#> [1] 53.2 33.6 47.0
d[1, ]            # first row, all columns
#>      site       date pm25
#> 1 Compton 2025-01-01 53.2
d[, "pm25"]       # all rows, one column
#> [1] 53.2 33.6 47.0
d[d$pm25 > 40, ]  # the rows where a condition is TRUE
#>      site       date pm25
#> 1 Compton 2025-01-01 53.2
#> 3  Reseda 2025-01-01 47.0

Sorting by position

sort() returns values. order() returns the positions that would put a vector in order.

v <- c(10, 50, 30)
order(v)              # positions, not values
#> [1] 1 3 2
v[order(v)]           # which is what sort() does
#> [1] 10 30 50
d[order(d$pm25), ]    # so this sorts the whole frame by one column
#>      site       date pm25
#> 2 Compton 2025-01-02 33.6
#> 3  Reseda 2025-01-01 47.0
#> 1 Compton 2025-01-01 53.2

Factors

A factor is how R stores a categorical column: the values live as numeric codes, with a lookup table of labels called levels.

site <- factor(c("Compton", "Reseda", "Compton", "Compton"))
site
#> [1] Compton Reseda  Compton Compton
#> Levels: Compton Reseda
levels(site)
#> [1] "Compton" "Reseda"
table(site)
#> site
#> Compton  Reseda 
#>       3       1

The factor trap

Remember "10" sorting before "9"? Factors do the same thing, and it is harder to see.

f <- factor(c("10", "9", "100"))   # numbers, stored as categories
as.numeric(f)                      # NOT the numbers -- the level codes
#> [1] 1 3 2
as.numeric(as.character(f))        # the repair: text first, then number
#> [1]  10   9 100

Practice

Eighteen more on the session page, with solutions behind a click. One or two lines each, in the order we covered them. Work through them this week.

Quick reference

The session page has every command from today in one table, plus the four quick checks and the format strings for dates.