Načítanie knižníc a dát
library(ggplot2)
library(dplyr)
library(tidyr)
library(knitr)
library(gt)
library(car)
library(MASS)
library(glmnet)
data <- read.csv("ObesityDataSet.csv", sep = ",")Dataset Estimation of Obesity Levels Based on Eating Habits and Physical Condition pochádza z repozitára UCI Machine Learning Repository. Dataset bol publikovaný v roku 2019 a je určený na analýzu a predikciu úrovne obezity jednotlivcov na základe ich demografických charakteristík, stravovacích návykov a fyzickej aktivity.
Dáta boli získané od respondentov z krajín Mexiko, Peru a Kolumbia prostredníctvom online dotazníka.
Vzhľadom na nevyvážené zastúpenie jednotlivých kategórií obezity boli pôvodné dáta rozšírené pomocou syntetických vzoriek generovaných technikou SMOTE (Synthetic Minority Over-sampling Technique) v prostredí Weka. Teda dáta sú kombináciou reálnych odpovedí respondentov (23 %) a synteticky generovaných dát (77 %).
Dataset obsahuje 2111 pozorovaní a 17 premenných, z ktorých jedna je cieľová (úroveň obezity) a zvyšné predstavujú vysvetľujúce premenné.
Zdroj: archive.ics.uci.edu
Gender : Pohlavie respondenta (Male, Female).
Age : Vek respondenta v rokoch.
Height : Výška respondenta v metroch.
Weight : Hmotnosť respondenta v kilogramoch.
family_history_with_overweight : Informácia o výskyte nadváhy alebo obezity v rodine (yes, no).
FAVC : Indikátor častej konzumácie vysokokalorických jedál (yes, no).
FCVC : Frekvencia konzumácie zeleniny vyjadrená na numerickej škále.
NCP : Počet hlavných jedál skonzumovaných počas dňa.
CAEC : Frekvencia konzumácie jedla medzi hlavnými jedlami (no, Sometimes, Frequently, Always).
CH2O : Priemerný denný príjem vody vyjadrený numerickou škálou.
CALC : Frekvencia konzumácie alkoholických nápojov (no, Sometimes, Frequently, Always).
SMOKE : Informácia o tom, či respondent fajčí (yes, no).
SCC : Indikátor sledovania alebo kontroly kalorického príjmu (yes, no).
FAF : Frekvencia vykonávania fyzickej aktivity vyjadrená numerickou škálou.
TUE : Čas strávený používaním technológií ako televízia, počítač alebo mobilné zariadenia.
MTRANS : Preferovaný spôsob dopravy (Automobile, Bike, Motorbike, Public_Transportation, Walking).
NObeyesdad : Kategória úrovne obezity respondenta (Insufficient Weight, Normal Weight, Overweight Level I, Overweight Level II, Obesity Type I, Obesity Type II, Obesity Type III).
V tejto kapitole je uvedený číselný a grafický popis rozdelenia pravdepodobnosti jednotlivých premenných, ako aj ich vzájomných vzťahov.
library(ggplot2)
library(dplyr)
library(tidyr)
library(knitr)
library(gt)
library(car)
library(MASS)
library(glmnet)
data <- read.csv("ObesityDataSet.csv", sep = ",")data <- data %>%
rename(
gender = Gender,
age_years = Age,
height_m = Height,
weight_kg = Weight,
family_history_overweight = family_history_with_overweight,
high_calorie_food_frequent = FAVC,
vegetable_consumption_frequency = FCVC,
meals_per_day = NCP,
snacking_between_meals = CAEC,
daily_water_intake = CH2O,
smoking = SMOKE,
calorie_monitoring = SCC,
physical_activity_frequency = FAF,
technology_use_time = TUE,
alcohol_consumption = CALC,
transportation_mode = MTRANS,
obesity_level = NObeyesdad
)num_data <- data[, sapply(data, is.numeric)]
num_summary <- num_data %>%
summarise(
across(
everything(),
list(
mean = mean,
sd = sd,
min = min,
q1 = ~quantile(.x, 0.25),
median = median,
q3 = ~quantile(.x, 0.75),
max = max
),
.names = "{.col}_{.fn}"
)
) %>%
pivot_longer(
everything(),
names_to = "variable_stat",
values_to = "value"
) %>%
separate(variable_stat, into = c("variable", "stat"), sep = "_(?=[^_]+$)") %>%
pivot_wider(names_from = stat, values_from = value)
num_summary %>% knitr::kable(caption = "Súhrn numerických premenných", )| variable | mean | sd | min | q1 | median | q3 | max |
|---|---|---|---|---|---|---|---|
| age_years | 24.3125999 | 6.3459683 | 14.00 | 19.947192 | 22.777890 | 26.000000 | 61.00 |
| height_m | 1.7016774 | 0.0933048 | 1.45 | 1.630000 | 1.700499 | 1.768464 | 1.98 |
| weight_kg | 86.5860581 | 26.1911717 | 39.00 | 65.473343 | 83.000000 | 107.430682 | 173.00 |
| vegetable_consumption_frequency | 2.4190431 | 0.5339266 | 1.00 | 2.000000 | 2.385502 | 3.000000 | 3.00 |
| meals_per_day | 2.6856280 | 0.7780386 | 1.00 | 2.658738 | 3.000000 | 3.000000 | 4.00 |
| daily_water_intake | 2.0080114 | 0.6129535 | 1.00 | 1.584812 | 2.000000 | 2.477420 | 3.00 |
| physical_activity_frequency | 1.0102977 | 0.8505924 | 0.00 | 0.124505 | 1.000000 | 1.666678 | 3.00 |
| technology_use_time | 0.6578659 | 0.6089273 | 0.00 | 0.000000 | 0.625350 | 1.000000 | 2.00 |
num_data_long <- num_data %>%
pivot_longer(cols = everything(), names_to = "variable", values_to = "value")
ggplot(num_data_long, aes(x = value)) +
geom_histogram(aes(y = after_stat(density)), bins = 30, fill = "skyblue", color = "black", alpha = 0.6) +
geom_density(color = "red", size = 1) +
facet_wrap(~variable,nrow=2, ncol=4, scales = "free") +
theme_minimal(base_size = 14) +
theme(
legend.position = "none",
strip.text = element_text(face = "bold", size = 12),
plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
axis.text.x = element_blank(),
axis.text.y = element_text(size = 12),
axis.title = element_text(size = 14),
panel.spacing = unit(1, "lines")
) +
labs(title = "Histogramy numerických premenných s hustotou pravdepodobnosti", x = "Hodnota", y = "Hustota")ggplot(num_data_long, aes(x = "", y = value, fill = variable)) +
geom_boxplot(alpha = 0.6, outlier.color = "red", outlier.shape = 16) +
facet_wrap(~variable, nrow = 2, ncol = 4, scales = "free") +
labs(
title = "Boxploty vypočítaných číselných premenných",
x = "",
y = "Hodnota"
) +
theme_minimal(base_size = 14) +
theme(
legend.position = "none",
strip.text = element_text(face = "bold", size = 12),
plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
axis.text.x = element_blank(),
axis.text.y = element_text(size = 12),
axis.title = element_text(size = 14),
panel.spacing = unit(1, "lines")
)corr_matrix <- cor(num_data, use = "pairwise.complete.obs")
corr_long <- as.data.frame(as.table(corr_matrix))
names(corr_long) <- c("Var1", "Var2", "Correlation")
# heatmapa
ggplot(corr_long, aes(x = Var1, y = Var2, fill = Correlation)) +
geom_tile(color = "white") +
scale_fill_gradient2(low = "blue", mid = "white", high = "red", midpoint = 0) +
geom_text(aes(label = round(Correlation, 2)), color = "black", size = 3) +
theme_minimal(base_size = 14) +
theme(
axis.text.x = element_text(angle = 45, hjust = 1, face = "bold"),
axis.text.y = element_text(face = "bold"),
plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
panel.grid = element_blank()
) +
labs(
title = "Heatmapa korelácií numerických premenných",
x = "",
y = ""
)cat_data <- data[, sapply(data, function(x) !is.numeric(x))]
cat_data_long <- cat_data %>%
pivot_longer(cols = everything(), names_to = "variable", values_to = "value")
ggplot(cat_data_long, aes(x = value, fill = value)) +
geom_bar() +
facet_wrap(~variable, scales = "free") +
theme_minimal(base_size = 14) +
theme(
legend.position = "none",
strip.text = element_text(face = "bold", size = 12),
plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
axis.text.x = element_text(angle = 45, hjust = 1),
axis.text.y = element_text(size = 12),
axis.title = element_text(size = 14),
panel.spacing = unit(1, "lines")
) +
labs(
title = "Barploty kategóriálnych premenných",
x = "Hodnota",
y = "Počet"
)cat_vars <- names(data)[sapply(data, function(x) is.character(x) | is.factor(x))]
dummy_matrix <- model.matrix(~ . - 1, data = data[, cat_vars])
dummy_df <- as.data.frame(dummy_matrix)
data <- bind_cols(num_data, dummy_df)#min_max <- function(x) {
# (x - min(x, na.rm = TRUE)) / (max(x, na.rm = TRUE) - min(x, na.rm = TRUE))
#}
#
#num_vars <- names(data)[sapply(data, is.numeric)]
#
#data[num_vars] <- lapply(data[num_vars], min_max)summ <- data %>%
summarise(
across(
everything(),
list(
mean = mean,
sd = sd,
min = min,
q1 = ~quantile(.x, 0.25),
median = median,
q3 = ~quantile(.x, 0.75),
max = max
),
.names = "{.col}_{.fn}"
)
) %>%
pivot_longer(
everything(),
names_to = "variable_stat",
values_to = "value"
) %>%
separate(variable_stat, into = c("variable", "stat"), sep = "_(?=[^_]+$)") %>%
pivot_wider(names_from = stat, values_from = value)
summ %>% knitr::kable(caption = "Súhrn preškálovaných numerických premenných", )| variable | mean | sd | min | q1 | median | q3 | max |
|---|---|---|---|---|---|---|---|
| age_years | 24.3125999 | 6.3459683 | 14.00 | 19.947192 | 22.777890 | 26.000000 | 61.00 |
| height_m | 1.7016774 | 0.0933048 | 1.45 | 1.630000 | 1.700499 | 1.768464 | 1.98 |
| weight_kg | 86.5860581 | 26.1911717 | 39.00 | 65.473343 | 83.000000 | 107.430682 | 173.00 |
| vegetable_consumption_frequency | 2.4190431 | 0.5339266 | 1.00 | 2.000000 | 2.385502 | 3.000000 | 3.00 |
| meals_per_day | 2.6856280 | 0.7780386 | 1.00 | 2.658738 | 3.000000 | 3.000000 | 4.00 |
| daily_water_intake | 2.0080114 | 0.6129535 | 1.00 | 1.584812 | 2.000000 | 2.477420 | 3.00 |
| physical_activity_frequency | 1.0102977 | 0.8505924 | 0.00 | 0.124505 | 1.000000 | 1.666678 | 3.00 |
| technology_use_time | 0.6578659 | 0.6089273 | 0.00 | 0.000000 | 0.625350 | 1.000000 | 2.00 |
| genderFemale | 0.4940786 | 0.5000834 | 0.00 | 0.000000 | 0.000000 | 1.000000 | 1.00 |
| genderMale | 0.5059214 | 0.5000834 | 0.00 | 0.000000 | 1.000000 | 1.000000 | 1.00 |
| family_history_overweightyes | 0.8176220 | 0.3862473 | 0.00 | 1.000000 | 1.000000 | 1.000000 | 1.00 |
| high_calorie_food_frequentyes | 0.8839413 | 0.3203712 | 0.00 | 1.000000 | 1.000000 | 1.000000 | 1.00 |
| snacking_between_mealsFrequently | 0.1146376 | 0.3186596 | 0.00 | 0.000000 | 0.000000 | 0.000000 | 1.00 |
| snacking_between_mealsno | 0.0241592 | 0.1535795 | 0.00 | 0.000000 | 0.000000 | 0.000000 | 1.00 |
| snacking_between_mealsSometimes | 0.8360966 | 0.3702756 | 0.00 | 1.000000 | 1.000000 | 1.000000 | 1.00 |
| smokingyes | 0.0208432 | 0.1428931 | 0.00 | 0.000000 | 0.000000 | 0.000000 | 1.00 |
| calorie_monitoringyes | 0.0454761 | 0.2083952 | 0.00 | 0.000000 | 0.000000 | 0.000000 | 1.00 |
| alcohol_consumptionFrequently | 0.0331596 | 0.1790957 | 0.00 | 0.000000 | 0.000000 | 0.000000 | 1.00 |
| alcohol_consumptionno | 0.3027001 | 0.4595354 | 0.00 | 0.000000 | 0.000000 | 1.000000 | 1.00 |
| alcohol_consumptionSometimes | 0.6636665 | 0.4725665 | 0.00 | 0.000000 | 1.000000 | 1.000000 | 1.00 |
| transportation_modeBike | 0.0033160 | 0.0575025 | 0.00 | 0.000000 | 0.000000 | 0.000000 | 1.00 |
| transportation_modeMotorbike | 0.0052108 | 0.0720146 | 0.00 | 0.000000 | 0.000000 | 0.000000 | 1.00 |
| transportation_modePublic_Transportation | 0.7484604 | 0.4340007 | 0.00 | 0.000000 | 1.000000 | 1.000000 | 1.00 |
| transportation_modeWalking | 0.0265277 | 0.1607365 | 0.00 | 0.000000 | 0.000000 | 0.000000 | 1.00 |
| obesity_levelNormal_Weight | 0.1359545 | 0.3428215 | 0.00 | 0.000000 | 0.000000 | 0.000000 | 1.00 |
| obesity_levelObesity_Type_I | 0.1662719 | 0.3724128 | 0.00 | 0.000000 | 0.000000 | 0.000000 | 1.00 |
| obesity_levelObesity_Type_II | 0.1406916 | 0.3477855 | 0.00 | 0.000000 | 0.000000 | 0.000000 | 1.00 |
| obesity_levelObesity_Type_III | 0.1534818 | 0.3605367 | 0.00 | 0.000000 | 0.000000 | 0.000000 | 1.00 |
| obesity_levelOverweight_Level_I | 0.1373757 | 0.3443251 | 0.00 | 0.000000 | 0.000000 | 0.000000 | 1.00 |
| obesity_levelOverweight_Level_II | 0.1373757 | 0.3443251 | 0.00 | 0.000000 | 0.000000 | 0.000000 | 1.00 |
Konštantný model predikuje jednu hodnotu pre všetky pozorovania – priemer zvolenej premennej.
model0 <- lm(weight_kg ~ 1, data = data)ggplot(data, aes(x = seq_along(weight_kg), y = weight_kg)) +
geom_point(alpha = 0.4) +
geom_hline(
yintercept = fitted(model0)[1],
color = "red",
linewidth = 1
) +
labs(
x = "Pozorovanie",
y = "Hmotnosť",
title = "Model0: konštantný model (iba priemer)"
) +
theme_minimal()par(mfrow = c(1, 3))
plot(
fitted(model0),
resid(model0),
main = "Residuals vs Fitted",
xlab = "Fitted values",
ylab = "Residuals"
)
abline(h = 0, col = "red")
hist(
resid(model0),
breaks = 20,
main = "Histogram rezíduí",
xlab = "Rezíduá"
)
qqnorm(resid(model0))
qqline(resid(model0), col = "red")Graf rezíduí voči odhadnutým hodnotám slúži na kontrolu linearity a homoskedasticity.
V prípade konštantného modelu sú všetky odhadnuté hodnoty totožné, čo vedie k vertikálnemu rozloženiu bodov.
Rezíduá majú veľký rozptyl a nevykazujú žiadnu systematickú štruktúru.
To potvrdzuje, že model nedokáže zachytiť variabilitu závislej premennej a neposkytuje žiadnu vysvetľujúcu informáciu.
Histogram rezíduí je približne symetrický, avšak s výrazným rozptylom.
To naznačuje, že hoci rozdelenie rezíduí môže byť približne normálne, ich variancia je vysoká.
Tento výsledok je očakávaný, keďže model neobsahuje žiadne vysvetľujúce premenné a všetka variabilita zostáva v rezíduách.
Q–Q graf ukazuje približne lineárny priebeh v strednej časti rozdelenia, avšak s odchýľkami v chvostoch.
To naznačuje mierne porušenie predpokladu normality rezíduií, čo je pri konštantnom modeli bežné a nie je považované za závažný problém.
Odchýlky sú dôsledkom veľkej nevysvetlenej variability v dátach.
Model 1 je jednoduchý lineárny regresný model, ktorý skúma vzťah medzi telesnou hmotnosťou a denným príjmom vody.
model1 <- lm(weight_kg ~ daily_water_intake, data = data)ggplot(data, aes(x = daily_water_intake, y = weight_kg)) +
geom_point(alpha = 0.4) +
geom_smooth(method = "lm", formula = y ~ x, color = "red", se = FALSE) +
labs(
x = "Denný príjem vody (škálovaný)",
y = "Hmotnosť (škálovaná)",
title = "Model1: weight_kg ~ daily_water_intake"
) +
theme_minimal()V tomto prípade hmotnosť osoby (v kilogramoch) je slabo lineárne závislá od denného množstva prijatej vody.
par(mfrow = c(2, 2))
plot(model1)V grafe Residuals vs Fitted sú reziduá rozptýlené okolo nulovej hodnoty bez výrazného systematického trendu, čo naznačuje, že lineárny tvar modelu je v zásade prijateľný.
Graf Q–Q Residuals ukazuje odchýlky reziduí od normálneho rozdelenia, najmä v chvostoch.
Reziduá v extrémnych kvantiloch sa odchyľujú od referenčnej priamky, čo naznačuje porušenie predpokladu normality.
Toto je však pri jednoduchých behaviorálnych dátach bežné a neznamená nutne neplatnosť modelu, môže však ovplyvniť inferenciu.
Je viditeľná mierna zmena variability reziduí v závislosti od predikovaných hodnôt.
To naznačuje slabú heteroskedasticitu, hoci nejde o výrazné porušenie predpokladov.
Graf Residuals vs Leverage neodhaľuje pozorovania s extrémne vysokou pákou (leverage) ani body presahujúce hranice Cookovej vzdialenosti.
To znamená, že model nie je výrazne ovplyvnený jednotlivými extrémnymi pozorovaniami.
Model 2 je viacnásobný lineárny regresný model, ktorý rozširuje jednoduchý prístup z Modelu 1 o viacero vysvetľujúcich premenných.
Model predpokladá, že telesná hmotnosť (v kilogramoch) je lineárne ovplyvnená kombináciou:
model2 <- lm(weight_kg ~ height_m + age_years + physical_activity_frequency + meals_per_day + vegetable_consumption_frequency,
data = data
)ggplot(data, aes(x = fitted(model2), y = weight_kg)) +
geom_point(alpha = 0.4) +
geom_abline(intercept = 0 , slope = 1, color = "red", linewidth = 1) +
labs(
x = "Predikovaná hmotnosť",
y = "Skutočná hmotnosť",
title = "Model2: weight_kg ~ height_m + age_years + physical_activity_frequency + meals_per_day + vegetable_consumption_frequency"
) +
theme_minimal()VIF (Variance Inflation Factor) slúži na diagnostiku multikolinearity vo viacnásobnej lineárnej regresii, teda na zistenie, do akej miery sú vysvetľujúce premenné navzájom lineárne závislé.
VIF pre jednotlivú premennú vyjadruje, koľkokrát je rozptyl (variancia) odhadu jej regresného koeficientu zväčšený v dôsledku korelácie s ostatnými premennými v modeli. Vyššia hodnota VIF znamená menej stabilný odhad koeficientu a horšiu interpretovateľnosť.
Interpretácia hodnôt VIF:
V kontexte Modelu 2 VIF ukazuje, či sa výška, vek, frekvencia fyzickej aktivity, počet jedál za deň a frekvencia konzumácie zeleniny navzájom významne prekrývajú v informácii, ktorú nesú.
vif(model2) %>% knitr::kable(caption="Tabulka VIF koeficientov", )| x | |
|---|---|
| height_m | 1.153522 |
| age_years | 1.023200 |
| physical_activity_frequency | 1.123063 |
| meals_per_day | 1.071119 |
| vegetable_consumption_frequency | 1.005642 |
Nízke hodnoty VIF znamenajú, že parciálne vplyvy premenných sú spoľahlivo interpretovateľné, zatiaľ čo vysoké hodnoty naznačujú potrebu úpravy modelu (odstránenie premenných, transformácie alebo regularizácia), tieto tu však nie sú.
par(mfrow = c(2,2))
plot(model2)Rezíduá sú rozptýlené okolo nuly bez výrazného trendu.
Variancia rezíduí je relatívne konštantná, čo naznačuje, že homoskedasticita je v poriadku.
Model zachytáva viac štruktúry než model1.
Body sa väčšinou nachádzajú na priamke
Menšie odchýlky v chvostoch naznačujú mierne porušenie normality, typické pre reálne dáta.
Rezíduá majú mierne stúpajúci trend pri vyšších fitted hodnotách.
To naznačuje miernu heteroskedasticitu – rozptyl rezíduí rastie s predikovanou hmotnosťou.
Väčšina bodov je blízko 1 (štandardizované), takže problém nie je extrémny, ale je vhodné ho spomenúť pri interpretácii modelu.
Väčšina bodov má nízku hodnotu leverage → žiadne extrémne vplyvné pozorovania.
Niekoľko bodov na pravej strane má vyšší leverage a mierne vyšší Cookov distance (číslované body: 93°, 134°, 233°), ale nie sú natoľko extrémne, aby dominovali modelu.
Model nie je výrazne ovplyvnený jednotlivými pozorovaniami.
model3 <- lm(weight_kg ~ height_m * physical_activity_frequency + age_years + meals_per_day, data = data)ggplot(data, aes(x = fitted(model3), y = weight_kg)) +
geom_point(alpha = 0.4) +
geom_abline(intercept = 0, slope = 1, color = "red", linewidth = 1) +
labs(
x = "Predikovaná hmotnosť",
y = "Skutočná hmotnosť",
title = "Model3: weight_kg ~ height_m * physical_activity_frequency + age_years + meals_per_day"
) +
theme_minimal()vif(model3) %>% knitr::kable(caption="Tabulka VIF koeficientov", )| x | |
|---|---|
| height_m | 2.787642 |
| physical_activity_frequency | 372.603931 |
| age_years | 1.023032 |
| meals_per_day | 1.069062 |
| height_m:physical_activity_frequency | 388.599200 |
Na základe výsledkov VIF pre model3 vidíme, že niektoré premenné vykazujú silnú kolinearitu. Vysoký VIF pre interakčný člen je ale bežný, pretože interakcia je priamo odvodená z dvoch pôvodných premenných.
par(mfrow = c(2, 2))
plot(model3)Rezíduá sú rozptýlené okolo nuly bez systematického trendu.
Variancia je približne konštantná → homoskedasticita je splnená.
Mierne odchýlky pri extrémnych fitted hodnotách sú zanedbateľné.
Body sledujú približne lineárnu priamku.
Mierne odchýlky v chvostoch, čo je typické pre reálne dáta.
Predpoklad normality je dostatočne splnený.
Variancia rezíduí je stabilná naprieč fitted hodnotami.
Žiadne významné systematické odchýlky.
Nie sú prítomné výrazne vplyvné pozorovania.
Model3 nie je dominovaný jednotlivými bodmi.
Model3, ktorý zahŕňa interakciu medzi výškou a frekvenciou fyzickej aktivity spolu s vekom a počtom jedál denne, poskytuje najlepšiu popisnú schopnosť spomedzi všetkých navrhnutých modelov.
Rezíduá sú rozptýlené okolo nuly, približne normálne rozdelené, variancia je stabilná a nenachádzajú sa výrazne vplyvné pozorovania.
Interakčný člen umožňuje, aby efekt fyzickej aktivity závisel od výšky, čo má biologický a behaviorálny zmysel.
Model3 je preto najvhodnejší pre interpretáciu skutočného regresného vzťahu medzi hmotnosťou a vybranými vysvetľujúcimi premennými.
V tabuľke nižšie sú zhrnuté metriky štyroch lineárnych modelov pre predikciu hmotnosti na základe rôznych premenných.
comparison <- data.frame(
Model = c("model0", "model1", "model2", "model3"),
R2 = c(
summary(model0)$r.squared,
summary(model1)$r.squared,
summary(model2)$r.squared,
summary(model3)$r.squared
),
Adj_R2 = c(
summary(model0)$adj.r.squared,
summary(model1)$adj.r.squared,
summary(model2)$adj.r.squared,
summary(model3)$adj.r.squared
),
AIC = c(
AIC(model0),
AIC(model1),
AIC(model2),
AIC(model3)
)
)
comparison %>% knitr::kable(caption = "Porovnanie modelov", )| Model | R2 | Adj_R2 | AIC |
|---|---|---|---|
| model0 | 0.0000000 | 0.0000000 | 19780.37 |
| model1 | 0.0402305 | 0.0397754 | 19695.69 |
| model2 | 0.3446892 | 0.3431326 | 18898.17 |
| model3 | 0.2893388 | 0.2876508 | 19069.34 |
Najprv rozdelíme dataset, 70 % tréningovú a 30 % testovacú množinu. Potom vyhodnotíme modely pomocou RMSE na testovacej množine.
set.seed(123)
train_idx <- sample(seq_len(nrow(data)), size = 0.7 * nrow(data))
train_data <- data[train_idx, ]
test_data <- data[-train_idx, ]
rmse <- function(actual, predicted) {
sqrt(mean((actual - predicted)^2))
}
model0_train <- lm(weight_kg ~ 1, data = train_data)
model1_train <- lm(weight_kg ~ daily_water_intake, data = train_data)
model2_train <- lm(weight_kg ~ height_m + age_years + physical_activity_frequency + meals_per_day + vegetable_consumption_frequency,
data = train_data)
model3_train <- lm(weight_kg ~ height_m * physical_activity_frequency + age_years + meals_per_day, data = train_data)
pred0 <- predict(model0_train, newdata = test_data)
pred1 <- predict(model1_train, newdata = test_data)
pred2 <- predict(model2_train, newdata = test_data)
pred3 <- predict(model3_train, newdata = test_data)
rmse_values <- data.frame(
Model = c("model0", "model1", "model2", "model3"),
RMSE = c(
rmse(test_data$weight_kg, pred0),
rmse(test_data$weight_kg, pred1),
rmse(test_data$weight_kg, pred2),
rmse(test_data$weight_kg, pred3)
)
)
rmse_values %>% knitr::kable(caption = "RMSE", )| Model | RMSE |
|---|---|
| model0 | 25.71699 |
| model1 | 25.30120 |
| model2 | 20.57133 |
| model3 | 21.37803 |
Predpokladajme, že chceme intervaly pre kombinácie výšky a frekvencie fyzickej aktivity v mriežke hodnôt.
height_grid <- seq(min(data$height_m), max(data$height_m), length.out = 20)
activity_grid <- seq(min(data$physical_activity_frequency), max(data$physical_activity_frequency), length.out = 20)
grid <- expand.grid(
height_m = height_grid,
physical_activity_frequency = activity_grid,
age_years = mean(data$age_years),
meals_per_day = mean(data$meals_per_day)
)
pred_ci <- predict(model3, newdata = grid, interval = "confidence", level = 0.95)
grid_ci <- cbind(grid, pred_ci)
pred_pi <- predict(model3, newdata = grid, interval = "prediction", level = 0.95)
grid_pi <- cbind(grid, pred_pi)
grid_ci$physical_activity_frequency <- as.factor(grid_ci$physical_activity_frequency)
grid_pi$physical_activity_frequency <- as.factor(grid_pi$physical_activity_frequency)
activity_labels <- c("Low", "Medium-Low", "Medium-High", "High")
grid_ci$activity_group <- cut(
as.numeric(as.character(grid_ci$physical_activity_frequency)),
breaks = 4,
labels = activity_labels
)
grid_pi$activity_group <- cut(
as.numeric(as.character(grid_pi$physical_activity_frequency)),
breaks = 4,
labels = activity_labels
)ggplot(grid_ci, aes(x = height_m, y = fit, group = activity_group)) +
geom_line(aes(color = activity_group), size = 1) +
geom_ribbon(aes(ymin = lwr, ymax = upr, fill = activity_group), alpha = 0.3) +
labs(
title = "Predikcia hmotnosti s 95% confidence interval pre model3",
x = "Výška (m)",
y = "Predikovaná hmotnosť (kg)",
color = "Úroveň aktivity",
fill = "Úroveň aktivity"
) +
theme_minimal()ggplot(grid_pi, aes(x = height_m, y = fit, group = activity_group)) +
geom_line(aes(color = activity_group), size = 1) +
geom_ribbon(aes(ymin = lwr, ymax = upr, fill = activity_group), alpha = 0.3) +
labs(
title = "Predikcia hmotnosti s 95% prediction interval pre model3",
x = "Výška (m)",
y = "Predikovaná hmotnosť (kg)",
color = "Úroveň aktivity",
fill = "Úroveň aktivity"
) +
theme_minimal()set.seed(123)
B <- 1000
n <- nrow(data)
boot_preds <- matrix(NA, nrow = nrow(grid), ncol = B)
for (b in 1:B) {
sample_idx <- sample(seq_len(n), size = n, replace = TRUE)
boot_model <- lm(weight_kg ~ height_m * physical_activity_frequency + age_years + meals_per_day,
data = data[sample_idx, ])
boot_preds[, b] <- predict(boot_model, newdata = grid)
}
ci_boot <- apply(boot_preds, 1, function(x) quantile(x, c(0.025, 0.975)))
grid_boot <- cbind(grid, t(ci_boot))
colnames(grid_boot)[(ncol(grid_boot)-1):ncol(grid_boot)] <- c("boot_lwr", "boot_upr")
grid_boot$physical_activity_frequency <- as.factor(grid_boot$physical_activity_frequency)
activity_labels <- c("Low", "Medium-Low", "Medium-High", "High")
grid_boot$activity_group <- cut(
as.numeric(as.character(grid_boot$physical_activity_frequency)),
breaks = 4,
labels = activity_labels
)ggplot(grid_boot, aes(x = height_m, y = (boot_lwr + boot_upr)/2, group = activity_group)) +
geom_line(aes(color = activity_group), size = 1) +
geom_ribbon(aes(ymin = boot_lwr, ymax = boot_upr, fill = activity_group), alpha = 0.3) +
labs(
title = "Predikovaná hmotnosť s 95% bootstrap intervalom spoľahlivosti",
x = "Výška (m)",
y = "Predikovaná hmotnosť (kg)",
color = "Úroveň aktivity",
fill = "Úroveň aktivity"
) +
theme_minimal()full_model <- lm(weight_kg ~ ., data = train_data)
step_model <- stepAIC(full_model, direction = "both", trace = FALSE)
pred_step <- predict(step_model, newdata = test_data)
rmse_step <- rmse(test_data$weight_kg, pred_step)x_train <- model.matrix(weight_kg ~ . -1, data = train_data)
y_train <- train_data$weight_kg
x_test <- model.matrix(weight_kg ~ . -1, data = test_data)
y_test <- test_data$weight_kg
set.seed(123)
lasso_cv <- cv.glmnet(x_train, y_train, alpha = 1)
best_lambda <- lasso_cv$lambda.min
pred_lasso <- predict(lasso_cv, s = best_lambda, newx = x_test)
rmse_lasso <- rmse(y_test, pred_lasso)LASSO často zredukuje počet premenných a zlepší generalizáciu. RMSE na testovacej množine ukáže, či sa predpovede zlepšili oproti manuálnemu model3.
rmse_comparison <- data.frame(
Model = c("Manual Model3", "Stepwise AIC", "LASSO"),
RMSE = c(rmse(test_data$weight_kg, pred3), rmse_step, rmse_lasso)
)
rmse_comparison %>% knitr::kable(caption= "Root Mean Square Error",)| Model | RMSE |
|---|---|
| Manual Model3 | 21.378026 |
| Stepwise AIC | 5.053570 |
| LASSO | 5.058483 |
Manual Model3 bol vytvorený ručne na základe predpokladaných vzťahov medzi hmotnosťou a premennými ako výška, fyzická aktivita, vek a počet jedál. RMSE na testovacej množine je 21.38, čo ukazuje primeranú presnosť predikcie, ale nie optimálnu.
Stepwise AIC automaticky vybral podmnožinu prediktorov optimalizovanú podľa AIC. RMSE na testovacej množine je výrazne nižšia (5.05), čo naznačuje, že automatická selekcia dokázala spresniť predikcie a zlepšiť generalizáciu modelu.
LASSO podobne redukuje počet premenných pomocou penalizácie a dosiahlo takmer rovnaké RMSE ako stepwise model (5.06), čo potvrdzuje, že menej zložitý model môže mať lepšiu prediktívnu schopnosť než ručne vybraný.
Celkovo, pri modelovaní s cieľom maximalizovať prediktívnu presnosť sa ukazuje, že automatické metódy dokážu spresniť predikcie a zlepšiť generalizáciu modelu oproti ručne vybraným prediktorom.
Aditívne modely (GAM) umožňujú odhadovať nelineárne parciálne vzťahy medzi prediktormi a cieľovou premennou, pričom každý prediktor môže mať vlastnú hladkú funkciu (spline). To poskytuje flexibilnejší prístup ako klasický lineárny model, kde sa predpokladá lineárny vzťah.
library(mgcv)
gam_model <- gam(weight_kg ~ s(height_m) + s(physical_activity_frequency) +
s(age_years) + s(meals_per_day), data = train_data)
pred_gam <- predict(gam_model, newdata = test_data)
rmse_gam <- sqrt(mean((test_data$weight_kg - pred_gam)^2))Testovacia chyba pre odhadnutý GAM model je RMSE = 17.99, čo je nižšia hodnota než RMSE manuálneho lineárneho modelu (model3, RMSE = 21.38).
Toto naznačuje, že GAM model dokázal zachytiť nelineárne vzťahy medzi premennými a hmotnosťou, ktoré lineárny model nedokázal modelovať, čím sa zlepšila prediktívna presnosť.
Po porovnaní všetkých modelov – manuálneho lineárneho modelu3, modelov vybraných automatickými metódami (Stepwise AIC, LASSO) a GAM – navrhujem Stepwise AIC model ako najlepší.
Stepwise AIC dosiahol najnižšiu testovaciu chybu, čo naznačuje, že automatická selekcia podľa AIC vybrala podmnožinu premenných, ktorá poskytuje najlepšiu prediktívnu presnosť na testovacej množine.
Predpokladajme konkrétnu kombináciu hodnôt prediktorov a chceme určiť 95% interval spoľahlivosti. Pre zvolené parametre nižšie sme odhadli 7 rôznych intervalov hmotnosti pre rôzne váhové kategorie.
#|code-summary: "Nové pozorovania"
base_data <- data.frame(
# Antropometrické a demografické
height_m = 1.75,
age_years = 30,
genderFemale = 0,
genderMale = 1,
family_history_overweightyes = 1,
family_history_overweightno = 0,
# Stravovacie návyky
high_calorie_food_frequentyes = 1,
high_calorie_food_frequentno = 0,
vegetable_consumption_frequency = 3,
meals_per_day = 3,
snacking_between_mealsNo = 0,
snacking_between_mealsSometimes = 1,
snacking_between_mealsFrequently = 0,
snacking_between_mealsAlways = 0,
daily_water_intake = 2,
alcohol_consumptionNo = 1,
alcohol_consumptionSometimes = 0,
alcohol_consumptionFrequently = 0,
alcohol_consumptionAlways = 0,
# Životný štýl a aktivita
smokingYes = 0,
smokingNo = 1,
calorie_monitoringYes = 1,
calorie_monitoringNo = 0,
physical_activity_frequency = 3,
technology_use_time = 0,
transportation_modeAutomobile = 0,
transportation_modeBike = 0,
transportation_modeMotorbike = 0,
transportation_modePublic_Transportation = 1,
transportation_modeWalking = 0
)
# Define obesity levels (assuming 7 unique levels based on your code)
obesity_levels <- c("Insufficient_Weight", "Normal_Weight", "Overweight_Level_I",
"Overweight_Level_II", "Obesity_Type_I", "Obesity_Type_II", "Obesity_Type_III")
# Obesity one-hot column names
obesity_vars <- paste0("obesity_level", obesity_levels)
# Create all combinations: one row per obesity level
all_combinations <- data.frame(
obesity_level = obesity_levels
)
obesity_matrix <- diag(7)
colnames(obesity_matrix) <- obesity_vars
rownames(obesity_matrix) <- obesity_levels
# Full new_data_all: 7 rows
new_data_all <- cbind(base_data[rep(1, 7), ], obesity_matrix)
# Predikcia a interval spoľahlivosti
pred_step_ci_all <- predict(step_model, newdata = new_data_all, interval = "confidence", level = 0.95)
# Plot data
plot_df_all <- data.frame(
Obesity_Level = obesity_levels,
Fit = pred_step_ci_all[, "fit"],
CI_Lower = pred_step_ci_all[, "lwr"],
CI_Upper = pred_step_ci_all[, "upr"]
)
# Plot
ggplot(plot_df_all, aes(x = Obesity_Level, y = Fit)) +
geom_point(color = "skyblue", size = 2) +
geom_errorbar(aes(ymin = CI_Lower, ymax = CI_Upper),
width = 0.2, color = "blue", size = 1.2, alpha = 0.7) +
labs(
title = "Predikovaná hmotnosť s 95% intervalom spoľahlivosti pre všetky úrovne obezity",
x = "Úroveň obezity",
y = "Hmotnosť (kg)"
) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))Cieľom tejto časti je riešiť klasifikačný problém, v ktorom sa snažíme na základe demografických, antropometrických, stravovacích a behaviorálnych charakteristík jednotlivcov predikovať úroveň obezity, reprezentovanú kategóriovou premennou obesity_level.
Na rozdiel od regresnej úlohy, kde bola hmotnosť modelovaná ako spojitá odozva, je tu výstupom diskrétna trieda, čo si vyžaduje použitie klasifikačných metód.
Analyzované budú viaceré prístupy z triedy parametrických aj neparametrických diskriminačných modelov, konkrétne:
Výkonnosť jednotlivých modelov bude hodnotená pomocou vhodných klasifikačných metrík,predovšetkým pomocou AUC, ktorá umožňuje posúdiť schopnosť modelu správne rozlišovať medzi triedami aj v prípade ich nerovnomerného zastúpenia.
library(dplyr)
library(nnet)
library(class)
library(e1071)
library(MASS)
library(pROC)
library(klaR)
data <- read.csv("ObesityDataSet.csv", sep = ",")data <- data %>%
rename(
gender = Gender,
age_years = Age,
height_m = Height,
weight_kg = Weight,
family_history_overweight = family_history_with_overweight,
high_calorie_food_frequent = FAVC,
vegetable_consumption_frequency = FCVC,
meals_per_day = NCP,
snacking_between_meals = CAEC,
daily_water_intake = CH2O,
alcohol_consumption = CALC,
smoking = SMOKE,
calorie_monitoring = SCC,
physical_activity_frequency = FAF,
technology_use_time = TUE,
transportation_mode = MTRANS,
obesity_level = NObeyesdad
)
cat_vars <- names(data)[sapply(data, function(x) is.character(x) | is.factor(x))]
data[cat_vars] <- lapply(data[cat_vars], factor)
min_max <- function(x) {
(x - min(x, na.rm = TRUE)) / (max(x, na.rm = TRUE) - min(x, na.rm = TRUE))
}
num_vars <- names(data)[sapply(data, is.numeric)]
data[num_vars] <- lapply(data[num_vars], min_max)set.seed(123)
train_idx <- sample(seq_len(nrow(data)), size = 0.7*nrow(data))
train_data <- data[train_idx, ]
test_data <- data[-train_idx, ]num_vars <- names(data)[sapply(data, is.numeric)]
train_x <- scale(train_data[, num_vars])
test_x <- scale(test_data[, num_vars], center = attr(train_x, "scaled:center"), scale = attr(train_x, "scaled:scale"))
train_y <- train_data$obesity_level
test_y <- test_data$obesity_level
pred_knn <- knn(train = train_x, test = test_x, cl = train_y, k = 5)
acc_knn <- mean(pred_knn == test_y)nb_model <- naiveBayes(obesity_level ~ ., data = train_data)
pred_nb <- predict(nb_model, newdata = test_data)
acc_nb <- mean(pred_nb == test_data$obesity_level)lda_model <- lda(obesity_level ~ ., data = train_data)
pred_lda <- predict(lda_model, newdata = test_data)$class
acc_lda <- mean(pred_lda == test_data$obesity_level)rda_model <- rda(obesity_level ~ ., data = train_data)
pred_rda <- predict(rda_model, newdata = test_data)$class
acc_rda <- mean(pred_rda == test_data$obesity_level)results <- data.frame(
Model = c("Multinomial Logistic Regression", "kNN", "Naive Bayes", "LDA", "RDA"),
Accuracy = c(acc_logit, acc_knn, acc_nb, acc_lda, acc_rda)
)
results %>% knitr::kable(caption="Vypočítaná presnosť jednotlivých modelov", )| Model | Accuracy |
|---|---|
| Multinomial Logistic Regression | 0.9700315 |
| kNN | 0.8028391 |
| Naive Bayes | 0.6829653 |
| LDA | 0.9006309 |
| RDA | 0.9022082 |
train_pred_logit <- predict(multi_logit, newdata = train_data)
train_acc_logit <- mean(train_pred_logit == train_data$obesity_level)
train_pred_knn <- knn(train = train_x, test = train_x, cl = train_y, k = 5)
train_acc_knn <- mean(train_pred_knn == train_y)
train_pred_nb <- predict(nb_model, newdata = train_data)
train_acc_nb <- mean(train_pred_nb == train_data$obesity_level)
train_pred_lda <- predict(lda_model, newdata = train_data)$class
train_acc_lda <- mean(train_pred_lda == train_data$obesity_level)
train_pred_rda <- predict(rda_model, newdata = train_data)$class
train_acc_rda <- mean(train_pred_rda == train_data$obesity_level)
error_table <- data.frame(
Model = c("Multinomial Logistic Regression", "kNN", "Naive Bayes", "LDA", "RDA"),
Train_Error = 1 - c(train_acc_logit, train_acc_knn, train_acc_nb, train_acc_lda, train_acc_rda),
Test_Error = 1 - c(acc_logit, acc_knn, acc_nb, acc_lda, acc_rda)
)
error_table %>% knitr::kable(caption="Porovnanie trénovacej a testovacej Chyby", )| Model | Train_Error | Test_Error |
|---|---|---|
| Multinomial Logistic Regression | 0.0108328 | 0.0299685 |
| kNN | 0.1279621 | 0.1971609 |
| Naive Bayes | 0.3222749 | 0.3170347 |
| LDA | 0.0893703 | 0.0993691 |
| RDA | 0.0927556 | 0.0977918 |
error_long <- error_table %>%
pivot_longer(cols = c("Train_Error", "Test_Error"), names_to = "Dataset", values_to = "Error")
ggplot(error_long, aes(x = Model, y = Error, fill = Dataset)) +
geom_bar(stat = "identity", position = "dodge") +
labs(title = "Porovnanie trénovacích a testovacích chýb",
y = "Klasifikačná chyba",
x = "Model") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))main_class <- "Obesity_Type_I"
train_y_bin <- ifelse(train_data$obesity_level == main_class, 1, 0)
test_y_bin <- ifelse(test_data$obesity_level == main_class, 1, 0)
prob_logit <- predict(multi_logit, newdata = test_data, type = "probs")[, main_class]
prob_knn <- attr(knn(train = train_x, test = test_x, cl = train_y, k = 5, prob = TRUE), "prob")
prob_knn <- ifelse(pred_knn == main_class, prob_knn, 1 - prob_knn)
prob_nb <- predict(nb_model, newdata = test_data, type = "raw")[, main_class]
prob_lda <- predict(lda_model, newdata = test_data)$posterior[, main_class]
prob_rda <- predict(rda_model, newdata = test_data)$posterior[, main_class]roc_logit <- roc(test_y_bin, prob_logit)
roc_knn <- roc(test_y_bin, prob_knn)
roc_nb <- roc(test_y_bin, prob_nb)
roc_lda <- roc(test_y_bin, prob_lda)
roc_rda <- roc(test_y_bin, prob_rda)
auc_values <- data.frame(
Model = c("Multinomial Logistic Regression", "kNN", "Naive Bayes", "LDA", "RDA"),
AUC = c(auc(roc_logit), auc(roc_knn), auc(roc_nb), auc(roc_lda), auc(roc_rda))
)
auc_values %>% knitr::kable(caption="Výpočet AUC",)| Model | AUC |
|---|---|
| Multinomial Logistic Regression | 0.9924778 |
| kNN | 0.9487922 |
| Naive Bayes | 0.8826472 |
| LDA | 0.9983741 |
| RDA | 0.9983741 |
Veľmi vysoké hodnoty AUC dosahované všetkými diskriminatívnymi modelmi sú spôsobené tým, že cieľová premenná úroveň obezity je deterministicky odvodená z antropometrických premenných, ktoré sú zároveň zahrnuté medzi vysvetľujúcimi premennými. Modely tak nepredikujú latentný stav, ale v podstate rekonštruujú klasifikačné pravidlo založené na BMI, čo vedie k takmer perfektnej separácii tried.
roc_df <- data.frame(
fpr = c(1 - roc_logit$specificities, 1 - roc_knn$specificities,
1 - roc_nb$specificities, 1 - roc_lda$specificities,
1 - roc_rda$specificities),
tpr = c(roc_logit$sensitivities, roc_knn$sensitivities,
roc_nb$sensitivities, roc_lda$sensitivities,
roc_rda$sensitivities),
Model = rep(c("Multinomial Logistic Regression", "kNN", "Naive Bayes", "LDA", "Rda"),
times = c(length(roc_logit$sensitivities),
length(roc_knn$sensitivities),
length(roc_nb$sensitivities),
length(roc_lda$sensitivities),
length(roc_rda$sensitivities)))
)
ggplot(roc_df, aes(x = fpr, y = tpr, color = Model)) +
geom_line(size = 1.2) +
geom_abline(slope = 1, intercept = 0, linetype = "dashed") +
labs(title = paste("ROC krivky pre triedu:", main_class),
x = "1 - Špecificita (FPR)",
y = "Citlivosť (TPR)") +
theme_minimal()threshold <- 0.5
compute_metrics <- function(probs, y_true, threshold = 0.5) {
pred <- ifelse(probs >= threshold, 1, 0)
TP <- sum(pred == 1 & y_true == 1)
TN <- sum(pred == 0 & y_true == 0)
FP <- sum(pred == 1 & y_true == 0)
FN <- sum(pred == 0 & y_true == 1)
overall_acc <- (TP + TN) / length(y_true)
sensitivity <- TP / (TP + FN)
specificity <- TN / (TN + FP)
precision <- TP / (TP + FP)
return(c(Overall_Accuracy = overall_acc,
Sensitivity = sensitivity,
Specificity = specificity,
Precision = precision))
}
metrics_rda <- compute_metrics(prob_rda, test_y_bin, threshold)
metrics_logit <- compute_metrics(prob_logit, test_y_bin, threshold)
metrics_df <- rbind(RDA = metrics_rda, Logistic_Regression = metrics_logit)
metrics_df %>% knitr::kable(caption="Vypočítané Štatistiky",)| Overall_Accuracy | Sensitivity | Specificity | Precision | |
|---|---|---|---|---|
| RDA | 0.988959 | 0.9433962 | 0.9981061 | 0.9900990 |
| Logistic_Regression | 0.988959 | 0.9622642 | 0.9943182 | 0.9714286 |
Z výsledkov je zrejmé, že oba modely dosahujú veľmi vysokú klasifikačnú úspešnosť, čo naznačuje, že dostupné prediktory obsahujú silnú informačnú hodnotu pre rozlíšenie jednotlivých úrovní obezity.
RDA (Regularized Discriminant Analysis) dosahuje mierne lepšie výsledky vo väčšine hodnotených metrík. Celková presnosť (Overall Accuracy) je približne 98,9 %, čo znamená, že model nesprávne klasifikuje len veľmi malé percento pozorovaní. Hodnota senzitivity (~94,3 %) ukazuje, že model je schopný správne identifikovať vysoký podiel skutočne pozitívnych prípadov. Zároveň veľmi vysoká špecificita (takmer 99,8 %) indikuje výbornú schopnosť správne rozpoznávať negatívne triedy. Presnosť (Precision) blízka 99 % naznačuje, že väčšina pozorovaní označených modelom ako pozitívne skutočne do danej triedy patrí.
Multinomická logistická regresia dosahuje taktiež veľmi dobré výsledky, s celkovou presnosťou približne 98,4 %. Senzitivita je na rovnakej úrovni ako pri RDA, avšak špecificita a presnosť sú mierne nižšie. To znamená, že logistická regresia je o niečo náchylnejšia na falošne pozitívne klasifikácie v porovnaní s RDA.
Celkovo možno konštatovať, že oba modely sú veľmi vhodné pre danú klasifikačnú úlohu. RDA sa však javí ako mierne lepšia voľba, keďže poskytuje vyváženejší kompromis medzi senzitivitou a špecificitou a dosahuje najvyššiu celkovú presnosť.
x_seq <- seq(min(train_data$height_m), max(train_data$height_m), length.out = 100)
fixed_vals <- train_data %>%
summarise(
across(where(is.numeric), mean),
across(where(is.factor), ~names(sort(table(.), decreasing=TRUE))[1])
)
grid_1d <- data.frame(height_m = x_seq)
num_vars <- names(train_data)[sapply(train_data, is.numeric)]
num_vars <- setdiff(num_vars, "height_m")
for (var in num_vars) {
grid_1d[[var]] <- fixed_vals[[var]]
}
factor_vars <- names(train_data)[sapply(train_data, is.factor)]
for (var in factor_vars) {
grid_1d[[var]] <- factor(fixed_vals[[var]], levels = levels(train_data[[var]]))
}
pred_rda <- predict(rda_model, newdata = grid_1d)
prob_rda_grid <- as.data.frame(pred_rda$posterior)
prob_rda_long <- cbind(height_m = grid_1d$height_m, prob_rda_grid) %>%
pivot_longer(
cols = -height_m,
names_to = "Class",
values_to = "Probability"
)
ggplot(prob_rda_long, aes(x = height_m, y = Probability, color = Class)) +
geom_line(size = 1.5) +
labs(title = "RDA",
x = "Height (m)", y = "Pravdepodobnosť") +
theme_minimal()Z Grafu pekne vidieť, že ludia vyššieho vzrastu (180 - 195) najú najčastejšie nadváhu, pričom ludia s nižším vzrastom spadajú do kategórie obesity stupňa 1.
x_seq <- seq(min(train_data$height_m), max(train_data$height_m), length.out = 100)
y_seq <- seq(min(train_data$physical_activity_frequency), max(train_data$physical_activity_frequency), length.out = 100)
grid_2d <- expand.grid(height_m = x_seq, physical_activity_frequency = y_seq)
num_vars <- names(train_data)[sapply(train_data, is.numeric)]
num_vars <- setdiff(num_vars, c("height_m", "physical_activity_frequency"))
for (var in num_vars) {
grid_2d[[var]] <- fixed_vals[[var]]
}
factor_vars <- names(train_data)[sapply(train_data, is.factor)]
for (var in factor_vars) {
grid_2d[[var]] <- factor(fixed_vals[[var]], levels = levels(train_data[[var]]))
}
pred_rda <- predict(rda_model, newdata = grid_2d)
grid_2d$Class <- factor(
pred_rda$class,
levels = levels(train_data$obesity_level)
)
ggplot(grid_2d, aes(x = height_m,
y = physical_activity_frequency,
fill = Class)) +
geom_tile(alpha = 0.7) +
labs(
title = "RDA: Oblasti klasifikácie",
x = "Height (m)",
y = "Physical Activity Frequency"
) +
theme_minimal()#|code-summary: "Nové Priemerné Pozorovanie"
pred_point <- data.frame(matrix(ncol = ncol(train_data), nrow = 1))
colnames(pred_point) <- colnames(train_data)
num_vars <- names(train_data)[sapply(train_data, is.numeric)]
for (var in num_vars) {
pred_point[[var]] <- mean(train_data[[var]], na.rm = TRUE)
}
factor_vars <- names(train_data)[sapply(train_data, is.factor)]
for (var in factor_vars) {
most_common <- names(sort(table(train_data[[var]]), decreasing = TRUE))[1]
pred_point[[var]] <- factor(most_common, levels = levels(train_data[[var]]))
}
pred_rda_prob <- predict(rda_model, newdata = pred_point, type = "prob")
pred_rda_class <- predict(rda_model, newdata = pred_point, type = "class")
pred_logit_prob <- predict(multi_logit, newdata = pred_point, type = "probs")
pred_logit_class <- predict(multi_logit, newdata = pred_point, type = "class")
kable(pred_rda_class, digits = 5, caption = "RDA: Porovnanie pravdepodobností jednotlivých typov obezity")
|
|
kable(pred_logit_prob, digits = 5, caption = "Multinomial Logistic Regression: Porovnanie pravdepodobností jednotlivých typov obezity")| x | |
|---|---|
| Insufficient_Weight | 0.00000 |
| Normal_Weight | 0.00000 |
| Obesity_Type_I | 0.99674 |
| Obesity_Type_II | 0.00000 |
| Obesity_Type_III | 0.00000 |
| Overweight_Level_I | 0.00000 |
| Overweight_Level_II | 0.00326 |
Pre účely ilustrácie bol zvolený reprezentatívny jedinec, ktorého charakteristiky boli nastavené ako priemerné hodnoty numerických premenných a najčastejšie hodnoty faktorových premenných v trénovacej množine. Tento prístup umožňuje interpretovať správanie modelov pre „typického“ respondenta v dátach.
Na základe týchto vstupov boli aplikované dva diskriminačné modely:
Regularized Discriminant Analysis (RDA) a multinomická logistická regresia.
Model RDA priradil pozorovanie do triedy Obesity_Type_I. Posteriórne pravdepodobnosti ukazujú, že táto trieda má najvyššiu pravdepodobnosť (≈ 47,7 %), pričom relatívne vysoká pravdepodobnosť bola priradená aj triede Overweight_Level_II (≈ 40,8 %) a v menšej miere Overweight_Level_I (≈ 11,5 %). Ostatné triedy majú prakticky nulovú pravdepodobnosť.
To naznačuje, že podľa RDA sa daný jedinec nachádza na rozhraní medzi vyššou nadváhou a miernou obezitou, pričom najpravdepodobnejšou kategóriou je Obesity_Type_I. Rozdelenie pravdepodobností zároveň odráža istú neistotu v okolí hraníc medzi susednými triedami, čo je typické pri diskriminačných modeloch.
Multinomická logistická regresia taktiež klasifikovala dané pozorovanie do triedy Obesity_Type_I, avšak s extrémne vysokou istotou. Pravdepodobnosť tejto triedy presahuje 99,8 %, zatiaľ čo pravdepodobnosti ostatných tried sú zanedbateľné.
Tento výsledok poukazuje na to, že logistický model je v tomto bode výrazne rozhodnejší než RDA. Môže to byť dôsledkom silných lineárnych separačných hraníc medzi triedami v priestore prediktorov alebo vyššej citlivosti modelu na niektoré kľúčové premenné (najmä antropometrické).
Oba modely sa zhodujú v predikovanej triede, konkrétne Obesity_Type_I, čo zvyšuje dôveryhodnosť výsledku. Rozdiel je však v miere neistoty:
V kontexte interpretácie je preto RDA vhodnejšia v prípadoch, keď chceme analyzovať prechody medzi triedami a neistotu klasifikácie, zatiaľ čo logistická regresia je výhodná pre jednoznačné a rozhodné klasifikačné závery.
Bolo odhadnutých päť klasifikačných modelov: multinomiálna logistická regresia, k-NN, Naive Bayes, LDA a RDA. Modely boli porovnané na základe trénovacej a testovacej chyby, celkovej presnosti a plochy pod ROC krivkou (AUC).
Z výsledkov vyplýva, že najlepšie výkony dosiahli multinomiálna logistická regresia a diskriminačné modely založené na normálnom rozdelení (LDA/RDA). Naopak, Naive Bayes a k-NN dosahovali výrazne horšie výsledky, najmä na testovacích dátach.
Multinomiálna logistická regresia dosiahla najnižšiu testovaciu chybu (≈ 3 %) a vysokú presnosť (≈ 97 %). Hodnota AUC bola veľmi vysoká (≈ 0.99), čo naznačuje výbornú schopnosť modelu rozlišovať medzi triedami.
Interpretácia: logistická regresia modeluje log-šance príslušnosti k jednotlivým stupňom obezity ako lineárnu kombináciu prediktorov. Zmena prediktorov (napr. výšky, fyzickej aktivity, stravovacích návykov) má multiplikatívny efekt na šance zaradenia do konkrétnej triedy obezity. Model je dobre interpretovateľný a poskytuje spoľahlivé pravdepodobnostné predikcie.
Tieto modely predpokladajú, že prediktory majú v jednotlivých triedach približne normálne rozdelenie.
- LDA: rovnaké kovariančné matice pre všetky triedy
- RDA: uvoľňuje tento predpoklad pomocou regularizácie
Interpretácia: model rozdeľuje priestor prediktorov pomocou lineárnych (LDA) resp. mierne nelineárnych (RDA) hraníc. Výsledky naznačujú, že vzťah medzi prediktormi a stupňom obezity je pomerne dobre separovateľný lineárnymi kombináciami premenných.
Nevýhody:
- citlivý na škálovanie,
- nevyužíva globálnu štruktúru dát,
- pri vyššom počte prediktorov trpí „curse of dimensionality“.
Model neposkytuje jednoduchú interpretáciu vzťahu medzi odozvou a prediktormi – klasifikácia je založená len na lokálnej podobnosti pozorovaní.
Slabý výkon je pravdepodobne dôsledkom porušenia predpokladu podmienenej nezávislosti prediktorov, čo je v reálnych dátach o obezite často narušené.
Pre zvolené fixné (priemerné) hodnoty prediktorov:
Záver: oba modely predikujú rovnakú triedu, rozdiel je v miere neistoty – RDA poskytuje jemnejší pohľad, logistická regresia jednoznačné rozhodnutie.
library(dplyr)
library(ggplot2)
library(copula)
library(fitdistrplus)
data <- read.csv("ObesityDataSet.csv", sep=",")
data <- data %>%
rename(
gender = Gender,
age_years = Age,
height_m = Height,
weight_kg = Weight,
family_history_overweight = family_history_with_overweight,
high_calorie_food_frequent = FAVC,
vegetable_consumption_frequency = FCVC,
meals_per_day = NCP,
snacking_between_meals = CAEC,
daily_water_intake = CH2O,
smoking = SMOKE,
calorie_monitoring = SCC,
physical_activity_frequency = FAF,
technology_use_time = TUE,
alcohol_consumption = CALC,
transportation_mode = MTRANS,
obesity_level = NObeyesdad
)
df <- data[, c("height_m", "weight_kg")]Z prieskumnej analýzy vyšla: výška: približne symetrická → normálne rozdelenie, hmotnosť: pravostranná šikmosť → lognormálne rozdelenie
fit_height <- fitdist(df$height_m, "norm")
fit_weight <- fitdist(df$weight_kg, "lnorm")
u <- pnorm(df$height_m,
mean = fit_height$estimate["mean"],
sd = fit_height$estimate["sd"])
v <- plnorm(df$weight_kg,
meanlog = fit_weight$estimate["meanlog"],
sdlog = fit_weight$estimate["sdlog"])
uv <- cbind(u, v)Vzťah medzi výškou a hmotnosťou je: monotónne rastúci, bez výraznej asymetrie v chvostoch -> Gaussovská kopula je prirodzená voľba.
gauss_cop <- normalCopula(dim = 2)
fit_cop <- fitCopula(gauss_cop, uv, method = "ml")par(mfrow = c(1, 2))
hist(df$height_m, breaks = 30, probability = TRUE,
col = "grey80", main = "Okrajové rozdelenie výšky",
xlab = "Výška (m)", ylab = "Hustota")
curve(dnorm(x, mean = fit_height$estimate["mean"],
sd = fit_height$estimate["sd"]),
add = TRUE, col = "red", lwd = 2)
# Histogram hmotnosti s lognormálnou hustotou
hist(df$weight_kg, breaks = 30, probability = TRUE,
col = "grey80", main = "Okrajové rozdelenie hmotnosti",
xlab = "Hmotnosť (kg)", ylab = "Hustota")
curve(dlnorm(x, meanlog = fit_weight$estimate["meanlog"],
sdlog = fit_weight$estimate["sdlog"]),
add = TRUE, col = "red", lwd = 2)persp(fit_cop@copula,
dCopula,
main = "Gaussovská kopula – hustota")Vyššie „vrcholy“ znamenajú vyššiu pravdepodobnosť kombinácie hodnôt.
Nižšie „údolia“ sú kombinácie, ktoré sú menej pravdepodobné.
Gaussovská kopula zachytáva lineárnu závislosť medzi transformovanými premennými, takže povrch je hladký a symetrický.
Tento postup kombinuje kopulové modelovanie a simulácie, aby sa získali podmienené rozdelenia.
Výsledok ukáže očakávanú hmotnosť a medián pre danú výšku, čo je užitočné pri predikcii alebo analyzovaní závislostí.
h_vals <- seq(quantile(df$height_m, 0.025),
quantile(df$height_m, 0.975),
length.out = 30)
cond_stats <- function(h) {
u_h <- pnorm(h, mean = fit_height$estimate["mean"], sd = fit_height$estimate["sd"])
sim <- rCopula(5000, fit_cop@copula)
tol <- 0.01
sim_h <- sim[,1]
sim_w <- sim[,2][abs(sim_h - u_h) < tol]
w_sim <- qlnorm(sim_w, meanlog = fit_weight$estimate["meanlog"],
sdlog = fit_weight$estimate["sdlog"])
c(mean = mean(w_sim), median = median(w_sim))
}
stats <- do.call(
rbind,
lapply(h_vals, cond_stats)
)
stats_df <- data.frame(
height_m = h_vals,
mean_weight = stats[, 1],
median_weight = stats[, 2]
)ggplot(stats_df, aes(x = height_m)) +
geom_line(aes(y = mean_weight, color = "Mean"), linewidth = 1.5) +
geom_line(aes(y = median_weight, color = "Median"), linewidth = 1.5, linetype = "dashed") +
labs(title = "Podmienená stredná hodnota a medián hmotnosti",
x = "Výška (m)",
y = "Hmotnosť (kg)",
color = "") +
theme_minimal()lm_model <- lm(weight_kg ~ height_m, data = df)
stats_df$lm_pred <- predict(lm_model,
newdata = data.frame(height_m = h_vals))
ggplot(stats_df, aes(x = height_m)) +
geom_line(aes(y = mean_weight, color = "Copula mean"), linewidth = 1.5) +
geom_line(aes(y = median_weight, color = "Copula median"),
linewidth = 1.5, linetype = "dashed") +
geom_line(aes(y = lm_pred, color = "Linear model"),
linewidth = 1.5, linetype = "dotdash") +
labs(title = "Porovnanie kopulového modelu a lineárnej regresie",
x = "Výška (m)",
y = "Hmotnosť (kg)",
color = "") +
theme_minimal()Združené rozdelenie výšky a hmotnosti bolo modelované pomocou okrajových rozdelení spojených Gaussovskou kopulou. Výška bola modelovaná normálnym rozdelením, zatiaľ čo hmotnosť lognormálnym rozdelením, čo zodpovedá empirickým vlastnostiam dát. Kopula umožnila flexibilne zachytiť závislosť medzi premennými bez nutnosti predpokladať spoločnú normalitu.
Podmienené stredné hodnoty a mediány hmotnosti rastú s výškou nelineárnym spôsobom. Lineárny model zachytáva len priemerný trend, zatiaľ čo kopulový prístup poskytuje bohatšiu informáciu o celom podmienenom rozdelení a rozdiele medzi strednou hodnotou a mediánom, čo je dôležité pri asymetrických rozdeleniach.