invisible(Sys.setlocale(locale='no_NB.utf8'))
library(tidyverse) # datahåndtering, grafikk og glimpse()
library(rsample) # for å dele data i training og testing
library(splines) # for spline-regresjon
library(Ecdat) # inneholder datasettet Clothing
library(mgcv) # for generaliserte additive modeller (GAM)2 Lineær regresjon
I dette kapittelt skal vi bruke følgende pakker:
Lineær regresjon slik vi skal bruke det her er helt vanlig regresjon slik du har lært om i grunnleggende kurs i kvantitative metoder. Altså lineær funksjon estimert med minste kvadraters metode. Alt du har lært før gjelder også her. Forskjellen er at vi nå ikke er så interessert i å tolke \(\beta\) men i den predikerte \(\hat{y}\).
2.1 Lese inn data
Vi illustrerer lineær regresjon med et empirisk eksempel. Her skal vi bruke data for norske kommuner i 2016. La oss si at vi er interessert i hvordan antall voldshendelser per 1000 innbyggere vil endre seg i en kommune. Dette kunne være relevant for langtidsplanlegging av forebygging, politibemanning, helsetjenester osv. Det kan være et område som er i stor endring slik at befolkningssammensetningen forventes å endre seg og/eller det er endrede lokale økonomiske utsikter.
Først leser vi inn dataene og tar en titt på variabellisten.
kommune <- readRDS( "data/kommunedata.rds")
glimpse(kommune)Rows: 1,529
Columns: 28
$ kommune_nr <chr> "0101", "0101", "0101", "0101", "0104", "0104",…
$ kommune <chr> "Halden (-2019)", "Halden (-2019)", "Halden (-2…
$ year <dbl> 2015, 2016, 2017, 2018, 2015, 2016, 2017, 2018,…
$ bef_18min <int> 3556, 3503, 3505, 3544, 3594, 3652, 3704, 3655,…
$ bef_18_25 <int> 3575, 3585, 3432, 3438, 3405, 3404, 3355, 3370,…
$ bef_26_35 <int> 3728, 3804, 3985, 4035, 4057, 4071, 4124, 4110,…
$ bef_totalt <int> 30328, 30544, 30790, 31037, 31802, 32182, 32407…
$ menn_18_25 <int> 1847, 1865, 1813, 1819, 1789, 1802, 1789, 1810,…
$ menn_26_35 <int> 1880, 1919, 2005, 2062, 2063, 2083, 2113, 2134,…
$ menn_36_67 <int> 7067, 7051, 7085, 7057, 7418, 7453, 7408, 7407,…
$ menn_67plus <int> 2496, 2624, 2697, 2806, 2671, 2777, 2856, 2895,…
$ menn_18min <int> 1880, 1847, 1873, 1876, 1842, 1885, 1919, 1878,…
$ kvinner_18_25 <int> 1728, 1720, 1619, 1619, 1616, 1602, 1566, 1560,…
$ kvinner_26_35 <int> 1848, 1885, 1980, 1973, 1994, 1988, 2011, 1976,…
$ kvinner_36_67 <int> 6880, 6832, 6844, 6848, 7479, 7519, 7537, 7596,…
$ kvinner_67plus <int> 3026, 3145, 3242, 3309, 3178, 3306, 3423, 3555,…
$ kvinner_18min <int> 1676, 1656, 1632, 1668, 1752, 1767, 1785, 1777,…
$ inntekt_totalt_median <int> 555000, 562000, 580000, 591000, 561000, 568000,…
$ inntekt_eskatt_median <int> 451000, 453000, 470000, 480000, 449000, 456000,…
$ ant_husholdninger <int> 13890, 14124, 14281, 14454, 15046, 15132, 15313…
$ shj_klienter <int> 1183, 1137, 1099, 1128, 1155, 1129, 1152, 1137,…
$ shj_unge <int> 262, 247, 248, 242, 267, 263, 238, 222, 307, 28…
$ vinningskriminalitet <dbl> 19.7, 18.7, 16.5, 14.5, 24.5, 21.5, 18.0, 18.0,…
$ voldskriminalitet <dbl> 11.2, 12.6, 12.3, 11.2, 7.8, 8.3, 8.7, 9.7, 6.8…
$ nark_alko_kriminalitet <dbl> 21.0, 21.9, 21.0, 20.3, 12.0, 10.2, 10.9, 10.1,…
$ ordenslovbrudd <dbl> 18.5, 16.5, 14.9, 13.7, 8.9, 9.0, 9.1, 9.2, 8.2…
$ trafikklovbrudd <dbl> 15.5, 16.3, 16.7, 19.2, 7.4, 6.3, 6.9, 8.0, 9.6…
$ andre_lovbrudd <dbl> 25.5, 26.5, 26.1, 25.2, 12.1, 12.2, 11.9, 12.4,…
2.1.1 Training og testing data
Vi starter med å dele datasettet i training og testing. Her kan vi bruke pakken ‘rsample’ og funksjonen initial_split() etterfulgt av funksjonene training() og testing(). Forhåndsvalget for splitten er 0.75, så dermed brukes 75% til training og resten til testing. Du kan også legge til argumentet prop = og sette en annen andel, f.eks. 0.7 for 70%.
Merk bruken av set.seed(). For å splitte genererer R tilfeldige tall, og seed styrer hvor den algoritmen starter. Når seed er satt vil du få nøyaktig samme resultatet som gjort her. Dette vil du trenge på eksamen for at sensor skal kunne sjekke resultatene dine, så begynn å bruke det med en gang. Tallet inni parentesen betyr ingenting1 og bare sørger for reproduserbarhet der hvor det er tilfeldige tall involvert.
set.seed(42)
kommune_split <- initial_split(kommune, prop = .7)
kommune_train <- training(kommune_split)
kommune_test <- testing(kommune_split)2.1.2 Enkel lineær regresjon i R
En ganske åpenbar faktor som forklarer forekomsten av vold er andel unge menn i kommunen. Rett og slett fordi dette er den demografiske gruppen som begår mest vold - og kriminalitet generelt, faktisk. Hvis befolkningssammensetningen forventes å bli yngre vil det medføre flere unge menn, og da kan vi kanskje forvente at det blir flere voldshendelser bare av den grunn? Sammenhengen mellom unge menn og voldsrate kan estimeres med helt vanlig lineær regresjon.
En god start på de fleste empiriske analyser er å beskrive sammenhengen med et plot. Her legger vi på en lineær regresjonslinje med geom_smooth() der vi presiserer lineær modell med method = "lm" og lar være å ta med konfidensintervallet se = FALSE.
kommune_train <- kommune_train %>%
mutate(prop_unge_menn = (menn_18_25 + menn_26_35)/bef_totalt*100)
ggplot(kommune_train, aes(x = prop_unge_menn,
y = voldskriminalitet)) +
geom_point() +
geom_smooth(method = "lm", se = FALSE)
Lineær regresjon estimeres med lm() og du kan få en enkel output med bruk av summary().
est <- lm(voldskriminalitet ~ prop_unge_menn, data = kommune_train)
summary(est)
Call:
lm(formula = voldskriminalitet ~ prop_unge_menn, data = kommune_train)
Residuals:
Min 1Q Median 3Q Max
-4.0302 -1.5764 -0.3951 1.1826 11.3367
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 0.50954 0.63482 0.803 0.422
prop_unge_menn 0.41329 0.05097 8.109 1.4e-15 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 2.248 on 1068 degrees of freedom
Multiple R-squared: 0.05799, Adjusted R-squared: 0.05711
F-statistic: 65.75 on 1 and 1068 DF, p-value: 1.395e-15
Man kan også hente ut kun \(r^2\) med følgende kode:
summary(est)$r.squared[1] 0.05799176
For ordens skyld: I tidligere metodekurs har du kanskje lært å få ut en penere regresjonstabell med f.eks. gtsummary-pakken. (Det finnes andre pakker også som gjør tilsvarende). Hvordan output er formatert spiller ingen rolle. I denne sammenhengen har vi lite bruk for en pen regresjonstabell da \(\beta\) ikke er av primær interesse.
Med andre ord kan voldsraten beskrives som:
\[ \operatorname{\widehat{voldskriminalitet}} = 0.51 + 0.41(\operatorname{prop\_unge\_menn}) \]
Men vi har også sett at \(r^2\) er ganske lav, bare 0.058. Denne koeffisienten kalles også “coefficient of determination” og sier noe om i hvor stor grad modellen fanger opp variasjoenen i dataene. En lav \(r^2\) betyr at modellen i liten grad gjør det. Vi må altså forvente at modellen vil bomme ganske kraftig i sine prediksjoner. Vi kan velge å ta modellen seriøst likevel, men ikke ha for store forventninger for prediksjonene!
Et annet mål på hvor godt modellen treffer er “Root mean square error”, RMSE. Dette kan skrives som:
\[ rmse = \sqrt{ \frac{ \sum{(O_i-P_i)^2} }{N} } \]
der \(O\) er de observerte verdiene og \(P\) er de predikerte verdiene for observasjon \(i\). Merk at \((O_i-P_i)\) er residualene. I R kan vi hente ut residualene fra regresjons-objektet med dollartegnet ...$res etter objektnavnet. Da kan du regne ut RMSE som følger:
rmse <- sqrt(mean(est$res^2))
rmse[1] 2.245685
RMSE sier altså omtrentlig hvor mye modellen i gjennomsnitt bommer på de observerte verdiene. 2. Hvorvidt det er presist nok eller ikke vil vel strengt tatt komme an på behovet for presisjon, altså: hva man skal bruke det til.
For å få litt bedre tak på hva RMSE betyr kan vi se på et plot av de predikerte og observerte verdiene. Vi kan predikere vold for hver enkelt kommune basert på denne modellen, som altså er den forventede voldsraten hvis modellen er sann. Funksjonen predict() gir oss hva vi trenger.
kom <- kommune_train %>%
mutate(pred = predict(est))Merk at koden her lagde en kopi av datasettet der vi har alle de opprinnelige variablene pluss en variabel med de predikerte verdiene. Vi kan nå sammenlignet prediksjonene med de observerte utfallene.
ggplot(kom, aes(x = voldskriminalitet, y = pred)) +
geom_point(alpha = .3) +
geom_smooth(method = "lm", se = FALSE)
Hvis prediksjonen hadde vært perfekt ville disse punktene ligget på linja, noe den jo ikke gjør. Modellen bommer altså ganske mye.
Hva hvis vi vil vite forventet voldsrate for en kommune for en gitt andel unge menn? Løsningen er å lage et nytt datasett med de verdiene vi er interessert i og så predikere for dette datasettet med å spesifisere newdata = dt. Her er et eksempel der vi ønsker å vite voldsraten hvis andelen unge menn er 15%.
dt <- data.frame(prop_unge_menn = 15)
p <- predict(est, newdata = dt)
p 1
6.708919
I følge modellen vil altså en kommune der 15% av populasjonen er unge menn ha en 6.709 voldshendelser per 1000 innbyggere. Fra tradisjonell statistikk vet vi jo at det er usikkerhet knyttet til dette estimatet og vi kan også ta det med i beregningen her. Vanligvis vil man estimere med et konfidensintervall, som gjelder hvis man estimerer et gjennomsnitt i en gruppe. Her skal vi derimot predikere for en enkelt kommune, som da har større usikkerhet enn om man estimerer for en enkelt observasjon. Dette kalles prediksjonsintervall og må spesifiseres i koden. Hvis det ikke er gitt vil R gi konfidensintervallet.
p_ki <- predict(est, newdata = dt, interval = "prediction")
p_ki fit lwr upr
1 6.708919 2.288514 11.12932
Tolkningen er ellers tilsvarende som for konfidensintervall: vi forventer med “95% sannsynlighet”3 at voldsraten vil være mellom 2.3 og 11.1 per 1000 innbyggere.
2.1.3 Multippel regresjon
Enkel regresjon er nettopp enkel og prediksjonen blir ikke så god. Men vi kan komplisere vesentlig ved å inkludere flere variable og bruke alle triksene man evt. har lært om multippel regresjon tidligere, primært interaksjonsledd, polynomer og transformasjoner osv. (Det er ok om du ikke har lært alt dette tidligere).
I R vil vi da bare legge til flere variabelnavn i formelen. Ellers er det meste likt som for enkel lineær regresjon.
est_m <- lm(voldskriminalitet ~ prop_unge_menn + inntekt_totalt_median + shj_unge +
ant_husholdninger ,
data = kommune_train)
summary(est_m)
Call:
lm(formula = voldskriminalitet ~ prop_unge_menn + inntekt_totalt_median +
shj_unge + ant_husholdninger, data = kommune_train)
Residuals:
Min 1Q Median 3Q Max
-4.6862 -1.3599 -0.2042 1.0689 12.4822
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 7.421e+00 7.333e-01 10.121 < 2e-16 ***
prop_unge_menn 4.177e-01 5.328e-02 7.840 1.09e-14 ***
inntekt_totalt_median -1.099e-05 8.539e-07 -12.871 < 2e-16 ***
shj_unge 4.461e-03 1.016e-03 4.392 1.23e-05 ***
ant_husholdninger -2.163e-05 8.361e-06 -2.587 0.00983 **
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 2.037 on 1065 degrees of freedom
Multiple R-squared: 0.2288, Adjusted R-squared: 0.2259
F-statistic: 79 on 4 and 1065 DF, p-value: < 2.2e-16
Merk at \(r^2\) nå har gått betraktelig opp, til ca 0.23. Gitt at vi tolker dette som i hvor stor grad vi kan predikere utfallet fra datasettet, så er det kanskje likevel ikke imponerende høyt: vi vil fremdeles forvente mye feil prediksjon.
Her er et scatterplot av observert mot forventet voldsrater:
kom_pred <- kommune_train %>%
mutate(pred = predict(est_m))
ggplot(kom_pred, aes(x = voldskriminalitet, y = pred)) +
geom_point() +
geom_smooth(method = "lm", se = FALSE)
2.1.4 Sammenligning av modeller med ulik kompleksitet
La oss nå systematisk sammenligne flere modeller med ulik kompleksitet. Vi lager først et datasett med utvalgte variable og bruker formelen voldskriminalitet ~ . for å ta med alle variable.
kom_s <- kommune_train %>%
select(-c(kommune, kommune_nr, ordenslovbrudd,
nark_alko_kriminalitet, trafikklovbrudd, andre_lovbrudd,
prop_unge_menn))
full_mod <- lm(voldskriminalitet ~ . , data = kom_s)
summary(full_mod)
Call:
lm(formula = voldskriminalitet ~ ., data = kom_s)
Residuals:
Min 1Q Median 3Q Max
-3.9650 -1.2333 -0.1813 0.8919 12.2114
Coefficients: (4 not defined because of singularities)
Estimate Std. Error t value Pr(>|t|)
(Intercept) -1.198e+02 9.309e+01 -1.287 0.19851
year 6.234e-02 4.646e-02 1.342 0.17998
bef_18min 3.156e-03 1.839e-03 1.716 0.08637 .
bef_18_25 -1.885e-05 1.513e-03 -0.012 0.99006
bef_26_35 1.180e-03 1.006e-03 1.173 0.24109
bef_totalt -2.272e-03 7.130e-04 -3.187 0.00148 **
menn_18_25 3.698e-03 2.136e-03 1.731 0.08373 .
menn_26_35 -1.481e-03 2.013e-03 -0.736 0.46197
menn_36_67 2.710e-03 9.461e-04 2.865 0.00425 **
menn_67plus 1.654e-03 1.339e-03 1.236 0.21676
menn_18min 2.461e-03 2.875e-03 0.856 0.39223
kvinner_18_25 NA NA NA NA
kvinner_26_35 NA NA NA NA
kvinner_36_67 -1.269e-04 9.021e-04 -0.141 0.88816
kvinner_67plus NA NA NA NA
kvinner_18min NA NA NA NA
inntekt_totalt_median -4.693e-05 7.600e-06 -6.176 9.40e-10 ***
inntekt_eskatt_median 5.580e-05 1.108e-05 5.037 5.55e-07 ***
ant_husholdninger 1.555e-03 3.169e-04 4.908 1.07e-06 ***
shj_klienter 2.170e-03 9.967e-04 2.177 0.02968 *
shj_unge 2.191e-03 3.054e-03 0.718 0.47322
vinningskriminalitet 1.066e-01 1.014e-02 10.513 < 2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 1.891 on 1052 degrees of freedom
Multiple R-squared: 0.343, Adjusted R-squared: 0.3324
F-statistic: 32.3 on 17 and 1052 DF, p-value: < 2.2e-16
\(r^2\) gikk noe opp, til 0.343.
2.1.5 Eksempel på overfitting
Men vi kan gjøre modellen ekstra komplisert ved inkludere alle mulige interaksjonsledd. En åpenbar ulempe med dette er at hver enkelt koeffisent blir svært mye vanskeligere å tolke. Vi fokuserer derfor kun på \(r^2\) og RMSE.
Her bygger vi fire modeller med økende kompleksitet og sammenligner RMSE for training-data:
est1 <- est # enkel modell med bare prop_unge_menn (estimert over)
est2 <- est_m # multippel regresjon (estimert over)
est3 <- lm(voldskriminalitet ~ .^2, data = kom_s) # alle 2-veis interaksjoner
est4 <- lm(voldskriminalitet ~ .^3, data = kom_s) # alle 3-veis interaksjonerRMSE for training-data:
sqrt(mean(est1$res^2))[1] 2.245685
sqrt(mean(est2$res^2))[1] 2.031873
sqrt(mean(est3$res^2))[1] 1.598957
sqrt(mean(est4$res^2))[1] 0.9963221
For training-data ser vi altså at RMSE synker for hver modell. Mer kompleksitet gir tilsynelatende bedre tilpassning. Men hva skjer med testing-data?
2.1.6 Predikere for nye data
Nå har vi bare sett på hvordan prediksjonen fungerer på training-datasettet. Vi må sjekke med testing-datasettet. Det lagde vi i begynnelsen, så nå henter vi det frem. I predict() må vi nå angi newdata =. Vi predikerer fra alle fire modellene samtidig og regner ut RMSE for testing-data:
kommune_test <- kommune_test %>%
mutate(prop_unge_menn = (menn_18_25 + menn_26_35)/bef_totalt*100)
kommune_pred <- kommune_test %>%
mutate(pred1 = predict(est1, newdata = .),
pred2 = predict(est2, newdata = .),
pred3 = predict(est3, newdata = .),
pred4 = predict(est4, newdata = .))
rmse_test <- kommune_pred %>%
mutate(res1 = pred1 - voldskriminalitet,
res2 = pred2 - voldskriminalitet,
res3 = pred3 - voldskriminalitet,
res4 = pred4 - voldskriminalitet) %>%
summarise(RMSE_enkel = sqrt(mean(res1^2)),
RMSE_multippel = sqrt(mean(res2^2)),
RMSE_2veis = sqrt(mean(res3^2)),
RMSE_3veis = sqrt(mean(res4^2)))
rmse_test RMSE_enkel RMSE_multippel RMSE_2veis RMSE_3veis
1 2.147951 1.876014 23.41166 7109.869
Her ser vi at de mest kompliserte modellene gir dårligere prediksjon på nye data! La oss også plotte predikert mot observert for den mest kompliserte modellen:
ggplot(kommune_pred, aes(x = voldskriminalitet, y = pred4)) +
geom_point(alpha = .3) +
geom_smooth(method = "lm", se = FALSE)
Ser dette rart ut? Det er noen observasjoner som har predikert skyhøy voldsrate! Disse prediksjonene bommer voldsomt.
Sammenlignet er den enklere multippelregresjonsmodellen vesentlig bedre på nye data:
ggplot(kommune_pred, aes(x = voldskriminalitet, y = pred2)) +
geom_point(alpha = .3) +
geom_smooth(method = "lm", se = FALSE)
Dette er en ganske ekstrem variant av overfitting. Ved å formulere en veldig komplisert modell som passer veldig godt til training-dataene kan vi ende opp med en modell som passer veldig, veldig dårlig til nye data. Hvis man skulle utforme f.eks. politikktiltak på grunnlag av en slik prediksjon vil det kunne gå riktig så dårlig.
Det er altså slik at en enklere modell kan passe til nye data langt bedre enn en veldig komplisert modell. Grunnen er overfitting: modellen fanger opp vel så mye tilfeldig støy som det underliggende mønsteret. Støyen vil jo være annerledes i de nye dataene.
2.2 Splines
I modellene over har vi antatt en lineær sammenheng mellom prediktor og utfall. Men hva hvis sammenhengen ikke er lineær? En mulighet er å bruke splines, som er stykkevis definerte funksjoner som kan tilpasse mer fleksible former. Splines er nyttige for prediksjon fordi de kan fange opp ikke-lineære mønstre i dataene uten at vi trenger å spesifisere funksjonsformen på forhånd.
Vi bruker datasettet Clothing fra pakken Ecdat som eksempel. Variabelen tsales er totalt salg og inv2 er investeringer.
data(Clothing)
glimpse(Clothing)Rows: 400
Columns: 13
$ tsales <int> 750000, 1926395, 1250000, 694227, 750000, 400000, 1300000, 495…
$ sales <dbl> 4411.765, 4280.878, 4166.667, 2670.104, 15000.000, 4444.444, 3…
$ margin <dbl> 41, 39, 40, 40, 44, 41, 39, 28, 41, 37, 32, 30, 39, 37, 34, 44…
$ nown <dbl> 1.0000, 2.0000, 1.0000, 1.0000, 2.0000, 2.0000, 1.2228, 2.0000…
$ nfull <dbl> 1.0000, 2.0000, 2.0000, 1.0000, 1.9556, 1.9556, 1.0000, 1.9556…
$ npart <dbl> 1.0000, 3.0000, 2.2222, 1.2833, 1.2833, 1.2833, 3.0000, 1.2833…
$ naux <dbl> 1.5357, 1.5357, 1.4091, 1.3673, 1.3673, 1.3673, 4.0000, 1.3673…
$ hoursw <int> 76, 192, 114, 100, 104, 72, 161, 80, 158, 87, 156, 40, 96, 305…
$ hourspw <dbl> 16.755960, 22.493760, 17.191200, 21.502600, 15.742790, 10.8988…
$ inv1 <dbl> 17166.67, 17166.67, 292857.20, 22207.04, 22207.04, 22207.04, 2…
$ inv2 <dbl> 27177.04, 27177.04, 71570.55, 15000.00, 10000.00, 22859.85, 22…
$ ssize <int> 170, 450, 300, 260, 50, 90, 400, 100, 450, 75, 120, 40, 90, 11…
$ start <dbl> 41, 39, 40, 40, 44, 41, 39, 28, 41, 37, 32, 30, 39, 37, 34, 44…
La oss først se på sammenhengen med et scatterplot og en lineær regresjonslinje:
p0 <- ggplot(Clothing, aes(inv2, tsales)) +
geom_point() +
geom_smooth(method = "lm", se = FALSE, col = "gray")
p0
Sammenhengen ser ikke helt lineær ut. Vi kan prøve en piecewise linear spline der vi angir knuter (knots) der funksjonen skifter retning. Funksjonen bs() fra pakken splines brukes til dette.
2.2.1 Piecewise linear spline
Med degree = 1 får vi en stykkevis lineær funksjon, altså rette linjer mellom knutene:
model1 <- lm(tsales ~ bs(inv2, knots = c(12000, 60000, 150000), degree = 1), data = Clothing)
summary(model1)
Call:
lm(formula = tsales ~ bs(inv2, knots = c(12000, 60000, 150000),
degree = 1), data = Clothing)
Residuals:
Min 1Q Median 3Q Max
-983725 -333443 -97927 178664 3670029
Coefficients:
Estimate Std. Error
(Intercept) 717496 84585
bs(inv2, knots = c(12000, 60000, 150000), degree = 1)1 78041 107382
bs(inv2, knots = c(12000, 60000, 150000), degree = 1)2 183187 134824
bs(inv2, knots = c(12000, 60000, 150000), degree = 1)3 761569 242818
bs(inv2, knots = c(12000, 60000, 150000), degree = 1)4 804300 365549
t value Pr(>|t|)
(Intercept) 8.483 4.47e-16 ***
bs(inv2, knots = c(12000, 60000, 150000), degree = 1)1 0.727 0.46780
bs(inv2, knots = c(12000, 60000, 150000), degree = 1)2 1.359 0.17501
bs(inv2, knots = c(12000, 60000, 150000), degree = 1)3 3.136 0.00184 **
bs(inv2, knots = c(12000, 60000, 150000), degree = 1)4 2.200 0.02837 *
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 572000 on 395 degrees of freedom
Multiple R-squared: 0.0486, Adjusted R-squared: 0.03897
F-statistic: 5.044 on 4 and 395 DF, p-value: 0.0005649
2.2.2 B-spline (kubisk)
Med degree = 3 får vi en glattere, kubisk funksjon:
model2 <- lm(tsales ~ bs(inv2, knots = c(12000, 60000, 150000), degree = 3), data = Clothing)
summary(model2)
Call:
lm(formula = tsales ~ bs(inv2, knots = c(12000, 60000, 150000),
degree = 3), data = Clothing)
Residuals:
Min 1Q Median 3Q Max
-921708 -329164 -125551 223018 3598276
Coefficients:
Estimate Std. Error
(Intercept) 419589 136370
bs(inv2, knots = c(12000, 60000, 150000), degree = 3)1 712180 213810
bs(inv2, knots = c(12000, 60000, 150000), degree = 3)2 63428 140939
bs(inv2, knots = c(12000, 60000, 150000), degree = 3)3 847253 269728
bs(inv2, knots = c(12000, 60000, 150000), degree = 3)4 1308842 707178
bs(inv2, knots = c(12000, 60000, 150000), degree = 3)5 -14067 996832
bs(inv2, knots = c(12000, 60000, 150000), degree = 3)6 1345263 419450
t value Pr(>|t|)
(Intercept) 3.077 0.002238 **
bs(inv2, knots = c(12000, 60000, 150000), degree = 3)1 3.331 0.000948 ***
bs(inv2, knots = c(12000, 60000, 150000), degree = 3)2 0.450 0.652929
bs(inv2, knots = c(12000, 60000, 150000), degree = 3)3 3.141 0.001810 **
bs(inv2, knots = c(12000, 60000, 150000), degree = 3)4 1.851 0.064949 .
bs(inv2, knots = c(12000, 60000, 150000), degree = 3)5 -0.014 0.988748
bs(inv2, knots = c(12000, 60000, 150000), degree = 3)6 3.207 0.001450 **
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 562100 on 393 degrees of freedom
Multiple R-squared: 0.08582, Adjusted R-squared: 0.07186
F-statistic: 6.149 on 6 and 393 DF, p-value: 3.54e-06
For å visualisere forskjellen lager vi et datasett med predikerte verdier for begge modellene:
inv2.grid <- data.frame(inv2 = seq(min(Clothing$inv2), max(Clothing$inv2)))
pred1 <- predict(model1, newdata = inv2.grid, se = TRUE, interval = "prediction")$fit %>%
bind_cols(inv2.grid)
pred2 <- predict(model2, newdata = inv2.grid, se = TRUE, interval = "prediction")$fit %>%
bind_cols(inv2.grid)
ggplot(Clothing, aes(inv2, tsales)) +
geom_point() +
geom_smooth(method = "lm", se = FALSE, col = "gray") +
geom_line(data = pred1, aes(y = fit), linewidth = 1, col = "blue") +
geom_line(data = pred2, aes(y = fit), linewidth = 1, col = "red")
Den blå linjen er den stykkevis lineære spline og den røde er den kubiske. Begge tilpasser dataene bedre enn den rette linjen.
2.2.3 GAM med smoothing spline
En enda enklere tilnærming er å bruke gam() fra mgcv-pakken med s() som automatisk velger en god smoothing:
model3 <- gam(tsales ~ s(inv2), data = Clothing)
summary(model3)
Family: gaussian
Link function: identity
Formula:
tsales ~ s(inv2)
Parametric coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 833584 28433 29.32 <2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Approximate significance of smooth terms:
edf Ref.df F p-value
s(inv2) 3.903 4.748 4.844 0.000409 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
R-sq.(adj) = 0.0501 Deviance explained = 5.94%
GCV = 3.2739e+11 Scale est. = 3.2338e+11 n = 400
pred3 <- inv2.grid %>%
mutate(fit = predict(model3, newdata = inv2.grid))
ggplot(Clothing, aes(inv2, tsales)) +
geom_point() +
geom_smooth(method = "lm", se = FALSE, col = "gray") +
geom_line(data = pred1, aes(y = fit), col = "blue") +
geom_line(data = pred2, aes(y = fit), col = "red") +
geom_line(data = pred3, aes(y = fit), col = "purple")
Den lilla linjen er GAM-modellen. Fordelen med GAM er at du ikke trenger å velge knuter selv – funksjonen finner en god tilpassning automatisk.
2.2.4 GAM med kommunedata
Generaliserte additive modeller er i utgangspunktet som lineær regresjon, men inkluderer en eller flere smoothing-parametre. Disse smoothing-parametrene “lærer” funksjonsformen fra dataene i stedet for å anta en lineær sammenheng. Funksjonen gam() fra pakken mgcv brukes for dette, og s() rundt en variabel angir at den skal ha en fleksibel form.
Her er et eksempel der vi bruker GAM med noen utvalgte variable fra kommunedataene:
gam_mod <- gam(voldskriminalitet ~ s(prop_unge_menn) + s(inntekt_totalt_median) + s(shj_unge),
data = kommune_train)
summary(gam_mod)
Family: gaussian
Link function: identity
Formula:
voldskriminalitet ~ s(prop_unge_menn) + s(inntekt_totalt_median) +
s(shj_unge)
Parametric coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 5.62673 0.05937 94.77 <2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Approximate significance of smooth terms:
edf Ref.df F p-value
s(prop_unge_menn) 1.000 1.000 68.21 <2e-16 ***
s(inntekt_totalt_median) 5.197 6.339 37.48 <2e-16 ***
s(shj_unge) 4.478 5.462 22.78 <2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
R-sq.(adj) = 0.296 Deviance explained = 30.3%
GCV = 3.8136 Scale est. = 3.772 n = 1070
Vi kan plotte de estimerte smoothing-funksjonene for å se hvordan sammenhengen ser ut:
plot(gam_mod)


Merk at s() tilpasser en fleksibel kurve til dataene. Fordelen med GAM er at vi ikke trenger å anta en lineær sammenheng. Ulempen er at det er vanskeligere å tolke koeffisientene direkte, men for prediksjon er dette ofte ikke et problem.
2.2.5 Oppsummerende kommentar
Det gjelder generelt at mer kompliserte regresjonsmodeller vil gi tilsynelatende bedre tilpassning til data, enten man måler med \(r^2\) eller RMSE. Man kan riktignok bruke mål på tilpassning som justerer for antall parametre etc (f.eks. justert \(r^2\), AIC eller BIC), som kanskje kan bedre dette noe, men det vil ofte lede til overfitting likevel.
Det er derfor veldig viktig å teste modellen mot nye data – i praksis testing-dataene man lagde til å begynne med. Splines og GAM gir mer fleksible modeller enn vanlig lineær regresjon, men de er langt fra så risikable som f.eks. 3-veis interaksjoner. Man kan godt bruke interaksjonsledd, polynomer, splines og andre verktøy. Men med måte.
Bortsett fra for dem som mener det er svaret på “the Ultimate Question of Life, the Universe, and Everything”↩︎
Denne formuleringen er ganske omtrentlig. RMSE er egentlig kvadratroten av gjennomsnittet til de kvadrerte residualene, som er noe litt annet enn gjennomsnittet av de absolutte verdiene av residualene. Det gir bl.a. litt mer vekt til store residualer enn et vanlig gjennomsnitt.↩︎
Dette er en omtrentelig formulering. Alle sannsynligheter gjelder i det lange løp: altså hvis man gjør undersøkelsen veldig mange ganger.↩︎