3.2 결측치(Missing Values) 처리

3.2.1 결측값이 포함되어 있는지 확인하는 방법: is.na()

3.2.1.1 데이터 세트 1

x <- c(1, 2, 3, 4, NA, 6, 7, 8, 9, NA) 

3.2.1.2 is.na() 함수 사용

is.na(x)
##  [1] FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE
  • 벡터 x의 각 요소에 대해 NA 여부를 확인. 논리형 벡터 반환.

3.2.1.3 데이터 세트 2

library(MASS)
## 
## Attaching package: 'MASS'
## The following object is masked from 'package:dplyr':
## 
##     select
str(Cars93)
## 'data.frame':    93 obs. of  27 variables:
##  $ Manufacturer      : Factor w/ 32 levels "Acura","Audi",..: 1 1 2 2 3 4 4 4 4 5 ...
##  $ Model             : Factor w/ 93 levels "100","190E","240",..: 49 56 9 1 6 24 54 74 73 35 ...
##  $ Type              : Factor w/ 6 levels "Compact","Large",..: 4 3 1 3 3 3 2 2 3 2 ...
##  $ Min.Price         : num  12.9 29.2 25.9 30.8 23.7 14.2 19.9 22.6 26.3 33 ...
##  $ Price             : num  15.9 33.9 29.1 37.7 30 15.7 20.8 23.7 26.3 34.7 ...
##  $ Max.Price         : num  18.8 38.7 32.3 44.6 36.2 17.3 21.7 24.9 26.3 36.3 ...
##  $ MPG.city          : int  25 18 20 19 22 22 19 16 19 16 ...
##  $ MPG.highway       : int  31 25 26 26 30 31 28 25 27 25 ...
##  $ AirBags           : Factor w/ 3 levels "Driver & Passenger",..: 3 1 2 1 2 2 2 2 2 2 ...
##  $ DriveTrain        : Factor w/ 3 levels "4WD","Front",..: 2 2 2 2 3 2 2 3 2 2 ...
##  $ Cylinders         : Factor w/ 6 levels "3","4","5","6",..: 2 4 4 4 2 2 4 4 4 5 ...
##  $ EngineSize        : num  1.8 3.2 2.8 2.8 3.5 2.2 3.8 5.7 3.8 4.9 ...
##  $ Horsepower        : int  140 200 172 172 208 110 170 180 170 200 ...
##  $ RPM               : int  6300 5500 5500 5500 5700 5200 4800 4000 4800 4100 ...
##  $ Rev.per.mile      : int  2890 2335 2280 2535 2545 2565 1570 1320 1690 1510 ...
##  $ Man.trans.avail   : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 1 1 1 1 1 ...
##  $ Fuel.tank.capacity: num  13.2 18 16.9 21.1 21.1 16.4 18 23 18.8 18 ...
##  $ Passengers        : int  5 5 5 6 4 6 6 6 5 6 ...
##  $ Length            : int  177 195 180 193 186 189 200 216 198 206 ...
##  $ Wheelbase         : int  102 115 102 106 109 105 111 116 108 114 ...
##  $ Width             : int  68 71 67 70 69 69 74 78 73 73 ...
##  $ Turn.circle       : int  37 38 37 37 39 41 42 45 41 43 ...
##  $ Rear.seat.room    : num  26.5 30 28 31 27 28 30.5 30.5 26.5 35 ...
##  $ Luggage.room      : int  11 15 14 17 13 16 17 21 14 18 ...
##  $ Weight            : int  2705 3560 3375 3405 3640 2880 3470 4105 3495 3620 ...
##  $ Origin            : Factor w/ 2 levels "USA","non-USA": 2 2 2 2 2 1 1 1 1 1 ...
##  $ Make              : Factor w/ 93 levels "Acura Integra",..: 1 2 4 3 5 6 7 9 8 10 ...
  • MASS 패키지에 있는 Cars93 데이터 세트
  • 27개의 컬럼과 93개의 행으로 구성되어 있다.

is.na() 함수는 데이터 구조의 각 요소별로 NA 여부를 확인한다.

위의 벡터처럼 구성요소 갯수가 몇 개 안될 경우 is.na() 한 후에 TRUE, FALSE 논리형 값을 눈으로 보고 확인할 수 있다.

3.2.1.4 is.na() 함수 사용

하지만 Cars93 데이터 세트처럼 변수(열) 갯수도 많고, 관측치(행) 갯수도 많은 경우 (대부분의 실무에서 쓰는 데이터 세트는 이처럼 변수도 많고 관측치도 많다.)

is.na() 함수만 가지고서는 아무래도 결측치 현황을 파악하는데 무리가 있다.

head(is.na(Cars93))
##   Manufacturer Model  Type Min.Price Price Max.Price MPG.city MPG.highway
## 1        FALSE FALSE FALSE     FALSE FALSE     FALSE    FALSE       FALSE
## 2        FALSE FALSE FALSE     FALSE FALSE     FALSE    FALSE       FALSE
## 3        FALSE FALSE FALSE     FALSE FALSE     FALSE    FALSE       FALSE
## 4        FALSE FALSE FALSE     FALSE FALSE     FALSE    FALSE       FALSE
## 5        FALSE FALSE FALSE     FALSE FALSE     FALSE    FALSE       FALSE
## 6        FALSE FALSE FALSE     FALSE FALSE     FALSE    FALSE       FALSE
##   AirBags DriveTrain Cylinders EngineSize Horsepower   RPM Rev.per.mile
## 1   FALSE      FALSE     FALSE      FALSE      FALSE FALSE        FALSE
## 2   FALSE      FALSE     FALSE      FALSE      FALSE FALSE        FALSE
## 3   FALSE      FALSE     FALSE      FALSE      FALSE FALSE        FALSE
## 4   FALSE      FALSE     FALSE      FALSE      FALSE FALSE        FALSE
## 5   FALSE      FALSE     FALSE      FALSE      FALSE FALSE        FALSE
## 6   FALSE      FALSE     FALSE      FALSE      FALSE FALSE        FALSE
##   Man.trans.avail Fuel.tank.capacity Passengers Length Wheelbase Width
## 1           FALSE              FALSE      FALSE  FALSE     FALSE FALSE
## 2           FALSE              FALSE      FALSE  FALSE     FALSE FALSE
## 3           FALSE              FALSE      FALSE  FALSE     FALSE FALSE
## 4           FALSE              FALSE      FALSE  FALSE     FALSE FALSE
## 5           FALSE              FALSE      FALSE  FALSE     FALSE FALSE
## 6           FALSE              FALSE      FALSE  FALSE     FALSE FALSE
##   Turn.circle Rear.seat.room Luggage.room Weight Origin  Make
## 1       FALSE          FALSE        FALSE  FALSE  FALSE FALSE
## 2       FALSE          FALSE        FALSE  FALSE  FALSE FALSE
## 3       FALSE          FALSE        FALSE  FALSE  FALSE FALSE
## 4       FALSE          FALSE        FALSE  FALSE  FALSE FALSE
## 5       FALSE          FALSE        FALSE  FALSE  FALSE FALSE
## 6       FALSE          FALSE        FALSE  FALSE  FALSE FALSE

3.2.2 결측치 갯수: sum(is.na())

3.2.2.1 벡터의 경우

sum(is.na(x))
## [1] 2

3.2.2.2 데이터 프레임의 경우 : Cars93 데이터 프레임.

sum(is.na(Cars93))
## [1] 13

3.2.2.3 Cars93 의 각 변수(컬럼)별로 결측값 개수

총 27개의 변수 중에서 Manufacturer, Price, Rear.seat.room, Luggage.room 등의 4개만 예시로 살펴본다.

sum(is.na(Cars93$Manufacturer)) 
## [1] 0
sum(is.na(Cars93$Price))
## [1] 0
sum(is.na(Cars93$Rear.seat.room))
## [1] 2
sum(is.na(Cars93$Luggage.room))
## [1] 11
  • 각 컬럼 별로 NA 요소 갯수가 반환된다.

sum(is.na()) 함수를 이용하면 변수가 많거나 관측값이 많은 경우도 결측값 현황을 금방 파악할 수 있다.

R은 TRUE 를 ‘1’로, FALSE 를’0‘으로 인식하기 때문에 sum(is.na())를 하게 되면 TRUE 값을’1’로 인식해서 합계를 구한다.

3.2.2.4 colSums() 함수 사용

colSums() 함수를 사용하면 데이터 프레임 내 다수 변수들에 대해서 한번에 각 개별 변수별 결측값의 개수 합계를 구할 수 있다.

바로 위에서 개별 함수별로 일일이 sum(is.na(Cars93$Manufacturer))…이런 식으로 변수의 개수만큼 쓰는 것을 colSums() 함수로는 한줄이면 해결할 수 있으니 훨씬 편하다.

colSums(is.na(Cars93)) 
##       Manufacturer              Model               Type          Min.Price 
##                  0                  0                  0                  0 
##              Price          Max.Price           MPG.city        MPG.highway 
##                  0                  0                  0                  0 
##            AirBags         DriveTrain          Cylinders         EngineSize 
##                  0                  0                  0                  0 
##         Horsepower                RPM       Rev.per.mile    Man.trans.avail 
##                  0                  0                  0                  0 
## Fuel.tank.capacity         Passengers             Length          Wheelbase 
##                  0                  0                  0                  0 
##              Width        Turn.circle     Rear.seat.room       Luggage.room 
##                  0                  0                  2                 11 
##             Weight             Origin               Make 
##                  0                  0                  0
  • colSums(is.na(Cars93)) : 함수로 간단하게 각 컬럼별 요소에 있는 NA의 갯수를 계산하였다.

3.2.3 결측치를 통계 분석시 제외시키기 : na.rm = TRUE

다음과 같은 함수는 데이터 세트에 NA가 포함되어 있으면 그 결과도 NA가 된다.

sum(x)
## [1] NA
mean(x)
## [1] NA
  • 벡터 x에 결측치(NA)가 포함되어 있으면 그 결과는 NA로 반환됨.

이런 경우에 na.rm = TRUE를 함수의 인수로 사용한다.

sum(x, na.rm = TRUE)
## [1] 40
mean(x, na.rm = TRUE)
## [1] 5
  • 데이터 세트에서 NA를 제외하고 그 결과를 반환한다.

결측값이 들어있는 벡터에 대해서 sum(), mean(), sd(), min(), max(), range() 등의 통계 함수를 적용하면 NA만 나오게 된다.

결측값을 제외하고 통계 함수 계산을 하는 옵션은 na.rm = TRUE 가 된다.

NA를 고려하지 않은 통계 처리의 경우

sum(Cars93$Luggage.room)
## [1] NA
mean(Cars93$Luggage.room)
## [1] NA
  • 모두 NA 결과

NA를 제외한 통계처리의 경우

sum(Cars93$Luggage.room, na.rm = TRUE)
## [1] 1139
mean(Cars93$Luggage.room, na.rm = TRUE)
## [1] 13.89024
  • NA를 제외한 통계 처리 결과

위 예제는 결측값이 포함된 데이터 프레임의 특정 변수에 대해 indexing을 해서 통계 함수를 적용해본 경우이다.

역시 na.rm = TRUE 옵션을 설정해 주어야 통계 계산이 제대로 됨을 알 수 있다.

na.rm = FALSE 가 디폴트이므로 na.rm = TRUE를 설정하지 않는 경우 결측값이 포함되어 있으면 NA가 결과로 나타나게 된다.

3.2.4 결측치가 있는 행 제거: na.omit()

na.rm = TRUE 옵션은 원래의 데이터 세트는 그대로 둔 채, 통계량 계산할 때만 NA를 포함하지 않게 된다.

따라서 다수의 통계 함수 혹은 다수의 변수에 통계 함수를 사용해야 하는 경우 매번 na.rm = TRUE 옵션을 설정해주는게 번거로울 수 있다.

따라서 차라리 원래 데이터 세트에서 결측값을 제거하면 된다.

결측값이 들어있는 행을 제거해 주는 함수가 na.omit() 함수이다.

좀 더 예리하게 특정 행과 열을 지정해서 그곳에 결측값이 있는 경우만 메스로 정밀 수술하는 함수가 complete.cases() 함수이다.

3.2.4.1 na.omit() 함수의 사용 예

Cars93_1 <- na.omit(Cars93) 
str(Cars93_1)
## 'data.frame':    82 obs. of  27 variables:
##  $ Manufacturer      : Factor w/ 32 levels "Acura","Audi",..: 1 1 2 2 3 4 4 4 4 5 ...
##  $ Model             : Factor w/ 93 levels "100","190E","240",..: 49 56 9 1 6 24 54 74 73 35 ...
##  $ Type              : Factor w/ 6 levels "Compact","Large",..: 4 3 1 3 3 3 2 2 3 2 ...
##  $ Min.Price         : num  12.9 29.2 25.9 30.8 23.7 14.2 19.9 22.6 26.3 33 ...
##  $ Price             : num  15.9 33.9 29.1 37.7 30 15.7 20.8 23.7 26.3 34.7 ...
##  $ Max.Price         : num  18.8 38.7 32.3 44.6 36.2 17.3 21.7 24.9 26.3 36.3 ...
##  $ MPG.city          : int  25 18 20 19 22 22 19 16 19 16 ...
##  $ MPG.highway       : int  31 25 26 26 30 31 28 25 27 25 ...
##  $ AirBags           : Factor w/ 3 levels "Driver & Passenger",..: 3 1 2 1 2 2 2 2 2 2 ...
##  $ DriveTrain        : Factor w/ 3 levels "4WD","Front",..: 2 2 2 2 3 2 2 3 2 2 ...
##  $ Cylinders         : Factor w/ 6 levels "3","4","5","6",..: 2 4 4 4 2 2 4 4 4 5 ...
##  $ EngineSize        : num  1.8 3.2 2.8 2.8 3.5 2.2 3.8 5.7 3.8 4.9 ...
##  $ Horsepower        : int  140 200 172 172 208 110 170 180 170 200 ...
##  $ RPM               : int  6300 5500 5500 5500 5700 5200 4800 4000 4800 4100 ...
##  $ Rev.per.mile      : int  2890 2335 2280 2535 2545 2565 1570 1320 1690 1510 ...
##  $ Man.trans.avail   : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 1 1 1 1 1 ...
##  $ Fuel.tank.capacity: num  13.2 18 16.9 21.1 21.1 16.4 18 23 18.8 18 ...
##  $ Passengers        : int  5 5 5 6 4 6 6 6 5 6 ...
##  $ Length            : int  177 195 180 193 186 189 200 216 198 206 ...
##  $ Wheelbase         : int  102 115 102 106 109 105 111 116 108 114 ...
##  $ Width             : int  68 71 67 70 69 69 74 78 73 73 ...
##  $ Turn.circle       : int  37 38 37 37 39 41 42 45 41 43 ...
##  $ Rear.seat.room    : num  26.5 30 28 31 27 28 30.5 30.5 26.5 35 ...
##  $ Luggage.room      : int  11 15 14 17 13 16 17 21 14 18 ...
##  $ Weight            : int  2705 3560 3375 3405 3640 2880 3470 4105 3495 3620 ...
##  $ Origin            : Factor w/ 2 levels "USA","non-USA": 2 2 2 2 2 1 1 1 1 1 ...
##  $ Make              : Factor w/ 93 levels "Acura Integra",..: 1 2 4 3 5 6 7 9 8 10 ...
##  - attr(*, "na.action")= 'omit' Named int [1:11] 16 17 19 26 36 56 57 66 70 87 ...
##   ..- attr(*, "names")= chr [1:11] "16" "17" "19" "26" ...
  • Cars93_1 <- na.omit(Cars93) : NA를 가진 행을 제외한 새로운 데이터 세트 Cars93_1을 생성한다.
  • str(Cars93_1) : 원래의 데이터 세트 Cars93에 있던 93개의 행이 82개로 줄어 들었다. 즉, 11개의 행을 삭제했다.

위의 예처럼 결측값이 들어있는 행을 통째로 삭제할 때는 만약의 사태를 대비해서 원본 Cars93은 그대로 유지하고, 행을 삭제한 데이터 셋을 별도의 이름 Cars93_1로 저장해서 분석을 진행하는 것을 추천한다.

3.2.4.2 특정 열에 결측값 존재 시 행 제거하기 : complete.cases() 함수

sum(is.na(Cars93))
## [1] 13
  • Cars93 데이터 세트에 있는 NA 요소의 갯수를 출력한다.
3.2.4.2.1 Cars93 데이터 프레임의 Rear.seat.room 컬럼의 결측치 행을 데이터 세트에서 제거하기

Cars93[ , “Rear.seat.room”] 또는 Cars93$Rear.seat.room 변수를 이용할 수 있다.

Cars93_2 <- Cars93[ complete.cases(Cars93[ , c("Rear.seat.room")]), ]
sum(is.na(Cars93_2)) 
## [1] 9

위의 구문은 다음과 같은 논리로 문제를 해결한다. 1. x <- Cars93$Rear.seat.room : 해당 컬럼을 변수 x로 대체 2. y <- complete.cases(x) : x 벡터의 요소에 대해 NA 존재 여부 확인 -> logical 벡터가 나온다. 3. Cars93_2 <- Cars93[y, ] : yTRUE 행만을 Cars93_2로 추출한다.

   x <- Cars93$Rear.seat.room; head(x)
## [1] 26.5 30.0 28.0 31.0 27.0 28.0
   y <- complete.cases(x); sum(!y)
## [1] 2
   z <- is.na(x); sum(z)
## [1] 2
   Cars93_2 <- Cars93[y, ]; str(Cars93_2); sum(is.na(Cars93_2)) 
## 'data.frame':    91 obs. of  27 variables:
##  $ Manufacturer      : Factor w/ 32 levels "Acura","Audi",..: 1 1 2 2 3 4 4 4 4 5 ...
##  $ Model             : Factor w/ 93 levels "100","190E","240",..: 49 56 9 1 6 24 54 74 73 35 ...
##  $ Type              : Factor w/ 6 levels "Compact","Large",..: 4 3 1 3 3 3 2 2 3 2 ...
##  $ Min.Price         : num  12.9 29.2 25.9 30.8 23.7 14.2 19.9 22.6 26.3 33 ...
##  $ Price             : num  15.9 33.9 29.1 37.7 30 15.7 20.8 23.7 26.3 34.7 ...
##  $ Max.Price         : num  18.8 38.7 32.3 44.6 36.2 17.3 21.7 24.9 26.3 36.3 ...
##  $ MPG.city          : int  25 18 20 19 22 22 19 16 19 16 ...
##  $ MPG.highway       : int  31 25 26 26 30 31 28 25 27 25 ...
##  $ AirBags           : Factor w/ 3 levels "Driver & Passenger",..: 3 1 2 1 2 2 2 2 2 2 ...
##  $ DriveTrain        : Factor w/ 3 levels "4WD","Front",..: 2 2 2 2 3 2 2 3 2 2 ...
##  $ Cylinders         : Factor w/ 6 levels "3","4","5","6",..: 2 4 4 4 2 2 4 4 4 5 ...
##  $ EngineSize        : num  1.8 3.2 2.8 2.8 3.5 2.2 3.8 5.7 3.8 4.9 ...
##  $ Horsepower        : int  140 200 172 172 208 110 170 180 170 200 ...
##  $ RPM               : int  6300 5500 5500 5500 5700 5200 4800 4000 4800 4100 ...
##  $ Rev.per.mile      : int  2890 2335 2280 2535 2545 2565 1570 1320 1690 1510 ...
##  $ Man.trans.avail   : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 1 1 1 1 1 ...
##  $ Fuel.tank.capacity: num  13.2 18 16.9 21.1 21.1 16.4 18 23 18.8 18 ...
##  $ Passengers        : int  5 5 5 6 4 6 6 6 5 6 ...
##  $ Length            : int  177 195 180 193 186 189 200 216 198 206 ...
##  $ Wheelbase         : int  102 115 102 106 109 105 111 116 108 114 ...
##  $ Width             : int  68 71 67 70 69 69 74 78 73 73 ...
##  $ Turn.circle       : int  37 38 37 37 39 41 42 45 41 43 ...
##  $ Rear.seat.room    : num  26.5 30 28 31 27 28 30.5 30.5 26.5 35 ...
##  $ Luggage.room      : int  11 15 14 17 13 16 17 21 14 18 ...
##  $ Weight            : int  2705 3560 3375 3405 3640 2880 3470 4105 3495 3620 ...
##  $ Origin            : Factor w/ 2 levels "USA","non-USA": 2 2 2 2 2 1 1 1 1 1 ...
##  $ Make              : Factor w/ 93 levels "Acura Integra",..: 1 2 4 3 5 6 7 9 8 10 ...
## [1] 9
  • compleste.cases(x) : 요소가 NA가 아니면 TRUE, NAFALSE 반환. - 따라서, NA인 요소의 갯수를 구하기 위해 sum(!y)를 사용. : x 컬럼의 NA 갯수는 2개.
  • is.na(x) : complete.cases() 함수와 비교하기 위함. is.na() 함수는 요소가 NA이면 TRUE, NA가 아니면 FALSE 반환.
  • 따라서 sum(z)NA인 요소의 갯수를 구한다. : x 컬럼의 NA 갯수는 2개.
  • Cars93[y, ] : Cars93 데이터 세트 중에서 벡터 yTRUE인 행인 모든 열([y, ])을 추출한다.
  • sum(is.na(Cars93_2)) : Cars93_2 데이터 세트에 NA 요소가 아직도 9개 남아 있음을 확인할 수 있다.
3.2.4.2.2 Cars93 데이터 프레임의 23~24번째 칼럼 내 결측값이 있는 행 전체 삭제

이의 해결은 다음과 같은 논리 절차과정을 거치게 된다.

  1. Cars93의 23, 24번쨰 컬럼은 다음과 같이 표현된다. : Cars93[ , 23:24]
  2. 이를 x 변수에 대입한다. : x <- Cars93[, 23:24]
  3. 이 x 변수에 대해 결측치가 없는 행을 확인하고 이를 y 변수에 대입한다. : y <- complete_cases(x)
  4. Cars93에서 결측치가 없는 행들을 Cars93_3에 대입한다. : Cars93_3 <- Cars93[y, ]
  5. Cars93_3의 결측치 요소 갯수를 확인한다. : sum(is.na(Cars93_3))

이 절차를 스크립트로 작성하면 다음과 같다.

x <- Cars93[, 23:24]
y <- complete.cases(x)
Cars93_3 <- Cars93[y, ]
sum(is.na(Cars93_3))
## [1] 0
  • Cars93_3 데이터 세트에는 NA 요소가 하나도 없다.

이러한 논리를 한 줄에 표현한 것이 다음의 스크립트이다.

Cars93_3 <- Cars93[ complete.cases(Cars93[ , c(23:24)]), ]
sum(is.na(Cars93_3))  
## [1] 0

행의 갯수 즉, 관측치의 갯수를 확인해 보자.

dim(Cars93_3)
## [1] 82 27
str(Cars93_3)
## 'data.frame':    82 obs. of  27 variables:
##  $ Manufacturer      : Factor w/ 32 levels "Acura","Audi",..: 1 1 2 2 3 4 4 4 4 5 ...
##  $ Model             : Factor w/ 93 levels "100","190E","240",..: 49 56 9 1 6 24 54 74 73 35 ...
##  $ Type              : Factor w/ 6 levels "Compact","Large",..: 4 3 1 3 3 3 2 2 3 2 ...
##  $ Min.Price         : num  12.9 29.2 25.9 30.8 23.7 14.2 19.9 22.6 26.3 33 ...
##  $ Price             : num  15.9 33.9 29.1 37.7 30 15.7 20.8 23.7 26.3 34.7 ...
##  $ Max.Price         : num  18.8 38.7 32.3 44.6 36.2 17.3 21.7 24.9 26.3 36.3 ...
##  $ MPG.city          : int  25 18 20 19 22 22 19 16 19 16 ...
##  $ MPG.highway       : int  31 25 26 26 30 31 28 25 27 25 ...
##  $ AirBags           : Factor w/ 3 levels "Driver & Passenger",..: 3 1 2 1 2 2 2 2 2 2 ...
##  $ DriveTrain        : Factor w/ 3 levels "4WD","Front",..: 2 2 2 2 3 2 2 3 2 2 ...
##  $ Cylinders         : Factor w/ 6 levels "3","4","5","6",..: 2 4 4 4 2 2 4 4 4 5 ...
##  $ EngineSize        : num  1.8 3.2 2.8 2.8 3.5 2.2 3.8 5.7 3.8 4.9 ...
##  $ Horsepower        : int  140 200 172 172 208 110 170 180 170 200 ...
##  $ RPM               : int  6300 5500 5500 5500 5700 5200 4800 4000 4800 4100 ...
##  $ Rev.per.mile      : int  2890 2335 2280 2535 2545 2565 1570 1320 1690 1510 ...
##  $ Man.trans.avail   : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 1 1 1 1 1 ...
##  $ Fuel.tank.capacity: num  13.2 18 16.9 21.1 21.1 16.4 18 23 18.8 18 ...
##  $ Passengers        : int  5 5 5 6 4 6 6 6 5 6 ...
##  $ Length            : int  177 195 180 193 186 189 200 216 198 206 ...
##  $ Wheelbase         : int  102 115 102 106 109 105 111 116 108 114 ...
##  $ Width             : int  68 71 67 70 69 69 74 78 73 73 ...
##  $ Turn.circle       : int  37 38 37 37 39 41 42 45 41 43 ...
##  $ Rear.seat.room    : num  26.5 30 28 31 27 28 30.5 30.5 26.5 35 ...
##  $ Luggage.room      : int  11 15 14 17 13 16 17 21 14 18 ...
##  $ Weight            : int  2705 3560 3375 3405 3640 2880 3470 4105 3495 3620 ...
##  $ Origin            : Factor w/ 2 levels "USA","non-USA": 2 2 2 2 2 1 1 1 1 1 ...
##  $ Make              : Factor w/ 93 levels "Acura Integra",..: 1 2 4 3 5 6 7 9 8 10 ...
  • 관측값이 82개로서 전체 93개 중에서 11개가 줄어들었음을 알 수 있다.

3.2.5 결측치를 다른 값으로 대체

3.2.5.1 기본 형식

결측치를 다른 값으로 대체하는 기본적인 형식은 다음과 같다.

dataset$var[is.na(dataset$var)] <- new_value

이 구문의 논리적 흐름은 다음과 같다. 1. dataset$var : dataset 데이터 세트의 var 컬럼 (x) 2. is.na(x) : x에 NA가 있는가 (y) 3. dataset$var[y] <- new_value : datasetvar 컬럼 중 yTRUE인 경우 new_value를 대입.

3.2.5.2 결측치 대체 예

Cars93 데이터 세트에서 Luggage.room 컬럼에 있는 결측치를 모두 0으로 변경해 본다.

3.2.5.2.1 결측치 요소 확인

Cars93$Luggage.room 변수에 대해 결측치 요소 확인

Cars93$Luggage.room
##  [1] 11 15 14 17 13 16 17 21 14 18 14 13 14 13 16 NA NA 20 NA 15 14 17 11 13 14
## [26] NA 16 11 11 15 12 12 13 12 18 NA 18 21 10 11  8 12 14 11 12  9 14 15 14  9
## [51] 19 22 16 13 14 NA NA 12 15  6 15 11 14 12 14 NA 14 14 16 NA 17  8 17 13 13
## [76] 16 18 14 12 10 15 14 10 11 13 15 NA 10 NA 14 15 14 15
sum(is.na(Cars93$Luggage.room))
## [1] 11
3.2.5.2.2 결측치를 0으로 대체하기

원래의 데이터 세트 Cars93을 Cars93_4로 복사한 다음, Cars93_4$Luggage.room의 결측치를 0으로 대체한다.

이 구문의 논리적 흐름은 다음과 같다. 1. Cars93_4 <- Cars93 : Cars93Cars93_4에 복사한다. 2. Cars93_4$Luggage.room : Cars93_4 데이터 세트의 var 컬럼 (x) 3. is.na(x) : x에 NA가 있는가 (y) 4. Cars93_4$var[y] <- 0 : Cars93_4Luggage.room 컬럼 중 yTRUE인 경우 new_value0`를 대입.

이를 스크립트로 표현하면 다음과 같다.

Cars93_4 <- Cars93
x <- Cars93_4$Luggage.room; x   # 수정 전 Luggage.room 컬럼
##  [1] 11 15 14 17 13 16 17 21 14 18 14 13 14 13 16 NA NA 20 NA 15 14 17 11 13 14
## [26] NA 16 11 11 15 12 12 13 12 18 NA 18 21 10 11  8 12 14 11 12  9 14 15 14  9
## [51] 19 22 16 13 14 NA NA 12 15  6 15 11 14 12 14 NA 14 14 16 NA 17  8 17 13 13
## [76] 16 18 14 12 10 15 14 10 11 13 15 NA 10 NA 14 15 14 15
y <- is.na(Cars93_4$Luggage.room)
Cars93_4$Luggage.room[y] <- 0
Cars93_4$Luggage.room           # 수정 후 Luggage.room 컬럼
##  [1] 11 15 14 17 13 16 17 21 14 18 14 13 14 13 16  0  0 20  0 15 14 17 11 13 14
## [26]  0 16 11 11 15 12 12 13 12 18  0 18 21 10 11  8 12 14 11 12  9 14 15 14  9
## [51] 19 22 16 13 14  0  0 12 15  6 15 11 14 12 14  0 14 14 16  0 17  8 17 13 13
## [76] 16 18 14 12 10 15 14 10 11 13 15  0 10  0 14 15 14 15
  • NA값들이 모두 0으로 대체되었음을 알 수 있다.

위의 스크립트를 간단하게 표현하면 다음과 같다. 여기서는 Cars93을 Cars93_5로 복사하여 처리한다.

# Luggage.room 변수 내 결측값을 '0'으로 대체
Cars93_5 <- Cars93
Cars93_5$Luggage.room     # 수정 전 Luggage.room 컬럼
##  [1] 11 15 14 17 13 16 17 21 14 18 14 13 14 13 16 NA NA 20 NA 15 14 17 11 13 14
## [26] NA 16 11 11 15 12 12 13 12 18 NA 18 21 10 11  8 12 14 11 12  9 14 15 14  9
## [51] 19 22 16 13 14 NA NA 12 15  6 15 11 14 12 14 NA 14 14 16 NA 17  8 17 13 13
## [76] 16 18 14 12 10 15 14 10 11 13 15 NA 10 NA 14 15 14 15
Cars93_5$Luggage.room[is.na(Cars93_5$Luggage.room)] <- 0
Cars93_5$Luggage.room     # 수정 후 Luggage.room 컬럼
##  [1] 11 15 14 17 13 16 17 21 14 18 14 13 14 13 16  0  0 20  0 15 14 17 11 13 14
## [26]  0 16 11 11 15 12 12 13 12 18  0 18 21 10 11  8 12 14 11 12  9 14 15 14  9
## [51] 19 22 16 13 14  0  0 12 15  6 15 11 14 12 14  0 14 14 16  0 17  8 17 13 13
## [76] 16 18 14 12 10 15 14 10 11 13 15  0 10  0 14 15 14 15

Cars93_5$Luggage.room[is.na(Cars93_5$Luggage.room)] <- 0 과 같은 스크립트에 익숙해질 필요가 있는 것이다.

3.2.5.2.3 결측치를 컬럼의 평균값으로 대체하기

sum(is.na(Cars93$Luggage.room)) 함수를 사용해 Cars93Luggage.room 변수에 보면 11개의 결측값이 있음을 알 수 있다.

Luggage.room 변수 내 결측값을 indexing 기법을 활용해서 ’0’로 대체하는 방법을 살펴보았다.

물론 ’0’이 아니라 다른 값으로도 대체가 가능하다.

아래의 예제에서는 결측값을 미포함(`na.rm = TRUE)했을 때의 Luggage.room의 평균값으로 결측값을 대체하여 보자,

Cars93_6 <- Cars93
Cars93_6$Luggage.room          # 수정 전 Luggage.room 
##  [1] 11 15 14 17 13 16 17 21 14 18 14 13 14 13 16 NA NA 20 NA 15 14 17 11 13 14
## [26] NA 16 11 11 15 12 12 13 12 18 NA 18 21 10 11  8 12 14 11 12  9 14 15 14  9
## [51] 19 22 16 13 14 NA NA 12 15  6 15 11 14 12 14 NA 14 14 16 NA 17  8 17 13 13
## [76] 16 18 14 12 10 15 14 10 11 13 15 NA 10 NA 14 15 14 15
Cars93_6$Luggage.room[is.na(Cars93_6$Luggage.room)] <- mean(Cars93_6$Luggage.room, na.rm = TRUE)
sum(is.na(Cars93_6$Luggage.room))
## [1] 0
round(Cars93_6$Luggage.room)   # 수정 후 Luggage.room 
##  [1] 11 15 14 17 13 16 17 21 14 18 14 13 14 13 16 14 14 20 14 15 14 17 11 13 14
## [26] 14 16 11 11 15 12 12 13 12 18 14 18 21 10 11  8 12 14 11 12  9 14 15 14  9
## [51] 19 22 16 13 14 14 14 12 15  6 15 11 14 12 14 14 14 14 16 14 17  8 17 13 13
## [76] 16 18 14 12 10 15 14 10 11 13 15 14 10 14 14 15 14 15
  • 0 값 대신에 Luggage.room 컬럼의 평균인 mean(Cars93_6$Luggage.room, na.rm = TRUE)로 대체하고 있음을 알 수 있다.
3.2.5.2.4 일괄 대체

데이터프레임의 모든 행의 결측값을 특정 값(가령, ‘0’)으로 일괄 대체 : dataset[is.na(dataset)] <- 0

Cars93_7 <- Cars93
# counting the number of missing values in Cars93 dataset
sum(is.na(Cars93_7)) # 13
## [1] 13
# converting the missing value in Cars93 dataframe to '0'
Cars93_7[is.na(Cars93_7)] <- 0
 
# counting the number of missing values in Cars93 dataset
sum(is.na(Cars93_7)) # 0
## [1] 0
3.2.5.2.5 데이터프레임의 각 변수의 결측값을 각 변수 별 평균값으로 일괄 대체

위의 3)번에서 결측값을 그 열의 평균으로 대체하는 방법을 알아보았다.

만약 결측값을 포함한 열이 매우 많다면 일일이 변수이름을 지정해가면서 나열해서 입력하는 것은 번거로운 일이다.

이럴 때 sapply() 함수를 사용하면 일괄로 결측값을 포함한 변수에 대해서는 해당 변수의 평균으로 대체하라고 프로그래밍할 수 있다.

sapply()를 적용하면 matrix를 반환하므로 dataframe으로 만들기 위해서 앞에 data.frame() 을 추가로 붙여주었다.

sapply(dataset, function(x) ifelse(is.na(x), mean(x, na.rm=TRUE), x))

이러한 문제의 해결을 위한 스크립트는 다음과 같다.

# converting the missing value in Cars93 dataframe to each column's mean value
Cars93_8 <- Cars93[1:20,c("Rear.seat.room", "Luggage.room")]
colSums(is.na(Cars93_8))
## Rear.seat.room   Luggage.room 
##              1              3
head(Cars93_8)
##   Rear.seat.room Luggage.room
## 1           26.5           11
## 2           30.0           15
## 3           28.0           14
## 4           31.0           17
## 5           27.0           13
## 6           28.0           16
sapply(Cars93_8, function(x) mean(x, na.rm=T))
## Rear.seat.room   Luggage.room 
##       29.10526       15.35294
Cars93_8 <- data.frame(sapply(Cars93_8, 
                              function(x) ifelse(is.na(x), 
                                                 mean(x, na.rm = TRUE),
                                                 x)
                              )
                       )
head(Cars93_8)
##   Rear.seat.room Luggage.room
## 1           26.5           11
## 2           30.0           15
## 3           28.0           14
## 4           31.0           17
## 5           27.0           13
## 6           28.0           16
3.2.5.2.6 그룹별 평균값으로 결측값 대체하기

base 패키지를 사용할 수도 있다.

그런데 여기서는 dplyr 패키지의 group_by()ifelse() 조건문을 사용한 mutate() 함수로 그룹 별 평균값으로 결측값 채우는 방법에 대하여 알아보자.

3.2.5.2.6.1 데이터 세트 생성
# make a sample DataFrame
grp <- c(rep('a', 5), rep('b', 5))
val <- c(1, 2, 3, NaN, 6, 2, 4, NaN, 10, 8)
df <- data.frame(grp, val)
df
##    grp val
## 1    a   1
## 2    a   2
## 3    a   3
## 4    a NaN
## 5    a   6
## 6    b   2
## 7    b   4
## 8    b NaN
## 9    b  10
## 10   b   8
3.2.5.2.6.2 그룹별 평균 구하기

결측값을 제외하고, 그룹 ‘a’와 그룹 ’b’ 별 평균을 계산해보자.

# mean value by group 'a' and 'b'
library(dplyr)
df %>% group_by(grp) %>% summarise(grp_mean = mean(val, na.rm = TRUE))
## `summarise()` ungrouping output (override with `.groups` argument)
## # A tibble: 2 x 2
##   grp   grp_mean
##   <chr>    <dbl>
## 1 a            3
## 2 b            6

그룹 ‘a’의 평균은 3, 그룹’b’의 평균은 6이다.

3.2.5.2.6.3 결측치를 그룹별 평균으로 대체하기

그러면 4번째 행에 있는 그룹 ‘a’의’val’ 칼럼 결측값을 그룹 ‘a’의 평균 3으로 대체(replace) 또는 채워넣기(fill in)를 하면 다음과 같다. 그리고 8번째 행에 있는 그룹’b‘의’val’ 칼럼 결측값을 그룹 ’b’의 평균 6으로 대체하면 다음과 같다.

df %>% 
   group_by(grp) %>% 
   mutate(val = ifelse(is.na(val), mean(val, na.rm=TRUE), val))
## # A tibble: 10 x 2
## # Groups:   grp [2]
##   grp     val
##   <chr> <dbl>
## 1 a         1
## 2 a         2
## 3 a         3
## # ... with 7 more rows

위에서 NA(Not Available)에 대해서 소개를 했는데요, 참고로 벡터에서 NaN (Not a Number), 무한값 Inf (Infinity), 유한값 finite 확인하는 방법도 마저 소개하겠습니다.

유의할 것은, is.na()에서 NA 뿐만 아니라 NaNTRUE 를 반환합니다.

my_data <- c(-1, 0, 10, NA, NaN, Inf)
my_data
## [1]  -1   0  10  NA NaN Inf
is.finite(my_data)
## [1]  TRUE  TRUE  TRUE FALSE FALSE FALSE
is.na(my_data)
## [1] FALSE FALSE FALSE  TRUE  TRUE FALSE
is.nan(my_data)
## [1] FALSE FALSE FALSE FALSE  TRUE FALSE
is.infinite(my_data)
## [1] FALSE FALSE FALSE FALSE FALSE  TRUE

Reference