En esta sección se crea un ensayo clínico simulado, utilizando
clinical_data() del paquete biostats, tal como
se recomienda en su documentación
oficial. Este diseño se asemeja a un estudio con tres brazos
paralelos y visitas repetidas, con pérdidas y datos faltantes, lo cual
refleja escenarios reales descritos en ICH
E9 Statistical Principles for Clinical Trials y Pocock (Clinical Trials:
A Practical Approach).
set.seed(2025) # reproducibilidad
# Ensayo clínico simulado:
# - 300 pacientes, 4 visitas (incluye basal y finales)
# - 3 brazos: Placebo, A, B
# - 10% de abandono (dropout) y 5% de missing en variables numéricas
# para reflejar escenarios de práctica clínica y ensayos reales.
clinical_df <- clinical_data(
n = 300,
visits = 4,
arms = c("Placebo", "A", "B"),
dropout = 0.10,
missing = 0.05
)
# Aseguramos tipos adecuados
clinical_df <- clinical_df %>%
mutate(
treatment = factor(treatment),
visit = factor(visit, ordered = TRUE),
response = factor(response)
)
str(clinical_df)
## 'data.frame': 1200 obs. of 8 variables:
## $ participant_id: chr "001" "001" "001" "001" ...
## $ visit : Ord.factor w/ 4 levels "1"<"2"<"3"<"4": 1 2 3 4 1 2 3 4 1 2 ...
## $ sex : Factor w/ 2 levels "Male","Female": 1 1 1 1 2 2 2 2 2 2 ...
## $ treatment : Factor w/ 3 levels "Placebo","A",..: 1 1 1 1 2 2 2 2 1 1 ...
## $ age : num 50 50 50 50 31 31 31 31 58 58 ...
## $ weight : num 61.9 66.7 63.3 62.1 87 85.3 NA NA 56.8 55.9 ...
## $ biomarker : num 43.5 53.6 52.7 54.9 41.4 ...
## $ response : Factor w/ 3 levels "Complete","Partial",..: NA 3 3 2 3 2 NA NA 3 3 ...
head(clinical_df)
## participant_id visit sex treatment age weight biomarker response
## 1 001 1 Male Placebo 50 61.9 43.48 <NA>
## 2 001 2 Male Placebo 50 66.7 53.59 None
## 3 001 3 Male Placebo 50 63.3 52.69 None
## 4 001 4 Male Placebo 50 62.1 54.92 Partial
## 5 002 1 Female A 31 87.0 41.37 None
## 6 002 2 Female A 31 85.3 51.59 Partial
La exploración inicial de la cohorte mediante una Tabla descriptiva,
es un paso esencial para comprender la estructura del conjunto de datos
y validar que las variables clave presentan comportamientos coherentes
con el diseño del estudio. En este punto, el paquete
{biostats} aporta un flujo estandarizado
que facilita esta evaluación de forma reproducible y consistente.
summary_table() consolida estadísticas descriptivas
relevantes (medias, medianas, IQR, proporciones, rangos) y reporta
automáticamente el número total de observaciones y valores faltantes por
variable. Esta información permite:
Evaluar la completitud de los datos, identificando variables con patrones relevantes de NA.
Describir la distribución interna de la cohorte, incluyendo composición por sexo, distribución de visitas, variabilidad del biomarcador y del peso corporal.
Revisar supuestos básicos (como normalidad), que son determinantes para la selección posterior de métodos paramétricos o no paramétricos.
Verificar consistencia del diseño, asegurando que las frecuencias por visita, tratamiento y respuesta sean plausibles.
Al centralizar esta información en una sola salida,
{biostats} permite una lectura rápida y técnica del
comportamiento basal del dataset, estableciendo una base sólida para los
análisis inferenciales y longitudinales que se desarrollan en etapas
posteriores.
La tabla descriptiva global resume las características estructurales del conjunto de datos antes de cualquier análisis comparativo. Permite identificar la distribución de las variables demográficas y clínicas, evaluar la presencia de valores faltantes y revisar supuestos básicos como normalidad. Esta lectura inicial es clave para determinar qué modelos, pruebas y ajustes serán adecuados en el análisis posterior.
tabla_global <- summary_table(
data = clinical_df,
all = TRUE,
exclude = "participant_id"
)
tabla_global
| variable | n | NAs | summary | normality |
|---|---|---|---|---|
| visit | 1200 | 0 | 1: 300 (25.0%); 2: 300 (25.0%); 3: 300 (25.0%); 4: 300 (25.0%) | NA |
| sex | 1200 | 0 | Male: 620 (51.7%); Female: 580 (48.3%) | NA |
| treatment | 1200 | 0 | Placebo: 368 (30.7%); A: 424 (35.3%); B: 408 (34.0%) | NA |
| age | 1200 | 0 | Mean (SD): 45.37 (14.4); Median (IQR): 44.00 (36.0,54.0); Range: 18.00,85.00 | <0.001 |
| weight | 1200 | 106 | Mean (SD): 70.28 (14.2); Median (IQR): 69.50 (60.5,79.6); Range: 45.00,118.80 | <0.001 |
| biomarker | 1200 | 86 | Mean (SD): 46.93 (10.1); Median (IQR): 47.22 (40.2,53.9); Range: 13.16,80.05 | 0.170 |
| response | 1200 | 167 | Complete: 356 (34.5%); Partial: 208 (20.1%); None: 469 (45.4%) | NA |
El comportamiento observado corresponde a datos
simulados bajo un diseño que replica la estructura típica de un
ensayo clínico, por lo que la homogeneidad entre visitas y tratamientos
es esperada y refleja un proceso de aleatorización controlado. La edad
(age) y el peso (weigth) muestran
distribuciones amplias y no normales (p < 0.001), mientras que el
biomarcador presenta una distribución compatible con normalidad,
coherente con los parámetros utilizados en la simulación.
Los porcentajes de datos faltantes en peso (8.8%) y respuesta (13.9%)
emulan escenarios realistas de práctica clínica y deben considerarse
posteriormente en análisis longitudinales o imputación. En conjunto, la
tabla confirma que el dataset se comporta conforme a los supuestos del
generador de datos de {biostats}, ofreciendo una base
sólida y controlada para demostrar el flujo analítico.
La comparación entre dos grupos mediante summary_table()
evalúa diferencias basales utilizando pruebas paramétricas o no
paramétricas según la distribución observada. {biostats}
selecciona automáticamente el test más adecuado e incorpora tamaños del
efecto, facilitando una interpretación completa y reproducible de la
magnitud y significancia de las diferencias entre grupos.
# Filtrar únicamente los grupos Placebo y A para el análisis comparativo
clinical_PA <- clinical_df %>%
filter(treatment %in% c("Placebo", "A"))
# Generar Tabla 1 comparativa entre Placebo y A:
# - Agrupa por tratamiento
# - Calcula estadísticas descriptivas
# - Selecciona automáticamente las pruebas de comparación
# - Incluye tamaños del efecto
# - Excluye participant_id por no ser informativa
tabla_PA <- summary_table(
data = clinical_PA,
group_by = "treatment",
all = TRUE,
effect_size = TRUE,
exclude = "participant_id"
)
tabla_PA
| variable | n | NAs | Placebo (Group A) | A (Group B) | normality | test | p_value | effect_size | effect_param |
|---|---|---|---|---|---|---|---|---|---|
| visit | A:368, B:424 | A: 0, B: 0 | 1: 92 (25.0%); 2: 92 (25.0%); 3: 92 (25.0%); 4: 92 (25.0%) | 1: 106 (25.0%); 2: 106 (25.0%); 3: 106 (25.0%); 4: 106 (25.0%) | NA | Fisher | 1.000 | NA | Cramer's V |
| sex | A:368, B:424 | A: 0, B: 0 | Male: 168 (45.7%); Female: 200 (54.3%) | Male: 224 (52.8%); Female: 200 (47.2%) | NA | Fisher | 0.054 | NA | Cramer's V |
| age | A:368, B:424 | A: 0, B: 0 | Mean (SD): 44.93 (14.6); Median (IQR): 45.00 (36.0,54.0); Range: 18.00,77.00 | Mean (SD): 46.64 (14.5); Median (IQR): 45.00 (37.0,58.0); Range: 18.00,82.00 | A:<0.001, B:<0.001 | Mann-Whitney U | 0.378 | 0.78 | r |
| weight | A:368, B:424 | A: 25, B: 43 | Mean (SD): 70.17 (15.5); Median (IQR): 69.60 (58.2,80.1); Range: 45.00,113.00 | Mean (SD): 69.91 (13.9); Median (IQR): 68.80 (61.2,78.7); Range: 45.00,118.80 | A:<0.001, B:<0.001 | Mann-Whitney U | 0.867 | 0.77 | r |
| biomarker | A:368, B:424 | A: 22, B: 36 | Mean (SD): 49.65 (10.2); Median (IQR): 50.46 (43.3,56.9); Range: 21.21,80.05 | Mean (SD): 46.92 (9.3); Median (IQR): 47.26 (40.8,52.7); Range: 18.29,72.01 | A:0.262, B:0.438 | Welch's t-test | <0.001 | 0.28 | Cohen's d |
| response | A:368, B:424 | A: 58, B: 49 | Complete: 49 (15.8%); Partial: 84 (27.1%); None: 177 (57.1%) | Complete: 140 (37.3%); Partial: 73 (19.5%); None: 162 (43.2%) | NA | Fisher | <0.001 | NA | Cramer's V |
En este dataset simulado, los grupos Placebo y A
fueron generados sin diferencias sistemáticas en las variables continuas
ni en la estructura demográfica, lo cual se refleja en p-valores no
significativos y tamaños del efecto cercanos a cero. La ligera variación
en la proporción de sexo (p = 0.054) es consistente con la
aleatorización simulada y no representa un desequilibrio relevante. La
variable de respuesta muestra diferencias significativas (p < 0.001),
atribuibles al mecanismo de simulación que asigna categorías de
respuesta condicionadas al biomarcador y no necesariamente a efectos
reales del tratamiento. En síntesis, la tabla confirma que el generador
de datos de {biostats} produce grupos comparables y
adecuados para demostrar análisis comparativos estandarizados, sin
inducir sesgos estructurales no deseados.
La identificación de valores faltantes y atípicos es crítica para evitar sesgos y elegir estrategias de imputación o manejo de datos extremos.
missing_values())La función missing_values() de {biostats}
resume de forma integrada el volumen y la distribución de datos
faltantes, tanto a nivel de filas como de variables, e incorpora un
panel gráfico con proporción de NA por variable y patrón de ausencia.
Esta salida permite dimensionar el impacto potencial del
missingness y decidir si basta con un análisis completo-casos o
si se requieren estrategias de imputación o análisis de
sensibilidad.
mv <- missing_values(
data = clinical_df,
all = TRUE
)
mv
##
## Missing Value Analysis
##
## Complete rows: 975 (81.2%)
## Missing cells: 359 (3.7%)
##
## n_missing pct_missing
## response 167 13.92
## weight 106 8.83
## biomarker 86 7.17
## participant_id 0 0.00
## visit 0 0.00
## sex 0 0.00
## treatment 0 0.00
## age 0 0.00
En este conjunto simulado, el 81.2% de las filas están completas
(975/1200) y solo el 3.7% de las celdas presentan valores faltantes, lo
que indica una pérdida global baja. Sin embargo, la ausencia no es
homogénea: se concentra en response (13.9%),
weight (8.8%) y biomarker (7.2%), mientras que
las variables de identificación, visita, sexo, tratamiento y edad no
tienen NA. El panel de patrones muestra que los faltantes se distribuyen
a lo largo del seguimiento, sin un bloqueo evidente en una visita
específica, lo que sugiere un mecanismo más cercano a MAR que a un fallo
sistemático del diseño. En un análisis aplicado, estas magnitudes
justificarían al menos comparar resultados entre análisis de casos
completos e imputación múltiple para las variables clínicas clave.
outliers())La función outliers() implementa la detección univariada
de valores atípicos mediante el criterio de Tukey (IQR × 1.5),
informando el número de observaciones extremas, los límites inferior y
superior utilizados y los índices implicados. Además, genera un gráfico
combinado de dispersión por índice y boxplot, que facilita juzgar si
esos puntos reflejan errores de registro, variación biológica extrema o
patrones que ameritan modelos más robustos.
out_biomarker <- outliers(
data = clinical_df,
x = "biomarker"
)
out_biomarker
##
## Outlier Analysis
##
## Variable: 'biomarker'
## n: 1114
## Missing: 86 (7.2%)
## Method: Tukey's IQR x 1.5
## Bounds: [19.636, 74.506]
## Outliers detected: 7 (0.6%)
##
## Outlier indices: 35, 102, 327, 420, 425, 445, 730
Para el biomarcador, se analizan 1114 observaciones no faltantes, con 86 NA ya contabilizados previamente. Con los límites [19.64, 74.51] definidos por el método de Tukey, se identifican solo 7 valores fuera de rango (0.6%), lo que indica una distribución muy concentrada y con baja proporción de extremos. Los puntos resaltados en el gráfico se ubican en los bordes de la distribución esperada para este escenario simulado y no configuran un problema de outliers masivo. En un contexto aplicado, estos valores podrían mantenerse en el análisis —eventualmente acompañado de métodos robustos o transformaciones—, documentando su presencia más como variación natural que como indicio de errores sistemáticos en la captura de datos.
normality())La evaluación de normalidad en variables continuas es un paso clave
para decidir entre pruebas paramétricas o no paramétricas. La función
normality() de {biostats} combina pruebas
formales (Kolmogorov–Smirnov, Shapiro–Wilk) con medidas de simetría y
curtosis, además de generar Q–Q plots e histogramas con densidad
ajustada. Esta integración permite juzgar de forma consistente si los
datos se aproximan a una distribución normal en cada grupo.
baseline <- clinical_df %>%
filter(visit == levels(visit)[1])
for (g in levels(baseline$treatment)) {
cat("\n--- Normalidad en:", g, "---\n")
print(
normality(
data = baseline %>% filter(treatment == g),
x = "biomarker"
)
)
}
##
## --- Normalidad en: Placebo ---
##
## Normality Test for 'biomarker'
##
## n = 91
## mean (SD) = 50.36 (10.7)
## median (IQR) = 51.51 (13.8)
##
## Kolmogorov-Smirnov: D = 0.066, p = 0.795
## Shapiro-Wilk: W = 0.982, p = 0.242
## Skewness: -0.36 (z = -1.41)
## Kurtosis: 0.47 (z = 0.95)
##
## Data appears normally distributed.
##
## (Use all = TRUE to see values outside 95%CI [1]).
##
## --- Normalidad en: A ---
##
## Normality Test for 'biomarker'
##
## n = 104
## mean (SD) = 47.72 (9.6)
## median (IQR) = 46.89 (14.2)
##
## Kolmogorov-Smirnov: D = 0.064, p = 0.787
## Shapiro-Wilk: W = 0.987, p = 0.441
## Skewness: 0.26 (z = 1.09)
## Kurtosis: -0.22 (z = -0.47)
##
## Data appears normally distributed.
##
##
## --- Normalidad en: B ---
##
## Normality Test for 'biomarker'
##
## n = 101
## mean (SD) = 44.32 (10.1)
## median (IQR) = 44.60 (15.3)
##
## Kolmogorov-Smirnov: D = 0.068, p = 0.738
## Shapiro-Wilk: W = 0.984, p = 0.284
## Skewness: -0.30 (z = -1.25)
## Kurtosis: -0.07 (z = -0.15)
##
## Data appears normally distributed.
##
En la visita basal, el biomarcador muestra una distribución compatible con normalidad en los tres brazos del estudio (Placebo, A y B). Tanto Shapiro–Wilk como Kolmogorov–Smirnov presentan valores p no significativos, y las métricas de asimetría y curtosis se encuentran dentro de rangos aceptables. Los Q–Q plots muestran alineación con la recta teórica, con pocos puntos fuera del intervalo del 95%.
Dado que los datos son simulados bajo parámetros controlados, este comportamiento homogéneo era esperable y confirma que las diferencias observadas entre grupos no están afectadas por distorsiones severas en la forma de la distribución. Esta evidencia respalda el uso de métodos paramétricos en comparaciones posteriores del biomarcador, siempre que se cumplan los demás supuestos del modelo.
La visualización exploratoria es un componente esencial para obtener
una lectura inmediata del comportamiento de las variables clave y
validar supuestos antes de aplicar modelos inferenciales. El paquete
{biostats} integra funciones estandarizadas para la
generación de gráficos diagnósticos que permiten identificar patrones de
distribución, relaciones entre variables y diferencias entre grupos de
tratamiento. Esto resulta especialmente útil en datasets simulados,
donde los patrones esperados deben corresponder con el diseño del
escenario generador de datos.
El histograma se construye con plot_hist(), una función
de {biostats} que automatiza la inspección de
distribuciones univariadas incorporando, en una sola llamada, una
estética estandarizada, escalas consistentes y densidad ajustada sin
necesidad de código adicional. A diferencia del flujo habitual con
ggplot2, donde cada elemento debe especificarse
manualmente, {biostats} prioriza rapidez, reproducibilidad
y uniformidad visual en análisis clínicos o epidemiológicos.
plot_hist(
data = clinical_df,
x = "biomarker",
title = "Distribución del biomarcador (todas las visitas)"
)
En nuestro resultado, el biomarcador muestra una distribución aparentemente simétrica, centrada entre 45–55 unidades, sin colas pronunciadas ni multimodalidad. Este patrón es coherente con los parámetros de simulación y sugiere que el biomarcador mantiene una variabilidad estable en toda la cohorte. La ausencia de valores extremos relevantes respalda el uso de métodos paramétricos en análisis posteriores, siempre que se confirme esta estabilidad dentro de subgrupos.
El gráfico proviene de plot_bar(), función que
simplifica la generación de barras apiladas y proporcionales integrando
automáticamente cálculo de proporciones, anotaciones y formateo clínico.
A diferencia de construir manualmente capas en ggplot2 o
transformar previamente tablas, {biostats} crea una
representación lista para interpretación, especialmente útil en análisis
por tratamiento o comparaciones descriptivas entre grupos.
plot_bar(
data = clinical_df,
x = "treatment",
group = "response",
position = "fill",
title = "Proporción de categorías de respuesta por tratamiento",
values = TRUE
)
La distribución de respuesta varía entre tratamientos: el grupo B presenta mayor proporción de respuesta completa, mientras que Placebo muestra predominio de ausencia de respuesta. Este patrón es consistente con el comportamiento esperado en escenarios simulados donde los brazos activos suelen mostrar mayor eficacia. Aunque no implica significancia por sí mismo, orienta el análisis comparativo formal y señala dónde evaluar efectos diferenciales de tratamiento.
La visualización se genera con plot_corr(), que combina
en una sola función el cálculo de correlaciones, su significancia y la
representación gráfica en formato estandarizado. Mientras que en otros
paquetes es necesario usar cor(), cor.test() y
luego construir un gráfico manual, {biostats} entrega un
panel completo, ordenado y clínicamente interpretable, ideal para
exploración rápida de dependencias entre variables continuas.
corr_data <- clinical_df %>%
select(age, weight, biomarker)
plot_corr(
data = corr_data,
vars = c("age", "weight", "biomarker"),
type = "lower",
show_sig = TRUE,
sig_only = FALSE,
title = "Matriz de correlación (edad, peso, biomarcador)"
)
Las correlaciones entre edad, peso y biomarcador son bajas, sin asociaciones lineales relevantes. Esto indica independencia práctica entre las variables y sugiere que el biomarcador no depende de características demográficas o antropométricas básicas en este escenario simulado. La ausencia de correlación significativa también reduce preocupación por multicolinealidad en modelos posteriores y confirma la consistencia del proceso de simulación.
omnibus())La función omnibus() de {biostats} ofrece
un enfoque automatizado para comparar grupos cuando el investigador no
quiere decidir manualmente entre ANOVA o Kruskal–Wallis. A diferencia de
otros paquetes que requieren especificar explícitamente el test,
{biostats} evalúa supuestos, selecciona el
método adecuado y entrega una salida estandarizada con estadísticos,
valor p y conclusiones. Esto reduce errores analíticos y mantiene un
flujo coherente incluso en datasets simulados como este.
final <- clinical_df %>%
filter(visit == last(levels(visit)))
omnibus_trt <- omnibus(
data = final,
y = "biomarker",
x = "treatment"
)
omnibus_trt
##
## Omnibus Test: Kruskal-Wallis
##
## Assumption Testing Results:
##
## Normality (Shapiro-Wilk Test):
## Placebo: W = 0.9851, p = 0.465
## A: W = 0.9678, p = 0.024
## B: W = 0.9737, p = 0.065
## Overall result: Non-normal distribution detected.
##
## Homogeneity of Variance (Levene Test):
## F(2,260) = 0.4210, p = 0.657
## Effect size (eta-squared) = 0.0032
## Result: Homogeneous variances.
##
## Test Results:
## Formula: biomarker ~ treatment
## alpha: 0.05
## Result: significant (p = <0.001)
##
## Post-hoc Multiple Comparisons
##
## Pairwise Wilcoxon-tests (alpha: 0.050) (p_method: holm):
## Placebo A
## A 0.245 -
## B 0.001* 0.016*
##
## The study groups show a moderately imbalanced distribution of sample sizes (Δn = 0.199).
El análisis global entre Placebo, A y B en la visita final muestra
diferencias estadísticamente significativas en el biomarcador, con un
p < 0.001 según el test de
Kruskal–Wallis, que {biostats} seleccionó
automáticamente tras identificar no normalidad en al menos un
brazo (Shapiro–Wilk p = 0.024 en tratamiento A). A pesar de
ello, las varianzas resultaron homogéneas, lo que
refuerza la consistencia del contraste.
Los análisis post-hoc (Wilcoxon pareado con corrección Holm) indican que:
Placebo vs A: no presenta diferencias significativas (p = 0.245).
Placebo vs B: diferencia significativa (p = 0.001).
A vs B: diferencia significativa (p = 0.016).
Este patrón muestra que el brazo B concentra los valores más bajos del biomarcador, mientras que Placebo y A mantienen distribuciones más próximas entre sí. La función también reporta una leve desproporción en el tamaño muestral (Δn ≈ 0.20), aunque insuficiente para comprometer el resultado.
Dado que los datos proceden de un generador simulado con diferencias programadas entre brazos, la detección de significancia en los contrastes con B es consistente con el diseño del dataset. No obstante, su interpretación se restringe a fines ilustrativos, pues no representa un efecto real sino un escenario construido para demostración.
La función plot_box() de {biostats}
construye boxplots estandarizados con soporte para agrupamiento, puntos
individuales, líneas de resumen y colores consistentes con los demás
gráficos del paquete. Su ventaja frente a alternativas de
{ggplot2} radica en que no requiere código
adicional para mostrar outliers, medianas o estadísticas clave,
facilitando la visualización comparativa entre tratamientos.
plot_box(
data = final,
x = "treatment",
y = "biomarker",
group = "treatment",
title = "Biomarcador por tratamiento – visita final",
xlab = "Tratamiento",
ylab = "Biomarcador",
points = TRUE
)
El boxplot revela un gradiente descendente claro en los valores del biomarcador desde Placebo hacia el brazo B. Las medianas de cada grupo muestran diferencias visibles: Placebo y A presentan niveles similares, mientras que B exhibe un desplazamiento consistente hacia valores menores, concordante con los resultados post-hoc que identificaron diferencias significativas únicamente en los contrastes que involucran a B.
La dispersión dentro de cada brazo es comparable, sin indicios de heterocedasticidad, lo cual coincide con el test de Levene que confirmó varianzas homogéneas. Los valores atípicos son escasos y no modifican la interpretación global del patrón.
Este comportamiento reproduce fielmente la estructura impuesta en los datos simulados, donde el brazo B fue diseñado con una reducción sistemática del biomarcador. En ese sentido, el gráfico ofrece una representación visual coherente con el hallazgo del análisis no paramétrico, reforzando que la diferencia relevante se concentra en la separación entre B y los otros dos tratamientos.
omnibus() también permite realizar contrastes
longitudinales en diseños con medidas repetidas. {biostats}
se encarga de verificar que exista una observación por sujeto-visita y
selecciona automáticamente entre ANOVA-RM o Friedman según normalidad y
estructura de varianzas. Esto evita la especificación manual de modelos
y facilita un análisis rápido de tendencias temporales.
n_visitas <- clinical_df %>%
filter(!is.na(biomarker)) %>%
pull(visit) %>%
n_distinct()
clinical_long_clean <- clinical_df %>%
filter(!is.na(biomarker)) %>%
group_by(participant_id, visit) %>%
slice(1) %>%
ungroup()
clinical_complete <- clinical_long_clean %>%
group_by(participant_id) %>%
filter(n_distinct(visit) == n_visitas) %>%
ungroup()
omnibus_long <- omnibus(
data = clinical_complete,
y = "biomarker",
x = "visit",
paired_by = "participant_id",
alpha = 0.05
)
omnibus_long
##
## Omnibus Test: Friedman
##
## Assumption Testing Results:
##
## Sphericity (Mauchly Test):
## W = 0.9867, p = 0.653
## Result: Sphericity assumed.
##
## Normality (Shapiro-Wilk Test):
## 1: W = 0.9953, p = 0.644
## 2: W = 0.9975, p = 0.970
## 3: W = 0.9956, p = 0.706
## 4: W = 0.9868, p = 0.021
## Overall result: Non-normal distribution detected.
##
## Homogeneity of Variance (Levene Test):
## F(3,992) = 1.0138, p = 0.386
## Effect size (eta-squared) = 0.0031
## Result: Homogeneous variances.
##
## Test Results:
## Formula: biomarker ~ visit | participant_id
## alpha: 0.05
## Result: not significant (p = 0.774)
## Post-hoc tests not performed (results not significant).
##
## The study groups show a moderately imbalanced distribution of sample sizes (Δn = 0.214).
El análisis longitudinal mediante el test de Friedman no identifica diferencias significativas en la evolución del biomarcador a lo largo de las cuatro visitas (p = 0.774). A pesar de que una de las visitas muestra desviación de normalidad, la función confirma esfericidad adecuada (Mauchly p = 0.653) y varianzas homogéneas, lo cual valida el uso de un enfoque no paramétrico para medidas repetidas.
El resultado no significativo indica que, en este conjunto simulado, el biomarcador no cambia de forma sistemática dentro de los sujetos a través del tiempo, y cualquier variación observada entre visitas es atribuible al azar más que a un efecto temporal consistente.
Este hallazgo contrasta con el patrón esperado en algunos diseños clínicos reales, pero es completamente coherente con el generador de datos utilizado, que en esta simulación particular no introdujo un gradiente temporal definido dentro del mismo individuo. Por ello, no se realizan pruebas post-hoc.
En resumen, el análisis longitudinal confirma estabilidad intra-sujeto del biomarcador a través de las visitas, reflejando la estructura neutral del dataset simulado para este componente.
plot_line() es una función de {biostats}
diseñada para visualización de tendencias, incorporando automáticamente
medias por punto temporal y barras de error (SE o SD). A diferencia de
gráficas manuales en {ggplot2}, este enfoque garantiza
consistencia estética y cálculo correcto de estadísticas de resumen.
plot_line(
data = clinical_df,
x = "visit",
y = "biomarker",
group = "treatment",
stat = "mean",
error = "se",
title = "Evolución promedio del biomarcador por tratamiento",
xlab = "Visita",
ylab = "Biomarcador",
legend_title = "Tratamiento"
)
La evolución promedio del biomarcador muestra fluctuaciones leves entre visitas, pero sin un patrón ascendente o descendente consistente dentro de cada brazo. Placebo, A y B mantienen trayectorias relativamente paralelas, con variaciones pequeñas en torno a sus niveles medios característicos de cada tratamiento.
Este comportamiento concuerda con el resultado del test de Friedman (p = 0.774), que indica ausencia de cambios significativos a lo largo del tiempo dentro de los sujetos. Las diferencias observadas entre puntos corresponden a variabilidad aleatoria, no a un efecto temporal sistemático.
Las barras de error estrechas reflejan una variabilidad intra-visita baja, propia del dataset simulado, pero dicha estabilidad no se traduce en una tendencia longitudinal significativa. En consecuencia, la gráfica confirma visualmente que las diferencias entre tratamientos se mantienen estables en el tiempo, mientras que la evolución temporal del biomarcador es esencialmente plana dentro de cada grupo.
effect_measures())La función effect_measures() de {biostats}
automatiza el cálculo de OR, RR, RD y NNT a partir de una tabla 2×2.
Integra correcciones por celdas vacías, intervalos de confianza y
valores p en una salida única, algo que habitualmente requiere múltiples
funciones dispersas en otros paquetes.
cutoff <- quantile(
final$biomarker[final$treatment == "Placebo"],
probs = 0.25,
na.rm = TRUE
)
final_PA <- final %>%
filter(treatment %in% c("Placebo", "A")) %>%
mutate(
exposed = ifelse(treatment == "A", 1, 0),
event = ifelse(biomarker <= cutoff, 1, 0)
)
tab_2x2 <- final_PA %>%
count(exposed, event) %>%
pivot_wider(
names_from = event,
values_from = n,
values_fill = 0
)
tab_2x2
## # A tibble: 2 × 4
## exposed `0` `1` `NA`
## <dbl> <int> <int> <int>
## 1 0 61 21 10
## 2 1 63 28 15
exposed_event <- tab_2x2$`1`[tab_2x2$exposed == 1]
exposed_no_event <- tab_2x2$`0`[tab_2x2$exposed == 1]
unexposed_event <- tab_2x2$`1`[tab_2x2$exposed == 0]
unexposed_no_event <- tab_2x2$`0`[tab_2x2$exposed == 0]
efectos <- effect_measures(
exposed_event = exposed_event,
exposed_no_event = exposed_no_event,
unexposed_event = unexposed_event,
unexposed_no_event = unexposed_no_event,
alpha = 0.05,
correction = TRUE
)
efectos
##
## Odds/Risk Ratio Analysis
##
## Contingency Table:
## Event No Event Sum
## Exposed 28 63 91
## Unexposed 21 61 82
## Sum 49 124 173
##
## Odds Ratio: 1.291 (95% CI: 0.663 - 2.514)
## Risk Ratio: 1.201 (95% CI: 0.743 - 1.943)
##
## Risk in exposed: 30.8%
## Risk in unexposed: 25.6%
## Absolute risk difference: 5.2%
## Number needed to harm (NNH): 19.4
##
## Note: Correction not applied (no zero values).
El punto de corte basado en el percentil 25 del biomarcador en el brazo Placebo permite definir un desenlace de “buen control” coherente con la estructura del dataset. Al comparar Placebo con el tratamiento A, la tabla 2×2 resultante muestra proporciones similares de eventos entre ambos grupos (30.8% en A vs. 25.6% en Placebo), lo que se refleja en estimadores de efecto cercanos a la unidad.
El Odds Ratio (1.29; IC95%: 0.66–2.51) y el Risk Ratio (1.20; IC95%: 0.74–1.94) indican una tendencia leve hacia mayor probabilidad de buen control en el tratamiento A, aunque con intervalos amplios que cruzan el valor nulo, lo que implica ausencia de significancia estadística. La diferencia absoluta de riesgo (5.2%) es pequeña y el NNH de 19.4 enfatiza que estas diferencias pueden atribuirse al azar dentro del contexto simulado.
Este patrón es congruente con el diseño del dataset, en el cual las
diferencias entre A y Placebo no fueron parametrizadas para ser
marcadamente divergentes en la distribución final del biomarcador. Así,
{biostats} facilita una lectura completa del efecto
mediante una salida integrada que resume riesgo, odds, diferencias
absolutas e intervalos de confianza en un único paso.
Las funciones visuales de {biostats} (como
plot_hist() y plot_box()) permiten extender
las exploraciones hacia modelos estratificados por tratamiento o por
visita sin necesidad de reconfigurar temas, escalas o códigos
adicionales. Esto hace que las visualizaciones mantengan uniformidad y
reduzcan la carga de maquetación, como ya se ha mencionado, a
continuación se exponen ejemplos de otras visualizaciones que se
consideran relevantes.
plot_hist(
data = clinical_df,
x = "biomarker",
facet = "treatment",
title = "Distribución del biomarcador por tratamiento"
)
El histograma estratificado revela tres distribuciones claramente diferenciadas, con desplazamientos en la media que reproducen fielmente los parámetros del generador simulado. Placebo se centra alrededor de valores más altos, seguido por A y luego B, manteniendo varianzas relativamente comparables entre grupos.
Esta separación visual confirma la estructura experimental programada: cada brazo fue diseñado con un nivel basal distinto del biomarcador para ilustrar contrastes clínicos plausibles. La forma ligeramente simétrica de las distribuciones, junto con la ausencia de colas extremas, es coherente con la normalidad observada en los análisis previos.
A diferencia de funciones base de R o de ggplot2 puras,
{biostats} automatiza la estratificación y aplica escalas y
estilos homogéneos entre facetas, permitiendo verificar en segundos si
la simulación o el diseño real siguen un patrón esperado entre
brazos.
plot_box(
data = clinical_df,
x = "visit",
y = "biomarker",
group = "treatment",
title = "Biomarcador por visita y tratamiento",
xlab = "Visita",
ylab = "Biomarcador"
)
Los boxplots estratificados por visita y tratamiento confirman un patrón temporal estable:
Placebo mantiene valores consistentemente superiores.
A ocupa una posición intermedia.
B refleja la reducción más marcada del biomarcador, respetando la jerarquía definida en la simulación.
Aunque los descensos entre visitas no alcanzan significancia en el análisis longitudinal (Friedman p = 0.774), los gráficos muestran fluctuaciones menores compatibles con la variabilidad aleatoria incorporada en el simulador. Se observan pocos atípicos y una dispersión estable entre brazos y momentos, reforzando el control de varianza impuesto en el modelo generador.
Esta visualización permite validar rápidamente si existe coherencia
entre el comportamiento longitudinal observado y los parámetros
simulados. {biostats} aporta un plus al producir una
representación estándar, con medianas, medias y distribución completa
por visita y brazo sin necesidad de codificación manual adicional.
sample_size() y
sample_size_range())Finalmente se ilustran las funciones sample_size() y
sample_size_range() de {biostats} las cuales
encapsulan fórmulas estándar de cálculo de potencia para ensayos
clínicos, eliminando la necesidad de codificar manualmente ecuaciones
para superioridad, no inferioridad o diferencias esperadas. El usuario
define parámetros clínicos clave y la función devuelve n ajustada por
dropout.
ss_sup <- sample_size(
sample = "two-sample",
design = "parallel",
outcome = "mean",
type = "superiority",
alpha = 0.05,
beta = 0.20,
x1 = 75, # tratamiento
x2 = 70, # control
SD = 10,
delta = 3, # Δ < (x1 - x2) = 5
dropout = 0.10,
k = 1
)
ss_sup
##
## Sample Size Calculation
##
## Test type: superiority
## Design: parallel, two-sample
## Outcome: mean
## Alpha (α): 0.050
## Beta (β): 0.200
## Power: 80.0%
##
## Parameters:
## x1 (treatment): 75.000
## x2 (control/reference): 70.000
## Difference (x1 - x2): 5.000
## Standard Deviation (σ): 10.000
## Allocation Ratio (k): 1.00
## Delta (δ): 3.000
## Dropout rate: 10.0%
##
## Required Sample Size
## n1 = 341
## n2 = 341
## Total = 682
##
## Note: Sample size increased by 10.0% to account for potential dropouts.
Ensayo de superioridad. El escenario de superioridad, basado en una diferencia esperada de 5 unidades con desviación estándar de 10, arroja un tamaño por grupo de 341 participantes, para un total de 682, ya ajustados por un 10% de pérdidas.
Este resultado es consistente con situaciones reales donde el efecto esperado es moderado y la variabilidad clínica relevante. La magnitud del tamaño muestral refleja adecuadamente el equilibrio entre potencia (80%) y la necesidad de detectar una diferencia clínicamente significativa.El escenario de superioridad, basado en una diferencia esperada de 5 unidades con desviación estándar de 10, arroja un tamaño por grupo de 341 participantes, para un total de 682, ya ajustados por un 10% de pérdidas.
Este resultado es consistente con situaciones reales donde el efecto esperado es moderado y la variabilidad clínica relevante. La magnitud del tamaño muestral refleja adecuadamente el equilibrio entre potencia (80%) y la necesidad de detectar una diferencia clínicamente significativa.
ss_ni <- sample_size(
sample = "two-sample",
design = "parallel",
outcome = "mean",
type = "non-inferiority",
alpha = 0.025,
beta = 0.20,
x1 = 70,
x2 = 70,
SD = 10,
delta = -5,
dropout = 0.10,
k = 1
)
ss_ni
##
## Sample Size Calculation
##
## Test type: non-inferiority
## Design: parallel, two-sample
## Outcome: mean
## Alpha (α): 0.025
## Beta (β): 0.200
## Power: 80.0%
##
## Parameters:
## x1 (treatment): 70.000
## x2 (control/reference): 70.000
## Difference (x1 - x2): 0.000
## Standard Deviation (σ): 10.000
## Allocation Ratio (k): 1.00
## Delta (δ): -5.000
## Dropout rate: 10.0%
##
## Required Sample Size
## n1 = 70
## n2 = 70
## Total = 140
##
## Note: Sample size increased by 10.0% to account for potential dropouts.
Ensayo de no inferioridad. El contraste de no inferioridad, con delta = –5, muestra un requerimiento notablemente menor: 70 participantes por grupo (total 140).
Este comportamiento es metodológicamente esperado:
Cuando la diferencia verdadera entre grupos es cero (x1 = x2),
Y el margen de no inferioridad es amplio (–5). La potencia requerida se alcanza con tamaños sustancialmente más pequeños.
Este ejemplo evidencia cómo {biostats} facilita la
comparación entre diseños alternativos bajo los mismos supuestos de
variabilidad, ayudando a dimensionar el impacto operativo de la elección
del margen Δ.
ss_range <- sample_size_range(
x1_range = c(65, 75),
x2 = 70,
step = 1,
sample = "two-sample",
design = "parallel",
outcome = "mean",
type = "non-inferiority",
SD = 10,
delta = -5,
dropout = 0.10
)
ss_range
##
## Sample Size Range Analysis
##
## Treatment range (x1): 65.000 to 66.000
## Control/Reference (x2): 70.000
## Step size: 1.000
##
## 70% Power: Total n = 22 to 2074
## 80% Power: Total n = 30 to 2722
## 90% Power: Total n = 40 to 3770
##
## Sample size increased by 10.0% to account for potential dropouts.
Análisis de rango (sensibilidad). El análisis de sensibilidad muestra de forma visual cómo pequeñas variaciones en la media esperada del tratamiento modifican drásticamente el tamaño de muestra necesario.
Resultados clave:
Para medias del tratamiento apenas inferiores al control (65–66), los tamaños totales pueden sobrepasar los 3.000 participantes.
A medida que x1 se aproxima a x2, la curva se estabiliza rápidamente en tamaños reducidos.
Las tres curvas de potencia (70%, 80%, 90%) conservan la misma forma, divergiendo únicamente en el umbral de muestra requerido.
Este comportamiento es plenamente coherente con la teoría:
cuanto menor es el efecto esperado, mayor n se necesita para
detectarlo. La función sample_size_range()
automatiza este análisis, produciendo una visualización clara y
directamente utilizable en informes de diseño o discusiones
metodológicas.
Los tres resultados confirman que {biostats} reproduce
correctamente los cálculos fundamentales para planificación de ensayos
clínicos, permitiendo:
Estimar tamaños por diseño (superioridad vs. no inferioridad).
Ajustar por pérdidas sin cálculos manuales.
Explorar rangos de escenarios de manera visual e interpretable.
Aunque los parámetros provienen de un ejercicio teórico y no de un protocolo real, las funciones operan bajo estándares epidemiológicos y estadísticos formalmente aceptados, ofreciendo una plataforma fiable para docencia, simulación o estructuración preliminar de estudios clínicos.
Este conjunto de procedimientos constituye una ruta
metodológica reproducible para la planificación estadística en
ensayos clínicos, integrando pruebas globales, análisis longitudinales,
medidas de efecto y cálculos de tamaño de muestra en un flujo unificado
mediante {biostats}.
La estandarización de funciones y la capacidad de ajustar supuestos
permiten replicar escenarios reales y adaptar los análisis a diferentes
diseños, niveles de evidencia y requerimientos operativos.
🔗 LinkedIn: https://www.linkedin.com/in/camilogz