Estadística

Paquetes R para inferencia, descriptiva y visualización estadística

r
statistics
inference
effect-size
clinical
visualization
tidyverse
Referencia comentada de paquetes R orientados a estadística aplicada: descriptiva clínica, tamaños del efecto, inferencia basada en simulación y visualización con resultados estadísticos integrados.

Sobre estadística en R

R nació como lenguaje estadístico antes que como lenguaje de programación de uso general, y eso se nota: el grueso de la estadística clásica está disponible en la base del lenguaje. stats::lm, stats::glm, stats::t.test, stats::aov, stats::cor.test, stats::wilcox.test cubren la mayor parte de lo que se necesita en un proyecto medio. Antes de añadir paquetes, conviene tener interiorizada esa API base: la mayoría de wrappers de “estadística tidy” devuelven al final un objeto que sigue heredando de lm o htest.

Sobre esa base se han construido tres capas que esta página cataloga:

  • Capa descriptiva. Resúmenes de cohorte, “tabla uno” en publicaciones clínicas, summary statistics por grupo. Aquí entra tableone (clínico) y, fuera de esta página, alternativas como gtsummary, arsenal::tableby o skimr.
  • Capa de interpretación. Tamaños del efecto y sus intervalos de confianza, la métrica que realmente comunica magnitud, frente a los p-values que solo comunican evidencia contra el nulo. effectsize y el resto del ecosistema easystats (parameters, performance, report) viven aquí.
  • Capa pedagógica e inferencial moderna. Inferencia basada en simulación/permutación con sintaxis tidy: infer. Más cercana al razonamiento estadístico que a un wrapper de tests.
  • Capa de comunicación visual. Gráficos con resultados estadísticos integrados, sin pegar p-values a mano: ggstatsplot.

Decisión recurrente al elegir herramienta: ¿necesito un modelo (stats::lm y derivados, lme4, nlme, brms), un resumen de cohorte (tableone, gtsummary), una métrica de impacto (effectsize), o un test puntual con sintaxis tidy (infer, rstatix)? Cada paquete de esta página resuelve uno de esos huecos. Rara vez se sustituyen entre sí.

El orden refleja jerarquía conceptual: primero descriptiva (tableone), después interpretación de modelos (effectsize), luego marco inferencial completo (infer), y al final el envoltorio de visualización (ggstatsplot).


tableone

tableone resuelve un problema muy concreto y muy frecuente en investigación clínica y epidemiológica: producir la Tabla 1 de un manuscrito, el resumen de características de la cohorte estratificado por grupo, con tests bivariantes opcionales. Su autor, Kazuki Yoshida, lo escribió pensando en publicaciones médicas, y esa orientación se nota en cada decisión por defecto (variables categóricas con conteos y porcentajes, continuas con media ± SD o mediana [IQR], tests apropiados según distribución).

No es una librería de modelado. Es una librería de presentación de descriptiva. Su salida está pensada para ir al portapapeles o a un Word/LaTeX casi sin retoques.

Cuándo usarlo

  • Estudios clínicos, epidemiológicos o de cohorte que requieran una Table 1 estratificada por grupo (tratamiento vs control, expuesto vs no expuesto).
  • Cuando el destino final es un manuscrito en formato académico clásico y quieres una salida limpia con SMD (standardized mean difference), útil en propensity score analyses.

Cuándo NO usarlo

  • Reporting moderno con flexibilidad de formato. gtsummary está mejor mantenido, tiene mejor integración con gt/flextable y es más extensible. Para proyectos nuevos suele ser la elección por defecto.
  • Exploración interactiva. Para inspección rápida de un dataset, skimr::skim() o summarytools::dfSummary() son más ágiles.
  • Tablas de regresión (coeficientes, OR, HR). Usa gtsummary::tbl_regression, broom::tidy + gt, o modelsummary.

Conceptos clave

  • CreateTableOne(vars, strata, data, factorVars, ...) es la función principal. strata define las columnas (grupos a comparar). vars, las filas.
  • print() sobre el objeto controla la presentación: nonnormal = c(...) fuerza mediana [IQR] en variables sesgadas. exact = c(...) fuerza test exacto de Fisher en categóricas con celdas pequeñas. smd = TRUE añade la columna de SMD.
  • Detección automática de tests: chi-cuadrado para categóricas, ANOVA/Kruskal-Wallis para continuas. Si quieres control, especifícalo explícitamente, los defaults pueden no ajustarse a tu protocolo estadístico.
  • tableone::ShowRegTable() formatea tablas de regresión a partir de coxph, glm, etc.
  • Exportación: print(tab, quote = TRUE, noSpaces = TRUE) produce algo pegable directamente en Excel. Para Word/PDF, conviene pasar por kableExtra o flextable.

Patrón mínimo

library(tableone)

vars         <- c("age", "sex", "bmi", "smoker", "hba1c", "creatinine")
factor_vars  <- c("sex", "smoker")
nonnormal    <- c("hba1c", "creatinine")   # mostrar como mediana [IQR]

tab <- CreateTableOne(
  vars       = vars,
  strata     = "treatment",                # grupo a comparar
  data       = cohort,
  factorVars = factor_vars,
  test       = TRUE
)

# Impresión con ajustes para el manuscrito
print(
  tab,
  nonnormal = nonnormal,
  smd       = TRUE,
  quote     = FALSE,
  noSpaces  = TRUE
)

Trampas habituales

  • Variables categóricas no declaradas. Si una variable categórica está codificada como entero (p. ej. sex = 0/1), tableone la tratará como continua y reportará media ± SD. Declara siempre factorVars o convierte a factor antes.
  • Test por defecto vs protocolo. Los defaults aplican chi-cuadrado y ANOVA. Si tu plan estadístico fija Fisher y Kruskal-Wallis, pásalos explícitamente vía exact y nonnormal. No confíes en los defaults para un análisis preregistrado.
  • SMD con múltiples grupos. Con strata de más de dos niveles, la SMD se calcula como una versión multivariada (Yang & Dalton). Revisa el output, suele confundirse con la SMD pareada clásica.

Enlaces

Relacionados en esta página

  • effectsize, para complementar la Tabla 1 con tamaños del efecto y no solo p-values.

effectsize

effectsize es la pieza del ecosistema easystats dedicada a calcular tamaños del efecto y sus intervalos de confianza, de forma coherente, con la misma API independientemente de si parten de un test, un modelo lineal, un GLM o un modelo mixto. Cierra una brecha histórica de R base: los p-values están en todas partes, los tamaños del efecto no.

Su valor real es la coherencia: cohens_d() sobre dos vectores, eta_squared() sobre un lm con varias covariables, standardize_parameters() sobre cualquier modelo de la familia parameters, todo devuelve objetos con CI, interpretación textual opcional y métodos print/plot consistentes.

Cuándo usarlo

  • Reportar magnitud del efecto junto al p-value (recomendación estándar de APA, CONSORT y la mayoría de revistas serias hoy).
  • Convertir entre métricas de efecto (d ↔︎ r ↔︎ OR) sin tener que recordar fórmulas.
  • Estandarizar coeficientes de un modelo de regresión sin reescalar manualmente las variables.
  • Interpretación cualitativa con interpret_cohens_d(), interpret_r(), etc., útil cuando escribes un report automático, aunque las reglas (Cohen, Sawilowsky, Funder & Ozer) son convenciones, no leyes.

Cuándo NO usarlo

  • Si solo necesitas el coeficiente bruto del modelo, broom::tidy() es más ligero y suficiente. effectsize aporta cuando quieres versión estandarizada, eta², omega², o medidas específicas de ANOVA.
  • Para tamaños del efecto bayesianos (bayes_R2, posterior de d), usa directamente brms::bayes_R2() o bayestestR, effectsize los expone, pero la API nativa es más expresiva.

Conceptos clave

  • Familia continua-continua: cohens_d(), hedges_g(), glass_delta() para comparaciones de dos grupos.
  • Familia ANOVA: eta_squared(), omega_squared(), epsilon_squared() sobre un aov o lm. Por defecto devuelven η² parcial. partial = FALSE para el clásico.
  • Familia categórica: cramers_v(), phi() para tablas de contingencia (a partir de chisq.test o table).
  • Estandarización de modelos: standardize_parameters() reescala coeficientes a unidades de desviación típica. Distintos métodos ("refit", "posthoc", "smart"), "refit" es el más correcto estadísticamente pero el más caro.
  • Reglas de interpretación. interpret_* aceptan distintas convenciones (rules = "cohen1988", "sawilowsky2009", "funder2019"). No las uses como verdad absoluta. Explicita la regla en el manuscrito.

Patrón mínimo

library(effectsize)

# 1) Dos grupos: Cohen's d con CI
cohens_d(mpg ~ am, data = mtcars)

# 2) ANOVA: eta² parcial sobre lm/aov
mod <- lm(mpg ~ cyl + hp + wt, data = mtcars)
eta_squared(mod, partial = TRUE)

# 3) Estandarizar coeficientes de un GLM
glm_mod <- glm(am ~ mpg + wt, data = mtcars, family = binomial())
standardize_parameters(glm_mod, method = "refit")

# 4) Conversión entre métricas
d_to_r(0.5)        # Cohen's d a correlación r
oddsratio_to_d(2)  # OR a Cohen's d (asume distribución logística)

Trampas habituales

  • η² parcial vs η² clásico. El default de eta_squared() es partial = TRUE. En diseños con varias covariables, el η² parcial puede sumar mucho más de 1, eso no es un error, es lo esperado, pero confunde a quien viene de SPSS o de literatura clásica.
  • standardize_parameters no es “estandarizar las variables y refitar”. Por defecto usa una aproximación posthoc (más rápida pero con supuestos). Si quieres el refit correcto, especifica method = "refit".
  • Conversiones entre métricas asumen distribuciones. d_to_r, oddsratio_to_d, etc., aplican fórmulas con supuestos (homocedasticidad, distribución logística…). En datos reales con asimetría fuerte, son aproximaciones, no equivalencias.

Enlaces

Relacionados en esta página

  • tableone, descriptiva por grupo. effectsize complementa con magnitud del efecto.
  • ggstatsplot, internamente usa effectsize para anotar los gráficos.

infer

infer es la implementación tidy del flujo de inferencia basado en simulación: permutaciones, bootstrap e intervalos de confianza expresados como una pipeline specify → hypothesize → generate → calculate. Pertenece al proyecto tidymodels y refleja una decisión pedagógica deliberada, separar los pasos conceptuales de la inferencia frecuentista en verbos explícitos.

Su origen es académico (Allen Downey, Andrew Bray, Chester Ismay) y se nota: el paquete está pensado tanto para enseñar como para producir. En producción su valor está en workflows donde necesitas tests no paramétricos sobre estadísticos no convencionales, o donde quieres trazabilidad explícita de qué se está aleatorizando.

Cuándo usarlo

  • Tests de permutación y bootstrap con sintaxis legible, especialmente útil cuando el estadístico no tiene distribución cerrada o cuando los supuestos paramétricos son cuestionables.
  • Enseñanza de inferencia frecuentista, el código se lee como el razonamiento estadístico.
  • Cuando quieres que cada paso (specify, hypothesize, generate, calculate) sea inspeccionable y reutilizable en un pipeline reproducible.

Cuándo NO usarlo

  • Tests clásicos puntuales. Para un t.test, un chisq.test o una correlación, stats:: de base es más directo. infer añade verbosidad sin valor cuando el test es estándar.
  • Modelado. infer no ajusta GLMs ni modelos mixtos. Para regresión seria usa stats::lm/glm, lme4, glmmTMB, brms, etc., y para tidy de resultados, broom.
  • Bayesiano. Si el enfoque conceptual del análisis es bayesiano, salta directo a brms, rstanarm o bayestestR. infer es estrictamente frecuentista.
  • Datasets grandes con muchas permutaciones. generate(reps = 10000) puede ser caro. En ese régimen, considera tests analíticos o usar coin (que es más rápido para permutación pura sin pipeline tidy).

Conceptos clave

  • specify(response ~ explanatory) define el problema. Es declarativo: solo dice qué variables.
  • hypothesize(null = "independence" | "point") fija la hipótesis nula. "independence" para tests de asociación. "point" para tests sobre un valor concreto (media, proporción).
  • generate(reps, type = "permute" | "bootstrap" | "draw") produce las réplicas simuladas. La elección del type debe ser coherente con la nula: permutación para independencia, bootstrap para CIs.
  • calculate(stat) colapsa cada réplica a un estadístico ("mean", "diff in means", "prop", "diff in props", "Chisq", "F", "t", "correlation", …).
  • get_p_value(obs_stat, direction) y get_confidence_interval() cierran el flujo. El argumento direction = "two-sided" / "left" / "right" es explícito, infer no lo adivina.
  • Modo teórico. assume(distribution = "t" | "F" | ...) permite recuperar el test paramétrico clásico con la misma sintaxis, útil para comparar simulación vs analítico.

Patrón mínimo

library(infer)
library(dplyr)

# Diferencia de medias por grupo, vía permutación
obs_diff <- gss |>
  specify(hours ~ college) |>
  calculate(stat = "diff in means", order = c("degree", "no degree"))

null_dist <- gss |>
  specify(hours ~ college) |>
  hypothesize(null = "independence") |>
  generate(reps = 1000, type = "permute") |>
  calculate(stat = "diff in means", order = c("degree", "no degree"))

null_dist |> get_p_value(obs_stat = obs_diff, direction = "two-sided")

# Intervalo de confianza bootstrap para la misma diferencia
boot_dist <- gss |>
  specify(hours ~ college) |>
  generate(reps = 1000, type = "bootstrap") |>
  calculate(stat = "diff in means", order = c("degree", "no degree"))

boot_dist |> get_confidence_interval(level = 0.95, type = "percentile")

Trampas habituales

  • order = c("nivel_a", "nivel_b") define el signo de la diferencia (a − b). Omitirlo da una advertencia y un signo arbitrario. Ser explícito ahorra confusión al interpretar.
  • hypothesize vs sin hypothesize. Para CIs por bootstrap no se llama a hypothesize (no hay nula que imponer). Para p-values por permutación, sí. Mezclar los flujos produce resultados sin sentido, el paquete no siempre te avisa.
  • reps pequeño. Con reps = 100 el p-value es ruidoso. Con reps = 1000 ya estabiliza para mayoría de casos. Para tests con p-values muy pequeños, hace falta reps mucho mayor (la resolución mínima es 1/reps).

Enlaces

Relacionados en esta página

  • effectsize, complemento natural: infer da el p-value/CI, effectsize da la magnitud.

ggstatsplot

ggstatsplot es un envoltorio de visualización sobre ggplot2 que produce gráficos con los resultados estadísticos anotados directamente, p-values, tamaños del efecto, intervalos de confianza, factores Bayes, sin tener que llamar a los tests por separado y pegarlos a mano con annotate(). Internamente combina effectsize, parameters, BayesFactor y otros paquetes de easystats.

Su autor, Indrajeet Patil, lo concibió como herramienta de comunicación estadística: cuando se reporta una comparación, el lector debería ver simultáneamente la distribución, el efecto, su magnitud y la evidencia contra el nulo. Es una opinión fuerte sobre cómo se debe presentar análisis exploratorio.

Cuándo usarlo

  • Exploración rápida de comparaciones donde quieres ver distribución + test + tamaño del efecto en una sola figura.
  • Reports internos, slide decks, exploración con stakeholders no estadísticos donde la anotación reduce ambigüedad.
  • Comparaciones entre múltiples grupos con tests pairwise corregidos (pairwise.comparisons = TRUE).

Cuándo NO usarlo

  • Figuras de manuscrito final. Las anotaciones por defecto son densas y opinadas, para una figura limpia que respete el estilo de una revista, suele ser preferible construirla a mano con ggplot2 + ggpubr::stat_pvalue_manual() o anotación manual.
  • Cuando ya tienes el modelo ajustado. Si has corrido un lm/lmer/glm y quieres visualizar efectos, ve directo a ggeffects, marginaleffects, o sjPlot. ggstatsplot re-ajusta internamente.
  • Performance crítico. Cada gráfico dispara múltiples ajustes (frequentista + bayesiano + tamaño del efecto). En datasets grandes o loops, conviene desactivar lo que no uses (bf.message = FALSE) o usar las funciones de bajo nivel.

Conceptos clave

  • API por tipo de plot: ggbetweenstats (grupos independientes), ggwithinstats (medidas repetidas), ggscatterstats (correlación), ggcorrmat (matriz de correlaciones), ggpiestats / ggbarstats (categóricas), gghistostats (univariado).
  • Argumento type controla el enfoque: "parametric", "nonparametric", "robust", "bayes". Cambiar type cambia el test, el tamaño del efecto y la métrica de evidencia mostrados.
  • pairwise.comparisons = TRUE añade contrastes pairwise con corrección (p.adjust.method = "holm" por defecto). Útil con >2 grupos, pero satura el gráfico rápidamente.
  • Bayes Factor automático. El mensaje BF₁₀ aparece por defecto y mide evidencia a favor de la alternativa. Apagarlo (bf.message = FALSE) limpia la figura.
  • grouped_* variantes generan paneles facetados sobre una variable adicional, conservando los tests por panel.

Patrón mínimo

library(ggstatsplot)

# Comparación entre grupos independientes
ggbetweenstats(
  data = iris,
  x    = Species,
  y    = Sepal.Length,
  type = "parametric",          # o "nonparametric", "robust", "bayes"
  pairwise.comparisons = TRUE,
  p.adjust.method      = "holm",
  bf.message           = FALSE  # quita el mensaje de BF si no lo necesitas
)

# Correlación con anotación completa
ggscatterstats(
  data = mtcars,
  x    = wt,
  y    = mpg,
  type = "nonparametric"        # Spearman
)

Trampas habituales

  • Cada gg*stats re-ajusta el modelo. En un loop con muchas comparaciones, el coste se acumula. Si ya tienes el ajuste, no uses ggstatsplot, anota a mano.
  • Tipografía y leyendas densas. La anotación por defecto incluye estadístico, p-value, tamaño del efecto, IC y BF en una sola línea. En PDF para imprenta queda apretada. Ajusta ggstatsplot.layer.position y reduce bf.message/results.subtitle según necesites.
  • Defaults frecuentista + bayesiano simultáneo. El gráfico muestra ambos enfoques por defecto. Si tu informe es estrictamente frecuentista o estrictamente bayesiano, configúralo: mezclar paradigmas sin explicar confunde al lector.

Enlaces

Relacionados en esta página

  • effectsize, motor de cálculo de tamaños del efecto que ggstatsplot usa internamente.
  • infer, alternativa cuando quieres el flujo inferencial explícito, no anotación visual automática.