ETL con Tidyverse

El Tidyverse es un conjunto de paquetes integrados para el lenguaje de programación R, diseñados con el objetivo de facilitar el análisis de datos de manera estructurada, legible y eficiente. Su filosofía se basa en el concepto de “datos ordenados” (tidy data), donde cada variable es una columna, cada observación una fila, y cada tipo de unidad observacional forma una tabla.

Estos paquetes comparten principios de diseño comunes y una gramática coherente, lo que permite a los usuarios aprender un conjunto de reglas aplicables en todo el ecosistema, aumentando así la productividad y la claridad del código.

Paquetes de tidyverse

Paquetes de tidyverse

Data Frames

Definición

Un data frame es una estructura de datos clave en estadística y en R.

  • La estructura básica de un data frame es que hay una observación por fila y cada columna representa una variable, medida, rasgo o característica de esa observación.
  • R tiene una implementación interna de los data frames que probablemente es la que más se utiliza en la práctica.

Importación de datos

Fuentes de datos en R

Los conjuntos de datos de gran tamaño, generalmente almacenados como data frames en R, suelen provenir de archivos externos.
Existen múltiples tipos de archivos que pueden importarse, entre ellos:

  • Archivos de texto en formatos como csv, txt, html y json.
  • Salidas de software estadístico como SAS y SPSS.
  • Recursos en línea como páginas html y servicios web.
  • Bases de datos relacionales y no relacionales.

El ecosistema Tidyverse ofrece funciones que permiten importar y gestionar estas diversas fuentes de datos de manera sencilla y eficiente.

Uso de readr de tidyverse

  • Veamos el siguiente ejemplo. Carguemos los siguientes datos:
library(tidyverse)

# CSV con punto decimal y encabezados
StudyArea <- read_csv("RDataSets/RDataSets/StudyArea.csv")

Transformación de datos

  • Antes de realizar un análisis de datos en R, con frecuencia es necesario manipular o transformar la información de distintas formas.
    Para ello, el paquete dplyr, que hace parte del ecosistema tidyverse, ofrece un conjunto de funciones que facilitan la transformación y manejo de datos de manera eficiente y estructurada.

  • En esta sección abordaremos los siguientes aspectos fundamentales:

    • Filtrar registros para obtener subconjuntos de datos
    • Seleccionar y limitar columnas específicas
    • Ordenar filas en forma ascendente o descendente
    • Incorporar nuevas filas a un conjunto existente
    • Resumir y agrupar información
    • Utilizar canalización (pipes) para mejorar la legibilidad y eficiencia del código

El paquete dplyr

Filtrar datos para crear un subconjunto

  • Crear un subconjunto de registros que contenga sólo los incendios forestales de más de \(25000\) acres.
library(tidyverse)

StudyArea %>% 
  filter(TOTALACRES >= 25000)
  • Crear un subconjunto de registros que contenga sólo los incendios forestales de más de \(1000\) acres en el año \(2016\).
StudyArea %>% 
  filter(TOTALACRES >= 1000,YEAR_== 2016)
  • Crear un subconjunto de registros que contenga sólo los incendios forestales de más de \(1000\) acres en el año \(2016\), pero usa el operador &.
StudyArea %>% 
  filter(TOTALACRES >= 1000 & YEAR_== 2016)
  • Crear un subconjunto de registros que contenga sólo los años \(2010\), \(2011\) y \(2012\)
StudyArea %>% 
  filter(YEAR_ %in% c(2010, 2011, 2012))

Acotar la lista de columnas con select()

Continuando con el ejemplo de las incendios forestales

  • Selecciona las columnas FIRENAME, TOTALACRES, YEAR_
StudyArea %>% 
  select(FIRENAME, TOTALACRES, YEAR_)
  • Selecciona las columnas FIRENAME, TOTALACRES, YEAR_, cambian el nombre de TOTALACRES por ACRES y YEAR_ por YR.
StudyArea %>% 
  select("FIRE" = "FIRENAME", "ACRES" ="TOTALACRES", "YR" = "YEAR_")
  • Selecciona las columnas que contengan la palabra DATE
StudyArea %>% 
  select(contains("DATE"))
  • Selecciona las columnas que contengan la palabra DATE y también inicien con TOTAL
StudyArea %>% 
  select(contains("DATE"),starts_with("TOTAL"))

Organizar las filas

Continuando con el ejemplo de los incendios forestales

  • Filtrar el conjunto de datos para que contenga solo los incendios de más de 1.000 acres quemados del año 2016. Despues selecciona las columnas FIRENAME, TOTALACRES, YEAR_ y renombralas NAME, ACRES, YR, respectivamente. Finalmente, ordena ACRES de forma ascendente y muestrame las 5 ultimas.
StudyArea %>% 
  filter(TOTALACRES >= 1000, YEAR_ == 2016) %>% 
  select("NAME" = "FIRENAME", "ACRES" = "TOTALACRES", "YR" = "YEAR_") %>%  
  arrange(ACRES) %>% 
  tail(n=5)
## # A tibble: 5 × 3
##   NAME      ACRES    YR
##   <chr>     <dbl> <dbl>
## 1 Cedar     45977  2016
## 2 Erskine   48007  2016
## 3 Range 12 171915  2016
## 4 Junkins  181320  2016
## 5 PIONEER  188404  2016
  • Filtrar el conjunto de datos para que contenga solo los incendios de más de 2.000 acres quemados del año 2016. Despues selecciona las columnas FIRENAME, TOTALACRES, YEAR_ y renombralas NAME, ACRES, YR, respectivamente. Finalmente, ordena ACRES de forma descendente y muestrame las 5 primeras.
StudyArea %>% 
  filter(TOTALACRES >= 2000, YEAR_ == 2016) %>% 
  select("NAME" = "FIRENAME", "ACRES" = "TOTALACRES", "YR" = "YEAR_") %>%   arrange(desc(ACRES)) %>% 
  head(n=5)
## # A tibble: 5 × 3
##   NAME      ACRES    YR
##   <chr>     <dbl> <dbl>
## 1 PIONEER  188404  2016
## 2 Junkins  181320  2016
## 3 Range 12 171915  2016
## 4 Erskine   48007  2016
## 5 Cedar     45977  2016

Añadir columnas con mutate

Continuando con el ejemplo de los incendios forestales

  • Seleccione únicamente las columnas ORGANIZATI, STATE, YEAR_, TOTALACRES, CAUSE y STARTDATED, filtre los registros para que solo se incluyan los incendios con más de 1.000 acres quemados y cuya causa sea Humana o Natural, cree una nueva columna llamada DOY que indique el día del año en que inició cada incendio a partir de la columna STARTDATED, y finalmente muestre las primeras filas del resultado.

Carguemos nuevamente los datos

StudyArea %>%
  select(ORGANIZATI, STATE, YEAR_, TOTALACRES, CAUSE, STARTDATED) %>%
  filter(TOTALACRES >= 1000 & CAUSE %in% c("Human", "Natural")) %>%
  mutate(DOY = yday(as.Date(STARTDATED, format="%m/%d/%y%H:%M"))) -> df_sol

# Vista rápida
knitr::kable(head(df_sol))
ORGANIZATI STATE YEAR_ TOTALACRES CAUSE STARTDATED DOY
FWS Arizona 1988 1500 Human 3/26/88 0:00 86
FWS Arizona 1986 10390 Human 5/15/86 0:00 135
FWS Montana 1986 1400 Human 6/27/86 0:00 178
FWS Arizona 2002 1035 Human 2/28/02 0:00 59
FWS Arizona 2000 5700 Human 4/9/00 0:00 100
FWS Arizona 2000 2750 Human 5/14/00 0:00 135

Agrupación y resumen de los datos

Continuando con el ejemplo de incendios forestales

  • Seleccione únicamente las columnas ORGANIZATI, STATE, YEAR_, TOTALACRES y CAUSE , filtre los registros para que solo se incluyan los incendios con más de 1.000 acres quemados, cree una nueva columna llamada DECADE que define la década en la que se produjo cada incendio, agrupa porDECADE y finalmente un resumen numérico completo del tamaño de los incendios forestales por década.
StudyArea %>%
  select(ORGANIZATI, STATE, YEAR_, TOTALACRES, CAUSE) %>%
  filter(TOTALACRES >= 1000) %>% 
  mutate(DECADE = ifelse(YEAR_ %in% 1980:1989, "1980-1989", 
                          ifelse(YEAR_ %in% 1990:1999, "1990-1999",
                          ifelse(YEAR_ %in% 2000:2009, "2000-2009", 
                          ifelse(YEAR_ %in% 2010:2016, "2010-2016", "-99"))))) %>% 
  group_by(DECADE) %>% 
  summarise(media_acres = mean(TOTALACRES),
            ds_acres = sd(TOTALACRES),
            mediana_acres = median(TOTALACRES),
            RIC_acres = IQR(TOTALACRES),
            min_acres = min(TOTALACRES),
            max_acres = max(TOTALACRES),
            q1_acres = quantile(TOTALACRES)[2],
            q3_acres = quantile(TOTALACRES)[4]) -> df_sol_2

# Vista rápida
knitr::kable(head(df_sol_2))
DECADE media_acres ds_acres mediana_acres RIC_acres min_acres max_acres q1_acres q3_acres
1980-1989 8128.645 23681.53 2887.5 5074.50 1000 427680.0 1543.25 6617.75
1990-1999 8333.036 18212.44 2925.0 5641.80 1000 231389.0 1545.00 7186.80
2000-2009 12329.181 30156.35 3653.5 7859.75 1000 590620.0 1803.00 9662.75
2010-2016 14443.197 39272.14 3926.0 8552.00 1000 558198.3 1782.00 10334.00

Ejercicio 1 – Forest Fires in Idaho

Ejercicio 1 para entregar

Ejercicio 1 para entregar

df_idaho <- StudyArea %>%
  filter(STATE == "Idaho")

Columnas relevantes

df_idaho <- StudyArea %>%
  filter(STATE == "Idaho") %>%
  select(YEAR_, CAUSE, TOTALACRES) %>%
  rename(
    year = YEAR_,
    cause = CAUSE,
    total_acres = TOTALACRES
  )

Cambiar el nombre de las columnas

summary_idaho <- df_idaho %>%
  group_by(cause, year) %>%
  summarise(acres_burned = sum(total_acres, na.rm = TRUE)) %>%
  ungroup()
## `summarise()` has grouped output by 'cause'. You can override using the
## `.groups` argument.
head(summary_idaho)
## # A tibble: 6 × 3
##   cause  year acres_burned
##   <chr> <dbl>        <dbl>
## 1 Human  1980       71975.
## 2 Human  1981      219362.
## 3 Human  1982       34016.
## 4 Human  1983       48242.
## 5 Human  1984       36838.
## 6 Human  1985       68035.
df_clean <- summary_idaho %>%
  filter(cause %in% c("Human", "Natural"))
ggplot(df_clean, aes(x = year, y = acres_burned / 1000000, color = cause)) +
  geom_line(linewidth = 1) +
  labs(
    title = "Evolución de Acres Quemados en Idaho",
    subtitle = "Comparación entre causas Human y Natural",
    x = "Año",
    y = "Millones de Acres Quemados",
    color = "Causa"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", size = 14),
    legend.position = "top"
  )

  • Entre 1980 y 2015, los incendios naturales en Idaho quemaron consistentemente más superficie que los provocados por el ser humano, con picos destacados en torno a 1990, 2000 y 2007 donde se superaron varios millones de acres, mientras que los incendios humanos se mantuvieron relativamente estables y nunca alcanzaron magnitudes comparables; esta diferencia evidencia que, aunque la actividad humana contribuye al problema, las condiciones climáticas extremas y los fenómenos naturales son los principales responsables de las grandes variaciones en la extensión de tierras afectadas.

Ordenación de datos

La ordenación de datos es una forma coherente de organizar los datos en R y puede facilitarse a través del paquete tidyr que se encuentra en el ecosistema tidyverse.
Hay tres reglas que podemos seguir para hacer un conjunto de datos ordenado:

Recopilación

  • A continuación, tendrás que nombrar la variable de la nueva columna. Esto también se llama la clave key, y en este caso será la variable del año (year). Por último, tendrás que proporcionar el valor value, que es el nombre de la variable cuyos valores se reparten por las celdas.

  • Carguemos los datos

library(tidyverse)
df <- read_csv("RDataSets/RDataSets/CountryPopulation.csv")
head(df,n=5)
## # A tibble: 5 × 10
##   `Country Name` `Country Code` `2010` `2011` `2012` `2013` `2014` `2015` `2016`
##   <chr>          <chr>           <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>
## 1 Aruba          ABW            1.02e5 1.02e5 1.03e5 1.03e5 1.04e5 1.04e5 1.05e5
## 2 Afghanistan    AFG            2.88e7 2.97e7 3.07e7 3.17e7 3.28e7 3.37e7 3.47e7
## 3 Angola         AGO            2.34e7 2.42e7 2.51e7 2.60e7 2.69e7 2.79e7 2.88e7
## 4 Albania        ALB            2.91e6 2.91e6 2.90e6 2.90e6 2.89e6 2.88e6 2.88e6
## 5 Andorra        AND            8.44e4 8.38e4 8.24e4 8.08e4 7.92e4 7.80e4 7.73e4
## # ℹ 1 more variable: `2017` <dbl>
  • Utilice la función gather como se ve a continuación
df2 = gather(df, 
                "2010", "2011", "2012", "2013", "2014", "2015", "2016", "2017", 
                key = "YEAR", 
                value = "POPULATION")
knitr::kable(head(df2, 5))
Country Name Country Code YEAR POPULATION
Aruba ABW 2010 101669
Afghanistan AFG 2010 28803167
Angola AGO 2010 23369131
Albania ALB 2010 2913021
Andorra AND 2010 84449

Distribución

years <- colnames(df)[grep("^\\d{4}$", colnames(df))]

df2 <- df %>%
  gather(key = "YEAR", value = "POPULATION", all_of(years))

knitr::kable(head(df2, 5))
Country Name Country Code YEAR POPULATION
Aruba ABW 2010 101669
Afghanistan AFG 2010 28803167
Angola AGO 2010 23369131
Albania ALB 2010 2913021
Andorra AND 2010 84449
  • Tomemos los datos de table2:
knitr::kable(head(table2))
country year type count
Afghanistan 1999 cases 745
Afghanistan 1999 population 19987071
Afghanistan 2000 cases 2666
Afghanistan 2000 population 20595360
Brazil 1999 cases 37737
Brazil 1999 population 172006362
  • Utilice la función spread() para corregir este problema.
table2b = spread(table2, key = type, value = count)
knitr::kable(head(table2b))
country year cases population
Afghanistan 1999 745 19987071
Afghanistan 2000 2666 20595360
Brazil 1999 37737 172006362
Brazil 2000 80488 174504898
China 1999 212258 1272915272
China 2000 213766 1280428583

Separación

df = read_csv("RDataSets/RDataSets/usco2005.csv")
knitr::kable(head(df, 5))
STATE STATEFIPS COUNTYFIPS FIPS State-County Name TP-TotPop
AL 1 1 1001 Alabama-Autauga County 48.612
AL 1 3 1003 Alabama-Baldwin County 162.586
AL 1 5 1005 Alabama-Barbour County 28.414
AL 1 7 1007 Alabama-Bibb County 21.516
AL 1 9 1009 Alabama-Blount County 55.725
df2 = separate(df, "State-County Name",into = c("StateAbbrev", "CountyName"))
## Warning: Expected 2 pieces. Additional pieces discarded in 3222 rows [1, 2, 3, 4, 5, 6,
## 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...].
knitr::kable(head(df2, 5))
## Warning in attr(x, "align"): 'xfun::attr()' está en desuso.
## Utilizar 'xfun::attr2()' en su lugar.
## Ver help("Deprecated")
## Warning in attr(x, "format"): 'xfun::attr()' está en desuso.
## Utilizar 'xfun::attr2()' en su lugar.
## Ver help("Deprecated")
STATE STATEFIPS COUNTYFIPS FIPS StateAbbrev CountyName TP-TotPop
AL 1 1 1001 Alabama Autauga 48.612
AL 1 3 1003 Alabama Baldwin 162.586
AL 1 5 1005 Alabama Barbour 28.414
AL 1 7 1007 Alabama Bibb 21.516
AL 1 9 1009 Alabama Blount 55.725

Unión

df3 = unite(df2, State_County_Name, StateAbbrev, CountyName)
knitr::kable(head(df3, 5))
STATE STATEFIPS COUNTYFIPS FIPS State_County_Name TP-TotPop
AL 1 1 1001 Alabama_Autauga 48.612
AL 1 3 1003 Alabama_Baldwin 162.586
AL 1 5 1005 Alabama_Barbour 28.414
AL 1 7 1007 Alabama_Bibb 21.516
AL 1 9 1009 Alabama_Blount 55.725