Doyun-lab

[Project] 마취 중 저혈압 환자 예측 본문

Project/On campus

[Project] 마취 중 저혈압 환자 예측

Doyun+ 2021. 6. 23. 00:28

Subject : Prediction of Patients with Low Blood Pressure during Anesthesia

Language : R

Data : ‘수술 중 마취한 환자의 혈압과 정보’ 데이터

Model : Random Forest, SVM, Boosting

1. Data parsing & preprocessing

# total 이라는 리스트에 모든 엑셀 파일을 불러와 저장
setwd("C:\\r_temp\\homework")
total <- lapply(Sys.glob("homework*.csv"), read.csv, stringsAsFactors = F)

# total 리스트 요소들을 하나씩 뽑아 전처리 하는 과정
for(i in 1:length(total)){
total[[i]]$date1 <- strptime(total[[i]]$date1, "%Y-%m-%d %H:%M") # date를 POSIxt 형으로 변환

# 집도의, 마취의, 신체적상태만 따로 추출하여 새로운 변수에 저장
x <- subset(total[[i]], total[[i]]$item == "집도과1")
y <- subset(total[[i]], total[[i]]$item == "마취의1")
z <-subset(total[[i]], total[[i]]$item == "신체적상태")

# 마취시작전 시간대 데이터만 추출하고 중복된 값 제거
total[[i]] <- subset(total[[i]], total[[i]]$date1 <= total[[i]]$date1[14])
total[[i]] <- total[[i]][-which(duplicated(total[[i]]$item)),]

# 마취시작전 시간대 데이터에 집도의, 마취의, 신체적 상태 행으로 붙이기
total[[i]] <- rbind(total[[i]], x, y, z)

# class가 열 2개로 이루어져 있는 데이터 1개 열 제거
total[[i]]$class.1 <- NULL

# 필요없는 열 빼고 다시 저장
total[[i]] <- total[[i]][, -c(1,7,8,9,10,11)]

# value1 값이 공백인 것 빼고 불러오기
total[[i]] <- total[[i]][!(total[[i]]$value1 == ""), ]

# 마취일반정보와 V/S 정보만 빼기
total[[i]] <- subset(total[[i]], total[[i]]$group == "V/S" | total[[i]]$group == "마취일반정보")
}
  • 파일을 불러와 리스트에 저장 후, 전처리 진행

- (1) date1 변수를 POSIxt형으로 변환해 줍니다.

- (2) 집도과1, 마취의1, 신체적상태는 subset 하여 변수에 따로 저장해줍니다.

- (3) 마취시작 전 시간대 데이터만 추출한 후, 중복된 행을 제거합니다.

- (4) [단계 (3)] 데이터에 [단계 (2)]에서 추출한 변수들을 rbind 하여 행을 추가해줍니다.

- (5) class가 열이 2개로 이루어져 있는 열을 하나로 만들어 줍니다.

- (6) 분석에 필요하지 않은 열을 빼줍니다.

- (7) value1 변수에 공백으로 이루어져 있는 것을 제외하고 다시 저장해줍니다.

- (8) 마취일반정보와 V/S 정보만 빼서 저장을 마칩니다.

 

# 행과 열을 바꿔주는 작업
for(i in 1:length(total)){

	# class 값을 따로 저장
	class1 <- total[[i]]$class[1]

	# item과 value1 값만 빼기
	total[[i]] <- total[[i]][,c(3, 4)]

	# 행과 열 바꿔주기
	total[[i]] <- t(total[[i]])

	# 데이터프레임으로 형 변환
	total[[i]] <- as.data.frame(total[[i]])

	# item 행을 열의 이름으로 바꿔주기
	colnames(total[[i]]) <- unlist(total[[i]][row.names(total[[i]]) == "item",])
	
    # item 값들 옆에 class 행 붙여주기
	total[[i]] <- cbind(total[[i]], class1)
}

 

  • Before Modeling, 행과 열 바꾸기

- (1) 행과 열을 바꾸기 전에 class 값을 따로 저장해줍니다.

- (2) item, value1 값만 빼서 저장해줍니다.

- (3) 행과 열을 바꿔줍니다. [ t() 함수 사용 ]

- (4) [단계 (3)]을 거친 데이터를 데이터프레임 형으로 변환해줍니다.

- (5) item 행을 [단계 (4)]의 데이터프레임 열 이름으로 바꿔줍니다.

- (6) 모든 과정을 마친 후, class 값을 새로운 열으로 붙여줍니다.

# 312명의 사람 데이터 합쳐주기
library(dplyr)
result <- total[[6]]
for(i in 1:length(total)) {
result <- bind_rows(result, total[[i]][2,])
}

# 필요없는 행과 중복된 행 제거
result <- result[-1,]
result <- unique(result)

# NA 값이 너무 많은 열 13 부터 열 22까지 지우기
result <- result[,-c(13:22)]

# 열 1 ~ 8 가 모두 NA 인 행 지우기
result <- result[(rowSums(is.na(result)) < 8),]

# 신체적 상태 숫자로 표현 ( 1 ~ 3)
result$신체적상태 <- substr(result$신체적상태, 1, 1)

# 형 변환
result[,1:8] <- as.numeric(unlist(result[,1:8]))
result$집도과1 <- as.factor(result$집도과1)
result$마취의1 <- as.factor(result$마취의1)
result$신체적상태 <- as.factor(result$신체적상태)
result$class1 <- as.factor(result$class1)
  • 리스트 요소를 모두 합쳐 데이터 프레임 생성 (bind_rows 함수 이용)
  • 만들어진 데이터 프레임을 다시 한 번 전처리

- (1) 필요없는 행과 중복되는 행을 제거해줍니다.

- (2) NA 값이 너무 많이 존재하는 열13 ~ 열22까지 지워줍니다

- (3) [단계 (2)]를 끝낸 데이터에서 NA 값이 8개 이상 존재하는 행을 지워줍니다.

- (4) “신체적상태”를 1 ~ 3으로 표현해줍니다

- (5) 모든 열들을 모델링하기 위해 형을 변환해줍니다.

 

2. Modeling

# train / test set 나누기
set.seed(123)
index <- sample.int(nrow(result), nrow(result)*0.7)
re_train <- result[index,]
re_test <- result[-index,]

# NA 처리안한 rpart 모델
library(rpart)
fit <- rpart(formula = class1 ~., data = re_train)

pred <- predict(fit, re_test, type = "class")
tb <- table(re_test$class1, pred)
sum(diag(tb))/sum(tb) # 정확도 = 58.6%
  • NA 값을 처리하지 않은 rpart 모델의 정확도는 58.6%
# library
library(DMwR)
library(caret)
library(randomForest)
library(e1071)
library(C50)
library(adabag)
library(class)

# knn Imputation 으로 NA 값 대체 해보기
# 1 ~ 100 => 최적의 k = 50
set.seed(123)
a <- knnImputation(result, 50)
names(a) <- c("nbps", "nbpd", "nbpm", "hr", "spo2", "bis", "tofratio", "tofcount", "집도과", "마취의", "신체적상태", "class")
  • k-nn imputation을 이용해 NA 값 처리

- (1) k-nn Imputation을 실시한 후, 열의 이름을 재지정해줍니다.

*k Nearest Neighbor Imputation = knn을 사용해 찾은 k개 주변 이웃의 값을 주변 이웃까지의 거리를 고려해 가중 평균한 값으로 대체하는 것.

- MODEL = 사용한 변수

-“NBP-S, NBP-D, NBP-M, HR, SPO2, BIS, TOF ratio, TOF count, 집도과, 마취의, 신체적상태”

 

  • Random Forest
# 랜덤 포레스트 모델
a.fit <- randomForest(class ~ ., data = a_train,
mtry = floor(sqrt(11)), ntree = 500, importance = T)
a.pred <- predict(a.fit, a_test)
tb.a <- table(a.pred, a_test$class)
sum(diag(tb.a))/sum(tb.a) # 정확도 = 73.9%

- (1) 랜덤포레스트 모형 — mtry(분류 문제이기 때문에 sqrt(변수의 개수) 실시)는 각각의 tree마다 몇 개의 feature를 사용할 것인지 정하는 것이며, ntree는 tree의 총 개수를 의미합니다.

- (2) 정확도는 73.9% 정도로 나오는 것을 볼 수 있습니다.

 

  • SVM
# SVM 모델
b.fit <- svm(class ~ ., data = a_train)
b.pred <- predict(b.fit, a_test)
tb.b <- table(b.pred, a_test$class)
sum(diag(tb.b))/sum(tb.b) # 정확도 = 66.3%

- 정확도는 66.3% 정도로 나오는 것을 볼 수 있습니다.

 

  • Boosting
# 부스팅
# trials => 50, 100  결과 비슷함
c_bst <- C5.0(class ~ ., data = a_train, trials = 100)
c_c50 <- C5.0(class ~ ., data = a_train)
summary(c_c50) # Errors = 17.3% / 정확도 = 82.6%

set.seed(300)
m_ada <- boosting(class ~ ., data = a_train)
p_ada <- predict(m_ada, a)

p_ada$confusion
sum(diag(p_ada$confusion))/sum(p_ada$confusion) # 89.2%

- (1) 과적합 문제로 채택하지 않았습니다.

  • 최종 Model로 Random Forest 채택