30 mar 2020

Nest data en R


Pese al título, esta entrada solo pretende intentar explicar las funcionalidades de una data tipo nest. Las próximas líneas presentan un marco de datos, que es una forma tradicional de una base de datos (no requiere explicación) y un nest data, en esta última agrupamos por variable/clase haciendo un collapse o summarise de la data donde variables/clases pasan a ser el id de la data. Posteriormente, las próximas celdas funcionan como lista, por ende, en ella podemos guardar diversos tipos de objetos, sin importar su dimensión y recuperar estos objetos adelante, sin necesidad de crear nuevos objetos y manteniendo una mejor organización.  

# Marco de datos
+ head(iris)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa

# nest data
> iris %>%
+   group_by(Species) %>%
+   nest()
# A tibble: 3 x 2
  Species    data            
  <fct>      <list>          
1 setosa     <tibble [50 x 4]>
2 versicolor <tibble [50 x 4]>
3 virginica  <tibble [50 x 4]>

Ahora se utiliza la función vectorizada (lapply) para estimar un modelo de regresión que se aplicara para cada una de los grupos de especies disponibles den la data:

nestdata <- iris %>%
  group_by(Species) %>%
  nest() %>%
  mutate(suma = lapply(data, function(x) sum(x[[1]])),
 MCO_model = lapply(data, function(x) lm(Sepal.Length ~ Petal.Length, data = x)))

nestdata
nestdata
# A tibble: 3 x 4
  Species    data              suma      MCO_model
  <fct>      <list>            <list>    <list>  
1 setosa     <tibble [50 x 4]> <dbl [1]> <lm>    
2 versicolor <tibble [50 x 4]> <dbl [1]> <lm>    
3 virginica  <tibble [50 x 4]> <dbl [1]> <lm>

# equivalente
lm(Sepal.Length ~ Petal.Length, data = iris[Specie== “setosa”    ,])
lm(Sepal.Length ~ Petal.Length, data = iris[Specie== “versicolor”,])
lm(Sepal.Length ~ Petal.Length, data = iris[Specie== “virginica” ,])

Al imprimir la estructura de la data nest se puede notar que en la primera columna se guarda en cada celda una lista con la información de la data correspondiente a cada variable y en la columna MCO_model se guardan los resultados de los modelos marcados como equivalentes:

nestdata$data[[1]]
# A tibble: 50 x 4
   Sepal.Length Sepal.Width Petal.Length Petal.Width
          <dbl>       <dbl>        <dbl>       <dbl>
 1          5.1         3.5          1.4         0.2
 2          4.9         3            1.4         0.2
 3          4.7         3.2          1.3         0.2
 4          4.6         3.1          1.5         0.2
 5          5           3.6          1.4         0.2
 6          5.4         3.9          1.7         0.4
 7          4.6         3.4          1.4         0.3
 8          5           3.4          1.5         0.2
 9          4.4         2.9          1.4         0.2
10          4.9         3.1          1.5         0.1
# ... with 40 more rows

nestdata$MCO_model[[1]]
Call:
lm(formula = Sepal.Length ~ Petal.Length, data = x)

Coefficients:
 (Intercept)  Petal.Length 
      4.2132        0.5423

También las funciones pueden modificarse para agregar otros elementos:

dataness <- iris %>%
  group_by(Species) %>%
  nest() %>%
  mutate(MCO_model = lapply(data, function(x) lm(Sepal.Length ~ Petal.Length, data = x)),
         rediduo =  lapply(data, function(x) lm(Sepal.Length ~ Petal.Length, data = x)$residuals))

dataness
# A tibble: 3 x 4
  Species    data              MCO_model rediduo  
  <fct>      <list>            <list>    <list>   
1 setosa     <tibble [50 x 4]> <lm>      <dbl [50]>
2 versicolor <tibble [50 x 4]> <lm>      <dbl [50]>
3 virginica  <tibble [50 x 4]> <lm>      <dbl [50]>

Note que al hacer un unnest de la data original, podemos recuperar la data original, pero con el residuo correspondiente a cada regresión anexado:

resids <- unnest(dataness, rediduo, data)
resids
# A tibble: 150 x 6
   Species rediduo Sepal.Length Sepal.Width Petal.Length Petal.Width
   <fct>     <dbl>        <dbl>       <dbl>        <dbl>       <dbl>
 1 setosa   0.128           5.1         3.5          1.4         0.2
 2 setosa  -0.0724          4.9         3            1.4         0.2
 3 setosa  -0.218           4.7         3.2          1.3         0.2
 4 setosa  -0.427           4.6         3.1          1.5         0.2
 5 setosa   0.0276          5           3.6          1.4         0.2
 6 setosa   0.265           5.4         3.9          1.7         0.4
 7 setosa  -0.372           4.6         3.4          1.4         0.3
 8 setosa  -0.0266          5           3.4          1.5         0.2
 9 setosa  -0.572           4.4         2.9          1.4         0.2
10 setosa  -0.127           4.9         3.1          1.5         0.1
# ... with 140 more rows

resids %>%
  ggplot(aes(x=Petal.Length, y=rediduo, group = Species)) +
  geom_point() +
  geom_hline(yintercept=0,col="red") +
  facet_wrap(~Species, nrow = 1, scales = "free_x")


Referencias

-          An, S. (2017). Linear regression and residuals. In: http://rstudio-pubs-static.s3.amazonaws.com/310380_07eebbdd4fac48aba8399c738ae0c31d.html.

-          Barrett, Tyson. List-columns in data.table: Nesting and unnesting data 2 tables and vectors. Utah State University.

-          Dancho, Matt (2018). Forecasting Time Series Groups in the tidyverse. In: https://cran.rstudio.com/web/packages/sweep/vignettes/SW01_Forecasting_Time_Series_Groups.html.

Creando variables por grupos en dplyr (group_by + mutate)

  Simulemos una base de hogares, donde se identifica el hogar, el sexo (1 mujer) y provincia y edad para cada miembro.   # Definir la lista ...