Welcome
In this week's lecture reviewed bivariate and multiple linear regressions. You also learned how they relate to the Potential Outcomes Framework and can be used, under certain conditions, for causal inference.
In this lab session we will:
- Take a step back to review how to create tables and perform t-tests in R
- Learn to run regression models using R
- Learn how to present to your results in neat tables
We will use a package called wooldridge that contains dozens of datasets used by Jeffrey M. Wooldridge with pedagogical purposes in his book on Introductory Econometrics.
# Load packages. Install them first, in case you don't have them yet.
library(wooldridge) # To get our example's dataset
library(broom) # To format regression output
library(stargazer) # To format regression tables
library(tidyverse) # To use dplyr functions and the pipe operator when needed
library(knitr) # To create tables with kable()
library(kableExtra) # To format tables with kable_styling()
Now let's load and take a look at our data.
data("wage1")
?wage1 #Using the help function you can explore what the variables in the dataset contain.
#You can also have a general view:
str(wage1)
head(wage1)
#Or look at the whole thing using the function View(wage1) in your console.
Review: balance tables and t-test
Let's take this dataset as an example to review how to perform a t-test in r (task 4 of your first assignment).
Remember the idea of using a t-test here is to check the statistical significance of a difference in means. In other words, we want to know how likely it is that the mean value of a variable in two groups or samples can be due to random chance (our null hypothesis).
In this case, let's imagine we want to check the statistical significance of the difference in means of years of education (interval variable) between men and women (categorical) in our dataset.
The general syntax for a t-test is simply as follows. The vectors refer to the variables whose mean you want to compare.
t.test(y ~ x, data = your_data)
Is the difference in average years of education between men and women statistically significant?
t.test(educ ~ female, data = wage1)
##
## Welch Two Sample t-test
##
## data: educ by female
## t = 1.9693, df = 517.81, p-value = 0.04946
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
## 0.001124132 0.940597569
## sample estimates:
## mean in group 0 mean in group 1
## 12.78832 12.31746
#OR
t.test(wage1$educ[wage1$female==1],wage1$educ[wage1$female == 0])
##
## Welch Two Sample t-test
##
## data: wage1$educ[wage1$female == 1] and wage1$educ[wage1$female == 0]
## t = -1.9693, df = 517.81, p-value = 0.04946
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
## -0.940597569 -0.001124132
## sample estimates:
## mean of x mean of y
## 12.31746 12.78832
Now let's take a look at how can we create a simple and good looking balance table. The idea here is to compare the mean values of different variables across populations or groups. In our case, let's see whether men and women differ in average wages, years of education, years of experience and number of years in their current job.
Here is one way to create the table.
wage1 %>%
dplyr::group_by(female) %>%
dplyr::select(wage, educ, exper, tenure) %>%
dplyr::summarise_all(mean) %>% # uses the summarize function to all the vars. we selected
t() %>% # To transpose the df
as.data.frame() %>%
dplyr::add_rownames("Variable") %>%
dplyr::filter(Variable != "female") %>% # drop the redundant variable
dplyr::rename(Male = "1", Female = "2") %>% # call the columns by what they are
dplyr::mutate(Difference = Female - Male) %>%
dplyr::mutate_if(is.numeric, round, 3) %>%
knitr::kable() %>%
kableExtra::kable_styling()
| Variable | Male | Female | Difference |
|---|---|---|---|
| wage | 7.099 | 4.588 | -2.512 |
| educ | 12.788 | 12.317 | -0.471 |
| exper | 17.558 | 16.429 | -1.130 |
| tenure | 6.474 | 3.615 | -2.859 |
Your turn
- Create a table to see if white and non-white respondents in the survey are balanced in terms of education levels and job experience.
wage1 %>%
dplyr::group_by(nonwhite) %>%
dplyr::select(educ, exper) %>%
dplyr::summarise_all(mean) %>% # uses the summarize function to all the vars. we selected
t() %>% # To transpose the df
as.data.frame() %>%
dplyr::add_rownames("Variable") %>%
dplyr::filter(Variable != "nonwhite") %>% # drop the redundant variable
dplyr::rename(White = "1", Non_white = "2") %>% # call the columns by what they are
dplyr::mutate(Difference = Non_white - White) %>%
dplyr::mutate_if(is.numeric, round, 3) %>%
knitr::kable() %>%
kableExtra::kable_styling()
| Variable | White | Non_white | Difference |
|---|---|---|---|
| educ | 12.642 | 11.870 | -0.772 |
| exper | 16.951 | 17.593 | 0.641 |
- Run a t-test to check the statistical significance of the differences in means that you observe.
t.test(educ ~ nonwhite, data = wage1)
##
## Welch Two Sample t-test
##
## data: educ by nonwhite
## t = 1.6471, df = 61.233, p-value = 0.1047
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
## -0.1650514 1.7082090
## sample estimates:
## mean in group 0 mean in group 1
## 12.64195 11.87037
t.test(exper ~ nonwhite, data = wage1)
##
## Welch Two Sample t-test
##
## data: exper by nonwhite
## t = -0.32877, df = 65.737, p-value = 0.7434
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
## -4.536204 3.253562
## sample estimates:
## mean in group 0 mean in group 1
## 16.95127 17.59259
#See the relationship with lm()
lm(exper ~ nonwhite, data = wage1) %>% summary()
##
## Call:
## lm(formula = exper ~ nonwhite, data = wage1)
##
## Residuals:
## Min 1Q Median 3Q Max
## -16.593 -11.951 -3.772 9.049 33.407
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 16.9513 0.6252 27.112 <2e-16 ***
## nonwhite 0.6413 1.9514 0.329 0.743
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 13.58 on 524 degrees of freedom
## Multiple R-squared: 0.0002061, Adjusted R-squared: -0.001702
## F-statistic: 0.108 on 1 and 524 DF, p-value: 0.7426
Bivariate linear regression
Imagine that, using the wage1 dataset you are interested in exploring the relationship between education and people's salaries. You want to look for the causal effect that getting one more year of education (educ) has on people's average wage per hour (wage).
You start by exploring their relationship:
plot(wage1$educ, wage1$wage)

cor(wage1$educ, wage1$wage)
## [1] 0.4059033
As we saw in the lecture, you can resort to regression to address this question using observational data.
So you formalize the relationship you're after in a first, short model:
\[wage = \alpha + \kappa education + u\]
Remember from the lecture (slide 18), that we can relate a regression equation with the potential outcomes framwework, as follows.
- Kappa, the regression coefficient for our independent variable education, is equal to the Naïve Average Treatment Effect or NATE.
\[\kappa = E[Y_1|D=1] - E[Y_0|D=0]\]
- The constant term (intercept) in our regression equation is equal to the expected outcome when not treated, for those untreated.
\[\alpha = E[Y_0|D=0] \]
- The sum of the constant term plus the regression coefficient in our bivariate model is equal to the expected outcome when treated, for those treated.
\[\alpha + \kappa = E[Y_1|D=1] \]
Now that we know what we are after, how to get these estimates in R?
Running a linear model in R
The general syntax for running a regression model in R is the following:
your_model <- lm(dependent_variable ~ independent_variable, data = your_dataset)
Now try to create an object with your model, based on the bivariate regression equation we specified above in which wage is our outcome of interest and educ is our independent or 'treatment' variable.
naive_model <- lm(wage ~ educ, data = wage1)
You have created an object that contains the all the coefficients, std errors and all other information included in your model. In order to see the estimates, you could use the base R function summary(), but base R is not the best at visualizing regression outputs.
The tidy() function from the broom package, instead, constructs a data frame that summarizes the model’s statistical findings. You can see what else you can do with broom by running: vignette("broom").
See the difference:
summary(naive_model)
##
## Call:
## lm(formula = wage ~ educ, data = wage1)
##
## Residuals:
## Min 1Q Median 3Q Max
## -5.3396 -2.1501 -0.9674 1.1921 16.6085
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -0.90485 0.68497 -1.321 0.187
## educ 0.54136 0.05325 10.167 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 3.378 on 524 degrees of freedom
## Multiple R-squared: 0.1648, Adjusted R-squared: 0.1632
## F-statistic: 103.4 on 1 and 524 DF, p-value: < 2.2e-16
broom::tidy(naive_model)
## # A tibble: 2 x 5
## term estimate std.error statistic p.value
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 (Intercept) -0.905 0.685 -1.32 1.87e- 1
## 2 educ 0.541 0.0532 10.2 2.78e-22
# You can save into an object so you can easily pull the values later
naive_model_df <- broom::tidy(naive_model)
# Remember magrittr can make your life easier
naive_model_df <- lm(wage ~ educ, data = wage1) %>% broom::tidy()
How would you interpret these results?
We can see that the coefficient for educ, (our kappa, our NATE), suggests that one more year of education is associated with a $0.54 increase in hourly wages.
You can also see the value of the intercept (alpha, or E[Y_0|D = 0]) and the significance levels of our coefficients.
What is the difference in expected hourly wages between a person who studies up to highschool (13 years) and a person with a college degree (17 years)?
#Different ways to get to the same:
(coef(naive_model)[1] + coef(naive_model)["educ"] * 17) -
(coef(naive_model)[1] + coef(naive_model)["educ"] * 13)
## (Intercept)
## 2.165437
predict(naive_model, newdata = data.frame(educ = 17)) -
predict(naive_model, newdata = data.frame(educ = 13))
## 1
## 2.165437
coef(naive_model)["educ"] * (17 - 13)
## educ
## 2.165437
- What is the expected hourly wage for someone with 25 years of education?
predict(naive_model, newdata = data.frame(educ = 25))
## 1
## 12.62913
coef(naive_model)[1] + coef(naive_model)["educ"] * 25
## (Intercept)
## 12.62913
- Under this model specifications, what percentage of the variation wage is explained by years of education?
summary(naive_model)$r.squared
## [1] 0.1647575
Multiple regression
Looking for ATE.
But you know that this survey data does not fulfill the assumptions needed in order to infer causality without much doubt. It is highly likely that we are picking up the effect of varaibles that affect both Y (wages) and D (levels of education). People do not get more education by random chance: there can be different causes leading to people getting more education and earning more. That is, you suspect that educ is not independent of the error term.
After a good time reading about the gender pay gap, you admit that gender can be a those confounding variables. So you decide to include the female variable in the model.
Now your model looks like this:
\[wage = \alpha^l + \kappa^leducation + \beta gender + u\]
Our interpretation of the regression equation in POF terms will not change much. Kappa (long) is still the coefficient we are most interested in: the treatment effect of one more year of educ on wage.
But remember that multiple regression only uses the unique variation in each regressor not explained by other regressors, so including an additional term (female) -if it is associated with education and wage- will affect our treatment effect estimate.
Let's see: run a multiple regression using the model above. The general syntax for it is this:
your_model <- lm(dependent_variable ~ independent_variable1 + independent_variable2, data = your_dataset)
less_naive_model <- lm(wage ~ educ + female, wage1)
less_naive_model_df <- broom::tidy(less_naive_model)
How would you interpret these results now?
What does the coefficient for the
femalevariable tell us?What is the expected wage for a woman with 20 years of education?
What is the difference if a man had the same amount of education
Observing biases
In order to see how biased the estimate from our more naïve or 'short' model was, we can compare the regression coefficients between our two models. Let's use the stargazer package to do so.
The function (also called) stargazer() allows you to see the resutls of different models side by side. The general syntax is:
stargazer::stargazer(name_of_model_1, name_of_model_2, type = "format of output")
#try out "text" and "html" as outputs
You can very specifically define the format and information included in your tables using stargazer. Check out, for example, this and this
- Create a stargazer table with both models. Try out using "text" and "html" for the output.
stargazer::stargazer(naive_model, less_naive_model,
type = "text")
##
## ====================================================================
## Dependent variable:
## ------------------------------------------------
## wage
## (1) (2)
## --------------------------------------------------------------------
## educ 0.541*** 0.506***
## (0.053) (0.050)
##
## female -2.273***
## (0.279)
##
## Constant -0.905 0.623
## (0.685) (0.673)
##
## --------------------------------------------------------------------
## Observations 526 526
## R2 0.165 0.259
## Adjusted R2 0.163 0.256
## Residual Std. Error 3.378 (df = 524) 3.186 (df = 523)
## F Statistic 103.363*** (df = 1; 524) 91.315*** (df = 2; 523)
## ====================================================================
## Note: *p<0.1; **p<0.05; ***p<0.01
remember that to render the html output from stargazer you need to have results=asis on your chunk
stargazer::stargazer(naive_model, less_naive_model,
type = "html")
| Dependent variable: | ||
| wage | ||
| (1) | (2) | |
| educ | 0.541*** | 0.506*** |
| (0.053) | (0.050) | |
| female | -2.273*** | |
| (0.279) | ||
| Constant | -0.905 | 0.623 |
| (0.685) | (0.673) | |
| Observations | 526 | 526 |
| R2 | 0.165 | 0.259 |
| Adjusted R2 | 0.163 | 0.256 |
| Residual Std. Error | 3.378 (df = 524) | 3.186 (df = 523) |
| F Statistic | 103.363*** (df = 1; 524) | 91.315*** (df = 2; 523) |
| Note: | p<0.1; p<0.05; p<0.01 | |
- How biased was the estimate from our first model compared to our second model?
naive_model_df[2,2] - less_naive_model_df[2,2]
## estimate
## 1 0.03490714
# Or, the same
coef(naive_model)["educ"] - coef(less_naive_model)["educ"]
## educ
## 0.03490714
Your turn
can you think of additional observed confounding variable that could also have an effect on education and wages that you should include in your regression model?
Specify the model to include an additional variable you thought of, see your results in a tidy format using broom::tidy() and include them in your stargazer() table.
How would you interpret the results?