Kleiber, C., & Zeileis, A. (2008). Applied econometrics with R. Springer Science & Business Media.
R obsahuje mnoho různých nástrojů, které téměř kompletně pokrývají potřeby empirického výzkumu. Základní estimátory (lm()
a glm()
) jsou implementovány v základním R, ale pro mnoho funkcí je nutné sahat do dalších balíků, které nejsou bohužel příliš standardizované.
Účelem tohoto handoutu není předvést všechny ekonometrické nástroje, které jsou v R dostupné – to by díky jejich počtu ani nebylo možné – ale vysvětlit logiku fungování základní sady nástrojů. Praktický příklad ukazuje v mnoha ohledech nejjednoduší druh regresní analýzy – odhad modelu pomocí OLS na průřezových datech. Její pochopení Vám umožní lehce pochopit fungování i složitějších nástrojů.
Pro každou ekonometrickou analýzu potřebujete několik věcí:
Když dáte tyto věci dohromady získáte výsledky, které můžete dále zpracovávat, analyzovat nebo rovnou zahodit…
Používání ekonometrických nátrojů v R se řídí stejnou logikou:
Fungování ekonometrických nástrojů můžeme ilustrovat na příkladu datasetu s výškou, váhou, věkem a pohlavím sportovců, kteří se zůčastnili olympijských her v Londýně v roce 2012 (dostupný v VGAMdata::oly12
):
print(oly12, n=5)
## # A tibble: 9,038 × 4
## Height Weight Sex Age
## <dbl> <int> <fctr> <int>
## 1 1.70 60 M 23
## 2 1.93 125 M 33
## 3 1.87 76 M 30
## 4 1.78 85 F 26
## 5 1.82 80 M 27
## # ... with 9,033 more rows
Nejprve nás bude zajímat vztah mezi váhou a výškou sportovců. Ten si můžeme vykreslit pomocí jednoduchého bodového grafu:
oly12 %>%
ggplot(
aes(
x = Height,
y = Weight
)
) +
geom_point(
alpha = 0.1
) +
theme_linedraw()
Zdá se, že existuje lineární vztah mezi váhou a výškou sportovců, který můžeme popsat rovnicí: \[Weight = \alpha + \beta Height + \varepsilon\] kde \(\alpha\) a \(\beta\) jsou neznámé parametry, které chceme odhadnout a \(\varepsilon\) je náhodná složka.
Tuto rovnici si můžeme představit jako přímku, která je proložena mrakem pozorování:
Našim cílem je najít takové parametry \(\alpha\) a \(\beta\) (tj. úrovňovou konstantu a sklon) přímky, které zajistí “nejlepší proložení”.
Pro jejich nalezení (odhad modelu) je potřeba najít vhodný estimátor. Pro odhad parametrů použijeme metodu nejmenších čtverců (OLS), která je implementována ve funkci lm()
:
lm(formula, data, subset, weights, na.action,
method = "qr", model = TRUE, x = FALSE, y = FALSE, qr = TRUE,
singular.ok = TRUE, contrasts = NULL, offset, ...)
Funkce lm()
má mnoho parametrů:
formula
je objekt třídy “formula”, který obsahuje symbolický popis rovnice, která se má odhadnoutdata
je data.frame
který obsahuje data, která se mají použít. Jména sloupců musí odpovídat proměnným použitým v parametru formula
subset
je nepovinný parametr, který určuje, které pozorování (řádky) se mají použít pro odhadweights
je vektor vah. Pokud není zadán je pro odhad použit obyčejný OLS estimátor, ve kterém mají všechna pozorování stejnou váhu. V případě zadání vah je vráceno WLS.Data již máme v tabulce oly12
. Máme i představu o rovnici, kterou chceme odhadnout:
\[Weight = \alpha + \beta Height + \varepsilon\]
Tuto rovnici však musíme zapsat tak, aby byla srozumitelná i pro R. K tomu slouží objekty třídy formula
. Jejich formulace následuje tuto syntax:
LHS ~ RHS
Tedy výrazy na levé straně (LHS) \(=\) (zastoupené znakem ~
) výrazy na pravé straně (RHS). Rovnici vysvětlující váhu bychom tedy mohli popsat jako:
model <- Weight ~ Height
class(model)
## [1] "formula"
V ekonomii je závislá (vysvětlovaná) proměnná (tedy proměnná na levé straně rovnice) funkcí více než jedné nezávislé (vysvětlující) proměnné. Pokud bychom chtěli vysvětlovat váhu nejen výškou, ale i věkem, potom bychom rozšířili specifikaci následujícím způsobem:
model <- Weight ~ Height + Age
Dodatečné proměnné jsou přidávány pomocí +
.
Za povšimnutí stojí, že při použití takovéto specifikace by byl model odhadnut s úrovňovou konstantou \(\alpha\) – i když není explicitně v rovnici přítomna. Explicitně se naopak musí zadat její nepřítomnost připojením -1
nebo 0
:
model <- Weight ~ Height - 1
model <- Weight ~ Height + 0
Jména proměnných v rovnici musí odpovídat jménům sloupců v datové tabulce. Zásadní výhodou R je, že do rovnice je možné zadat i transformace dat:
model <- Weight ~ log(Height)
model <- Weight ~ Height >= 1.8
První varianta například odpovídá specifikaci:
\[Weight = \alpha + \beta \log(Height) + \varepsilon\]
Ve druhé je váha vysvětlována dummy (umělou proměnnou). Výraz Height >= 1.8
vytvoří logickou proměnnou, která je při volání estimační funkce (lm
,…) transformována na vektor s hondnotami 1 a 0. Viz příklad takovéto transformace v tabulce:
oly12 %>%
mutate(
cond_l = Height >= 1.8
) %>%
mutate(
cond_n = as.numeric(cond_l)
) %>% print(n=5)
## # A tibble: 9,038 × 6
## Height Weight Sex Age cond_l cond_n
## <dbl> <int> <fctr> <int> <lgl> <dbl>
## 1 1.70 60 M 23 FALSE 0
## 2 1.93 125 M 33 TRUE 1
## 3 1.87 76 M 30 TRUE 1
## 4 1.78 85 F 26 FALSE 0
## 5 1.82 80 M 27 TRUE 1
## # ... with 9,033 more rows
Není tedy potřeba vytvářet nové transformované sloupce v datové tabulce. To je velký rozdíl mezi R a Statou, Gretlem,… (Jakkoliv je v některých případech vytvoření transformovaných proměnných do tabulky doporučeníhodné.)
V rovnici může být přimo přítomna celá řada funkcí – s výjimkou těch, jejichž interpratace by nebyla jasná. Pokud bychom například chtěli odhadnout modelovou specifikaci
\[Weight = \alpha + \beta (Height + Age) + \varepsilon\]
potom je nutná sdělit R, že má nejdříve sečíst Height
a Age
a pro vysvětlení Weight
použít až výsledný součet. Pro tyto účely se používá funkce I()
. Ta sděluje, že se mají nejprve provést operace definované uvnitř funkce a pro odhad použít až výsledek.
Výše uvedený příklad by se tak R tlumočil jako
model <- Weight ~ I(Height + Age)
Třída forrmula umožňuje jednoduše jednoduše definovat i interakční členy pomocí operátorů :
a *
:
model <- Weight ~ Height:Sex
Chápe R jako
\[Weight = \alpha + \beta Height\times Sex_{male} + \gamma Height\times Sex_{female} + \varepsilon\] Tato rovnice vyjadřuje možný odlišný mezní efekt výšky na váhu u mužů a žen. Výsledné proložení mrakem pozorování by se tak v bodovém grafu výše vlastně rozpadlo na dvě přímky s odlišným sklonem (parametry \(\beta\) a \(\gamma\)) a shodnou úrovňovou konstantou \(\alpha\).
Další možností jak popsat interakci proměnných je *
:
model <- Weight ~ Height*Sex
Tato formula
odpovídá jiné rovnici:
\[Weight = \alpha + \beta Height + \epsilon Sex_{male} + \gamma Height\times Sex_{male} + \varepsilon\] Tato specifikace umožňuje odlišnost regresní křivky pro muže a ženy jak ve sklonu, tak v úrovňové konstantě.
Zvláštní význam má i symbol \(^\). Rovnici
\[Weight = \alpha + \beta_1 Height + \beta_2 Age + \beta_3 Sex_{male} + \gamma_1 Height\times Sex_{male} + \gamma_2 Height\times Age + \gamma_3 Sex_{male}\times Age + \varepsilon\] můžeme zapsat jako:
model <- Weight ~ (Height + Age + Sex)^2
Výraz se přeloží tak, že výsledná rovnice obsahuje jednotlivé proměnné a také jejich interakce do, v tomto případě, druhého řádu.
Pro třídu formula jsou naimplementovány i některé zajímavé metody. Velmi užitečná je například funkce update()
s velmi prostou syntaxí:
update(old,new,...)
Kde old
je původní rovnice a new
pravidla, která stanovují, jak se původní rovnice má upravit.
model <- Weight ~ Height + Age
print(model)
## Weight ~ Height + Age
Takto můžeme zachovat všechny proměnné – reprezentované pomocí .
a jen některou přidat nebo odebrat:
model %>%
update(. ~ . -Age)
## Weight ~ Height
Nebo je možné nahradit celou stranu rovnice:
model %>%
update(. ~ Age)
## Weight ~ Age
Při volání funkce lm()
nebo jiné estimační funkce se dohromady spojí všechny tři komponenty: modelová specifikace, data a estimátor.
Pro odhad použijeme model v následující specifikaci:
model <- Weight ~ Height + Age + Sex
Proměnné Weight
, Height
a Age
jsou numerické spojité proměnné. Sex
je zcela jiný případ. Je to kategoriální proměnná, která nemůže vstoupit do regrese přímo. Před samotným odhadem je jí nutné transformovat na vektor (matici v případě více než dvou úrovní) nul a jedniček.
Estimační funkce si všechny vstupní faktory transformuje sama pomocí interního volání funkce model.matrix()
:
model.matrix(model, data = oly12) %>% head
## (Intercept) Height Age SexM
## 1 1 1.70 23 1
## 2 1 1.93 33 1
## 3 1 1.87 30 1
## 4 1 1.78 26 0
## 5 1 1.82 27 1
## 6 1 1.82 30 0
Pokud je vstupní proměnná character
, potom si ji model.matrix()
vnitřně konvertuje na faktor a provede transformaci na vektor/matici dummy proměnných:
oly12 %>%
mutate(
Sex = as.character(Sex)
) %>%
model.matrix(model, data = .) %>%
head
Přesto existuje dobrý důvod, proč může uživatel chtít provést konverzi do faktoru sám.
Počet sloupců, které vzniknou po transformaci faktoru je (v případě, že modelová specifikace obsahuje konstantu) o jeden nižší, než je počet úrovní faktoru (přítomných v datech). Jedna úroveň faktoru je totiž použita jako referenční a v modelu není explicitně obsažena. Odhadnuté koeficienty pro zachované úrovně následně ukazují rozdíl v průměrné hodnotě vůči referenční úrovni.
V předchozích příkladech se proměnná Sex
transformovala do vektoru s 1
pro muže a 0
pro ženy. Toto chování se dá změnit nastavením referenční úrovně faktoru.
S pomocí base
funkce:
oly12 %>%
mutate(
Sex = relevel(Sex, ref="M")
) %>%
model.matrix(model, data = .)
Nebo funkce fct_relevel()
z balíku forcats
(součást tidyverse
):
oly12 %>%
mutate(
Sex = forcats::fct_relevel(Sex, "M")
) %>%
model.matrix(model, data = .) %>%
head
## (Intercept) Height Age SexF
## 1 1 1.70 23 0
## 2 1 1.93 33 0
## 3 1 1.87 30 0
## 4 1 1.78 26 1
## 5 1 1.82 27 0
## 6 1 1.82 30 1
Pokud je proměnná numerická (typicky integer
), ale chceme, aby její hodnoty byly chápány jako úrovně faktoru, je nutné provést transformaci. Buď v datech, nebo přímo v rovnici, např.:
Weight ~ Height + factor(Age) + Sex
Konverze numerických hodnot do faktoru je poměrně častá. Zvláště u dotazníkových dat čísla mohou pouze kódovat hodnoty kategoriální proměnné. Dalším připadem je například vytvoření fixních efektů pro roky.
R umožňuje tvorbu tzv. ordered faktorů. Ty doporučujeme nepoužívat – pokud si nejste jisti, co děláte.
lm()
S připraveným modelem a daty je možné volat funkci lm()
:
lm(model, data = oly12) -> est_model
print(est_model)
##
## Call:
## lm(formula = model, data = oly12)
##
## Coefficients:
## (Intercept) Height Age SexM
## -103.040 94.898 0.167 5.573
Produkt funkce lm()
je teď přiřazen do proměnné est_model
. To je S3 objekt zvláštní třídy lm
:
class(est_model)
## [1] "lm"
Tento objekt si můžeme představit jako list se standardizovanou strukturou:
names(est_model)
## [1] "coefficients" "residuals" "effects" "rank"
## [5] "fitted.values" "assign" "qr" "df.residual"
## [9] "contrasts" "xlevels" "call" "terms"
## [13] "model"
Její základní položky jsou:
coefficients
– pojmenovaný vektor odhadnutých koeficientůest_model$coefficients
## (Intercept) Height Age SexM
## -103.040347 94.898203 0.166981 5.573154
residuals
– vektor reziduí – tj. odhadů náhodné složky \(\varepsilon\)est_model$residuals %>% head
## 1 2 3 4 5 6
## -7.700316 33.803288 -9.001877 14.780039 0.243976 -1.683813
fitted.values
– vyrovnané hodnotyest_model$fitted.values %>% head
## 1 2 3 4 5 6
## 67.70032 91.19671 85.00188 70.21996 79.75602 74.68381
Třídu lm
dědí i výsledky jiných estimačních funkcí. Například glm()
vrací objekty s třídami glm
a lm
:
glm(model, data = oly12) %>% class
## [1] "glm" "lm"
Uživatelé Gretlu a jiného statistického software jsou zvyklí, že odhad nevrací jen velmi střídmý přehled odhadnutých koeficientů, ale že ukazuje velmi bohatou tabulku s výsledky různých testů atp.
Takový výstup můžeme získat pomocí funkce summary
:
summary(est_model)
##
## Call:
## lm(formula = model, data = oly12)
##
## Residuals:
## Min 1Q Median 3Q Max
## -56.277 -5.770 -1.352 3.754 135.731
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -103.0404 1.9867 -51.864 <2e-16 ***
## Height 94.8982 1.1355 83.572 <2e-16 ***
## Age 0.1670 0.0196 8.518 <2e-16 ***
## SexM 5.5732 0.2560 21.774 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 10.13 on 9034 degrees of freedom
## Multiple R-squared: 0.6028, Adjusted R-squared: 0.6027
## F-statistic: 4571 on 3 and 9034 DF, p-value: < 2.2e-16
Všimněte si, že
summary()
používá méně obvyklé – a přísnější – hvězdičkové značení!
V R najdete řadu dalších funkcí, které pracují s objekty třídy lm
a extrahují (nebo dopočítávají) z něj užitečné informace:
Funkce | Popis |
---|---|
coefficients() , coefs() |
Vrací odhadnuté koeficienty – obsah .$coefficients |
residuals() , resid() |
Vrací vektor reziduí – pro objekty lm je výstup identický s .$residuals |
fitted.values() |
Vrací očekávané hodnoty – obsah .$fitted.values |
predict() |
Umožňuje dopočítat predikce pro jiná data, než na kterých byl model odhadnut |
plot() |
Vrací diagnostické grafy vytvořené pomocí základní grafiky (ne v ggplot2 ) |
confint() |
Vrací konfidenční intervaly pro odhady koeficientů |
deviance() |
Vrací MSE – průměr čtverců reziduí |
vcov() |
Vrací odhad kovarianční matice |
logLik() |
Vrací log-likelihood |
AIC() |
Vrací Akaikovo informační krítérium (AIC) |
BIC() |
Vrací Schwarzovo Bayesian kritérium (BIC) |
nobs() |
Vrací počet pozorování |
Funkce tidy()
z balíku broom
vytvoří z objektu třídy lm
data.frame s odhady parametrů, jejich směrodatnými chybami, t-statistikami a p-hodnotami:
est_model %>% tidy
## term estimate std.error statistic p.value
## 1 (Intercept) -103.040347 1.98673555 -51.864149 0.000000e+00
## 2 Height 94.898203 1.13552521 83.572080 0.000000e+00
## 3 Age 0.166981 0.01960294 8.518163 1.881305e-17
## 4 SexM 5.573154 0.25595405 21.774042 1.711081e-102
Funkce broom::glance()
umožňuje získat jednořátkovou summarizaci “statistik” pro odhad modelu:
est_model %>% glance
## r.squared adj.r.squared sigma statistic p.value df logLik AIC
## 1 0.6028396 0.6027077 10.12833 4570.826 0 4 -33748.38 67506.76
## BIC deviance df.residual
## 1 67542.31 926736.1 9034
Funkce tidy()
a glance()
jsou zvláště užitečné při strojovém zpracování velkého množstvé modelů.
Často nás zajímá, jak dobře model predikuje. Kromě různých statistik je zajímá se podívat na bodový graf, kde na jedné osa jsou pozorované hodnoty vysvětlované proměnné (Weight
) a na druhé její očekávané hodnoty – tj. predikce modelu.
Abychom mohli takový obrázek vytvořit, potřebujeme data.frame
ve kterém budou pozorované i predikované hodnoty. Momentálně máme pozorované hodnoty v tabulce oly12
, popřípadě v est_model$model
, a predikované hodnoty v est_model$fitted.values
. Spojit je dohromady nám pomůže funkce augment
z balíku broom
:
augment(est_model) %>% as_tibble()
## # A tibble: 9,038 × 11
## Weight Height Age Sex .fitted .se.fit .resid .hat
## <int> <dbl> <int> <fctr> <dbl> <dbl> <dbl> <dbl>
## 1 60 1.70 23 M 67.70032 0.2155292 -7.70031552 0.0004528311
## 2 125 1.93 33 M 91.19671 0.2172001 33.80328752 0.0004598795
## 3 76 1.87 30 M 85.00188 0.1622649 -9.00187722 0.0002566690
## 4 85 1.78 26 F 70.21996 0.1802440 14.78003929 0.0003166983
## 5 80 1.82 27 M 79.75602 0.1434618 0.24397601 0.0002006302
## 6 73 1.82 30 F 74.68381 0.2186361 -1.68381294 0.0004659807
## 7 75 1.87 23 M 83.83301 0.1684282 -8.83301004 0.0002765372
## 8 80 1.90 27 M 87.34788 0.1639915 -7.34788024 0.0002621600
## 9 60 1.73 28 F 65.80901 0.1670191 -5.80901261 0.0002719296
## 10 64 1.71 28 F 63.91105 0.1651660 0.08895145 0.0002659287
## # ... with 9,028 more rows, and 3 more variables: .sigma <dbl>,
## # .cooksd <dbl>, .std.resid <dbl>
Pro podobnou funkcionalitu jakou poskytuje balík
broom
můžete použít i balíkmodelr
.broom
však obsahuje nástroje pro mnohem širší škálu tříd.
Ta vrací původní datovou tabulku rozšířenou o dodatečné sloupce (rezidua, predikované hodnoty a další). Momentálně je podstatné i to, že vrací tidy data.frame. Její výstup můžeme použít pro vytvoření obrázku:
augment(est_model) %>%
ggplot(
aes(
x = Weight,
y = .fitted
)
) +
geom_point(
alpha = 0.1
) +
geom_abline(slope = 1, intercept = 0, color = "red") +
geom_smooth(method = "lm") +
theme_linedraw()
Do prostého bodového grafu je na obrázku pomocí geom_abline()
vložena i červená přímka zobrazující osu kvadrantu. V ideálním případě by všechny hodnoty měly ležet právě na ní.
Modrou barvou je vyznačeno proložení vztahu mezi pozorovanou a predikovanou hodnotou. V idálním případě by měly obě přímky splývat do jedné. Tady se to ovšem neděje. Zejména u vyšších vah je proložení vychylováno klastrem vlivných pozorování.
Pro popis kvality proložení (předpovědi) se používají i statistiky jako je například Mean squared error (MSE) – tedy průměr čtverců reziduí. Tato statistika se dá vypočítat ze součástí objektu lm
. Buď je možné přímo adresovat vektor reziduí (est_model$residuals
), nebo použít speciální funkci residuals()
, která je extrahuje:
residuals(est_model)^2 %>% mean
## [1] 102.5377
Popřípadě lze použít funkci deviance()
. Výsledek musí být identický:
deviance(est_model)/nobs(est_model)
## [1] 102.5377
To znamená, že průměrná vzdálenost pozorované a vyrovnané hodnoty je 10.13 Kg.
Doporučeníhodné je použití funkci residuals()
a to zejména proto, že umí extrahovat různé typy reziduí. To je užitečné zejména u jiných estimačních funkcí – typicky glm()
(logit, probit, poisson, PPML,…).
Kromě vykreslení vyrovnaných hodnot (fitted values) je možné vypočítat pomocí funkce predict()
i predikované hodnoty. Při zavolání funkce predict()
bez nastavení dalších parametrů vrací vyrovnané hodnoty.
all.equal(predict(est_model),fitted(est_model))
## [1] TRUE
To znamená, že predikované hodnoty jsou vypočítány pro pozorování z tabulka, která byla využita pro odhad modelu – jedná se tzv. o in-sample predikce.
Při mnoha aplikacích nás spíše zajímá schopnost modelu predikovat out-of-sample. Tedy predikovat hodnoty vysvětlované proměnné na jiném vzorku dat, než ktrerý byl použit k odhadu parametrů. I pro tento účel se hodí funkce predict()
.
Nejprve si tabulku oly12
rozdělíme na dvě dílčí tabulky oly12_a
a oly12_b
:
# Náhodně vybereme polovinu řádků
idx <- sample(nrow(oly12), round(nrow(oly12)/2))
oly12[idx,] -> oly12_a
oly12[-idx,] -> oly12_b
Pro odhad parametrů použijeme tabulku oly12_a
:
oly12_a %>%
lm(model, data=.) -> est_model_a
Na základě odhadnutého modelu vypočítáme očekávané hodnoty Weight
pro pozorování ze vzorku oly12_b
:
predict(est_model_a, newdata = oly12_b) -> prediction_oly12_b
Výsledkem je vektor predikovaných hodnot. Podívejme se, jak si model vedl při predikci out-of-sample:
oly12_b %<>%
cbind(Weight_predicted = prediction_oly12_b) %>%
mutate(
prediction_error = Weight - Weight_predicted
)
oly12_b %>%
ggplot(
aes(
x = Weight,
y = Weight_predicted
)
) +
geom_point(
alpha = 0.1
) +
geom_abline(slope = 1, intercept = 0, color = "red") +
geom_smooth(method = "lm") +
theme_linedraw()
Můžeme opět vypočítat MSE – v tomto případě tedy MSPE (mean squared prediction error):
oly12_b$prediction_error^2 %>% mean
## [1] 103.8298
To znamená, že v out-of-sample predikci je průměrná vzdálenost mezi pozorovanou a očekávanou hodnotou 10.19 Kg. Model si tak vede stejně dobře (špatně) v in-sample a out-of-sample predikcích. (To je obecně dobré znamení.)
Populárním způsobem, jak najít pozorování, která významně ovlivňují výsledky modelu, je použití leave-one-out strategií. V nich se sleduje efekt vyloučení jednoho pozorování na odhady koeficientů, popřípadě na vyrovnaná pozorování. V R jsou naimplementovány v influence.measures()
:
influence.measures(est_model) -> infl_obs
infl_obs %>% class
## [1] "infl"
influence.measures()
najednou volá celou řadu diagnostických testů – mj. dfbetas()
(vliv jednoho pozorování na odhadnuté koeficienty) a dffits()
(vliv jednoho pozorování na vyrovnané hodnoty).
Výstupní objekt třídy infl
obsahuje dva prvky (matice). .$infmat
obsahuje hodnoty jednotlivých statistik a .$is.inf
rozhodnutí (o podezření), zda se jedná o vlivné pozorování.
Viz obrázek:
augment(est_model) %>%
bind_cols(.,as.data.frame(infl_obs$is.inf)) %>%
ggplot(
aes(x=Weight,
y=.fitted)
) +
geom_point(
aes(
color = dffit
),
alpha = 0.2
) +
xlab("Observed values") +
ylab("Fitted values") +
scale_color_discrete(name="Influential\nobservation\n(DFFIT)") +
theme_bw()
Hodnoty, u kterých se predikce velmi odlišuje od pozorovaných hodnot jsou podle statistiky DFFIT jsou navrženy jako vlivné.
Tyto výsledky naznačují, že se v datech může dít něco zvláštního, nebo že nemáme k dispozici dostatečný počet pozorování z určité řásti prostoru – v tomto případě například u sportovců s vyšší hmotností.
Prvním způsobem, jak ověřit normalitu reziduí je pohledem na jejich rozdělení:
augment(est_model) %>%
ggplot(
aes(x = .resid)
) +
geom_histogram(
binwidth = 1
) +
geom_density(
aes(y = 1 * ..count..),
color = "blue"
) +
theme_linedraw()
Prostou okonometrií se zdá, že rezidua mohou být od normálního rozdělení odchýlena.
Jiný pohled poskytuje Q-Q plot, který porovnává kvantily normálního rozdělení a standardizovaných reziduí. V případě, že je rozdělení reizudí normální, by pozorování měla ležet na ose kvadrantu:
augment(est_model) %>%
ggplot(
aes(sample = .std.resid)
) +
geom_qq() +
geom_abline(
slope = 1,
intercept = 0,
color = "red"
) +
theme_linedraw()
Ani jeden okometrický test pro normalitu příliš nehovoří. Nicméně je potřeba nepodlehnout zdání. R poskytuje v balíku normtest
celou baterii statistických testů.
Například můžeme zkusit otestovat normalitu pomocí Jarque-Bera testu:
library(normtest)
residuals(est_model) %>%
jb.norm.test() -> jb
class(jb)
## [1] "htest"
print(jb)
##
## Jarque-Bera test for normality
##
## data: .
## JB = 82321, p-value < 2.2e-16
Výstupem jb.norm.test()
je objekt typu htest
. Tato třída je oblíbená pro výsledky statistických testů. Pro tuto ptřídu existují metody v balíku broom
, např.:
tidy(jb)
## statistic p.value method
## 1 82320.78 0 Jarque-Bera test for normality
Nulovou hypotézou Jarque-Bera testu je normalita, kterou tento test zamítá.
Z testů linearity je dostupný například Rainbow test, který srovnává kvalitu vyrovnání v modelech odhadnutých “uprostřed” pozorování a na celém vzroku.
V R je Rainbow test dostupný v balíku lmtest
ve funkci raintest
:
raintest(formula, fraction = 0.5, order.by = NULL, center = NULL,
data=list())
Parametr fraction
udává velikost samplu pro odhad modelu “uprostřed”. V parametru order.by
se udává proměnná, podél které se mají pozorování řadit.
Parametr formula
může být naplněn i odhadnutým lm
modelem. V tom případě není nutné zabývat se parametrem data
.
library(lmtest)
raintest(model, order.by = ~ Height, data = oly12)
##
## Rainbow test
##
## data: model
## Rain = 1.1299, df1 = 4519, df2 = 4515, p-value = 2.047e-05
Rain test v tomto případě zamítá nulovou hypotézu. To znamená, že fit na neomezeném vzroku je významně horší, než na podmožině “prostředních” pozorování. Tento výsledek indikuje možný problém s linearitou.
Pokud je porušena homoskedasticita – tzn. konstantní rozptyl náhodné složky – potom není OLS estimátor BLUE (Best linear unbiased estimator), nicméně odhady parametrů jsou stále nevychýlené (unbiased). To bohužel neplatí pro odhady jejich rozptylu – ty naopak vychýlené jsou a v důsledku jsou nedůvěryhodné i testy statistické významnosti parametrů.
est_model %>%
augment %>%
arrange(Height) %>%
ggplot(
aes(
x = Height,
y = .resid
)
) +
geom_point(
alpha = 0.1
) +
ylab("Residuals") +
theme_bw()
V reálném světě je podmínka homoskedasticity porušena velmi často. Jestli tomu tak je můžeme testovat pomocí mnoha testů. Jako příklad mohou šloužit přibuzné testy bptest()
z balíku lmtest
a ncvTest
z balíku car
:
library(lmtest)
bptest(est_model)
##
## studentized Breusch-Pagan test
##
## data: est_model
## BP = 70.186, df = 3, p-value = 3.895e-15
library(car)
ncvTest(est_model)
## Non-constant Variance Score Test
## Variance formula: ~ fitted.values
## Chisquare = 535.4902 Df = 1 p = 1.805286e-118
Oba testy testují nulovou hypotézu o homoskedasticitě oba ji suveréně zamítají – p-hodnoty obou testů jsou v podsatatě nerozlišitelné od nuly.
To je problém. Nyní už víme, že nelze věřit testům významnosti parametrů, které vrací volání summary()
– jsou totiž vychýlené:
summary(est_model)
##
## Call:
## lm(formula = model, data = oly12)
##
## Residuals:
## Min 1Q Median 3Q Max
## -56.277 -5.770 -1.352 3.754 135.731
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -103.0404 1.9867 -51.864 <2e-16 ***
## Height 94.8982 1.1355 83.572 <2e-16 ***
## Age 0.1670 0.0196 8.518 <2e-16 ***
## SexM 5.5732 0.2560 21.774 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 10.13 on 9034 degrees of freedom
## Multiple R-squared: 0.6028, Adjusted R-squared: 0.6027
## F-statistic: 4571 on 3 and 9034 DF, p-value: < 2.2e-16
Jejich odhad jde naštestí opravit použitím tzv. HC (heteroskedasticity consistent) estimátorů kovarianční matice. Ty jsou naimplementovány například v balících car
(car::hccm
) nebo sandwich
(sandwich::vcovHC
):
vcovHC(x,
type = c("HC3", "const", "HC", "HC0", "HC1", "HC2", "HC4", "HC4m", "HC5"),
omega = NULL, sandwich = TRUE, ...)
Pokud obsahuje definice funkce více možných nastavení parametru (zde například vektor v parametru
type
), potom se první z nich chápe jako defaultní nastavení.
Vstupem funkce je odhadnutý model třídy lm
. Funkce také umožňuje zvolit si v parametru type
způsob korekce heteroskedasticity. Výběr je velký (pro bližší popis viz help). Balík sandwich
obsahuje i HAC (heteroskedasticity and autocorrelation consistent) estimátory kovarianční matice ve vcovHAC
.
Výstupem funkce je kovarianční matice, kterou můžeme použít pro proveení testů významnosti odhadnutých parametrů. Ten provedeme pomocí funkce coeftest()
z balíku lmtest
.
coeftest(x, vcov. = NULL, df = NULL, ...)
coeftest()
očekává jako první argument odhadnutý model třídy lm
. V, pro nás monetálně klíčovém, parametru vcov.
se vkládá robustní kovarianční matice, nebo funkce, která se použije pro její odhad:
library(sandwich)
coeftest(est_model, vcov. = vcovHC)
##
## t test of coefficients:
##
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -103.040347 2.133328 -48.300 < 2.2e-16 ***
## Height 94.898203 1.213206 78.221 < 2.2e-16 ***
## Age 0.166981 0.018707 8.926 < 2.2e-16 ***
## SexM 5.573154 0.243771 22.862 < 2.2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
V této konfigurace je použita defaultní hodnota type
u funkce vcovHC()
. Pokud chceme zvolit jiný type
musíme do parametru vcov.
vložit již odhadnutou kovarianční matici:
coeftest(est_model, vcov. = vcovHC(est_model, type="HC0"))
##
## t test of coefficients:
##
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -103.040347 2.131765 -48.3357 < 2.2e-16 ***
## Height 94.898203 1.212318 78.2783 < 2.2e-16 ***
## Age 0.166981 0.018689 8.9348 < 2.2e-16 ***
## SexM 5.573154 0.243641 22.8744 < 2.2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
V tomto případě jsou výsledky při použití defaultní HC3
a HC0
v podstatě identické. Odhadnuté koeficienty zůstavají statisticky významné i při použití robustních chyb.
Často můžeme chtít odhadnout model v různých specifikacích, nebo na různých vzorcích dat.
Častá úloha je odhad různých modelových specifikací na stejném vzorku pozorování. Pro tyto účely doporučuji následující postup:
list
se všemi rovnicemi (specifikacemi)lapply()
Najprve vytvoříme různé modelové specifikace s pomocí funkce update()
:
list(
# Baseline model
model,
# Přidána interakce Sex*Height
model %>% update(.~. + Sex*Height),
# Přidán fixní efekt na zemi původu atleta
model %>% update(.~. + factor(Country))
) -> Models
Nyní pomocí lapply()
zavoláme lm()
na všechny prvky listu Models
– tedy na všechny modelové specifikace:
lapply(Models, lm, data = VGAMdata::oly12) -> Spec_models
lapply(Models, function(x) lm(x, data = VGAMdata::oly12)) -> Spec_models
Oba výše uvedené způsoby použití
lapply
jsou v pořádku. Z nějakého důvodu však balíkstargazer
neumí pracovat s listem odhadnutých modelů, který byl vytvořen prvním callem.
Výsledné objekty jsou nyní uložené jako položky listu Spec_models
. K nim můžeme přistupovat pomocí subsetování:
Spec_models[[2]] %>% summary
##
## Call:
## lm(formula = x, data = VGAMdata::oly12)
##
## Residuals:
## Min 1Q Median 3Q Max
## -59.075 -5.639 -1.235 3.645 135.519
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -76.03684 3.12692 -24.317 <2e-16 ***
## Height 79.11129 1.81191 43.662 <2e-16 ***
## Age 0.16446 0.01947 8.446 <2e-16 ***
## SexM -39.60004 4.06565 -9.740 <2e-16 ***
## Height:SexM 25.75678 2.31361 11.133 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 10.06 on 9033 degrees of freedom
## (1346 observations deleted due to missingness)
## Multiple R-squared: 0.6082, Adjusted R-squared: 0.608
## F-statistic: 3506 on 4 and 9033 DF, p-value: < 2.2e-16
Základní model můžeme odhadnout na různých vzorcích dat – v tomto případě pro různé sporty (proměnná Sport
).
VGAMdata::oly12 %>%
# Vyřadíme pozorování s chybějícími pozorováními na LHS
filter(!is.na(Weight)) %>%
# Pokud atlet nastoupil ve více sportech tak se zkopíruje
# na více řádků -- každý pro jiný sport (tidyr)
separate_rows(Sport,sep=",") %>%
# Na některých řádcích jsou chyby -- mezery na koncích jmen
# sportů. Ty je pro správné grupování potřeba odstranit:
rowwise() %>%
mutate(
Sport = str_trim(Sport)
) %>%
# Některé sporty (moderní gymnastika a synchronizované plavání)
# jsou otevřeny pouze ženám. Ty musíme kvůli specifikaci modelu
# vyloučit:
group_by(Sport) %>%
mutate(
Males_total = sum(Sex=="M")
) %>%
filter(Males_total != 0) %>%
# S pomocí funkce dplyr::do se model odhadne pro každou
# skupinu pozorování
do(
est_model = lm(Weight ~ Height + Age + Sex, data = .)
) -> Sport_models
Výsledná proměnná je tibble se dvěma sloupci: groupující proměnnou Sport
a est_model
, který obsahuje odhadnuté modely:
Sport_models %>% class
## [1] "rowwise_df" "tbl_df" "tbl" "data.frame"
Sport_models %>% print
## Source: local data frame [30 x 2]
## Groups: <by row>
##
## # A tibble: 30 × 2
## Sport est_model
## * <chr> <list>
## 1 Archery <S3: lm>
## 2 Athletics <S3: lm>
## 3 Badminton <S3: lm>
## 4 Basketball <S3: lm>
## 5 Beach Volleyball <S3: lm>
## 6 Canoe Slalom <S3: lm>
## 7 Canoe Sprint <S3: lm>
## 8 Cycling - BMX <S3: lm>
## 9 Cycling - Mountain Bike <S3: lm>
## 10 Cycling - Road <S3: lm>
## # ... with 20 more rows
S modely uloženými ve sloupci est_model
můžeme dále pracovat. Následující kód například opět za využití funkce do()
vytvoří tabulku se jménem sportu a adjustovaným koeficientem determinace:
Sport_models %>%
do(glance(.$est_model)) %>%
bind_cols(Sport_models,.) %>%
select(Sport,adj.r.squared) %>%
arrange(desc(adj.r.squared)) %>%
print
## # A tibble: 30 × 2
## Sport adj.r.squared
## <chr> <dbl>
## 1 Triathlon 0.8741160
## 2 Modern Pentathlon 0.8707596
## 3 Beach Volleyball 0.8694573
## 4 Canoe Sprint 0.8492032
## 5 Handball 0.8484207
## 6 Hockey 0.8355383
## 7 Basketball 0.8300793
## 8 Tennis 0.8142285
## 9 Rowing 0.8135938
## 10 Swimming 0.8044436
## # ... with 20 more rows
Je vidět, že nejvyšší \(\bar{R}^2\) má model odhadnutý na vzorku triatlonistů.
stargazer
Pro porovnání výsledků odhadů je užitečné srovnat si je vedle sebe do tabulky, jakou znáte z empirických článků. Tento úkol umí splnit balík Marka Hlaváče stargazer
.
library(stargazer)
Stargazer dokaže vytvářet a do ASCII, HTML a LaTeXu exportovat:
data.frame
bez další transformace (to je užitečné například pro export korelačních tabulek)Balík stargazer
obsahuje jedinou funkci stargazer()
, která má velké nožství parametrů – srozumitelný tutorial najdete například zde: http://jakeruss.com/cheatsheets/stargazer.html
Vstupem do funkce může být:
Vstupy mohou být vloženy jednotlivě nebo jako list
, popřípadě list
v listu
.
Tvorbu tabulky s popisnými statistikami je možné ilustrovat na minimalistickém příkladu. Vstupem je v tomto případě data.frame
. Klíčovým parametrem je summary = TRUE
, který říká stargazeru
, že má vytvořit popisné statistiky. V případě FALSE
by exportoval vstupní tabulku tak jak je.
Parametr type
udává formát, do kterého stargazer
exportuje. Možná nastavení jsou html
, text
a latex
(default). Zde nevyužítým parametrem je out
– do něj se vkládá jméno výstupního souboru.
stargazer(
VGAMdata::oly12,
summary = TRUE,
type = "html"
)
Statistic | N | Mean | St. Dev. | Min | Max |
Age | 10,384 | 26.069 | 5.441 | 13 | 71 |
Height | 9,823 | 1.769 | 0.113 | 1.320 | 2.210 |
Weight | 9,104 | 72.853 | 16.067 | 36 | 218 |
Gold | 10,384 | 0.017 | 0.136 | 0 | 2 |
Silver | 10,384 | 0.017 | 0.133 | 0 | 2 |
Bronze | 10,384 | 0.018 | 0.136 | 0 | 2 |
Total | 10,384 | 0.052 | 0.250 | 0 | 5 |
V defaultním nastavení stargazer
vrací počet pozorování, průměr, směrodatnou odchylku, minimum a maximum. Výčet statistik, stejná jako formátování tabulky, lze měnit pomocí parametrů funkce stargazer()
.
Pozor, stargazer neumí zpracovat tibble! Tabulka oly12
je tibble:
stargazer(
oly12,
summary = TRUE,
type = "html"
)
Výstup bude vypdat následovně:
Statistic | N | Mean | St. Dev. | Min | Max |
Pro správnou funkci je potřeba tabulku odtibblovat – například pomocí as.data.frame(oly12)
.
Pokud jsou vstupem stagazeru odhadnuté modely nebo jejich list, potom stargazer
automaticky vytvoří regresní tabulku. Pro ilustraci můžeme použít list Spec_models
.
Pro vytvoření regresní tabulky ze Spec_models
je vhodné použít další z mnoha parametrů funkce stargazer()
. Jeden z modelů ve Spec_models
obsahuje velké množství fixníxh efektů. Jejich odhady nás v podstatě nezajímají, ale jejich výpis by tabulku zvětšil do obrovských rozměrů. Použijeme proto parametr omit
, který umožňuje potlačit výpis parametrů, jejichž jména odpovídají zadanému regulárnímu výrazu.
V parametru omit.labels
je možné nastavit jméno, které se má použít pro signalizaci přítomnosti nevytisknutých proměnných v regresi.
stargazer(
Spec_models,
type = "html",
omit = "factor\\(Country\\)",
omit.labels = "Country FE"
)
Dependent variable: | |||
Weight | |||
(1) | (2) | (3) | |
Height | 94.898*** | 79.111*** | 95.116*** |
(1.136) | (1.812) | (1.232) | |
Age | 0.167*** | 0.164*** | 0.178*** |
(0.020) | (0.019) | (0.020) | |
SexM | 5.573*** | -39.600*** | 5.495*** |
(0.256) | (4.066) | (0.266) | |
Height:SexM | 25.757*** | ||
(2.314) | |||
Constant | -103.040*** | -76.037*** | -108.614*** |
(1.987) | (3.127) | (6.081) | |
Country FE | No | No | No |
Observations | 9,038 | 9,038 | 9,038 |
R2 | 0.603 | 0.608 | 0.628 |
Adjusted R2 | 0.603 | 0.608 | 0.619 |
Residual Std. Error | 10.128 (df = 9034) | 10.060 (df = 9033) | 9.913 (df = 8838) |
F Statistic | 4,570.826*** (df = 3; 9034) | 3,505.755*** (df = 4; 9033) | 74.902*** (df = 199; 8838) |
Note: | p<0.1; p<0.05; p<0.01 |
Za povšimnutí stojí, že stargazer
defaultně používá jinou hvězdičkovou konvenci, než je tomu ve zbytku R. Ve v tabulce vytvořené stargazerem vidíte stejnou konvenci, na kterou jste zvyklí z Gretlu. R defaultně hvězdičkami více šetří – viz summary
výše.
Stargazer poskytuje extrémně užitečnou funkcionalitu, nicméně celkově se jedná o dost nemoderní balík s velmi složitým kódem, který je náchylný k chybám a divnému chování.
Výše ukázané tabulky neobsahují robustní chyby. Protože z testů víme, že rezidua jsou heteroskedastická, musíme je do tabulek dostat.
V defaultním nastavení stargazer
získává odhad standardních chyb vnitřním voláním summary
. Uživatel však může vložit vlastní odhady robustních chyb do argumentu se
. Ten očekává list
numerických vektorů. (V nápovědě k parametru se
je v aktuální verzi chyba.)
Postup je náseldující:
get.se
, která odhadne robustní chyby a vrátí je jako vektor.lapply
aplikujeme funci get.se
na všechny modely v listu Spec_models
.se
stargazeru
.get.se <- function(x) coeftest(x, vcov. = vcovHC(x, type = "HC0")) %>% tidy %>% extract2("std.error")
se.list <- lapply(Spec_models, get.se)
stargazer(
Spec_models,
type = "html",
omit = "Country",
omit.labels = "Country FE",
se = se.list
)
Dependent variable: | |||
Weight | |||
(1) | (2) | (3) | |
Height | 94.898*** | 79.111*** | 95.116*** |
(1.212) | (1.680) | (1.359) | |
Age | 0.167*** | 0.164*** | 0.178*** |
(0.019) | (0.019) | (0.019) | |
SexM | 5.573*** | -39.600*** | 5.495*** |
(0.244) | (4.118) | (0.264) | |
Height:SexM | 25.757*** | ||
(2.349) | |||
Constant | -103.040*** | -76.037*** | -108.614*** |
(2.132) | (2.889) | (3.091) | |
Country FE | No | No | No |
Observations | 9,038 | 9,038 | 9,038 |
R2 | 0.603 | 0.608 | 0.628 |
Adjusted R2 | 0.603 | 0.608 | 0.619 |
Residual Std. Error | 10.128 (df = 9034) | 10.060 (df = 9033) | 9.913 (df = 8838) |
F Statistic | 4,570.826*** (df = 3; 9034) | 3,505.755*** (df = 4; 9033) | 74.902*** (df = 199; 8838) |
Note: | p<0.1; p<0.05; p<0.01 |
Pokud bychom chtěli vytvořit regresní tabulku z modelů odhadnutých pro jednotlivé sporty, můžeme využít toho, že data.frame
je ve své podstatě list
:
stargazer(
Sport_models$est_model,
type = "html"
)
Následující tabulka představuje pár estimačních funkcí dostupných v R.
Model/estimátor | Funkce | Poznámka |
---|---|---|
Logit/probit | glm() |
Modely diskrétní volby |
Poisson | glm() |
Count data |
Poisson pseudo-ML (PPML) | glm() |
Např. gravitační modely |
Lineární panelová data | plm::plm() |
pooled, FE, RE,… |
Arellano-Bond | plm::pgmmm() |
Dynamické panely |
Common Correlated Effects | plm::pcce() |
Např. gravitační modely |
Poisson, logit, ordered probit,… (na panelu) | pglm::pglm() |
|
Tobit | AER::tobit() |
|
2SLS | AER::ivreg() |
|
Heckit | sampleSelection::heckit() |
…a mnoho, mnoho dalších.
Hint: Funkce pro regresní analýzu jsou roztříštěny do mnoha balíků. O jejich unfikaci se snažil balík
AER
, který s sebou natáhne většinu užitečných balíků.
Odhadněte průměrný rozdíl mezi mzdami mužů a žen v ČR.
Velikost hodinové hrubé mzdy \(w_{it}\) respondenta \(i\) je vysvětlena modelem:
\[\log(w_{i}) = \beta_0 + \beta_1log(age_{i}) + \beta_2\log(experience_{i} + 0.1) + \beta_3educ_i + \beta_4gender_i + \varepsilon_i\]
kde \(age_i\) je věk respondenta; \(experience_i\) délka jeho praxe; \(educ_i\) úroveň zdělání a \(gender_i\) je pohlaví respondenta. K hodnotě proměnné \(experience\) je přičteno 0,1, aby bylo možné tuto proměnnou logaritmovat.
Pro odhad koeficientů \(\beta\) použijte OLS estimátor a data ze souboru HW_wages_select.Rdata
. Ty obsahují následující proměnné:
surveyy
– rok sběru datgender
– pohlaví (1 \(=\) žena) – kategoriální proměnnáwagegrhr
– hrubá hodinová mzdatenuexpe
– délka praxe v letecheducalmh
– úroveň vzdělání (1 = nízká, 2 = střední, 3 = vysoká) – kategoriální proměnnáage
– věk respondentaV proměnné gender
nastavte hodnotu žena
jako referenční. Pozor, některé proměnné jsou kategoriální – ve skutečnosti nejde o čísla. Ta jen kódují různé úrovně.
Máte za úkol:
Odhadněte model a výsledný objekt třídy lm
uložte do proměnné emodel
.
Vytvořte tabulku etab
(třídy data.frame
), která bude ve sloupcích obsahovat:
term
– pro identifikaci musí jméno proměnné obsahovat jméno sloupce z tabulky data
a Intercept
pro konstantu – např. SEXgender
je v pořádku, pouze SEX
nikoliv. Opravný skript by v druhém případě vyhodnotil řešení jako špatné.std.error
– použijte White-corrected standardní chyby (tj. HC0
)Do proměnné adjR2
uložte adjustovaný koeficient determinace odhadnutého modelu.
Do proměnné cor_OF
uložte korelační koeficient pozorovaných a vyrovnaných (fitted) hodnot
Pamatujte:
emodel
nezáleží.adjR2
a cor_OF
bude mít za výsledek nula bodů.