Este documento desarrolla un diseño factorial de dos factores aplicado al dataset Sleep, Screen Time and Stress Analysis de Kaggle. La idea central consiste en estudiar cómo dos factores explicativos influyen sobre una variable respuesta y si existe o no interacción entre ellos.
En este caso, el análisis se propone así:
Desde el punto de vista metodológico, este análisis reproduce la estructura de un diseño factorial de dos factores. No obstante, debe aclararse que la base de Kaggle corresponde a datos observacionales y no a un experimento aleatorizado controlado. Por tanto, el procedimiento es muy útil para enseñanza, interpretación y práctica del ANOVA factorial, pero la inferencia causal debe formularse con cautela.
Sea un diseño con:
Entonces, el número total de observaciones es:
\[ N = abr \]
En un diseño factorial de dos factores se estudian tres componentes fundamentales:
La interacción es especialmente importante porque permite establecer si el efecto de un factor cambia según el nivel del otro.
El modelo lineal del diseño factorial de dos factores es:
\[ Y_{ijk} = \mu + \alpha_i + \beta_j + (\alpha\beta)_{ij} + \varepsilon_{ijk} \]
donde:
Se supone que:
\[ \varepsilon_{ijk} \sim N(0,\sigma^2) \]
e independientes.
\[ H_0: \alpha_1 = \alpha_2 = \cdots = \alpha_a = 0 \]
\[ H_1: \text{al menos uno de los efectos } \alpha_i \text{ es diferente de cero} \]
\[ H_0: \beta_1 = \beta_2 = \cdots = \beta_b = 0 \]
\[ H_1: \text{al menos uno de los efectos } \beta_j \text{ es diferente de cero} \]
\[ H_0: (\alpha\beta)_{ij} = 0 \quad \text{para todo } i,j \]
\[ H_1: \text{existe al menos una interacción distinta de cero} \]
La media de la celda \((i,j)\) es:
\[ \bar{Y}_{ij.} = \frac{1}{r} \sum_{k=1}^{r} Y_{ijk} \]
La media marginal del factor A es:
\[ \bar{Y}_{i..} = \frac{1}{br} \sum_{j=1}^{b}\sum_{k=1}^{r} Y_{ijk} \]
La media marginal del factor B es:
\[ \bar{Y}_{.j.} = \frac{1}{ar} \sum_{i=1}^{a}\sum_{k=1}^{r} Y_{ijk} \]
La media general es:
\[ \bar{Y}_{...} = \frac{1}{N}\sum_{i=1}^{a}\sum_{j=1}^{b}\sum_{k=1}^{r} Y_{ijk} \]
La suma de cuadrados total es:
\[ SC_T = \sum_{i=1}^{a}\sum_{j=1}^{b}\sum_{k=1}^{r} (Y_{ijk} - \bar{Y}_{...})^2 \]
La suma de cuadrados del factor A es:
\[ SC_A = br\sum_{i=1}^{a}(\bar{Y}_{i..} - \bar{Y}_{...})^2 \]
La suma de cuadrados del factor B es:
\[ SC_B = ar\sum_{j=1}^{b}(\bar{Y}_{.j.} - \bar{Y}_{...})^2 \]
La suma de cuadrados de la interacción es:
\[ SC_{AB} = r\sum_{i=1}^{a}\sum_{j=1}^{b} (\bar{Y}_{ij.} - \bar{Y}_{i..} - \bar{Y}_{.j.} + \bar{Y}_{...})^2 \]
La suma de cuadrados del error es:
\[ SC_E = SC_T - SC_A - SC_B - SC_{AB} \]
En un diseño balanceado:
\[ CM_A = \frac{SC_A}{a-1} \]
\[ CM_B = \frac{SC_B}{b-1} \]
\[ CM_{AB} = \frac{SC_{AB}}{(a-1)(b-1)} \]
\[ CM_E = \frac{SC_E}{ab(r-1)} \]
Los estadísticos de prueba son:
\[ F_A = \frac{CM_A}{CM_E}, \qquad F_B = \frac{CM_B}{CM_E}, \qquad F_{AB} = \frac{CM_{AB}}{CM_E} \]
El dataset utilizado corresponde a:
Sleep, Screen Time and Stress Analysis en Kaggle. La página del dataset indica que incluye variables relacionadas con screen time, sleep duration y stress level. citeturn690952search0turn614312search0
Descargue el archivo CSV desde Kaggle y guárdelo en la misma carpeta
donde se encuentra este archivo .Rmd.
Nota importante: como los nombres exactos de columnas pueden variar ligeramente entre versiones del CSV, este documento incorpora una detección flexible basada en palabras clave.
library(readr)
# Cambie el nombre del archivo si su CSV tiene otro nombre
datos <- read_csv("sleep_mobile_stress_dataset_15000.csv")
datos<-as.data.frame(unclass(datos), stringsAsFactors=TRUE)
head(datos)
## user_id age gender occupation daily_screen_time_hours
## 1 1 56 Female Designer 3.26
## 2 2 46 Female Teacher 1.85
## 3 3 32 Female Designer 3.04
## 4 4 25 Male Software Engineer 9.00
## 5 5 38 Female Teacher 3.52
## 6 6 56 Male Teacher 7.26
## phone_usage_before_sleep_minutes sleep_duration_hours sleep_quality_score
## 1 86 5.31 7.72
## 2 32 7.36 9.70
## 3 107 4.50 6.38
## 4 36 6.68 5.53
## 5 56 7.57 6.69
## 6 10 7.79 6.16
## stress_level caffeine_intake_cups physical_activity_minutes
## 1 3.49 0 35
## 2 3.01 0 16
## 3 5.03 0 17
## 4 10.00 0 3
## 5 6.71 4 92
## 6 10.00 2 50
## notifications_received_per_day mental_fatigue_score
## 1 119 3.57
## 2 299 1.91
## 3 21 6.05
## 4 220 9.92
## 5 167 5.99
## 6 198 7.85
names(datos)
## [1] "user_id" "age"
## [3] "gender" "occupation"
## [5] "daily_screen_time_hours" "phone_usage_before_sleep_minutes"
## [7] "sleep_duration_hours" "sleep_quality_score"
## [9] "stress_level" "caffeine_intake_cups"
## [11] "physical_activity_minutes" "notifications_received_per_day"
## [13] "mental_fatigue_score"
buscar_columna <- function(nombres, patrones) {
idx <- unique(unlist(lapply(patrones, function(p) grep(p, nombres, ignore.case = TRUE))))
if (length(idx) == 0) return(NA_character_)
nombres[idx[1]]
}
col_sleep <- buscar_columna(names(datos), c("sleep", "duration"))
col_screen <- buscar_columna(names(datos), c("screen", "time"))
col_stress <- buscar_columna(names(datos), c("stress"))
data.frame(
variable_analitica = c("sueño", "pantalla", "estrés"),
columna_detectada = c(col_sleep, col_screen, col_stress)
)
## variable_analitica columna_detectada
## 1 sueño phone_usage_before_sleep_minutes
## 2 pantalla daily_screen_time_hours
## 3 estrés stress_level
if (any(is.na(c(col_sleep, col_screen, col_stress)))) {
stop("No fue posible identificar automáticamente las columnas de sueño, pantalla y estrés. Revise los nombres del CSV y ajuste manualmente la sección de detección.")
}
datos2 <- datos[, c(col_sleep, col_screen, col_stress)]
names(datos2) <- c("Sleep_Duration", "Screen_Time", "Stress_Level")
datos2 <- na.omit(datos2)
datos2$Sleep_Duration <- as.numeric(datos2$Sleep_Duration)
datos2$Screen_Time <- as.numeric(datos2$Screen_Time)
datos2$Stress_Level <- as.numeric(datos2$Stress_Level)
summary(datos2)
## Sleep_Duration Screen_Time Stress_Level
## Min. : 0.00 Min. : 1.000 Min. : 1.00
## 1st Qu.: 29.00 1st Qu.: 3.260 1st Qu.: 4.75
## Median : 60.00 Median : 5.490 Median : 7.38
## Mean : 59.71 Mean : 5.502 Mean : 6.98
## 3rd Qu.: 90.00 3rd Qu.: 7.760 3rd Qu.:10.00
## Max. :119.00 Max. :10.000 Max. :10.00
Para adaptar el dataset al diseño factorial de dos factores se discretizan las variables continuas de sueño y tiempo de pantalla en tres niveles:
datos2$Sleep_cat <- ifelse(datos2$Sleep_Duration <= 5, "Bajo",
ifelse(datos2$Sleep_Duration <= 7, "Medio", "Alto"))
datos2$Sleep_cat <- factor(datos2$Sleep_cat, levels = c("Bajo", "Medio", "Alto"))
table(datos2$Sleep_cat)
##
## Bajo Medio Alto
## 736 247 14017
datos2$Screen_cat <- ifelse(datos2$Screen_Time <= 3, "Bajo",
ifelse(datos2$Screen_Time <= 6, "Medio", "Alto"))
datos2$Screen_cat <- factor(datos2$Screen_cat, levels = c("Bajo", "Medio", "Alto"))
table(datos2$Screen_cat)
##
## Bajo Medio Alto
## 3348 5037 6615
tabla_diseno <- table(datos2$Sleep_cat, datos2$Screen_cat)
tabla_diseno
##
## Bajo Medio Alto
## Bajo 165 269 302
## Medio 56 75 116
## Alto 3127 4693 6197
freq <- as.vector(tabla_diseno)
c(minimo = min(freq), maximo = max(freq), iguales = length(unique(freq)) == 1)
## minimo maximo iguales
## 56 6197 0
medias_celda <- aggregate(Stress_Level ~ Sleep_cat + Screen_cat, data = datos2, mean)
medias_celda
## Sleep_cat Screen_cat Stress_Level
## 1 Bajo Bajo 2.808364
## 2 Medio Bajo 2.654286
## 3 Alto Bajo 3.460915
## 4 Bajo Medio 5.505316
## 5 Medio Medio 5.624267
## 6 Alto Medio 6.305821
## 7 Bajo Alto 8.794139
## 8 Medio Alto 9.113103
## 9 Alto Alto 9.369127
medias_A <- aggregate(Stress_Level ~ Sleep_cat, data = datos2, mean)
medias_A
## Sleep_cat Stress_Level
## 1 Bajo 6.250190
## 2 Medio 6.589393
## 3 Alto 7.025468
medias_B <- aggregate(Stress_Level ~ Screen_cat, data = datos2, mean)
medias_B
## Screen_cat Stress_Level
## 1 Bajo 3.415263
## 2 Medio 6.252922
## 3 Alto 9.338387
media_general <- mean(datos2$Stress_Level)
media_general
## [1] 6.980247
N <- nrow(datos2)
N
## [1] 15000
SC_total <- sum((datos2$Stress_Level - mean(datos2$Stress_Level))^2)
SC_total
## [1] 113379
modelo <- aov(Stress_Level ~ Sleep_cat * Screen_cat, data = datos2)
summary(modelo)
## Df Sum Sq Mean Sq F value Pr(>F)
## Sleep_cat 2 459 229 110.955 <2e-16 ***
## Screen_cat 2 81914 40957 19815.321 <2e-16 ***
## Sleep_cat:Screen_cat 4 21 5 2.533 0.0383 *
## Residuals 14991 30985 2
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
anova(modelo)
## Analysis of Variance Table
##
## Response: Stress_Level
## Df Sum Sq Mean Sq F value Pr(>F)
## Sleep_cat 2 459 229 110.9547 < 2e-16 ***
## Screen_cat 2 81914 40957 19815.3208 < 2e-16 ***
## Sleep_cat:Screen_cat 4 21 5 2.5325 0.03834 *
## Residuals 14991 30985 2
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Sleep_cat presenta un p-valor pequeño, la categoría
de sueño influye en el nivel de estrés.Screen_cat presenta un p-valor pequeño, la categoría
de tiempo de pantalla influye en el nivel de estrés.Sleep_cat:Screen_cat presenta un p-valor pequeño, el
efecto del sueño depende del tiempo de pantalla.library(ggplot2)
ggplot(datos2, aes(x = Stress_Level)) +
geom_histogram(bins = 30, fill = "steelblue", color = "white") +
labs(
title = "Distribución general del nivel de estrés",
x = "Stress_Level",
y = "Frecuencia"
) +
theme_minimal()
ggplot(datos2, aes(x = Stress_Level)) +
geom_density(fill = "lightblue", alpha = 0.5) +
labs(
title = "Densidad del nivel de estrés",
x = "Stress_Level",
y = "Densidad"
) +
theme_minimal()
ggplot(datos2, aes(x = Sleep_cat, y = Stress_Level)) +
geom_boxplot(fill = "lightgreen") +
labs(
title = "Nivel de estrés por categoría de sueño",
x = "Categoría de sueño",
y = "Stress_Level"
) +
theme_minimal()
ggplot(datos2, aes(x = Screen_cat, y = Stress_Level)) +
geom_boxplot(fill = "lightcoral") +
labs(
title = "Nivel de estrés por categoría de tiempo de pantalla",
x = "Categoría de pantalla",
y = "Stress_Level"
) +
theme_minimal()
ggplot(datos2, aes(x = interaction(Sleep_cat, Screen_cat), y = Stress_Level, fill = Screen_cat)) +
geom_boxplot() +
labs(
title = "Nivel de estrés por combinación sueño × pantalla",
x = "Combinación de tratamientos",
y = "Stress_Level"
) +
theme_minimal()
ggplot(medias_A, aes(x = Sleep_cat, y = Stress_Level)) +
geom_col(fill = "skyblue") +
labs(
title = "Media del nivel de estrés por categoría de sueño",
x = "Categoría de sueño",
y = "Media de Stress_Level"
) +
theme_minimal()
ggplot(medias_B, aes(x = Screen_cat, y = Stress_Level)) +
geom_col(fill = "tan") +
labs(
title = "Media del nivel de estrés por categoría de pantalla",
x = "Categoría de pantalla",
y = "Media de Stress_Level"
) +
theme_minimal()
ggplot(medias_celda,
aes(x = Screen_cat, y = Stress_Level, color = Sleep_cat, group = Sleep_cat)) +
geom_point(size = 3) +
geom_line(linewidth = 1) +
labs(
title = "Gráfico de interacción: sueño × pantalla",
x = "Categoría de pantalla",
y = "Media de Stress_Level",
color = "Sueño"
) +
theme_minimal()
ggplot(medias_celda, aes(x = Screen_cat, y = Sleep_cat, fill = Stress_Level)) +
geom_tile(color = "white") +
geom_text(aes(label = round(Stress_Level, 2)), size = 4) +
labs(
title = "Mapa de calor de medias por combinación",
x = "Categoría de pantalla",
y = "Categoría de sueño",
fill = "Media"
) +
theme_minimal()
ggplot(datos2, aes(x = Sleep_cat, y = Stress_Level, fill = Sleep_cat)) +
geom_violin(alpha = 0.6) +
geom_boxplot(width = 0.12, fill = "white") +
labs(
title = "Distribución del nivel de estrés por categoría de sueño",
x = "Categoría de sueño",
y = "Stress_Level"
) +
theme_minimal() +
theme(legend.position = "none")
ggplot(datos2, aes(x = Screen_cat, y = Stress_Level, fill = Screen_cat)) +
geom_violin(alpha = 0.6) +
geom_boxplot(width = 0.12, fill = "white") +
labs(
title = "Distribución del nivel de estrés por categoría de pantalla",
x = "Categoría de pantalla",
y = "Stress_Level"
) +
theme_minimal() +
theme(legend.position = "none")
Los supuestos principales son:
residuos <- residuals(modelo)
ajustados <- fitted(modelo)
diagnostico <- data.frame(
Ajustados = ajustados,
Residuos = residuos
)
head(diagnostico)
## Ajustados Residuos
## 1 6.305821 -2.8158214
## 2 3.460915 -0.4509146
## 3 6.305821 -1.2758214
## 4 9.369127 0.6308730
## 5 6.305821 0.4041786
## 6 9.369127 0.6308730
set.seed(123)
muestra_res <- sample(residuos, size = min(5000, length(residuos)))
shapiro.test(muestra_res)
##
## Shapiro-Wilk normality test
##
## data: muestra_res
## W = 0.96993, p-value < 2.2e-16
ggplot(diagnostico, aes(sample = Residuos)) +
stat_qq() +
stat_qq_line(color = "red") +
labs(
title = "Q-Q plot de los residuos",
x = "Cuantiles teóricos",
y = "Cuantiles muestrales"
) +
theme_minimal()
ggplot(diagnostico, aes(x = Residuos)) +
geom_histogram(bins = 30, fill = "gray70", color = "white") +
labs(
title = "Histograma de residuos",
x = "Residuos",
y = "Frecuencia"
) +
theme_minimal()
ggplot(diagnostico, aes(x = Residuos)) +
geom_density(fill = "gray80", alpha = 0.7) +
labs(
title = "Densidad de residuos",
x = "Residuos",
y = "Densidad"
) +
theme_minimal()
ggplot(diagnostico, aes(x = Ajustados, y = Residuos)) +
geom_point(alpha = 0.4) +
geom_hline(yintercept = 0, linetype = "dashed", color = "red") +
labs(
title = "Residuos vs valores ajustados",
x = "Valores ajustados",
y = "Residuos"
) +
theme_minimal()
diagnostico$RaizAbsRes <- sqrt(abs(diagnostico$Residuos))
ggplot(diagnostico, aes(x = Ajustados, y = RaizAbsRes)) +
geom_point(alpha = 0.4) +
geom_smooth(se = FALSE, color = "blue") +
labs(
title = "Scale-Location plot",
x = "Valores ajustados",
y = expression(sqrt("|Residuos|"))
) +
theme_minimal()
bartlett.test(Stress_Level ~ interaction(Sleep_cat, Screen_cat), data = datos2)
##
## Bartlett test of homogeneity of variances
##
## data: Stress_Level by interaction(Sleep_cat, Screen_cat)
## Bartlett's K-squared = 1565.3, df = 8, p-value < 2.2e-16
fligner.test(Stress_Level ~ interaction(Sleep_cat, Screen_cat), data = datos2)
##
## Fligner-Killeen test of homogeneity of variances
##
## data: Stress_Level by interaction(Sleep_cat, Screen_cat)
## Fligner-Killeen:med chi-squared = 2069.4, df = 8, p-value < 2.2e-16
diagnostico$Sleep_cat <- datos2$Sleep_cat
ggplot(diagnostico, aes(x = Sleep_cat, y = Residuos)) +
geom_boxplot(fill = "lavender") +
labs(
title = "Residuos por categoría de sueño",
x = "Categoría de sueño",
y = "Residuos"
) +
theme_minimal()
diagnostico$Screen_cat <- datos2$Screen_cat
ggplot(diagnostico, aes(x = Screen_cat, y = Residuos)) +
geom_boxplot(fill = "mistyrose") +
labs(
title = "Residuos por categoría de pantalla",
x = "Categoría de pantalla",
y = "Residuos"
) +
theme_minimal()
TukeyHSD(modelo, which = "Sleep_cat")
## Tukey multiple comparisons of means
## 95% family-wise confidence level
##
## Fit: aov(formula = Stress_Level ~ Sleep_cat * Screen_cat, data = datos2)
##
## $Sleep_cat
## diff lwr upr p adj
## Medio-Bajo 0.3392025 0.09140428 0.5870007 0.0038232
## Alto-Bajo 0.7752774 0.64784425 0.9027106 0.0000000
## Alto-Medio 0.4360749 0.21977646 0.6523734 0.0000069
TukeyHSD(modelo, which = "Screen_cat")
## Tukey multiple comparisons of means
## 95% family-wise confidence level
##
## Fit: aov(formula = Stress_Level ~ Sleep_cat * Screen_cat, data = datos2)
##
## $Screen_cat
## diff lwr upr p adj
## Medio-Bajo 2.840054 2.764912 2.915196 0
## Alto-Bajo 5.920663 5.849190 5.992137 0
## Alto-Medio 3.080609 3.017592 3.143626 0
tabla_aov <- anova(modelo)
SC <- tabla_aov$"Sum Sq"
nombres <- rownames(tabla_aov)
eta2 <- SC / sum(SC)
data.frame(Fuente = nombres, Eta2 = eta2)
## Fuente Eta2
## 1 Sleep_cat 0.0040454788
## 2 Screen_cat 0.7224791637
## 3 Sleep_cat:Screen_cat 0.0001846741
## 4 Residuals 0.2732906834
Desde el punto de vista aplicado:
Por tanto, el análisis factorial permite responder:
La respuesta a estas preguntas debe basarse en la tabla ANOVA, el gráfico de interacción y la revisión de supuestos.
Las conclusiones deben redactarse con base en los resultados obtenidos al ejecutar este documento. Un formato apropiado es el siguiente:
ggplot2 para apoyar la
interpretación de efectos principales e interacción.