[R] Do It R 3편 데이터 전처리 및 가공
카테고리: R
💡 R을 처음 배우기 위해 공부한 책입니다.
책에서 공부한 내용을 정리하고, 이를 기반으로 직접 문제를 만들어 풀이해보는 식으로 학습했습니다.
KBO, MLB 야구 기록들을 csv 파일로 크롤링하여 문제에 사용했고,
야구 데이터 패키지인 Lahman
을 토대로 문제를 만들기도 했습니다.
책 소개
06. 데이터 전처리 및 가공
06-1. 조건에 맞는 데이터만 추출
분석에 적합하게 데이터를 가공하는 작업을 데이터 전처리(data Preprocessing)라고 한다. dplyr
은 데이터 전처리 작업에 가장 많이 사용되는 패키지이다. 우선 패키지와 데이터를 준비한다.
install.packages("dplyr")
library(dplyr) #패키지 로드
exam <- read.csv("csv_exam.csv") #변수에 저장
dplyr
패키지는 %>%
기호를 이용해 함수들을 나열하는 방식으로 코드를 작성한다. filter()
함수에 조건을 입력하면 조건에 해당되는 행만 출력된다. 1반인 학생들만 추출해보자. 파이썬처럼 등호 두개는 ‘같다’를 의미한다. 그리고 =! 기호를 이용하여 3반이 아닌 경우를 출력해보자. 비슷한 방식으로 여러 조건을 걸어보자.
exam %>% filter(class==1)
exam %>% filter(class!=3)
exam$avg <- (exam$math+exam$english+exam$science)/3 #3과목 평균 점수 파생변수
options(digits=4) #소수점 2째자리까지만
exam %>% filter(avg>=70) #평균 70점 이상만 추출
기호 &
를 사용하면 여러 조건을 모두 충족하는 데이터를 추출할 수 있다.
기호 |
를 사용하면 여러 조건 중 하나라도 충족하는 데이터를 추출할 수 있다.
%in%
기호와 c()
함수를 이용하면 코드가 더 간편해진다. %in%
는 지정한 조건 목록에 해당하는지 확인한다.
exam %>% filter(class==2 & avg>=65) #2반이면서 평균이 65 이상인 경우
exam %>% filter(math>=85 | english>=85) #수학 점수가 85점 이상이거나 영어 점수가 85점 이상인 경우
exam %>% filter(class %in% c(1,5)) #1반이나 5반인 경우
추출한 행으로 새 변수를 만들 수도 있다. 1반 데이터만 추출해서 1반의 평균 점수를 구해보자.
class1 <- exam %>% filter(class==1)
mean(class1$math) #46.25
mean(class1$english) #94.75
mean(class1$science) #61.5
mean(class1$avg) #67.5
❓ 문제.
자동차 배기량에 따라 도시 연비가 다른지 알아보려고 한다.
`displ`(배기량)이 4이하인 자동차와 5이상인 자동차 중 어떤 자동차의 `cty`(도시 연비)가 더 높은지 알아보자.
💡 답.
mpg <- as.data.frame(ggplot2::mpg) #데이터프레임으로 만들기
displ4 <- mpg %>% filter(displ<=4) #배기량이 4이하인 행만 추출해서 변수로 만들기
displ5 <- mpg %>% filter(displ>=5) #배기량이 5이상인 행만 추출해서 변수로 만들기
mean(displ4$cty) #18.7
mean(displ5$cty) #12.63
#배기량이 4이하인 자동차가 도시연비가 더 높다.
❓ 문제.
17kia_record.xlsx 파일을 데이터 프레임으로 불러오고,
**1)** 규정타석수 변수(`RMAB`)를 정의하라. (규정타석은 소속 팀 경기 수의 3.1배이다.)
**2)** 규정타석수를 충족한 선수들만 추출하여 `df_RMAB` 변수로 만들어라.
**3)** 규정타석수를 충족한 선수들의 평균 타율을 구하라.
💡 답.
#1)
RMAB <- 144*3.1
#2)
df_RMAB <- df_kia %>% filter(RMAB<=df_kia$타석)
#3)
mean(df_RMAB$타율) #0.3203
❓ 문제.
Baseball Reference에서 2021 batting record를 크롤링하여 “MLB batting record.xlsx”로 저장하고, 이를 데이터 프레임으로 불러오고,
1) 20-20 클럽을 달성한 선수들만 추출하여 `club20_20` 변수로 만들어라.
2) `select()`를 이용하여 club20_20 선수들의 이름, 홈런, 도루 컬럼을 추출하라.
3) 토론토 블루제이스 팀 선수들 중 규정타석을 채운 선수들만 추출하여 `tor` 변수로 만들고
이 선수들의 평균 타율을 구하라.
💡 답.
크롤링해온 데이터를 View한 모습이다.
#1)
club20_20 <- df_mlb %>% filter(df_mlb$SB>=20 & df_mlb$HR>=20)
#2)
club20_20 %>% select(Name, HR, SB)
Name HR SB
1 Bo Bichette 29 25
2 Ozzie Albies# 30 20
3 Cedric Mullins* 30 30
4 Trea Turner 28 32
5 Robbie Grossman# 23 20
6 Jose Ramirez# 36 27
7 Shohei Ohtani* 46 26
8 Randy Arozarena 20 20
9 Trevor Story 24 20
10 Fernando Tatis Jr. 42 25
#3)
tor <- df_mlb %>% filter(df_mlb$Tm=='TOR', PA>=162*3.1)
mean(tor$BA) #0.2812
06-2. 필요한 변수만 추출
필요한 행이 아니라 컬럼을 추출하기 위해선 select()
함수를 사용한다.
특정 변수만 제외하고 추출하는 방법도 있다. 제외할 변수명 앞에 -
기호만 붙이면 된다.
exam %>% select(math) #math변수(열)만 추출
exam %>% select(english) #english(열)만 추출
exam %>% select(english, science) #english, science열 추출
dplyr
함수들은 %>%
를 이용해 조합할 수 있다는 장점이 있다. filter()
와 select()
를 조합해보자. 이 때 %>%
를 기준으로 줄바꿈을 해주는 것이 좋다.
exam %>%
filter(class==1) %>%
select(math, english)
06-3. 순서대로 정렬하기
arrange()
를 이용하면 데이터를 원하는 순서로 정렬할 수 있다. 괄호 안에 정렬 기준으로 삼을 변수명을 입력하면 된다. 기본값은 오름차순이고, 내림차순을 원하면 desc()
로 변수를 감싸준다.
exam %>% arrange(math) #math 기준으로 오름차순 정렬
exam %>% arrange(desc(math)) #math 기준으로 내림차순 정렬
06-4. 파생변수 추가하기
앞서 파생변수 추가하는 법을 이미 배웠다. 이번엔 mutate()
함수로 파생변수를 추가하는 방법이다. 이 방법의 장점은 아까와는 달리 데이터프레임명을 반복해서 입력할 필요가 없다는 것이다. 때문에 훨씬 간결하게 코드를 작성할 수 있다.
ifelse()
를 적용하면 조건문으로 변수를 추가할 수 있다.
exam %>% mutate(avg = (math+english+science)/3) #평균점수를 나타내는 avg파생변수 추가
exam %>% mutate(test=ifelse(avg>=65, "pass", "fail")) #평균이 65를 넘으면 pass 못 넘으면 fail
❓ 문제.
mlb batting record를 불러오고, dplyr함수를 이용해 전처리를 하라
1) wOBA(가중출루율) 파생변수를 추가하라.
`가중 출루율 = (0.691×(볼넷-고의4구) + 0.722×몸에 맞는 볼 + 0.884×1루타 + 1.257×2루타 + 1.593×3루타 + 2.058×홈런) / (타수 + 볼넷 - 고의4구 + 희생플라이 + 몸에 맞는 볼)`
1) wOBA를 기준으로 정렬하여 상위 10명의 선수 데이터를 `df_mlb_wOBA_top`변수에 담자. 이 때 출력할 컬럼은 Name, Tm, OBP, wOBA이고, 규정타석을 채운 선수만 출력하도록 한다.
💡 답.
#1)
df_mlb <- df_mlb %>% mutate(wOBA = (0.691*(BB-IBB) + 0.722*HBP + 0.884*(H-X2B-X3B-HR) + 1.257*X2B + 1.593*X3B + 2.058*HR)/(AB+BB-IBB+SF+HBP))
#2)
df_mlb %>%
filter(df_mlb$PA >= 162*3.1)%>%
select(Name, Tm, OBP, wOBA) %>%
arrange(desc(wOBA))%>%
head(10)
Name Tm OBP wOBA
1 Bryce Harper* PHI 0.429 0.4357
2 Vladimir Guerrero Jr. TOR 0.401 0.4240
3 Juan Soto* WSN 0.465 0.4239
4 Fernando Tatis Jr. SDP 0.364 0.4081
5 Shohei Ohtani* LAA 0.372 0.3987
6 Nick Castellanos CIN 0.362 0.3960
7 Joey Votto* CIN 0.375 0.3960
8 Aaron Judge NYY 0.373 0.3915
9 Trea Turner TOT 0.375 0.3903
10 Bryan Reynolds# PIT 0.390 0.3888
06-5. 집단별로 요약하기
각 집단을 요약한 값을 구할 때는 group_by()
와 summarise()
를 사용한다. summary()
와 헷갈리지 말자. 보통 summarise()
는 group_by()
와 조합해 집단별 요약표를 만들 때 사용한다.
exam %>%
group_by(class) %>% #먼저 반별로 그룹화를 해주고
summarise(mean_math=mean(math)) #각 반의 수 평균을 집계
# A tibble: 5 x 2
class mean_math
<int> <dbl>
1 1 46.2
2 2 61.2
3 3 45
4 4 56.8
5 5 78
exam %>%
group_by(class) %>%
summarise(mean_math = mean(math),
median_math = median(math),
n=n())
tibble: 5 X 2
라는 것은 이 데이터가 5행 2열의 tibble 형태라는 걸 의미한다. group_by()
는 출력 결과를 데이터 프레임의 업그레이드 버전인 tibble로 출력한다. 변수명 아래 <int>
는 정수, <dbl>
은 소수점이 있는 숫자를 의미한다.
n()
은 데이터가 몇 행인지를 구한다. 여기선 반별로 집단을 나눴으니, 반별 학생수를 의미한다.
각 집단별로 다시 하위 집단을 나눌 수도 있다. group_by(기준변수1, 기준변수2)
식으로 작성하면 된다. mpg데이터에서 회사별로 집단을 나누고, 구동 방식별로 하위집단을 나눠보자.
mpg %>%
group_by(manufacturer, drv) %>% #manufacturer 변수를 기준으로 집단 나누고, drv 변수를 기준으로 하위 집단
summarise(mean_cty = mean(cty)) %>%
head(10)
manufacturer drv mean_cty
<chr> <chr> <dbl>
1 audi 4 16.8
2 audi f 18.9
3 chevrolet 4 12.5
4 chevrolet f 18.8
5 chevrolet r 14.1
6 dodge 4 12
7 dodge f 15.8
8 ford 4 13.3
9 ford r 14.8
10 honda f 24.4
❓ 문제.
회사별로 “midsize” 자동차의 도시 및 고속도로 통합 연비 평균을 구해 내림차순으로 정렬하고, 하위 5위를 출력하기
💡 답.
mpg %>%
group_by(manufacturer) %>% #회사별로 그룹화
filter(class == 'midsize') %>% #midsize 차종류 행만 추출
mutate(total = (hwy+cty)/2) %>% #도시 및 고속도로 통합 연비 변수 추가
summarise(total_mean = mean(total)) %>% #연비 평균 집계
arrange(desc(total_mean)) %>% #내림차순 정렬
tail(5) #하위 5개 회사 출력
❓ 문제.
mlb batting record에서 팀별로 26세 이하 선수들의 삼진대비볼넷 비율 평균을 구해 내림차순으로 정렬하라.
💡 답.
#만약 앞서 배운 방식대로 코드를 작성하면
df_mlb %>%
group_by(Tm) %>%
filter(Age<=26) %>%
mutate(SBP = BB/SO) %>%
summarise(sbp_mean = mean(SBP)) %>%
arrange(sbp_mean)
Tm sbp_mean
<chr> <dbl>
1 CHC 0.164
2 OAK 0.209
3 TBR Inf
4 ARI NaN
5 ATL NaN
6 BAL NaN
7 BOS NaN
8 CHW NaN
9 CIN NaN
10 CLE NaN
#이 같은 결과가 나온다.
#NaN은 분모, 분자가 모두 0이 되어 발생하는 에러고
#Inf는 분모가 0이 되어 infinite가 되는 에러다.
#이는 삼진과 볼넷이 0인 데이터가 포함되어서 발생한 문제다.
#이 문제를 해결하기 위해 적절한 전처리를 해주자
df_mlb_nona <- df_mlb %>%
filter(BB!=0 & SO!=0) #볼넷과 삼진이 모두 0이 아닌 행만 추출
df_mlb_nona %>%
group_by(Tm) %>% #팀 기준으로 그룹화
filter(Age<=26) %>% #26세 이하 행만 추출
mutate(SBP = BB/SO) %>% #삼진대비볼넷 변수 추가
summarise(sbp_mean = mean(SBP),
BB_sum = sum(BB),
SO_sum = sum(SO))%>%
arrange(desc(sbp_mean)) #삼진대비볼넷 평균 기준 내림차순
librar
Tm sbp_mean BB_sum SO_sum
<chr> <dbl> <int> <int>
1 CIN 0.583 145 308
2 WSN 0.567 227 309
3 NYY 0.505 112 264
4 TOR 0.491 242 487
5 CHW 0.485 224 530
6 LAA 0.476 137 369
7 TBR 0.459 284 664
8 NYM 0.454 116 279
9 DET 0.408 155 528
10 STL 0.405 208 609
#신시내티가 가장 준수한 기록을 보유하고 있다
06-6. 데이터 합치기
여러 데이터를 하나의 데이터로 합치는 것도 가능하다. 먼저 데이터를 가로로 합치는 방법을 알아보자. 학생 다섯 명이 중간고사와 기말고사를 봤다고 가정하고 2개의 데이터 프레임을 만들어보자. left_join()
함수를 쓰면 데이터를 가로로 합칠 수 있다. 괄호 안에 데이터 프레임명을 나열하고, 기준으로 삼을 변수명을 by파라미터에 지정하면 된다.
term1 <- data.frame(id=c(1,2,3,4,5),
midterm=c(60,80,70,90,85))
term2 <- data.frame(id=c(1,2,3,4,5),
final=c(70,83,65,95,80))
total <- left_join(term1,term2,by="id") #id를 기준으로 두 데이터를 가로로 합치기
total
left_join()
을 응용하면 특정 변수를 기준으로 다른 데이터를 불러올 수도 있다. 예를 들어 각 반 학생의 시험 점수인 exam데이터를 분석하는데, 반별 담임교사 명단 데이터프레임에서 교사명을 exam에 합치려는 경우를 생각해보자.
t_list <- data.frame(class=c(1,2,3,4,5),
teacher=c("kim", "lee","park","choi","jung")) #반별 담임교사 명단
exam_new <- left_join(exam, t_list, by="class")
exam_new
id class math english science teacher
1 1 1 50 98 50 kim
2 2 1 60 97 60 kim
3 3 1 NA 86 78 kim
4 4 1 30 98 58 kim
5 5 2 25 80 65 lee
6 6 2 50 89 98 lee
이번에는 데이터를 세로로 합치는 방법을 알아보자. 학생 5명이 시험을 먼저 보고 5명이 나중에 보았을 때 이들을 세로로 합치는 상황이다. bind_rows()
를 이용하면 데이터를 세로로 합치게 된다.
group1 <- data.frame(id=c(1,2,3,4,5),
test=c(60,80,70,90,85))
group2 <- data.frame(id=c(6,7,8,9,10),
test=c(70,83,65,95,80))
groupall <- bind_rows(group1,group2)
groupall
❓ 문제.
Lahman패키지의 Batting 데이터와 Fielding 데이터를 가로로 합치려고 한다. 이 때 2020년 LA에인절스의 데이터만을 합쳐서 가져온다.
💡 답.
df_bat <- Batting %>%
filter(yearID==2020 & teamID=="LAA") #2020년 LAA팀 데이터만을 추출한다
df_bat_new <- left_join(df_bat, Fielding, by="playerID") #두 데이터를 선수 이름 기준으로 합친다
View(df_bat_new)