library(readr)
library(dplyr)
library(tidyr)
library(ggplot2)
library(base)
#library(tigerstats)
library(mosaic)
library(pROC) # for ROC/AUC
library(caret)Untitled
Predicting Incidence of Heart Disease as a Result of Lifestyle Habits
The aim of the project is to create a model from the data set that will reliably predict the incidence of heart disease based off a number of ‘lifestyle habits’. Lifestyle habits are defined as ,for the purpose of the study: smoking, drinking, and the different food consumptions.
#data import and manipulation
cvd <- read_csv("D:/python stuff/CVD_cleaned.csv")
cvd <-as.data.frame(cvd)
cvd <- cvd %>%
rename(
height = `Height_(cm)`,
weight = `Weight_(kg)`,
age = Age_Category,
Fruit = Fruit_Consumption,
Fries = FriedPotato_Consumption,
Alcohol = Alcohol_Consumption,
Smoking = Smoking_History,
Greens = Green_Vegetables_Consumption) %>% mutate(
Heart_Disease = factor(ifelse(Heart_Disease == "Yes", "1", "0"), levels = c("0","1")),
Smoking = factor(ifelse(Smoking == "Yes", 1, 0)),
Exercise = factor(Exercise))
#subset dataframe
lifestyle_df <- cvd[,c(3,4,15:19)]The dataset has 19 variables with a sample size of 308854. There is strong class imbalance between the Heart Disease variable with ‘0’ accounting for ~92% of the dataset.
## Data Quality Check + +`{r message=FALSE, warning=FALSE} +# Missingness by variable +
sum(is.na(cvd))[1] 0
lifestyle_df |> count(Heart_Disease) |> mutate(Proportion = round(n / sum(n), 3)) Heart_Disease n Proportion
1 0 283883 0.919
2 1 24971 0.081
ggplot(lifestyle_df, aes(x = Heart_Disease, fill = Heart_Disease)) + geom_bar() +
geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.5) +
labs(title = "Heart Disease Class Distribution", x = "Heart Disease", y = "Count") +
scale_y_continuous(labels = scales::label_comma())+
theme_bw() +
theme(legend.position = "none") #data exploration
head(cvd) General_Health Checkup Exercise Heart_Disease Skin_Cancer
1 Poor Within the past 2 years No 0 No
2 Very Good Within the past year No 1 No
3 Very Good Within the past year Yes 0 No
4 Poor Within the past year Yes 1 No
5 Good Within the past year No 0 No
6 Good Within the past year No 0 No
Other_Cancer Depression Diabetes Arthritis Sex age height weight BMI
1 No No No Yes Female 70-74 150 32.66 14.54
2 No No Yes No Female 70-74 165 77.11 28.29
3 No No Yes No Female 60-64 163 88.45 33.47
4 No No Yes No Male 75-79 180 93.44 28.73
5 No No No No Male 80+ 191 88.45 24.37
6 No Yes No Yes Male 60-64 183 154.22 46.11
Smoking Alcohol Fruit Greens Fries
1 1 0 30 16 12
2 0 0 30 0 4
3 0 4 12 3 16
4 0 0 30 30 8
5 1 0 8 4 0
6 0 0 12 12 12
str(cvd)'data.frame': 308854 obs. of 19 variables:
$ General_Health: chr "Poor" "Very Good" "Very Good" "Poor" ...
$ Checkup : chr "Within the past 2 years" "Within the past year" "Within the past year" "Within the past year" ...
$ Exercise : Factor w/ 2 levels "No","Yes": 1 1 2 2 1 1 2 2 1 1 ...
$ Heart_Disease : Factor w/ 2 levels "0","1": 1 2 1 2 1 1 2 1 1 1 ...
$ Skin_Cancer : chr "No" "No" "No" "No" ...
$ Other_Cancer : chr "No" "No" "No" "No" ...
$ Depression : chr "No" "No" "No" "No" ...
$ Diabetes : chr "No" "Yes" "Yes" "Yes" ...
$ Arthritis : chr "Yes" "No" "No" "No" ...
$ Sex : chr "Female" "Female" "Female" "Male" ...
$ age : chr "70-74" "70-74" "60-64" "75-79" ...
$ height : num 150 165 163 180 191 183 175 165 163 163 ...
$ weight : num 32.7 77.1 88.5 93.4 88.5 ...
$ BMI : num 14.5 28.3 33.5 28.7 24.4 ...
$ Smoking : Factor w/ 2 levels "0","1": 2 1 1 1 2 1 2 2 2 1 ...
$ Alcohol : num 0 0 4 0 0 0 0 3 0 0 ...
$ Fruit : num 30 30 12 30 8 12 16 30 12 12 ...
$ Greens : num 16 0 3 30 4 12 8 8 12 12 ...
$ Fries : num 12 4 16 8 0 12 0 8 4 1 ...
life_long <- lifestyle_df %>%
pivot_longer(
cols = c(Greens, Fruit,Fries,Alcohol),
names_to = "Food",
values_to = "Value")
ggplot(life_long,aes(Heart_Disease,y=Value, fill = Food=='Alcohol'))+
geom_col(position = "dodge")+
theme_minimal()+
scale_fill_manual(
values = c(`0` = "#440154",
`1` = "#FDE725"))Warning: No shared levels found between `names(values)` of the manual scale and the
data's fill values.
No shared levels found between `names(values)` of the manual scale and the
data's fill values.
life_long %>% select(Heart_Disease,Smoking,Food,Value) %>% group_by(Heart_Disease,Food) %>% summarise(n = sum(Value)) %>%
ggplot(aes(Food,n, fill=Heart_Disease))+
geom_col( position = 'dodge')+
scale_y_continuous(labels = scales::label_comma())+
theme_bw()`summarise()` has grouped output by 'Heart_Disease'. You can override using the
`.groups` argument.
ggplot(life_long) +
aes(x = Food, y = Value, fill = Heart_Disease) +
geom_col() +
scale_fill_hue(direction = 1) +
theme_minimal() +
facet_wrap(vars(Heart_Disease), scales = "free_y")#life_long %>% select(Heart_Disease,Smoking) %>% filter(Heart_Disease=='1') %>%
# ggplot(life_long,aes(Heart_Disease,fill=Smoking))+
# geom_bar(position = 'dodge')+facet_grid(~Heart_Disease)# Smoking x Heart Disease
ggplot(life_long) +
aes(x = Heart_Disease, fill = Smoking) +
geom_bar(aes(y = after_stat(count / sum(count))), position = "dodge2") +
scale_fill_manual(values = c(`0` = "#440154", `1` = "#FDE725")) +
labs(title = "Smoking x Heart Disease", y = "Proportion") +
theme_bw() +
facet_wrap(vars(Heart_Disease), scales = "free")# Exercise x Heart Disease
ggplot(life_long) +
aes(x = Heart_Disease, fill = Exercise) +
geom_bar(aes(y = after_stat(count / sum(count))), position = "dodge2") +
scale_fill_manual(values = c(No = "#440154", Yes = "#FDE725")) +
labs(title = "Exercise x Heart Disease", y = "Proportion") +
theme_bw() +
facet_wrap(vars(Heart_Disease), scales = "free")From the graphs above, we can see that smoking is much higher represented in the heart disease group. Inversely, given no heart disease ‘no smoking’ represents much higher proportion of the population
A different trend is observed for exercise such that while exercise is in higher proportion in both Heart Disease populations, it is much more likely for those who do not have heart disease to indicate that they do exercise.
A logistic regression to determine what variables are significant predictors of heart disease diagnosis
# compute class weights to correct for 92/8 imbalance
class_counts <- table(lifestyle_df$Heart_Disease)
class_weights <- 1 / class_counts[lifestyle_df$Heart_Disease]
#class_weights
life_lm <- glm(Heart_Disease ~ ., data = lifestyle_df,
family = binomial());summary(life_lm)
Call:
glm(formula = Heart_Disease ~ ., family = binomial(), data = lifestyle_df)
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -2.2029434 0.0173526 -126.952 < 2e-16 ***
ExerciseYes -0.6219205 0.0144355 -43.083 < 2e-16 ***
Smoking1 0.7679578 0.0136314 56.337 < 2e-16 ***
Alcohol -0.0187079 0.0009083 -20.596 < 2e-16 ***
Fruit 0.0002526 0.0002857 0.884 0.377
Greens -0.0022513 0.0004931 -4.566 4.98e-06 ***
Fries -0.0070181 0.0008540 -8.218 < 2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 173478 on 308853 degrees of freedom
Residual deviance: 167313 on 308847 degrees of freedom
AIC: 167327
Number of Fisher Scoring iterations: 5
life_lm_reduced <- glm(Heart_Disease ~ . - Fruit, data = lifestyle_df,
family = binomial())
anova(life_lm_reduced, life_lm, test = "Chisq") # no leading +Analysis of Deviance Table
Model 1: Heart_Disease ~ (Exercise + Smoking + Alcohol + Fruit + Greens +
Fries) - Fruit
Model 2: Heart_Disease ~ Exercise + Smoking + Alcohol + Fruit + Greens +
Fries
Resid. Df Resid. Dev Df Deviance Pr(>Chi)
1 308848 167313
2 308847 167313 1 0.78005 0.3771
cbind(minus_fruit = AIC(life_lm_reduced), with_fruit = AIC(life_lm)) minus_fruit with_fruit
[1,] 167325.4 167326.6
round(exp(cbind(OR = coef(life_lm_reduced),
confint.default(life_lm_reduced))), 3) OR 2.5 % 97.5 %
(Intercept) 0.111 0.108 0.115
ExerciseYes 0.538 0.523 0.553
Smoking1 2.153 2.097 2.212
Alcohol 0.981 0.980 0.983
Greens 0.998 0.997 0.999
Fries 0.993 0.991 0.995
#main effects
lifestyle_df$Heart_Disease<-relevel(lifestyle_df$Heart_Disease, ref = "0")
life_unweight<-glm(Heart_Disease ~., data = lifestyle_df, family = binomial(link = "logit"))
summary(life_lm)
Call:
glm(formula = Heart_Disease ~ ., family = binomial(), data = lifestyle_df)
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -2.2029434 0.0173526 -126.952 < 2e-16 ***
ExerciseYes -0.6219205 0.0144355 -43.083 < 2e-16 ***
Smoking1 0.7679578 0.0136314 56.337 < 2e-16 ***
Alcohol -0.0187079 0.0009083 -20.596 < 2e-16 ***
Fruit 0.0002526 0.0002857 0.884 0.377
Greens -0.0022513 0.0004931 -4.566 4.98e-06 ***
Fries -0.0070181 0.0008540 -8.218 < 2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 173478 on 308853 degrees of freedom
Residual deviance: 167313 on 308847 degrees of freedom
AIC: 167327
Number of Fisher Scoring iterations: 5
round(exp(cbind(OR = coef(life_lm), confint.default(life_lm))),3) OR 2.5 % 97.5 %
(Intercept) 0.110 0.107 0.114
ExerciseYes 0.537 0.522 0.552
Smoking1 2.155 2.099 2.214
Alcohol 0.981 0.980 0.983
Fruit 1.000 1.000 1.001
Greens 0.998 0.997 0.999
Fries 0.993 0.991 0.995
library(broom)
aug_df <- augment(life_lm_reduced, type.predict = "response", type.residuals = "deviance")
ggplot(aug_df, aes(x = .fitted, y = .resid)) +
geom_point(alpha = 0.1) +
geom_smooth(se = FALSE, colour = "#440154") +
geom_hline(yintercept = 0, linetype = "dashed", colour = "grey50") +
labs(title = "Deviance residuals vs fitted values",
x = "Fitted probability", y = "Deviance residual") +
theme_bw()`geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'
unweight_df <- augment(life_unweight , type.predict = "response", type.residuals = "deviance")
ggplot(unweight_df, aes(x = .fitted, y = .resid)) +
geom_point(alpha = 0.1) +
geom_smooth(se = FALSE, colour = "#440154") +
geom_hline(yintercept = 0, linetype = "dashed", colour = "grey50") +
labs(title = "Deviance residuals vs fitted values",
x = "Fitted probability", y = "Deviance residual") +
theme_bw()`geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'
The main effects model indicates that only fruit consumption is not a statistical significant covariate, and the model could likely be improved by removing it. Smoking has a high odds of heart disease with (~2.15 OR) . Surprisingly, Fruit, Greens and Fries Consumption shows effectively little to no difference in odds of a positive heart disease (1.0, .99, .99,1.00) and the confidence intervals are very close to 1. Alcohol consumption indicates a 2.2% decrease in odds for every 1 extra alcoholic drink taken. Only exercise seems to have a protective effect with a 0.53 (0.980, 0.983) odds of a heart attack compared to those that don’t exercise. The seeming protective effects of fries consumption on heart-disease outcome should be investigated as there may be confounding variable or it may indicate that given a heart disease diagnoses may reduce alcohol and fries intake.
life_lm_reduced <- glm(Heart_Disease ~ . - Fruit, data = lifestyle_df, family = binomial())
+ anova(life_lm_reduced, life_lm, test = 'Chisq') Resid. Df Resid. Dev Df Deviance Pr(>Chi)
1 308848 167313.4 NA NA NA
2 308847 167312.6 1 0.780054 0.3771246
#comparing AIC
cbind(minus_fruit=AIC(life_lm_reduced),with_fruit=AIC(life_lm)) minus_fruit with_fruit
[1,] 167325.4 167326.6
Removing fruit from the main model improved the model’s AIC score and comparing the models with a chi-square test indicates that they are not statistically different.
life_lm_interact <- glm(Heart_Disease ~ .^2, data = lifestyle_df,
family = binomial())
summary(life_lm_interact)
Call:
glm(formula = Heart_Disease ~ .^2, family = binomial(), data = lifestyle_df)
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -2.216e+00 2.971e-02 -74.588 < 2e-16 ***
ExerciseYes -6.155e-01 3.215e-02 -19.146 < 2e-16 ***
Smoking1 7.343e-01 3.100e-02 23.689 < 2e-16 ***
Alcohol -1.821e-02 2.647e-03 -6.881 5.96e-12 ***
Fruit 1.847e-03 6.766e-04 2.730 0.006326 **
Greens 2.094e-03 1.248e-03 1.679 0.093216 .
Fries -1.388e-02 2.085e-03 -6.661 2.72e-11 ***
ExerciseYes:Smoking1 1.225e-01 2.950e-02 4.153 3.28e-05 ***
ExerciseYes:Alcohol -2.903e-04 2.031e-03 -0.143 0.886324
ExerciseYes:Fruit -1.660e-03 6.225e-04 -2.667 0.007653 **
ExerciseYes:Greens -2.718e-03 1.073e-03 -2.534 0.011277 *
ExerciseYes:Fries 2.169e-05 1.733e-03 0.013 0.990012
Smoking1:Alcohol -4.690e-04 1.979e-03 -0.237 0.812627
Smoking1:Fruit -3.857e-04 5.771e-04 -0.668 0.503975
Smoking1:Greens -4.616e-03 9.946e-04 -4.641 3.47e-06 ***
Smoking1:Fries 5.149e-03 1.769e-03 2.910 0.003614 **
Alcohol:Fruit -1.142e-05 4.043e-05 -0.283 0.777489
Alcohol:Greens 4.452e-05 6.715e-05 0.663 0.507308
Alcohol:Fries -3.320e-05 1.126e-04 -0.295 0.768028
Fruit:Greens -2.883e-05 1.639e-05 -1.759 0.078629 .
Fruit:Fries 4.453e-05 3.087e-05 1.443 0.149151
Greens:Fries 1.191e-04 3.229e-05 3.687 0.000227 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 173478 on 308853 degrees of freedom
Residual deviance: 167229 on 308832 degrees of freedom
AIC: 167273
Number of Fisher Scoring iterations: 5
anova(life_lm_reduced, life_lm_interact, test = "Chisq")Analysis of Deviance Table
Model 1: Heart_Disease ~ (Exercise + Smoking + Alcohol + Fruit + Greens +
Fries) - Fruit
Model 2: Heart_Disease ~ (Exercise + Smoking + Alcohol + Fruit + Greens +
Fries)^2
Resid. Df Resid. Dev Df Deviance Pr(>Chi)
1 308848 167313
2 308832 167229 16 84.1 3.01e-11 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
cbind(AIC(life_lm_reduced), AIC(life_lm_interact)) [,1] [,2]
[1,] 167325.4 167273.3
exp(cbind(OR = coef(life_lm), confint.default(life_lm))) OR 2.5 % 97.5 %
(Intercept) 0.1104775 0.1067833 0.1142995
ExerciseYes 0.5369123 0.5219343 0.5523202
Smoking1 2.1553601 2.0985377 2.2137211
Alcohol 0.9814660 0.9797203 0.9832148
Fruit 1.0002526 0.9996927 1.0008128
Greens 0.9977512 0.9967874 0.9987159
Fries 0.9930065 0.9913457 0.9946700
test<-glm(Heart_Disease~ ., data = cvd, family = binomial,weights = class_weights); summary(test)
Call:
glm(formula = Heart_Disease ~ ., family = binomial, data = cvd,
weights = class_weights)
Coefficients:
Estimate Std. Error
(Intercept) -4.121e+00 1.239e+02
General_HealthFair 1.753e+00 7.526e+00
General_HealthGood 1.069e+00 6.664e+00
General_HealthPoor 2.321e+00 9.766e+00
General_HealthVery Good 4.994e-01 6.701e+00
CheckupNever 1.256e-01 3.339e+01
CheckupWithin the past 2 years 1.693e-01 1.293e+01
CheckupWithin the past 5 years 7.563e-02 1.512e+01
CheckupWithin the past year 4.870e-01 1.166e+01
ExerciseYes -3.788e-02 4.163e+00
Skin_CancerYes 1.108e-01 5.207e+00
Other_CancerYes 1.204e-01 5.126e+00
DepressionYes 2.980e-01 4.546e+00
DiabetesNo, pre-diabetes or borderline diabetes 1.932e-01 1.033e+01
DiabetesYes 5.910e-01 4.534e+00
DiabetesYes, but female told only during pregnancy 1.083e-01 2.304e+01
ArthritisYes 3.018e-01 3.729e+00
SexMale 8.989e-01 5.126e+00
age25-29 2.825e-01 2.349e+01
age30-34 5.729e-01 2.127e+01
age35-39 6.839e-01 2.040e+01
age40-44 9.876e-01 1.956e+01
age45-49 1.369e+00 1.902e+01
age50-54 1.674e+00 1.848e+01
age55-59 2.030e+00 1.817e+01
age60-64 2.283e+00 1.800e+01
age65-69 2.538e+00 1.796e+01
age70-74 2.802e+00 1.800e+01
age75-79 3.060e+00 1.827e+01
age80+ 3.402e+00 1.823e+01
height -3.817e-03 7.211e-01
weight -2.277e-03 6.702e-01
BMI 9.053e-03 1.931e+00
Smoking1 4.329e-01 3.580e+00
Alcohol -9.483e-03 2.145e-01
Fruit 9.184e-05 7.446e-02
Greens 1.365e-03 1.277e-01
Fries -4.555e-04 2.128e-01
z value Pr(>|z|)
(Intercept) -0.033 0.973
General_HealthFair 0.233 0.816
General_HealthGood 0.160 0.873
General_HealthPoor 0.238 0.812
General_HealthVery Good 0.075 0.941
CheckupNever 0.004 0.997
CheckupWithin the past 2 years 0.013 0.990
CheckupWithin the past 5 years 0.005 0.996
CheckupWithin the past year 0.042 0.967
ExerciseYes -0.009 0.993
Skin_CancerYes 0.021 0.983
Other_CancerYes 0.023 0.981
DepressionYes 0.066 0.948
DiabetesNo, pre-diabetes or borderline diabetes 0.019 0.985
DiabetesYes 0.130 0.896
DiabetesYes, but female told only during pregnancy 0.005 0.996
ArthritisYes 0.081 0.936
SexMale 0.175 0.861
age25-29 0.012 0.990
age30-34 0.027 0.979
age35-39 0.034 0.973
age40-44 0.050 0.960
age45-49 0.072 0.943
age50-54 0.091 0.928
age55-59 0.112 0.911
age60-64 0.127 0.899
age65-69 0.141 0.888
age70-74 0.156 0.876
age75-79 0.168 0.867
age80+ 0.187 0.852
height -0.005 0.996
weight -0.003 0.997
BMI 0.005 0.996
Smoking1 0.121 0.904
Alcohol -0.044 0.965
Fruit 0.001 0.999
Greens 0.011 0.991
Fries -0.002 0.998
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 2.7726 on 308853 degrees of freedom
Residual deviance: 1.9947 on 308816 degrees of freedom
AIC: 76
Number of Fisher Scoring iterations: 5
# KS test on n=300k is useless — use visual inspection instead
ggplot(lifestyle_df, aes(sample = Fruit)) +
stat_qq() +
stat_qq_line(colour = "steelblue") +
labs(title = "Q-Q plot: Fruit consumption") +
theme_bw()# t-tests are still fine — keep these
var.test(lifestyle_df$Fruit ~ lifestyle_df$Heart_Disease)
F test to compare two variances
data: lifestyle_df$Fruit by lifestyle_df$Heart_Disease
F = 1.0387, num df = 283882, denom df = 24970, p-value = 5.209e-05
alternative hypothesis: true ratio of variances is not equal to 1
95 percent confidence interval:
1.019821 1.057831
sample estimates:
ratio of variances
1.038726
t.test(lifestyle_df$Fruit ~ lifestyle_df$Heart_Disease,
mu = 0, var.equal = FALSE, alternative = "greater")
Welch Two Sample t-test
data: lifestyle_df$Fruit by lifestyle_df$Heart_Disease
t = 11.326, df = 29720, p-value < 2.2e-16
alternative hypothesis: true difference in means between group 0 and group 1 is greater than 0
95 percent confidence interval:
1.564253 Inf
sample estimates:
mean in group 0 mean in group 1
29.98316 28.15314
var.test(lifestyle_df$Fries ~ lifestyle_df$Heart_Disease)
F test to compare two variances
data: lifestyle_df$Fries by lifestyle_df$Heart_Disease
F = 1.0195, num df = 283882, denom df = 24970, p-value = 0.0397
alternative hypothesis: true ratio of variances is not equal to 1
95 percent confidence interval:
1.000910 1.038215
sample estimates:
ratio of variances
1.019464
t.test(lifestyle_df$Fries ~ lifestyle_df$Heart_Disease,
mu = 0, var.equal = FALSE, alternative = "two.sided")
Welch Two Sample t-test
data: lifestyle_df$Fries by lifestyle_df$Heart_Disease
t = 5.1697, df = 29628, p-value = 2.361e-07
alternative hypothesis: true difference in means between group 0 and group 1 is not equal to 0
95 percent confidence interval:
0.1803644 0.4006544
sample estimates:
mean in group 0 mean in group 1
6.320104 6.029594
lifestyle_df$Heart_Disease<-relevel(lifestyle_df$Heart_Disease, ref = "0")
cvd$Heart_Disease<-ifelse(cvd$Heart_Disease=="Yes","1","0")
fries_lm<-glm( as.factor(Heart_Disease) ~age*Fruit, data = cvd, family = binomial(link = "logit"))Warning: glm.fit: algorithm did not converge
summary(fries_lm)
Call:
glm(formula = as.factor(Heart_Disease) ~ age * Fruit, family = binomial(link = "logit"),
data = cvd)
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -2.657e+01 3.874e+03 -0.007 0.995
age25-29 -9.919e-21 5.803e+03 0.000 1.000
age30-34 -1.002e-20 5.595e+03 0.000 1.000
age35-39 -1.006e-20 5.485e+03 0.000 1.000
age40-44 -1.009e-20 5.419e+03 0.000 1.000
age45-49 -1.009e-20 5.424e+03 0.000 1.000
age50-54 -1.009e-20 5.192e+03 0.000 1.000
age55-59 -1.003e-20 5.078e+03 0.000 1.000
age60-64 -9.971e-21 4.951e+03 0.000 1.000
age65-69 -9.956e-21 4.952e+03 0.000 1.000
age70-74 -6.906e-12 5.029e+03 0.000 1.000
age75-79 -1.010e-20 5.556e+03 0.000 1.000
age80+ -1.016e-20 5.532e+03 0.000 1.000
Fruit -1.601e-22 1.075e+02 0.000 1.000
age25-29:Fruit 1.622e-22 1.609e+02 0.000 1.000
age30-34:Fruit 1.646e-22 1.513e+02 0.000 1.000
age35-39:Fruit 1.654e-22 1.456e+02 0.000 1.000
age40-44:Fruit 1.658e-22 1.438e+02 0.000 1.000
age45-49:Fruit 1.662e-22 1.455e+02 0.000 1.000
age50-54:Fruit 1.664e-22 1.400e+02 0.000 1.000
age55-59:Fruit 1.652e-22 1.371e+02 0.000 1.000
age60-64:Fruit 1.643e-22 1.333e+02 0.000 1.000
age65-69:Fruit 1.642e-22 1.328e+02 0.000 1.000
age70-74:Fruit 3.526e-15 1.353e+02 0.000 1.000
age75-79:Fruit 1.664e-22 1.467e+02 0.000 1.000
age80+:Fruit 1.677e-22 1.455e+02 0.000 1.000
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 0.0000e+00 on 308853 degrees of freedom
Residual deviance: 1.7918e-06 on 308828 degrees of freedom
AIC: 52
Number of Fisher Scoring iterations: 25
ggplot(data = lifestyle_df,aes(Fries))+
geom_density() ggplot(data=cvd,aes(age,Fries,fill=age))+
geom_col()+
facet_grid(rows = vars(Heart_Disease))# soime visualizations
library(ggplot2)
library(patchwork)Warning: package 'patchwork' was built under R version 4.5.3
p1 <- ggplot(lifestyle_df, aes(x = Fries, fill = factor(Heart_Disease))) +
geom_histogram(bins = 40, position = "fill") +
labs(title = "Fries: Proportion with Heart Disease by Value",
x = "Fries (servings/month?)", y = "Proportion", fill = "Heart Disease")
p2 <- ggplot(lifestyle_df, aes(x = Alcohol, fill = factor(Heart_Disease))) +
geom_histogram(bins = 30, position = "fill") +
labs(title = "Alcohol: Proportion with Heart Disease by Value",
x = "Alcohol (drinks/month?)", y = "Proportion", fill = "Heart Disease")
p1 / p2#model test
# train/test split
train_idx <- createDataPartition(lifestyle_df$Heart_Disease, p = 0.8, list = FALSE)
train_df <- lifestyle_df[ train_idx, ]
test_df <- lifestyle_df[-train_idx, ]
train_weights <- class_weights[train_idx]
final_model <- glm(Heart_Disease ~ . - Fruit, data = train_df,
family = binomial())
# predictions on test set
pred_prob <- predict(final_model, newdata = test_df, type = "response")
pred_class <- factor(ifelse(pred_prob > 0.5, "1", "0"), levels = c("0", "1"))
# confusion matrix
confusionMatrix(pred_class, test_df$Heart_Disease, positive = "1")Confusion Matrix and Statistics
Reference
Prediction 0 1
0 56776 4994
1 0 0
Accuracy : 0.9192
95% CI : (0.917, 0.9213)
No Information Rate : 0.9192
P-Value [Acc > NIR] : 0.5038
Kappa : 0
Mcnemar's Test P-Value : <2e-16
Sensitivity : 0.00000
Specificity : 1.00000
Pos Pred Value : NaN
Neg Pred Value : 0.91915
Prevalence : 0.08085
Detection Rate : 0.00000
Detection Prevalence : 0.00000
Balanced Accuracy : 0.50000
'Positive' Class : 1
# ROC / AUC
roc_obj <- roc(test_df$Heart_Disease, pred_prob)Setting levels: control = 0, case = 1
Setting direction: controls < cases
auc(roc_obj)Area under the curve: 0.6479
ggroc(roc_obj, colour = "#440154") +
geom_abline(slope = 1, intercept = 1, linetype = "dashed", colour = "grey60") +
labs(title = paste0("ROC curve (AUC = ", round(auc(roc_obj), 3), ")"),
x = "Specificity", y = "Sensitivity") +
theme_bw()library(broom)
or_df <- tidy(life_lm_reduced, exponentiate = TRUE, conf.int = TRUE) %>%
filter(term != "(Intercept)")
ggplot(or_df, aes(x = estimate, y = reorder(term, estimate))) +
geom_point(size = 3, colour = "#440154") +
geom_errorbarh(aes(xmin = conf.low, xmax = conf.high), height = 0.2) +
geom_vline(xintercept = 1, linetype = "dashed", colour = "grey50") +
labs(title = "Odds ratios (95% CI)", x = "Odds ratio", y = NULL) +
theme_bw()Warning: `geom_errorbarh()` was deprecated in ggplot2 4.0.0.
ℹ Please use the `orientation` argument of `geom_errorbar()` instead.
`height` was translated to `width`.