Capítulo6 Transformación: Estructura de un archivo y seleccionar datos
## [1] "2026-09-09"
El tema proviene de los siguientes sitios.
English: https://r4ds.had.co.nz/transform.html
Español: https://es.r4ds.hadley.nz/05-transform.html
## # A tibble: 6 × 19
## anio mes dia horario_salida salida_programada atraso_salida
## <int> <int> <int> <int> <int> <dbl>
## 1 2013 1 1 517 515 2
## 2 2013 1 1 533 529 4
## 3 2013 1 1 542 540 2
## 4 2013 1 1 544 545 -1
## 5 2013 1 1 554 600 -6
## 6 2013 1 1 554 558 -4
## # ℹ 13 more variables: horario_llegada <int>, llegada_programada <int>,
## # atraso_llegada <dbl>, aerolinea <chr>, vuelo <int>, codigo_cola <chr>,
## # origen <chr>, destino <chr>, tiempo_vuelo <dbl>, distancia <dbl>,
## # hora <dbl>, minuto <dbl>, fecha_hora <dttm>
6.1 Evaluar el archivo el vuelo
Mirar el archivo y entender la información antes de hacer cualquier analisis/
- Cuáles son las variables
- Cuales son. los tipos de datos
- int valores enteros,
- dbl números reales, significa dobles
- chr caracteres o vectores
- dttm fecha y tiempo, minutos
- date fecha
- S3: POSIXct Fecha y tiempo, minutos
- lgl valores lógicos “cierto” (TRUE) y “falso” (FALSE)
- fctr factores categóricos.
Evalua en este archivo los tipos de variables que se encuentra.
Esa línea de abreviaturas que aparece debajo de los nombres de las columnas es lo
primero que hay que leer de un tibble, y casi nadie la lee. Dice el tipo de
cada variable, y el tipo determina qué se puede hacer con ella. Una columna que
debería ser numérica y aparece como <chr> es una columna rota: no se le puede
sacar el promedio, no se ordena bien y las gráficas salen mal. Detectarlo aquí,
en el primer vistazo, ahorra horas.
El otro dato importante es el de la primera línea: # A tibble: 336,776 x 19.
Son 336,776 vuelos y 19 variables. Cada fila es un vuelo que salió de uno de
los tres aeropuertos de Nueva York en 2013. Saber qué representa una fila es la
pregunta que hay que contestar antes de calcular nada, porque de ella depende el
significado de cualquier promedio que se calcule después.
glimpse(vuelos) es una alternativa a escribir el nombre: en vez de mostrar las
primeras filas a lo ancho, muestra todas las columnas, una por línea, con su
tipo y sus primeros valores. Con tablas anchas es mucho más útil, porque no se
pierden columnas por falta de espacio.
## # A tibble: 336,776 × 19
## anio mes dia horario_salida salida_programada atraso_salida
## <int> <int> <int> <int> <int> <dbl>
## 1 2013 1 1 517 515 2
## 2 2013 1 1 533 529 4
## 3 2013 1 1 542 540 2
## 4 2013 1 1 544 545 -1
## 5 2013 1 1 554 600 -6
## 6 2013 1 1 554 558 -4
## 7 2013 1 1 555 600 -5
## 8 2013 1 1 557 600 -3
## 9 2013 1 1 557 600 -3
## 10 2013 1 1 558 600 -2
## # ℹ 336,766 more rows
## # ℹ 13 more variables: horario_llegada <int>, llegada_programada <int>,
## # atraso_llegada <dbl>, aerolinea <chr>, vuelo <int>, codigo_cola <chr>,
## # origen <chr>, destino <chr>, tiempo_vuelo <dbl>, distancia <dbl>,
## # hora <dbl>, minuto <dbl>, fecha_hora <dttm>
6.1.1 Cual son todos los destinos de vuelos?
- cual función usará
## [1] "ABQ" "ACK" "ALB" "ANC" "ATL" "AUS" "AVL" "BDL" "BGR" "BHM" "BNA" "BOS"
## [13] "BQN" "BTV" "BUF" "BUR" "BWI" "BZN" "CAE" "CAK" "CHO" "CHS" "CLE" "CLT"
## [25] "CMH" "CRW" "CVG" "DAY" "DCA" "DEN" "DFW" "DSM" "DTW" "EGE" "EYW" "FLL"
## [37] "GRR" "GSO" "GSP" "HDN" "HNL" "HOU" "IAD" "IAH" "ILM" "IND" "JAC" "JAX"
## [49] "LAS" "LAX" "LEX" "LGA" "LGB" "MCI" "MCO" "MDW" "MEM" "MHT" "MIA" "MKE"
## [61] "MSN" "MSP" "MSY" "MTJ" "MVY" "MYR" "OAK" "OKC" "OMA" "ORD" "ORF" "PBI"
## [73] "PDX" "PHL" "PHX" "PIT" "PSE" "PSP" "PVD" "PWM" "RDU" "RIC" "ROC" "RSW"
## [85] "SAN" "SAT" "SAV" "SBN" "SDF" "SEA" "SFO" "SJC" "SJU" "SLC" "SMF" "SNA"
## [97] "SRQ" "STL" "STT" "SYR" "TPA" "TUL" "TVC" "TYS" "XNA"
Dos funciones encadenadas de dentro hacia afuera: unique() se queda con los
valores distintos y sort() los ordena alfabéticamente. El resultado son los
códigos de aeropuerto de la IATA, los mismos que aparecen en un boleto de avión:
SJU es Luis Muñoz Marín, BQN es Aguadilla, PSE es Ponce.
Este es el paso obligatorio antes de filtrar por una variable categórica. Filtrar
por destino == "San Juan" no devuelve nada, y el error no es un error: es una
tabla vacía, que es mucho peor, porque parece un resultado. Mirar primero los
valores que existen evita ese callejón.
6.1.2 Selecciona los vuelos donde el destino es San Juan
## # A tibble: 5,819 × 19
## anio mes dia horario_salida salida_programada atraso_salida
## <int> <int> <int> <int> <int> <dbl>
## 1 2013 1 1 615 615 0
## 2 2013 1 1 628 630 -2
## 3 2013 1 1 701 700 1
## 4 2013 1 1 711 715 -4
## 5 2013 1 1 820 820 0
## 6 2013 1 1 820 820 0
## 7 2013 1 1 840 845 -5
## 8 2013 1 1 926 929 -3
## 9 2013 1 1 1202 1159 3
## 10 2013 1 1 1245 1249 -4
## # ℹ 5,809 more rows
## # ℹ 13 more variables: horario_llegada <int>, llegada_programada <int>,
## # atraso_llegada <dbl>, aerolinea <chr>, vuelo <int>, codigo_cola <chr>,
## # origen <chr>, destino <chr>, tiempo_vuelo <dbl>, distancia <dbl>,
## # hora <dbl>, minuto <dbl>, fecha_hora <dttm>
filter() se queda con las filas que cumplen la condición. La condición se
evalúa fila por fila y produce un TRUE o un FALSE; se conservan las que dan
TRUE.
Los operadores de comparación son == (igual), != (distinto), >, <, >= y
<=. Para combinar varias condiciones están & (y), | (o) y ! (no). Y hay
un atajo muy usado: separar las condiciones con comas dentro del mismo filter()
equivale a unirlas con &.
6.1.4 Selecciona los vuelos que salen de San Juan en el dia de ti cumpleaños y contabiliza cuanto hubo
df_dia_cumpl=vuelos %>%
filter(destino =="SJU") %>%
filter(mes== 2, dia %in% c(1,2)) |>
nrow()
df_dia_cumpl## [1] 28
Aquí el resultado de la cadena no es una tabla sino un número, porque el último
paso es nrow(). Es un patrón que se usa mucho: filtrar y contar. Nota además que
la cadena mezcla los dos pipes, %>% y |>; funciona porque para lo que hacemos
aquí son intercambiables, pero conviene escoger uno y ser consistente.
6.1.4.1 Organizar los datos en orden de más grande a más pequeño
- Selecciona los mes, dia, destino, atraso_salida, atraso_llegada
- Selecciona solamente el mes de noviembre
- Selecciona solamente los vuelos a destino de a San Juan
- organiza los datos en orden de más atraso_salido de mayor a menor
## [1] "anio" "mes" "dia"
## [4] "horario_salida" "salida_programada" "atraso_salida"
## [7] "horario_llegada" "llegada_programada" "atraso_llegada"
## [10] "aerolinea" "vuelo" "codigo_cola"
## [13] "origen" "destino" "tiempo_vuelo"
## [16] "distancia" "hora" "minuto"
## [19] "fecha_hora"
vuelos %>%
dplyr::select(mes, dia, destino, atraso_salida, atraso_llegada) %>%
filter(mes==11) %>%
filter(destino =="SJU") %>%
arrange(desc(atraso_salida))## # A tibble: 429 × 5
## mes dia destino atraso_salida atraso_llegada
## <int> <int> <chr> <dbl> <dbl>
## 1 11 7 SJU 231 190
## 2 11 28 SJU 223 235
## 3 11 23 SJU 187 154
## 4 11 24 SJU 181 188
## 5 11 23 SJU 157 165
## 6 11 12 SJU 145 127
## 7 11 10 SJU 94 88
## 8 11 24 SJU 90 112
## 9 11 30 SJU 68 59
## 10 11 21 SJU 66 42
## # ℹ 419 more rows
arrange() ordena las filas. Por defecto de menor a mayor, y con desc()
alrededor de la variable, de mayor a menor. Se le pueden dar varias variables:
arrange(mes, desc(atraso_salida)) ordena por mes y, dentro de cada mes, por
atraso descendente.
Un detalle que sorprende: los NA siempre van al final, tanto en orden
ascendente como descendente. R los trata como “no sé dónde va esto” y los deja
fuera del orden en lugar de inventarles una posición.
arrange() y desc(): ordenar las filas.
arrange(col): ordena de menor a mayor (o alfabéticamente).arrange(desc(col)): de mayor a menor.arrange(a, desc(b)): varias claves, en orden de prioridad.- Ojo: los
NAvan siempre al final, yarrange()sin argumentos no ordena nada y tampoco avisa.
El orden en que se encadenan los verbos importa para el resultado y también para
la velocidad. Aquí se hace select() antes que filter(), y funciona porque las
columnas que se filtran están entre las seleccionadas. La costumbre segura es
filtrar primero (reduce las filas) y seleccionar después: así nunca se
pierde una columna que hacía falta para la condición.
6.2 Temas: Reconocer y aplicar las diferentes funciones
Tipos de variables:
- int: valores enteros
- dbl: números reales
- chr: caracteres
- dttm: fecha y tiempo
- lgl: valores logicos
- fctr: factores
- date: fecha
Funciones de dplyr:
- filter() : seleccionar filas
- arrange(): ordenar filas
- select(): seleccionar columnas
- summarize(): resumir conjuntos de datos
- group_by(): agrupar por variables
Los verbos de dplyr: cada uno hace una cosa y se encadenan con el pipe.
filter(): selecciona filas por condición.select(): selecciona columnas.arrange(): ordena las filas (desc()para descendente).group_by()+summarise(): agrupa y calcula un resumen por grupo (media, mediana, conteo…).mutate(): crea columnas nuevas (ver capítulo de mutate).
Estos cinco verbos son casi todo dplyr, y tienen tres cosas en común que
conviene notar: el primer argumento siempre es la tabla (por eso funcionan con
el pipe), los nombres de las columnas se escriben sin comillas, y el resultado
es siempre otra tabla, nunca una modificación de la original. Eso último es
importante: filter(vuelos, mes == 12) no cambia vuelos. Si quieres conservar
el resultado hay que guardarlo con <-.
dplyr fue creado por Hadley Wickham (2014) como una “gramática de la manipulación de datos”: un pequeño conjunto de verbos con nombres de acción. Sustituyó al paquete anterior plyr con una sintaxis más clara y rápida.
library(datos)
library(tidyverse)
por_dia <- vuelos %>% # pipe
group_by(dia) %>% # group by day
summarise(atraso_promedio = mean(atraso_salida, na.rm = TRUE))
por_dia## # A tibble: 31 × 2
## dia atraso_promedio
## <int> <dbl>
## 1 1 14.2
## 2 2 14.1
## 3 3 10.8
## 4 4 5.79
## 5 5 7.82
## 6 6 6.99
## 7 7 14.3
## 8 8 21.8
## 9 9 14.6
## 10 10 18.3
## # ℹ 21 more rows
Esta pareja, group_by() y summarise(), es la herramienta más potente del
capítulo. group_by() no cambia los datos: los marca, dividiéndolos en
grupos. Después, summarise() colapsa cada grupo en una sola fila. Aquí, de
336,776 vuelos salen 31 filas, una por día del mes.
El na.rm = TRUE no es opcional: sin él, el promedio de cualquier grupo que
contenga un solo NA sale NA, y como hay 8,255 vuelos sin dato de salida, casi
todos los días saldrían NA. Ponerlo es decirle a R “calcula con lo que hay”.
Ahora bien, hay que ponerlo sabiendo lo que se descarta: si los vuelos sin
dato son precisamente los cancelados, el promedio que sale es el atraso de los
vuelos que sí salieron, que no es lo mismo que el atraso de todos los vuelos
programados.
group_by(), summarise(), n() y ungroup(): resumir por grupo.
group_by(var): no cambia los datos, los marca por grupo. Con dos variables agrupa por la combinación.summarise(nombre = funcion(col)): colapsa cada grupo en una fila. Acepta varios resúmenes separados por comas.n(): cuántas filas tiene el grupo. Ponlo siempre: un promedio sin su tamaño de muestra no se puede evaluar.na.rm = TRUE: calcula ignorando los faltantes. Cámbialo solo sabiendo qué se descarta.ungroup()o.groups = "drop": quita la agrupación al final, para que los cálculos siguientes no la arrastren sin querer.
Este resumen agrupa por dia sin agrupar por mes, así que el día 1 junta el
1 de enero, el 1 de febrero y los otros diez primeros de mes. Puede ser lo que se
quiere (¿hay algo especial en el primer día del mes?) o puede ser un descuido. El
resultado no lo dice: hay que saberlo. Más abajo, en la sección de agrupar por
múltiples variables, se hace la versión correcta.
## The median of the data set by day of the month
por_dia_2 <- vuelos %>%
group_by( dia) %>%
summarise(atraso_promedio = mean(atraso_salida, na.rm = TRUE),
atraso_mediana = median(atraso_salida, na.rm=TRUE))
por_dia_2## # A tibble: 31 × 3
## dia atraso_promedio atraso_mediana
## <int> <dbl> <dbl>
## 1 1 14.2 -2
## 2 2 14.1 -1
## 3 3 10.8 -2
## 4 4 5.79 -3
## 5 5 7.82 -3
## 6 6 6.99 -2
## 7 7 14.3 -1
## 8 8 21.8 -1
## 9 9 14.6 -1
## 10 10 18.3 -1
## # ℹ 21 more rows
summarise() acepta tantos resúmenes como quieras, separados por comas, y cada
uno se convierte en una columna. Comparar la media con la mediana es de las cosas
más informativas que se pueden hacer de un vistazo: cuando la media es mucho mayor
que la mediana, la distribución tiene una cola larga hacia la derecha. Es
exactamente lo que pasa con los atrasos, porque la mayoría de los vuelos salen
más o menos a tiempo y unos pocos salen con horas de retraso, y esos pocos
arrastran el promedio.
6.3 Funciones
- summarise = resumir conjuntos de datos
- mean, # promedio
- median, # mediana
- mode, # moda
- operaciones boolean
# El valor más común en un conjunto de datos
# Crear una función: no existe en los paquetes de R
getmode <- function(v) {
uniqv <- unique(v)
uniqv[which.max(tabulate(match(v, uniqv)))]
}
por_dia_3=vuelos |>
dplyr::select(dia, mes, atraso_salida) |>
group_by(dia) %>%
summarise(atraso_promedio = mean(atraso_salida, na.rm = TRUE),
atraso_max=max(atraso_salida, na.rm=TRUE),
atraso_mode = getmode(atraso_salida))
por_dia_3## # A tibble: 31 × 4
## dia atraso_promedio atraso_max atraso_mode
## <int> <dbl> <dbl> <dbl>
## 1 1 14.2 853 -5
## 2 2 14.1 696 -4
## 3 3 10.8 878 -4
## 4 4 5.79 545 -5
## 5 5 7.82 896 -5
## 6 6 6.99 589 -4
## 7 7 14.3 653 -5
## 8 8 21.8 520 NA
## 9 9 14.6 1301 -4
## 10 10 18.3 1126 -5
## # ℹ 21 more rows
R tiene mean() y median(), pero no tiene una función para la moda. La que
existe, mode(), hace algo completamente distinto: devuelve el tipo interno de un
objeto. Por eso aquí se escribe una a mano.
Vale la pena leer getmode() por dentro, porque son cuatro funciones anidadas:
unique(v) saca los valores distintos; match(v, uniqv) dice, para cada dato, en
qué posición de esa lista está; tabulate() cuenta cuántas veces aparece cada
posición; y which.max() devuelve la posición con la cuenta más alta, que se usa
para sacar el valor. Se lee de dentro hacia afuera.
getmode() devuelve un solo valor aunque haya empate: which.max() se queda
con el primero que encuentra y no avisa de que había otros con la misma
frecuencia. Además, la moda casi no sirve para variables continuas, donde
prácticamente ningún valor se repite. Aquí funciona porque los atrasos están
redondeados a minutos enteros.
Añadir el promedio, mediana y moda al mismo data frame
## The mode of the value
library(DescTools)
por_dia_4 <- vuelos %>%
group_by(dia) %>%
summarise(atraso_promedio = mean(atraso_salida, na.rm = TRUE),
atraso_median = median(atraso_salida, na.rm=TRUE),
atraso_mode = getmode(atraso_salida))
por_dia_4## # A tibble: 31 × 4
## dia atraso_promedio atraso_median atraso_mode
## <int> <dbl> <dbl> <dbl>
## 1 1 14.2 -2 -5
## 2 2 14.1 -1 -4
## 3 3 10.8 -2 -4
## 4 4 5.79 -3 -5
## 5 5 7.82 -3 -5
## 6 6 6.99 -2 -4
## 7 7 14.3 -1 -5
## 8 8 21.8 -1 NA
## 9 9 14.6 -1 -4
## 10 10 18.3 -1 -5
## # ℹ 21 more rows
6.4 Agrupar por múltiples variables
Aquí se agrupar por día y mes
library(tidyverse)
library(datos)
vuelos %>%
dplyr::select(anio, mes, dia, atraso_llegada, destino, aerolinea) %>%
filter(destino =="SJU") %>%
filter(dia ==25) %>%
# filter(mes == 12) %>%
group_by(dia, aerolinea) %>%
summarise(atraso = mean(atraso_llegada, na.rm = TRUE))## # A tibble: 4 × 3
## # Groups: dia [1]
## dia aerolinea atraso
## <int> <chr> <dbl>
## 1 25 AA -5.41
## 2 25 B6 4.21
## 3 25 DL -4.19
## 4 25 UA -4.33
Por último, group_by(dia, aerolinea) agrupa por la combinación de las dos
variables, así que cada fila del resultado es un día y una aerolínea a la vez. Es
lo que hay que hacer siempre que la pregunta tenga dos dimensiones.
Al correr ese bloque, R imprime un mensaje que empieza por
summarise() has grouped output by 'dia'. No es un error. Cuando se agrupa por
dos variables y se resume, summarise() quita el último nivel de agrupación
pero deja el primero puesto, así que la tabla resultante sigue agrupada por dia.
Eso importa si después se hace otro cálculo, porque se hará por día sin que nadie
lo haya pedido. La costumbre es cerrar la cadena con ungroup(), o escribir
summarise(..., .groups = "drop"), para dejar la tabla limpia.
Nota también la línea comentada # filter(mes == 12). Sin ella, el “día 25” son
los veinticinco de los doce meses, no la Navidad. Quitar y poner ese comentario es
una buena manera de ver en clase cuánto cambia el resultado.
6.5 Ejercicios del capítulo
Sobre los ejercicios de este capítulo. Cada ejercicio se entrega con tres
cosas: el script, el resultado (que el bloque corra) y una explicación
en palabras. Los ejercicios usan millas del paquete datos, no vuelos, que
es el conjunto con el que se enseña en el capítulo. La hoja completa para entregar
está en Ejercicios/Ejercicios_Capitulo_06_estructura.Rmd.
6.5.1 Ejercicio 1. Leer la estructura
Datos: millas. Usa glimpse() para ver la estructura y contesta cuántas
columnas hay y cuántas de ellas son numéricas.
Verificación: millas tiene 234 filas y 11 columnas, y 5 de esas
columnas son numéricas (<int> o <dbl>).
En palabras: la columna anio aparece como número. ¿Tiene sentido calcular
mean(millas$anio)? Explica qué significaría ese número y por qué el tipo de dato
no basta para decidir qué operaciones son razonables.
6.5.2 Ejercicio 2. Los valores que existen
Datos: millas. Antes de filtrar nada, averigua qué valores distintos tiene
clase y cuántos son, ordenados alfabéticamente. Haz lo mismo con fabricante.
Verificación: deben salir 7 clases y 15 fabricantes.
En palabras: si filtraras por clase == "SUV", en mayúsculas, ¿cuántas filas
te devolvería R y qué mensaje daría? Explica por qué ese resultado es más
peligroso que un error.
6.5.3 Ejercicio 3. Filtrar con una condición
Datos: millas. Quédate con los automóviles de clase "suv".
Verificación: deben quedar 62 filas.
En palabras: filter() devolvió una tabla nueva. ¿Cambió millas? Comprueba
nrow(millas) después de filtrar y explica qué significa eso para tu manera de
trabajar.
6.5.4 Ejercicio 4. Filtrar con %in%
Datos: millas. Quédate con los automóviles que sean de clase "suv" o
"2seater", usando %in%. Después haz la misma consulta con | y comprueba que
dan el mismo número de filas.
Verificación: las dos versiones deben devolver 67 filas (62 más 5).
En palabras: escribe también la versión incorrecta,
filter(millas, clase == c("suv", "2seater")), mira cuántas filas devuelve y
explica qué hizo R.
6.5.5 Ejercicio 5. Seleccionar y ordenar
Datos: millas. Quédate con las columnas fabricante, modelo, clase,
ciudad y autopista; filtra los automóviles del año 2008; y ordena de mayor a
menor consumo en autopista.
Verificación: la tabla resultante debe tener 5 columnas, y el automóvil de la primera fila debe rendir más en carretera que el de la última.
En palabras: hiciste select() y filter() en algún orden. ¿Habría
funcionado si hubieras seleccionado solo fabricante y modelo antes de filtrar
por año? Explica por qué.
6.5.6 Ejercicio 6. Agrupar y resumir
Datos: millas. Calcula, para cada clase, el promedio y la mediana del consumo
en autopista y cuántos automóviles hay en esa clase (usa n()). Ordena de mayor
a menor promedio.
Verificación: la tabla debe tener 7 filas y 4 columnas. La suma de la
columna de conteos tiene que dar 234. La clase pickup debe quedar en el
último lugar y 2seater debe tener solo 5 automóviles.
En palabras: ¿por qué el promedio de 2seater es poco confiable aunque el
número que sale sea perfectamente razonable? Y explica en una oración por qué el
resultado de pickup tiene sentido.
6.5.7 Ejercicio 7 (BONO). Dos variables de agrupación
Datos: millas. Calcula el promedio de autopista por la combinación de clase
y traccion. Lee el mensaje que imprime R y después cierra la cadena de manera
que la tabla resultante no quede agrupada.
Verificación: la tabla debe tener menos filas que el producto de 7 clases por
3 tracciones, porque no todas las combinaciones existen. Comprueba con
dplyr::group_vars() que al final no queda ninguna agrupación.
En palabras: copia el mensaje que imprimió summarise(), explica qué está
avisando, y da un ejemplo concreto de un cálculo posterior que saldría mal si no
lo hubieras atendido.