¿Cuál es la evaluación tidy en R? De strings a símbolos guía completa de `sym()`

Si alguna vez has intentado usar el nombre de una columna almacenado en una variable dentro de dplyr y te has topado con errores, este artículo es para ti. La función sym() y la evaluación tidy son conceptos fundamentales que todo analista de datos en R debería dominar.

El Problema: Cuando los Strings No Son Suficientes

Imagina que estás trabajando con datos electorales y quieres calcular el total de votos por partido, pero el nombre del partido está almacenado en una variable:

library(dplyr)

party_name <- "MOVIMIENTO CIUDADANO"

# ❌ Esto NO funciona
datos |> 
  summarize(partido = sum(party_name))
# Error: no puede sumar un string

# ✅ Esto funciona, pero es inflexible
datos |> 
  summarize(partido = sum(`MOVIMIENTO CIUDADANO`))
# Funciona, pero ¿qué pasa si el nombre cambia?

El problema es que dplyr necesita trabajar con nombres de columnas (símbolos), no con texto (strings). Aquí es donde entra sym().

¿Qué es sym() y de Dónde Viene?

Origen y Librería

La función sym() pertenece al paquete rlang, el motor que implementa la evaluación tidy (tidy evaluation) en el tidyverse.

# Técnicamente, sym() viene de rlang
library(rlang)

# Pero en la práctica, con dplyr es suficiente
library(dplyr)  # Ya incluye rlang automáticamente

No necesitas cargar rlang explícitamente si ya estás usando dplyr, tidyr, o cualquier paquete del tidyverse que dependa de él.

La Solución: sym() + !! (Bang-Bang Operator)

Sintaxis Básica

party_name <- "MOVIMIENTO CIUDADANO"

partido <- datos |>
  group_by(id_seccion) |>
  summarize(
    partido = sum(!!sym(party_name)),  # ✅ Funciona perfectamente
    nominal = sum(lista_nominal),
    total = sum(total_votos)
  )

¿Qué Está Pasando Aquí?

Desglosemos el proceso paso a paso:

partido = sum(!!sym(party_name))
  1. party_name contiene el string "MOVIMIENTO CIUDADANO"
  2. sym(party_name) convierte ese string en un símbolo (nombre de columna)
  3. !! (bang-bang operator) inyecta ese símbolo en la expresión
  4. Resultado final: Es como si hubieras escrito sum(\MOVIMIENTO CIUDADANO`)`

Ejemplo Interactivo

# Veamos la transformación paso a paso
party_name <- "MOVIMIENTO CIUDADANO"

# Paso 1: party_name es un string
print(party_name)
# [1] "MOVIMIENTO CIUDADANO"

# Paso 2: sym() lo convierte en símbolo
simbolo <- sym(party_name)
print(simbolo)
# MOVIMIENTO CIUDADANO  (sin comillas - es un símbolo)

# Paso 3: !! lo evalúa en el contexto de dplyr
datos |> summarize(total = sum(!!simbolo))

Casos de Uso Comunes

1. Análisis con Múltiples Variables

# Función flexible para analizar cualquier partido
analizar_partido <- function(datos, nombre_partido) {
  datos |>
    group_by(municipio) |>
    summarize(
      votos = sum(!!sym(nombre_partido)),
      porcentaje = votos / sum(total_votos) * 100
    ) |>
    arrange(desc(votos))
}

# Ahora puedes analizar cualquier partido fácilmente
analizar_partido(datos, "MORENA")
analizar_partido(datos, "PAN")
analizar_partido(datos, "MOVIMIENTO CIUDADANO")

2. Automatización de Reportes

partidos <- c("MORENA", "PAN", "PRI", "MOVIMIENTO CIUDADANO")

# Crear resumen para todos los partidos automáticamente
resultados <- map_df(partidos, function(partido) {
  datos |>
    summarize(
      partido = partido,
      total_votos = sum(!!sym(partido)),
      porcentaje = (total_votos / sum(total_votos)) * 100
    )
})

3. Selección Dinámica de Columnas

# Seleccionar columnas basadas en condiciones
columnas_analizar <- c("votos_2021", "votos_2024")

datos |>
  select(municipio, !!!syms(columnas_analizar))  # Nota: syms() para múltiples

Alternativas Modernas: .data[[]]

En versiones recientes de dplyr, existe una alternativa más simple y legible: el operador .data:

party_name <- "MOVIMIENTO CIUDADANO"

# Método tradicional con sym()
datos |>
  summarize(partido = sum(!!sym(party_name)))

# Método moderno con .data
datos |>
  summarize(partido = sum(.data[[party_name]]))  # ✅ Más simple

Comparación de Métodos

Método Sintaxis Ventajas Cuándo Usar
sym() + !! !!sym(var) Tradicional, bien documentado Código legacy, casos complejos
.data[[]] .data[[var]] Más legible, menos símbolos Código nuevo, casos simples
{{ }} {{ var }} Para parámetros de función Dentro de funciones personalizadas

Ejemplo con los Tres Métodos

# 1. Con sym() y !!
mi_funcion_v1 <- function(datos, columna) {
  datos |> summarize(total = sum(!!sym(columna)))
}

# 2. Con .data[[]]
mi_funcion_v2 <- function(datos, columna) {
  datos |> summarize(total = sum(.data[[columna]]))
}

# 3. Con {{ }} (curly-curly/embrace)
mi_funcion_v3 <- function(datos, columna) {
  datos |> summarize(total = sum({{ columna }}))
}

# Los tres funcionan igual
mi_funcion_v1(datos, "MORENA")
mi_funcion_v2(datos, "MORENA")
mi_funcion_v3(datos, MORENA)  # Nota: sin comillas con {{ }}

Errores Comunes y Soluciones

Error 1: Olvidar el !!

# ❌ MAL
datos |> summarize(total = sum(sym(party_name)))
# Error: no puede sumar un símbolo sin evaluar

# ✅ BIEN
datos |> summarize(total = sum(!!sym(party_name)))

Error 2: Usar sym() con Nombres Directos

# ❌ INNECESARIO
datos |> summarize(total = sum(!!sym("MORENA")))

# ✅ SIMPLE
datos |> summarize(total = sum(MORENA))

Regla de oro: Solo usa sym() cuando el nombre de la columna está en una variable.

Error 3: Confundir sym() con syms()

columnas <- c("votos_2021", "votos_2024")

# ❌ MAL - sym() es para UNA columna
datos |> select(!!sym(columnas))

# ✅ BIEN - syms() es para MÚLTIPLES columnas
datos |> select(!!!syms(columnas))  # Nota: tres signos !!!

Cuándo Usar Cada Método

Usa sym() + !! cuando:

  • Trabajas con código legacy
  • Necesitas compatibilidad con versiones antiguas
  • Tienes casos de uso muy complejos

Usa .data[[]] cuando:

  • Escribes código nuevo
  • Quieres máxima legibilidad
  • Trabajas con strings simples

Usa {{ }} cuando:

  • Creas funciones personalizadas
  • Quieres que los usuarios pasen nombres sin comillas
  • Necesitas capturar expresiones completas

Ejemplo Práctico Completo: Análisis Electoral

library(dplyr)

# Datos de ejemplo
datos_electorales <- tibble(
  seccion = 1:100,
  MORENA = sample(100:500, 100),
  PAN = sample(100:500, 100),
  PRI = sample(100:500, 100),
  `MOVIMIENTO CIUDADANO` = sample(50:300, 100)
)

# Función flexible de análisis
analizar_resultados <- function(datos, partido, nivel_agregacion = "total") {
  
  # Método moderno con .data
  resumen <- datos |>
    summarize(
      partido_nombre = partido,
      total_votos = sum(.data[[partido]]),
      promedio_seccion = mean(.data[[partido]]),
      max_seccion = max(.data[[partido]]),
      min_seccion = min(.data[[partido]])
    )
  
  return(resumen)
}

# Usar la función
analizar_resultados(datos_electorales, "MORENA")
analizar_resultados(datos_electorales, "MOVIMIENTO CIUDADANO")

# Análisis comparativo automático
partidos <- c("MORENA", "PAN", "PRI", "MOVIMIENTO CIUDADANO")

comparativo <- map_df(partidos, ~analizar_resultados(datos_electorales, .x))
print(comparativo)

Resumen Ejecutivo

Aspecto Descripción
Librería rlang (incluida automáticamente con dplyr)
Propósito Convertir strings en símbolos para evaluación tidy
Sintaxis clásica !!sym("nombre_columna")
Sintaxis moderna .data[["nombre_columna"]]
Cuándo usar Cuando el nombre de columna está en una variable
Alternativa para funciones {{ variable }} (curly-curly)

Conclusión

La evaluación tidy y funciones como sym() son herramientas poderosas que hacen que tu código R sea más flexible y reutilizable. Aunque puede parecer complejo al principio, dominar estos conceptos te permitirá crear análisis más dinámicos y automatizados.

Consejo final: Empieza con .data[[]] para casos simples y ve incorporando sym() + !! o {{ }} según tus necesidades específicas crezcan.


¿Has usado evaluación tidy en tus proyectos? ¿Cuál método prefieres? Comparte tu experiencia en los comentarios.

Comentarios

Entradas más populares de este blog

¿Cómo comparar nombres de equipos de distintas fuentes?

¿Cómo hacemos filtrado y extracción de listas anidadas utilizando `purrr`?