Una vez que importamos datos a R es conveniente limpiarlos, esto implica almacenarlos de una manera consisistente que nos permita enfocarnos en responder preguntas de los datos en lugar de estar luchando con los datos.
Datos limpios son datos que facilitan las tareas del análisis de datos:
Visualización: Resúmenes de datos usando gráficas, análisis exploratorio, o presentación de resultados.
Manipulación: Manipulación de variables como agregar, filtrar, reordenar, transformar.
Modelación: Ajustar modelos es sencillo si los datos están en la forma correcta.
Los principios de los datos limpios [@tidy] proveen una manera estándar de organizar la información:
Vale la pena notar que los principios de los datos limpios se pueden ver como teoría de algebra relacional para estadísticos, estós principios junto con cada tipo de unidad observacional forma una tabla equivalen a la tercera forma normal de Codd con enfoque en una sola tabla de datos en lugar de muchas conectadas en bases de datos relacionales.
Veamos un ejemplo:
La mayor parte de las bases de datos en estadística tienen forma rectangular, ¿cuántas variables tiene la siguiente tabla?
tratamientoA | tratamientoB | |
---|---|---|
Juan Aguirre | - | 2 |
Ana Bernal | 16 | 11 |
José López | 3 | 1 |
La tabla anterior también se puede estructurar de la siguiente manera:
Juan Aguirre | Ana Bernal | José López | |
---|---|---|---|
tratamientoA | - | 16 | 3 |
tratamientoB | 2 | 11 | 1 |
Si vemos los principios (cada variable forma una columna, cada observación forma un renglón, cada tipo de unidad observacional forma una tabla), ¿las tablas anteriores cumplen los principios?
Para responder la pregunta identifiquemos primero cuáles son las variables y cuáles las observaciones de esta pequeña base. Las variables son: persona/nombre, tratamiento y resultado. Entonces, siguiendo los principios de datos limpios obtenemos la siguiente estructura:
nombre | tratamiento | resultado |
---|---|---|
Juan Aguirre | a | - |
Ana Bernal | a | 16 |
José López | a | 3 |
Juan Aguirre | b | 2 |
Ana Bernal | b | 11 |
José López | b | 1 |
Los principios de los datos limpios parecen obvios pero la mayor parte de los datos no los cumplen debido a:
Algunos de los problemas más comunes en las bases de datos que no están limpias son:
La mayor parte de estos problemas se pueden arreglar con pocas herramientas, a continuación veremos como limpiar datos usando 2 funciones del paquete tidyr
:
gather: recibe múltiples columnas y las junta en pares de valores y nombres y alarga los datos.
spread: recibe 2 columnas y las separa, haciendo los datos más anchos.
Repasaremos los problemas más comunes que se encuentran en conjuntos de datos sucios y mostraremos como se puede manipular la tabla de datos (usando las funciones gather y spread) con el fin de estructurarla para que cumpla los principios de datos limpios.
Usaremos ejemplos para entender los conceptos más facilmente. Comenzaremos con una tabla de datos que contiene las mediciones de partículas suspendidas PM2.5 de la red automática de monitoreo atmosférico (RAMA) para los primeros meses del 2019.
library(tidyverse)
library(estcomp)
pm25_2019
#> # A tibble: 5,088 x 26
#> date hour AJM AJU BJU CAM CCA COY FAR GAM HGM
#> <date> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <lgl> <lgl> <dbl> <dbl>
#> 1 2019-01-01 1 19 35 62 90 66 NA NA NA 56
#> 2 2019-01-01 2 17 24 88 104 84 NA NA NA 61
#> 3 2019-01-01 3 14 20 107 140 95 NA NA NA 74
#> 4 2019-01-01 4 6 15 101 162 97 NA NA NA 90
#> 5 2019-01-01 5 4 8 121 133 88 NA NA NA 90
#> 6 2019-01-01 6 7 7 93 106 77 NA NA NA 106
#> 7 2019-01-01 7 12 8 84 98 51 NA NA NA 115
#> 8 2019-01-01 8 15 7 101 82 39 NA NA NA 90
#> 9 2019-01-01 9 24 3 89 54 26 NA NA NA 90
#> 10 2019-01-01 10 24 NA 88 76 26 NA NA NA 99
#> # … with 5,078 more rows, and 15 more variables: INN <dbl>, MER <dbl>,
#> # MGH <dbl>, MON <dbl>, MPA <lgl>, NEZ <dbl>, PED <dbl>, SAC <lgl>,
#> # SAG <dbl>, SFE <dbl>, SJA <lgl>, TLA <dbl>, UAX <dbl>, UIZ <dbl>,
#> # XAL <dbl>
¿Cuáles son las variables en estos datos?
Esta base de datos tiene 4 variables: fecha, hora, estación y medición (en microgramos por metro cúbico \(\mu g/m^3\)).
Al alargar los datos desaparecerán las columnas que se agrupan y darán lugar a dos nuevas columnas: la correspondiente a estación y la correspondiente a medición. Entonces, usamos la función gather()
que recibe los argumentos:
select()
.pm25_2019_tidy <- gather(pm25_2019, key = station, value = measurement, -date,
-hour)
head(pm25_2019_tidy)
#> # A tibble: 6 x 4
#> date hour station measurement
#> <date> <dbl> <chr> <dbl>
#> 1 2019-01-01 1 AJM 19
#> 2 2019-01-01 2 AJM 17
#> 3 2019-01-01 3 AJM 14
#> 4 2019-01-01 4 AJM 6
#> 5 2019-01-01 5 AJM 4
#> 6 2019-01-01 6 AJM 7
tail(pm25_2019_tidy)
#> # A tibble: 6 x 4
#> date hour station measurement
#> <date> <dbl> <chr> <dbl>
#> 1 2019-07-31 19 XAL 12
#> 2 2019-07-31 20 XAL 11
#> 3 2019-07-31 21 XAL 7
#> 4 2019-07-31 22 XAL NA
#> 5 2019-07-31 23 XAL 7
#> 6 2019-07-31 24 XAL 7
Observemos que en la tabla original teníamos bajo la columna AJM, en el renglón correspondiente a 2019-01-01 hora 1 un valor de 19, y podemos ver que este valor en la tabla larga se almacena bajo la columna measurement y corresponde a la estación AJM.
La nueva estructura de la base de datos nos permite, por ejemplo, hacer fácilmente una gráfica donde podemos comparar las diferencias en las frecuencias.
pm25_2019_tidy %>%
mutate(
missing = is.na(measurement),
station = reorder(station, missing, sum)
) %>%
ggplot(aes(x = date, y = hour, fill = is.na(measurement))) +
geom_raster(alpha = 0.8) +
facet_wrap(~ station) +
scale_fill_manual("faltante",
values = c("TRUE" = "salmon", "FALSE" = "gray"))
Otro ejemplo, veamos los datos df_edu
, ¿cuántas variables tenemos?
df_edu
#> # A tibble: 7,371 x 16
#> state_code municipio_code region state_name state_abbr municipio_name
#> <chr> <chr> <chr> <chr> <chr> <chr>
#> 1 01 001 01001 Aguascali… AGS Aguascalientes
#> 2 01 001 01001 Aguascali… AGS Aguascalientes
#> 3 01 001 01001 Aguascali… AGS Aguascalientes
#> 4 01 002 01002 Aguascali… AGS Asientos
#> 5 01 002 01002 Aguascali… AGS Asientos
#> 6 01 002 01002 Aguascali… AGS Asientos
#> 7 01 003 01003 Aguascali… AGS Calvillo
#> 8 01 003 01003 Aguascali… AGS Calvillo
#> 9 01 003 01003 Aguascali… AGS Calvillo
#> 10 01 004 01004 Aguascali… AGS Cosío
#> # … with 7,361 more rows, and 10 more variables: sex <chr>, pop_15 <dbl>,
#> # no_school <dbl>, preschool <dbl>, elementary <dbl>, secondary <dbl>,
#> # highschool <dbl>, higher_edu <dbl>, other <dbl>, schoolyrs <dbl>
Notemos que el nivel de escolaridad esta guardado en 6 columnas (preschool, elementary, …, other), este tipo de almacenamiento no es limpio aunque puede ser útil al momento de ingresar la información o para presentarla.
Para tener datos limpios apilamos los niveles de escolaridad de manera que sea una sola columna (nuevamente alargamos los datos):
df_edu_tidy <- gather(data = df_edu, grade, percent, preschool:other,
na.rm = TRUE)
glimpse(df_edu_tidy)
#> Observations: 44,226
#> Variables: 12
#> $ state_code <chr> "01", "01", "01", "01", "01", "01", "01", "01", "…
#> $ municipio_code <chr> "001", "001", "001", "002", "002", "002", "003", …
#> $ region <chr> "01001", "01001", "01001", "01002", "01002", "010…
#> $ state_name <chr> "Aguascalientes", "Aguascalientes", "Aguascalient…
#> $ state_abbr <chr> "AGS", "AGS", "AGS", "AGS", "AGS", "AGS", "AGS", …
#> $ municipio_name <chr> "Aguascalientes", "Aguascalientes", "Aguascalient…
#> $ sex <chr> "Total", "Hombres", "Mujeres", "Total", "Hombres"…
#> $ pop_15 <dbl> 631064, 301714, 329350, 31013, 14991, 16022, 3867…
#> $ no_school <dbl> 2.662329, 2.355211, 2.943677, 4.011221, 4.389300,…
#> $ schoolyrs <dbl> 10.211152, 10.380144, 10.056383, 7.854005, 7.6920…
#> $ grade <chr> "preschool", "preschool", "preschool", "preschool…
#> $ percent <dbl> 0.17335801, 0.17466873, 0.17215728, 0.25795634, 0…
El parámetro na.rm = TRUE
se utiliza para eliminar los renglones con valores faltantes en la columna de porcentaje, esto es, eliminamos aquellas observaciones que tenían NA
en la columnas de nivel de escolaridad de la tabla ancha. En este caso optamos por que los faltantes sean implícitos, la conveniencia de tenerlos implícitos/explícitos dependerá de la aplicación.
Con los datos limpios es facil hacer manipulaciones y grfiacs, ¿cómo habrían hecho la siguiente gráfica antes de la limpieza?
df_edu_cdmx <- df_edu_tidy %>%
filter(state_abbr == "CDMX", sex != "Total", grade != "other") %>%
mutate(municipio_name = reorder(municipio_name, percent, last))
ggplot(df_edu_cdmx, aes(x = grade,
y = percent, group = sex, color = sex)) +
geom_path() +
facet_wrap(~municipio_name) +
theme(axis.text.x = element_text(angle = 60, hjust = 1)) +
scale_x_discrete(limits = c("preschool", "elementary",
"secondary", "highschool", "higher_edu"))
Utilizaremos un subconjunto de los datos de la prueba ENLACE a nivel primaria, la prueba ENLACE evaluaba a todos los alumnos de tercero a sexto de primaria y a los alumnos de secundaria del país en 3 áreas: español, matemáticas y formación cívica y ética.
data("enlacep_2013")
enlacep_sub_2013 <- enlacep_2013 %>%
select(CVE_ENT:PUNT_FCE_6) %>%
sample_n(1000)
glimpse(enlacep_sub_2013)
#> Observations: 1,000
#> Variables: 22
#> $ CVE_ENT <chr> "11", "15", "24", "25", "15", "06", "13", "10", "21",…
#> $ NOM_ENT <chr> "GUANAJUATO", "MEXICO", "SAN LUIS POTOSI", "SINALOA",…
#> $ CCT <chr> "11DPR3703N", "15DPR1974O", "24DPR0999Y", "25DPR1173D…
#> $ TURNO <chr> "VESPERTINO", "MATUTINO", "MATUTINO", "MATUTINO", "MA…
#> $ ESCUELA <chr> "20 DE NOVIEMBRE", "GUADALUPE VICTORIA", "JOSE MA. MO…
#> $ TIPO <chr> "GENERAL", "GENERAL", "GENERAL", "GENERAL", "GENERAL"…
#> $ CVE_MUN <chr> "032", "003", "007", "006", "037", "002", "059", "015…
#> $ NOM_MUN <chr> "SAN JOSE ITURBIDE", "ACULCO", "CEDRAL", "CULIACAN", …
#> $ CVE_LOC <chr> "0051", "0026", "0045", "0529", "0017", "0001", "0048…
#> $ NOM_LOC <chr> "EL MAGUEYAL (LA ESQUINA)", "SAN PEDRO DENXHI", "SANT…
#> $ PUNT_ESP_3 <dbl> NA, 523, 507, 537, 573, 506, 526, 472, 570, 456, 631,…
#> $ PUNT_MAT_3 <dbl> NA, 578, 503, 643, 678, 562, 513, 440, 633, 509, 666,…
#> $ PUNT_FCE_3 <dbl> NA, 487, 488, 495, 485, 449, 416, 431, 456, 434, 589,…
#> $ PUNT_ESP_4 <dbl> NA, 528, 497, 575, 662, 517, 483, 467, 602, 483, 610,…
#> $ PUNT_MAT_4 <dbl> NA, 567, 595, 553, 711, 558, 559, 435, 616, 550, 758,…
#> $ PUNT_FCE_4 <dbl> NA, 485, 510, 541, 514, 459, 456, 414, 505, 434, 621,…
#> $ PUNT_ESP_5 <dbl> 506, 449, 508, 552, 628, 510, 523, 433, 573, 424, 493…
#> $ PUNT_MAT_5 <dbl> 548, 494, 493, 623, 660, 579, 534, 454, 635, 464, 511…
#> $ PUNT_FCE_5 <dbl> 486, 422, 486, 497, 609, 465, 431, 427, 524, 436, 453…
#> $ PUNT_ESP_6 <dbl> 448, 496, 490, 502, 490, 534, 503, 406, 578, 443, 800…
#> $ PUNT_MAT_6 <dbl> 477, 570, 572, 645, 587, 605, 554, 388, 665, 511, 852…
#> $ PUNT_FCE_6 <dbl> 479, 481, 467, 482, 446, 476, 400, 405, 571, 396, 652…
¿Cuántas variables tiene este subconjunto de los datos?
gather()
para apilar las columnas correspondientes a área-grado.enlacep_long <- gather(enlacep_sub_2013, AREA_GRADO, PUNTAJE,
contains("PUNT"), na.rm = TRUE)
enlacep_long
#> # A tibble: 11,259 x 12
#> CVE_ENT NOM_ENT CCT TURNO ESCUELA TIPO CVE_MUN NOM_MUN CVE_LOC
#> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
#> 1 15 MEXICO 15DP… MATU… GUADAL… GENE… 003 ACULCO 0026
#> 2 24 SAN LU… 24DP… MATU… JOSE M… GENE… 007 CEDRAL 0045
#> 3 25 SINALOA 25DP… MATU… PROF. … GENE… 006 CULIAC… 0529
#> 4 15 MEXICO 15DP… MATU… CUAUHT… GENE… 037 HUIXQU… 0017
#> 5 06 COLIMA 06DP… MATU… CLUB D… GENE… 002 COLIMA 0001
#> 6 13 HIDALGO 13DP… MATU… EMILIA… INDê… 059 TECOZA… 0048
#> 7 10 DURANGO 10DP… MATU… IGNACI… GENE… 015 NAZAS 0026
#> 8 21 PUEBLA 21DP… MATU… IGNACI… GENE… 027 CALTEP… 0002
#> 9 25 SINALOA 25DP… MATU… PRESID… GENE… 006 CULIAC… 0430
#> 10 32 ZACATE… 32DP… MATU… JOSE M… GENE… 014 GENERA… 0013
#> # … with 11,249 more rows, and 3 more variables: NOM_LOC <chr>,
#> # AREA_GRADO <chr>, PUNTAJE <dbl>
Ahora separaremos las variables área y grado de la columna AREA_GRADO
, para ello debemos pasar a la función separate()
, esta recibe como parámetros:
el nombre de la base de datos,
el nombre de la variable que deseamos separar en más de una,
la posición de donde deseamos “cortar” (hay más opciones para especificar como separar, ver ?separate
). El default es separar valores en todos los lugares que encuentre un caracter que no es alfanumérico (espacio, guión,…).
enlacep_tidy <- separate(data = enlacep_long, col = AREA_GRADO,
into = c("AREA", "GRADO"), sep = 9)
enlacep_tidy
#> # A tibble: 11,259 x 13
#> CVE_ENT NOM_ENT CCT TURNO ESCUELA TIPO CVE_MUN NOM_MUN CVE_LOC
#> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
#> 1 15 MEXICO 15DP… MATU… GUADAL… GENE… 003 ACULCO 0026
#> 2 24 SAN LU… 24DP… MATU… JOSE M… GENE… 007 CEDRAL 0045
#> 3 25 SINALOA 25DP… MATU… PROF. … GENE… 006 CULIAC… 0529
#> 4 15 MEXICO 15DP… MATU… CUAUHT… GENE… 037 HUIXQU… 0017
#> 5 06 COLIMA 06DP… MATU… CLUB D… GENE… 002 COLIMA 0001
#> 6 13 HIDALGO 13DP… MATU… EMILIA… INDê… 059 TECOZA… 0048
#> 7 10 DURANGO 10DP… MATU… IGNACI… GENE… 015 NAZAS 0026
#> 8 21 PUEBLA 21DP… MATU… IGNACI… GENE… 027 CALTEP… 0002
#> 9 25 SINALOA 25DP… MATU… PRESID… GENE… 006 CULIAC… 0430
#> 10 32 ZACATE… 32DP… MATU… JOSE M… GENE… 014 GENERA… 0013
#> # … with 11,249 more rows, and 4 more variables: NOM_LOC <chr>,
#> # AREA <chr>, GRADO <chr>, PUNTAJE <dbl>
# creamos un mejor código de área
enlacep_tidy <- enlacep_tidy %>%
mutate(
AREA = substr(AREA, 6, 8),
GRADO = as.numeric(GRADO)
)
glimpse(enlacep_tidy)
#> Observations: 11,259
#> Variables: 13
#> $ CVE_ENT <chr> "15", "24", "25", "15", "06", "13", "10", "21", "25", "3…
#> $ NOM_ENT <chr> "MEXICO", "SAN LUIS POTOSI", "SINALOA", "MEXICO", "COLIM…
#> $ CCT <chr> "15DPR1974O", "24DPR0999Y", "25DPR1173D", "15DPR1617Z", …
#> $ TURNO <chr> "MATUTINO", "MATUTINO", "MATUTINO", "MATUTINO", "MATUTIN…
#> $ ESCUELA <chr> "GUADALUPE VICTORIA", "JOSE MA. MORELOS", "PROF. SEVERO …
#> $ TIPO <chr> "GENERAL", "GENERAL", "GENERAL", "GENERAL", "GENERAL", "…
#> $ CVE_MUN <chr> "003", "007", "006", "037", "002", "059", "015", "027", …
#> $ NOM_MUN <chr> "ACULCO", "CEDRAL", "CULIACAN", "HUIXQUILUCAN", "COLIMA"…
#> $ CVE_LOC <chr> "0026", "0045", "0529", "0017", "0001", "0048", "0026", …
#> $ NOM_LOC <chr> "SAN PEDRO DENXHI", "SANTA RITA DEL SOTOL", "PALO BLANCO…
#> $ AREA <chr> "ESP", "ESP", "ESP", "ESP", "ESP", "ESP", "ESP", "ESP", …
#> $ GRADO <dbl> 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,…
#> $ PUNTAJE <dbl> 523, 507, 537, 573, 506, 526, 472, 570, 456, 631, 371, 6…
El problema más difícil es cuando las variables están tanto en filas como en columnas, veamos una base de datos de fertilidad. ¿Cuáles son las variables en estos datos?
data("df_fertility")
df_fertility
#> # A tibble: 306 x 11
#> state size_localidad est age_15_19 age_20_24 age_25_29 age_30_34
#> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 01 A… Menos de 2 50… Valor 74.2 175. 175. 102.
#> 2 01 A… Menos de 2 50… Erro… 6.71 11.0 9.35 8.05
#> 3 01 A… 2 500-14 999 … Valor 82.5 171. 140. 103.
#> 4 01 A… 2 500-14 999 … Erro… 9.79 12.5 10.4 8.76
#> 5 01 A… 15 000-49 999… Valor 72.6 146. 147. 99.0
#> 6 01 A… 15 000-49 999… Erro… 7.07 10.8 10.5 8.11
#> 7 01 A… 100 000 y más… Valor 66.3 120. 102. 84.2
#> 8 01 A… 100 000 y más… Erro… 7.57 8.66 8.98 8.59
#> 9 02 B… Menos de 2 50… Valor 89.6 158. 117. 86.0
#> 10 02 B… Menos de 2 50… Erro… 15.8 17.2 13.2 12.3
#> # … with 296 more rows, and 4 more variables: age_35_39 <dbl>,
#> # age_40_44 <dbl>, age_45_49 <dbl>, global <dbl>
Estos datos tienen variables en columnas individuales (state, size_localidad), en múltiples columnas (grupo de edad, age_15_19,..) y en filas (Valor y Error estándar).
Comencemos por apilar las columnas.
fertility_long <- gather(df_fertility, age_bracket, value, age_15_19:global,
na.rm = TRUE)
fertility_long
#> # A tibble: 2,448 x 5
#> state size_localidad est age_bracket value
#> <chr> <chr> <chr> <chr> <dbl>
#> 1 01 Aguascalientes Menos de 2 500 habitan… Valor age_15_19 74.2
#> 2 01 Aguascalientes Menos de 2 500 habitan… Error están… age_15_19 6.71
#> 3 01 Aguascalientes 2 500-14 999 habitantes Valor age_15_19 82.5
#> 4 01 Aguascalientes 2 500-14 999 habitantes Error están… age_15_19 9.79
#> 5 01 Aguascalientes 15 000-49 999 habitant… Valor age_15_19 72.6
#> 6 01 Aguascalientes 15 000-49 999 habitant… Error están… age_15_19 7.07
#> 7 01 Aguascalientes 100 000 y más habitant… Valor age_15_19 66.3
#> 8 01 Aguascalientes 100 000 y más habitant… Error están… age_15_19 7.57
#> 9 02 Baja Californ… Menos de 2 500 habitan… Valor age_15_19 89.6
#> 10 02 Baja Californ… Menos de 2 500 habitan… Error están… age_15_19 15.8
#> # … with 2,438 more rows
Podemos crear algunas variables adicionales.
fertility_vars <- fertility_long %>%
mutate(
state_code = str_sub(state, 1, 2),
state_name = str_sub(state, 4)
) %>%
select(-state)
fertility_vars
#> # A tibble: 2,448 x 6
#> size_localidad est age_bracket value state_code state_name
#> <chr> <chr> <chr> <dbl> <chr> <chr>
#> 1 Menos de 2 500 habi… Valor age_15_19 74.2 01 Aguascalien…
#> 2 Menos de 2 500 habi… Error es… age_15_19 6.71 01 Aguascalien…
#> 3 2 500-14 999 habita… Valor age_15_19 82.5 01 Aguascalien…
#> 4 2 500-14 999 habita… Error es… age_15_19 9.79 01 Aguascalien…
#> 5 15 000-49 999 habit… Valor age_15_19 72.6 01 Aguascalien…
#> 6 15 000-49 999 habit… Error es… age_15_19 7.07 01 Aguascalien…
#> 7 100 000 y más habit… Valor age_15_19 66.3 01 Aguascalien…
#> 8 100 000 y más habit… Error es… age_15_19 7.57 01 Aguascalien…
#> 9 Menos de 2 500 habi… Valor age_15_19 89.6 02 Baja Califo…
#> 10 Menos de 2 500 habi… Error es… age_15_19 15.8 02 Baja Califo…
#> # … with 2,438 more rows
Finalmente, la columna est no es una variable, sino que almacena el nombre de 2 variables: Valor y Error Estándar la operación que debemos aplicar (spread()
) es el inverso de apilar (gather
), sus argumentos son:
data.frame
que vamos a ensanchar.fertility_tidy <- spread(data = fertility_vars, key = est, value = value)
Y podemos mejorar los nombres de las columnas, una opción rápida es usar el paquete janitor.
fertility_tidy %>%
janitor::clean_names() %>%
glimpse()
#> Observations: 1,224
#> Variables: 6
#> $ size_localidad <chr> "100 000 y más habitantes", "100 000 y más habita…
#> $ age_bracket <chr> "age_15_19", "age_15_19", "age_15_19", "age_15_19…
#> $ state_code <chr> "01", "02", "03", "04", "05", "06", "07", "08", "…
#> $ state_name <chr> "Aguascalientes", "Baja California", "Baja Califo…
#> $ error_estandar <dbl> 7.572352, 3.204220, 13.055474, 9.145983, 4.819321…
#> $ valor <dbl> 66.33564, 43.03023, 58.97916, 61.79522, 80.08338,…
o podemos hacerlo manualmente
names(fertility_tidy)[5:6] <- c("est", "std_error")
Ahora es inmediato no solo hacer gráficas sino también ajustar un modelo.
# ajustamos un modelo lineal donde la variable respuesta es temperatura
# máxima, y la variable explicativa es el mes
fertility_sub <- filter(fertility_tidy, age_bracket != "global")
fertility_lm <- lm(est ~ age_bracket, data = fertility_sub)
summary(fertility_lm)
#>
#> Call:
#> lm(formula = est ~ age_bracket, data = fertility_sub)
#>
#> Residuals:
#> Min 1Q Median 3Q Max
#> -7.3784 -2.3282 -0.5896 1.1359 31.5091
#>
#> Coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) 6.8723 0.3277 20.973 < 2e-16 ***
#> age_bracketage_20_24 2.3948 0.4634 5.168 2.83e-07 ***
#> age_bracketage_25_29 2.3272 0.4634 5.022 5.99e-07 ***
#> age_bracketage_30_34 1.2363 0.4634 2.668 0.00775 **
#> age_bracketage_35_39 -0.9413 0.4634 -2.031 0.04246 *
#> age_bracketage_40_44 -3.7525 0.4634 -8.098 1.52e-15 ***
#> age_bracketage_45_49 -6.0480 0.4634 -13.051 < 2e-16 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Residual standard error: 4.053 on 1064 degrees of freedom
#> Multiple R-squared: 0.3479, Adjusted R-squared: 0.3443
#> F-statistic: 94.62 on 6 and 1064 DF, p-value: < 2.2e-16
Vale la pena notar que aunque los datos limpios facilitan las tareas de análisis, distintas funciones o tareas requieren los datos en distintos formas y saber reestructurar las tablas es indispensable para tener flexibilidad, por ejemplo, al graficar.
Grafica el valor estimado de fertilidad del grupo de edad 20-24 contra 25-29. ¿Qué transformación debes hacer? Tip: elimina la columna que corresponde al error estándar antes de ensanchar los datos.
También es común que los valores sobre una misma unidad observacional estén separados en muchas tablas o archivos, es común que estas tablas esten divididas de acuerdo a una variable, de tal manera que cada archivo representa a una persona, año o ubicación. Para juntar los archivos hacemos lo siguiente:
Veamos un ejemplo, descargamos la carpeta con los datos de varios contaminantes de RAMA,
usethis::use_zip("https://github.com/tereom/estcomp/raw/master/data-raw/19RAMA.zip",
"data")
ésta contiene 9 archivos de excel que almacenan información de monitoreo de contaminantes. Cada archivo contiene información de un contaminante y el nombre del archivo indica el contaminante.
Los pasos en R (usando el paquete purrr
), primero creamos un vector con los nombres de los archivos en un directorio, eligiendo aquellos que contengan las letras “.csv”.
paths <- dir("data/19RAMA", pattern = "\\.xls$", full.names = TRUE)
Después le asignamos el nombre del archivo al nombre de cada elemento del vector. Este paso se realiza para preservar los nombres de los archivos ya que estos los asignaremos a una variable mas adelante.
paths <- set_names(paths, basename(paths))
La función map_df
itera sobre cada dirección, lee el archivo excel de dicha dirección y los combina en un data frame.
library(readxl)
rama <- map_df(paths, read_excel, .id = "FILENAME")
# eliminamos la basura del id
rama <- rama %>%
mutate(PARAMETRO = str_remove(FILENAME, "2019") %>% str_remove(".xls")) %>%
select(PARAMETRO, FECHA:AJU)
#> Error in stri_replace_first_regex(string, pattern, fix_replacement(replacement), : object 'FILENAME' not found
# y apilamos para tener una columna por estación
rama_tidy <- rama %>%
gather(estacion, valor, ACO:AJU) %>%
mutate(valor = ifelse(-99, NA, valor))
#> Error in is_string(x): object 'ACO' not found
rama_tidy
#> Error in eval(expr, envir, enclos): object 'rama_tidy' not found
En las buenas prácticas es importante tomar en cuenta los siguientes puntos:
Incluir un encabezado con el nombre de las variables.
Los nombres de las variables deben ser entendibles (e.g. AgeAtDiagnosis es mejor que AgeDx).
En general los datos se deben guardar en un archivo por tabla.
Escribir un script con las modificaciones que se hicieron a los datos crudos (reproducibilidad).
Otros aspectos importantes en la limpieza de datos son: selección del tipo de variables (por ejemplo fechas), datos faltantes, typos y detección de valores atípicos.
Data Transformation Cheat Sheet, RStudio.
Limpiar nombres de columnas, eliminar filas vacías y más, paquete janitor.
Lectura de datos tabulares con distintas estructuras, paquete tidycells.