R) 전처리 - 결측치 처리-01

R) 전처리 - 결측치 처리-01

데이터 분석을 본격적으로 실시하기 전에 결측치 처리는 꼭 실시해야 한다.
관련 함수와 그 활용 방법을 알아보고자 한다.

개요

정의

자료가 누락되어있는 상태를 결측(missing)이라고 하며 누락 되어있는 상태를 별도의 숫자 또는 문자로 표기한 것을 결측값(missing value)이라고 한다.

종류

결측치 종류는 정말 많다. R에서 사용하고 있는 NA 만 있는 것이 아니다. 필자가 데이터 분석을 하며 겪은 결측치 종류를 나열해보겠다.

  1. NA/<NA>: R의 기본 결측값이며, factor의 경우 <NA>로 표기된다.
  2. null/Null/NULL: 데이터베이스나 타 언어에서 결측을 표기한 것을 그대로 들고오는 경우에 볼 수 있다.
  3. “”/“ “: 이것은 빈칸과 한 칸 띄어쓰기를 의미하며, 보통 엑셀에서 빈 셀의 경우 이런 식으로 표기 된다.
  4. 0: 모든 수가 양수일 때 결측 표기
  5. -1: 모든 수가 0 이상일 때 결측 표기
  6. -1 ~ -9: 모든 수가 0 이상일 때 결측 표기이며, “사용자 입력 누락”이나 “삭제 요청” 등 결측의 사유를 표기하기 위해 사용된다.
  7. 99/-99/-999/999: 관측값이 실수이면서, 값 범위가 -99~99 사이에 있거나, -99~999 사이에 있는 경우의 결측 표기
  8. x/-/.: 기타 등등 기호나 문자를 사용하여 데이터가 없다는 것을 적극적으로 표현한 경우

결측치 확인

관련 함수

결측값을 확인하는 기본 함수는 다음과 같다.

  • is.na(): NA를 확인하는 함수이며 결과값은 논리값(TRUE, FALSE)으로 출력된다.
  • is.null(): NULL를 확인하는 함수이며 결과값은 논리값(TRUE, FALSE)으로 출력된다.

이 외에도 간접적으로 확인하는 함수가 있으며 별도의 패키지에서 제공하는 함수가 있을 수 있다. 하지만 R의 기본 함수를 온전히 이해하지 못하면 향후 고급 기능을 사용하는데 어려움이 있을 수 있기 때문에 위 함수의 입출력 정도는 이해하고 넘어가는 것을 권장한다.

결측치 개수 세기

논리값 TRUEFALSE는 각각 10으로 동작하는 것을 잘 알고 있을 것이다. 그래서 합연산을 할 경우 TRUE의 개수를 세는 것과 같다.

1
2
3
4
5
6
aa = c(1, 2, NA)
is.na(aa)
## [1] FALSE FALSE TRUE

sum(is.na(aa))
## 1

논리값 연산에 어려움을 느낀다면 summary() 함수를 사용해도 된다. 예제는 다음과 같다.

1
2
3
4
5
6
7
8
9
aa = c(1, 2, NA)
summary(aa)
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## 1.00 1.25 1.50 1.50 1.75 2.00 1

bb = 1:3
summary(bb)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 1.0 1.5 2.0 2.0 2.5 3.0

summary() 함수는 1차원 벡터와 데이터프레임 둘 다 사용할 수 있지만, 결측치가 존재하지 않는 경우 결측치가 0이라고 표기되지 않는다.

is.na(), summary() 함수 외에도 table() 함수가 있다. 단순히 원소 또는 조합의 개수를 세는 함수지만, 사람들이 useNA 라는 인자의 존재를 잘 모른다. 해당 인자에는 “no”, “ifany”, “always”중 하나를 입력하여 입력된 객체에 결측치가 있을 경우 표기 여부를 결정할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cc = c(1, 1, 2, 2, 2, NA)
table(cc)
## cc
## 1 2
## 2 3

table(cc, useNA = "no")
## cc
## 1 2
## 2 3

table(cc, useNA = "ifany")
## cc
## 1 2 <NA>
## 2 3 1

table(cc, useNA = "always")
## cc
## 1 2 <NA>
## 2 3 1

데이터프레임 객체의 경우 다음과 같다.

1
2
3
4
5
df = data.frame(aa = 1:3,
bb = c(NA, 1, NA),
cc = c(NA, 2, 3))
sum(is.na(df$bb))
## 2

apply() 함수를 사용하면 다음과 같다.

1
2
3
apply(X = df, MARGIN = 2, FUN = function(x){sum(is.na(x))})
## aa bb cc
## 0 2 1

sapply() 함수를 사용하면 다음과 같다.

1
2
3
sapply(X = df, FUN = function(x){sum(is.na(x))})
## aa bb cc
## 0 2 1

관측치 개수 세기

결측치를 제외한 관측치 개수만 센다면 단순히 논리값을 반전시키는 ! 연산자를 사용하면 된다. 반전 연산을 살짝 살펴보자.

1
2
3
4
5
!TRUE
## FALSE

!FALSE
## TRUE

응용 버전은 다음과 같다.

1
2
3
aa = c(1, 2, NA)
sum(!is.na(aa))
## 2

데이터프레임 객체의 경우 다음과 같다.

1
2
3
4
5
df = data.frame(aa = 1:3,
bb = c(NA, 1, NA),
cc = c(NA, 2, 3))
sum(is.na(df$bb))
## 2

apply() 함수를 사용하면 다음과 같다.

1
2
3
4
5
6
7
apply(X = df, MARGIN = 2, FUN = function(x){sum(!is.na(x))})
## aa bb cc
## 3 1 2

apply(X = df, MARGIN = 2, FUN = function(x){sum(is.na(x) == FALSE)})
## aa bb cc
## 3 1 2

sapply() 함수를 사용하면 다음과 같다.

1
2
3
4
5
6
7
sapply(X = df, FUN = function(x){sum(!is.na(x))})
## aa bb cc
## 3 1 2

sapply(X = df, FUN = function(x){sum(is.na(x) == FALSE)})
## aa bb cc
## 3 1 2

결측치 아닌 결측치 개수 세기

여기서 결측치 아닌 결측치란 R의 정식 결측치 표기인 NA가 아닌 결측치를 말한다. 물론 간혹 NULL이 등장하기도 하지만 예외로 한다. NA가 아니면 is.na() 함수 사용이 불가하다. 그래서 비교연산자 ==를 사용해야 한다.

1
2
3
4
5
6
7
8
9
10
11
bb = c("a", "b", "c", "") # 1
sum(bb == "")
## 1

cc = c("a", "b", "c", " ") # 2
sum(cc == " ")
## 1

dd = c("a", "b", "", " ") # 3
sum(dd %in% c("", " "))
## 2

결측치 종류가 2개 이상인 경우 3번 코드와 같이 == 대신 %in% 연산자를 사용하도록 한다.

결측치 처리

제거

기존에 사용하던 is.na(), is.null() 함수를 사용할 수 있지만, 결측값 제거 전용 na.omit() 함수가 있다.

  • na.omit(): 결측치를 제거하는 함수이며 결측값을 제거한 객체를 출력해준다.

각 예시를 보도록 하겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ee = c(1, 2, NA)
ee[!is.na(ee)] # 1
## [1] 1 2

ee[is.na(ee) == FALSE] # 2
## [1] 1 2

na.omit(ee) # 3
## [1] 1 2
## attr(,"na.action")
## [1] 3
## attr(,"class")
## [1] "omit"

df_na = data.frame(xx = c(1, NA, NA),
yy = c(1, 2, NA))
na.omit(df_na) # 4
## xx yy
## 1 1 1

3번 코드와 같이 1차원 객체에 na.omit()을 쓰게 되면 관측치와 결측치의 위치를 알려준다. 하지만, 4번 코드 처럼 데이터 프레임에 사용하는 경우는 결측치가 row에 하나라도 존재할 경우 다 제거해준다.

치환

앞에서 알아본 is.na() 함수를 사용하여 결측치에 접근하고 원하는 값을 대입한다. 특히 데이터프레임의 경우 변수명을 지정하지 않아 에러를 유발하는 경우가 많으니 주의하도록 하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
ff = c(1, 2, NA)
ff[is.na(ff)] = 99
ff
## [1] 1 2 99

df_na = data.frame(xx = c(1, NA, NA),
yy = c(1, 2, NA))
df_na[is.na(df_na$xx), "xx"] = 99
df_na
## xx yy
## 1 1 1
## 2 99 2
## 3 99 NA

주로 에러가 많이 나는 코드의 유형은 다음과 같다.

1
2
3
df_na[is.na(df_na), ] = 99 # 1
df_na[is.na(df_na$xx), ] = 99 # 2
df_na[is.na(df_na), "xx"] = 99 # 3

마치며

결측치 처리는 단순히 빼는 것이 가장 쉽지만, 실제 분석에서는 결측치를 특정값이나 통계량으로 채워넣는 것을 권장한다. 그리고 실 데이터를 다루다 보면 정형화되지 않은 데이터를 전처리 하는 것이 참 어려운데 이는 기본 함수의 충분한 이해와 원소 및 객체의 다각도로 다뤄본 경험이 없다면 매우 어렵게 느껴질 수 있다. 그리하여 다음 결측치 관련 포스팅에는 다음과 같이 조금 더 현실적인 결측치 처리를 다뤄보고자 한다.

  • 결측치를 통계량으로 대체하기
  • 결측치가 n개 이상인 변수만 처리하기
  • 결측치가 n개 이상인 row만 처리하기

<결측치 처리 시리즈>
R 결측치 처리 - 1
R 결측치 처리 - 2

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×