age
: Edad del individuo en años.height_cm
: Altura en centímetros.weight_kg
: Peso en kilogramos.heart_rate
: Frecuencia cardíaca en reposo en latidos
por minuto.blood_pressure
: Presión arterial sistólica en
mmHg.sleep_hours
: Promedio de horas de sueño al día.nutrition_quality
: Puntuación de calidad nutricional
diaria entre 0 y 10.activity_index
: Puntuación del nivel de actividad
física entre 1 y 5.smokes
: ¿fuma?gender
: Género del individuo.is_fit
: Variable objetivo (la persona está en forma o
no).Se importan las librerías esenciales para facilitar el análisis que abarca la carga de datos, la evaluación estadística, la visualización, la transformación de datos, la fusión y la unión.
## Cargando paquete requerido: dplyr
## Warning: package 'dplyr' was built under R version 4.4.3
##
## Adjuntando el paquete: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
## Cargando paquete requerido: tibble
## Cargando paquete requerido: stringr
## Warning: package 'stringr' was built under R version 4.4.3
## Cargando paquete requerido: ggplot2
## Warning: package 'ggplot2' was built under R version 4.4.3
## Cargando paquete requerido: ggpubr
## Warning: package 'ggpubr' was built under R version 4.4.3
## Cargando paquete requerido: e1071
## Warning: package 'e1071' was built under R version 4.4.3
## Cargando paquete requerido: psych
## Warning: package 'psych' was built under R version 4.4.3
##
## Adjuntando el paquete: 'psych'
## The following objects are masked from 'package:ggplot2':
##
## %+%, alpha
## Warning: package 'reshape2' was built under R version 4.4.1
## Warning: package 'Hmisc' was built under R version 4.4.3
##
## Adjuntando el paquete: 'Hmisc'
## The following object is masked from 'package:psych':
##
## describe
## The following object is masked from 'package:e1071':
##
## impute
## The following objects are masked from 'package:dplyr':
##
## src, summarize
## The following objects are masked from 'package:base':
##
## format.pval, units
## Warning: package 'Amelia' was built under R version 4.4.3
## Cargando paquete requerido: Rcpp
## ##
## ## Amelia II: Multiple Imputation
## ## (Version 1.8.3, built: 2024-11-07)
## ## Copyright (C) 2005-2025 James Honaker, Gary King and Matthew Blackwell
## ## Refer to http://gking.harvard.edu/amelia/ for more information
## ##
df = read.csv("C:/Users/johan/Downloads/data_eda/fitness_dataset.csv", sep=",", header=TRUE, fileEncoding = "UTF-8")
Se muestran las 5 primeras filas del DataFrame.
## age height_cm weight_kg heart_rate blood_pressure sleep_hours
## 1 56 152 65 69.6 117.0 NA
## 2 69 186 95 60.8 114.8 7.5
## 3 46 192 103 61.4 116.4 NA
## 4 32 189 83 60.2 130.1 7.0
## 5 60 175 99 58.1 115.8 8.0
## 6 25 172 85 81.2 119.2 7.7
## nutrition_quality activity_index smokes gender is_fit
## 1 2.37 3.97 no F 1
## 2 8.77 3.19 0 F 1
## 3 8.20 2.03 0 F 0
## 4 6.18 3.68 0 M 1
## 5 9.95 4.83 yes F 1
## 6 7.35 4.08 yes M 0
Se muestran las 5 últimas filas del DataFrame.
## age height_cm weight_kg heart_rate blood_pressure sleep_hours
## 1995 41 158 113 79.6 104.8 11.5
## 1996 52 173 98 60.7 106.1 NA
## 1997 61 186 74 51.4 123.8 9.4
## 1998 77 198 89 76.7 103.6 8.3
## 1999 62 190 63 80.7 115.9 6.7
## 2000 51 166 78 89.3 101.8 8.3
## nutrition_quality activity_index smokes gender is_fit
## 1995 3.96 1.85 yes M 0
## 1996 1.54 3.25 1 M 1
## 1997 8.63 3.15 no M 1
## 1998 1.98 3.36 yes M 0
## 1999 9.21 2.39 1 F 0
## 2000 4.42 1.02 1 M 0
Se presenta un resumen detallado de todas las variables del dataset, incluyendo el tipo de dato (entero, punto flotante o carácter) lo que permite evaluar la integridad y estructura de los datos para su posterior procesamiento.
## Rows: 2,000
## Columns: 11
## $ age <int> 56, 69, 46, 32, 60, 25, 78, 38, 56, 75, 36, 40, 28, …
## $ height_cm <int> 152, 186, 192, 189, 175, 172, 193, 188, 164, 198, 15…
## $ weight_kg <int> 65, 95, 103, 83, 99, 85, 83, 57, 108, 55, 63, 55, 90…
## $ heart_rate <dbl> 69.6, 60.8, 61.4, 60.2, 58.1, 81.2, 79.6, 81.2, 70.1…
## $ blood_pressure <dbl> 117.0, 114.8, 116.4, 130.1, 115.8, 119.2, 132.5, 110…
## $ sleep_hours <dbl> NA, 7.5, NA, 7.0, 8.0, 7.7, 7.4, 6.6, 9.1, 8.1, 5.9,…
## $ nutrition_quality <dbl> 2.37, 8.77, 8.20, 6.18, 9.95, 7.35, 2.16, 8.47, 4.15…
## $ activity_index <dbl> 3.97, 3.19, 2.03, 3.68, 4.83, 4.08, 3.42, 4.96, 2.06…
## $ smokes <chr> "no", "0", "0", "0", "yes", "yes", "yes", "0", "no",…
## $ gender <chr> "F", "F", "F", "M", "F", "M", "F", "M", "F", "F", "M…
## $ is_fit <int> 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0…
Interpretación del Dataset:
El dataset presentado contiene 2,000 observaciones y 11 variables, que incluyen una combinación de tipos de datos numéricos (enteros y de punto flotante) y de tipo carácter.
Las variables numéricas abarcan características como edad
(age
), altura (height_cm
), peso
(weight_kg
), frecuencia cardíaca (heart_rate
),
presión arterial (blood_pressure
), horas de sueño
(sleep_hours
), calidad de nutrición
(nutrition_quality
) e índice de actividad
(activity_index
), donde algunas presentan valores faltantes
(NA) en sleep_hours
.
Las variables cualitativas sonsmokes
(hábito de
fumar) y gender (género
), mientras que la variable objetivo
is_fit
(indicador binario de condición física) está
codificada como entero.
La estructura sugiere que el dataset está diseñado para analizar
relaciones entre factores de salud y estilo de vida, aunque se observan
inconsistencias en smokes
(valores posiblemente erróneos),
lo que podría requerir limpieza previa al análisis.
Se calcula el número de valores faltantes para cada variable del DataFrame.
## age height_cm weight_kg heart_rate
## 0 0 0 0
## blood_pressure sleep_hours nutrition_quality activity_index
## 0 160 0 0
## smokes gender is_fit
## 0 0 0
Interpretación de los Valores Faltantes:
El análisis de valores faltantes en el dataset revela que la variable
sleep_hours
presenta 160 NA (8% del total de observaciones
de esa columna), lo que indica una proporción significativa de datos
ausentes que podría afectar los análisis relacionados con patrones de
sueño.
El resto de las variables, incluyendo indicadores de salud como
presión arterial (blood_pressure
), calidad nutricional
(nutrition_quality
), y variables demográficas como género
(gender
), no contienen valores faltantes.
Esta estructura sugiere que el dataset está mayormente completo, excepto por la variable de sueño, lo que podría requerir estrategias como imputación o exclusión controlada para evitar sesgos en estudios posteriores.
Como nuestra variable tiene un porcentaje de datos faltantes de 8%, podemos imputar por la media (en caso de que siga una distribución normal) o mediana (si no tiene una distribución normal). Antes de realizar la imputacion vamos a revisar la normalidad de nuestros datos para saber que metodo vamos a utilizar.
##
## Shapiro-Wilk normality test
##
## data: df_antes$sleep_hours
## W = 0.99655, p-value = 0.0003575
Como el p-valor obtenido es 0.0003575<0.05, se rechaza la hipotesis nula de que nuestro conjunto de datos es normal, por lo que debemos realizar la imputación por medio de la mediana.
median_sleep <- median(df$sleep_hours, na.rm = TRUE)
print(paste("La mediana de horas de sueño es:", round(median_sleep, 2)))
## [1] "La mediana de horas de sueño es: 7.5"
na_count_before <- sum(is.na(df_antes$sleep_hours))
df <- df %>%
mutate(sleep_hours = ifelse(is.na(sleep_hours), median_sleep, sleep_hours))
missmap(df, col = c("red", "blue"), legend = TRUE)
Realizaremos un histograma y una prueba de normalidad para ver si luego de hacer la imputación de los datos se mantiene la distribucion.
par(mfrow = c(1, 2))
hist(df_antes$sleep_hours, main = "Antes de imputación",
xlab = "Horas de sueño", col = "orange")
hist(df$sleep_hours, main = "Después de imputación",
xlab = "Horas de sueño", col = "lightgreen")
##
## Shapiro-Wilk normality test
##
## data: df$sleep_hours
## W = 0.99405, p-value = 3.307e-07
Vemos que el p-valor es 3.307e-07<0.05, por lo que usaremos una prueba no parametrica para comprobar si mantiene o no la distribuciónn.
Sleep_hours
- MedianaPara evaluar si la imputación mantuvo la forma original de la distribución de las variables, se emplea la prueba de Kolmogorov–Smirnov, la cual permite comparar estadísticamente dos muestras y determinar si provienen de la misma distribución.
Hipótesis nula: ambas muestras provienen de la misma distribución.
Hipótesis alternativa: las muestras provienen de distribuciones diferentes.
##
## Asymptotic two-sample Kolmogorov-Smirnov test
##
## data: df_antes$sleep_hours and df$sleep_hours
## D = 0.039565, p-value = 0.09953
## alternative hypothesis: two-sided
Como el p-valor es mayor que el nivel de significancia, no hay evidencia significativa para rechazar H_0, por lo que ambas muestras provienen de la misma distribución, esto es, despues de la imputación no se afecto la distribución.
## age height_cm weight_kg heart_rate
## 0 0 0 0
## blood_pressure sleep_hours nutrition_quality activity_index
## 0 0 0 0
## smokes gender is_fit
## 0 0 0
Ahora ejecutamos nuevamente el comando para ver los datos faltantes
por variables y efectivamente, nuestra variable sleep_hours
tiene 0 datos faltantes
Se calcula el número de valores únicos para cada variable del DataFrame.
## age height_cm weight_kg heart_rate
## 62 50 80 484
## blood_pressure sleep_hours nutrition_quality activity_index
## 563 79 866 398
## smokes gender is_fit
## 4 2 2
Interpretación de los Valores únicos:
Variables continuas: heart_rate
(484 únicos),
blood_pressure
(563), nutrition_quality
(866)
y activity_index
(398) presentan alta granularidad, lo que
sugiere mediciones precisas o decimales. sleep_hours
(79
únicos) también refleja variabilidad, aunque con menos detalle.
Variables discretas: age
(62 únicos),
height_cm
(50) y weight_kg
(80) indican rangos
demográficos y físicos esperados en una muestra de 2,000
observaciones.
Variables categóricas: smokes
tiene 4 categorías
(posiblemente incluyendo errores), gender
y
is_fit
son binarias (2 únicos cada una), lo que confirma su
naturaleza dicotómica.
Esta estructura resalta la adecuación del dataset para análisis
estadísticos, aunque es necesario revisar las categorías de
smokes
para corregir inconsistencias. La alta cardinalidad
en variables continuas sugiere riqueza de datos para modelado
predictivo.
La variable smokes
tiene 4 valores únicos, lo cual es
inesperado, ya que se espera que sea una variable binaria que represente
si una persona fuma o no. Entonces, se identifican los valores
inesperados.
## [1] "no" "0" "yes" "1"
Se estandarizan las variables smokes
e
is_fit
para mejorar la legibilidad y la coherencia de los
datos.
La variable is_fit
se encontraba codificada con los
valores 0 y 1, que se reemplazan por “no” y “yes”, respectivamente, para
hacerla más comprensible.
La variable smokes
contenía cuatro valores únicos:
0, 1, “no” y “yes”. Para consolidar esta información y evitar errores en
el análisis, se unifican todos los valores a dos categorías: 0 se
reemplaza por “no” y 1 por “yes”.
df$smokes = str_replace(df$smokes, "0", "no")
df$smokes = str_replace(df$smokes, "1", "yes")
df$is_fit = str_replace(df$is_fit, "0", "no")
df$is_fit = str_replace(df$is_fit, "1", "yes")
Se transforman las variables tipo carácter a factor
.
Aplicaremos la técnica de capping basada en percentiles para atenuar el efecto de valores atípicos extremos, reemplazándolos por los límites de los cuartiles y preservando así la estructura general de los datos sin eliminarlos.
capping_iqr <- function(x) {
Q1 <- quantile(x, 0.25, na.rm = TRUE)
Q3 <- quantile(x, 0.75, na.rm = TRUE)
IQR_val <- Q3 - Q1
lower <- Q1 - 1.5 * IQR_val
upper <- Q3 + 1.5 * IQR_val
x[x < lower] <- lower
x[x > upper] <- upper
return(x)
}
variables_predictores <- setdiff(names(df), "is_fit")
df_melted <- melt(df[, variables_predictores])
## Using smokes, gender as id variables
ggplot(df_melted, aes(x = "", y = value)) +
geom_boxplot(fill = "#FBD000", color = "red") +
theme_minimal() +
labs(title = "Boxplot inicial", x = "", y = "Valor") +
facet_wrap(~variable, scales = "free_y")
variables_numericas <- variables_predictores[sapply(df[variables_predictores], is.numeric)]
for (col in variables_numericas) {
df[[col]] <- capping_iqr(df[[col]])
}
df_melted_final <- melt(df[, variables_predictores])
## Using smokes, gender as id variables
ggplot(df_melted_final, aes(x = "", y = value)) +
geom_boxplot(fill = "#FBD000", color = "red") +
theme_minimal() +
labs(title = "Boxplot después de capping por IQR", x = "", y = "Valor") +
facet_wrap(~variable, scales = "free_y")
Tras aplicar el capping, los boxplots muestran una distribución más uniforme y sin valores atípicos extremos, lo que facilita un análisis más estable y representativo de las variables.
El Análisis Univariado es la primera fase del análisis exploratorio de datos. Se enfoca en el estudio individual de cada variable para entender su distribución, características y valores atípicos. Esto permite identificar patrones y la calidad de los datos antes de un análisis más complejo.
Se genera un doble gráfico para cada variable binaria, permitiendo comparar su distribución mediante:
color = (c("orange", "lightblue"))
fig1 = ggplot(data=df, aes(x=smokes)) +
geom_bar(fill = color) +
labs(title = "Frecuencia de smokes", y = "Frecuencia", x = "Categorias") +
theme_bw() +
theme(plot.title = element_text(hjust = 0.5)) +
geom_text(aes(label = ..count..), stat = "count", vjust = 2, colour = "black")
fig2 = ggplot(data=df, aes(x=gender)) +
geom_bar(fill = color) +
labs(title = "Frecuencia de gender", y = "Frecuencia", x = "Categorias") +
theme_bw() +
theme(plot.title = element_text(hjust = 0.5)) +
geom_text(aes(label = ..count..), stat = "count", vjust = 2, colour = "black")
fig3 = ggplot(data=df, aes(x=is_fit)) +
geom_bar(fill = color) +
labs(title = "Frecuencia de is_fit", y = "Frecuencia", x = "Categorias") +
theme_bw() +
theme(plot.title = element_text(hjust = 0.5)) +
geom_text(aes(label = ..count..), stat = "count", vjust = 2, colour = "black")
ggarrange(fig1,fig2, fig3, ncol=3 ,nrow=1)
## Warning: The dot-dot notation (`..count..`) was deprecated in ggplot2 3.4.0.
## ℹ Please use `after_stat(count)` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
fig1 = ggplot(data = df, aes(x = "", fill = smokes)) +
geom_bar(width = 1, color = "white") +
coord_polar(theta = "y") +
labs(title = "Distribucion de smokes", x = NULL, y = NULL) + theme_bw() +
theme(plot.title = element_text(hjust = 0.5)) +
geom_text(aes(label = paste0(round(..count.. / sum(..count..) * 100, 1), "%)")),stat = "count", position = position_stack(vjust = 0.5), color = "black") +
scale_fill_manual(values = color)
fig2 = ggplot(data = df, aes(x = "", fill = gender)) +
geom_bar(width = 1, color = "white") +
coord_polar(theta = "y") +
labs(title = "Distribucion de gender", x = NULL, y = NULL) + theme_bw() +
theme(plot.title = element_text(hjust = 0.5)) +
geom_text(aes(label = paste0(round(..count.. / sum(..count..) * 100, 1), "%)")),stat = "count", position = position_stack(vjust = 0.5), color = "black") +
scale_fill_manual(values = color)
fig3 = ggplot(data = df, aes(x = "", fill = is_fit)) +
geom_bar(width = 1, color = "white") +
coord_polar(theta = "y") +
labs(title = "Distribucion de is_fit", x = NULL, y = NULL) + theme_bw() +
theme(plot.title = element_text(hjust = 0.5)) +
geom_text(aes(label = paste0(round(..count.. / sum(..count..) * 100, 1), "%)")),stat = "count", position = position_stack(vjust = 0.5), color = "black") +
scale_fill_manual(values = color)
ggarrange(fig1, fig2, fig3, ncol=3, nrow=1)
Interpretación de las Variables Binarias:
En cuanto al hábito de fumar, se observa que el 54.9% de las personas no fuma, frente al 45.1% que sí lo hace, con una diferencia moderada entre ambos grupos (1,099 vs. 901 individuos).
En la variable género, la distribución es bastante equilibrada, con un 51.5% de mujeres y un 48.5% de hombres (1,030 vs. 970 personas).
Por último, en cuanto a la condición física, un 60.1% de los participantes no se considera en forma, mientras que el 40% sí lo está (1,201 vs. 799 personas), evidenciando una mayor diferencia entre categorías respecto a las otras variables.
En conjunto, estos resultados permiten apreciar que las proporciones
se mantienen cercanas al equilibrio en smokes
y
gender
, pero en is_fit
predomina claramente la
categoría “no”.
Se visualiza algunos estadísticos descriptivos para las variables númericas discretas.
## age height_cm weight_kg
## Min. :18.00 Min. :150.0 Min. : 30.00
## 1st Qu.:34.00 1st Qu.:162.0 1st Qu.: 64.00
## Median :49.00 Median :174.0 Median : 83.00
## Mean :49.11 Mean :174.5 Mean : 82.91
## 3rd Qu.:65.00 3rd Qu.:187.0 3rd Qu.:102.00
## Max. :79.00 Max. :199.0 Max. :159.00
Interpretación de las Variables Númericas Discretas:
En cuanto a age
, la edad de los participantes oscila
entre 18 y 79 años, con la mayoría concentrada entre 34 y 65
años.
La estatura (height_cm
) varía entre 150 y 199 cm,
siendo más común observar valores en el rango de 162 a 187 cm.
Finalmente, el peso (weight_kg
) presenta un rango
amplio de 30 a 159 kg, con mayor concentración entre 64 y 102
kg.
Estos resultados ofrecen una visión general del rango y concentración de cada variable.
Se genera un histograma para visualizar la distribución de frecuencias y la dispersión de los datos. Además se evalua:
Asimetría:
skew = 0
: Distribución simétrica (valores aceptables
skew \(\in (-1,1)\)).skew > 0
: Mayor peso en la cola izquierda de la
distribución (sesgo positivo).skew < 0
: Mayor peso en la cola derecha de la
distribución (sesgo negativo).Kurtosis: Determina si una distribución tiene colas gruesas con respecto a la distribución normal. Proporciona información sobre la forma de una distribución de frecuencias.
kurtosis = 3
: se denomina mesocúrtica (distribución
normal).kurtosis < 3
: se denomina platicúrtica (distribución
con colas menos gruesas que la normal).kurtosis > 3
: se denomina leptocúrtica (distribución
con colas más gruesas que la normal) y significa que trata de producir
más valores atípicos que la distribución normal.for (i in 1:3) {
nombre_col = names(df)[i]
a = kurtosis(df[[nombre_col]])
b = skew(df[[nombre_col]])
print(paste("Kurtosis:", round(a, 2)))
print(paste("Skew:", round(b, 2)))
fig1 = ggplot(data = df, aes(x = .data[[nombre_col]])) +
geom_bar(fill = "lightblue", color = "white") +
labs(title = paste("Frecuencias de", nombre_col), x = "Categorias", y = "Frecuencias")+ theme_bw() + theme(plot.title = element_text(hjust = 0.5))
print(fig1)
}
## [1] "Kurtosis: -1.17"
## [1] "Skew: -0.04"
## [1] "Kurtosis: -1.19"
## [1] "Skew: 0.01"
## [1] "Kurtosis: -0.26"
## [1] "Skew: 0.22"
Interpretación de las Variables Númericas Discretas:
El análisis de las distribuciones para age
,
height_cm
y weight_kg
muestra comportamientos
diferenciados.
El gráfico muestra que la distribución de la edad es plana y casi simétrica. La curtosis de -1.17 indica que la distribución es platicúrtica (más aplanada que una distribución normal), sin un pico claro. La asimetría (skew) de -0.04, que está muy cerca de cero, confirma que no hay una inclinación significativa hacia edades más jóvenes o mayores. En general, las edades están distribuidas de manera uniforme, sin una concentración notable en ningún rango.
El gráfico de frecuencias de altura (height_cm) muestra una distribución plana y simétrica. La curtosis de -1.19 indica que es platicúrtica (más aplanada que una distribución normal), sin un pico claro. La asimetría (skew) de 0.01, muy cercana a cero, confirma que la distribución es simétrica, sin inclinación hacia valores más altos o más bajos. En conclusión, no hay una altura dominante en los datos.
El gráfico de peso (weight_kg) presenta una distribución platicúrtica con una ligera asimetría positiva. La curtosis de -0.26 indica que la distribución es más plana que una curva normal. La asimetría (skew) de 0.22, un valor positivo, muestra una ligera inclinación hacia valores más altos, impulsada por un valor atípico (outlier) alrededor de los 160 kg. En general, el peso está distribuido de manera uniforme, con la excepción de este dato extremo.
Estos resultados sugieren que, mientras la edad y la estatura se distribuyen de forma más equilibrada, el peso muestra asimetría y alta concentración en torno a ciertos valores, con casos extremos que podrían influir en análisis posteriores.
Se visualiza algunos estadísticos descriptivos para las variables númericas continuas.
## heart_rate blood_pressure sleep_hours nutrition_quality
## Min. : 45.00 Min. : 90.0 Min. : 4.00 Min. : 0.000
## 1st Qu.: 62.10 1st Qu.:109.7 1st Qu.: 6.60 1st Qu.: 2.547
## Median : 70.25 Median :120.0 Median : 7.50 Median : 5.065
## Mean : 70.27 Mean :119.9 Mean : 7.51 Mean : 5.035
## 3rd Qu.: 78.42 3rd Qu.:129.8 3rd Qu.: 8.40 3rd Qu.: 7.470
## Max. :102.91 Max. :159.9 Max. :11.10 Max. :10.000
## activity_index
## Min. :1.000
## 1st Qu.:2.038
## Median :2.980
## Mean :2.999
## 3rd Qu.:3.950
## Max. :4.990
Interpretación de las Variables Númericas Continuas:
Estos resultados ofrecen una visión general del rango y concentración de cada variable.
Se genera un histograma para visualizar la distribución de frecuencias y la dispersión de los datos. Además se evalua:
Asimetría:
skew = 0
: Distribución simétrica (valores aceptables
skew \(\in (-1,1)\)).skew > 0
: Mayor peso en la cola izquierda de la
distribución (sesgo positivo).skew < 0
: Mayor peso en la cola derecha de la
distribución (sesgo negativo).Kurtosis: Determina si una distribución tiene colas gruesas con respecto a la distribución normal. Proporciona información sobre la forma de una distribución de frecuencias.
kurtosis = 3
: se denomina mesocúrtica (distribución
normal).kurtosis < 3
: se denomina platicúrtica (distribución
con colas menos gruesas que la normal).kurtosis > 3
: se denomina leptocúrtica (distribución
con colas más gruesas que la normal) y significa que trata de producir
más valores atípicos que la distribución normal.for (i in 4:8) {
nombre_col = names(df)[i]
a = kurtosis(df[[nombre_col]], na.rm = TRUE)
b <- skew(df[[nombre_col]], na.rm = TRUE)
print(paste("Kurtosis:", round(a, 2)))
print(paste("Skew:", round(b, 2)))
fig1 = ggplot(data = df, aes(x = .data[[nombre_col]])) + geom_histogram(fill = "lightblue", bins = 30, color = "white", na.rm = TRUE) + labs(title = paste("Histograma de", nombre_col), x = "Categorias", y = "Frecuencia") + theme_bw() + theme(plot.title = element_text(hjust = 0.5))
print(fig1)
}
## [1] "Kurtosis: -0.26"
## [1] "Skew: 0.11"
## [1] "Kurtosis: -0.34"
## [1] "Skew: 0.08"
## [1] "Kurtosis: -0.06"
## [1] "Skew: -0.05"
## [1] "Kurtosis: -1.2"
## [1] "Skew: 0.01"
## [1] "Kurtosis: -1.16"
## [1] "Skew: 0.04"
Interpretación de las Variables Númericas Continuas:
Los histogramas y las medidas de forma (kurtosis y asimetría) indican que las variables analizadas presentan distribuciones cercanas a la normalidad, aunque con algunas particularidades.
El histograma del ritmo cardíaco (heart_rate) muestra una distribución platicúrtica con una ligera asimetría positiva. El valor de la curtosis de -0.26, que es negativo, indica una distribución más aplanada que una curva normal, lo que significa que los datos no están tan concentrados en un único pico. Esto se puede apreciar visualmente en la forma ancha y redondeada de la distribución. Por otro lado, la asimetría (skew) de 0.11 es un valor pequeño y positivo, lo que sugiere un sesgo muy leve hacia la derecha. Aunque la mayoría de los datos se agrupan alrededor del centro, la cola de la distribución se extiende ligeramente hacia valores más altos (ritmos cardíacos más rápidos). En resumen, los ritmos cardíacos están distribuidos de manera bastante uniforme, con una ligera tendencia hacia valores más altos.
El histograma de presión sanguínea (blood_pressure) muestra una distribución platicúrtica con una asimetría positiva muy leve. La curtosis de -0.34, siendo un valor negativo, indica que la distribución es más plana que una curva normal, lo cual se observa en el gráfico por la falta de un pico pronunciado. Por su parte, la asimetría (skew) de 0.08, un valor cercano a cero, sugiere una distribución casi simétrica con un sesgo muy sutil hacia valores más altos. En esencia, la presión sanguínea está distribuida de manera bastante uniforme en el rango central, sin una concentración excesiva, y presenta una cola muy ligera que se extiende hacia valores más altos.
El histograma de horas de sueño (sleep_hours) presenta una distribución mesocúrtica y simétrica. A diferencia de los gráficos anteriores, la curtosis de -0.06 está muy cerca de cero, lo que indica que la distribución es similar a una curva normal en términos de su pico y colas, aunque ligeramente más aplanada. La asimetría (skew) de -0.05 también está muy cerca de cero, confirmando que la distribución es casi perfectamente simétrica. Visualmente, se observa un pico claro en el centro (alrededor de 7-8 horas de sueño) y una caída gradual y equilibrada hacia ambos extremos. Esto sugiere que la mayoría de las personas duermen una cantidad de horas estándar, con una cantidad similar de individuos que duermen menos y más de lo recomendado.
El histograma del índice de actividad (activity_index) muestra una distribución platicúrtica y simétrica. La curtosis de -1.16, que es un valor negativo, indica que la distribución es más plana que una curva normal, lo que significa que los datos están dispersos en un rango amplio en lugar de concentrarse en un pico. El valor de la asimetría (skew) de 0.04 está muy cerca de cero, lo que confirma que la distribución es casi perfectamente simétrica, sin un sesgo notable hacia valores más bajos o más altos. Visualmente, el gráfico es bastante plano y uniforme, lo que sugiere que el índice de actividad está distribuido de manera homogénea.
El histograma de calidad de la nutrición (nutrition_quality) muestra una distribución platicúrtica y simétrica. La curtosis de -1.2, al ser un valor negativo, indica que la distribución es más plana que una curva normal, lo que significa que los datos no se concentran en un único punto. La asimetría (skew) de 0.01 está muy cerca de cero, lo que confirma que la distribución es prácticamente simétrica, sin un sesgo notable hacia valores bajos o altos. Visualmente, el gráfico es bastante uniforme, sin picos pronunciados. Esto sugiere que la calidad de la nutrición en la población estudiada está distribuida de manera homogénea en un amplio rango de valores.
Se genera un un boxplot para visualizar la distribución e identificar outliers (puntos fuera de los bigotes) y la dispersión de los datos. Además se evalua:
for (i in 4:8) {
nombre_col = names(df)[i]
media = mean(df[[nombre_col]], na.rm = TRUE)
desviacion = sd(df[[nombre_col]], na.rm = TRUE)
coef_variacion = (desviacion / media) * 100
print(paste("El coeficiente de variacion es: ", round(coef_variacion, 2), "%"))
fig2 = ggplot(data = df, aes(x = .data[[nombre_col]])) +
geom_boxplot(fill = "orange", color = "black", na.rm = TRUE) + labs(title = paste("Dispersion de", nombre_col), x = "Categorias") + theme_bw() + theme(plot.title = element_text(hjust = 0.5))
print(fig2)
}
## [1] "El coeficiente de variacion es: 16.79 %"
## [1] "El coeficiente de variacion es: 12.13 %"
## [1] "El coeficiente de variacion es: 19.09 %"
## [1] "El coeficiente de variacion es: 56.88 %"
## [1] "El coeficiente de variacion es: 37.89 %"
Interpretación de las Variables Númericas Continuas:
El coeficiente de variación del ritmo cardíaco es de 16.79%, lo que indica una baja dispersión de los datos. Esto significa que la mayoría de los valores de ritmo cardíaco están muy cerca de la media. El diagrama de caja y bigotes corrobora esto, mostrando una caja estrecha y la ausencia de valores atípicos, lo que confirma que los datos son muy consistentes.
El coeficiente de variación para la presión sanguínea es de 12.13%, lo que indica una baja dispersión y una alta consistencia en los datos. El diagrama de caja y bigotes corrobora esto, mostrando que la mitad de los datos se agrupan en un rango estrecho y que no hay valores atípicos, lo que refuerza la idea de que la presión sanguínea es bastante uniforme en esta población.
El coeficiente de variación para las horas de sueño es de 19.09%, lo que indica una baja dispersión en los datos. El diagrama de caja confirma esta consistencia, mostrando una caja relativamente estrecha y la ausencia de valores atípicos, lo que sugiere que la mayoría de los individuos duermen una cantidad de horas similar, con poca variación.
El coeficiente de variación para la calidad de la nutrición es de 56.88%. Este valor, al ser superior a 50%, indica una dispersión moderada-alta en los datos. A diferencia de los gráficos anteriores, la caja del diagrama de caja es más ancha, lo que visualmente confirma que el 50% de los datos centrales tienen un rango más amplio. Esto sugiere que hay una mayor variabilidad en la calidad de la nutrición entre los individuos, con valores más dispersos alrededor de la media y la mediana.
El coeficiente de variación del índice de actividad es de 37.89%. Este valor indica una dispersión moderada de los datos, ya que es significativamente mayor que los de las variables fisiológicas (ritmo cardíaco, presión sanguínea, horas de sueño), pero menor que el de la calidad de la nutrición. El diagrama de caja y bigotes corrobora esto, mostrando una caja más ancha que las primeras variables, lo que sugiere que hay una mayor variabilidad en los niveles de actividad entre los individuos. Sin embargo, no se aprecian valores atípicos que distorsionen la distribución.
El análisis bivariado es la segunda fase del análisis exploratorio de datos. Se enfoca en las relaciones entre dos variables para obtener datos estadísticos sobre sus influencias mutuas.
Se generan graficos de barras para analizar la relación entre el conjunto de variables binarias y la variable estado de forma con el fin de identificar qué variables binarias están asociadas en mayores proporciones.
for (i in 9:10) {
nombre_col <- names(df)[i]
df_summary <- df %>%
group_by(.data[[nombre_col]]) %>%
summarise(
pct_yes = mean(is_fit == "yes") * 100,
.groups = "drop"
)
fig <- ggplot(df_summary, aes(x = .data[[nombre_col]], y = pct_yes, fill = .data[[nombre_col]])) +
geom_bar(stat = "identity", width = 0.8, color = "black") +
scale_fill_manual(values = c("orange", "lightblue")) +
labs(
title = paste(nombre_col, "vs is_fit"), x = "Categorias",
y = "Porcentaje en buen estado de forma (%)",
fill = nombre_col
) +
theme_bw() +
theme(plot.title = element_text(hjust = 0.5)) +
geom_text(
aes(label = sprintf("%.1f%%", pct_yes)),
vjust = 2,
color = "black"
) +
scale_y_continuous(limits = c(0, 100))
print(fig)
}
Interpretación:
Estos gráficos de barras comparan el porcentaje de personas en buen
estado de forma (is_fit
) con dos variables categóricas:
género (gender
) y si fuman (smokes
).
El gráfico de barras muestra la relación entre fumar y estar en buena forma física. La barra naranja, que representa a los no fumadores (“no”), indica que el 53.3% de este grupo está en buena forma física. En contraste, la barra azul, que representa a los fumadores (“yes”), muestra que solo el 23.6% de ellos está en buena forma física. Esto sugiere que hay una relación negativa entre fumar y el buen estado de forma, ya que el porcentaje de personas en buena forma física es significativamente más alto entre los no fumadores que entre los fumadores.
El gráfico de barras muestra una clara relación entre fumar y el estado de forma física. El 53.3% de las personas que no fuman están en buen estado de forma, mientras que solo el 23.6% de las personas que sí fuman lo están. Esto indica que hay una asociación negativa significativa: la probabilidad de estar en buen estado físico es más del doble entre los no fumadores que entre los fumadores.
Se generan graficos de barras para analizar la relación entre variables númericas discretas y el estado de forma en la población estudiada. Con el fin de identificar qué variables presentan mayores proporciones.
for (i in 1:3) {
nombre_col <- names(df)[i]
min_val <- floor(min(df[[nombre_col]]))
max_val <- ceiling(max(df[[nombre_col]]))
breaks <- seq(min_val, max_val, by = 5)
if (max_val > tail(breaks, 1)) {
breaks <- c(breaks, tail(breaks, 1) + 5)
}
df_summary <- df %>%
mutate(
grupo = cut(
.data[[nombre_col]],
breaks = breaks,
include.lowest = TRUE,
right = FALSE,
labels = paste0(breaks[-length(breaks)], "-", breaks[-1])
)
) %>%
group_by(grupo) %>%
summarise(
pct_yes = mean(is_fit == "yes") * 100,
.groups = "drop"
) %>%
mutate(
centro = sapply(strsplit(as.character(grupo), "-"), function(x) mean(as.numeric(x)))
)
fig <- ggplot(df_summary, aes(x = grupo, y = pct_yes)) +
geom_col(fill = "lightblue", color = "white", width = 0.7) +
geom_line(aes(group = 1), color = "orange", linewidth = 1) +
labs(
title = paste(nombre_col,"vs is_fit"),
x = "Rangos de 5 unidades",
y = "Porcentaje en buen estado de forma(%)"
) +
theme_bw() +
theme(
plot.title = element_text(hjust = 0.5),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
scale_y_continuous(limits = c(0, 100))
print(fig)
}
Interpretación:
El gráfico revela una relación inversa entre la edad y el estado de forma. El porcentaje de personas en buen estado físico es más alto en los rangos de edad jóvenes (cerca del 60% entre 18-23 años) y disminuye progresivamente a medida que la edad aumenta (cayendo a menos del 25% entre 78-83 años).
La línea de tendencia naranja se mantiene relativamente plana y no muestra una disminución o aumento constante. El porcentaje de personas en buen estado de forma fluctúa entre un 30% y 45% en casi todos los rangos de altura. Esto sugiere que no existe una relación significativa entre la altura y la probabilidad de estar en buen estado de forma física.
El gráfico de peso vs. estado de forma muestra una relación curvilínea. La probabilidad de estar en forma es baja en los pesos extremos (muy bajos o muy altos). El porcentaje de personas en buen estado de forma aumenta hasta alcanzar un pico entre los 60 y 95 kg, donde se encuentra cerca del 50%, y luego disminuye a medida que el peso se incrementa.
Se generan histogramas para analizar la relación entre variables númericas continuas y el estado de forma en la población estudiada. Con el fin de identificar qué variables presentan mayores proporciones.
for (i in 4:8) {
nombre_col <- names(df)[i]
df_filtered <- df %>% filter(!is.na(.data[[nombre_col]]))
fig <- ggplot(df_filtered, aes(x = .data[[nombre_col]], fill = is_fit)) +
geom_histogram(aes(y = after_stat(count)/sum(after_stat(count))*100),
position = "identity", bins = 30, color = "white") +
labs(title = paste(nombre_col, "vs is_fit"),
x = "Categorias", y = "Porcentaje (%)") +
theme_bw() +
theme(plot.title = element_text(hjust = 0.5))
print(fig)
}
Interpretación:
El gráfico muestra que existe una relación inversa entre el ritmo cardíaco y el estado de forma. Las personas en buen estado físico (“yes”) se concentran en los rangos de ritmo cardíaco más bajos (por debajo de 80), mientras que a medida que el ritmo cardíaco aumenta, el porcentaje de personas en buen estado de forma disminuye considerablemente.
El gráfico muestra que existe una relación inversa entre la presión sanguínea y el estado de forma física. Las personas en buen estado físico (“yes”) se concentran en los rangos de presión sanguínea más bajos y normales. A medida que la presión sanguínea aumenta, el porcentaje de personas en buen estado de forma disminuye notablemente, siendo más predominantes las personas “no” en forma.
El gráfico muestra que existe una relación positiva entre las horas de sueño y el estado de forma física. El mayor porcentaje de personas en buen estado físico (“yes”) se concentra en quienes duermen entre 7 y 8 horas. Los porcentajes de personas en buen estado físico disminuyen drásticamente en los extremos, es decir, en quienes duermen muy poco o demasiado.
El gráfico muestra que existe una relación positiva entre la calidad de la nutrición y el estado de forma física. Las personas con baja calidad nutricional están predominantemente “no” en forma, mientras que las que tienen alta calidad nutricional están en su mayoría “yes” en forma. Esto indica que una mejor nutrición está directamente asociada a un mejor estado físico.
El gráfico muestra una fuerte relación positiva entre el índice de actividad y el estado de forma física. A medida que el índice de actividad aumenta, el porcentaje de personas en buen estado de forma también lo hace. Las personas con un índice de actividad bajo están predominantemente “no” en forma, mientras que aquellas con un índice alto están en su mayoría “yes” en forma.
El análisis multivariado nos permite examinar simultáneamente las relaciones entre múltiples variables, revelando patrones complejos que no son evidentes en los análisis univariados o bivariados.
Se genera un heatmap de correlación de Pearson el cual nos permitira visualizar relaciones de dos o mas variables de manera compacta, destacando asociaciones lineales fuertes (positivas o negativas) entre las variables numéricas del dataset, a demas, nos permitira detectar posibles multicolinealidades y o entender la estructura de los datos.
df_cor <- df %>%
select(age, height_cm, weight_kg, heart_rate,
blood_pressure, sleep_hours,
nutrition_quality, activity_index, is_fit) %>%
mutate(is_fit = ifelse(is_fit == "yes", 1, 0))
cor_matrix <- cor(df_cor, use = "complete.obs")
ggplot(melt(cor_matrix), aes(Var1, Var2, fill = value)) +
geom_tile(color = "white", linewidth = 0.7) +
geom_text(aes(label = round(value, 2)),
color = "black", size = 3.5) +
scale_fill_gradient2(low = "blue", high = "red", mid = "white",
midpoint = 0, limit = c(-1, 1)) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
panel.grid = element_blank()) +
labs(title = "Matriz de Correlación",
x = "",
y = "",
fill = "Correlación") +
coord_fixed()
Interpretación
La relación más fuerte con el estado de forma física (is_fit) se da con las siguientes variables:
Las demás variables (altura, peso, ritmo cardíaco, horas de sueño y presión sanguínea) muestran correlaciones muy débiles con el estado de forma física, con valores cercanos a cero. Esto indica que no hay una relación lineal significativa entre estas variables y si una persona está en buena forma física o no.
A continuación se presenta un resumen de los hallazgos principales del análisis estadístico y de gráficos:
Análisis de Distribución y Dispersión:
Relación con el Estado de Forma Física (is_fit):
En resumen, los factores más importantes para un buen estado de forma física son un alto índice de actividad, una buena nutrición, un peso en el rango óptimo y la ausencia de tabaquismo.