Capítulo11 Transformación: Rangos y otras funciones
Fecha de la última revisión
## [1] "2026-09-09"
## _
## platform aarch64-apple-darwin23
## arch aarch64
## os darwin23
## system aarch64, darwin23
## status
## major 4
## minor 6.1
## year 2026
## month 06
## day 24
## svn rev 90187
## language R
## version.string R version 4.6.1 (2026-06-24)
## nickname Happy Hop
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
11.1 Funciones en este módulo
Rangos:
min_rank(): asigna rangos a los valores, dejando huecos en los empatesdense_rank(): igual, pero sin dejar huecosrow_number(): un número distinto para cada fila, aunque haya empatesrank(): la versión de R base, promedia los empatesdesc(): invierte el orden, de mayor a menor
Rangos en escala de 0 a 1:
percent_rank(): la proporción de valores más pequeñoscume_dist(): la proporción de valores menores o iguales
Resúmenes y series:
group_by()consummarise(): resumir por grupofirst(): el primer valor en el orden de las filasrollmean(): promedio móvil (paquetezoo)
11.3 Rangos y pruebas no paramétricas
La pruebas no paramétricas no son basado en distribución normal y los indices como promedio, desviación estándar no se usan.
El rango es la pieza básica de casi todas ellas: en vez de trabajar con el valor observado, se trabaja con la posición que ocupa ese valor entre los demás.
Trabajar con rangos en vez de valores es una idea vieja y muy productiva. Charles Spearman publicó en 1904 su coeficiente de correlación de rangos, y en 1945 Frank Wilcoxon propuso las pruebas de rangos con signo y de suma de rangos, que hoy son el sustituto no paramétrico estándar de la prueba t.
11.4 min_rank() y min_rank(desc())
Asignar valores de rangos a los valores originales o de más grande a más pequeño o viceversa.
min_rank(), dense_rank(), row_number(): asignar rangos.
- Qué hacen: convierten valores en su posición (rango) de menor a mayor;
desc()invierte el orden. - Diferencia en empates:
min_rank()deja huecos,dense_rank()no,row_number()da un número único a cada fila. - Relacionadas:
percent_rank()ycume_dist()dan rangos en escala 0–1.
## [1] 22 1 36 46 7 39
## # A tibble: 10 × 1
## value
## <int>
## 1 22
## 2 1
## 3 36
## 4 46
## 5 7
## 6 39
## 7 34
## 8 10
## 9 24
## 10 30
df %>%
dplyr::select(value) %>%
mutate(rango_minimo=min_rank(value)) %>%
mutate(rango_min_desc=min_rank(desc(value)) )## # A tibble: 10 × 3
## value rango_minimo rango_min_desc
## <int> <int> <int>
## 1 22 4 7
## 2 1 1 10
## 3 36 8 3
## 4 46 10 1
## 5 7 2 9
## 6 39 9 2
## 7 34 7 4
## 8 10 3 8
## 9 24 5 6
## 10 30 6 5
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 y paises del paquete datos, es
decir, conjuntos distintos a los de los ejemplos de arriba: no se resuelven
cambiando el nombre de una columna en el script copiado. La hoja completa para
entregar está en Ejercicios/Ejercicios_Capitulo_11_rangos.Rmd.
Ejercicio 1: min_rank() y desc(). Toma una muestra de 500 diamantes con
set.seed(2026) y slice_sample(diamantes, n = 500). Añade el rango del
precio de menor a mayor y otro de mayor a menor con desc(), y enseña los 10
diamantes más caros de la muestra.
Verifica: la fila con el precio más alto debe tener rango descendente igual a 1.
En palabras: ¿cuánto suman los dos rangos en una misma fila? ¿Es siempre el mismo número? ¿Qué te dice eso sobre los empates?
11.5 row_number()
Qué hace la función row_number?
## [1] 10 21 22 NA 5 4
## # A tibble: 6 × 1
## value
## <dbl>
## 1 10
## 2 21
## 3 22
## 4 NA
## 5 5
## 6 4
## # A tibble: 6 × 2
## value row
## <dbl> <int>
## 1 10 3
## 2 21 4
## 3 22 5
## 4 NA NA
## 5 5 2
## 6 4 1
11.6 dense_rank()
min_rank(), dense_rank() y row_number() se diferencian en cómo tratan los empates: min_rank() deja huecos tras un empate, dense_rank() no deja huecos y row_number() da un valor distinto a cada fila.
## # A tibble: 6 × 1
## value
## <dbl>
## 1 10
## 2 12
## 3 12
## 4 NA
## 5 51
## 6 4
df %>%
dplyr::select(value) %>%
mutate(dense=dense_rank(value)) # equivalente a rank, NOTA que los NA no son asignado un valor## # A tibble: 6 × 2
## value dense
## <dbl> <int>
## 1 10 2
## 2 12 3
## 3 12 3
## 4 NA NA
## 5 51 4
## 6 4 1
Ejercicio 2: los empates. Con la misma muestra de diamantes, crea de una vez
tres columnas sobre quilate: min_rank(), dense_rank() y row_number().
Ordena por quilate y enseña las primeras 20 filas.
Verifica: el valor máximo de la columna de dense_rank() tiene que ser menor o
igual que el de min_rank().
En palabras: ¿cuál de las tres deja huecos después de un empate, cuál no deja huecos, y cuál le da un número distinto a cada fila aunque estén empatadas?
11.7 percent_rank()
Un número entre 0 y 1: es min_rank() re-escalado, así que el valor más pequeño
recibe 0 y el más grande recibe 1.
## [1] 1 2 2 NA 5 4
## # A tibble: 6 × 1
## value
## <dbl>
## 1 1
## 2 2
## 3 2
## 4 NA
## 5 5
## 6 4
df %>%
dplyr::select(value) %>%
mutate(porcentaje_rank=percent_rank(value)) # equivalente a rank, Un número entre a 0 y 1 calculado haciendo una re-escala de los valores usando min_rank to [0, 1]. NOTA que el valor más pequeño es reasignado un valor de 0 y el más grande un valor de 1. ## # A tibble: 6 × 2
## value porcentaje_rank
## <dbl> <dbl>
## 1 1 0
## 2 2 0.25
## 3 2 0.25
## 4 NA NA
## 5 5 1
## 6 4 0.75
11.7.1 percent_rank() sin NA
## # A tibble: 7 × 1
## value
## <int>
## 1 22
## 2 1
## 3 36
## 4 46
## 5 7
## 6 39
## 7 34
## # A tibble: 7 × 2
## value porc2
## <int> <dbl>
## 1 22 0.333
## 2 1 0
## 3 36 0.667
## 4 46 1
## 5 7 0.167
## 6 39 0.833
## 7 34 0.5
Ejercicio 3: los rangos y los NA. Los diamantes no traen valores
faltantes, así que crea unos: con if_else(), haz una columna igual a precio
pero con NA cuando el precio sea mayor de 10000. Cuenta cuántos NA quedaron y
calcula min_rank() y row_number() sobre esa columna nueva.
En palabras: ¿qué rango recibe una fila con NA? ¿Se lo saltan, quedan de
últimas, o quedan como NA? ¿Por qué esto importa cuando quieres “los 10 más
grandes” de una variable con datos incompletos?
11.8 cume_dist()
Es la proporción de valores menores o iguales al valor de la fila, es decir, la distribución acumulada empírica.
## [1] 1 2 3 NA 5 4 10 8
## # A tibble: 8 × 1
## value
## <dbl>
## 1 1
## 2 2
## 3 3
## 4 NA
## 5 5
## 6 4
## 7 10
## 8 8
## # A tibble: 8 × 2
## value rangos_cumulativo
## <dbl> <dbl>
## 1 1 0.143
## 2 2 0.286
## 3 3 0.429
## 4 NA NA
## 5 5 0.714
## 6 4 0.571
## 7 10 1
## 8 8 0.857
Ejercicio 4: percent_rank() y cume_dist(). Con la muestra de diamantes,
calcula las dos sobre quilate y enseña la fila del diamante más pequeño y la
del más grande.
Verifica: uno de los dos índices vale exactamente 0 en el mínimo y el otro no.
En palabras: una de las dos contesta “¿qué proporción de los diamantes es más pequeña que este?” y la otra “¿qué proporción es menor o igual que este?”. Di cuál es cuál y por qué el mínimo vale 0 en una y no en la otra.
11.9 Ejemplo trabajado: los vuelos más atrasados
Este es el ejercicio de la sección 5.5.2 del libro en español, resuelto en clase:
encontrar los 10 vuelos más retrasados con una función de ordenamiento, y decidir
qué hacer con los empates. Lee la documentación de min_rank() mientras lo sigues.
## [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(atraso_salida, aerolinea)%>%
arrange(desc(atraso_salida))%>%
mutate(mas_atrados=min_rank(desc(atraso_salida))) %>%
head(n=10)## # A tibble: 10 × 3
## atraso_salida aerolinea mas_atrados
## <dbl> <chr> <int>
## 1 1301 HA 1
## 2 1137 MQ 2
## 3 1126 MQ 3
## 4 1014 AA 4
## 5 1005 MQ 5
## 6 960 DL 6
## 7 911 DL 7
## 8 899 DL 8
## 9 898 DL 9
## 10 896 AA 10
## [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"
## # A tibble: 10 × 20
## anio mes dia horario_salida salida_programada atraso_salida
## <int> <int> <int> <int> <int> <dbl>
## 1 2013 12 7 2040 2123 -43
## 2 2013 2 3 2022 2055 -33
## 3 2013 11 10 1408 1440 -32
## 4 2013 1 11 1900 1930 -30
## 5 2013 1 29 1703 1730 -27
## 6 2013 8 9 729 755 -26
## 7 2013 10 23 1907 1932 -25
## 8 2013 3 30 2030 2055 -25
## 9 2013 3 2 1431 1455 -24
## 10 2013 5 5 934 958 -24
## # ℹ 14 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>, rango_minimo <int>
11.10 Resúmenes por grupo: group_by() y summarise()
group_by() y summarise(): resumir por grupo.
group_by(variable): no cambia los datos, solo marca cómo hay que dividirlos.summarise(): colapsa cada grupo a una fila, con las columnas que le pidas.- Ojo:
mutate()también respeta la agrupación, y conviene cerrar conungroup().
Esta sección usa flights del paquete nycflights13, que tiene los nombres de
las columnas en inglés (dep_delay, arr_delay, dest). En el resto del libro
usamos vuelos del paquete datos, que es la misma tabla con los nombres en
español (atraso_salida, atraso_llegada, destino).
## # A tibble: 1 × 1
## delay
## <dbl>
## 1 12.6
## # A tibble: 1 × 1
## delay
## <dbl>
## 1 12.6
by_day <- group_by(flights, year, month, day)
summarise(by_day, delay = mean(dep_delay, na.rm = TRUE))## # A tibble: 365 × 4
## # Groups: year, month [12]
## year month day delay
## <int> <int> <int> <dbl>
## 1 2013 1 1 11.5
## 2 2013 1 2 13.9
## 3 2013 1 3 11.0
## 4 2013 1 4 8.95
## 5 2013 1 5 5.73
## 6 2013 1 6 7.15
## 7 2013 1 7 5.42
## 8 2013 1 8 2.55
## 9 2013 1 9 2.28
## 10 2013 1 10 2.84
## # ℹ 355 more rows
## # A tibble: 365 × 4
## # Groups: year, month [12]
## year month day delay
## <int> <int> <int> <dbl>
## 1 2013 1 1 11.5
## 2 2013 1 2 13.9
## 3 2013 1 3 11.0
## 4 2013 1 4 8.95
## 5 2013 1 5 5.73
## 6 2013 1 6 7.15
## 7 2013 1 7 5.42
## 8 2013 1 8 2.55
## 9 2013 1 9 2.28
## 10 2013 1 10 2.84
## # ℹ 355 more rows
11.10.1 La aerolínea peor en atraso de salidas
flights %>%
group_by(carrier) %>%
summarise(delay = mean(dep_delay, na.rm = TRUE)) %>%
arrange(desc(delay))## # A tibble: 16 × 2
## carrier delay
## <chr> <dbl>
## 1 F9 20.2
## 2 EV 20.0
## 3 YV 19.0
## 4 FL 18.7
## 5 WN 17.7
## 6 9E 16.7
## 7 B6 13.0
## 8 VX 12.9
## 9 OO 12.6
## 10 UA 12.1
## 11 MQ 10.6
## 12 DL 9.26
## 13 AA 8.59
## 14 AS 5.80
## 15 HA 4.90
## 16 US 3.78
11.10.2 Atraso promedio y distancia por destino
## # A tibble: 336,776 × 19
## # Groups: dest [105]
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 1 517 515 2 830 819
## 2 2013 1 1 533 529 4 850 830
## 3 2013 1 1 542 540 2 923 850
## 4 2013 1 1 544 545 -1 1004 1022
## 5 2013 1 1 554 600 -6 812 837
## 6 2013 1 1 554 558 -4 740 728
## 7 2013 1 1 555 600 -5 913 854
## 8 2013 1 1 557 600 -3 709 723
## 9 2013 1 1 557 600 -3 838 846
## 10 2013 1 1 558 600 -2 753 745
## # ℹ 336,766 more rows
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
delay <- summarise(by_dest,
count = n(),
dist = mean(distance, na.rm = TRUE),
delay = mean(arr_delay, na.rm = TRUE))
delay## # A tibble: 105 × 4
## dest count dist delay
## <chr> <int> <dbl> <dbl>
## 1 ABQ 254 1826 4.38
## 2 ACK 265 199 4.85
## 3 ALB 439 143 14.4
## 4 ANC 8 3370 -2.5
## 5 ATL 17215 757. 11.3
## 6 AUS 2439 1514. 6.02
## 7 AVL 275 584. 8.00
## 8 BDL 443 116 7.05
## 9 BGR 375 378 8.03
## 10 BHM 297 866. 16.9
## # ℹ 95 more rows
#> `summarise()` ungrouping output (override with `.groups` argument)
delay <- filter(delay, count > 20, dest != "HNL")
# It looks like delays increase with distance up to ~750 miles
# and then decrease. Maybe as flights get longer there's more
# ability to make up delays in the air?
ggplot(data = delay, aes(x = dist, y = delay)) +
geom_point(aes(size = count), alpha = 1/3) +
geom_smooth(se = FALSE)
## [1] "dest" "count" "dist" "delay"
## # A tibble: 6 × 4
## dest count dist delay
## <chr> <int> <dbl> <dbl>
## 1 ABQ 254 1826 4.38
## 2 ACK 265 199 4.85
## 3 ALB 439 143 14.4
## 4 ATL 17215 757. 11.3
## 5 AUS 2439 1514. 6.02
## 6 AVL 275 584. 8.00
ggplot(data = delay, aes(x = dist, y = delay, label=dest)) +
geom_point() +
geom_smooth(se = FALSE)+
geom_text(size=2,aes(label=dest), hjust=1, vjust=-1)
Ejercicio 5: group_by() con summarise(). Con diamantes completo,
calcula por cada categoría de corte el número de diamantes, el precio promedio
y el quilate promedio, y ordena de mayor a menor precio promedio.
Verifica: la suma de la columna de conteos debe dar el total de filas de
diamantes.
En palabras: el corte "Ideal" es el mejor corte. ¿Sale con el precio
promedio más alto? Mira la columna del quilate promedio y propón una explicación.
11.11 Promedio móvil: rollmean()
rollmean() (paquete zoo): promedio móvil.
- Qué hace: sustituye cada valor por el promedio de una ventana de
kobservaciones vecinas, lo que suaviza la serie. align:"center"(por defecto),"right"(usa solo el pasado) o"left".fill = NA: rellena conNAlas posiciones donde la ventana no cabe.
Fuente: https://stackoverflow.com/questions/743812/calculating-moving-average
## # A tibble: 10 × 1
## day
## <int>
## 1 1
## 2 2
## 3 3
## 4 4
## 5 5
## 6 6
## 7 7
## 8 8
## 9 9
## 10 10
# cma = centered moving average
# tma = trailing moving average
some_data %>%
mutate(cma = rollmean(day, k = 3, fill = NA)) %>%
mutate(tma = rollmean(day, k = 3, fill = NA, align = "right")) %>%
mutate(lma = rollmean(day, k = 3, fill = NA, align = "left")) %>%
mutate(cmax=rollmax(day, k=30, fill=NA))## # A tibble: 10 × 5
## day cma tma lma cmax
## <int> <dbl> <dbl> <dbl> <lgl>
## 1 1 NA NA 2 NA
## 2 2 2 NA 3 NA
## 3 3 3 2 4 NA
## 4 4 4 3 5 NA
## 5 5 5 4 6 NA
## 6 6 6 5 7 NA
## 7 7 7 6 8 NA
## 8 8 8 7 9 NA
## 9 9 9 8 NA NA
## 10 10 NA 9 NA NA
## # A tibble: 10 × 1
## day
## <int>
## 1 1
## 2 2
## 3 3
## 4 4
## 5 5
## 6 6
## 7 7
## 8 8
## 9 9
## 10 10
Ejercicio 6: rollmean(). Con paises, filtra un país (por ejemplo
"Costa Rica") y ordena por anio. Calcula tres promedios móviles de
esperanza_de_vida con ventana de 3: centrado, align = "right" y
align = "left", con fill = NA. Grafica la serie original junto al promedio
móvil centrado.
En palabras: ¿en qué posiciones aparece NA en cada versión y por qué
cambian de lugar? ¿Qué le hace el promedio móvil a las subidas y bajadas de la
serie?
Ejercicio 7 (reto integrador). Combina dos funciones del capítulo. Con
diamantes completo, agrupa por color, calcula cume_dist(precio) dentro de
cada grupo y quédate con el 5 % más caro de su propio color. Cuenta cuántos
son por color. Después repite el ejercicio sin agrupar, con el 5 % más caro de
toda la tabla, y compara las dos listas.
En palabras: ¿cuál de las dos versiones contesta la pregunta “¿cuáles son los diamantes caros para su color?” y por qué la otra no?