[R] Do It R 4편 결측치, 이상치 정제
카테고리: R
💡 R을 처음 배우기 위해 공부한 책입니다.
책에서 공부한 내용을 정리하고, 이를 기반으로 직접 문제를 만들어 풀이해보는 식으로 학습했습니다.
KBO, MLB 야구 기록들을 csv 파일로 크롤링하여 문제에 사용했고,
야구 데이터 패키지인 Lahman
을 토대로 문제를 만들기도 했습니다.
책 소개
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.rm
을 TRUE
로 설정하면 결측치를 제외하고 함수를 적용한다.
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