Regressione lineare e raggruppamento per in R


92

Voglio fare una regressione lineare in R usando la lm()funzione. I miei dati sono una serie temporale annuale con un campo per l'anno (22 anni) e un altro per lo stato (50 stati). Voglio adattare una regressione per ogni stato in modo che alla fine abbia un vettore di risposte lm. Posso immaginare di fare un ciclo for per ogni stato, quindi eseguire la regressione all'interno del ciclo e aggiungere i risultati di ciascuna regressione a un vettore. Tuttavia, non sembra molto R-like. In SAS dovrei fare un'istruzione "by" e in SQL fare un "group by". Qual è il modo R per farlo?


1
Voglio solo dire alle persone che sebbene ci siano molte funzioni group-by in R, non tutte sono quelle giuste per la regressione group-by. Ad esempio, aggregatenon è giusto ; nessuno dei due lo ètapply .
李哲源

Risposte:


46

Ecco un modo per utilizzare il lme4pacchetto.

 library(lme4)
 d <- data.frame(state=rep(c('NY', 'CA'), c(10, 10)),
                 year=rep(1:10, 2),
                 response=c(rnorm(10), rnorm(10)))

 xyplot(response ~ year, groups=state, data=d, type='l')

 fits <- lmList(response ~ year | state, data=d)
 fits
#------------
Call: lmList(formula = response ~ year | state, data = d)
Coefficients:
   (Intercept)        year
CA -1.34420990  0.17139963
NY  0.00196176 -0.01852429

Degrees of freedom: 20 total; 16 residual
Residual standard error: 0.8201316

2
C'è un modo per elencare R2 per entrambi questi due modelli? ad esempio, aggiungi una colonna R2 dopo l'anno. Aggiungi anche il valore p per ciascuno dei coeff?
ToToRo

3
@ToToRo qui puoi trovare una soluzione debole (meglio tardi che mai): Your.df [, summary (lm (Y ~ X)) $ r.squared, by = Your.factor] dove: Y, X e Your.factor sono le tue variabili. Tieni presente che Your.df deve essere una classe
data.table

59

Ecco un approccio che utilizza il pacchetto plyr :

d <- data.frame(
  state = rep(c('NY', 'CA'), 10),
  year = rep(1:10, 2),
  response= rnorm(20)
)

library(plyr)
# Break up d by state, then fit the specified model to each piece and
# return a list
models <- dlply(d, "state", function(df) 
  lm(response ~ year, data = df))

# Apply coef to each model and return a data frame
ldply(models, coef)

# Print the summary of each model
l_ply(models, summary, .print = TRUE)

Supponiamo che tu abbia aggiunto un'ulteriore variabile indipendente che non era disponibile in tutti gli stati (ad esempio miles.of.ocean.shoreline) rappresentata da NA nei tuoi dati. La chiamata lm non fallirebbe? Come potrebbe essere affrontato?
MikeTP

All'interno della funzione dovresti testare per quel caso e utilizzare una formula diversa
hadley

È possibile aggiungere il nome del sottogruppo a ciascuna chiamata nel riepilogo (ultimo passaggio)?
barbabietola

se corri layout(matrix(c(1,2,3,4),2,2)) # optional 4 graphs/page e poi l_ply(models, plot)ottieni anche ogni traccia residua. È possibile etichettare ciascuno dei grafici con il gruppo (ad esempio, "stato" in questo caso)?
Brian D

48

Dal 2009, dplyrè stato rilasciato che in realtà fornisce un modo molto carino per fare questo tipo di raggruppamento, molto simile a quello che fa SAS.

library(dplyr)

d <- data.frame(state=rep(c('NY', 'CA'), c(10, 10)),
                year=rep(1:10, 2),
                response=c(rnorm(10), rnorm(10)))
fitted_models = d %>% group_by(state) %>% do(model = lm(response ~ year, data = .))
# Source: local data frame [2 x 2]
# Groups: <by row>
#
#    state   model
#   (fctr)   (chr)
# 1     CA <S3:lm>
# 2     NY <S3:lm>
fitted_models$model
# [[1]]
# 
# Call:
# lm(formula = response ~ year, data = .)
# 
# Coefficients:
# (Intercept)         year  
#    -0.06354      0.02677  
#
#
# [[2]]
# 
# Call:
# lm(formula = response ~ year, data = .)
# 
# Coefficients:
# (Intercept)         year  
#    -0.35136      0.09385  

Per recuperare i coefficienti e Rsquared / p.value, si può utilizzare il broompacchetto. Questo pacchetto fornisce:

tre generici S3: tidy, che riassume i risultati statistici di un modello come i coefficienti di regressione; aumento, che aggiunge colonne ai dati originali come previsioni, residui e assegnazioni di cluster; e glance, che fornisce un riepilogo di una riga delle statistiche a livello di modello.

library(broom)
fitted_models %>% tidy(model)
# Source: local data frame [4 x 6]
# Groups: state [2]
# 
#    state        term    estimate  std.error  statistic   p.value
#   (fctr)       (chr)       (dbl)      (dbl)      (dbl)     (dbl)
# 1     CA (Intercept) -0.06354035 0.83863054 -0.0757668 0.9414651
# 2     CA        year  0.02677048 0.13515755  0.1980687 0.8479318
# 3     NY (Intercept) -0.35135766 0.60100314 -0.5846187 0.5749166
# 4     NY        year  0.09385309 0.09686043  0.9689519 0.3609470
fitted_models %>% glance(model)
# Source: local data frame [2 x 12]
# Groups: state [2]
# 
#    state   r.squared adj.r.squared     sigma statistic   p.value    df
#   (fctr)       (dbl)         (dbl)     (dbl)     (dbl)     (dbl) (int)
# 1     CA 0.004879969  -0.119510035 1.2276294 0.0392312 0.8479318     2
# 2     NY 0.105032068  -0.006838924 0.8797785 0.9388678 0.3609470     2
# Variables not shown: logLik (dbl), AIC (dbl), BIC (dbl), deviance (dbl),
#   df.residual (int)
fitted_models %>% augment(model)
# Source: local data frame [20 x 10]
# Groups: state [2]
# 
#     state   response  year      .fitted   .se.fit     .resid      .hat
#    (fctr)      (dbl) (int)        (dbl)     (dbl)      (dbl)     (dbl)
# 1      CA  0.4547765     1 -0.036769875 0.7215439  0.4915464 0.3454545
# 2      CA  0.1217003     2 -0.009999399 0.6119518  0.1316997 0.2484848
# 3      CA -0.6153836     3  0.016771076 0.5146646 -0.6321546 0.1757576
# 4      CA -0.9978060     4  0.043541551 0.4379605 -1.0413476 0.1272727
# 5      CA  2.1385614     5  0.070312027 0.3940486  2.0682494 0.1030303
# 6      CA -0.3924598     6  0.097082502 0.3940486 -0.4895423 0.1030303
# 7      CA -0.5918738     7  0.123852977 0.4379605 -0.7157268 0.1272727
# 8      CA  0.4671346     8  0.150623453 0.5146646  0.3165112 0.1757576
# 9      CA -1.4958726     9  0.177393928 0.6119518 -1.6732666 0.2484848
# 10     CA  1.7481956    10  0.204164404 0.7215439  1.5440312 0.3454545
# 11     NY -0.6285230     1 -0.257504572 0.5170932 -0.3710185 0.3454545
# 12     NY  1.0566099     2 -0.163651479 0.4385542  1.2202614 0.2484848
# 13     NY -0.5274693     3 -0.069798386 0.3688335 -0.4576709 0.1757576
# 14     NY  0.6097983     4  0.024054706 0.3138637  0.5857436 0.1272727
# 15     NY -1.5511940     5  0.117907799 0.2823942 -1.6691018 0.1030303
# 16     NY  0.7440243     6  0.211760892 0.2823942  0.5322634 0.1030303
# 17     NY  0.1054719     7  0.305613984 0.3138637 -0.2001421 0.1272727
# 18     NY  0.7513057     8  0.399467077 0.3688335  0.3518387 0.1757576
# 19     NY -0.1271655     9  0.493320170 0.4385542 -0.6204857 0.2484848
# 20     NY  1.2154852    10  0.587173262 0.5170932  0.6283119 0.3454545
# Variables not shown: .sigma (dbl), .cooksd (dbl), .std.resid (dbl)

2
Ho dovuto farlo rowwise(fitted_models) %>% tidy(model)per far funzionare il pacchetto della scopa, ma per il resto, ottima risposta.
pedram

3
Funziona alla grande ... può fare tutto questo senza lasciare la pipa:d %>% group_by(state) %>% do(model = lm(response ~ year, data = .)) %>% rowwise() %>% tidy(model)
holastello

23

A mio parere, un modello lineare misto è un approccio migliore per questo tipo di dati. Il codice sotto riportato nell'effetto fisso la tendenza generale. Gli effetti casuali indicano come la tendenza per ogni singolo stato differisce dalla tendenza globale. La struttura di correlazione tiene conto dell'autocorrelazione temporale. Dai un'occhiata a Pinheiro & Bates (modelli a effetti misti in S e S-Plus).

library(nlme)
lme(response ~ year, random = ~year|state, correlation = corAR1(~year))

3
Questa è un'ottima risposta sulla teoria delle statistiche generali che mi fa pensare ad alcune cose che non avevo considerato. L'applicazione che mi ha indotto a porre la domanda non sarebbe applicabile a questa soluzione, ma sono contento che tu l'abbia sollevata. Grazie.
JD Long,

1
Non è una buona idea iniziare con un modello misto: come fai a sapere che una qualsiasi delle ipotesi è giustificata?
Hadley

7
Si dovrebbe verificare l'assunzione tramite la convalida del modello (e la conoscenza dei dati). A proposito, non puoi nemmeno garantire l'ipotesi sui singoli film. Dovresti convalidare tutti i modelli separatamente.
Thierry

14

Una bella soluzione che utilizza è data.tablestata pubblicata qui in CrossValidated da @Zach. Aggiungo solo che è possibile ottenere iterativamente anche il coefficiente di regressione r ^ 2:

## make fake data
    library(data.table)
    set.seed(1)
    dat <- data.table(x=runif(100), y=runif(100), grp=rep(1:2,50))

##calculate the regression coefficient r^2
    dat[,summary(lm(y~x))$r.squared,by=grp]
       grp         V1
    1:   1 0.01465726
    2:   2 0.02256595

così come tutti gli altri output da summary(lm):

dat[,list(r2=summary(lm(y~x))$r.squared , f=summary(lm(y~x))$fstatistic[1] ),by=grp]
   grp         r2        f
1:   1 0.01465726 0.714014
2:   2 0.02256595 1.108173

8

Penso che valga la pena aggiungere l' purrr::mapapproccio a questo problema.

library(tidyverse)

d <- data.frame(state=rep(c('NY', 'CA'), c(10, 10)),
                                 year=rep(1:10, 2),
                                 response=c(rnorm(10), rnorm(10)))

d %>% 
  group_by(state) %>% 
  nest() %>% 
  mutate(model = map(data, ~lm(response ~ year, data = .)))

Vedi la risposta di @Paul Hiemstra per ulteriori idee sull'utilizzo del broompacchetto con questi risultati.


Una piccola estensione nel caso in cui si desideri una colonna di valori o residui: racchiudere la chiamata lm () in una chiamata resid () e quindi convogliare tutto nell'ultima riga in una chiamata unnest (). Ovviamente, vorresti cambiare il nome della variabile da "modello" a qualcosa di più rilevante.
randy

8
## make fake data
 ngroups <- 2
 group <- 1:ngroups
 nobs <- 100
 dta <- data.frame(group=rep(group,each=nobs),y=rnorm(nobs*ngroups),x=runif(nobs*ngroups))
 head(dta)
#--------------------
  group          y         x
1     1  0.6482007 0.5429575
2     1 -0.4637118 0.7052843
3     1 -0.5129840 0.7312955
4     1 -0.6612649 0.9028034
5     1 -0.5197448 0.1661308
6     1  0.4240346 0.8944253
#------------ 
## function to extract the results of one model
 foo <- function(z) {
   ## coef and se in a data frame
   mr <- data.frame(coef(summary(lm(y~x,data=z))))
   ## put row names (predictors/indep variables)
   mr$predictor <- rownames(mr)
   mr
 }
 ## see that it works
 foo(subset(dta,group==1))
#=========
              Estimate Std..Error   t.value  Pr...t..   predictor
(Intercept)  0.2176477  0.1919140  1.134090 0.2595235 (Intercept)
x           -0.3669890  0.3321875 -1.104765 0.2719666           x
#----------
## one option: use command by
 res <- by(dta,dta$group,foo)
 res
#=========
dta$group: 1
              Estimate Std..Error   t.value  Pr...t..   predictor
(Intercept)  0.2176477  0.1919140  1.134090 0.2595235 (Intercept)
x           -0.3669890  0.3321875 -1.104765 0.2719666           x
------------------------------------------------------------ 
dta$group: 2
               Estimate Std..Error    t.value  Pr...t..   predictor
(Intercept) -0.04039422  0.1682335 -0.2401081 0.8107480 (Intercept)
x            0.06286456  0.3020321  0.2081387 0.8355526           x

## using package plyr is better
 library(plyr)
 res <- ddply(dta,"group",foo)
 res
#----------
  group    Estimate Std..Error    t.value  Pr...t..   predictor
1     1  0.21764767  0.1919140  1.1340897 0.2595235 (Intercept)
2     1 -0.36698898  0.3321875 -1.1047647 0.2719666           x
3     2 -0.04039422  0.1682335 -0.2401081 0.8107480 (Intercept)
4     2  0.06286456  0.3020321  0.2081387 0.8355526           x

6

Ora la mia risposta arriva un po 'in ritardo, ma stavo cercando una funzionalità simile. Sembrerebbe che anche la funzione incorporata 'by' in R possa eseguire facilmente il raggruppamento:

? by contiene il seguente esempio, che si adatta per gruppo ed estrae i coefficienti con sapply:

require(stats)
## now suppose we want to extract the coefficients by group 
tmp <- with(warpbreaks,
            by(warpbreaks, tension,
               function(x) lm(breaks ~ wool, data = x)))
sapply(tmp, coef)

3

La lm()funzione sopra è un semplice esempio. A proposito, immagino che il tuo database abbia le colonne come nella seguente forma:

anno stato var1 var2 y ...

Dal mio punto di vista, puoi usare il seguente codice:

require(base) 
library(base) 
attach(data) # data = your data base
             #state is your label for the states column
modell<-by(data, data$state, function(data) lm(y~I(1/var1)+I(1/var2)))
summary(modell)

0

La domanda sembra essere su come chiamare funzioni di regressione con formule che vengono modificate all'interno di un ciclo.

Ecco come puoi farlo (usando il set di dati dei diamanti):

attach(ggplot2::diamonds)
strCols = names(ggplot2::diamonds)

formula <- list(); model <- list()
for (i in 1:1) {
  formula[[i]] = paste0(strCols[7], " ~ ", strCols[7+i])
  model[[i]] = glm(formula[[i]]) 

  #then you can plot the results or anything else ...
  png(filename = sprintf("diamonds_price=glm(%s).png", strCols[7+i]))
  par(mfrow = c(2, 2))      
  plot(model[[i]])
  dev.off()
  }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.