Capítulo7 Transformación: Operaciones booleanas y lógicas
Fecha de la última revisión
## [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>
Tema de este módulo:
- Filtrar filas con filter()
- Seleccionar columnas con select()
- Agrupar con group_by()
- Resumir con summarise()
- Ordenar con arrange()
- funciones Booleanas y lógicas
7.2 Operaciones lógicas boolean:
- & “ampersand” == “conjunción”
- “|” or = “o”
- !, no incluye, excluir….
- %in%, filtrar para múltiples valores
- ==, es igual a… “exactamente”
- “<” menor que
- “>” mayor que
- “<=” es menor o igual
- “>=” es mayor o igual
7.3 Cómo piensa R una condición
Todo lo de este capítulo descansa sobre una idea sola: una condición no devuelve
filas, devuelve TRUE y FALSE. Cuando escribes filter(vuelos, mes == 12),
R evalúa mes == 12 para las 336,776 filas y obtiene un vector de 336,776 valores
lógicos. Después filter() se queda con las filas donde ese vector dice TRUE.
Puedes verlo por separado, y vale la pena hacerlo la primera vez:
head(vuelos$mes == 12) # TRUE / FALSE, uno por vuelo
sum(vuelos$mes == 12) # cuántos TRUE hay: TRUE cuenta como 1
mean(vuelos$mes == 12) # qué proporción son TRUEEse truco de sum() y mean() sobre una condición es de los más útiles del
curso: sum() de un vector lógico cuenta los casos que la cumplen, y
mean() da directamente la proporción, porque TRUE vale 1 y FALSE vale 0.
Los operadores lógicos combinan esos TRUE y FALSE. Su comportamiento se
resume en una tabla pequeña que conviene tener clara:
x |
y |
x & y |
x \| y |
!x |
|---|---|---|---|---|
TRUE |
TRUE |
TRUE |
TRUE |
FALSE |
TRUE |
FALSE |
FALSE |
TRUE |
FALSE |
FALSE |
TRUE |
FALSE |
TRUE |
TRUE |
FALSE |
FALSE |
FALSE |
FALSE |
TRUE |
Dos advertencias sobre la palabra “o”. En español, “quiero los vuelos de enero o
de diciembre” significa uno u otro, y | hace exactamente eso. Pero en español
también decimos “los vuelos de enero y de diciembre” queriendo decir lo mismo, y
si se traduce literalmente a mes == 1 & mes == 12 el resultado son cero
filas, porque ningún vuelo puede ser de enero y de diciembre a la vez. La
traducción correcta de esa frase es |, o mejor, %in% c(1, 12).
& se evalúa antes que |, igual que la multiplicación antes que la suma.
Así que a | b & c significa a | (b & c) y no (a | b) & c, que es una tabla
completamente distinta. Cuando mezcles los dos operadores, pon paréntesis
siempre, aunque creas que no hacen falta. Es gratis y evita un error que no da
ningún aviso.
filter() con operadores lógicos: combinar condiciones.
&(y),|(o),!(no / excluir).%in% c(...): ¿el valor está en esta lista? (evita repetir==).between(x, a, b): equivale ax >= a & x <= b.- Comparaciones:
==,!=,<,>,<=,>=.
Nunca compares dos números con decimales usando ==. La computadora guarda los
decimales de manera aproximada, así que sqrt(2)^2 == 2 devuelve FALSE.
Para eso está near(x, y), de dplyr, que pregunta si dos números son iguales
dentro de una tolerancia razonable. Con enteros, con texto y con factores, ==
funciona sin problema.
Y hay un tercer valor que casi nadie espera. Además de TRUE y FALSE, una
comparación puede devolver NA, y lo hace siempre que uno de los lados sea
NA. La lógica es correcta: si no sé cuánto se atrasó un vuelo, tampoco sé si se
atrasó más de una hora. Por eso NA > 60 es NA, y por eso NA == NA también es
NA (dos cosas que no conozco no tienen por qué ser iguales).
Esto tiene una consecuencia práctica muy concreta: filter() descarta los
NA, porque solo conserva lo que es TRUE. Si quieres verlos, hay que pedirlo
con is.na():
NA > 60 # NA
NA == NA # NA
is.na(NA) # TRUE, esta es la manera correcta de preguntar
sum(is.na(vuelos$atraso_salida)) # 8255 vuelos sin dato de salidaEl tema de los valores faltantes tiene su propio capítulo a continuación.
Bollean, que incluye múltiples opciones %in%
No confundas = con ==. Un solo = asigna un valor, mientras que == compara si dos valores son iguales; dentro de filter() siempre se usa ==. Para comparar contra varios valores usa %in% en lugar de encadenar muchos ==. Encontrarás más errores frecuentes y cómo resolverlos en el capítulo Errores comunes (14).
vuelos %>%
dplyr::select(anio, mes, dia, atraso_llegada, destino) %>%
filter(destino == "SJU") %>%
filter(mes %in% c(6:12) & dia == 25) %>%
group_by(dia, mes) %>%
summarise(atraso = mean(atraso_llegada, na.rm = TRUE))## # A tibble: 7 × 3
## # Groups: dia [1]
## dia mes atraso
## <int> <int> <dbl>
## 1 25 6 34.3
## 2 25 7 3.58
## 3 25 8 11.1
## 4 25 9 -15.2
## 5 25 10 -3.77
## 6 25 11 -18.9
## 7 25 12 3.57
Lee la cadena de arriba abajo, que es como está escrita: toma vuelos, quédate
con cinco columnas, quédate con los vuelos a San Juan, quédate con los que sean
del 25 de un mes entre junio y diciembre, agrupa por día y mes, y calcula el
atraso promedio de llegada.
Fíjate en mes %in% c(6:12). El 6:12 genera el vector 6 7 8 9 10 11 12, y
%in% pregunta si el mes de cada vuelo está en esa lista. Escribirlo con |
habría requerido siete condiciones.
vuelos %>%
dplyr::select(anio, mes, dia, atraso_salida, aerolinea) %>%
group_by(mes, aerolinea) %>%
filter(mes %in% c(1:6)) %>% # mes entre 1 y 6
filter(aerolinea %in% c("AA", "UA")) %>%
summarise(atraso = mean(atraso_salida, na.rm = TRUE))## # A tibble: 12 × 3
## # Groups: mes [6]
## mes aerolinea atraso
## <int> <chr> <dbl>
## 1 1 AA 6.93
## 2 1 UA 8.33
## 3 2 AA 8.28
## 4 2 UA 7.71
## 5 3 AA 8.70
## 6 3 UA 11.7
## 7 4 AA 11.7
## 8 4 UA 13.7
## 9 5 AA 9.66
## 10 5 UA 12.3
## 11 6 AA 14.6
## 12 6 UA 20.3
Aquí hay algo poco habitual: el group_by() está antes de los filter().
Funciona, porque filter() respeta la agrupación y la mantiene, pero es confuso
de leer. La convención es agrupar justo antes de resumir, que es cuando la
agrupación hace falta.
Aprovecha para notar el orden general que se repite en casi todo el curso: seleccionar y filtrar primero, agrupar después, resumir al final. Filtrar antes de agrupar es además más rápido, porque el cálculo pesado se hace sobre menos filas.
7.3.1 Otra alternativa para filtrar
7.3.1.1 “|” or
vuelos %>%
group_by(mes) %>%
dplyr::select(anio, mes, dia, atraso_salida) %>%
filter(mes == 1 | mes == 12) |> # mes 1 o 12
summarise(atraso = mean(atraso_salida, na.rm = TRUE))## # A tibble: 2 × 2
## mes atraso
## <int> <dbl>
## 1 1 10.0
## 2 12 16.6
Este es el caso del “o”. mes == 1 | mes == 12 conserva los vuelos de enero y
también los de diciembre. Con dos valores, | se lee bien; con cinco, conviene
pasarse a %in% c(1, 2, 11, 12, ...).
7.3.1.2 & el “y”
## [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(anio, mes, dia, atraso_salida, aerolinea) %>%
filter(mes == 12 & dia == 25 & aerolinea == "AA")## # A tibble: 78 × 5
## anio mes dia atraso_salida aerolinea
## <int> <int> <int> <dbl> <chr>
## 1 2013 12 25 2 AA
## 2 2013 12 25 -4 AA
## 3 2013 12 25 1 AA
## 4 2013 12 25 0 AA
## 5 2013 12 25 -3 AA
## 6 2013 12 25 -2 AA
## 7 2013 12 25 26 AA
## 8 2013 12 25 -7 AA
## 9 2013 12 25 -6 AA
## 10 2013 12 25 21 AA
## # ℹ 68 more rows
Y este es el caso del “y”. Las tres condiciones tienen que cumplirse a la vez, y
cada una habla de una variable distinta, que es cuando & tiene sentido. Este
mismo filtro se puede escribir separando las condiciones con comas,
filter(mes == 12, dia == 25, aerolinea == "AA"), que es la forma que prefiere la
documentación de dplyr.
7.5
!, excluye algo, En este caso estamos excluyendo las aerolíneas “AA” y “DL”
vuelos %>%
dplyr::select(anio, mes, dia, atraso_salida, aerolinea) %>%
filter(mes == 11 & !aerolinea %in% c("AA", "DL")) %>%
group_by(aerolinea) %>%
summarise(atraso = mean(atraso_salida, na.rm = TRUE))## # A tibble: 14 × 2
## aerolinea atraso
## <chr> <dbl>
## 1 9E 7.56
## 2 AS 3.08
## 3 B6 3.52
## 4 EV 9.83
## 5 F9 13.5
## 6 FL 16.9
## 7 HA -5.44
## 8 MQ 3.28
## 9 OO 0.8
## 10 UA 6.37
## 11 US 0.576
## 12 VX 7.80
## 13 WN 11.0
## 14 YV 10.5
El ! niega, es decir, invierte los TRUE y los FALSE. Aquí se lee “que la
aerolínea no esté entre AA y DL”.
Vale la pena mirar dónde está colocado: !aerolinea %in% c("AA", "DL") niega el
resultado completo de %in%, que es lo que queremos. La colocación funciona
porque %in% se evalúa antes que !. Aun así, muchos prefieren escribirlo con
paréntesis, !(aerolinea %in% c("AA", "DL")), que dice exactamente lo mismo y no
obliga a recordar la precedencia.
Después de un filtro con !, comprueba el resultado con unique(): si las
aerolíneas que querías excluir ya no aparecen, el filtro hizo lo que creías. Es
una comprobación de dos segundos que evita conclusiones equivocadas.
Ejercicios:
Hacer los ejercicios en la sección 5.2.4 del libro en español
5.2.4 Ejercicios
- Encuentra todos los vuelos que:
Tuvieron un retraso de llegada de dos o más horas
Volaron a Houston (IAH o HOU)
Cuáles son estos aeropuertos, Hou == Houston, IAH == George Bush, Texas
Selecciona solamente los vuelos que fueron operados por United, American o Delta
Selecciona solamente los vuelos que partieron en invierno del hemisferio sur (julio, agosto y septiembre)
Selecciona solamente los vuelos excluyendo los días del 2 al 29 de cualquier mes
## # A tibble: 29 × 19
## anio mes dia horario_salida salida_programada atraso_salida
## <int> <int> <int> <int> <int> <dbl>
## 1 2013 1 27 1419 1420 -1
## 2 2013 10 7 1350 1350 0
## 3 2013 10 7 1357 1359 -2
## 4 2013 10 16 657 700 -3
## 5 2013 11 1 658 700 -2
## 6 2013 3 18 1844 1847 -3
## 7 2013 4 17 1635 1640 -5
## 8 2013 4 18 558 600 -2
## 9 2013 4 18 655 700 -5
## 10 2013 5 22 1827 1830 -3
## # ℹ 19 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>
Selecciona solamente los vuelos que llegaron más de dos horas tarde, pero no salieron tarde
Selecciona solamente los vuelos que se retrasaron por lo menos una hora, pero repusieron más de 30 minutos en vuelo
Selecciona solamente los vuelos que partieron entre la medianoche y las 6a.m. (incluyente)
Otra función de dplyr que es útil para usar filtros es between(). ¿Qué hace? ¿Puedes usarla para simplificar el código necesario para responder a los desafíos anteriores?
between(x, izq, der) es un atajo equivalente a x >= izq & x <= der e incluye ambos extremos. Hace tus filtros más cortos y legibles.
Ojo con un detalle de este ejemplo concreto: la pregunta pide los vuelos entre la
medianoche y las 6 de la mañana, y horario_salida está escrito como número de
reloj (517 son las 5:17), no en minutos. Así que el límite es 600, las seis en
punto, y no 360. Es exactamente el tipo de confusión que produce guardar una
hora como si fuera un número corriente, y la razón por la que existe el capítulo
de fechas y horas.
between() viene en dplyr, pero el paquete data.table tiene una función con
el mismo nombre. Si has cargado data.table en la sesión, between() puede dejar
de comportarse como esperas. En ese caso, dplyr::between().
- Determinar cual “aerolinea” tiene más vuelos
7.5.1 La función arrange()
arrange()
arrange(): ordenar las filas.
- Qué hace: reordena el data frame según una o más columnas (ascendente por defecto).
desc(col): orden descendente (de mayor a menor).
En el primer bloque de abajo, arrange() se llama sin argumentos. Eso no
ordena nada: devuelve la tabla tal cual, sin error y sin aviso. Es un buen ejemplo
de código que parece hacer algo y no hace nada. Compáralo con el segundo bloque,
donde arrange(desc(horario_salida)) sí ordena.
Dos cosas más sobre arrange(). La primera: acepta varias variables, y las aplica
en orden de prioridad. arrange(aerolinea, desc(atraso_salida)) ordena
alfabéticamente por aerolínea y, dentro de cada una, por atraso de mayor a menor.
La segunda: los NA van siempre al final, ordenes como ordenes. Si buscas los
valores extremos de una variable con faltantes, esa es la razón por la que la cola
de la tabla está llena de NA en vez de los valores más grandes.
## # A tibble: 10 × 1
## horario_salida
## <int>
## 1 517
## 2 533
## 3 542
## 4 544
## 5 554
## 6 554
## 7 555
## 8 557
## 9 557
## 10 558
## # A tibble: 10 × 19
## anio mes dia horario_salida salida_programada atraso_salida
## <int> <int> <int> <int> <int> <dbl>
## 1 2013 10 30 2400 2359 1
## 2 2013 11 27 2400 2359 1
## 3 2013 12 5 2400 2359 1
## 4 2013 12 9 2400 2359 1
## 5 2013 12 9 2400 2250 70
## 6 2013 12 13 2400 2359 1
## 7 2013 12 19 2400 2359 1
## 8 2013 12 29 2400 1700 420
## 9 2013 2 7 2400 2359 1
## 10 2013 2 7 2400 2359 1
## # ℹ 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>
5.3.1 Ejercicios
Ordena vuelos para encontrar los vuelos más retrasados en salida.
Encuentra los vuelos que salieron más temprano.
Ordena vuelos para encontrar los vuelos más rápidos (que viajaron a mayor velocidad).
¿Cuáles vuelos viajaron más lejos?
¿Cuál viajó más cerca?
Otra función
- desc()
7.7 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 diamantes 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_07_booleanas.Rmd.
7.7.1 Ejercicio 1. Contar sin filtrar
Datos: diamantes. Sin usar filter(), y usando solo sum() y mean() sobre
una condición, averigua cuántos diamantes cuestan más de 15,000 dólares y qué
proporción del total representan.
Verificación: nrow(diamantes) es 53,940. Tu proporción tiene que quedar
entre 0 y 1, y multiplicada por 53,940 tiene que devolver tu conteo.
En palabras: explica por qué sum() de una condición devuelve un conteo.
¿Qué valor numérico tienen TRUE y FALSE para R?
7.7.2 Ejercicio 2. & frente a |
Datos: diamantes. Construye dos tablas: una con los diamantes de corte
"Ideal" y precio menor de 1,000; otra con los de corte "Ideal" o
precio menor de 1,000.
Verificación: la tabla del & tiene que tener menos filas que la del |,
y las dos tienen que tener menos de 53,940.
En palabras: un compañero dice “quiero los diamantes Ideal y Premium” y
escribe filter(diamantes, corte == "Ideal" & corte == "Premium"). ¿Cuántas filas
le devuelve y por qué? Escribe la versión correcta.
7.7.3 Ejercicio 3. Excluir con ! y %in%
Datos: diamantes. La variable color tiene 7 niveles. Construye una tabla que
excluya los colores "D" y "E", usando ! y %in%.
Verificación: en tu tabla deben quedar exactamente 5 colores distintos
(compruébalo con unique()), y las filas de tu tabla más las de la tabla
complementaria tienen que sumar 53,940.
En palabras: escribe también filter(diamantes, !color == "D" & !color == "E")
y comprueba que da el mismo resultado. ¿Cuál de las dos versiones prefieres y por
qué?
7.7.4 Ejercicio 4. between()
Datos: diamantes. Quédate con los diamantes cuyo quilate esté entre 1 y 2,
extremos incluidos, primero con >= y <= y después con between().
Verificación: las dos versiones tienen que devolver el mismo número de
filas. En la tabla resultante, min(quilate) tiene que ser exactamente 1 y
max(quilate) exactamente 2.
En palabras: ¿between() incluye o excluye los extremos? Explica cómo lo
comprobaste con los datos, y no solo con la ayuda.
7.7.5 Ejercicio 5. La lógica de los NA
diamantes no tiene valores faltantes, así que constrúyete uno:
x <- c(3, 7, NA, 12). Evalúa x > 5, x == NA, is.na(x), sum(x) y
sum(x, na.rm = TRUE). Después usa filter() sobre un tibble que contenga x
con la condición x > 5 y mira cuántas filas quedan.
Verificación: x > 5 tiene que devolver un NA en la tercera posición, no un
FALSE. x == NA tiene que devolver cuatro NA. El filter() tiene que
devolver 2 filas, no 3.
En palabras: explica por qué NA == NA no es TRUE, usando una frase que no
mencione a R. Y explica qué pasó con la fila del NA en el filter().
7.7.6 Ejercicio 6. La precedencia
Datos: diamantes. Corre estas dos consultas y compara el número de filas:
filter(diamantes, corte == "Ideal" | corte == "Premium" & precio < 500)filter(diamantes, (corte == "Ideal" | corte == "Premium") & precio < 500)
Verificación: las dos tienen que dar números muy distintos. En la versión (b), todos los precios tienen que ser menores de 500; en la (a), no.
En palabras: explica en palabras qué pregunta contesta cada una de las dos. ¿Cuál habrías escrito tú si te pidieran “los diamantes Ideal o Premium que cuesten menos de 500”?
7.7.7 Ejercicio 7 (BONO). Ordenar con dos claves
Datos: diamantes. Ordena la tabla por corte y, dentro de cada corte, por
precio de mayor a menor. Quédate con las 15 primeras filas y con las columnas
corte, quilate, color y precio.
Verificación: el precio máximo de todo el conjunto es 18,823 y el mínimo es 326. Si tu primera fila no tiene un precio cercano al máximo dentro de su corte, revisa el orden de tus argumentos.
En palabras: corte es un factor ordenado
(Regular < Bueno < Muy bueno < Premium < Ideal). ¿En qué orden aparecen los
cortes en tu resultado, alfabético o de calidad? Explica por qué, y qué habría
pasado si corte fuera texto corriente.