본문 바로가기
프로그래밍 언어/R

R 6. 데이터 정제하기

by Jinger 2024. 1. 30.

서론

    현장에서 만들어진 실제 데이터는 오류를 포함하고 있기 때문에 분석하기 전에 오류를 수정해야 한다. 이 과정을 데이터 정제라고 부른다. 여기서는 대표적으로 결측치와 이상치를 찾고 제거하는 방법을 다룬다.


결측치

    결측치(Missing Value)는 누락된 값, 비어 있는 값을 의미한다. 현장에서 만들어진 실제 데이터는 수집 과정에서 발생한 오류로 인해 결측치를 포함하고 있을 때가 많다. 결측치가 있으면 함수가 적용되지 않거나 분석 결과가 왜곡되는 문제가 발생한다.

결측치 찾기

    R에서는 결측치를 'NA'로 표기한다. 문자로 구성된 변수는 'NA'가 <>에 감싸진 형태로 출려된다. NA 앞뒤에 따옴표가 있다면 이는 영문자 "NA"를 의미한다.

    "is.na()"를 이용하면 데이터에 결측치가 들어 있는 지있는지 알 수 있다. "is.na()"에 앞에서 만든 'df'를 적용하면 결측치는 'TRUE', 결측치가 아닌 값은 'FALSE'로 표시해 데이터를 출력한다. "is.na()"를 "table()"에 적용하면 데이터에 결측치가 총 몇 개 있는지 출력한다.

    결측치를 제거하려면 데이터 전체가 아니라 구체적으로 어떤 변수에 결측치가 있는 지 알아야 한다. "table(is.na())에 변수명을 지정하면 해당 변수에 결측치가 몇 개 있는지 알 수 있다. 또한, 결측치가 포함된 데이터를 함수에 적용하면 정상적으로 연산되지 않고 "NA"가 출력된다.

결측치 제거하기

    결측치 있는 행을 제거하기 위해 "is.na()"와 "filter()"에 적용하면 결측치가 있는 행을 제거할 수 있다. 먼저 결측치가 있는 행만 추출해보자.

    "is.na()" 앞에 "아니다(not)"을 의미하는 "!"를 붙여 "!is.na()"를 입력하면 NA가 아닌 값, 즉 결측치가 아닌 값을 의미한다.

    위와 같이 추출한 데이터로 데이터 프레임을 만들면 결측치가 없는 데이터가 된다. 결측치가 제거됐으니 수치 연산 함수를 적용하면 결과가 정상적으로 출력된다. 만약 여러 변수 동시에 결측치 없는 데이터 추출하려면 "filter()"에 "&" 기호를 이용해 조건을 나열하면 여러 변수에 모두 결측치가 없는 행을 추출할 수 있다.

    앞에서는 일일이 변수를 지정해 결측치가 있는 행을 제거하도록 코드를 구성했다. 그러나 이 작업은 데이터가 많아질수록 번거롭다. 그렇기에 결측치가 하나라도 있으면 제거하는 "na.omit()"을 이용하면 변수를 지정하지 않고 결측치가 있는 행을 한 번에 제거할 수 있다. 그러나 이 함수는 결측치를 모두 한 번에 제거하기에 간편하지만 분석에 필요한 행까지 손실된다는 단점이 있다. 따라서 filter()를 이용해 분석에 사용할 변수의 결측치만 제거하는 방식을 추천한다.

더보기

    예를 들어, 성별, 소득, 지역 세 가지 변수로 구성된 데이터 분석하는 상황이라고 하자. 분석 목적이 성별에 따른 소득의 차이를 알아보는 것이라면 성별, 소득의 두 변수에서만 결측치가 있는 행을 제거하면 된다. 그런데 이때 "na.omit()"을 사용하면 성별, 소득은 결측치가 아니지만 지역이 결측치인 행도 제거된다.

결측치 건들지 않고 작업

    "mean()"과 같은 수치 연산 함수들은 결측치를 제외하고 연산하도록 설정하는 "na.rm" 파라미터를 지원한다. "na.rm"을 "TRUE"로 설정하면 결측치를 제외하고 함수를 적용하기 때문에 일일이 결측치를 제거하는 절차를 건너뛰고 바로 분석할 수 있다. 하지만 모든 함수가 "na.rm"을 지원하는 것은 아니다.

mean(df$score, na.rm = T)	# 결측치 제외하고 평균 구하기

    데이터가 크고 결측치가 얼마 없는 경우에는 결측치를 제거하고 분석하더라도 무리가 없다. 하지만 데이터가 작고 결측치가 많은 경우 결측치를 제거하면 너무 많은 데이터가 손실돼 분석 결과가 왜곡되는 문제가 발생한다. 그래서 결측치를 제거하는 대신 다른 값을 채워 넣는 방법 '결측치 대체법(Imputation)'을 사용한다. 결측치를 대체하는 방법에는 평균이나 최빈값 같은 대푯값을 구해 모든 결측치를 하나의 값으로 일괄 대체하는 방법과 통계 분석 기법으로 각 결측치의 예측값을 추정해 대체하는 방법이 있다.

exam <- read.csv("csv_exam.csv")
exam[c(3, 8, 15), "math"] <- NA	# 3, 8, 15 행의 math에 NA 할당
mean(exam$math, na.rm =T)	# math 평균 산출
## [1] 55.23
exam$math <- ifelse(is.na(exam$math), 55, exam$math)	# math가 NA면 55로 대체
table(is.na(exam$math))	# 결측치 빈도표 생성
## FALSE
## 20
mean(exam$math)	# math 평균 산출
## [1] 55.2

이상치

    정상 범주에서 크게 벗어난 값을 '이상치(Outlier)'라고 한다. 데이터 수집 과정에서 오류가 발생할 수 있기 때문에 현장에서 만들어진 실제 데이터에는 이상치가 포함될 수 있다. 혹은 오류는 아니지만 굉장히 드물게 발생하는 극단적인 값이 있을 수도 있다. 이상치가 포함되어 있으면 분석 결과가 왜곡되기 때문에 분석에 앞서 이상치를 제거하는 작업을 해야 한다.

존재할 수 없는 값

    논리적으로 존재할 수 없는 값이 데이터에 포함되어 있는 경우가 있다. 예를 들어, 게임을 플레이 한 자는 1, 안 한 자는 2라고 할 때 정의하지 않은 3이라는 값이 들어 있는 경우이다. 이는 분명한 오류이기 때문에 결측치로 변환 후 분석에서 제외하면 된다.

outlier <- data.frame(player = c(1,2,1,3,1,2), game_score=c(3,4,1,5,3,6))
table(outlier$player)	# 이상치 확인
table(outlier$game_score)
outlier$player <- ifelse(outlier$player == 3, NA, outlier$player)	# 이상치이면 NA 할당
outlier$game_score <- ifelse(outlier$game_score > 5, NA, outlier$game_score)	# 이상치이면 NA 할당
outlier %>% filter(!is.na(player) & !is.na(game_score)) %>%
				group_by(player)%>%
            			summarise(mean_score = mean(game_score)) # 이상치 변환 후 평균 구하기

극단적인 값

    논리적으로 존재할 수 있지만 극단적으로 크거나 작은 값을 '극단치'라고 한다. 예를 들어, 몸무게 변수에 200kg 이상의 값이 있다면, 존재할 가능성은 있지만 굉장히 드문 경우기 때문에 분석하기 전에 제거해야 한다. 극단치를 제거하려면 우선 어디까지를 정상 범위로 볼 것인지를 정해야 한다. 보통 논리적으로 판단하여 정하거나 통계적인 기준으로 정한다. 대표적인 예로 박스플롯(box plot)이 있다.

boxplot(mpg$hwy)	# 박스플롯 그리기
boxplot(mpg$hwy)$stats	# 박스플롯 통계치 출력

##       [,1]
## [1,]    12	# 아래쪽 극단치 경계
## [2,]    18	# 1사분위수
## [3,]    24	# 중앙값
## [4,]    27	# 3사분위수
## [5,]    37	# 위쪽 극단치 경계
## attr(,"class")
##         1
## "integer"

mpg$hwy <- ifelse(mpg$hwy < 12 | mpg$hwy > 37, NA, mpg$hwy)	# 이상치이면 NA 할당
table(is.na(mpg$hwy))	# 이상치 확인
mpg %>% group_by(drv) %>% summaruse(mean_hwy = mean(hwy, na.rm = T))	# NA 제외한 평균 구하기

주섬주섬

  • 박스 플롯은 아래 사이트 혹은 데이터 분석 관련 강의를 통해 배우는 것을 추천한다.
  • 아래는 연습 문제이다.
더보기

결측치 문제를 풀기 전 'mpg'에 결측치를 넣자.

mpg <- as.data.frame(ggplot2::mpg)
mpg[c(65, 124, 131, 153, 212), "hwy"] <- NA

1. 'drv'(구동 방식) 별로 'hwy'(고속도로 연비) 평균이 어떻게 다른지 알아보자. 분석을 하기 전에 우선 두 변수에 결측치가 있는지 확인하고 'drv'변수와 'hwy' 변수에 결측치가 몇 개 있는지 알아보자.

2. 'filter()'를 이용해 'hwy' 변수의 결측치를 제외하고, 어떤 구동 방식의 'hwy' 평균이 높은지 알아보자. 하나의 'dplyr' 구문으로 만들어야 한다.

 

이상치 문제를 풀기 전 'mpg'에 이상치를 넣자.

mpg <- as.data.frame(ggplot2::mpg)
mpg[c(10, 14, 58, 93), "drv"] <- "k"
mpg[c(29, 43, 129, 203), "cty"] <- c(3, 4, 39, 42)

1. 'drv'에 이상치가 있는지 확인하자. 이상치를 결측 처리한 후 이상치가 사라졌는지 확인하자. 결측 처리를 할 때는 '%>%' 기호를 활용하자.

2. 박스플롯을 이용해 'cty'에 이상치가 있는 지 확인하자. 박스플롯의 통계치를 이용해 정상 범위를 벗어난 값을 결측 처리한 후 다시 상자 그림을 만들어 이상치가 사라졌는 지 확인하자.

3. 두 변수의 이상치를 결측 처리 했으니 이제 분석하자. 이상치를 제외한 다음 'drv'별로 'cty' 평균이 어떻게 다른지 알아보자. 하나의 'dplyr' 구문으로 만들어야 한다.

  • 아래는 정리이다.
더보기
## 1. 결측치 정제하기
# 결측치 확인
table(is.na(df$score))

# 결측치 제거
df_nomiss <- df %>% filter(!is.na(score))

# 여러 번수 동시에 결측치 제거
df_nomiss <- df %>% filter(!is.na(score) & !is.na(sex))

# 함수의 결측치 제외 기능 이용하기
mean(df$score, na.rm =T)
exam %>% summarise(mean_math = mean(math, na.rm = T))

## 2.이상치 정제하기
# 이상치 확인
table(outlier$sex)

# 결측 처리
outlier$sex <- ifelse(outlier$sex == 3, NA, outlier$sex)

# boxplot으로 극단치 기준 찾기
boxplot(mpg$hwy)$stats

# 극단치 결측 처리
mpg$hwy <- ifelse(mpg$hwy < 12 | mpg$hwy > 37, NA, mpg$hwy)

참고

 

Understanding Boxplots

Boxplot (box plot) are graphs that tell you how your data’s values are spread out. Here’s how to read a boxplot and even create your own.

builtin.com

 

반응형

'프로그래밍 언어 > R' 카테고리의 다른 글

R 8. 데이터 분석하기  (0) 2024.02.01
R 7. 그래프 만들기  (1) 2024.01.31
R 5. 데이터 가공하기  (0) 2024.01.29
R 4. 데이터 파악하기  (2) 2024.01.27
R 3. 데이터 프레임  (1) 2024.01.26

댓글