Programación Básica en R Studio

Gestión de Data Frames con el paquete paquete dplyr

Data Frames

Un data frame es una estructura de datos fundamental en estadística y en el lenguaje R. Esta estructura se organiza de manera que cada fila corresponde a una observación, mientras que cada columna representa una variable, característica, medida o atributo de dicha observación. En R, existe una implementación propia de los data frames que es, probablemente, la más utilizada.

Librería dplyr

El paquete dplyr, creado por Hadley Wickham de RStudio, es una versión optimizada y refinada de su paquete plyr. Una de sus principales aportaciones es que introduce una “gramática” (específicamente, verbos) para la manipulación de datos y el trabajo con data frames. Esta gramática permite expresar de manera clara y comprensible las operaciones que se realizan en un data frame, lo que resulta útil porque brinda una abstracción para la manipulación de datos que antes no existía. Además, otro aspecto destacado de dplyr es su alta velocidad, ya que muchas de sus funciones clave están implementadas en C++

Gramática dplyr

  • Algunos de los “verbos” clave proporcionados por el paquete dplyr son

    • select: devuelve un subconjunto de las columnas de un dataframe, utilizando una notación flexible

    • filter: extraer un subconjunto de filas de un dataframe basándose en condiciones lógicas

    • arrange: reordenar las filas de un dataframe

    • rename: renombrar las variables de un dataframe

    • mutate: añadir nuevas variables/columnas o transformar variables existentes

    • summarise: generar estadísticas de resumen de diferentes variables en el dataframe, posiblemente dentro de los estratos

    • %>%: el operador “pipe” se utiliza para conectar varias acciones verbales en una pipeline

Para ello, realicemos el proceso de instlación y llamado de la librería dplyr. Recordemos la metáfora de comprar la escoba y colocarnos a barrer. ¡Vayamos al R Studio!

select()

Para los ejemplos de esta sección utilizaremos un dataset que contiene información sobre la contaminación del aire y la temperatura de la ciudad de Chicago (ver Air pollution dataset). Todos los datos usados en este curso los podrás descargar en: RDataSets o en su defecto en la carpeta del curso

  • Después de descargar el archivo, puede cargar los datos en R utilizando la función readRDS(). Esta función proporciona los medios para guardar y restaurar un único objeto R en una conexión. Esta difiere de save y load, que guardan y restauran uno o más objetos con nombre en un entorno. Son ampliamente utilizadas por el propio R, por ejemplo para almacenar los metadatos de un paquete y para almacenar las bases de datos de help.search: la extensión de archivo ".rds" es la más utilizada. ¡Vayamos al R Studio!
chicago <- readRDS("chicago.rds")
  • Puede ver algunas características básicas del dataset con las funciones dim() y str().
dim(chicago)
## [1] 6940    8
str(chicago)
## 'data.frame':    6940 obs. of  8 variables:
##  $ city      : chr  "chic" "chic" "chic" "chic" ...
##  $ tmpd      : num  31.5 33 33 29 32 40 34.5 29 26.5 32.5 ...
##  $ dptp      : num  31.5 29.9 27.4 28.6 28.9 ...
##  $ date      : Date, format: "1987-01-01" "1987-01-02" ...
##  $ pm25tmean2: num  NA NA NA NA NA NA NA NA NA NA ...
##  $ pm10tmean2: num  34 NA 34.2 47 NA ...
##  $ o3tmean2  : num  4.25 3.3 3.33 4.38 4.75 ...
##  $ no2tmean2 : num  20 23.2 23.8 30.4 30.3 ...
  • La función select() se utiliza para elegir las columnas específicas de un dataframe en las que desea enfocarse. Es común tener un dataframe grande que incluye todos los datos, pero en un análisis particular, es posible que solo se necesite un subconjunto de variables u observaciones. Con select(), puede extraer fácilmente las columnas que son relevantes para su análisis, simplificando así el trabajo con conjuntos de datos grandes y centrándose en la información más pertinente.

  • Supongamos que queremos tomar sólo las 3 primeras columnas. Hay varias formas de hacerlo. Podríamos, por ejemplo, utilizar índices numéricos. Pero también podemos utilizar los nombres directamente.

names(chicago)[1:3]
## [1] "city" "tmpd" "dptp"
subset <- select(chicago, names(chicago)[1:3])
head(subset)
##   city tmpd   dptp
## 1 chic 31.5 31.500
## 2 chic 33.0 29.875
## 3 chic 33.0 27.375
## 4 chic 29.0 28.625
## 5 chic 32.0 28.875
## 6 chic 40.0 35.125
subset <- select(chicago, city:dptp)
head(subset)
##   city tmpd   dptp
## 1 chic 31.5 31.500
## 2 chic 33.0 29.875
## 3 chic 33.0 27.375
## 4 chic 29.0 28.625
## 5 chic 32.0 28.875
## 6 chic 40.0 35.125
  • Tenga en cuenta que el signo : normalmente no se puede utilizar con nombres o cadenas, pero dentro de la función select() puede utilizarlo para especificar un rango de nombres de variables. También puede omitir variables con la función select() utilizando el signo negativo
subset <- select(chicago, -(city:dptp))
head(subset)
##         date pm25tmean2 pm10tmean2 o3tmean2 no2tmean2
## 1 1987-01-01         NA   34.00000 4.250000  19.98810
## 2 1987-01-02         NA         NA 3.304348  23.19099
## 3 1987-01-03         NA   34.16667 3.333333  23.81548
## 4 1987-01-04         NA   47.00000 4.375000  30.43452
## 5 1987-01-05         NA         NA 4.750000  30.33333
## 6 1987-01-06         NA   48.00000 5.833333  25.77233
  • La función select() también permite una sintaxis especial que permite especificar nombres de variables basados en patrones. Así, por ejemplo, si quisiéramos conservar todas las variables que terminan con un “2”, podríamos hacer
subset <- select(chicago, ends_with("2"))
str(subset)
## 'data.frame':    6940 obs. of  4 variables:
##  $ pm25tmean2: num  NA NA NA NA NA NA NA NA NA NA ...
##  $ pm10tmean2: num  34 NA 34.2 47 NA ...
##  $ o3tmean2  : num  4.25 3.3 3.33 4.38 4.75 ...
##  $ no2tmean2 : num  20 23.2 23.8 30.4 30.3 ...
  • O si quisiéramos mantener todas las variables que empiezan por “d”, podríamos hacer
subset <- select(chicago, starts_with("d"))
str(subset)
## 'data.frame':    6940 obs. of  2 variables:
##  $ dptp: num  31.5 29.9 27.4 28.6 28.9 ...
##  $ date: Date, format: "1987-01-01" "1987-01-02" ...

filter()

  • La función filter() se utiliza para extraer subconjuntos de filas de un dataframe. Esta función es similar a la función subset() existente en R, pero es bastante más rápida. Supongamos que queremos extraer las filas del dataframe PM2.5 (la materia particulada 2.5, incluye sustancias químicas orgánicas, polvo, hollín y metales.) que sean superiores a 30 (que es un nivel razonablemente alto), podríamos hacer
chic.f <- filter(chicago, pm25tmean2 > 30)
head(chic.f, 5)
##   city tmpd dptp       date pm25tmean2 pm10tmean2  o3tmean2 no2tmean2
## 1 chic   23 21.9 1998-01-17      38.10   32.46154  3.180556  25.30000
## 2 chic   28 25.8 1998-01-23      33.95   38.69231  1.750000  29.37630
## 3 chic   55 51.3 1998-04-30      39.40   34.00000 10.786232  25.31310
## 4 chic   59 53.7 1998-05-01      35.40   28.50000 14.295125  31.42905
## 5 chic   57 52.0 1998-05-02      33.30   35.00000 20.662879  26.79861
  • Podemos colocar una secuencia lógica arbitrariamente compleja dentro de filter(), por lo que podríamos, por ejemplo, extraer las filas en las que PM2.5 es mayor que 30 y la temperatura, tmpd es mayor que 80 grados Fahrenheit

 

chic.f <- filter(chicago, pm25tmean2 > 30 & tmpd > 80)
head(chic.f, 5)
##   city tmpd dptp       date pm25tmean2 pm10tmean2 o3tmean2 no2tmean2
## 1 chic   81 71.2 1998-08-23    39.6000       59.0 45.86364  14.32639
## 2 chic   81 70.4 1998-09-06    31.5000       50.5 50.66250  20.31250
## 3 chic   82 72.2 2001-07-20    32.3000       58.5 33.00380  33.67500
## 4 chic   84 72.9 2001-08-01    43.7000       81.5 45.17736  27.44239
## 5 chic   85 72.6 2001-08-08    38.8375       70.0 37.98047  27.62743

arrange()

La función arrange() se utiliza para reorganizar las filas de un dataframe en función de una o más variables/columnas, manteniendo el orden de las demás columnas correspondiente a ese reordenamiento. En R, reordenar las filas de un dataframe puede ser una tarea complicada, pero arrange() simplifica este proceso considerablemente. Esta función permite ordenar los datos en orden ascendente o descendente utilizando la función desc() en combinación con arrange(). Por ejemplo, se puede ordenar un dataframe por la columna de fechas para que la primera fila corresponda a la observación más antigua y la última a la más reciente. Además, arrange() es parte del paquete dplyr, lo que significa que puede integrarse fácilmente en flujos de trabajo de manipulación de datos más amplios, especialmente cuando se combina con otras funciones de dplyr mediante el operador pipe %>%. Esto facilita la creación de pipelines eficientes y legibles para el procesamiento de datos.

chicago <- arrange(chicago, date)
  • Las columnas también se pueden ordenar de forma descendente utilizando el operador especial desc()
chicago <- arrange(chicago, desc(date))
head(chicago)
##   city tmpd dptp       date pm25tmean2 pm10tmean2  o3tmean2 no2tmean2
## 1 chic   35 30.1 2005-12-31   15.00000       23.5  2.531250  13.25000
## 2 chic   36 31.0 2005-12-30   15.05714       19.2  3.034420  22.80556
## 3 chic   35 29.4 2005-12-29    7.45000       23.5  6.794837  19.97222
## 4 chic   37 34.5 2005-12-28   17.75000       27.5  3.260417  19.28563
## 5 chic   40 33.6 2005-12-27   23.56000       27.0  4.468750  23.50000
## 6 chic   35 29.6 2005-12-26    8.40000        8.5 14.041667  16.81944

rename()

  • Renombrar una variable en un dataframe en R es sorprendentemente difícil de hacer. La función rename() está diseñada para facilitar este proceso. Aquí puedes ver los nombres de las cinco primeras variables del dataframe chicago. en la consola ejecutemos lo siguiente colnames(chicago) y apreciemos los nombres que contiene el df

  • La columna dptp se supone que representa la temperatura del punto de rocío y la columna pm25tmean2 proporciona los datos PM2.5. Sin embargo, estos nombres son incómodos y probablemente deban ser renombrados a algo más sencillos. Dew point: temperatura más alta a la que empieza a condensarse el vapor de agua contenido en el aire, produciendo rocío, neblina, cualquier tipo de nube o, en caso de que la temperatura sea lo suficientemente baja, escarcha.

chicago <- rename(chicago, dewpoint = dptp, pm25 = pm25tmean2)
head(chicago[, 1:5], 3)
##   city tmpd dewpoint       date     pm25
## 1 chic   35     30.1 2005-12-31 15.00000
## 2 chic   36     31.0 2005-12-30 15.05714
## 3 chic   35     29.4 2005-12-29  7.45000

Nota: La sintaxis dentro de la función rename() es tener el nuevo nombre en el lado izquierdo del signo = y el nombre antiguo en el lado derecho.

mutate()

  • La función mutate() existe para calcular las transformaciones de las variables de un dataframe. A menudo, se desea crear nuevas variables derivadas de variables existentes y mutate() proporciona una interfaz limpia para hacerlo. Por ejemplo, con los datos de contaminación atmosférica, a menudo queremos eliminar la tendencia de los datos restando la media de los mismos

  • De este modo, podemos ver si el nivel de contaminación atmosférica de un día determinado es superior o inferior a la media (en lugar de observar su nivel absoluto). Aquí creamos una variable pm25detrend que resta la media de la variable pm25.

chicago <- mutate(chicago, pm25detrend = pm25 - mean(pm25, na.rm = TRUE))
head(chicago)
##   city tmpd dewpoint       date     pm25 pm10tmean2  o3tmean2 no2tmean2
## 1 chic   35     30.1 2005-12-31 15.00000       23.5  2.531250  13.25000
## 2 chic   36     31.0 2005-12-30 15.05714       19.2  3.034420  22.80556
## 3 chic   35     29.4 2005-12-29  7.45000       23.5  6.794837  19.97222
## 4 chic   37     34.5 2005-12-28 17.75000       27.5  3.260417  19.28563
## 5 chic   40     33.6 2005-12-27 23.56000       27.0  4.468750  23.50000
## 6 chic   35     29.6 2005-12-26  8.40000        8.5 14.041667  16.81944
##   pm25detrend
## 1   -1.230958
## 2   -1.173815
## 3   -8.780958
## 4    1.519042
## 5    7.329042
## 6   -7.830958
  • También existe la función relacionada llamada transmute(), que hace lo mismo que mutate() pero luego elimina todas las variables no transformadas. En este caso, se remueve la tendencia de las variables PM10 y ozono (O3)
head(transmute(chicago, 
               pm10detrend = pm10tmean2 - mean(pm10tmean2, na.rm = TRUE),
               o3detrend = o3tmean2 - mean(o3tmean2, na.rm = TRUE)))
##   pm10detrend  o3detrend
## 1  -10.395206 -16.904263
## 2  -14.695206 -16.401093
## 3  -10.395206 -12.640676
## 4   -6.395206 -16.175096
## 5   -6.895206 -14.966763
## 6  -25.395206  -5.393846

group_by()

  • La función group_by() se utiliza para generar estadísticas de resumen del dataframe dentro de los estatus definidos por una variable. Por ejemplo, en este dataset de contaminación atmosférica, podría querer saber cuál es el nivel medio anual de PM2,5. Entonces el estatus es el año, y eso es algo que podemos derivar de la variable fecha. Junto con la función group_by() se suele utilizar la función summarize() (o summarise()` para algunas partes del mundo).

  • La operación general aquí es una combinación de dividir un dataframe en piezas separadas definidas por una variable o grupo de variables (group_by()), y luego aplicar una función de resumen en esos subconjuntos (summarize()). En primer lugar, podemos crear una variable de año utilizando as.POSIXlt(). Las opciones para días y meses respectivamente, están disponibles usando $mday y mon+1 (ver as.POSIXlt). Los valores anuales se almacenan utilizando un valor de índice base de 1900. Así, 2015 se almacena como 115 ($year = 115).

chicago <- mutate(chicago, year = as.POSIXlt(date)$year + 1900)
head(chicago)
##   city tmpd dewpoint       date     pm25 pm10tmean2  o3tmean2 no2tmean2
## 1 chic   35     30.1 2005-12-31 15.00000       23.5  2.531250  13.25000
## 2 chic   36     31.0 2005-12-30 15.05714       19.2  3.034420  22.80556
## 3 chic   35     29.4 2005-12-29  7.45000       23.5  6.794837  19.97222
## 4 chic   37     34.5 2005-12-28 17.75000       27.5  3.260417  19.28563
## 5 chic   40     33.6 2005-12-27 23.56000       27.0  4.468750  23.50000
## 6 chic   35     29.6 2005-12-26  8.40000        8.5 14.041667  16.81944
##   pm25detrend year
## 1   -1.230958 2005
## 2   -1.173815 2005
## 3   -8.780958 2005
## 4    1.519042 2005
## 5    7.329042 2005
## 6   -7.830958 2005

Ahora podemos crear un dataframe separado que divida el dataset original por año

years <- group_by(chicago, year)
years
## # A tibble: 6,940 × 10
## # Groups:   year [19]
##    city   tmpd dewpoint date        pm25 pm10tmean2 o3tmean2 no2tmean2
##    <chr> <dbl>    <dbl> <date>     <dbl>      <dbl>    <dbl>     <dbl>
##  1 chic     35     30.1 2005-12-31 15          23.5     2.53      13.2
##  2 chic     36     31   2005-12-30 15.1        19.2     3.03      22.8
##  3 chic     35     29.4 2005-12-29  7.45       23.5     6.79      20.0
##  4 chic     37     34.5 2005-12-28 17.8        27.5     3.26      19.3
##  5 chic     40     33.6 2005-12-27 23.6        27       4.47      23.5
##  6 chic     35     29.6 2005-12-26  8.4         8.5    14.0       16.8
##  7 chic     35     32.1 2005-12-25  6.7         8      14.4       13.8
##  8 chic     37     35.2 2005-12-24 30.8        25.2     1.77      32.0
##  9 chic     41     32.6 2005-12-23 32.9        34.5     6.91      29.1
## 10 chic     22     23.3 2005-12-22 36.6        42.5     5.39      33.7
## # ℹ 6,930 more rows
## # ℹ 2 more variables: pm25detrend <dbl>, year <dbl>
  • Por último, calculamos los estadísticos de resumen para cada año del dataframe con la función summarize(). La función summarize() devuelve un dataframe con year como primera columna, y luego los promedios anuales de pm25, o3 y no2.
summarize(years, pm25 = mean(pm25, na.rm = TRUE),
          o3 = max(o3tmean2, na.rm = TRUE),
          no2 = median(no2tmean2, na.rm = TRUE))
## # A tibble: 19 × 4
##     year  pm25    o3   no2
##    <dbl> <dbl> <dbl> <dbl>
##  1  1987 NaN    63.0  23.5
##  2  1988 NaN    61.7  24.5
##  3  1989 NaN    59.7  26.1
##  4  1990 NaN    52.2  22.6
##  5  1991 NaN    63.1  21.4
##  6  1992 NaN    50.8  24.8
##  7  1993 NaN    44.3  25.8
##  8  1994 NaN    52.2  28.5
##  9  1995 NaN    66.6  27.3
## 10  1996 NaN    58.4  26.4
## 11  1997 NaN    56.5  25.5
## 12  1998  18.3  50.7  24.6
## 13  1999  18.5  57.5  24.7
## 14  2000  16.9  55.8  23.5
## 15  2001  16.9  51.8  25.1
## 16  2002  15.3  54.9  22.7
## 17  2003  15.2  56.2  24.6
## 18  2004  14.6  44.5  23.4
## 19  2005  16.2  58.8  22.6

pipe %>%

  • Antes de explicar este operador, tengamos presente que una forma sencilla de sacar la pipe conjugando las siguientes teclas (ctrl + shift + m ). El operador pipeline %>% es muy útil para encadenar múltiples funciones dplyr en una secuencia de operaciones. Nótese que hasta ahora, cada vez que se desea aplicar más de una función, la secuencia quedaba dentro de una secuencia de llamadas a funciones anidadas que es difícil de leer, por ejemplo

third(second(first(x)))

  • Este anidamiento no es una forma natural de pensar en una secuencia de operaciones. El operador %>% permite encadenar las operaciones de izquierda a derecha, es decir

first(x) %>%

second %>%

third

  • Tomemos el ejemplo que acabamos de hacer en la última sección, en el que hemos calculado la media de o3 y no2 dentro de los quintiles de pm25. Allí tuvimos que

    1. Crear una nueva variable pm25.quint

    2. Agrupar el dataframe por esa nueva variable

    3. Calcular la media de o3 y no2 en los subgrupos definidos por pm25.quint

  • Eso se puede hacer con la siguiente secuencia en una sola expresión de R

library(tidyr)
#variable categórica de pm25 dividida en quintiles 

qq <- quantile(chicago$pm25, seq(0, 1, 0.20), na.rm = TRUE)
qq
##     0%    20%    40%    60%    80%   100% 
##  1.700  8.700 12.375 16.700 22.610 61.500
mutate(chicago, pm25.quint = cut(pm25, qq)) %>%
  group_by(pm25.quint) %>%
  summarize(o3 = mean(o3tmean2, na.rm = TRUE), no2 = mean(no2tmean2, na.rm = TRUE)) %>%
  drop_na()
## # A tibble: 5 × 3
##   pm25.quint     o3   no2
##   <fct>       <dbl> <dbl>
## 1 (1.7,8.7]    21.7  18.0
## 2 (8.7,12.4]   20.4  22.1
## 3 (12.4,16.7]  20.7  24.4
## 4 (16.7,22.6]  19.9  27.3
## 5 (22.6,61.5]  20.3  29.6
  • De esta manera no tenemos que crear un conjunto de variables temporales en el camino o crear una secuencia masiva anidada de llamadas a funciones. Observe en el código anterior que el dataframe chicago pasa a la primera llamada a mutate(), pero después no tiene que pasar el primer argumento a group_by() o a summarize(). Una vez que se recorre el pipeline con %>%, el primer argumento se toma como la salida del elemento anterior en la pipeline.