Jako projekt wybrałem analizę danych, który jest zbiorem podstawowych informacji dotyczących biologicznych sygnałów zdrowotnych. Celem jest określenie czy określona osoba jest palaczem czy nie za pomocą biosygnałów.
Poniżej zaprezentowane są dane na których będę pracował. Zbiór zawiera 26 zmiennych na temat poszczególnych osób, palących i niepalących.
df <- read.csv2("smoking.csv", sep = ",", header = TRUE)
head(df, 2)
## ID gender age height.cm. weight.kg. waist.cm. eyesight.left. eyesight.right.
## 1 0 F 40 155 60 81.3 1.2 1.0
## 2 1 F 40 160 60 81.0 0.8 0.6
## hearing.left. hearing.right. systolic relaxation fasting.blood.sugar
## 1 1.0 1.0 114.0 73.0 94.0
## 2 1.0 1.0 119.0 70.0 130.0
## Cholesterol triglyceride HDL LDL hemoglobin Urine.protein serum.creatinine
## 1 215.0 82.0 73.0 126.0 12.9 1.0 0.7
## 2 192.0 115.0 42.0 127.0 12.7 1.0 0.6
## AST ALT Gtp oral dental.caries tartar smoking
## 1 18.0 19.0 27.0 Y 0 Y 0
## 2 22.0 19.0 18.0 Y 0 Y 0
Ładowanie potrzebnych bibliotek:
library(tree)
require(caret)
library(nnet)
library(corrplot)
library(reshape2)
library(dplyr)
set.seed(21)
Usuwanie pustych wierszy:
df <- df[complete.cases(df), ]
Zamiana kolumny gender, oral oraz tartar z warości tekstowych na liczbowe (0, 1):
unique_gender <- unique(as.character(df$gender))
df <- data.frame(gender = as.numeric(factor(df$gender, level = unique_gender)), df)
unique_oral <- unique(as.character(df$oral))
df <- data.frame(oral = as.numeric(factor(df$oral, level = unique_oral)), df)
unique_tartar <- unique(as.character(df$tartar))
df <- data.frame(tartar = as.numeric(factor(df$tartar, level = unique_tartar)), df)
Usunięcie zbędnych zmiennych:
df <- subset(df, select = -c(ID, oral.1, gender.1, tartar.1))
Zamiana typu kolumn z character na numeric:
df[] <- lapply(df, function(x) as.numeric(as.character(x)))
sapply(df, class)
W pierwszej kolejności została zbadana korelacja pomiędzy badanymi zmiennymi:
corrplot(cor(df[,c(-2)], method='spearman'),
method = "number",
type = "upper")
Jak widać zmienne nie są pomiędzy sobą mocno skorelowane. Można
dostrzec, że płeć jest najbardziej skorelowana z paleniem, ale także
jest najbardziej powiązana z resztą zmiennych. Po za tym palenie
najmocniej skorelowane jest z hemoglobiną (0,40) oraz wzrostem
(0,40).
Następnie zostały przedstawione zmienne dyskretne jako wykresy kołowe:
p1 <-df%>%
count(tartar)%>%
ggplot(aes(x = "", y = n ,fill = factor(tartar))) +
geom_col() +
coord_polar(theta = "y")+
ggtitle("Rozkład zmiennej tartar") +
theme(plot.title = element_text(hjust = 0.5))
p2 <- df%>%
count(oral)%>%
ggplot(aes(x = "", y = n ,fill = factor(oral))) +
geom_col() +
coord_polar(theta = "y")+
ggtitle("Rozkład zmiennej oral") +
theme(plot.title = element_text(hjust = 0.5))
p3 <- df%>%
count(gender)%>%
ggplot(aes(x = "", y = n ,fill = factor(gender))) +
geom_col() +
coord_polar(theta = "y")+
ggtitle("Rozkład zmiennej gender") +
theme(plot.title = element_text(hjust = 0.5))
p4 <- df%>%
count(smoking)%>%
ggplot(aes(x = "", y = n ,fill = factor(smoking))) +
geom_col() +
coord_polar(theta = "y")+
ggtitle("Rozkład zmiennej smoking") +
theme(plot.title = element_text(hjust = 0.5))
p5 <- df%>%
count(dental.caries)%>%
ggplot(aes(x = "", y = n ,fill = factor(dental.caries))) +
geom_col() +
coord_polar(theta = "y")+
ggtitle("Rozkład zmiennej dental.caries") +
theme(plot.title = element_text(hjust = 0.5))
Można z nich wywnioskować, że zmienna oral (ocena stanu
ustnego) jest bezużyteczna w naszym zbiorze danych i możemy ją
usunąć:
df <- subset(df, select = -c(oral))
Następnie zostały przedstawione pozostałe zmienne jako rozkłady dla osób palących i niepalących:
p5 <- df %>%
ggplot( aes(x = factor(smoking), y = age , fill=factor(smoking))) +
geom_violin() +
ggtitle("Rozkład zmiennej wiek dla osób niepalący i palących ") +
theme(plot.title = element_text(hjust = 0.5)) +
ylab("")+
xlab("") +
theme(legend.position="none")
p6 <- df %>%
ggplot( aes(x = factor(smoking), y = height.cm. , fill=factor(smoking))) +
geom_violin() +
ggtitle("Rozkład zmiennej height.cm. dla osób niepalący i palących ") +
theme(plot.title = element_text(hjust = 0.5)) +
ylab("")+
xlab("") +
theme(legend.position="none")
p7 <- df %>%
ggplot( aes(x = factor(smoking), y = weight.kg. , fill=factor(smoking))) +
geom_violin() +
ggtitle("Rozkład zmiennej weight.kg. dla osób niepalący i palących ") +
theme(plot.title = element_text(hjust = 0.5)) +
ylab("")+
xlab("") +
theme(legend.position="none")
p8 <- df %>%
ggplot( aes(x = factor(smoking), y = waist.cm. , fill=factor(smoking))) +
geom_violin() +
ggtitle("Rozkład zmiennej waist.cm. dla osób niepalący i palących ") +
theme(plot.title = element_text(hjust = 0.5)) +
ylab("")+
xlab("") +
theme(legend.position="none")
p9 <- df %>%
ggplot( aes(x = factor(smoking), y = systolic , fill=factor(smoking))) +
geom_violin() +
ggtitle("Rozkład zmiennej systolic dla osób niepalący i palących ") +
theme(plot.title = element_text(hjust = 0.5)) +
ylab("")+
xlab("") +
theme(legend.position="none")
p10 <- df %>%
ggplot( aes(x = factor(smoking), y = relaxation , fill=factor(smoking))) +
geom_violin() +
ggtitle("Rozkład zmiennej relaxation dla osób niepalący i palących ") +
theme(plot.title = element_text(hjust = 0.5)) +
ylab("")+
xlab("") +
theme(legend.position="none")
p11 <- df %>%
ggplot( aes(x = factor(smoking), y = fasting.blood.sugar , fill=factor(smoking))) +
geom_violin() +
ggtitle("Rozkład zmiennej fasting.blood.sugar dla osób niepalący i palących ") +
theme(plot.title = element_text(hjust = 0.5)) +
ylab("")+
xlab("") +
theme(legend.position="none")
p12 <- df %>%
ggplot( aes(x = factor(smoking), y = Cholesterol , fill=factor(smoking))) +
geom_violin() +
ggtitle("Rozkład zmiennej Cholesterol dla osób niepalący i palących ") +
theme(plot.title = element_text(hjust = 0.5)) +
ylab("")+
xlab("") +
theme(legend.position="none")
p13 <- df %>%
ggplot( aes(x = factor(smoking), y = triglyceride , fill=factor(smoking))) +
geom_violin() +
ggtitle("Rozkład zmiennej triglyceride dla osób niepalący i palących ") +
theme(plot.title = element_text(hjust = 0.5)) +
ylab("")+
xlab("") +
theme(legend.position="none")
p14 <- df %>%
ggplot( aes(x = factor(smoking), y = HDL , fill=factor(smoking))) +
geom_violin() +
ggtitle("Rozkład zmiennej HDL dla osób niepalący i palących ") +
theme(plot.title = element_text(hjust = 0.5)) +
ylab("")+
xlab("") +
theme(legend.position="none")
p15 <- df %>%
ggplot( aes(x = factor(smoking), y = LDL , fill=factor(smoking))) +
geom_violin() +
ggtitle("Rozkład zmiennej LDL dla osób niepalący i palących ") +
theme(plot.title = element_text(hjust = 0.5)) +
ylab("")+
xlab("") +
theme(legend.position="none")
p16 <- df %>%
ggplot( aes(x = factor(smoking), y = hemoglobin , fill=factor(smoking))) +
geom_violin() +
ggtitle("Rozkład zmiennej hemoglobin dla osób niepalący i palących ") +
theme(plot.title = element_text(hjust = 0.5)) +
ylab("")+
xlab("") +
theme(legend.position="none")
p17 <- df %>%
ggplot( aes(x = factor(smoking), y =AST , fill=factor(smoking))) +
geom_violin() +
ggtitle("Rozkład zmiennej AST dla osób niepalący i palących ") +
theme(plot.title = element_text(hjust = 0.5)) +
ylab("")+
xlab("") +
theme(legend.position="none")
p18 <- df %>%
ggplot( aes(x = factor(smoking), y = ALT , fill=factor(smoking))) +
geom_violin() +
ggtitle("Rozkład zmiennej ALT dla osób niepalący i palących ") +
theme(plot.title = element_text(hjust = 0.5)) +
ylab("")+
xlab("") +
theme(legend.position="none")
p19 <- df %>%
ggplot( aes(x = factor(smoking), y = Gtp , fill=factor(smoking))) +
geom_violin() +
ggtitle("Rozkład zmiennej Gtp dla osób niepalący i palących ") +
theme(plot.title = element_text(hjust = 0.5)) +
ylab("")+
xlab("") +
theme(legend.position="none")
Z wykresów można odczytać, że w przypadku zmiennych:
fasting.blood.sugar, HDL, LDL, AST, ALT, Gtp występują tak zwane
outliery, które przyjmują wartości nierealne jeżeli chodzi o tego typu
wyniki badań. Mogą one zaburzać wyniki, dlatego W celu normalizacji
danych dla powyższych zmiennych zostaną usunięte dane, które są powyżej
wybranego percentyla, a więc tylko dla bardzo małej liczby
obserwacji.
attach(df)
q_fasting.blood.sugar <- quantile(fasting.blood.sugar, probs=c( .99), na.rm = FALSE)
q_HDL <- quantile(fasting.blood.sugar, probs=c( .99), na.rm = FALSE)
q_LDL <- quantile(fasting.blood.sugar, probs=c( .999), na.rm = FALSE)
q_AST <- quantile(fasting.blood.sugar, probs=c( .85), na.rm = FALSE)
q_ALT <- quantile(fasting.blood.sugar, probs=c( .90), na.rm = FALSE)
q_Gtp <- quantile(fasting.blood.sugar, probs=c( .99), na.rm = FALSE)
df <- subset(df, df$fasting.blood.sugar < (q_fasting.blood.sugar[1]))
df <- subset(df, df$HDL < (q_HDL[1]))
df <- subset(df, df$LDL < (q_LDL[1]))
df <- subset(df, df$AST < (q_AST[1]))
df <- subset(df, df$ALT < (q_ALT[1]))
df <- subset(df, df$Gtp < (q_Gtp[1]))
Po redukcji można zobaczyć jak teraz wyglądają rozkłądy poszczegółnych zmiennych:
Jak widać po redukcji danych mocno odstających rozkłady zmiennych
zmieniły się na bardziej skoncentrowane.
Po analizie zbioru danych możesz przejść do podzielenia danych na train i test:
idx <- floor(0.75 * nrow(df))
train_ind <- sample(seq_len(nrow(df)), size = idx)
trainset <- df[train_ind, ]
test <- df[-train_ind, ]
Następnie dane zostaną zredukowane w oparciu o analizę PCA, co pozwoli na ograniczenie liczby zmiennych. Pozwoli to na zmniejszenie czasu potrzebnego na oszacowanie modelu oraz możliwe, że przyczyni się do redukcji błędów szacowanych modeli.
#df1 <- data.matrix(df)
pca <- princomp(~ tartar + gender + age + height.cm. +
weight.kg. + waist.cm. + eyesight.left. +
eyesight.right. + hearing.left. + hearing.right. +
systolic + relaxation + fasting.blood.sugar +
Cholesterol + triglyceride + HDL + LDL +
hemoglobin + Urine.protein + serum.creatinine + AST +
ALT + Gtp + dental.caries,
data = trainset, cor = T)
plot((pca$sdev)^2, type = "c")
Na podstawie powyższego wykresu można stwierdzić, że optymalna
liczba nowych zmiennych (komponentów) powinna być określona na poziomie
10. W celu dokłądniejszego określenia liczby zmiennych poniżej zostały
zaprezentowane dodatkowa analiza:
summary(pca)
## Importance of components:
## Comp.1 Comp.2 Comp.3 Comp.4 Comp.5
## Standard deviation 2.2271892 1.5566911 1.37652751 1.21827695 1.20618216
## Proportion of Variance 0.2066822 0.1009703 0.07895117 0.06184161 0.06061981
## Cumulative Proportion 0.2066822 0.3076525 0.38660363 0.44844524 0.50906505
## Comp.6 Comp.7 Comp.8 Comp.9 Comp.10
## Standard deviation 1.13779722 1.08377979 1.07459906 1.01130687 0.94828080
## Proportion of Variance 0.05394094 0.04894078 0.04811513 0.04261423 0.03746819
## Cumulative Proportion 0.56300599 0.61194676 0.66006189 0.70267613 0.74014431
## Comp.11 Comp.12 Comp.13 Comp.14 Comp.15
## Standard deviation 0.91278349 0.90673550 0.87073605 0.7973892 0.77764686
## Proportion of Variance 0.03471557 0.03425705 0.03159089 0.0264929 0.02519728
## Cumulative Proportion 0.77485988 0.80911694 0.84070782 0.8672007 0.89239799
## Comp.16 Comp.17 Comp.18 Comp.19 Comp.20
## Standard deviation 0.72636816 0.70649209 0.68324994 0.61029718 0.481687383
## Proportion of Variance 0.02198378 0.02079713 0.01945127 0.01551928 0.009667614
## Cumulative Proportion 0.91438177 0.93517890 0.95463017 0.97014945 0.979817063
## Comp.21 Comp.22 Comp.23 Comp.24
## Standard deviation 0.456534598 0.428185901 0.296435712 0.0689156225
## Proportion of Variance 0.008684327 0.007639299 0.003661422 0.0001978901
## Cumulative Proportion 0.988501389 0.996140688 0.999802110 1.0000000000
Liczba zmiennych PCA poprzez analizę cumulative proportion została określona na poziomie 11 i właśnie dla takiej liczby zostały stworzene nowe zbiory test i train:
transformed_test <- as.data.frame(predict(pca, test)[,1:12])
new_train <- as.data.frame(cbind(trainset$smoking, pca$scores[, 1:12]))
colnames(new_train)[1] <- "smoking"
W tej części zostaną stworzone modele drzew decyzyjnych dla danych pierwotnych i PCA.
Stworzenie modelu drzewa decyzyjnego dla danych pierwotnych:
drzewo <- tree(as.factor(smoking)~., data = trainset)
error <- sum(test$smoking != predict(drzewo, test, type = "class"))
relative_error <- (error/nrow(test))
round(1 - relative_error, 4)
## [1] 0.7254
Błąd dla tego modelu wynosi 0.7279, co wskazuje, że model ten w 72,79% poprawnie przewiduje czy dana osoba jest palaczem na podstawie dostępnych zmiennych.
plot(drzewo)
text(drzewo)
Stworzenie modelu drzewa decyzyjnego dla danych PCA:
drzewo2 <- tree(as.factor(smoking)~., data = new_train)
error2 <- sum(test$smoking != predict(drzewo2, transformed_test, type = "class"))
relative_error2 <- (error2/nrow(transformed_test))
round(1 - relative_error2, 4)
## [1] 0.6988
Błąd dla tego modelu wynosi 0.6988, co wskazuje, że model ten w 69,88% poprawnie przewiduje czy dana osoba jest palaczem na podstawie dostępnych zmiennych. Jest on więc gorzej dopasowany niż model dla danych pierwotnych.
plot(drzewo2)
text(drzewo2)
Na tym etapie zostaną stworzone dwa modele wykorzystujące sieci neuronowe dla danych pierwotnych oraz zreukowanych po PCA. Najpierw natomiast została stworzona funkcja optimal_size, która miała za zadanie sprawdzić dla jakiej ilości neuronów nasz model jest najlepiej dopasowany. W przypadku obu najlepiej dopasowały się dla 6 neuronów.
optimal_size <- function(dane, test1){
bledy <- list()
for (i in 2:10){
blad = 0
model = nnet(dane, class.ind(smoking), softmax = T, size = i, maxit = 1000, decay = 0.1)
blad = sum(predict(model, test1, type="class") != test$smoking)
bledy[i] <- blad
}
min_value <- min(unlist(bledy))
min_index <- match(min_value, bledy)
return(min_index)
}
# optimal_size(dane, test) wynik optymalizacji, 6 neuronóW
# optimal_size(dane2, transformed_test) dla danych PCA najlepsza wartość size to również 6
attach(trainset)
dane <- cbind(tartar, gender, age, height.cm.,
weight.kg., waist.cm., eyesight.left.,
eyesight.right., hearing.left., hearing.right.,
systolic, relaxation, fasting.blood.sugar,
Cholesterol, triglyceride, HDL, LDL,
hemoglobin, Urine.protein, serum.creatinine, AST,
ALT, Gtp, dental.caries)
model <- nnet(dane, class.ind(smoking), softmax = T, size = 6, maxit = 1000)
blad <- sum(predict(model, test, type="class")!= test$smoking)
round(1 - (blad/nrow(test)),4)
## [1] 0.7208
Dopasowanie dla danych pierwotnych wynosi ponad 72%
attach(new_train)
dane2 <- cbind(Comp.1, Comp.2, Comp.3, Comp.4, Comp.5,
Comp.6, Comp.7, Comp.8, Comp.9, Comp.10,
Comp.11, Comp.12)
model2 <- nnet(dane2, class.ind(smoking), softmax=T, size=6, maxit=1000, decay = 0.05)
blad2 <- sum(predict(model2, transformed_test, type="class")!= test$smoking)
round(1-(blad2/nrow(transformed_test)),4)
## [1] 0.7528
Dopasowanie modelu sieci neuronowych dla danych PCA wynosi ponad 75% co wsazuje, że jest to najlepszy najbardziej trafjny model spośród wszystkich badanych.
Podsumowując, w projekcie zostały zaprezentowane 4 modele
tworzone na podstawie drzewa decyzyjnego oraz sieci neuronowych z
wykorzystaniem danych zwykłych oraz przetworzonych za pomocą metody PCA.
Z analizy błędu można stwierdzić, że najlepiej dopasowanym modelem jest
ostatni badany, który wykorzystuje sieci neuronowe oraz dane PCA.
Dopasowanie dla niego wynosi ponad 75%. Jesteśmy więc w stanie
stwierdzić, że z 72% dokładnością możemy określić na podstawie
“sygnałów” ciała czy dana osoba jest palaczem.
Drzewa
decyzyjne z kolei pokazały, że jednym z kluczowych elementów branych pod
uwagę jest płeć. Wynika to z tego, że statystycznie więcej mężczyzn jest
palaczami, co zwiększa prawdopodobieńśtwo, że właśnie osoba z tej grupy
będzie palaczem. Jeżeli chcialibyśmy rzeczywiście działać tylko i
wyłącznie na danych dotyczących organizmu powinniśmy pozbyć się
zmiennych takich jak: wzrost, płeć, wiek itp., które bardziej określają
cechy osób.