Date:     Updated:

카테고리:

태그: , ,


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


책 소개

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


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` 변수로 만들고
 이 선수들의 평균 타율을 구하라.

💡 답.

df_mlb

크롤링해온 데이터를 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)


태그: , ,

카테고리:

업데이트: