Session 4 — Statistical Tests and Regressions
\[ \definecolor{wongBlack}{RGB}{0,0,0} \definecolor{wongGold}{RGB}{230, 159, 0} \definecolor{wongLightBlue}{RGB}{86, 180, 233} \definecolor{wongGreen}{RGB}{0, 158, 115} \definecolor{wongYellow}{RGB}{240, 228, 66} \definecolor{wongBlue}{RGB}{0, 114, 178} \definecolor{wongOrange}{RGB}{213, 94, 0} \definecolor{wongPurple}{RGB}{204, 121, 167} \definecolor{colA}{RGB}{255, 221, 85} \definecolor{colB}{RGB}{148, 78, 223} \definecolor{colC}{RGB}{63, 179, 178} \definecolor{colGpeZero}{RGB}{127, 23, 14} \definecolor{colGpeUn}{RGB}{27, 149, 224} \]
The slides were adapted from those of Pierre Michel and Morgan Raux, both researchers at AMSE, who kindly shared their work.
This slide deck was made with Quarto and reveal.js, it is translated by Claude Sonnet 5 from a LaTex presentation previously made with beamer.
A common hypothesis test is the Student’s t-Test, used to compare the means of two independent samples.
In the first tutorial, you created a CSV file with life satisfaction and per capita GDP for multiple countries, on an annual basis, with data originally from Eurostat.
Welch Two Sample t-test
data: happy$gdp and unhappy$gdp
t = 5.9988, df = 26.295, p-value = 2.36e-06
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
19591.33 39999.32
sample estimates:
mean of x mean of y
41377.83 11582.50
Before computing \(T\), we need the mean, variance and size of each group:
We can now plug these into the \(T\)-statistic formula: \[ T = \frac{\overline{X}_1 - \overline{X}_2}{\sqrt{\tfrac{s_1^2}{n_1} + \tfrac{s_2^2}{n_2}}} \]
[1] 5.998805
t = ... reported by t.test(): it matches!Since the two groups don’t have equal variances, the degrees of freedom are not simply \(n_1 + n_2 - 2\). The Welch–Satterthwaite approximation is used instead: \[ \nu = \frac{\left(\tfrac{s_1^2}{n_1} + \tfrac{s_2^2}{n_2}\right)^2}{\dfrac{(s_1^2/n_1)^2}{n_1-1} + \dfrac{(s_2^2/n_2)^2}{n_2-1}} \]
num <- (var_happy/n_happy + var_unhappy/n_unhappy)^2
denom <-
(var_happy/n_happy)^2 / (n_happy - 1) +
(var_unhappy/n_unhappy)^2 / (n_unhappy - 1)
df_obs <- num / denom
df_obs[1] 26.29469
df = ... reported by t.test().qt(p, df) is the quantile function of the Student’s \(t\)-distribution: it returns the value \(t^\star\) such that \(\mathbb{P}[T_{\nu} \le t^\star] = p\).alpha <- 0.05
t_crit <- qt(1 - alpha / 2, df = df_obs)
# x-range for the graph
x_max <- max(abs(t_obs), t_crit) + 1.5
curve_data <- tibble(x = seq(-x_max, x_max, by = .01)) |>
mutate(y = dt(x, df = df_obs))
# Rejection area
reject_data <- curve_data |>
filter(abs(x) >= t_crit)
ggplot(curve_data, aes(x = x, y = y)) +
# rejection regions (both tails), shaded
geom_area(
data = reject_data,
mapping = aes(group = x > 0),
fill = "#D55E00", alpha = .25
) +
geom_line(linewidth = 0.8) + # density curve
# critical values \pm t_crit
geom_vline(
xintercept = c(-t_crit, t_crit),
colour = "#D55E00", linetype = "dotted", linewidth = .7
) +
# observed statistic
geom_vline(
xintercept = t_obs,
colour = "#0072B2", linetype = "dashed", linewidth = .9
) +
# labels
annotate(
geom = "label",
x = t_obs, y = max(curve_data$y) * .95,
label = str_c("t[obs] == ", round(t_obs, 2)),
parse = TRUE, colour = "#0072B2", fill = "white"
) +
annotate(
geom = "text",
x = c(-t_crit, t_crit), y = -max(curve_data$y) * .03,
label = str_c(c("-", "+"), round(t_crit, 2)),
colour = "#D55E00", size = 3.5
) +
labs(
title = "Student's t-distribution under H0",
subtitle = str_c(
"df = ", round(df_obs, 1),
", shaded areas: rejection region at α = ", alpha
),
x = "t",
y = "Density"
) +
theme_minimal(base_size = 14) +
theme(plot.title.position = "plot")
pt()pt(q, df) is the cumulative distribution function (CDF): it returns \(\mathbb{P}[T_{\nu} \le q]\).p-value = ... reported by t.test().qt() and pt() are inverses of each other: qt(pt(x, df), df) == x.ggplot(
data = tb |> filter(year == 2018),
mapping = aes(x = gdp, y = life_satisf)
) +
geom_point() +
geom_smooth(method = "lm", se = FALSE) +
labs(
x = "Real per capita GDP in 2013 (2020 Euro)",
y = "Life satisfaction"
) +
scale_x_continuous(
labels = scales::label_number(
suffix = "", scale = 1, big.mark = ","
)
)
In the previous slide, we assumed the following model: \[ {\color{blue}Y_i} = {\color{green}\beta_0} + {\color{orange}\beta_1} {\color{purple}X_i} +\varepsilon_i, \] where
Interactive Application A cool interactive online graphical application to understand the mechanisms of OLS can be accessed at the following URL: https://www.econometrics-with-r.org/SimpleRegression.html
R, the OLS estimators of a linear model can be easily obtained, using the lm() function:
lm() function with a formula of the relationship we want to model,sim1 included in the modelr package, there are two variables, y and x. If we want to explain the variations of y by the variations of x, we write:lm() in the console, we have:Hence, the estimated model writes: \[ \hat{y}_i = 4.221 + 2.05 \times x_i \]
coef() function.Instead of computing the predicted values using the extracted values of the coefficients and the matrix of explanatory variables, R offers the function predict():
1 2 3 4 5 6 7 8
6.272355 6.272355 6.272355 8.323888 8.323888 8.323888 10.375421 10.375421
9 10 11 12 13 14 15 16
10.375421 12.426954 12.426954 12.426954 14.478487 14.478487 14.478487 16.530020
17 18 19 20 21 22 23 24
16.530020 16.530020 18.581553 18.581553 18.581553 20.633087 20.633087 20.633087
25 26 27 28 29 30
22.684620 22.684620 22.684620 24.736153 24.736153 24.736153
Similarly, for the residuals, R provides the residuals() function:
1 2 3 4 5 6
-2.072442018 1.238279125 -4.146882207 0.664969362 1.919217378 2.972935148
7 8 9 10 11 12
-3.019056466 0.129928252 0.136179642 0.007634878 -0.534352991 1.831009860
13 14 15 16 17 18
4.651562487 -2.740466108 1.546366596 -3.256043368 -0.574045413 0.364775796
19 20 21 22 23 24
1.504439222 -1.409703118 1.354755377 1.092815968 -2.242173633 1.842466099
25 26 27 28 29 30
4.092390235 0.120490168 -1.556314330 0.231946675 -1.389730610 -2.760952005
The summary() function applied to a linear regression model provides a lot of useful information on model fit:
Call:
lm(formula = y ~ x, data = modelr::sim1)
Residuals:
Min 1Q Median 3Q Max
-4.1469 -1.5197 0.1331 1.4670 4.6516
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 4.2208 0.8688 4.858 4.09e-05 ***
x 2.0515 0.1400 14.651 1.17e-14 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 2.203 on 28 degrees of freedom
Multiple R-squared: 0.8846, Adjusted R-squared: 0.8805
F-statistic: 214.7 on 1 and 28 DF, p-value: 1.173e-14
R, there is a convenient package called {stargazer} which provides a function with the same name that allows you to export your summary table as a nice table in HTML or LaTeX format.
type argument).Using type = "text" (as shown on the right-hand side) is convenient for looking at your results in the console. However, in a report, prefer another format.
===============================================
Dependent variable:
---------------------------
y
-----------------------------------------------
x 2.052***
(0.140)
Constant 4.221***
(0.869)
-----------------------------------------------
Observations 30
R2 0.885
Adjusted R2 0.880
Residual Std. Error 2.203 (df = 28)
F Statistic 214.660*** (df = 1; 28)
===============================================
Note: *p<0.1; **p<0.05; ***p<0.01
factor in R.levels argument of the factor() function,fct_relevel() from the {forcats} package to directly set the reference level.x is a character variable which contains the following unique values: "a", "b", "c", "d"."a".Using the factor() function or the fct_relevel() function from {forcats}:
We may believe the effect of a variable (\(x_1\)) depends on the value of another (\(x_2\)).
We will consider two cases:
Call:
lm(formula = y ~ x1 + x2, data = sim3)
Coefficients:
(Intercept) x1 x2b x2c x2d
1.8717 -0.1967 2.8878 4.8057 2.3596
Call:
lm(formula = y ~ x1 * x2, data = sim3)
Coefficients:
(Intercept) x1 x2b x2c x2d x1:x2b
1.30124 -0.09302 7.06938 4.43090 0.83455 -0.76029
x1:x2c x1:x2d
0.06815 0.27728
ggplot(
data = sim3 |>
modelr::gather_predictions(mod_bench, mod_interact) |>
mutate(
model = factor(
model,
levels = c("mod_bench", "mod_interact"),
labels = c("Benchmark", "Interaction~between~x[1]~and~x[2]"))
),
mapping = aes(x = x1, y = y, colour = x2)
) +
geom_point() +
geom_line(aes(y = pred)) +
facet_wrap(
~ model,
labeller = labeller(
model = label_parsed
)
)
ggplot(
data = sim3 |>
modelr::gather_residuals(mod_bench, mod_interact) |>
mutate(
model = factor(
model,
levels = c("mod_bench", "mod_interact"),
labels = c("Benchmark", "Interaction~between~x[1]~and~x[2]"))
),
mapping = aes(x = x1, y = resid, colour = x2)
) +
geom_point() +
facet_grid(
model ~ x2,
labeller = labeller(
model = label_parsed
)
) +
labs(y = "residuals")
# A tibble: 600 × 6
model x1 x2 rep y pred
<chr> <dbl> <dbl> <int> <dbl> <dbl>
1 mod1 -1 -1 1 4.25 0.996
2 mod1 -1 -1 2 1.21 0.996
3 mod1 -1 -1 3 0.353 0.996
4 mod1 -1 -0.778 1 -0.0467 0.378
5 mod1 -1 -0.778 2 4.64 0.378
6 mod1 -1 -0.778 3 1.38 0.378
7 mod1 -1 -0.556 1 0.975 -0.240
8 mod1 -1 -0.556 2 2.50 -0.240
9 mod1 -1 -0.556 3 2.70 -0.240
10 mod1 -1 -0.333 1 0.558 -0.859
# ℹ 590 more rows

R formula, if the transformation involves one of the following signs, the transformation must appear inside the I() function when writing the formula:
+, *, ^, -.poly() function (polynomials):The dataset containing real per capita GDP merged with the dataset giving life satisfaction, both downloaded from Eurostat (see tutorial 1) contains annual values (from 2010 to 2023) for multiple countries (6).
The FE model estimated with the lm() function:
Using the {plm} package:
With the lm() function:
With the plm() function:
The package {fixest} seems nice:
Practice with the third tutorial!

Introduction to programming for data analysis — Session 2