02 Econometrics Advanced Notes With R
02 Econometrics Advanced Notes With R
Syllabus 5
1 Introduction to R 9
1.1 Getting Started . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.2 Starting R and RStudio . . . . . . . . . . . . . . . . . . . . . . . 10
1.3 Basic Calculations . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.4 Getting Help . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.5 Installing Packages . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.6 Code vs Output in this Book . . . . . . . . . . . . . . . . . . . . 14
1.7 ScPoEconometrics Package . . . . . . . . . . . . . . . . . . . . . 15
1.8 Data Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.9 Data Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.10 Data Frames . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
1.11 Programming Basics . . . . . . . . . . . . . . . . . . . . . . . . . 35
3 Linear Regression 67
3.1 How are x and y related? . . . . . . . . . . . . . . . . . . . . . . 67
3.2 Ordinary Least Squares (OLS) Estimator . . . . . . . . . . . . . 73
3.3 Predictions and Residuals . . . . . . . . . . . . . . . . . . . . . . 77
3.4 Correlation, Covariance and Linearity . . . . . . . . . . . . . . . 78
3.5 Analysing V ar(y) . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
3.6 Assessing the Goodness of Fit . . . . . . . . . . . . . . . . . . . . 82
3.7 An Example: A Log Wage Equation . . . . . . . . . . . . . . . . 82
3.8 Scaling Regressions . . . . . . . . . . . . . . . . . . . . . . . . . . 86
3.9 A Particular Rescaling: The log Transform . . . . . . . . . . . . 88
4 Multiple Regression 93
4.1 All Else Equal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
3
4 CONTENTS
4.2 Multicolinearity . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
4.3 Log Wage Equation . . . . . . . . . . . . . . . . . . . . . . . . . 97
4.4 How To Make Predictions . . . . . . . . . . . . . . . . . . . . . . 100
8 Projects 143
8.1 Trade Exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
Syllabus
Objective
This course aims to teach you the basics of data analysis needed in a Social
Sciences oriented University like SciencesPo. We purposefully start at a level
that assumes no prior knowledge about statistics whatsoever. Our objective is
to have you understand and be able to interpret linear regression analysis. We
will not rely on maths and statistics, but practical learning in order to teach
the main concepts.
5
6 CONTENTS
You can find the topics we want to go over in the left panel of this page. The
later chapters are optional and depend on the speed with which we will proceed
eventually. Chapters 1-4 are the core material of the course.
The only requirement is that you bring your own personal computer to
each session. We will be using the free statistical computing language R very
intensively. Before coming to the first session, please install R and RStudio as
explained at the beginning of chapter 1.
Course Structure
Groups meet once per week for 2 hours. The main purpose of the weekly meet-
ings is to clarify any questions, and to work together through tutorials. The
little theory we need will be covered in this book, and you are expected to
read through this in your own time before coming to class.
What you are looking at is an online textbook. You can therefore look at it in
your browser (as you are doing just now), on your mobile phone or tablet, but
you can also download it as a pdf file or as an epub file for your ebook-reader.
We don’t have any ambition to actually produce and publish a book for now, so
you should just see this as a way to disseminate our lecture notes to you. The
second part of course material next to the book is an extensive suite of tutorials
and interactive demonstrations, which are all contained in the R package that
builds this book (and which you installed by issuing the above commands).
Open Source
The book and all other content for this course are hosted under an open source
license on github. You can contribute to the book by just clicking on the appro-
priate edit symbol in the top bar of this page. Other teachers who want to use
our material can freely do so, observing the terms of the license on the github
repository.
CONTENTS 7
Assessments
Communication
We will communicate exclusively on our slack group. You will get an invitation
email to join from your instructor in due course.
8 CONTENTS
Chapter 1
Introduction to R
The popularity of R is on the rise, and everyday it becomes a better tool for
statistical analysis. It even generated this book!
The following few chapters will serve as a whirlwind introduction to R. They are
by no means meant to be a complete reference for the R language, but simply an
introduction to the basics that we will need along the way. Several of the more
important topics will be re-stressed as they are actually needed for analyses.
9
10 CHAPTER 1. INTRODUCTION TO R
A key difference for you to understand is the one between R, the actual program-
ming language, and RStudio, a popular interface to R which allows you to work
efficiently and with greater ease with R.
The best way to appreciate the value of RStudio is to start using R without
RStudio. To do this, double-click on the R GUI that you should have down-
loaded on your computer following the steps above (on windows or Mac), or
start R in your terminal (on Linux or Mac) by just typing R in a terminal, see
figure ??. You’ve just opened the R console which allows you to start typing
code right after the > sign, called prompt. Try typing 2 + 2 or print("Your
Name") and hit the return key. And voilà, your first R commands!
Typing one command after the other into the console is not very convenient
as our analysis becomes more involved. Ideally, we would like to collect all
1.3. BASIC CALCULATIONS 11
command statements in a file and run them one after the other, automatically.
We can do this by writing so-called script files or just scripts, i.e. simple text
files with extension .R or .r which can be inserted (or sourced) into an R session.
RStudio makes this process very easy.
Open RStudio by clicking on the RStudio application on your computer, and
notice how different the whole environment is from the basic R console – in fact,
that very same R console is running in your bottom left panel. The upper-left
panel is a space for you to write scripts – that is to say many lines of codes
which you can run when you choose to. To run a single line of code, simply
highlight it and hit Command + Return.
RStudio has a large number of useful keyboard shortcuts. A list of these can be
found using a keyboard shortcut – the keyboard shortcut to rule them all:
• On Windows: Alt + Shift + K
• On Mac: Option + Shift + K
The RStudio team has developed a number of “cheatsheets” for working with
both R and RStudio. This particular cheatseet for Base R will summarize many
of the concepts in this document. 1
To get started, we’ll use R like a simple calculator. Run the following code either
directly from your RStudio console, or in RStudio by writting them in a script
and running them using Command + Return.
1 When programming, it is often a good practice to follow a style guide. (Where do spaces
go? Tabs or spaces? Underscores or CamelCase when naming variables?) No style guide is
“correct” but it helps to be aware of what others do. The more import thing is to be consistent
within your own code. Here are two guides: Hadley Wickham Style Guide, and the Google
Style Guide. For this course, our main deviation from these two guides is the use of = in place
of <-. For all practical purposes, you should think = whenever you see <-.
12 CHAPTER 1. INTRODUCTION TO R
Exponents
Mathematical Constants
Logarithms
Note that we will use ln and log interchangeably to mean the natural logarithm.
There is no ln() in R, instead it uses log() to mean the natural logarithm.
Trigonometry
Frequently one of the most difficult things to do when learning R is asking for
help. First, you need to decide to ask for help, then you need to know how
to ask for help. Your very first line of defense should be to Google your error
message or a short description of your issue. (The ability to solve problems
using this method is quickly becoming an extremely valuable skill.) If that fails,
and it eventually will, you should ask for help. There are a number of things
you should include when contacting an instructor, or posting to a help website
such as Stack Overflow.
• Describe what you expect the code to do.
• State the end goal you are trying to achieve. (Sometimes what you expect
the code to do, is not what you want to actually do.)
• Provide the full text of any errors you have received.
• Provide enough code to recreate the error. Often for the purpose of this
course, you could simply post your entire .R script or .Rmd to slack.
• Sometimes it is also helpful to include a screenshot of your entire RStudio
window when the error occurs.
If you follow these steps, you will get your issue resolved much quicker, and
possibly learn more in the process. Do not be discouraged by running into
errors and difficulties when learning R. (Or any other technical skill.) It is
simply part of the learning process.
Once a package is installed, it must be loaded into your current R session before
being used. Think of this as taking the book off of the shelf and opening it up
to read.
library(ggplot2)
Once you close R, all the packages are closed and put back on the imaginary
shelf. The next time you open R, you do not have to install the package again,
but you do have to load any packages you intend to use by invoking library().
A quick note on styling choices in this book. We had to make a decision how to
visually separate R code and resulting output in this book. We decided to prefix
all output lines with #OUT> to make the distinction. A typical code snippet with
output is thus going to look like this:
1 + 3
#OUT> [1] 4
# everything after a # is a comment, i.e. R disregards it.
where you see on the first line the R code, and on the second line the output.
As mentioned, that line starts with #OUT> to say this is an output, followed by
[1] (indicating this is a vector of length one - more on this below!), followed by
the actual result - 1 + 3 = 4!
Notice that you can simply copy and paste all the code you see into your R
console. In fact, you are strongly encouraged to actually do this and try out all
the code you see in this book.
Finally, please note that this way of showing output is fully our choice in this
textbook, and that you should expect other output formats elsewhere. For
example, in my RStudio console, the above code and output looks like this:
> 1 + 3
[1] 4
1.7. SCPOECONOMETRICS PACKAGE 15
To fully take advantage of our course, please install the associated R package
directly from its online code repository. You can do this by copy and pasting
the following three lines into your R console:
if (!require("devtools")) [Link]("devtools")
library(devtools)
install_github(repo = "ScPoEcon/ScPoEconometrics")
In order to check whether everything works fine, you could load the library, and
check it’s current version:
library(ScPoEconometrics)
packageVersion("ScPoEconometrics")
R has a number of basic data types. While R is not a strongly typed language
(i.e. you can be agnostic about types most of the times), it is useful to know
what data types are available to you:
• Numeric
– Also known as Double. The default type when dealing with numbers.
– Examples: 1, 1.0, 42.5
• Integer
– Examples: 1L, 2L, 42L
• Complex
– Example: 4 + 2i
• Logical
– Two possible values: TRUE and FALSE
– You can also use T and F, but this is not recommended.
– NA is also considered logical.
• Character
– Examples: "a", "Statistics", "1 plus 2."
• Categorical or factor
– A mixture of integer and character. A factor variable assigns a label
to a numeric value.
– For example factor(x=c(0,1),labels=c("male","female"))
assigns the string male to the numeric values 0, and the string
female to the value 1.
16 CHAPTER 1. INTRODUCTION TO R
1.9.1 Vectors
#OUT> [1] 1 3 5 7 8 9
Here R simply outputs this vector. If we would like to store this vector in
a variable we can do so with the assignment operator =. In this case the
variable x now holds the vector we just created, and we can access the vector
by typing x.
x = c(1, 3, 5, 7, 8, 9)
x
#OUT> [1] 1 3 5 7 8 9
As an aside, there is a long history of the assignment operator in R, partially
due to the keys available on the keyboards of the creators of the S language.
(Which preceded R.) For simplicity we will use =, but know that often you will
see <- as the assignment operator.
Because vectors must contain elements that are all the same type, R will au-
tomatically coerce (i.e. convert) to a single type when attempting to create a
vector that combines multiple types.
1.9. DATA STRUCTURES 17
#OUT> [1] 42 1
Frequently you may wish to create a vector based on a sequence of numbers.
The quickest and easiest way to do this is with the : operator, which creates a
sequence of integers between two specified integers.
(y = 1:100)
#OUT> [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
#OUT> [18] 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
#OUT> [35] 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
#OUT> [52] 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
#OUT> [69] 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
#OUT> [86] 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
Here we see R labeling the rows after the first since this is a large vector. Also,
we see that by putting parentheses around the assignment, R both stores the
vector in a variable called y and automatically outputs y to the console.
Note that scalars do not exists in R. They are simply vectors of length 1.
2
#OUT> [1] 2
If we want to create a sequence that isn’t limited to integers and increasing by
1 at a time, we can use the seq() function.
seq(from = 1.5, to = 4.2, by = 0.1)
#OUT> [1] 1.5 1.6 1.7 1.8 1.9 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 3.0 3.1
#OUT> [18] 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9 4.0 4.1 4.2
We will discuss functions in detail later, but note here that the input labels
from, to, and by are optional.
seq(1.5, 4.2, 0.1)
#OUT> [1] 1.5 1.6 1.7 1.8 1.9 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 3.0 3.1
#OUT> [18] 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9 4.0 4.1 4.2
Another common operation to create a vector is rep(), which can repeat a
single value a number of times.
rep("A", times = 10)
#OUT> [1] "A" "A" "A" "A" "A" "A" "A" "A" "A" "A"
18 CHAPTER 1. INTRODUCTION TO R
The rep() function can be used to repeat a vector some number of times.
rep(x, times = 3)
#OUT> [1] 1 3 5 7 8 9 1 3 5 7 8 9 1 3 5 7 8 9
We have now seen four different ways to create vectors:
• c()
• :
• seq()
• rep()
So far we have mostly used them in isolation, but they are often used together.
c(x, rep(seq(1, 9, 2), 3), c(1, 2, 3), 42, 2:4)
#OUT> [1] 1 3 5 7 8 9 1 3 5 7 9 1 3 5 7 9 1 3 5 7 9 1 2
#OUT> [24] 3 42 2 3 4
The length of a vector can be obtained with the length() function.
length(x)
#OUT> [1] 6
length(y)
[Link] Task 1
[Link] Subsetting
To subset a vector, i.e. to choose only some elements of it, we use square brackets,
[]. Here we see that x[1] returns the first element, and x[3] returns the third
element:
1.9. DATA STRUCTURES 19
#OUT> [1] 1 3 5 7 8 9
x[1]
#OUT> [1] 1
x[3]
#OUT> [1] 5
We can also exclude certain indexes, in this case the second element.
x[-2]
#OUT> [1] 1 5 7 8 9
Lastly we see that we can subset based on a vector of indices.
x[1:3]
#OUT> [1] 1 3 5
x[c(1,3,4)]
#OUT> [1] 1 5 7
All of the above are subsetting a vector using a vector of indexes. (Remember a
single number is still a vector.) We could instead use a vector of logical values.
z = c(TRUE, TRUE, FALSE, TRUE, TRUE, FALSE)
z
#OUT> [1] 1 3 7 8
R is able to perform many operations on vectors and scalars alike:
x = 1:10 # a vector
x + 1 # add a scalar
#OUT> [1] 2 3 4 5 6 7 8 9 10 11
2 * x # multiply all elements by 2
#OUT> [1] 2 4 6 8 10 12 14 16 18 20
2 ^ x # take 2 to the x as exponents
#OUT> [1] 3 6 9 12 15 18 21 24 27 30
We see that when a function like log() is called on a vector x, a vector is
returned which has applied the function to each element of the vector x.
x == 3 & x != 3
#OUT> [1] 5 7 8 9
x[x != 3]
#OUT> [1] 1 5 7 8 9
sum(x > 3)
#OUT> [1] 4
[Link](x > 3)
#OUT> [1] 0 0 1 1 1 1
Here we saw that using the sum() function on a vector of logical TRUE and
FALSE values that is the result of x > 3 results in a numeric result: you just
counted for how many elements of x, the condition > 3 is TRUE. During the call
to sum(), R is first automatically coercing the logical to numeric where TRUE
is 1 and FALSE is 0. This coercion from logical to numeric happens for most
mathematical operations.
# which(condition of x) returns true/false
# each index of x where condition is true
which(x > 3)
#OUT> [1] 3 4 5 6
x[which(x > 3)]
#OUT> [1] 5 7 8 9
max(x)
#OUT> [1] 9
which(x == max(x))
#OUT> [1] 6
[Link](x)
#OUT> [1] 6
22 CHAPTER 1. INTRODUCTION TO R
[Link] Task 2
1. Create a vector filled with 10 numbers drawn from the uniform distribution
(hint: use function runif) and store them in x.
2. Using logical subsetting as above, get all the elements of x which are larger
than 0.5, and store them in y.
3. using the function which, store the indices of all the elements of x which
are larger than 0.5 in iy.
4. Check that y and x[iy] are identical.
1.9.3 Matrices
R can also be used for matrix calculations. Matrices have rows and columns
containing a single data type. In a matrix, the order of rows and columns is
important. (This is not true of data frames, which we will see later.)
Matrices can be created using the matrix function.
x = 1:9
x
#OUT> [1] 1 2 3 4 5 6 7 8 9
X = matrix(x, nrow = 3, ncol = 3)
X
#OUT> [1] 4
Here we accessed the element in the first row and the second column. We could
also subset an entire row or column.
X[1, ]
#OUT> [1] 1 4 7
X[, 2]
#OUT> [1] 4 5 6
We can also use vectors to subset more than one row or column at a time. Here
we subset to the first and third column of the second row:
X[2, c(1, 3)]
#OUT> [1] 2 8
Matrices can also be created by combining vectors as columns, using cbind, or
combining vectors as rows, using rbind.
x = 1:9
rev(x)
#OUT> [1] 9 8 7 6 5 4 3 2 1
rep(1, 9)
#OUT> [1] 1 1 1 1 1 1 1 1 1
rbind(x, rev(x), rep(1, 9))
#OUT> [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9]
#OUT> x 1 2 3 4 5 6 7 8 9
#OUT> 9 8 7 6 5 4 3 2 1
24 CHAPTER 1. INTRODUCTION TO R
#OUT> 1 1 1 1 1 1 1 1 1
cbind(col_1 = x, col_2 = rev(x), col_3 = rep(1, 9))
X * Y
1.9.4 Arrays
#OUT> , , 1
#OUT>
#OUT> [,1] [,2]
#OUT> [1,] 1 5
#OUT> [2,] 2 6
#OUT> [3,] 3 7
26 CHAPTER 1. INTRODUCTION TO R
#OUT> [4,] 4 8
#OUT>
#OUT> , , 2
#OUT>
#OUT> [,1] [,2]
#OUT> [1,] 9 13
#OUT> [2,] 10 14
#OUT> [3,] 11 15
#OUT> [4,] 12 16
You can see that d3 are simply two (4,2) matrices laid on top of each other,
as if there were two pages. Similary, d4 would have two pages, and another 3
registers in a fourth dimension. And so on. You can subset an array like you
would a vector or a matrix, taking care to index each dimension:
d3[ ,1,1] # all elements from col 1, page 1
#OUT> [1] 1 2 3 4
d3[2:3, , ] # rows 2:3 from all pages
#OUT> , , 1
#OUT>
#OUT> [,1] [,2]
#OUT> [1,] 2 6
#OUT> [2,] 3 7
#OUT>
#OUT> , , 2
#OUT>
#OUT> [,1] [,2]
#OUT> [1,] 10 14
#OUT> [2,] 11 15
d3[2,2, ] # row 2, col 2 from both pages.
#OUT> [1] 6 14
[Link] Task 3
1.9.5 Lists
#OUT> [[1]]
#OUT> [1] 42
#OUT>
#OUT> [[2]]
#OUT> [1] "Hello"
#OUT>
#OUT> [[3]]
#OUT> [1] TRUE
# creation with fieldnames
ex_list = list(
a = c(1, 2, 3, 4),
b = TRUE,
c = "Hello!",
d = function(arg = 42) {print("Hello World!")},
e = diag(5)
)
Lists can be subset using two syntaxes, the $ operator, and square brackets [].
The $ operator returns a named element of a list. The [] syntax returns a
list, while the [[]] returns an element of a list.
• ex_list[1] returns a list contain the first element.
• ex_list[[1]] returns the first element of the list, in this case, a vector.
# subsetting
ex_list$e
#OUT> $a
28 CHAPTER 1. INTRODUCTION TO R
#OUT> [1] 1 2 3 4
#OUT>
#OUT> $b
#OUT> [1] TRUE
ex_list[1]
#OUT> $a
#OUT> [1] 1 2 3 4
ex_list[[1]]
#OUT> [1] 1 2 3 4
ex_list[c("e", "a")]
#OUT> $e
#OUT> [,1] [,2] [,3] [,4] [,5]
#OUT> [1,] 1 0 0 0 0
#OUT> [2,] 0 1 0 0 0
#OUT> [3,] 0 0 1 0 0
#OUT> [4,] 0 0 0 1 0
#OUT> [5,] 0 0 0 0 1
#OUT>
#OUT> $a
#OUT> [1] 1 2 3 4
ex_list["e"]
#OUT> $e
#OUT> [,1] [,2] [,3] [,4] [,5]
#OUT> [1,] 1 0 0 0 0
#OUT> [2,] 0 1 0 0 0
#OUT> [3,] 0 0 1 0 0
#OUT> [4,] 0 0 0 1 0
#OUT> [5,] 0 0 0 0 1
ex_list[["e"]]
ex_list$d(arg = 1)
[Link] Task 4
1. Copy and paste the above code for ex_list into your R session. Re-
member that list can hold any kind of R object. Like…another list! So,
create a new list new_list that has two fields: a first field called “this”
with string content "is awesome", and a second field called “ex_list” that
contains ex_list.
2. Accessing members is like in a plain list, just with several layers now. Get
the element c from ex_list in new_list!
3. Compose a new string out of the first element in new_list, the element
under label this. Use the function paste to print R is awesome to your
screen.
We have previously seen vectors and matrices for storing data as we introduced
R. We will now introduce a data frame which will be the most common way
that we store and interact with data in this course. A [Link] is similar to
a python [Link] or a julia DataFrame. (But the R version was the
first! :-) )
example_data = [Link](x = c(1, 3, 5, 7, 9, 1, 3, 5, 7, 9),
y = c(rep("Hello", 9), "Goodbye"),
z = rep(c(TRUE, FALSE), 5))
Unlike a matrix, which can be thought of as a vector rearranged into rows and
columns, a data frame is not required to have the same data type for each
element. A data frame is a list of vectors, and each vector has a name. So,
each vector must contain the same data type, but the different vectors can
store different data types. Note, however, that all vectors must have the same
length (differently from a list)!
example_data
#OUT> x y z
#OUT> 1 1 Hello TRUE
#OUT> 2 3 Hello FALSE
#OUT> 3 5 Hello TRUE
#OUT> 4 7 Hello FALSE
#OUT> 5 9 Hello TRUE
#OUT> 6 1 Hello FALSE
#OUT> 7 3 Hello TRUE
#OUT> 8 5 Hello FALSE
#OUT> 9 7 Hello TRUE
#OUT> 10 9 Goodbye FALSE
Unlike a list, which has more flexibility, the elements of a data frame must all
be vectors. Again, we access any given column with the $ operator:
example_data$x
#OUT> [1] 1 3 5 7 9 1 3 5 7 9
[Link](length(example_data$x),
length(example_data$y),
length(example_data$z))
#OUT> [1] 10
ncol(example_data)
#OUT> [1] 3
dim(example_data)
#OUT> [1] 10 3
names(example_data)
The [Link]() function above is one way to create a data frame. We can
also import data from various file types in into R, as well as use data stored in
packages.
To read this data back into R, we will use the built-in function [Link]:
path = [Link](package="ScPoEconometrics","datasets","[Link]")
example_data_from_disk = [Link](path)
This particular line of code assumes that you installed the associated R
package to this book, hence you have this dataset stored on your computer at
[Link](package = "ScPoEconometrics","datasets","[Link]").
example_data_from_disk
#OUT> x y z
#OUT> 1 1 Hello TRUE
#OUT> 2 3 Hello FALSE
#OUT> 3 5 Hello TRUE
#OUT> 4 7 Hello FALSE
#OUT> 5 9 Hello TRUE
#OUT> 6 1 Hello FALSE
#OUT> 7 3 Hello TRUE
#OUT> 8 5 Hello FALSE
#OUT> 9 7 Hello TRUE
#OUT> 10 9 Goodbye FALSE
When using data, there are three things we would generally like to do:
• Look at the raw data.
• Understand the data. (Where did it come from? What are the variables?
Etc.)
• Visualize the data.
To look at data in a [Link], we have two useful commands: head() and
str().
# we are working with the built-in mtcars dataset:
mtcars
In this dataset an observation is for a particular model of a car, and the variables
describe attributes of the car, for example its fuel efficiency, or its weight.
To understand more about the data set, we use the ? operator to pull up the
documentation for the data.
?mtcars
R has a number of functions for quickly working with and extracting basic in-
formation from data frames. To quickly obtain a vector of the variable names,
we use the names() function.
names(mtcars)
#OUT> [1] "mpg" "cyl" "disp" "hp" "drat" "wt" "qsec" "vs" "am" "gear"
#OUT> [11] "carb"
#OUT> [1] 21.0 21.0 22.8 21.4 18.7 18.1 14.3 24.4 22.8 19.2 17.8 16.4 17.3 15.2
#OUT> [15] 10.4 10.4 14.7 32.4 30.4 33.9 21.5 15.5 15.2 13.3 19.2 27.3 26.0 30.4
#OUT> [29] 15.8 19.7 15.0 21.4
mtcars$wt
#OUT> [1] 2.620 2.875 2.320 3.215 3.440 3.460 3.570 3.190 3.150 3.440 3.440
#OUT> [12] 4.070 3.730 3.780 5.250 5.424 5.345 2.200 1.615 1.835 2.465 3.520
#OUT> [23] 3.435 3.840 3.845 1.935 2.140 1.513 3.170 2.770 3.570 2.780
We can use the dim(), nrow() and ncol() functions to obtain information
about the dimension of the data frame.
34 CHAPTER 1. INTRODUCTION TO R
dim(mtcars)
#OUT> [1] 32 11
nrow(mtcars)
#OUT> [1] 32
ncol(mtcars)
#OUT> [1] 11
Here nrow() is also the number of observations, which in most cases is the
sample size.
Subsetting data frames can work much like subsetting matrices using square
brackets, [ , ]. Here, we find vehicles with mpg over 25 miles per gallon and
only display columns cyl, disp and wt.
# mpg[row condition, col condition]
mtcars[mtcars$mpg > 20, c("cyl", "disp", "wt")]
[Link] Task 5
4. What is the average value of mpg for cars with more than 4 cylinders,
i.e. with cyl>4?
1.11.1 Variables
We encountered the term variable already several times, but mainly in the con-
text of a column of a [Link]. In programming, a variable is denotes an object.
Another way to say it is that a variable is a name or a label for something:
x = 1
y = "roses"
z = function(x){sqrt(x)}
Here x refers to the value √1, y holds the string “roses”, and z is the name of
a function that computes x. Notice that the argument x of the function is
different from the x we just defined. It is local to the function:
x
#OUT> [1] 1
z(9)
#OUT> [1] 3
Control Flow relates to ways in which you can adapt your code to different
circumstances. Based on a condition being TRUE, your program will do one
thing, as opposed to another thing. This is most widely known as an if/else
statement. In R, the if/else syntax is:
if (condition = TRUE) {
some R code
} else {
some other R code
}
For example,
x = 1
y = 3
36 CHAPTER 1. INTRODUCTION TO R
#OUT> [1] 16
1.11.3 Loops
For example, consider this simple for loop, which will simply print the value of
the iterator (called i in this case) to screen:
for (i in 1:5){
print(i)
}
#OUT> [1] 1
#OUT> [1] 2
#OUT> [1] 3
#OUT> [1] 4
#OUT> [1] 5
Notice that instead of 1:5, we could have any kind of iterable collection:
for (i in c("mangos","bananas","apples")){
print(paste("I love",i)) # the paste function pastes together strings
}
1.11. PROGRAMMING BASICS 37
1.11.4 Functions
So far we have been using functions, but haven’t actually discussed some of
their details. A function is a set of instructions that R executes for us, much like
those collected in a script file. The good thing is that functions are much more
flexible than scripts, since they can depend on input arguments, which change
the way the function behaves. Here is how to define a function:
function_name <- function(arg1,arg2=default_value){
# function body
# you do stuff with arg1 and arg2
# you can have any number of arguments, with or without defaults
# any valid `R` commands can be included here
# the last line is returned
}
[Link] Task 6
1. Write a for loop that counts down from 10 to 1, printing the value of the
iterator to the screen.
2. Modify that loop to write “i iterations to go” where i is the iterator
3. Modify that loop so that each iteration takes roughly one second. You can
achieve that by adding the command [Link](1) below the line that
prints “i iterations to go”.
Chapter 2
In this chapter we will first learn some basic concepts that help summarizing
data. Then, we will tackle a real-world task and read, clean, and summarize
data from the web.
Central Tendency
Suppose we want to know the mean and median of all the values stored in the
[Link] column mpg$cty:
Measure R Result
Mean mean(mpg$cty) 16.8589744
Median median(mpg$cty) 17
Spread
How do the values in that column vary? How far spread out are they?
39
40 CHAPTER 2. WORKING WITH DATA
Measure R Result
Variance var(mpg$cty) 18.1130736
Standard Deviation sd(mpg$cty) 4.2559457
IQR IQR(mpg$cty) 5
Minimum min(mpg$cty) 9
Maximum max(mpg$cty) 35
Range range(mpg$cty) 9, 35
Categorical
For categorical variables, counts and percentages can be used for summary.
table(mpg$drv)
#OUT>
#OUT> 4 f r
#OUT> 103 106 25
table(mpg$drv) / nrow(mpg)
#OUT>
#OUT> 4 f r
#OUT> 0.4401709 0.4529915 0.1068376
2.2 Plotting
Now that we have some data to work with, and we have learned about the
data at the most basic level, our next tasks will be to visualize it. Often, a
proper visualization can illuminate features of the data that can inform further
analysis.
We will look at four methods of visualizing data by using the basic plot facilities
built-in with R:
• Histograms
• Barplots
• Boxplots
• Scatterplots
2.2.1 Histograms
hist(mpg$cty)
Histogram of mpg$cty
80
60
Frequency
40
20
0
5 10 15 20 25 30 35
mpg$cty
40
30
Frequency
20
10
0
10 15 20 25 30 35
Importantly, you should always be sure to label your axes and give the plot a
title. The argument breaks is specific to hist(). Entering an integer will give
a suggestion to R for how many bars to use for the histogram. By default R will
attempt to intelligently guess a good number of breaks, but as we can see here,
it is sometimes useful to modify this yourself.
2.2.2 Barplots
4 f r
barplot(table(mpg$drv),
xlab = "Drivetrain (f = FWD, r = RWD, 4 = 4WD)",
ylab = "Frequency",
main = "Drivetrains",
col = "dodgerblue",
border = "darkorange")
Drivetrains
100
80
Frequency
60
40
20
0
4 f r
2.2.3 Boxplots
First note that we can use a single boxplot as an alternative to a histogram for
visualizing a single numerical variable. To do so in R, we use the boxplot()
function. The box shows the interquartile range, the solid line in the middle is
the value of the median, the wiskers show 1.5 times the interquartile range, and
the dots are outliers.
boxplot(mpg$hwy)
45
40
35
30
25
20
15
However, more often we will use boxplots to compare a numerical variable for
different values of a categorical variable.
boxplot(hwy ~ drv, data = mpg)
2.2. PLOTTING 45
45
40
35
30
hwy
25
20
15
4 f r
drv
45
40
Miles Per Gallon (Highway)
35
30
25
20
15
4 f r
Again, boxplot() has a number of additional arguments which have the ability
to make our plot more visually appealing.
2.2.4 Scatterplots
Lastly, to visualize the relationship between two numeric variables we will use a
scatterplot. This can be done with the plot() function and the ~ syntax we
just used with a boxplot. (The function plot() can also be used more generally;
see the documentation for details.)
plot(hwy ~ displ, data = mpg)
2.2. PLOTTING 47
45
40
35
30
hwy
25
20
15
2 3 4 5 6 7
displ
plot(hwy ~ displ, data = mpg,
xlab = "Engine Displacement (in Liters)",
ylab = "Miles Per Gallon (Highway)",
main = "MPG (Highway) vs Engine Displacement",
pch = 20,
cex = 2,
col = "dodgerblue")
35
30
25
20
15
2 3 4 5 6 7
2.2.5 ggplot
All of the above plots could also have been generated using the ggplot function
from the already loaded ggplot2 package. Which function you use is up to you,
but sometimes a plot is easier to build in base R (like in the boxplot example
maybe), sometimes the other way around.
ggplot(data = mpg,mapping = aes(x=displ,y=hwy)) + geom_point()
40
30
hwy
20
2 3 4 5 6 7
displ
40
Miles Per Gallon (Highway)
30
20
2 3 4 5 6 7
Engine Displacement (in Liters)
If you want to see ggplot in action, you could start with this and then look at
that very nice tutorial? It’s fun!
The answer to this type of question can be addressed by computing the covari-
ance of both variables:
cov(x,y)
3
2
1
y
0
−1
−2
−2 −1 0 1 2
x
Figure 2.1: How are x and y related?
x
z=
σx
cov(x, y)
rx,y = ,
σx σy
and we get rid of the units problem. In R, you can call directly
cor(x,y)
Figure 2.2: visual estimation on σ. The x-axis labels min and max as well as
mean of x.
Now this is better. Given that the correlation has to lie in [−1, 1], a value of
0.91 is indicative of a rather strong positive relationship for the data in figure
2.1
Note that x, y being drawn from a continuous distribution (they are joint nor-
mally distributed) had no implication for covariance and correlation: We can
compute those measures also for discrete random variables (like the throws of
two dice, as you will see in one of our tutorials).
This is illustrated in figure 2.2. Here we see that range(x)/4 gives 3.16 which
compares favourably to the actual standard deviation 3.
52 CHAPTER 2. WORKING WITH DATA
We could have used the read_csv() function from the readr package to read
our example dataset from the previous chapter. The readr function read_csv()
has a number of advantages over the built-in [Link]. For example, it is much
faster reading larger data. It also uses the tibble package to read the data as a
tibble. A tibble is simply a data frame that prints with sanity. Notice
in the output below that we are given additional information such as dimension
and variable type.
library(readr) # you need `[Link]("readr")` once!
path = [Link](package="ScPoEconometrics","datasets","[Link]")
example_data_from_disk = read_csv(path)
#OUT> # A tibble: 10 x 11
#OUT> manufacturer model displ year cyl trans drv cty hwy fl class
#OUT> <chr> <chr> <dbl> <int> <int> <chr> <chr> <int> <int> <chr> <chr>
2.4. THE TIDYVERSE 53
The function head() will display the first n observations of the data frame, as
we have seen. The head() function was more useful before tibbles. Notice
that mpg is a tibble already, so the output from head() indicates there are only
10 observations. Note that this applies to head(mpg, n = 10) and not mpg
itself. Also note that tibbles print a limited number of rows and columns by
default. The last line of the printed output indicates with rows and columns
were omitted.
mpg
Let’s look at str as well to get familiar with the content of the data:
str(mpg)
Working with tibbles is mostly the same as working with plain [Link]:
names(mpg)
#OUT> [1] 1999 1999 2008 2008 1999 1999 2008 1999 1999 2008 2008 1999 1999 2008
#OUT> [15] 2008 1999 2008 2008 2008 2008 2008 1999 2008 1999 1999 2008 2008 2008
#OUT> [29] 2008 2008 1999 1999 1999 2008 1999 2008 2008 1999 1999 1999 1999 2008
#OUT> [43] 2008 2008 1999 1999 2008 2008 2008 2008 1999 1999 2008 2008 2008 1999
#OUT> [57] 1999 1999 2008 2008 2008 1999 2008 1999 2008 2008 2008 2008 2008 2008
#OUT> [71] 1999 1999 2008 1999 1999 1999 2008 1999 1999 1999 2008 2008 1999 1999
#OUT> [85] 1999 1999 1999 2008 1999 2008 1999 1999 2008 2008 1999 1999 2008 2008
#OUT> [99] 2008 1999 1999 1999 1999 1999 2008 2008 2008 2008 1999 1999 2008 2008
#OUT> [113] 1999 1999 2008 1999 1999 2008 2008 2008 2008 2008 2008 2008 1999 1999
#OUT> [127] 2008 2008 2008 2008 1999 2008 2008 1999 1999 1999 2008 1999 2008 2008
#OUT> [141] 1999 1999 1999 2008 2008 2008 2008 1999 1999 2008 1999 1999 2008 2008
#OUT> [155] 1999 1999 1999 2008 2008 1999 1999 2008 2008 2008 2008 1999 1999 1999
#OUT> [169] 1999 2008 2008 2008 2008 1999 1999 1999 1999 2008 2008 1999 1999 2008
#OUT> [183] 2008 1999 1999 2008 1999 1999 2008 2008 1999 1999 2008 1999 1999 1999
#OUT> [197] 2008 2008 1999 2008 1999 1999 2008 1999 1999 2008 2008 1999 1999 2008
#OUT> [211] 2008 1999 1999 1999 1999 2008 2008 2008 2008 1999 1999 1999 1999 1999
#OUT> [225] 1999 2008 2008 1999 1999 2008 2008 1999 1999 2008
mpg$hwy
#OUT> [1] 29 29 31 30 26 26 27 26 25 28 27 25 25 25 25 24 25 23 20 15 20 17 17
#OUT> [24] 26 23 26 25 24 19 14 15 17 27 30 26 29 26 24 24 22 22 24 24 17 22 21
#OUT> [47] 23 23 19 18 17 17 19 19 12 17 15 17 17 12 17 16 18 15 16 12 17 17 16
#OUT> [70] 12 15 16 17 15 17 17 18 17 19 17 19 19 17 17 17 16 16 17 15 17 26 25
#OUT> [93] 26 24 21 22 23 22 20 33 32 32 29 32 34 36 36 29 26 27 30 31 26 26 28
#OUT> [116] 26 29 28 27 24 24 24 22 19 20 17 12 19 18 14 15 18 18 15 17 16 18 17
2.4. THE TIDYVERSE 55
#OUT> [139] 19 19 17 29 27 31 32 27 26 26 25 25 17 17 20 18 26 26 27 28 25 25 24
#OUT> [162] 27 25 26 23 26 26 26 26 25 27 25 27 20 20 19 17 20 17 29 27 31 31 26
#OUT> [185] 26 28 27 29 31 31 26 26 27 30 33 35 37 35 15 18 20 20 22 17 19 18 20
#OUT> [208] 29 26 29 29 24 44 29 26 29 29 29 29 23 24 44 41 29 26 28 29 29 29 28
#OUT> [231] 29 26 26 26
Subsetting is also similar to dataframe. Here, we find fuel efficient vehicles
earning over 35 miles per gallon and only display manufacturer, model and
year.
# mpg[row condition, col condition]
mpg[mpg$hwy > 35, c("manufacturer", "model", "year")]
#OUT> # A tibble: 6 x 3
#OUT> manufacturer model year
#OUT> <chr> <chr> <int>
#OUT> 1 honda civic 2008
#OUT> 2 honda civic 2008
#OUT> 3 toyota corolla 2008
#OUT> 4 volkswagen jetta 1999
#OUT> 5 volkswagen new beetle 1999
#OUT> 6 volkswagen new beetle 1999
An alternative would be to use the subset() function, which has a much more
readable syntax.
subset(mpg, subset = hwy > 35, select = c("manufacturer", "model", "year"))
Lastly, and most tidy, we could use the filter and select functions from
the dplyr package which introduces the pipe operator f(x) %>% g(z) from the
magrittr package. This operator takes the output of the first command, for
example y = f(x), and passes it as the first argument to the next function,
i.e. we’d obtain g(y,z) here.1
library(dplyr)
mpg %>%
filter(hwy > 35) %>%
select(manufacturer, model, year)
#OUT> # A tibble: 6 x 3
#OUT> manufacturer model year
#OUT> <chr> <chr> <int>
#OUT> 1 honda civic 2008
#OUT> 2 honda civic 2008
#OUT> 3 toyota corolla 2008
1 A pipe is a concept from the Unix world, where it means to take the output of some
command, and pass it on to another command. This way, one can construct a pipeline of
commands. For additional info on the pipe operator in R, you might be interested in this
tutorial.
56 CHAPTER 2. WORKING WITH DATA
Note that the above syntax is equivalent to the following pipe-free command
(which is much harder to read!):
library(dplyr)
select(filter(mpg, hwy > 35), manufacturer, model, year)
#OUT> # A tibble: 6 x 3
#OUT> manufacturer model year
#OUT> <chr> <chr> <int>
#OUT> 1 honda civic 2008
#OUT> 2 honda civic 2008
#OUT> 3 toyota corolla 2008
#OUT> 4 volkswagen jetta 1999
#OUT> 5 volkswagen new beetle 1999
#OUT> 6 volkswagen new beetle 1999
All three approaches produce the same results. Which you use will be largely
based on a given situation as well as your preference.
[Link] Task 1
1. Make sure to have the mpg dataset loaded by typing data(mpg) (and
library(ggplot2) if you haven’t!). Use the table function to find out
how many cars were built by mercury?
2. What is the average year the audi’s were built in this dataset? Use the
function mean on the subset of column year that corresponds to audi. (Be
careful: subsetting a tibble returns a tibble (and not a vector)!. so get
the year column after you have subset the tibble.)
3. Use the dplyr piping syntax from above first with group_by and then
with summarise(newvar=your_expression) to find the mean year by all
manufacturers (i.e. same as previous task, but for all manufacturers. don’t
write a loop!).
The data we will look at is from Eurostat on demography and migration. You
should download the data yourself (click on previous link, then drill down to
database by themes > Population and social conditions > Demograph and mi-
gration > Population change - Demographic balance and crude rates at national
level (demo_gind)).
2.4. THE TIDYVERSE 57
Once downloaded, we can read the data with the function read_excel from the
package readxl, again part of the tidyverse suite.
It’s important to know how the data is organized in the spreadsheet. Open the
file with Excel to see:
• There is a heading which we don’t need.
• There are 5 rows with info that we don’t need.
• There is one table per variable (total population, males, females, etc)
• Each table has one row for each country, and one column for each year.
• As such, this data is not tidy.
Now we will read the first chunk of data, from the first table: total population:
library(readxl) # load the library
# Notice that if you installed the R package of this book,
# you have the .xls data file already at
# `[Link](package="ScPoEconometrics",
# "datasets","demo_gind.xls")`
# otherwise:
# * download the file to your computer
# * change the argument `path` to where you downloaded it
# you may want to change your working directory with `setwd("your/directory")
# or in RStudio by clicking Session > Set Working Directory
#OUT> # A tibble: 59 x 11
#OUT> Country `2008` `2009` `2010` `2011` `2012` `2013` `2014` `2015` `2016`
#OUT> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
#OUT> 1 Europe~ 50029~ 50209~ 50317~ 50296~ 50404~ 50516~ 50701~ 50854~ 51027~
#OUT> 2 Europe~ 43872~ 44004~ 44066~ 43994~ 44055~ 44125~ 44266~ 44366~ 44489~
#OUT> 3 Europe~ 49598~ 49778~ 49886~ 49867~ 49977~ 50090~ 50276~ 50431~ 50608~
#OUT> 4 Euro a~ 33309~ 33447~ 33526~ 33457~ 33528~ 33604~ 33754~ 33856~ 33988~
#OUT> 5 Euro a~ 32988~ 33128~ 33212~ 33152~ 33228~ 33307~ 33459~ 33563~ 33699~
#OUT> 6 Belgium 10666~ 10753~ 10839~ 11000~ 11075~ 11137~ 11180~ 11237~ 11311~
#OUT> 7 Bulgar~ 75180~ 74671~ 74217~ 73694~ 73272~ 72845~ 72456~ 72021~ 71537~
#OUT> 8 Czech ~ 10343~ 10425~ 10462~ 10486~ 10505~ 10516~ 10512~ 10538~ 10553~
#OUT> 9 Denmark 54757~ 55114~ 55347~ 55606~ 55805~ 56026~ 56272~ 56597~ 57072~
#OUT> 10 German~ 82217~ 82002~ 81802~ 80222~ 80327~ 80523~ 80767~ 81197~ 82175~
#OUT> # ... with 49 more rows, and 1 more variable: `2017` <chr>
58 CHAPTER 2. WORKING WITH DATA
This shows a tibble, which we encountered just above. The column names are
Country,2008,2009,..., and the rows are numbered 1,2,3,.... Notice, in
particular, that all columns seem to be of type <chr>, i.e. characters - a string,
not a number! We’ll have to fix that, as this is clearly numeric data.
[Link] tidyr
In the previous tibble, each year is a column name (like 2008) instead of all
years being collected in one column year. We really would like to have several
rows for each Country, one row per year. We want to gather() all years into a
new column to tidy this up - and here is how:
1. specify which columns are to be gathered: in our case, all years (note
that paste(2008:2017) produces a vector like ["2008", "2009",
"2010",...])
2. say what those columns should be gathered into, i.e. what is the key for
those values: we’ll call it year.
3. Finally, what is the name of the new resulting column, containing the
value from each cell: let’s call it counts.
library(tidyr) # for the gather function
tot_pop = gather(tot_pop_raw, paste(2008:2017),key="year", value = "counts")
tot_pop
[Link] dplyr
65.0
Country
millions
France
62.5 Italy
United Kingdom
60.0
2008 2009 2010 2011 2012 2013 2014 2015 2016 2017
year
Arrange a tibble
#OUT> # A tibble: 5 x 3
#OUT> Country year counts
2.4. THE TIDYVERSE 61
#OUT> # A tibble: 5 x 3
#OUT> Country year counts
#OUT> <chr> <chr> <int>
#OUT> 1 San Marino 2015 32789
#OUT> 2 San Marino 2014 32520
#OUT> 3 San Marino 2008 32054
#OUT> 4 San Marino 2011 31863
#OUT> 5 San Marino 2009 31269
Now this is not exactly what we wanted. It’s always the same country in both
top and bottom, because there are multiple years per country. Let’s compute
average population over the last 5 years and rank according to that:
topbottom = tot_pop %>%
group_by(Country) %>%
filter(year > 2012) %>%
summarise(mean_count = mean(counts)) %>%
arrange(desc(mean_count))
#OUT> # A tibble: 5 x 2
#OUT> Country mean_count
#OUT> <chr> <dbl>
#OUT> 1 European Economic Area (EU28 - current composition, plus IS,~ 514029320
#OUT> 2 European Economic Area (EU27 - before the accession of Croat~ 509813491.
#OUT> 3 European Union (current composition) 508502858.
#OUT> 4 European Union (before the accession of Croatia) 504287028.
#OUT> 5 European Union (without United Kingdom) 443638309.
bottom5
#OUT> # A tibble: 5 x 2
#OUT> Country mean_count
#OUT> <chr> <dbl>
#OUT> 1 Luxembourg 563319.
#OUT> 2 Malta 440467.
62 CHAPTER 2. WORKING WITH DATA
Sometimes data is missing, and R represents it with the special value NA (not
available). It is good to know where in our dataset we are going to encounter
any missing values, so the task here is: let’s produce a table that has three
columns:
1. the names of countries with missing data
2. how many years of data are missing for each of those
3. and the actual years that are missing
missings = tot_pop %>%
filter([Link](counts)) %>% # [Link](x) returns TRUE if x is NA
group_by(Country) %>%
summarise(n_missing = n(),years = paste(year,collapse = ", "))
knitr:::kable(missings) # knitr:::kable makes a nice table
Let’s look at the numbers by male and female population. They are in the
same xls file, but at different cell ranges. Also, I just realised that the special
character : indicates missing data. We can feed that to read_excel and that
will spare us the need to convert data types afterwards. Let’s see:
females_raw = read_excel(
path = [Link](package="ScPoEconometrics",
"datasets","demo_gind.xls"),
sheet="Data", # which sheet
range="A141:K200", # which excel cell range to read
na=":" ) # missing data indicator
2.4. THE TIDYVERSE 63
#OUT> # A tibble: 59 x 11
#OUT> Country `2008` `2009` `2010` `2011` `2012` `2013` `2014` `2015` `2016`
#OUT> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#OUT> 1 Europe~ 2.56e8 2.57e8 2.58e8 2.58e8 2.58e8 2.59e8 2.60e8 2.60e8 2.61e8
#OUT> 2 Europe~ 2.25e8 2.26e8 2.26e8 2.26e8 2.26e8 2.26e8 2.27e8 2.27e8 2.28e8
#OUT> 3 Europe~ 2.54e8 2.55e8 2.55e8 2.56e8 2.56e8 2.57e8 2.57e8 2.58e8 2.59e8
#OUT> 4 Euro a~ 1.71e8 1.71e8 1.72e8 1.72e8 1.72e8 1.72e8 1.73e8 1.73e8 1.74e8
#OUT> 5 Euro a~ 1.69e8 1.70e8 1.70e8 1.70e8 1.70e8 1.71e8 1.71e8 1.72e8 1.72e8
#OUT> 6 Belgium 5.44e6 5.48e6 5.53e6 5.60e6 5.64e6 5.67e6 5.69e6 5.71e6 5.74e6
#OUT> 7 Bulgar~ 3.86e6 3.83e6 3.81e6 3.78e6 3.76e6 3.74e6 3.72e6 3.70e6 3.68e6
#OUT> 8 Czech ~ 5.28e6 5.31e6 5.33e6 5.34e6 5.35e6 5.35e6 5.35e6 5.36e6 5.37e6
#OUT> 9 Denmark 2.76e6 2.78e6 2.79e6 2.80e6 2.81e6 2.82e6 2.83e6 2.85e6 2.87e6
#OUT> 10 German~ 4.19e7 4.18e7 4.17e7 4.11e7 4.11e7 4.11e7 4.12e7 4.14e7 4.17e7
#OUT> # ... with 49 more rows, and 1 more variable: `2017` <dbl>
You can see that R now correctly read the numbers as such, after we told it that
the : character has the special missing meaning: before, it coerced the entire
2008 column (for example) to be of type chr after it hit the first :. We had
to manually convert the column back to numeric, in the process automatically
coercing the :s into NA. Now we addressed that issue directly. Let’s also get the
male data in the same way:
males_raw = read_excel(
path = [Link](package="ScPoEconometrics",
"datasets","demo_gind.xls"),
sheet="Data", # which sheet
range="A75:K134", # which excel cell range to read
na=":" ) # missing data indicator
names(males_raw)[1] <- "Country" # lets rename the first column
Let’s try to tweak our above plot to show the same data in two separate panels:
one for males and one for females. This is easiest to do with ggplot if we have
all the data in one single [Link] (or tibble), and marked with a group
identifier. Let’s first add this to both datasets, and then let’s just combine both
into one:
females$sex = "female"
males$sex = "male"
sexes = rbind(males,females) # "row bind" 2 [Link]
sexes
64 CHAPTER 2. WORKING WITH DATA
female male
34
Country
32
millions
France
Italy
United Kingdom
30
2010 2012 2014 2016 2018 2010 2012 2014 2016 2018
year
2.4. THE TIDYVERSE 65
How do our three countries compare with respect to the biggest country in
the EU in terms of population? What fraction of Germany does the French
population make in any given year, for example?
# remember that the pipe operator %>% takes the
# result of the previous operation and passes it
# as the *first* argument to the next function call
merge_GER <- tot_pop %>%
# 1. subset to countries of interest
filter(
Country %in%
c("France",
"United Kingdom",
"Italy")
) %>%
# 2. group data by year
group_by(year) %>%
# 3. add GER's count as new column *by year*
left_join(
# Germany only
filter(tot_pop,
Country %in% "Germany including former GDR"),
# join back in `by year`
by="year")
merge_GER
#OUT> # A tibble: 30 x 5
#OUT> # Groups: year [10]
#OUT> Country.x year counts.x Country.y counts.y
#OUT> <chr> <chr> <int> <chr> <int>
#OUT> 1 France 2008 64007193 Germany including former GDR 82217837
#OUT> 2 Italy 2008 58652875 Germany including former GDR 82217837
#OUT> 3 United Kingdom 2008 61571647 Germany including former GDR 82217837
#OUT> 4 France 2009 64350226 Germany including former GDR 82002356
#OUT> 5 Italy 2009 59000586 Germany including former GDR 82002356
#OUT> 6 United Kingdom 2009 62042343 Germany including former GDR 82002356
#OUT> 7 France 2010 64658856 Germany including former GDR 81802257
#OUT> 8 Italy 2010 59190143 Germany including former GDR 81802257
#OUT> 9 United Kingdom 2010 62510197 Germany including former GDR 81802257
#OUT> 10 France 2011 64978721 Germany including former GDR 80222065
#OUT> # ... with 20 more rows
Here you see that the merge (or join) operation labelled col.x and col.y if
both datasets contained a column called col. Now let’s continue to compute
what proportion of german population each country amounts to:
66 CHAPTER 2. WORKING WITH DATA
80.0
percent of German population
77.5
Country
France
Italy
United Kingdom
75.0
72.5
2008 2009 2010 2011 2012 2013 2014 2015 2016 2017
year
Chapter 3
Linear Regression
In this chapter we will learn an additional way how one can represent the rela-
tionship between outcome, or dependent variable variable y and an explanatory
or independent variable x. We will refer throughout to the graphical represen-
tation of a collection of independent observations on x and y, i.e., a dataset.
We will look at the built-in cars dataset. Let’s get a view of this by just typing
View(cars) in Rstudio. You can see something like this:
#OUT> speed dist
#OUT> 1 4 2
#OUT> 2 4 10
#OUT> 3 7 4
#OUT> 4 7 22
#OUT> 5 8 16
#OUT> 6 9 10
We have a [Link] with two columns: speed and dist. Type help(cars)
to find out more about the dataset. There you could read that
The data give the speed of cars (mph) and the distances taken to
stop (ft).
It’s good practice to know the extent of a dataset. You could just type
dim(cars)
67
68 CHAPTER 3. LINEAR REGRESSION
#OUT> [1] 50 2
to find out that we have 50 rows and 2 columns. A central question that we
want to ask now is the following:
The simplest way to start is to plot the data. Remembering that we view each
row of a [Link] as an observation, we could just label one axis of a graph
speed, and the other one dist, and go through our table above row by row. We
just have to read off the x/y coordinates and mark them in the graph. In R:
plot(dist ~ speed, data = cars,
xlab = "Speed (in Miles Per Hour)",
ylab = "Stopping Distance (in Feet)",
main = "Stopping Distance vs Speed",
pch = 20,
cex = 2,
col = "red")
80
60
40
20
0
5 10 15 20 25
Here, each dot represents one observation. In this case, one particular measure-
ment speed and dist for a car. Now, again:
How are speed and dist related? How could one best summarize this
relationship?
3.1. HOW ARE X AND Y RELATED? 69
One thing we could do, is draw a straight line through this scatterplot, like so:
plot(dist ~ speed, data = cars,
xlab = "Speed (in Miles Per Hour)",
ylab = "Stopping Distance (in Feet)",
main = "Stopping Distance vs Speed",
pch = 20,
cex = 2,
col = "red")
abline(a = 60,b = 0,lw=3)
80
60
40
20
0
5 10 15 20 25
Now that doesn’t seem a particularly good way to summarize the relationship.
Clearly, a better line would be not be flat, but have a slope, i.e. go upwards:
70 CHAPTER 3. LINEAR REGRESSION
100 120
Stopping Distance (in Feet)
80
60
40
20
0
5 10 15 20 25
That is slightly better. However, the line seems at too high a level - the point
at which it crosses the y-axis is called the intercept; and it’s too high. We just
learned how to represent a line, i.e. with two numbers called intercept and slope.
Let’s write down a simple formula which represents a line where some outcome
z is related to a variable x:
z = b0 + b1 x (3.1)
Here b0 represents the value of the intercept (i.e. z when x = 0), and b1 is the
value of the slope. The question for us is now: How to choose the number b0
and b1 such that the result is the good line?
In order to be able to reason about good or bad line, we need to denote the
output of equation (3.1). We call the value ŷi the predicted value for obseration
i, after having chosen some particular values b0 and b1 :
ŷi = b0 + b1 xi (3.2)
10
8
y
6
4
2
0 5 10
Suppose we have the following set of 9 observations on x and y, and we put the
best straight line into it, that we can think of. It would look like this:
Here, the red arrows indicate the distance between the prediction (i.e. the
black line) to each data point, in other words, each arrow is a particular ei . An
upward pointing arrow indicates a positive value of a particular ei , and vice
versa for downward pointing arrows. The erros are also called residuals, which
comes from the way can write the equation for this relationship between two
particular values (yi , xi ) belonging to observation i:
yi = b0 + b1 xi + ei (3.3)
You realize of course that ŷi = yi − ei , which just means that our prediction is
the observed value yi minus any error ei we make. In other words, ei is what is
left to be explained on top of the line b0 + b1 xi , hence, it’s a residual to explain
yi . Here are y, ŷ and the resulting e which are plotted in figure 3.1:
72 CHAPTER 3. LINEAR REGRESSION
x y y_hat error
0.00 2.09 2.57 -0.48
1.25 2.79 3.41 -0.62
2.50 6.49 4.25 2.24
3.75 1.71 5.10 -3.39
5.00 9.89 5.94 3.95
6.25 7.62 6.78 0.83
7.50 4.86 7.63 -2.77
8.75 7.38 8.47 -1.09
10.00 10.63 9.31 1.32
If our line was a perfect fit to the data, all ei = 0, and the column error
would display 0 for each row - there would be no errors at all. (All points in
figure 3.1 would perfectly line up on a straight line).
Now, back to our claim that this particular line is the best line. What exactly
characterizes this best line? We now come back to what we said above - how to
make the errors as small as possible? Keeping in mind that each residual ei is
yi − ŷi , we have the following minization problem to solve:
∑
N
e21 + · · · + e2N = e2i ≡ SSR(b0 , b1 ) (3.5)
i=1
∑
N
2
(b0 , b1 ) = arg min [yi − (int + slope xi )] (3.6)
int,slope
i=1
10
8
y
6
4
2
0 5 10
library(ScPoEconometrics)
launchApp("reg_simple_arrows")
launchApp("reg_simple") # with squared errors
launchApp("SSR_cone") # visualize the minimzation problem from above!
Most of our apps have an associated about document, which gives extra infor-
mation and explanations. After you have looked at all three apps, we invite you
thus to have a look at the associated explainers by typing
aboutApp("reg_simple_arrows")
aboutApp("reg_simple")
aboutApp("SSR_cone")
cov(x, y)
b1 = , (3.7)
var(x)
i.e. the estimate of the slope coefficient is the covariance between x and y divided
74 CHAPTER 3. LINEAR REGRESSION
by the variance of x, both computed from our sample of data. With b1 in hand,
we can get the estimate for the intercept as
b0 = ȳ − b1 x̄. (3.8)
where z̄ denotes the sample mean of variable z. The interpretation of the OLS
slope coefficient b1 is as follows. Given a line as in y = b0 + b1 x,
dy
• b1 = dx measures the change in y resulting from a one unit change in x
• For example, if y is wage and x is years of education, b1 would measure
the effect of an additional year of education on wages.
There is an alternative representation for the OLS slope coefficient which relates
to the correlation coefficient r. Remember from section 2.3 that r = cov(x,y)
sx sy ,
where sz is the standard deviation of variable z. With this in hand, we can
derive the OLS slope coefficient as
cov(x, y)
b1 = (3.9)
var(x)
cov(x, y)
= (3.10)
sx sx
sy
=r (3.11)
sx
In other words, the slope coefficient is equal to the correlation coefficient r times
the ratio of standard deviations of y and x.
There are several important special cases for the linear regression introduced
above. Let’s start with the most obvious one: What is the meaning of running
a regression without any regressor, i.e. without a x? Our line becomes very
simple. Instead of (3.1), we get
y = b0 . (3.12)
This means that our minization problem in (3.6) also becomes very simple: We
only have to choose b0 ! We have
∑
N
2
b0 = arg min [yi − int] ,
int
i=1
3.2. ORDINARY LEAST SQUARES (OLS) ESTIMATOR 75
1 ∑
N
b0 = yi = y.
N i=1
We follow the same logic here, just that we miss another bit from our initial
equation and the minimisation problem in (3.6) now becomes:
∑
N
2
b1 = arg min [yi − slope xi ] (3.13)
slope
i=1
1
∑N
xi yi x̄ȳ
7→ b1 = N
1
∑N
i=1
= (3.14)
2 x2
N i=1 xi
In this case we only get to choose the slope b1 of this anchored line.1 You should
now try out both of those restrictions on our linear model by spending some
time with
launchApp("reg_constrained")
1
∑N
N i=1 x̃i ỹi
b1 = 1
∑N 2 (3.15)
N i=1 x̃i
∑ N
i=1 (xi − x̄)(yi −
1
N ȳ)
= ∑N (3.16)
i=1 (xi − x̄)
1 2
N
cov(x, y)
= (3.17)
var(x)
This last expression is identical to the one in (3.7)! It’s the standard OLS
estimate for the slope coefficient. We note the following:
To get a better feel for what is going on here, you can try this out now by
yourself by typing:
launchApp("demeaned_reg")
1
∑N
N i=1 x̆i y̆i
b1 = 1
∑N 2 (3.18)
N i=1 x̆i
1
∑N xi −x̄ yi −ȳ
N i=1 σx σy
=
1
∑N ( xi −x̄ )2 (3.19)
N i=1 σx
Cov(x, y)
= (3.20)
σx σy
= Corr(x, y) (3.21)
3.3. PREDICTIONS AND RESIDUALS 77
And also for this case we have a practical application for you. Just type this
and play around with the app for a little while!
launchApp("reg_standardized")
Now we want to ask how our residuals ei relate to the prediction ∑ yˆi . Let us first
N
think about the average of all predictions yˆi , i.e. the number N1 i=1 yˆi . Let’s
just take (3.2) and plug this into this average, so that we get
1 ∑ 1 ∑
N N
yˆi = b0 + b1 x i (3.22)
N i=1 N i=1
1 ∑
N
= b0 + b1 xi (3.23)
N i=1
= b0 + b1 x̄ (3.24)
(3.25)
But that last line is just equal to the formula for the OLS intercept (3.8), b0 =
ȳ − b1 x̄! That means of course that
1 ∑
N
yˆi = b0 + b1 x̄ = ȳ
N i=1
in other words:
The average of our predictions yˆi is identically equal to the mean of the
outcome y. This implies that the average of the residuals is equal to
zero.
Related to this result, we can show that the prediction ŷ and the residuals are
uncorrelated, something that is often called orthogonality between ŷi and ei .
We would write this as
78 CHAPTER 3. LINEAR REGRESSION
1 ∑ 1 ∑
N N
Cov(ŷ, e) = (ŷi − ȳ)(ei − ē) = (ŷi − ȳ)ei (3.26)
N i=1 N i=1
1 ∑ 1 ∑
N N
= ŷi ei − ȳ ei = 0 (3.27)
N i=1 N i=1
It’s useful to bring back the sample data which generate figure 3.1 at this point
in order to verify these claims:
#OUT> y y_hat error
#OUT> 1 2.09 2.57 -0.48
#OUT> 2 2.79 3.41 -0.62
#OUT> 3 6.49 4.25 2.24
#OUT> 4 1.71 5.10 -3.39
#OUT> 5 9.89 5.94 3.95
#OUT> 6 7.62 6.78 0.83
#OUT> 7 4.86 7.63 -2.77
#OUT> 8 7.38 8.47 -1.09
#OUT> 9 10.63 9.31 1.32
Let’s check that these claims are true in this sample of data. We want that
1. The average of ŷi to be the same as the mean of y
2. The average of the errors should be zero.
3. Prediction and errors should be uncorrelated.
# 1.
[Link](mean(ss$error), 0)
OLS (see just above), you can see that the regression line inherits this property
from the Covariance. A famous exercise by Francis Anscombe (1973) illustrates
this by constructing 4 different datasets which all have identical linear statis-
tics: mean, variance, correlation and regression line are identical. However, the
usefulness of the statistics to describe the relationship in the data is not clear.
dataset 1 dataset 2
12
12
y1
y2
8
8
6
6
4
4
5 10 15 5 10 15
x1 x2
dataset 3 dataset 4
12
12
y3
y4
8
8
6
6
4
5 10 15 5 10 15
x3 x4
Always visually inspect your data, and don’t rely exclusively on sum-
mary statistics like mean, variance, correlation and regression line. All
of those assume a linear relationship between the variables in your data.
30
25
y
20
15
10
20
15
10
30
25
y
20
15
10
yi = b0 + b1 xi + b2 x2i + ei (3.28)
This is a special case of multiple regression, which we will talk about in chapter
4. You can see that there are multiple slope coefficients. For now, let’s just see
how this performs:
We have seen above in 3.3 that the covariance between prediction ŷ and error e
is zero, that’s why we have Cov(ŷ, e) = 0 in (3.31). What this tells us in words
is that we can decompose the variance in the observed outcome y into a part
that relates to variance as explained by the model and a part that comes from
82 CHAPTER 3. LINEAR REGRESSION
unexplained variation. Finally, we know the definition of variance, and can thus
write down the respective formulae for each part:
∑N
• V ar(y) = N1 i=1 (yi − ȳ)2
∑N
• V ar(ŷ) = N1 i=1 (yˆi − ȳ)2 , because the mean of ŷ is ȳ as we know. Finally,
∑N
• V ar(e) = N1 i=1 e2i , because the mean of e is zero.
We can thus formulate how the total variation in outcome y is aportioned be-
tween model and unexplained variation:
It is easy to see that a good fit is one where the sum of explained squares (SSE)
is large relativ to the total variation (SST). In such a case, we observe an R2
close to one. In the opposite case, we will see an R2 close to zero. Notice that a
small R2 does not imply that the model is useless, just that it explains a small
fraction of the observed variation.
y = wage1$wage
if (log){
y = log(wage1$wage)
}
plot(y = y,
x = wage1$educ,
col = "red", pch = 21, bg = "grey",
cex=1.25, xaxt="n", frame = FALSE, # set default x-axis to none
main = ifelse(log,"log(Wages) vs. Education, 1976","Wages vs. Education, 1976"),
xlab = "years of education",
ylab = ifelse(log,"Log Hourly wages","Hourly wages"))
axis(side = 1, at = c(0,6,12,18)) # add custom ticks to x axis
if (rug) rug(wage1$wage, side=2, col="red") # add `rug` to y axis
}
Looking at the top panel of figure 3.5, you notice two things: From the red
ticks on the y axis, you see that wages are very concentrated at around 5 USD
per hour, with fewer and fewer observations at higher rates; and second, that
it seems that the hourly wage seems to increase with higher education levels.
The bottom panel reinforces the first point, showing that the estimated pdf
(probability density function) shown as a black line has a very long right tail:
there are always fewer and fewer, but always larger and larger values of hourly
wage in the data.
You have seen this shape of a distribution in the tutorial for chapter 2
already! Do you remember the name of this particular shape of a distribu-
tion? (why not type ScPoEconometrics::runTutorial('chapter2'))
to check?
10
5
0
0 6 12 18
years of education
0.10
0.00
0 5 10 15 20 25
hourly wage
15
10
5
0
0 6 12 18
years of education
Figure 3.6: Wages vs Education from the wooldridge dataset wage1, with re-
gression
and we can add the resulting regression line to our above plot:
plotfun(wage1)
abline(hourly_wage, col = 'black', lw = 2) # add regression line
The hourly_wage object contains the results of this estimation. We can get a
summary of those results with the summary method:
summary(hourly_wage)
#OUT>
#OUT> Call:
#OUT> lm(formula = wage ~ educ, data = wage1)
#OUT>
#OUT> Residuals:
#OUT> Min 1Q Median 3Q Max
#OUT> -5.3396 -2.1501 -0.9674 1.1921 16.6085
#OUT>
#OUT> Coefficients:
#OUT> Estimate Std. Error t value Pr(>|t|)
#OUT> (Intercept) -0.90485 0.68497 -1.321 0.187
86 CHAPTER 3. LINEAR REGRESSION
Regression estimates (b0 , b1 ) are in the scale of the data. The actual
value of the estimates will vary, if we change the scale of the data. The
overall fit of the model to the data would not change, however, so that
the R2 statistic would be constant.
Suppose we wanted to use the above estimates to report the effect of years of
education on annual wages instead of hourly ones. Let’s assume we have full-
time workers, 7h per day, 5 days per week, 45 weeks per year. Calling this factor
δ = 7 × 5 × 45 = 1575, we have that x dollars per hour imply x × δ = x × 1575
dollars per year.
What would be the effect of using ỹ = wage × 1575 instead of y = wage as
outcome variable on our regression coefficients b0 and b1 ? Well, let’s try!
% Table created by stargazer v.5.2.2 by Marek Hlavac, Harvard University. E-
mail: hlavac at [Link] % Date and time: Thu, Oct 10, 2019 - 11:40:26
Let’s call the coefficients in the column labelled (1) as b0 and b1 , and let’s call the
ones in column (2) b∗0 and b∗1 . In column (1) we see that another year increaeses
hourly wage by 0.54 dollars, as before. In column (2), the corresponding number
is 852.64, i.e. another year of education will increase annual wages by 852.64
dollars, on average. Notice however, that b0 × δ = −0.9 × 1575 = −1425.14 = b∗0
and that b1 × δ = 0.54 × 1575 = 852.64 = b∗1 , that is we just had to multiply
both coefficients by the scaling factor applied to original outcome y to obtain
our new coefficients b∗0 and b∗1 ! Also, observe that the R2 s of both regressions
3.8. SCALING REGRESSIONS 87
Dependent variable:
wage annual_wage
(1) (2)
educ 0.541∗∗∗ 852.641∗∗∗
(0.053) (83.866)
are identical! So, really, we did not have to run the regression in column (2) at
all to make this change: multiplying all coefficients through by δ is enough in
this case. We keep the identically same fit to the data.
Rescaling the regressors x is slightly different, but it’s easy to work out how
different, given the linear nature of the covariance operator, which is part of the
OLS estimator. Suppose we rescale x by the number c. Then, using the OLS
formula in (3.7), we see that we get new slope coefficient b∗1 via
Cov(cx, y)
b∗1 = (3.34)
V ar(cx)
cCov(x, y)
= 2 (3.35)
c V ar(x)
1
= b1 . (3.36)
c
88 CHAPTER 3. LINEAR REGRESSION
1 ∑
N
b∗0 = ȳ − b∗1 c · xi (3.37)
N i=1
c ∑
N
= ȳ − b∗1 xi (3.38)
N i=1
1
= ȳ − b1 c ∗ x̄ (3.39)
c
= ȳ − b1 ∗ x̄ (3.40)
= b0 (3.41)
That is, we change the slope by the inverse of the scaling factor applied to
regressor x, but the intercept is unaffected from this. You should play around
for a while with our rescaling app to get a feeling for this:
library(ScPoEconometrics)
launchApp('Rescale')
Let’s use the update function to modify our previous regression model:
log_hourly_wage = update(hourly_wage, log(wage) ~ ., data = wage1)
3.9. A PARTICULAR RESCALING: THE LOG TRANSFORM 89
The update function takes an existing lm object, like hourly_wage here, and
updates the formula. Here the . on the right hand side means leave unchanged
(so the RHS stays unchanged). How do our pictures change?
par(mfrow = c(1,2))
plotfun(wage1,rug = FALSE)
abline(hourly_wage, col = 'black', lw = 2) # add regression line
3
20
2
15
1
10
5
0
0
0 6 12 18 0 6 12 18
par(mfrow = c(1,1))
It looks as if the regression line has the same slope, but beware of the different
scales of the y-axis! You can clearly see that all y-values have been compressed
by the log transformation. The log case behaves differently from our scaling by
a constant number case above because it is a nonlinear function. Let’s compare
the output between both models:
The interpretation of the transformed model in column (2) is now the following:
90 CHAPTER 3. LINEAR REGRESSION
Dependent variable:
wage NA
(1) (2)
educ 0.541∗∗∗ 0.083∗∗∗
(0.053) (0.008)
Notice that the R2 slightly improved, so have a better fit to the data. This is
due the fact that the log compressed large outlier values. Whether we apply
the log to left or right-hand side variables makes a difference, as outlined in this
important table:
Common Regression Specifications
You may remember from your introductory micro course what the definition
of the elasticity of y with respect to x is: This number tells us by how many
percent y will change, if we change x by one percent. Let’s look at another
example from the wooldridge package of datasets, this time concerning CEO
salaries and their relationship with company sales.
data("ceosal1", package = "wooldridge")
par(mfrow = c(1,2))
plot(salary ~ sales, data = ceosal1, main = "Sales vs Salaries",xaxt = "n",frame = FALS
axis(1, at = c(0,40000, 80000))
rug(ceosal1$salary,side = 2)
rug(ceosal1$sales,side = 1)
3.9. A PARTICULAR RESCALING: THE LOG TRANSFORM 91
9
10000
log(salary)
8
salary
5000
7
6
0
0 40000 80000 5 6 7 8 9 10
sales log(sales)
Multiple Regression
We can extend the discussion from chapter 3 to more than one explanatory
variable. For example, suppose that instead of only x we now had x1 and x2
in order to explain y. Everything we’ve learned for the single variable case
applies here as well. Instead of a regression line, we now get a regression plane,
i.e. an object representable in 3 dimenions: (x1 , x2 , y). As an example, suppose
we wanted to explain how many miles per gallon (mpg) a car can travel as a
function of its horse power (hp) and its weight (wt). In other words we want to
estimate the equation
#OUT> mpg hp wt
#OUT> Mazda RX4 21.0 110 2.620
#OUT> Mazda RX4 Wag 21.0 110 2.875
#OUT> Datsun 710 22.8 93 2.320
#OUT> Hornet 4 Drive 21.4 110 3.215
#OUT> Hornet Sportabout 18.7 175 3.440
#OUT> Valiant 18.1 105 3.460
How do you think hp and wt will influence how many miles per gallon of gasoline
each of those cars can travel? In other words, what do you expect the signs of
b1 and b2 to be?
With two explanatory variables as here, it is still possible to visualize the re-
gression plane, so let’s start with this as an answer. The OLS regression plane
through this dataset looks like in figure 4.1:
93
94 CHAPTER 4. MULTIPLE REGRESSION
WebGL is not
supported by
data
your browser -
visit
[Link]
for more info
Figure 4.1: Multiple Regression - a plane in 3D. The red lines indicate the
residual for each observation.
This visualization shows a couple of things: the data are shown with red points
and the grey plane is the one resulting from OLS estimation of equation (4.1).
You should realize that this is exactly the same story as told in figure 3.1 - just
in three dimensions!
Furthermore, multiple regression refers the fact that there could be more than
two regressors. In fact, you could in principle have K regressors, and our theory
developed so far would still be valid:
a plane that is downward sloping in both the weight and horse power directions.
Suppose now we wanted to know impact of hp on mpg in isolation, so as if we
could ask
Keeping the value of wt fixed for a certain car, what would be the impact
on mpg be if we were to increase only its hp? Put differently, keeping
all else equal, what’s the impact of changing hp on mpg?
We ask this kind of question all the time in econometrics. In figure 4.1 you clearly
see that both explanatory variables have a negative impact on the outcome of
interest: as one increases either the horse power or the weight of a car, one finds
that miles per gallon decreases. What is kind of hard to read off is how negative
an impact each variable has in isolation.
As a matter of fact, the kind of question asked here is so common that it has
got its own name: we’d say “ceteris paribus, what is the impact of hp on mpg?”.
ceteris paribus is latin and means the others equal, i.e. all other variables fixed.
In terms of our model in (4.1), we want to know the following quantity:
∂mpgi
= b1 (4.4)
∂hpi
The ∂ sign denotes a partial derivative of the function describing mpg with
respect to the variable hp. It measures how the value of mpg changes, as we
change the value of hp ever so slightly. In our context, this means: keeping
all other variables fixed, what is the effect of hp on mpg?. We call the value
of coefficient b1 therefore also the partial effect of hp on mpg. In terms of our
dataset, we use R to run the following multiple regression:
#OUT>
#OUT> Call:
#OUT> lm(formula = mpg ~ wt + hp, data = mtcars)
#OUT>
#OUT> Residuals:
#OUT> Min 1Q Median 3Q Max
#OUT> -3.941 -1.600 -0.182 1.050 5.854
#OUT>
#OUT> Coefficients:
#OUT> Estimate Std. Error t value Pr(>|t|)
#OUT> (Intercept) 37.22727 1.59879 23.285 < 2e-16 ***
#OUT> wt -3.87783 0.63273 -6.129 1.12e-06 ***
#OUT> hp -0.03177 0.00903 -3.519 0.00145 **
#OUT> ---
#OUT> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#OUT>
#OUT> Residual standard error: 2.593 on 29 degrees of freedom
96 CHAPTER 4. MULTIPLE REGRESSION
4.2 Multicolinearity
One important requirement for multiple regression is that the data be not lin-
early dependent: Each variable should provide at least some new information
for the outcome, and it cannot be replicated as a linear combination of other
variables. Suppose that in the example above, we had a variable wtplus defined
as wt + 1, and we included this new variable together with wt in our regression.
In this case, wtplus provides no new information. It’s enough to know wt, and
add 1 to it. In this sense, wt_plus is a redundant variable and should not be
included in the model. Notice that this holds only for linearly dependent vari-
ables - nonlinear transformations (like for example wt2 ) are exempt from this
rule. Here is why:
y = b0 + b1 wt + b2 wtplus + e (4.5)
= b0 + b1 wt + b2 (wt + 1) + e (4.6)
= (b0 + b2 ) + wt(b1 + b2 ) + e (4.7)
This shows that we cannot identify the regression coefficients in case of linearly
dependent data. Variation in the variable wt identifies a different coefficient,
say γ = b1 + b2 , from what we actually wanted: separate estimates for b1 , b2 .
WebGL is not
supported by
data
your browser -
visit
[Link]
for more info
Figure 4.2: Log wages vs education and experience in 3D.
Let’s go back to our previous example of the relationship between log wages and
education. How does this relationship change if we also think that experience in
the labor market has an impact, next to years of education? Here is a picture:
Let’s add even more variables! For instance, what’s the impact of experience in
the labor market, and time spent with the current employer? Let’s first look at
how those variables co-vary with each other:
cmat = round(cor(subset(wage1,select = c(lwage,educ,exper,tenure))),2) # correlation matrix
corrplot::corrplot(cmat,type = "upper",method = "ellipse")
98 CHAPTER 4. MULTIPLE REGRESSION
tenure
lwage
exper
educ
1
0.8
lwage
0.6
0.4
educ
0.2
−0.2
exper
−0.4
−0.6
tenure
−0.8
−1
The way to read the so-called correlation plot in figure ?? is straightforward:
each row illustrates the correlation of a certain variable with the other variables.
In this example both the shape of the ellipse in each cell as well as their color
coding tell us how strongly two variables correlate. Let us put this into a
regression model now:
educ_only <- lm(lwage ~ educ , data = wage1)
educ_exper <- lm(lwage ~ educ + exper , data = wage1)
log_wages <- lm(lwage ~ educ + exper + tenure, data = wage1)
stargazer::stargazer(educ_only, educ_exper, log_wages,type = if (knitr:::is_latex_outpu
Table 4.1:
Dependent variable:
lwage
(1) (2) (3)
educ 0.083∗∗∗ 0.098∗∗∗ 0.092∗∗∗
(0.008) (0.008) (0.007)
tenure 0.022∗∗∗
(0.003)
from left to right across the columns. Given the correlation structure shown
in figure ??, it is only natural that this is happening: We see that educ and
exper are negatively correlated, for example. So, if we omit exper from the
model in column (1), educ will reflect part of this correlation with exper by a
lower estimated value. By directly controlling for exper in column (2) we get
an estimate of the effect of educ net of whatever effect exper has in isolation
on the outcome variable. We will come back to this point later on.
but R has a more complete prediction interface, using the function predict. For
starters, you can predict the model on all data points which were contained in
the dataset we used for estimation, i.e. wage1 in our case:
head(predict(log_wages)) # first 6 observations of wage1 as predicted by our model
#OUT> 1 2 3 4 5 6
#OUT> 1.304921 1.523506 1.304921 1.819802 1.461690 1.970451
Often you want to add that prediction to the original dataset:
wage_prediction = cbind(wage1, prediction = predict(log_wages))
head(wage_prediction[, c("lwage","educ","exper","tenure","prediction")])
Categorial Variables
{
1 if i is male
[Link] = ,
0 if i is not male.
103
104 CHAPTER 5. CATEGORIAL VARIABLES
{
1 if i is female
[Link] =
0 if i is not female.
yi = b0 + b1 [Link] + b2 [Link] + ei
would be invalid because of perfect colinearity between [Link] and [Link] .
The solution to this is pragmatic and simple:
Now let’s try this out. We start by creating the female indicator as above,
{
1 if i is female
[Link] =
0 if i is not female.
and let’s suppose that yi is a measure of i’s annual labor income. Our model is
yi = b0 + b1 [Link] + ei (5.1)
#OUT>
#OUT> Call:
#OUT> lm(formula = y ~ [Link], data = dta)
1 There are transgender individuals where this example will not apply.
5.1. THE BINARY REGRESSOR CASE 105
#OUT>
#OUT> Residuals:
#OUT> Min 1Q Median 3Q Max
#OUT> -2.3901 -0.6565 0.1612 0.6846 2.7850
#OUT>
#OUT> Coefficients:
#OUT> Estimate Std. Error t value Pr(>|t|)
#OUT> (Intercept) 2.0847 0.2396 8.700 1.97e-11 ***
#OUT> is.female1 -3.0631 0.3202 -9.566 1.06e-12 ***
#OUT> ---
#OUT> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#OUT>
#OUT> Residual standard error: 1.124 on 48 degrees of freedom
#OUT> Multiple R-squared: 0.6559, Adjusted R-squared: 0.6488
#OUT> F-statistic: 91.5 on 1 and 48 DF, p-value: 1.061e-12
Notice that R displays the level of the factor to which coefficient b1 belongs here,
i.e. is.female1 means this coefficient is on level [Link] = 1 - the reference
level is [Link] = 0, and it has no separate coefficient. Also interesting is
that b1 is equal to the difference in conditional means between male and female
A dummy variable measures the difference or the offset in the mean of the
response variable, E[y], conditional on x belonging to some category -
relative to a baseline category. In our artificial example, the coefficient
b1 informs us that women earn on average 3.756 units less than men.
In figure 5.1 we see that this regression simplifies to the straight line connecting
the mean, or the expected value of y when [Link] = 0, i.e. E[y|[Link] =
0], to the mean when [Link] = 1, i.e. E[y|[Link] = 1]. It is useful to
remember that the unconditional mean of y, i.e. E[y], is going to be the result
of regressing y only on an intercept, illustrated by the blue line. This line will
always lie in between both conditional means. As indicated by the red arrow,
the estimate of the coefficient on the dummy, b1 , is equal to the difference in
conditional means for both groups. You should look at our app now to deepen
your understanding of what’s going on here:
library(ScPoEconometrics)
launchApp("reg_dummy")
106 CHAPTER 5. CATEGORIAL VARIABLES
4
E[Y | [Link] = 0]
2 b1= −3.06
y
E[Y | [Link] = 1]
−2
−1 0 1 2
Figure 5.1: regressing y ∈ R on [Link] ∈ {0, 1}. The blue line is E[y], the
red arrow is the size of b1 . Which is the same as the slope of the regression line
in this case and the difference in conditional means!
What happens if there are more predictors than just the dummy variable in a
regression? For example, what if instead we had
where experi would measure years of experience in the labor market? As above,
the dummy variable acts as an intercept shifter. We have
{
b0 + b1 + b2 × experi + ei if [Link]=1
yi = (5.3)
b0 + + b2 × experi + ei if [Link]=0
so that the intercept is b0 + b1 for women but b0 for men. We will see this in
the real-world example below, but for now let’s see the effect of switching the
dummy on and off in this app:
library(ScPoEconometrics)
launchApp("reg_dummy_example")
5.3. CATEGORICAL VARIABLES IN R: FACTOR 107
R has extensive support for categorical variables built-in. The relevant data
type representing a categorical variable is called factor. We encountered them
as basic data types in section 1.8 already, but it is worth repeating this here.
We have seen that a factor categorizes a usually small number of numeric values
by labels, as in this example which is similar to what I used to create regressor
[Link] for the above regression:
[Link] = factor(x = c(0,1,1,0), labels = c(FALSE,TRUE))
[Link]
We can go beyond binary categorical variables such as TRUE vs FALSE. For exam-
ple, suppose that x measures educational attainment, i.e. it is now something
like xi ∈ {high school,some college,BA,MSc}. In R parlance, high school, some
college, BA, MSc are the levels of factor x. A straightforward extension of the
above would dictate to create one dummy variable for each category (or level),
like
but you can see that this is cumbersome. There is a better solution for us
available:
108 CHAPTER 5. CATEGORIAL VARIABLES
Notice here that R will apply the labels in increasing order the way you supplied
it (i.e. a numerical value 4 will correspond to “MSc”, no matter the ordering in
x.)
The above developed factor terminology fits neatly into R’s linear model fitting
framework. Let us illustrate the simplest use by way of example.
Going back to our wage example, let’s say that a worker’s wage depends on
their education as well as their sex:
We know the results from column (1) very well by now. How does the relation-
ship change if we include the female indicator? Remember from above that
female is a factor with two levels, 0 and 1, where 1 means that’s a female.
We see in the above output that R included a regressor called female1. This is
a combination of the variable name female and the level which was included
in the regression. In other words, R chooses a reference category (by default
the first of all levels by order of appearance), which is excluded - here this is
female==0. The interpretation is that b2 measures the effect of being female rel-
ative to being male. R automatically creates a dummy variable for each potential
level, excluding the first category.
Figure 5.2 illustrates this. The left panel is our previous model. The right panel
adds the female dummy. You can see that both male and female have the same
upward sloping regression line. But you can also see that there is a parallel
downward shift from male to female line. The estimate of b2 = −0.36 is the size
of the downward shift.
5.3. CATEGORICAL VARIABLES IN R: FACTOR 109
Table 5.1:
Dependent variable:
lwage
(1) (2)
∗∗∗
educ 0.083 0.077∗∗∗
(0.008) (0.007)
female1 −0.361∗∗∗
(0.039)
3 3
2 2
female
lwage
0
1 1 1
0 0
0 5 10 15 0 5 10 15
educ educ
Figure 5.2: log wage vs educ. Right panel with female dummy.
110 CHAPTER 5. CATEGORIAL VARIABLES
5.4 Interactions
In that model, the partial effect of area on log(price), keeping all other vari-
ables fixed, is
∂ log(price)
= b1 + b3 (age) (5.6)
∂area
If we find that b3 > 0 in a regression, we conclude that the size of a house values
more in older houses. We call b3 the interaction effect between area and age.
Let’s look at that regression model now.
data(hprice3, package = "wooldridge")
summary(lm(lprice ~ area*age, data = hprice3))
#OUT>
#OUT> Call:
#OUT> lm(formula = lprice ~ area * age, data = hprice3)
#OUT>
#OUT> Residuals:
#OUT> Min 1Q Median 3Q Max
#OUT> -1.27226 -0.16538 -0.00298 0.20673 0.83985
#OUT>
#OUT> Coefficients:
#OUT> Estimate Std. Error t value Pr(>|t|)
#OUT> (Intercept) 1.071e+01 6.633e-02 161.448 < 2e-16 ***
#OUT> area 3.647e-04 2.875e-05 12.686 < 2e-16 ***
#OUT> age -7.377e-03 1.358e-03 -5.434 1.1e-07 ***
#OUT> area:age 9.168e-07 4.898e-07 1.872 0.0622 .
#OUT> ---
#OUT> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#OUT>
#OUT> Residual standard error: 0.2925 on 317 degrees of freedom
#OUT> Multiple R-squared: 0.5586, Adjusted R-squared: 0.5545
#OUT> F-statistic: 133.7 on 3 and 317 DF, p-value: < 2.2e-16
In this instance, we see that indeed there is a small positive interaction between
area and age on the sales price: even though age in isolation decreases the sales
value, bigger houses command a small premium if they are older.
5.4. INTERACTIONS 111
It is straightforward to extend the interactions logic to allow not only for differ-
ent intercepts, but also different slopes for each subgroup in a dataset. Let’s go
back to our dataset of wages from section 5.3.2 above. Now that we know how
to create and interaction between two variables, we can easily modify equation
(5.4) like this:
The only peculiarity here is that female is a factor with levels 0 and 1: i.e. the
interaction term b3 will be zero for all men. Similarly to above, we can test
whether there are indeed different returns to education or men and women by
looking at the estimated value b3 :
lm_w_interact <- lm(lwage ~ educ * female , data = wage1) # R expands to full interactions model
summary(lm_w_interact)
#OUT>
#OUT> Call:
#OUT> lm(formula = lwage ~ educ * female, data = wage1)
#OUT>
#OUT> Residuals:
#OUT> Min 1Q Median 3Q Max
#OUT> -2.02673 -0.27468 -0.03721 0.26221 1.34740
#OUT>
#OUT> Coefficients:
#OUT> Estimate Std. Error t value Pr(>|t|)
#OUT> (Intercept) 8.260e-01 1.181e-01 6.997 8.08e-12 ***
#OUT> educ 7.723e-02 8.988e-03 8.593 < 2e-16 ***
#OUT> female1 -3.601e-01 1.854e-01 -1.942 0.0527 .
#OUT> educ:female1 -6.408e-05 1.450e-02 -0.004 0.9965
#OUT> ---
#OUT> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#OUT>
#OUT> Residual standard error: 0.4459 on 522 degrees of freedom
#OUT> Multiple R-squared: 0.3002, Adjusted R-squared: 0.2962
#OUT> F-statistic: 74.65 on 3 and 522 DF, p-value: < 2.2e-16
We will in the next chapter learn that the estimate for b3 on the interaction
educ:female1 is difficult for us to distinguish from zero in a statistical sense;
Hence for now we conclude that there are no significantly different returns in
education for men and women in this data. This is easy to verify visually in
this plot, where we are unable to detect a difference in slopes in the right panel.
112 CHAPTER 5. CATEGORIAL VARIABLES
3 3
2 2
log wage
1 1
0 0
0 5 10 15 0 5 10 15
educ educ
Figure 5.3: log wage vs educ. Right panel allows slopes to be different - turns
out they are not!
Finally, dummary variables are sometimes very important to account for spuri-
ous relationships in that data. Consider the following (artificial example):
1. Suppose we collected data on hourly wage data together with a the number
of hours worked for a set of individuals.
2. We plot want to investigate labour supply behaviour of those individuals,
hence we run regression hours_worked ~ wage.
3. We expect to get a positive coefficient on wage: the higher the wage, the
more hours worked.
4. You know that individuals are members of either group g=0 or g=1.
5.5. (UNOBSERVED) INDIVIDUAL HETEROGENEITY 113
10
5
hours
−5
10
10
5
5 g
hours
hours
1
2
0
0
−5 −5
0.0 0.5 1.0 1.5 2.0 0.0 0.5 1.0 1.5 2.0
wage wage
Figure 5.4: Left and right panel exhibit the same data. The right panel controls
for group composition.
Chapter 6
Standard Errors
6.1 Sampling
115
116 CHAPTER 6. STANDARD ERRORS
2
log hourly wage
10 15
years of education
Figure 6.2: A glass jar filled with Fusilli pasta in three different colors.
6.1. SAMPLING 117
Figure 6.3: Manually separating Fusilli by their color is very costly in terms of
effort and cost.
Additionally, you may draw worried looks from the people around you, while
you are doing it. Maybe this is not the right way to approach this task?2
We started by randomly grabbing a handful of Fusilli from the jar and by letting
drop exactly N = 20 into a paper coffee cup, pictured in 6.5. We call N the
sample size. The count and corresponding proportions of each color in this first
sample are shown in the following table:
So far, so good. We have our first estimate of the population proportion of green
Fusilli in the overall population: 0.25. Notice that taking a sample of N = 20
was much quicker and much less painful than performing the full count (i.e. the
census) of Fusilli performed above.
Then, we put my sample back into the jar, and we reshuffled the Fusilli. Had
we taken another sample, again of N = 20, would we again have gotten 7
Red, 5 Green, and 8 White, just as in the first sample? Maybe, but maybe not.
Suppose we had carried on for several times drawing samples of 20 and counting
the colors: Would we also have observed 5 green Fusilli? Definitely not. We
would have noted some degree of variability in the proportions computed from
our samples. The sample proportions in this case are an example of a sample
statistic.
Figure 6.6: Taking eleven samples of 20 Fusilli each from the jar, and plotting
the histogram of obtained sample proportions of Green Fusilli.
#OUT> 2 2 2 0.25
#OUT> 3 3 3 0.25
#OUT> 4 4 4 0.30
#OUT> 5 5 5 0.15
#OUT> 6 6 6 0.30
#OUT> 7 7 7 0.25
#OUT> 8 8 8 0.25
#OUT> 9 9 9 0.20
#OUT> 10 10 10 0.25
#OUT> 11 11 11 0.20
This produces an associated histogram which looks very much like the one we
draws onto the board:
3
2
1
0
6.2.1 Recap
Figure 6.7: The Moderndive package used red and white balls instead of fusilli
pasta.
upon drawing a new sample of 20 Fusilli, we should expect to see the same out-
come - and we concluded: maybe, but maybe not. In short, we discovered some
random variation from sample to sample. We called this sampling variation.
The purpose of this little activity was three-fold:
1. To understand that random samples differ and that there is sampling
variation.
2. To understand that bigger samples will yield smaller sampling variation.
3. To illustrate that the sampling distribution of any statistic (i.e. not only
the sample proportion as in our case) computed from a random sample
converges to a normal distribution as the sample size increases.
The value of this exercise consisted in making you perform the sampling
activity yourself. We will now hand over to the brilliant moderndive
package, which will further develop this chapter.
The sampling activity in moderndive was performed with red and white balls
instead of green fusilli pasta. The rest is identical. We will now read sections
7.2 and 7.3 in their book, and return to this page later.
Imagine we were tasked by the Director of our school to provide him with
our best guess of the mean body height µ amongst all SciencesPo students in
order to assess which height the new desks should have. Of course, we are
122 CHAPTER 6. STANDARD ERRORS
Histogram of height
10
8
Frequency
6
4
2
0
height
Figure 6.8: Our ficitious sample of SciencesPo students’ body height. The small
ticks indicate the location of each measurement.
You may want to ask: Why bother with this estimation business at all, and
not just measure all students’ height, compute µ, and that’s it? That’s a good
question! In most cases, we cannot do this, either because we do not have access
to the entire population (think of computing the mean height of all Europeans!),
or it’s too costly to measure everyone, or it’s impractical. That’s why we take
samples from the wider population, to make inference. In our example, suppose
we’d randomly measure students coming out of the SciencesPo building at 27
Rue Saint Guillaume until we have 50 measurements on any given Monday.
Suppose further that we found a sample mean height x̄ = 168.5, and that the
sample standard deviation was s = 10. In short, we found the data summarized
in figure 6.8
What are we going to tell Monsieur le Directeur now, with those two numbers
and figure 6.8 in hand? Before we address this issue, we need to make a short
detour into test statistics.
6.4. INFERENCE IN THEORY 123
We have encountered many statistics already: think of the sample mean, or the
standard deviation. Statistics are just functions of data. Test statistics are used
to perform statistical tests.
Many test statistics rely on some notion of standardizing the sample data so
that it becomes comparable to a theoretical distribution. We encountered this
idea already in section 3.2.4, where we talked about a standardized regression.
The most common standardization is the so-called z-score, which says that
x−µ
≡ z ∼ N (0, 1), (6.1)
σ
in other words, substracting the population mean from random variable x and
dividing by it’s population standard deviation yields a standard normally dis-
tributed random variable, commonly called z.
A very similar idea applies if we don’t know the population variance (which is
our case here!). The corresponding standardization gives rise to the t-statistic,
and it looks very similar to (6.1):
√ x̄ − µ
n ≡ T ∼ tn−1 (6.2)
s
Back to our example now! We are clearly in need of some measure of confidence
about our sample statistic x̄ = 168.5 before we communicate our result. It seems
reasonable to inform the Director about x̄, but surely we also need to tell him
that there was considerable dispersion in the data: Some people were as short
as 143.98cm, while others were as tall as 189.41cm!
The way to proceed is to construct a confidence interval about the true popula-
tion mean µ, based on x̄, which will take this uncertainty into account. We will
use the t statistic from above. We want to have a symmetric interval around
124 CHAPTER 6. STANDARD ERRORS
tn−1
α
= 0.025 1 − α = 0.95 α
= 0.025
2 2
T
0 −c 0 c
Figure 6.9: Confidence Interval Construction. The blue area is called *coverage
region* which contains the true µ with probability 1 − α.
x̄ which contains the true value µ with probability 1 − α. One very popular
choice of α is 0.05, hence we cover µ with 95% probability. After computing
our statistic T as defind in (6.2), this interval is defined as follows:
Pr (−c ≤ T ≤ c) = 1 − α (6.3)
where c stands for critical value, which we need to choose. This is illustrated in
figure 6.9.
Given the symmetry of the t distribution it’s enough to find c at the upper tail:
the point above which α2 of all probability mass of the tdf distribution comes to
lie. In other words, if Tdf is the CDF of the t distribution with df degrees of
freedom, we find c as
α
Tdf (c) ≡ Pr (T < c) = 1 − = 0.975 (6.4)
2
c =Tdf−1 (Tdf (c)) = Tdf−1 (0.975) (6.5)
Here Tdf−1 stands for the quantile function, i.e. the inverse of the CDF. In our ex-
ample with df = 49, you can find thus that c = 2.01 by typing qt(0.975,df=49)
into your R session.3 Now we only have to expand the definition of the T statistic
from (6.2) inside (6.3) to obtain
3 You often will see c = 1.96, which comes from the fact that one relies on the t distribution
Finally, filling in our numbers for s etc, this implies that a 95% confidence
interval about the location of the true average height of all SciencesPo students,
µ, is given by:
We would tell the director that with 95% probability, the true average height of
all students comes to lie within those two bounds.
Now know by now how the standard errors of an OLS estimate are computed,
and what they stand for. We can now briefly4 discuss a very common usage
of this information, in relation to which variables we should include in our
regression. There is a statistical proceedure called hypothesis testing which
helps us to make such decisions. In hypothesis testing, we have a baseline, or
null hypothesis H0 , which we want to confront with a competing alternative
hypthesis H1 . Continuing with our example of the mean height of SciencesPo
students (µ), one potential hypothesis could be
H0 :µ = 167 (6.10)
H1 :µ ̸= 167 (6.11)
Here we state that under the null hypthesis, µ = 167, and under the alternative,
it’s not equal to that value. This would be called a two-sided test, because it
tests deviations from H0 below as well as above. An alternative formulation
could use the one-sided test that
H0 :µ = 167 (6.12)
H1 :µ > 167. (6.13)
4 We will not go into great detail here. Please refer back to your statistics course from last
spring semester (chapters 8 and 9), or the short note I wrote while ago
126 CHAPTER 6. STANDARD ERRORS
which would mean: under the null hypothesis, the average of all ScPo students’
body height is 167cm. Under the alternative, it is larger. You can immediately
see that this is very similar to confidence interval construction.
Suppose as above that we found x̄ = 168.5, and that the sample standard
deviation is still s = 10. Would you regard this as strong or weak evidence
against H0 and in favor of H1 ?
You should now remember what you saw when you did launchApp("estimate").
Look again at this app and set the slider to a sample size of 50, just as in our
running example. You can see that the app draws one hundred (100) samples
for you, locates their sample mean on the x-axis, and estimates the red density.
The crucial thing to note here is that, given we are working with a ran-
dom sample from a population with a certain distribution of height, our
sample statistic x̄ is also a random variable. Every new set of ran-
domly drawn students would yield a different x̄, and all of them together
would follow the red density in the app. In reality we often only get to
draw one single sample, and we can use knowledge about the sampling
distribution to make inference.
Our task is now to decide if given that particular sampling distribution, given
our estimate x̄ and given an observed sample variance s2 , whether x̄ = 168.5
is far away from x̄ = 167, or not. The way to proceed is by computing a test
statistic, which is to be compared to a critical value: if the test statistic exceeds
that value, we reject H0 , otherwise we cannot. The critical value depends on
the sampling distribution, and the size of the test. We talk about this next.
There are two types of error one can make when deploying such a test:
1. We might reject H0 , when in fact it is true! Here, upon observing x̄ = 168.5
we might conclude that indeed µ > 167 and thus we’d reject. But we might
have gotten unlucky and by chance have obtained an unusually tall sample
of students. This is called type one error.
2. We might fail to reject H0 when in fact H1 is true. This is called the type
two error.
We design a test with a certain probability of type one error α in mind. In
other words, we choose with which probability α we are willing to make a type
one error. (Notice that the best tests also avoid making type two errors! The
number 1 − Pr(type 2 error) is called power, hence we prefer tests with high
power). A typical choice for α is 0.05, i.e. we are willing to make a type one
error with probability 5%. α is commonly called the level of significance or
the size of a test.
6.4. INFERENCE IN THEORY 127
f (x̄|µ = 167)
1 − α = 0.95
α = 0.05
x̄
0 µ = 167 x̄c = 171.74
√
t= n x̄−µ
s
0 tc = 1.676
You can see from this that whether or not our test statistic is far way from the
critical value, or just below does not change our decision: it’s either accept or
reject. We never know if we narrowly rejected a H0 , or not. P-values are an
improvement over this stark dichotomy. The p-value is defined as the particular
level of significance α∗ , up to which all H0 ’s would be rejected. If this is a
very small number, we have overwhelming support to reject the null. If, on the
contrary, α∗ turns out to be rather large, we only found weak evidence against
H0 .
We define the p-value as the sum of rejection areas for a given test statistic T ∗ .
Notice that the symmetry of the t distribution implies that we would multiply
by two each of the two tail probabilities in the case of a two-sided test.
In the previous chapters we have seen how the OLS method can produce es-
timates about intercept and slope coefficients from data. You have seen this
method at work in R by using the lm function as well. It is now time to in-
troduce the notion that given that b0 , b1 and b2 are estimates of some unkown
population parameters, there is some degree of uncertainty about their values.
An other way to say this is that we want some indication about the precision of
those estimates. The underlying issue that the data we have at hand are usually
samples from a larger population.
A statistical model is simply a set of assumptions about how some data have
been generated. As such, it models the data-generating process (DGP), as we
have it in mind. Once we define a DGP, we could simulate data from it and
see how this compares to the data we observe in the real world. Or, we could
change the parameters of the DGP so as to understand how the real world data
would change, could we (or some policy) change the corresponding parameters
in reality. Let us now consider one particular statistical model, which in fact
we have seen so many times already.
6.7. THE CLASSICAL REGRESSION MODEL (CRM) 129
yi = β0 + β1 xi + εi (6.15)
The smallest set of assumptions used to define the classical regression model as
in (6.15) are the following:
1. The data are not linearly dependent: Each variable provides new infor-
mation for the outcome, and it cannot be replicated as a linear combina-
tion of other variables. We have seen this in section 4.2. In the particular
case of one regressor, as here, we require that x exhibit some variation in
the data, i.e. V ar(x) ̸= 0.
2. The mean of the residuals conditional on x should be zero, E[ε|x] = 0.
Notice that this also means that Cov(ε, x) = 0, i.e. that the errors and
our explanatory variable(s) should be uncorrelated. It is said that x should
be strictly exogenous to the model.
These assumptions are necessary to successfully (and correctly!) run an OLS
regression. They are often supplemented with an additional set of assumptions,
which help with certain aspects of the exposition, but are not strictly necessary:
3. The data are drawn from a random sample of size n: observation (xi , yi )
comes from the exact same distribution, and is independent of observation
(xj , yj ), for all i ̸= j.
4. The variance of the error term ε is the same for each value of x: V ar(ε|x) =
σ 2 . This property is called homoskedasticity.
5. The error is normally distributed, i.e. ε ∼ N (0, σ 2 )
Invoking assumption 5. in particular defines what is commonly called the normal
linear regression model.
6.7.1 b is not β!
Let’s talk about the small but important modifications we applied to model
(3.3) to end up at (6.15) above:
• β0 and β1 and intercept and slope parameters
• ε is the error term.
First, we assumed that (6.15) is the correct represenation of the DGP. With
that assumption in place, the values β0 and β1 are the true parameter values
which generated the data. Notice that β0 and β1 are potentially different from
b0 and b1 in (3.3) for a given sample of data - they could in practice be very
close to each other, but b0 and b1 are estimates of β0 and β1 . And, crucially,
those estimates are generated from a sample of data. Now, the fact that our
130 CHAPTER 6. STANDARD ERRORS
data {yi , xi }N
i=1 are a sample from a larger population, means that there will be
sampling variation in our estimates - exactly like in the case of the sample mean
estimating the population average as mentioned above. One particular sample
of data will generate one particular set of estimates b0 and b1 , whereas another
sample of data will generate estimates which will in general be different - by how
much those estimates differ across samples is the question in this chapter. In
general, the more observations we have the greater the precision of our estimates,
hence, the closer the estimates from different samples will lie together.
#OUT>
#OUT> Call:
#OUT> lm(formula = mpg ~ wt + wtplus, data = .)
#OUT>
#OUT> Coefficients:
#OUT> (Intercept) wt wtplus
#OUT> 37.285 -5.344 NA
That the coefficient on wtplus is NA is the result of the direct linear depen-
dence. (Notice that creating wtplus2 = (wt + 1)^2) would work, since
that is not linear!)
2. Conditional Mean of errors is zero, E[ε|x] = 0. Going back to our running
example in figure 6.1 about wages and education: Suppose that each in-
dividual i in our data something like innate ability, something we might
wish to measure with an IQ-test, however imperfecty. Let’s call it ai . It
seems reasonable to think that high ai will go together with high wages.
At the same time, people with high ai will find studying for exams and
school work much less burdensome than others, hence they might select
into obtaining more years of schooling. The problem? Well, there is no
ai in our regression equation - most of time we don’t have a good mea-
sure of it to start with. So it’s an unobserved variable, and as such, it is
part of the error term ε in our model. We will attribute to educ part of
the effect on wages that is actually caused by ability ai ! Sometimes we
6.8. STANDARD ERRORS IN THEORY 131
may be able to reason about whether our estimate on educ is too high or
too low, but we will never know it’s true value. We don’t get the ceteris
paribus effect (the true partial derivative of educ on lwage). Technically,
the assumption E[ε|x] = 0 implies that Cov(ε, x) = 0, so that’s the part
that is violated.
3. Data from Random Sample. One common concern here is that the obser-
vations in the data could have been selected in a particular fashion, which
would make it less representative of the underlying population. Suppose
we had ended up with individuals only from the richest neighborhood of
town; Our interpretation the impact of education on wages might not be
valid for other areas.
4. Homoskedasticity. For correct inference (below!), we want to know
whether the variance of ε varies with our regressor x or not. Here is a
typical example where it does:
Food Expenditure vs Log(income)
2000
1500
foodexp
1000
500
log(income)
tions 1. through 4. above we can define the formula for the variance of our
slope coefficient in the context of our single regressor model (6.15) as follows:
σ2
V ar(b1 |xi ) = ∑N (6.16)
i (xi − x̄)2
∑n ∑n 2
SSR i=1 (yi − b0 − b1 xi )2 e
2
s = = = i=1 i
n−p n−p n−p
where n − p are the degrees of freedom available in this estimation. p is the
number of parameters we wish to estimate (here: 1). So, the variance formula
would become
SSR
V ar(b1 |xi ) = ∑N (6.17)
(n − p) i (xi − x̄)2
We most of the time work directly with the standard error of a coefficient, hence
we define
√
√ SSR
SE(b1 ) = V ar(b1 |xi ) = ∑N (6.18)
(n − p) i (xi − x̄)2
You can clearly see that, as n increases, the denominator increases, and therefore
variance and standard error of the estimate will decrease.
We would like to further make this point in an experiential way, i.e. we want
you to experience what is going on. We invite you to spend some time with the
following apps. In particular, make sure you have a thorough understanding of
launchApp("estimate").
library(ScPoEconometrics)
launchApp("estimate")
launchApp("sampling")
launchApp("standard_errors_simple")
launchApp("standard_errors_changeN")
6.8. STANDARD ERRORS IN THEORY 133
f (bk |βk = 0)
α
= 0.025 1 − α = 0.95 α
= 0.025
2 2
bk
0 −bck βk = 0 bck
bk −0
t= sk
tdown = −1.96 0 tup = 1.96
H0 :βk = 0 (6.19)
H1 :βk ̸= 0. (6.20)
bk − 0
t value = (6.21)
sk
H0 :βwt = 0 (6.22)
H1 :βwt ̸= 0 (6.23)
We just take the t value entry, and see whether it lies above or below either
critical value: Indeed, we see that −4.053 < −2.048, and we are happy to reject
H0 .
On the other hand, when testing for statistical significance of drat that does
not seem to be the case:
6.9. WHAT’S IN MY MODEL? (AND WHAT IS NOT?) 135
H0 :βdrat = 0 (6.24)
H1 :βdrat ̸= 0 (6.25)
Here we find that 1.316 ∈ [−2.048, 2.048], hence it does not lie in any rejection
region, and we can not reject H0 . We would say that coefficient βdrat is not
statistically significant at the 5% level. As such, we should not include it in our
regression.
R also reports two additional columns in its regression output. The so-called p-
value in column Pr(>|t|) and a column with stars. P-values are an improvement
over the dichotomy introduced in the standard reject/accept framework above.
We never know if we narrowly rejected a H0 , or not. The p-value is defined as
the particular level of significance α∗ , up to which all H0 ’s would be rejected. If
this is a very small number, we have overwhelming support to reject the null. If,
on the contrary, α∗ turns out to be rather large, we only found weak evidence
against H0 .
We define the p-value as the sum of rejection areas for a given test statistic T ∗ .
Notice that the symmetry of the t distribution implies that we multiply by two
each of the two tail probabilities:
The stars in the final column are a visualization of this information. They show
a quick summary of the magnitude of each p-value. Commonly, *** means
an extremely small reference significance level α∗ = 0 (almost zero), ** means
α∗ = 0.001, etc. In that case, up to a significance level of 0.1%, all H0 would be
rejected. You clearly see that all columns Std. Error, t value and Pr(>|t|)
give a different type of the same information.
Great. But what does this mean? How could x be correlated with something
we don’t even observe?! Good questions - let’s try with an example.
Imagine that we assume that
yi = β0 + β1 xi + εi (6.27)
represents the DGP of impact the sales price of houses (y) as a function of
number of bathrooms (x). We run OLS as
yi = b0 + b1 xi + ei
You find a positive impact of bathrooms on houses:
data(Housing, package="Ecdat")
hlm = lm(price ~ bathrms, data = Housing)
summary(hlm)
#OUT>
#OUT> Call:
#OUT> lm(formula = price ~ bathrms, data = Housing)
#OUT>
#OUT> Residuals:
#OUT> Min 1Q Median 3Q Max
#OUT> -77225 -15271 -2510 11704 102729
#OUT>
#OUT> Coefficients:
#OUT> Estimate Std. Error t value Pr(>|t|)
#OUT> (Intercept) 32794 2694 12.17 <2e-16 ***
#OUT> bathrms 27477 1952 14.08 <2e-16 ***
#OUT> ---
#OUT> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#OUT>
#OUT> Residual standard error: 22880 on 544 degrees of freedom
#OUT> Multiple R-squared: 0.267, Adjusted R-squared: 0.2657
#OUT> F-statistic: 198.2 on 1 and 544 DF, p-value: < 2.2e-16
In fact, from this you conclude that each additional bathroom increases the
sales price of a house by 27477 dollars. Let’s see if our assumption E[ε|x] = 0
is satisfied:
6.9. WHAT’S IN MY MODEL? (AND WHAT IS NOT?) 137
library(dplyr)
# add residuals to the data
Housing$resid <- resid(hlm)
Housing %>%
group_by(bathrms) %>%
summarise(mean_of_resid=mean(resid))
#OUT> # A tibble: 4 x 2
#OUT> bathrms mean_of_resid
#OUT> <dbl> <dbl>
#OUT> 1 1 -118.
#OUT> 2 2 955.
#OUT> 3 3 -11195.
#OUT> 4 4 32298.
Oh, that doesn’t look good. Even though the unconditional mean E[e] = 0 is
very close to zero (type mean(resid(hlm))!), this doesn’t seem to hold at all by
categories of x. This indicates that there is something in the error term e which
is correlated with bathrms. Going back to our discussion about ceteris paribus
in section 4.1, we stated that the interpretation of our OLS slope estimate is
that
Keeping everything else fixed at the current value, what is the impact of
x on y? Everything also includes things in ε (and, hence, e)!
It looks like our DGP in (6.27) is the wrong model. Suppose instead, that in
reality sales prices are generated like this:
y i = β0 + β1 x i + β2 z i + ε i (6.28)
This would now mean that by running our regression, informed by the wrong
DGP, what we estimate is in fact this:
yi = b0 + b1 xi + (b2 zi + ei ) = b0 + b1 xi + ui .
What we are discussing here is called Omitted Variable Bias. There is a variable
which we omitted from our regression, i.e. we forgot to include it. It is often
difficult to find out what that variable could be, and you can go a long way by
138 CHAPTER 6. STANDARD ERRORS
just reasoning about the data-generating process. In other words, do you think
it’s reasonable that price be determined by the number of bathrooms only? Or
could there be another variable, omitted from our model, that is important to
explain prices, and at the same time correlated with bathrms?
Let’s try with lotsize, i.e. the size of the area on which the house stands. Intu-
itively, larger lots should command a higher price; At the same time, however,
larger lots imply more space, hence, you can also have more bathrooms! Let’s
check this out:
#OUT>
#OUT> Call:
#OUT> lm(formula = price ~ bathrms + lotsize, data = Housing)
#OUT>
#OUT> Residuals:
#OUT> Min 1Q Median 3Q Max
#OUT> -60752 -12532 -1674 10514 92931
#OUT>
#OUT> Coefficients:
#OUT> Estimate Std. Error t value Pr(>|t|)
#OUT> (Intercept) 1.008e+04 2.810e+03 3.588 0.000364 ***
#OUT> bathrms 2.281e+04 1.703e+03 13.397 < 2e-16 ***
#OUT> lotsize 5.575e+00 3.944e-01 14.136 < 2e-16 ***
#OUT> ---
#OUT> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#OUT>
#OUT> Residual standard error: 19580 on 543 degrees of freedom
#OUT> Multiple R-squared: 0.4642, Adjusted R-squared: 0.4622
#OUT> F-statistic: 235.2 on 2 and 543 DF, p-value: < 2.2e-16
Here we see that the estimate for the effect of an additional bathroom decreased
from 27477 to 22811.5 by almost 5000 dollars! Well that’s the problem then.
We said above that one more bathroom is worth 27477 dollars - if nothing
else changes! But that doesn’t seem to hold, because we have seen that as we
increase bathrms from 1 to 2, the mean of the resulting residuals changes quite
a bit. So there is something in ε which does change, hence, our conclusion
that one more bathroom is worth 27477 dollars is in fact invalid!
The way in which bathrms and lotsize are correlated is important here, so
let’s investigate that:
This shows that lotsize and the number of bathrooms is indeed positively related.
Larger lot of the house, more bathrooms. This leads to a general result:
6.9. WHAT’S IN MY MODEL? (AND WHAT IS NOT?) 139
2e−04
bathrms
density
1
2
3
1e−04
0e+00
5000 10000
lotsize
Instrumental Variables
• Measurement error
• Omitted Variable Bias
• Reverse Causality / Simultaneity Bias
are all called endogeneity problems.
crimeit = f (policeit )
policeit = g(crimeit )
We need a factor that is outside this circular system, affecting only the size
of the police force, but not the actual crime rate. Such a factor is called an
instrumental variable.
141
142 CHAPTER 7. INSTRUMENTAL VARIABLES
Chapter 8
Projects
143