datos <- datos |>
mutate(variable_nueva = funcion(var1))Funciones condicionales en R
Introducción
Continuamos incorporando funciones útiles para el manejo de datos, enfocándonos en la creación de nuevas variables y en la transformación de variables cuantitativas a variables categóricas, ya sean nominales u ordinales.
Algunas de estas operaciones serán necesarias para alcanzar los objetivos del trabajo práctico grupal de esta unidad, y seguramente también se utilizarán en las unidades siguientes.
Como vimos dentro del universo tidyverse, para crear nuevas variables a partir de cálculos utilizamos la función-verbo mutate():
Frecuentemente necesitaremos agrupar, resumir o discretizar variables continuas, dividiendo su rango en categorías que pueden ser dicotómicas o politómicas, según los objetivos del análisis.
Condicional simple: if_else()
Para obtener salidas dicotómicas, podemos usar la función condicional if_else() del paquete dplyr. Esta función es una versión simplificada y segura del clásico condicional if que existe en la mayoría de los lenguajes de programación, así como en hojas de cálculo como Microsoft Excel® y Google Sheets®, entre otras.
Supongamos que tenemos un dataframe llamado datos, con una variable cuantitativa denominada var1:
Queremos crear una variable cualitativa dentro de datos, llamada variable_nueva, cuyos valores estén dados por el cumplimiento de alguna condición de var1. Por ejemplo, si los valores de var1 son mayores a 10, entonces variable_nueva, tomará el valor “mayor a 10”, en caso contrario, tomará el valor “menor o igual a 10”:
variable_nueva n percent
mayor a 10 44 0.88
menor o igual a 10 6 0.12
La función if_else() tiene tres argumentos obligatorios:
condition: la condición a evaluar.true: valor que tomará la nueva variable si se cumple la condición.false: valor que tomará la nueva variable si no se cumple la condición.
Este proceso se conoce como dicotomización de una variable, ya que el resultado posible consta de solo dos categorías.
Los valores de salida pueden ser de distintos tipos (carácter, numérico o lógico). Sin embargo, cuando discretizamos una variable cuantitativa, habitualmente generamos una variable cualitativa de tipo ordinal, con categorías expresadas como texto (tipo character).
Ahora bien, al ser ordinal estas categorías de la variable_nueva deben “ordenarse” en la forma de los valores de la variable, pero el lenguaje R no sabe con que estamos trabajando y respeta siempre el ordenamiento alfanumérico. Por lo tanto, en este ejemplo las categorías se van a estar ordenando al reves del orden numérico natural (de menor a mayor).
La categoría "mayor a 10” se ordena alfabéticamente antes de “menor o igual a 10”, porque luego del empate de las letras m, le siguen la a en el primer caso y la e en el segundo.
Para ordenar estas categorías debemos transformar la variable de character a factor. Esto se puede hacer en un solo paso dentro del mutate():
variable_nueva n percent
menor o igual a 10 6 0.12
mayor a 10 44 0.88
Otra forma más artesanal, igualmente válido, es “forzar” el ordenamiento con las categorías así:
variable_nueva n percent
1.menor o igual a 10 6 0.12
2.mayor a 10 44 0.88
Aquí agregamos números iniciales a las etiquetas de las categorías para darle el orden que deseamos, sin necesidad de convertir a factor.
Función cut_interval()
El ecosistema tidyverse ofrece la función cut_interval() para la creación de intervalos regulares.
Es una adaptación de la función cut() de R base para tidy data y sus argumentos son similares. Volviendo al ejemplo anterior:
datos <- datos |>
mutate(grupo_var = cut_interval(x = var1,
length = 10,
right = TRUE,
ordered_result = TRUE)
)
# Explora grupo_var
tabyl(datos, grupo_var) grupo_var n percent
[0,10] 6 0.12
(10,20] 4 0.08
(20,30] 7 0.14
(30,40] 6 0.12
(40,50] 6 0.12
(50,60] 5 0.10
(60,70] 4 0.08
(70,80] 7 0.14
(80,90] 5 0.10
Los argumentos función son:
x: conjunto de datos numéricos de entrada (obligatorio).length: longitud de cada intervalo regular (obligatorio).n: cantidad de intervalos a crear (obligatorio, omitir si se definiólength).right: indica si los intervalos son cerrados a la derecha o viceversa (valor por defectoTRUE, intervalos cerrados a la derecha).labels: etiquetas de los intervalos automáticas o numéricas (por defecto intervalos matemáticos).ordered_result: determina si el resultado es un factor ordenado. Por defecto valeFALSE(la salida es tipo carácter).
No es necesario definir los argumentos opcionales siempre y cuando los valores por defecto sirvan para nuestra tarea.
Función case_when()
Cuando las condiciones no son simples, es decir, el resultado no es dicotómico y además los intervalos son irregulares, utilizamos la función case_when() que es una vectorización de la función if_else().
Supongamos que queremos agrupar la variable cuantitativa de números enteros (var1) en tres grupos irregulares:
grupo_var n percent
Grupo 1 12 0.24
Grupo 2 23 0.46
Grupo 3 15 0.30
Existe una condición por cada grupo creado, como si fuese un if_else() donde el valor declarado siempre es el verdadero. Se utilizan operadores de comparación como mayor ( > ), menor ( < ) y/o igual ( == ) y conectores lógicos como & (AND), | (NOT) e %in%. En cada línea va una virgulilla similar a la usada en la sintaxis formula (~) y luego la etiqueta que tomarán las observaciones que cumplan con esa condición en la nueva variable (grupo_var).
Esta evaluación es secuencial y su funcionamiento provoca que el usuario del lenguaje tenga el control de lo que esta sucediendo, por lo que cualquier mala definición de las condiciones puede provocar resultados incorrectos.
Si incorporamos el argumento .default podemos indicar que valor toma si no se cumple ninguna de las condiciones anteriores.
Por ejemplo, podríamos tener algún valor perdido (NA) en var1 y queremos que la variable grupo_var etiquete esos valores perdidos como "Sin dato".
grupo_var n percent
Grupo 1 12 0.24
Grupo 2 22 0.44
Grupo 3 15 0.30
Sin dato 1 0.02
Otra forma de definir los valores que no cumplen con ninguna condición es usando TRUE ~ valor:
grupo_var n percent
Grupo 1 12 0.24
Grupo 2 22 0.44
Grupo 3 15 0.30
Sin dato 1 0.02
Las salidas son de tipo carácter (chr) y debemos manejar el ordenamiento de las etiquetas como vimos anteriormente, por medio de factores o comenzando con caracteres ordenados alfabéticamente.
Para simplificar el trabajo de estos intervalos de clase irregulares y no provocar errores en la confección de las condiciones, tidyverse tiene a la función between().
Intervalos: función between()
La función between() básicamente opera como un atajo para condiciones de intervalos. Define dentro de los argumentos los límites inferior y superior de un intervalo y se utiliza dentro de una función de condición tipo if_else() o case_when().
Aplicado sobre el ejemplo anterior se vería así:
grupo_var n percent
Grupo 1 12 0.24
Grupo 2 22 0.44
Grupo 3 15 0.30
Sin dato 1 0.02
Los valores declarados como límites quedan incluidos siempre dentro del intervalo (son cerrados ambos). También podemos utilizar valores reservados como Inf o -Inf cuando desconocemos con que valor máximo o mínimo nos vamos a encontrar en la variable cuantitativa original.
Intervalos: función age_categories()
El paquete epikit (Spina, Kamvar, y Schumacher 2024), contiene herramientas útiles para trabajar con datos en salud públicas. La función age_categories(), transforma una variable numérica discreta en grupos de edad. Volviendo al ejemplo anterior, supongamos que var1 contiene edades de pacientes:
# Cargar paquete
library(epikit)
datos <- datos |>
mutate(edad_cat = age_categories(x = var1,
lower = 1,
upper = 80,
by = 10,
separator = " a ",
above.char = " y más"))
# Explora edad_cat
tabyl(datos, edad_cat) edad_cat n percent valid_percent
1 a 10 6 0.12 0.12244898
11 a 20 4 0.08 0.08163265
21 a 30 7 0.14 0.14285714
31 a 40 6 0.12 0.12244898
41 a 50 5 0.10 0.10204082
51 a 60 5 0.10 0.10204082
61 a 70 4 0.08 0.08163265
71 a 79 7 0.14 0.14285714
80 y más 5 0.10 0.10204082
<NA> 1 0.02 NA
Los argumentos principales de la función son:
x: vector numérico (obligatorio).lower: límite inferior de edades (por defecto0).upper: límite superior de edades.by: intervalo de años para los grupos.breakers: alternativo aby, para definir manualmente los grupos.separator: carácter que separa el rango de edades (por defecto"-").ceiling: define si incluir el valor más alto en los grupos (por defectoFALSE).above.char: el valor que queremos que aparezca para las edades por fuera del grupo de edad más alto (por defecto+). Solo funciona siceiling = FALSE.
Ejemplo práctico en R
Tomemos un caso clásico como la variable edad medida en años, variable que generalmente tenemos en toda tabla de datos vinculada a personas. Trabajaremos con la base de datos “edad.txt” que contiene 106 observaciones de pacientes:
Rows: 106
Columns: 3
$ id_mujer <dbl> 115971, 1689710, 114710, 3058900, 3223547, 3234027, 3…
$ fecha_nacimiento <chr> "30/05/1967", "23/04/1991", "06/11/1962", "09/03/1964…
$ fecha_test <chr> "17/01/2022", "01/02/2022", "16/03/2022", "23/02/2022…
A continuación, vamos a transformar las variables cuyo nombre contenga “fecha_” a formato fecha para poder calcular edades:
datos <- datos |>
# Transformar a formato fecha
mutate(across(.cols = starts_with("fecha_"),
.fns = ~ dmy(.x))) |>
# Calcular edad al momento del testeo
mutate(edad = round(
as.duration(fecha_test - fecha_nacimiento) / dyears(1)))Una primera posibilidad es dicotomizar la edad usando el valor de la mediana:
Aplicando el valor de la mediana dentro de un if_else() podríamos hacer:
# A tibble: 2 × 2
grupo_edad1 n
<chr> <int>
1 mayor a la mediana 52
2 menor o igual a la mediana 54
Observamos en el conteo que grupo_edad1 se construyó adecuadamente pero el orden de los niveles no es correcto si queremos que siga el ordenamiento natural de edad (de menor a mayor).
Una de las formas que vimos es convertir a factor:
# A tibble: 2 × 2
grupo_edad1 n
<fct> <int>
1 menor o igual a la mediana 54
2 mayor a la mediana 52
Vemos que en el conteo el formato de la variable ya no es chr sino fct y el orden de las etiquetas siguen la forma “menor a mayor”.
Otra forma es:
# A tibble: 2 × 2
grupo_edad1 n
<chr> <int>
1 1.menor o igual a la mediana 54
2 2.mayor a la mediana 52
Si en cambio necesitamos que los grupos sean mas de dos y que estos intervalos de clase sean regulares, podemos usar cut_interval():
datos <- datos |>
mutate(grupo_edad2 = cut_interval(x = edad,
length = 10))
# Explorar grupo_edad2
datos |>
count(grupo_edad2)# A tibble: 8 × 2
grupo_edad2 n
<fct> <int>
1 [0,10] 3
2 (10,20] 3
3 (20,30] 2
4 (30,40] 3
5 (40,50] 13
6 (50,60] 52
7 (60,70] 27
8 (70,80] 3
La salida muestra ocho grupos etarios con etiquetas ordenadas con notación matemática, donde un corchete indica que el límite del intervalo es cerrado, es decir contiene el valor y un paréntesis es abierto y no lo hace.Así es que el primer grupo va de 0 a 10 años y el segundo de 11 a 20.
Estos sucede así porque en forma predeterminada el argumento right = TRUE. Veamos que pasa si lo cambiamos a FALSE:
datos <- datos |>
mutate(grupo_edad2 = cut_interval(x = edad,
length = 10,
right = F))
# Explorar grupo_edad2
datos |>
count(grupo_edad2)# A tibble: 8 × 2
grupo_edad2 n
<fct> <int>
1 [0,10) 3
2 [10,20) 3
3 [20,30) 2
4 [30,40) 3
5 [40,50) 10
6 [50,60) 48
7 [60,70) 32
8 [70,80] 5
En esta salida el primer grupo va de 0 a 9 y el segundo de 10 a 19.
Hasta ahora la variable grupo_edad2 es de tipo carácter, pero si deseamos que la salida sea factor podemos incorporar el argumento ordered_result = TRUE:
datos <- datos |>
mutate(grupo_edad2 = cut_interval(x = edad,
length = 10,
ordered_result = T))
# Explorar grupo_edad2
datos |>
count(grupo_edad2)# A tibble: 8 × 2
grupo_edad2 n
<ord> <int>
1 [0,10] 3
2 (10,20] 3
3 (20,30] 2
4 (30,40] 3
5 (40,50] 13
6 (50,60] 52
7 (60,70] 27
8 (70,80] 3
Construimos así una variable factor ordenada <ord>.
Por último, con el argumento labels= FALSE hacemos que las etiquetas de los ocho grupos sean numéricas:
datos <- datos |>
mutate(grupo_edad2 = cut_interval(x = edad,
length = 10,
labels = F))
# Explorar grupo_edad2
datos |>
count(grupo_edad2)# A tibble: 8 × 2
grupo_edad2 n
<int> <int>
1 1 3
2 2 3
3 3 2
4 4 3
5 5 13
6 6 52
7 7 27
8 8 3
Otro ejemplo, podría ser aplicando case_when() donde discretizamos la edad en cuatro grupos irregulares, forzando sus etiquetas para lograr el orden adecuado:
# A tibble: 4 × 2
grupo_edad3 n
<chr> <int>
1 1.Niño 3
2 2.Adolescente 5
3 3.Adulto_joven 86
4 4.Adulto_mayor 12
Si no hubiésemos etiquetado con los números por delante el orden alfabético hacía que Niño fuese a parar al final del conteo.
De la misma forma pero más sencillo y controlado es:
# A tibble: 4 × 2
grupo_edad3 n
<chr> <int>
1 1.Niño 3
2 2.Adolescente 5
3 3.Adulto_joven 86
4 4.Adulto_mayor 12
Estas funciones condicionales que tratamos en este documento no se limitan a la tarea de construir agrupamientos de variables cuantitativas sino que sirven para cualquier situación donde a partir de una o más condiciones se produzcan una o más valores como respuesta.
Finalmente veamos como quedarían los grupos de edad con la función age_categories():
datos <- datos |>
mutate(grupo_edad4 = age_categories(x = edad,
breakers = c(0, 12, 25, 65),
above.char = " y más")
)
# Explorar grupo_edad4
datos |>
count(grupo_edad4)# A tibble: 4 × 2
grupo_edad4 n
<fct> <int>
1 0-11 3
2 12-24 4
3 25-64 87
4 65 y más 12
En este caso se genera una variable de tipo factor con los grupos de edad ordenados de mayor a menor.