ADP (R)

[R] 결측치 처리 방법 (중위수/최빈값)

멋쟁이천재사자 2022. 10. 23. 15:00

1. 시험에 나왔습니다

ADP 실기 시험을 보았는데 결측치 처리 문제(https://it-freelancer.tistory.com/293)가 첫 부분에 나왔습니다.
그렇게 어려운 문제가 아니었음에도,  제한 시간 내에 만족스런 답을 제출하지 못하고 허둥댄 것 같습니다.

2. 결론부터 말하자면, 다음에는 이렇게 해야겠습다.

# 결측값 확인

library(skimr)
library(dplyr)
data(Cars93,package="MASS")


Cars93 %>% 
  skim() %>% 
  filter(n_missing > 0)

# 최빈값 중위수 대치
Cars93 <- randomForest::na.roughfix(Cars93)

 

3. 결측치 처리 방법

결측치 처리에는 여러 가지 방법이 있습니다.

 

Wiki 를 찾아보면 크게는 단순 대치 다중 대치가 있고, 단순 대치에는 핫덱 콜드덱 그리고 블라블라 있습니다.
2 Single imputation
2.1 Hot-deck
2.2 Cold-deck
2.3 Mean substitution
2.4 Non-negative matrix factorization
2.5 Regression
3 Multiple imputation

출처 : https://en.wikipedia.org/wiki/Imputation_(statistics)#Mean_substitution

 

randomForest 패키지에서 제공하는 Rough Imputation 도 있습니다.

Impute Missing Values by median/mode.
na.roughfix: Rough Imputation of Missing Values

출처 : https://www.rdocumentation.org/packages/randomForest/versions/4.7-1.1/topics/na.roughfix

 

중위수와 최빈값은 대푯값이라는 측면에서는 평균과 비슷한 성격입니다.그래서 평균대체법의 한가지 방법이라고 할 수도 있을 것 같습니다. 그렇지만, Mean substitution 의 Mean 이 평균값이므로 median/mode 를 이용하는 Rough Imputation 과 동일하다고 확신할 수가 없네요. wiki 자료에 median/mode 에 대한 언급이 없어 아쉽습니다. 

 

4. R script 연구

# 결측치 대체 - 연속형 중위수 대치 (간단한 데이터셋)
# 변수 개수가 적은 경우 눈으로 확인해서 한 땀 한 땀 코딩하면 됩니다.
#------------------------------------------------------------------
airquality
summary(airquality)  # Ozone 37 Solar.R 7 - Median : 31.50   Median :205.0

airquality.clean <-  airquality
airquality.clean$Ozone <- ifelse(is.na(airquality.clean$Ozone),
                                 31.50,
                                 airquality.clean$Ozone)

airquality.clean$Solar.R <- ifelse(is.na(airquality.clean$Solar.R),
                                   205.0,
                                 airquality.clean$Solar.R)
summary(airquality.clean) # 결측치 없어지고, 중위수는 그대로 있음


# 결측치 대체 - 범주형의 최빈값 대치
# 가장 많은 범주를 table 함수로 일일이 확인해서...
#------------------------------------------------------------------
ToothGrowth
summary(ToothGrowth)  #원본에는 결측이 없습니다.
ToothGrowth.noise <- ToothGrowth

#연습을 위해 첫 번째 라인에 결측치를 강제로 만들기
str(ToothGrowth)
rownames(ToothGrowth)
ToothGrowth.noise[,1:3]
ToothGrowth.noise$supp <- ifelse(rownames(ToothGrowth)==1,
                                 NA,
                                 ToothGrowth.noise$supp)

summary(ToothGrowth.noise)
# 1,2 가 30 개씩이었는데 1행 VC(2) 하나를 NA 로 만드니 30개 29개입니다 
table(ToothGrowth.noise$supp) # 1 이 30개

ToothGrowth.clean <- ToothGrowth.noise
ToothGrowth.clean$supp <- ifelse(is.na(ToothGrowth.clean$supp),
                                   1,
                                   ToothGrowth.clean$supp)
summary(ToothGrowth.clean)
table(ToothGrowth.clean$supp)
# table 로 최빈값 확인해서 ifelse 이용해서 대치하는 거 노가다군요


# 변수가 많은 (변수 27개) 케이스에서는
#------------------------------------------------------------------
data(Cars93,package="MASS")
str(Cars93)
sum(is.na(Cars93)) #결측치 13 있음. 
summary(Cars93)   # 눈으로 못찾을 것은 아니지만...
apply(is.na(Cars93),2,sum) # 조금 낫긴 하지만 역시 눈이 아픔
library(dplyr)
apply(is.na(Cars93),2,sum) %>% 
  data.frame() %>% 
  filter(.>0)     # 이런 식으로 사용하는 것이 맞는 것인가?


library(skimr)
skim(Cars93)

Cars93 %>% 
  skim() %>% 
  filter(n_missing > 0) # 저는 이 방식이 맘에 들었습니다.

# roughfix 전후 비교를 위해 확인해 둡니다.
sum(Cars93$Rear.seat.room) #NA 가 있을때 sum 하면 NA 가 되는군요
sum(Cars93$Rear.seat.room,na.rm = T)      #2532.5
median(Cars93$Rear.seat.room,na.rm = T)   #27.5

Cars93.clean <- Cars93
library(randomForest)
# 할당을 해주면 된다.
Cars93.clean <- na.roughfix(Cars93.clean)
# 중위수는 그대로고 합은 NA 가 중위수로 바뀐만큼 증가했습니다.
sum(Cars93.clean$Rear.seat.room,na.rm = T)    #2587.5 <- 2532.5 + 2*27.5
median(Cars93.clean$Rear.seat.room,na.rm = T) #27.5

 


참고한 자료들

 

https://en.wikipedia.org/wiki/Imputation_(statistics)#Mean_substitution 

https://www.rdocumentation.org/packages/randomForest/versions/4.7-1.1/topics/na.roughfix

https://terms.naver.com/entry.naver?docId=3434283&cid=58456&categoryId=58456