Date:     Updated:

카테고리:

태그: , ,


💡 R을 처음 배우기 위해 공부한 책입니다. 책에서 공부한 내용을 정리하고, 이를 기반으로 직접 문제를 만들어 풀이해보는 식으로 학습했습니다. KBO, MLB 야구 기록들을 csv 파일로 크롤링하여 문제에 사용했고, 야구 데이터 패키지인 Lahman을 토대로 문제를 만들기도 했습니다.


책 소개

Do it! 쉽게 배우는 R 데이터 분석


07. 결측치, 이상치 정제

07-1. 결측치 제거하기

결측치(missing value) 는 누락된 값을 의미한다. 결측치가 있으면 에러가 뜨는 경우가 빈번하여 이를 꼭 정제해주어야 한다.

결측치는 NA로 표기된다. 만약 이를 따옴표로 감싸 “NA”가 된다면 결측치가 아니라 문자열”NA”를 의미하게 된다. 결측치를 확인하기 위해 간단한 데이터 프레임을 만들어보자.

df <- data.frame(sex=c("M","F",NA,"M","F"),
                 score=c(5,4,3,4,NA))
df
    sex score
1    M     5
2    F     4
3 <NA>     3
4    M     4
5    F    NA

이 때 문자로 구성된 변수는 그냥 NA로 나오지 않고 <NA>로 출력된다.

is.na()함수를 사용하면 결측치를 확인할 수 있다. 결측치라면 TRUE, 결측치가 아니라면 FALSE를 반환한다.

is.na(df)
      sex score
[1,] FALSE FALSE
[2,] FALSE FALSE
[3,]  TRUE FALSE
[4,] FALSE FALSE
[5,] FALSE  TRUE

만약 결측치가 있는 데이터를 함수에 적용하면 NA 에러라 뜬다.

이제 결측치를 제거해보자. filter()함수를 이용해 결측치가 아닌 값만 추출하면 될 것이다. 즉 filter(!is.na(변수명))로 작성하면 된다. 그리고 이것을 새로운 변수로 만들어주면 함수를 적용해도 에러 없이 작동할 것이다.

df %>%
  filter(!is.na(score) & !is.na(sex)) #score의 결측치와 sex의 결측치 모두 제거

sum(df_new$score) #13

filter()에 일일이 변수를 지정하여 제거하는 방법보단, na.omit()를 이용하여 변수를 지정하지 않고도 한 번에 결측치를 없애는 것이 낫다.

na.omit()은 결측치가 하나라도 있으면 모조리 제거한다는 편리함이 있지만, 분석에 필요한 행까지도 모두 삭제해버릴 수 있다는 단점이 있다. 따라서 경우에 따라 na.omit()filter()를 잘 구분하여 사용하여야 한다.

df_new2 <- na.omit(df)
df_new2

수치 연산 함수들은 결측치를 제외하고 연산하게끔 하는 na.rm 파라미터를 지원한다. na.rmTRUE로 설정하면 결측치를 제외하고 함수를 적용한다.

mean(df$score, na.rm=T) #4

#summarise도 na.rm 지원
exam[c(3,8,15),"math"] <- NA #[행위치, 열위치]. 3,8,15행의 math열에 NA 부여
exam
exam %>%
  summarise(mean_math = mean(math, na.rm=T),
            median_math = median(math, na.rm=T))


07-2. 결측치 대체하기

데이터가 작고 결측치가 많은 경우엔 결측치를 제거하면 많은 데이터가 손실되어 결과가 왜곡될 수도 있다. 이런 경우엔 결측치를 대체(imputation)해주는 편이 현명하다.

일반적인 경우에 결측치는 평균으로 대체한다.

mean(exam$math, na.rm=T) #55.23529
exam$math <- ifelse(is.na(exam$math),55,exam$math) #결측치라면 55(평균), 결측치가 아니라면 원래값
table(is.na(exam$math)) #FALSE 20.

문제.

Lahman Batting 데이터에서 팀별 통산 BABIP을 내림차순으로 출력하려 한다. 
이 때 결측치를 적절히 제거하여 에러 없이 값을 출력하라.

💡 답.

batting_nona <- Batting %>%
  filter(!is.na(SF)&(AB-SO-HR+SF)!=0) #20세기 초반까지는 SF데이터가 집계되지 않았다.
#때문에 SF가 결측치인 데이터들을 제거해줘야 한다.
#BABIP의 분모가 0이 되는 데이터들도 제거해준다.

team_BABIP <- batting_nona %>%
  group_by(teamID) %>% #팀별로 그룹화한다
  mutate(BABIP=(H-HR)/(AB-SO-HR+SF)) %>% #BABIP 파생변수를 만든다
  summarise(mean_BABIP=mean(BABIP)) %>% #팀별로 BABIP평균을 집계한다
  arrange(desc(mean_BABIP)) #BABIP의 내림차순으로 정렬한다
head(team_BABIP)

  teamID    mean_BABIP
  <fct>       <dbl>
1 TEX         0.278
2 COL         0.275
3 ANA         0.274
4 KCA         0.271
5 TBA         0.269
6 BOS         0.268
#텍사스의 통산 babip이 가장 우수한 것을 알 수 있다.


07-3. 이상치 제거하기

이상치는 크게 두 가지로 나눠볼 수 있다. 논리적으로 존재할 수 없는 값(EX: 남,여가 아닌 제3의 성)과 극단적인 값이다.

논리적으로 존재할 수 없는 값들은 결측값으로 대체를 해주어야 한다. 1은 남자, 2는 여자를 나타내는 데이터를 만들자. 이 때 3이라는 이상치를 삽입한다. score도 1~5의 값을 가져야 한다고 가정하자. 이 때 6이라는 이상치를 삽입한다.

outlier <- data.frame(sex=c(1,2,1,3,2,1),
                      score=c(5,4,3,4,2,6))

table(outlier$sex)
1 2 3 
3 2 1 #이상치 3이 하나 껴있다.

#성별이 3이라면 NA로 바꾼다.
outlier$sex <- ifelse(outlier$sex==3,NA,outlier$sex)
#score가 6이라면 NA로 바꾼다.
outlier$score <- ifelse(outlier$score==6,NA,outlier$score)

#이제 결측치를 제거해주고 성별에 따른 score 평균을 구해보자
outlier %>%
  filter(!is.na(sex) & !is.na(score)) %>%
  group_by(sex) %>%
  summarise(mean_score=mean(score))

극단적으로 크거나 작은 값을 제거하기 위해선 정상 범주의 기준을 정해야 한다. Boxplot을 이용해 중심에서 크게 벗어난 값을 극단치로 간주해보자.

boxplot(mpg$hwy)
boxplot(mpg$hwy)$stats #Boxplot 통계치 출력
      [,1]
[1,]   12 #아래쪽 극단치 경계
[2,]   18 #1사분위수
[3,]   24 #2사분위수
[4,]   27 #3사분위수
[5,]   37 #위쪽 극단치 경계_

극단치 경계가 위로는 37, 아래로는 12에 위치해있으므로 12~37을 벗어나면 극단치로 분류하여 결측값으로 만들면 된다.

mpg$hwy <- ifelse(mpg$hwy<12 | mpg$hwy>37, NA, mpg$hwy) #12미만, 27초과인 경우 NA
mpg %>%
  group_by(drv) %>% #구동방식별로 그룹화
  summarise(mean_hwy=mean(hwy, na.rm=T)) #결측치는 제거한 채 평균 계산

   drv   mean_hwy
  <chr>    <dbl>
1 4         19.2
2 f         27.7
3 r         21

태그: , ,

카테고리:

업데이트: