聰明不如鈍筆
총명불여둔필
assignment Kidult

최대한 친절하게 쓴 R로 데이터 뽑아내기(feat. dplyr)

access_time 2018.08.26 23:52


R는 마이크로소프트(MS) 엑셀만큼이나 데이터도 아주 잘 처리합니다. 특히 해들리 위컴 박사가 개발한 dplyr 패키지와 함께라면 더더욱 그렇습니다. 이번 포스트에서는 프로야구를 처음 시작한 1982년부터 지난해(2017년)까지 팀 타격 데이터를 가지고 dplyr를 어떻게 활용하는지 알아보도록 하겠습니다. 


인터넷 포털 사이트 등에서 dplyr를 찾아 이 포스트를 찾아오신 분이라면 이미 R가 무엇인지 잘 알고 계실 겁니다. 혹시 모르시는 분들께 R 홈페이지에 올라온 대로 설명드리면 R는 통계 계산과 그래픽에 활용하는 무료 소프트웨어 환경(R is a free software environment for statistical computing and graphics)"입니다.


무료니까 누구든 이 페이지에서 원하시는 R 버전을 선택해 설치하실 수 있습니다. 2018년 8월 현재 최신 버전은 3.5.1이며 MS 윈도에 이를 설치하시려면 이 링크를 누르시면 됩니다. 이 포스트 제목에는 분명 '최대한 친절하게'라고 썼지만 프로그램 설치법까지 모르실 거라고는 생각하지 않으니 넘어가도록 하겠습니다.


설치를 마치시고 R를 실행하시면 아래 화면이 나타납니다. 이 창을 흔히 'R 콘솔(Console)'이라고 부릅니다. 창 제목에도 그렇게 나와 있습니다.



여기서 '>' 표시 뒤에 명령어를 입력하면 프로그래밍(코딩)을 진행할 수 있습니다. 코딩은 '이러저러한 게 하고 싶다'고 컴퓨터에게 알려주는 일을 뜻합니다. 제가 "print('Hello, World!')"라고 쓴 건 Hello, World!를 (화면에) 출력하고 싶다고 컴퓨터에게 알려준 겁니다. 이 "Hello, World!" 프로그램은 코딩을 처음 배울 때 제일 많이 쓰는 기본 예제입니다. 


이 22글자도 치기 귀찮으시다면 '1+1'을 입력해 보세요. 그러면 '2'를 표시할 겁니다. 기껏 R를 깔았는데 어떻게 사용해야 할지 모르실 때는 계산기로 쓰셔도 된다는 뜻입니다. 물론 컴퓨터에 이미 계산기 프로그램이 들어 있는 게 함정이기는 하지만 말입니다. (네, 재미없는 농담 죄송합니다.)


이제 두 번째 명령어(함수)를 배울 차례가 됐습니다. 두 번째 함수는 R에 패키지를 설치하라는 뜻인 'install.packages()'입니다. 패키지는 R에 기본으로 들어 있지 않은 각종 기능을 추가할 수 있는 묶음이라고 생각하시면 쉽습니다.


그렇다면 이번에 우리가 설치해야 하는 패키지 이름은 뭘까요? 맞습니다. 'dplyr'입니다. dplyr를 설치하시려면 아래처럼 입력하시면 그만입니다.

install.packages('dplyr')

R에서는 패키지를 설치했다고 곧바로 사용할 수 있게 되는 건 아닙니다. 패키지를 불러오는 절차를 한 번 더 거쳐야 합니다. 이때는 'library()'라는 함수를 쓰시면 됩니다.

library('dplyr')

아무 반응도 없다고요? 네, 그러면 dplyr를 불러오는 데 성공한 겁니다. 축하드립니다. 이제 누군가 dplyr 이야기를 꺼내면 '나도 안다'고 하실 수 있게 됐습니다. 


새로운 패키지를 불러들이고 나면 사용할 수 있는 함수가 늘어납니다. dplyr에서 제일 중요한 함수는 아래 있는 여섯 가지입니다. 이번 포스트도 이 여섯 가지 함수에 초점을 맞추게 됩니다.


▌dplyr 주요 함수 

 함수  기능  사용법
 arrange()  행 정렬  arrange(데이터, 변수 이름)
 filter()  행 추출  filter(데이터, 조건)
 group_by()  행 결합  group_by(조건)
 mutate()  열 추가  mutate(데이터, 새 변수 = 계산식)
 select()  열 선택  select(데이터, 변수 이름 또는 인덱스)
 summarise()  행 요약  summarise(데이터, 새 변수 = 계산식)



데이터 불러와서 확인하기

이 여섯 가지 함수를 활용하려면 일단 처리해야 할 데이터가 있어야겠죠? 앞서 말씀드린 것처럼 1982~2017 프로야구 팀 타격 기록을 활용하도록 하겠습니다. 아래 링크에서 내려받으시면 됩니다.


kbo.csv


이 데이터는 쉼표로 구분된 값(CSV·Comma Separated Value)이라는 형태로 되어 있습니다. 기본적으로 텍스트 파일에서 각 열을 쉼표로 구분한 형태라고 생각하시면 됩니다. 이 파일을 확인하는 가장 쉬운 방법은 MS 엑셀을 활용하시는 것. 컴퓨터에 이미 MS 엑셀이 깔려 있다면 그냥 더블클릭만으로 내용을 확인하실 수 있습니다. 아니면 아래처럼 구글 스프레드시트를 활용하셔도 됩니다. 이번 데이터는 행이 284개, 열이 21개인 구조입니다. 



R 역시 CSV 파일을 읽을 수 있도록 함수를 제공하고 있습니다. 이름 그대로 'read.csv()'가 그 주인공. 이 파일을 읽으시려면 read.csv('kbo.csv')라고만 치셔도 무방합니다. 단, 이때 이 파일이 R 작업 디렉토리(폴더)에 들어 있어야 합니다. 작업 디렉토리는 기본적으로 '내 문서'입니다. 그러니까 한번도 작업 폴더를 바꾸신 적이 없다면 내 문서에 이 파일을 내려받으시면 됩니다.


만약 작업 디렉토리를 바꾸고 싶으실 때는 메뉴에서 File - Change dir...를 선택하시면 됩니다. 이게 제일 간단하고 쉬운 방법입니다. 콘솔에 직접 입력하고 싶으실 때는 'setwd()'라는 함수 뒤에 원하는 폴더를 쓰셔도 같은 효과를 경험하실 수 있습니다. 


이렇게 원하는 위치에 파일 저장을 마치셨으면 진짜 파일을 불러옵니다.

read.csv('kbo.csv')

파일 내용이 잘 보였으리라 믿습니다. 그런데 이렇게 그냥 내용을 읽기만 하는 걸로는 다음 번 작업에 활용하기가 곤란합니다. 그래서 컴퓨터 메모리 안에 빈 방을 만들어 이 내용을 담아두는 방법을 씁니다. 프로그래밍에서는 이런 빈 방을 '변수'라고 부릅니다.


R에서 변수에 어떤 값을 넣을 때는 '<-' 또는 '=' 기호를 씁니다. 이번에는 kbo라는 빈 방에 이 결과를 넣어보도록 하겠습니다. 이렇게 치시면 됩니다.

kbo <- read.csv('kbo.csv')

이번에는 아무 내용도 출력하지 않습니다. 변수 내용을 확인하시려면 그냥 변수 이름만 치시면 됩니다. > 다음에 kbo라고 쓰시면 print(kbo)라고 치셨을 때와 같은 화면이 나타날 겁니다.


문제가 있다면 자료가 너무 많아서 어떤 모양인지 알아보기가 쉽지 않다는 것. 이럴 때는 head()를 써보시면 좋습니다. head()는 어떤 데이터에서 처음 여섯 줄만 보여주는 구실을 합니다.

head(kbo)


텍스트 형태로 화면에 표시하다 보니 폭이 잘 맞지 않지만 일단 원하는 자료는 다 들어왔습니다. 여기서 눈에 띄는 또 한 가지 사실은 '2루타', '3루타'로 되어 있던 열 이름 앞에 'X'가 붙었다는 점입니다. R에서는 열 이름을 숫자로 시작할 수 없기 때문에 자동으로 이렇게 바뀐 겁니다. '몸에 맞는 공'이 '몸에.맞는.공'으로 바뀐 건 공백 때문입니다.


혹시 이런 생각하시는 분은 안 계신가요? '왜 여섯 줄이냐? 나는 다섯 줄만 보고 싶다.' 걱정 없습니다. head(변수, 숫자) 형태로 쓰시면 처음부터 원하는 행까지 데이터 확인이 가능합니다.

거꾸로 'tail()'이라는 함수도 있습니다. head()와 반대로 끝에서부터 자료를 보여주는 구실을 합니다. tail()도 head()와 마찬가지로 tail(변수, 숫자) 형태로 쓰실 수 있습니다.


이제 dplyr 공부를 시작할 준비를 모두 마쳤습니다. 원래 시험 때도 공부할 준비를 마쳤으면 잠시 쉬어주는 게 예의. 기지개라도 한번 켜시면서 다음으로 넘어가 볼까요?



데이터 정렬 그리고 파이프

데이터를 처리할 때 꼭 필요한 기능을 꼽으라면 역시 정렬입니다. dplyr로 데이터를 정렬할 때 쓰는 함수는? 네, 위에서 보신 것처럼 arrange()입니다.


지금 우리가 쓰고 있는 데이터는 기본적으로 '연도'를 오름차순으로 정렬한 형태입니다. 이걸 팀 이름 순서로 바꾸려면 어떻게 하면 될까요?


arrange()는 arrange(데이터, 변수) 순서로 활용합니다. 이때 데이터는 'kbo'이고, 변수는 '팀'이 됩니다. 그렇다면 arrange(kbo, 팀)이라고 입력하면 우리가 원하는 결과를 얻을 수 있을 겁니다. 결과가 다 나오면 너무 기니까 head() 함수를 써서 확인해 보겠습니다. 이렇게 함수를 겹쳐 쓸 때는 어떤 함수 안에 다른 함수를 포함하면 그만입니다. 

head(arrange(kbo, 팀))


일단 잘 나왔습니다. KIA가 제일 먼저 나온 건 알파벳부터 오름차순으로 정렬했기 때문입니다. 그러면 내림차순으로 정렬할 때는 어떻게 하면 될까요? 이때는 변수 이름 앞에 desc만 쓰면 됩니다. desc는 내림차순을 뜻하는 'descending'을 줄인 말입니다.


그렇다면 arrange(kbo, desc(팀))이라고 쓰면 될 테도 그 결과는 아래와 같습니다. 

arrange(kbo, desc(팀))


물론 head()만 이렇게 쓸 수 있는 건 아닙니다. tail(arrange(kbo, desc(팀)), 5) 같은 표현도 가능합니다. 이러면 팀 이름을 내림차순으로 정렬한 데이터 가운데 끝에서 다섯 번째까지 데이터를 출력하게 됩니다.

tail(arrange(kbo, desc(팀)), 5)


지금 보기는 식이 간단하지만 명령어가 길어지면 이렇게 함수(함수(함수(함수())))로 표시하기가 쉽지 않게 됩니다. 


그래서 dplyr는 파이프(pipe)라는 기능을 가지고 있습니다. 파이프가 한 곳에서 다른 곳으로 물 같은 액체를 보낼 수 있게 도와주는 것처럼 이 녀석도 함수에서 다른 함수로 자료를 손쉽게 보내주는 구실을 합니다. R에서 파이프는 '%>%'로 표시합니다.


바로 위에서 쓴 명령어를 파이프를 넣어 바꾸면 arrange(kbo, desc(팀)) %>% tail(, 5)로 바꿔쓸 수 있습니다.

arrange(kbo, desc(팀)) %>% tail(, 5)


수식이 간단해지는 것 말고 장점이 하나 더 있습니다. tail(arrange())로 쓴 것보다 이 쪽이 어떤 작업을 하는지 한눈에 더 잘 들어오지 않나요? 



필요한 데이터만 뽑아내기

dplyr에는 데이터를 추출하는 함수가 두 개 들어있습니다. 행을 추출할 때는 filter(), 열을 추출할 때는 select()입니다. 


그렇다면 2017년 데이터만 뽑아내고 싶을 때는 어떻게 하면 될까요? filter()는 filter(데이터, 조건) 형태로 씁니다. 데이터는 계속 kbo입니다. 어느 해 데이터인지는 '연도'라는 열에 들어 있습니다. 그러면 filter(kbo, 연도=2017)이 될 겁니다. 


그런데 이렇게 치면 'Error: `연도` (`연도 = 2017`) must not be named, do you need `==`?'라고 에러 메시지가 뜹니다. 저 설명이 알려주고 있는 것처럼 조건에서 똑같다고 표시할 때는 '='가 아니라 '=='라고 써야 합니다. '='는 어떤 변수에 데이터를 넣으라는 뜻이니까요.


그래서 정답은 filter(kbo, 연도==2017)이 됩니다. 파이프를 써서 앞부분만 확인하려면? 네, 그렇습니다. filter(kbo, 연도==2017) %>% head()가 정답니다.

filter(kbo, 연도==2017) %>% head()


그러면 2017년 자료 중에서 안타, 2루타, 3루타, 홈런만 확인하고 싶을 때는 어떻게 할까요? 이번에는 열을 선택해야 하니까 select()를 씁니다. 위에서 살펴본 것처럼 R 변수 이름 규칙 때문에 바뀐 게 있으니까 select(kbo, 안타, X2루타, X3루타, 홈런)으로 쓰면 됩니다.

select(kbo, 안타, X2루타, X3루타, 홈런)

이렇게 써도 잘 작동하지만 사실 이건 R에서 데이터를 표시하는 원칙에는 맞지 않습니다. R는 이렇게 데이터를 한꺼번에 묶어야 할 때 '묶는다'는 뜻인 'concatenate'를 줄인 c를 쓰도록 하고 있습니다. 그냥 c(안타, X2루타, X3루타, 홈런)으로 쓰면 그만입니다. 그러면 select(kbo, c(안타, X2루타, X3루타, 홈런))이 됩니다.


아직은 연도를 적용하기 전이라서 이렇게 치면 1982년부터 2017년까지 자료가 모두 뜹니다. 2017년만 뽑아 내려면 filter()를 써야겠죠? 이때는 데이터가 kbo가 아니라 위에서 본 것처럼 filter(kbo, 연도==2017)이 됩니다.


만약 파이프가 없는 상태로 head()까지 써서 이를 나타내려면 'head(select(filter(kbo, 연도==2017), c(안타, X2루타, X3루타, 홈런)))' 이렇게 쓰면 됩니다. 괄호를 어디에 어떻게 써야 할지도 헷갈릴 지경입니다.


우리는 파이프를 아니까 이렇게 쓸 필요가 없습니다. 

filter(kbo, 연도==2017) %>% select(안타, X2루타, X3루타, 홈런) %>% head()


어떤가요? 이 정도만 전체 명령어가 길어져도 파이프를 쓰는 편이 훨씬 편하고 직관적이지 않습니까?


아, 경우에 따라서는 특정 열만 빼고 선택하고 싶으실 때가 있을 겁니다. 이때는 변수 이름 앞에 '-'를 붙여주시면 됩니다. 그러니까 2017년 데이터 가운데 안타, 2루타, 3루타, 홈런을 빼고 싶으시면 이렇게 쓰시면 되는 겁니다.

filter(kbo, 연도==2017) %>% select(-안타, -X2루타, -X3루타, -홈런) %>% head()


계산하고 붙여 넣자

야구에서 타자 기록 중 가장 기본이 되는 건 타율이고 '안타÷타수'로 계산합니다. kbo 데이터에는 아직 이 기록이 없습니다. 그러면 새로 열을 만들어 넣어야 할 겁니다. 이럴 때 쓰는 함수는? 네, 그렇습니다. mutate()입니다.


이 함수는 'mutate(데이터, 새 변수 = 계산식)' 형태로 사용합니다. 우리는 kbo라는 데이터에 타율이라는 새 열을 만드는데, 그 안에 '안타/타수'를 넣으려고 합니다. (÷와 /가 같은 뜻인 거 설마 모르시지 않겠죠?) 이를 종합하면 이렇게 쓰면 됩니다. (R에서 #는 주석을 시작한다는 뜻입니다. 따라서 아래에서 # 다음 부분은 코드에 아무 영향도 주지 않습니다. 그냥 코드 전체를 Ctrl C/V하셔도 괜찮다는 뜻입니다.)

kbo %>% mutate(타율=안타/타수) #아래는 결과 확인 차원에서 head()를 넣었습니다.


파이프를 쓰는 방식이 조금 달라진 것 눈치채셨습니까? 지금까지는 첫 함수에는 데이터 이름을 따로 넣었습니다. 그러니까 이렇게 썼던 것.

mutate(kbo, 타율=안타/타수) %>% head()

그런데 사실은 데이터부터 곧바로 파이프를 써도 관계가 없습니다. 그리고 이 쪽이 더 직관적입니다. 그렇죠?


이렇게 타율을 구하고 나니 연도별 리그 평균 타율을 계산하고 싶다는 생각이 듭니다. 이럴 때는 어떻게 하면 될까요?



그루핑(grouping) 그리고 tibble 혹은 tbl_df

네, 이럴 때 쓰라고 group_by() 함수가 기다리고 있습니다. 우리는 연도를 기준으로 묶고 싶은 거니까 group_by(연도)라고 쓰면 그만입니다. 이렇게 말입니다.

kbo %>% group_by(연도)


이상한 모양이 등장했습니다. 이렇게 생긴 데이터 형태를 (위에 나온 것처럼) tibble 또는 tbl_df라고 부릅니다. 이때 df는 데이터 프레임(Data Frame)이라는 뜻입니다. 요컨대 tibble은 데이터 프레임을 현대적으로 업그레이드한 형태라고 생각하시면 됩니다.


데이터 프레임은 그럼 뭘까요? MS 엑셀이 데이터를 다루는 방식이 바로 데이터 프레임입니다. 지금까지 우리가 사용한 CSV도 마찬가지. 그러니까 행과 열이 있는 표 형태로 데이터를 저장하는 방식을 데이터 프레임이라고 생각하시면 쉽습니다. 


자, 다시 본론으로 돌아와서 우리는 연도별 리그 평균 타율을 구하려고 합니다. 리그 기록은 기본적으로 각 구단 기록을 모두 더한 결과물입니다. R에서 이렇게 모두 더하라는 함수는 sum()입니다. 타율은 여전히 '안타÷타수'로 계산할 수 있으니까 '(리그) 타율 = sum(안타)/sum(타수)'가 됩니다. 


이 계산식을 어디에 넣어야 할까요? 그렇습니다. summarise()입니다. 리그 평균을 구하는 것도 전체 데이터를 요약하는 작업입니다. 이렇게 생각하면 summarise() 함수를 쓰는 게 당연한 일입니다. 이미 연도별 그루핑은 해놓은 상태니까 이렇게 쓰면 됩니다.

kbo %>% group_by(연도) %>% summarise(타율=sum(안타)/sum(타수))


예상대로 잘 나왔습니다. 꼭 mutate()를 쓰시고 싶거나 총 안타와 총 타수를 표시하고 싶으실 때는 이렇게 쓰셔도 됩니다.

kbo %>% group_by(연도) %>% summarise(총안타=sum(안타), 총타수=sum(타수)) %>% mutate(타율=총안타/총타수)

우리는 타율만 필요한 상태니까 처음에 썼던 버전을 계속 쓰겠습니다. 



필요한 데이터만 뽑아내기 #2

이번에는 계살 결과 중에서 필요한 데이터만 뽑아내는 걸 연습해 보겠습니다. 연도별 타율 가운데 2001년 리그 평균 타율을 구하고 싶을 때는 어떻게 해야 할까요? 별 거 없습니다. 파이프를 이어가기면 하면 됩니다.

kbo %>% group_by(연도) %>% summarise(타율=sum(안타)/sum(타수)) %>% filter(연도==2001)


난도를 조금 높여보겠습니다. 리그 평균 타율이 제일 높았던 연도를 알아보려면 어떻게 해야 할까요? 힌트는 R에서 최댓값을 구하는 함수는 max()라는 점입니다. 어떻게요?

kbo %>% group_by(연도) %>% summarise(타율=sum(안타)/sum(타수)) %>% filter(타율==max(타율))


아, 그런데 여기서 아쉬운 점이 있습니다. 명령어 전체가 너무 길다 보니까 화면에서 잘립니다. 화면에서 보이지 않는 거야 저처럼 이렇게 설명을 드리는 처지가 아니라면 별 관계가 없는 게 사실. 그래도 잘 보이지 않으면 직관적으로 명령을 이해하기가 쉽지 않습니다. 


그럴 때는 그냥 행을 나누면 됩니다. 이렇게 말입니다.

kbo %>%
group_by(연도) %>%
summarise(타율=sum(안타)/sum(타수)) %>%
filter(타율==max(타율))

위에 있는 코드를 그냥 그대로 콘솔에 붙여 넣으셔도 한 줄로 된 코드와 100% 똑같이 작동합니다. 그냥 행갈이만 한 거니까요.



빈 방 채우기

지금까지는 곧바로 결과를 확인하려고 명령어를 전부 한 줄에 넣었지만 데이터를 처리한 걸 별도로 빈 방에 담아 새 데이터를 만들 수도 있습니다.


예를 들어 연도별 타율(batting average)을 avg라는 함수에 넣고 싶다면 이렇게 쓰면 됩니다.

avg <- kbo %>% group_by(연도) %>% summarise(타율=sum(안타)/sum(타수))
avg


혹시 잊으셨을까 봐 말씀드리면 데이터(변수) 이름만 콘솔에 치는 건 그 변수 내용을 보여달라는 뜻입니다.


물론 행갈이를 해서 저 내용을 이런 식으로 써도 같은 결과를 얻을 수 있습니다.

avg <- kbo %>%
group_by(연도) %>%
summarise(타율=sum(안타)/sum(타수))

이렇게 연도별 자료를 계산하고 나니 선 그래프로 추이를 확인하고 싶어지시죠? ggplot2라는 패키지를 써서 그려보도록 하겠습니다. ggplot2 역시 dplyr처럼 tidyverse 패키지 가운데 일부로 시각화를 도와주는 구실을 합니다. ggplot2를 어떻게 쓰는지 알아보고 싶으신 분은 '최대한 친절하게 쓴 R로 그래프 그리기(feat. ggplot2)' 포스트가 도움이 될 수 있습니다.


R에서 패키지를 쓰려면 제일 먼저 해야 할 일은? 네, install.packages() 함수로 패키지를 설치하는 겁니다. 그리고 library() 함수로 패키지를 로드해야 합니다.

install.packages('ggplot2')
library('ggplot2')

이어서 x축은 연도, y축은 타율을 나타내는 선 그래프를 그리라고 명령해 보겠습니다.

ggplot(data=avg, aes(x=연도, y=타율)) + geom_line()

예쁘게 잘 나왔습니다. 물론 파이프만 계속 써서 똑같은 그림을 그릴 수도 있습니다. 이렇게 말입니다.

kbo %>% 
group_by(연도) %>%&
summarise(타율=sum(안타)/sum(타수))  %>%
ggplot(., aes(x=연도, y=타율)) + geom_line()

원래 data=avg가 차지하고 있던 자리를 '.'이 차지해야 한다는 것만 생각하면 어려울 게 없는 코드입니다. 


연습 문제 풀이

이제 dplyr를 어떻게 쓰는지 어느 정도 감을 잡을셨을 줄로 믿습니다. 지금부터는 연습 문제 형태로 조금 더 디테일한 세계로 들어가 보겠습니다.


문1. 타격 기록 중 가장 기본이 되는 타율/출루율/장타력과 OPS(출루율+장타력) 변수(열)를 만드시오.


답1. 바로 mutate() 함수를 떠올리셨죠? 그렇다면 이제 필요한 건 공식뿐입니다. 타율은 이미 '안타 ÷ 타수'라는 걸 알고 있습니다. 출루율과 장타력은 아래 공식으로 계산합니다.


그런데 kbo 데이터에는 이미 '단타 + 2×2루타 + 3×3루타 + 4×홈런'을 계산한 변수가 들어 있습니다. 바로 총루타입니다. 그래서 장타력은 '총루타 ÷ 타수'로 바꿀 수 있습니다. OPS는 문자 그대로 '출루율 + 장타력'이 전부입니다. 그래서 이렇게 쓰면 됩니다.

kbo <- mutate(kbo, 타율=안타/타수,
출루율 = (안타+볼넷+몸에.맞는.공)/(타수+볼넷+몸에.맞는.공+희생플라이),
장타력=총루타/타수,
ops=출루율+장타력)



문2. OPS가 0.7 이상이면서 팀 홈런이 70개 미만이 몇 개인지 구하시오.


답2. '~면서'라는 건 '그리고(and)'라는 뜻입니다. 이렇게 논리적인 접근이 필요할 때는 '논리 연산자'를 씁니다. R에서 그리고는 '&'로 표시합니다. '또는'(or)은 '|'입니다. 같은 건 '=='라는 갈 아실 테고 다른 건 '!='입니다. 또 크다(>), 크거나 같다(>=),  작다(<), 작거나 같다(<=) 같은 연산자도 씁니다.


따라서 ops가 0.7 이상인 건 'ops >= 0.7', '~면서'는 '&', 홈런 70개 미만은 '홈런 < 70'라고 쓸 수 있습니다. 이걸 어디에 넣어야 할까요? 네, filter()입니다. filter(ops >= 0.7 & 홈런 < 70)처럼 쓰면 됩니다.


이를 파이프로 연결하면 이렇게 쓸 수 있습니다.

kbo %>% filter(ops >= 0.7 & 홈런 < 70)

이 코드를 그대로 실행하면 15개라는 게 한 눈에 보입니다. 자료가 너무 많아서 세기 어려울 때 직접 숫자를 출력하게 하려면 어떻게 해야 할까요? 개수를 세어주는 함수는 n()입니다. 우리는 이미 자료를 요약했습니다. 그렇다면 summarise() 함수를 쓰지 못할 이유가 없겠죠?

kbo %>%
filter(ops >= 0.7 & 홈런 < 70) %>%
summarise(n())

그렇다면 ops가 0.7 미만이거나 홈런이 70개 이하인 팀이 몇 팀인지 구하려면 어떻게 쓰면 될까요? 네 이번에는 '또는'을 활용하면 되니까 '|'를 쓰면 됩니다.

kbo %>%
filter(ops < 0.7 | 홈런 >= 70) %>%
summarise(n())



문3. 문2에서 뽑은 15개 팀 중 1982~1990년 사이 팀을 골라 각각 경기당 평균 득점과 도루 성공률을 구하시오.


답3. 일단 팀을 뽑을 때까지는 똑같으니까 filter() 부분까지는 똑같을 겁니다. 그다음 할 일은 1982~1990년 사이에 속하는 팀을 골라야겠죠? 이때는 여러 가지 방법이 있을 수 있지만 (당장 뒤에서도 다른 방식으로 계산합니다.) 우리는 위에서 잠깐 나온 c()를 써보도록 하겠습니다.


R에서는 ':'를 통해 연속해서 숫자를 표시할 수 있습니다. 예를 들어 콘솔에 1:5라고 쓰면 '[1] 1 2 3 4 5'라는 결과가 나옵니다. 마찬가지로 1982:1990이라고 쓰면 1982부터 1990까지 출력합니다. 이걸 한 데 묶어 두려면 c(1982:1990)으로 쓰면 그만입니다.


1982~1990년 사이 팀을 고르려면 연도가 이 범위 안에 들어있어야겠죠? 이럴 때는 '%in%'이라는 연산자를 쓰면 됩니다. '연도 %in% c(1982:1990)'이라고 쓰면 되는 것. (위에서 살펴본 것처럼 이때도 c를 쓰는 게 정석이지만 빼도 정상 작동합니다.)


이를 토대로 위 코드에서 filter를 이렇게 수정할 수 있습니다. 이번에도 그리고(and) 사례니까 '&' 기호를 써야겠죠?

kbo %>%
filter(ops >= 0.7 & 홈런 < 70 & 연도 %in% 1982:1990)

이제 남은 과제는 경기당 평균 득점과 도루 성공률을 구하는 것뿐입니다. 경기당 평균 득점은 '득점 ÷ 경기 (숫자)'로 계산하면 되고, 도루 성공률은 '도루 ÷ (도루 + 도루실패)로 계산합니다.


그러면 필요한 변수(열)는 경기, 득점, 도루, 도루실패가 됩니다 = select(경기, 득점, 도루, 도루실패). 이걸 다시 공식에 따라 계산해서 붙여 넣으면 되니까 mutate(평균득점 = 득점/경기, 도루성공률=도루/(도루+도루실패))라고 정리할 수 있습니다. 연도랑 팀도 같이 나오게 써볼까요?

kbo %>%
filter(ops >= 0.7 & 홈런 < 70 & 연도 %in% 1982:1990) %>%
select(연도, 팀, 경기, 득점, 도루, 도루실패) %>%
mutate(평균득점 = 득점/경기, 도루성공률=도루/(도루+도루실패))



문4. 1982~1990년, 1991~2000년, 2001~2010년, 2011~2017년 평균 희생번트 숫자를 구하시오. 


답4. 이번에는 다른 방법으로 그룹을 구분해 보도록 하겠습니다. 여러 그룹으로 자료를 분류할 때는 그룹 이름으로 새로운 변수(열)을 만들어 사용하면 편합니다. 그러니까 어떤 행이 △1982~1990년 △1991~2000년 △2001~2010년 △2011~2017년에 각각 속한다는 걸 알려주는 이름 변수를 추가하는 겁니다. 예를 들어 이 네 개 그룹에 각각 A~D라는 이름을 붙일 수 있을 겁니다.


이 이름을 붙여주려면 조건문이라는 걸 쓰면 됩니다. '연도' 변수가 1982~1990에 속해 있다면 '그룹'이라는 열에 A라는 값을 추가하는 방식입니다. R에서 조건문을 쓸 때는 if()라는 함수를 쓰는 게 제일 일반적입니다. 이런 구조입니다.

if(연도<=1990){
그룹 <- "A"
} else{
if(연도<=2000){
그룹 <- "B"
} else{
if(연도<=2010){
그룹 <- "C"
}
else{
그룹 <- "D"
}}}

if와 else를 계속 반복하니까 좀 지겹죠? 그래서 R에는 ifelse()라는 함수도 들어 있습니다. ifelse()는 MS 엑셀에서 if 함수를 활용할 때와 같은 방식입니다. 그래서 이 코드를 이렇게 쓸 수 있습니다.

ifelse(연도<=1990, 그룹 <-"A",
ifelse(연도<=2000, 그룹 <- "B",
ifelse(연도<=2010, 그룹 <- "C", 그룹 <- "D")))

우리는 이 그룹을 새로운 열에 추가하기로 했으니까 mutate()를 써야겠죠? 그다음 파이프로 연결하면 이렇게 됩니다.

kbo %>% mutate(그룹 = ifelse(연도<=1990, "A",
ifelse(연도<=2000, "B",
ifelse(연도<=2010, "C", "D"))))

이제 그룹으로 묶고 평균을 내면 됩니다. 그러면 group_by(), summarise()를 쓰면 되겠죠?

kbo %>% mutate(그룹 = ifelse(연도<=1990, "A",
ifelse(연도<=2000, "B",
ifelse(연도<=2010, "C", "D")))) %>% 
group_by(그룹) %>%
summarise(평균희생번트=mean(희생번트))



문5. 통산 병살타가 가장 많은 세 개 팀을 고르시오.


답5. 마지막이라 쉬운 문제로 준비했습니다. 답은 아래와 같습니다.

kbo %>% group_by(팀) %>%
summarise(병살=sum(병살)) %>%
arrange(desc(병살)) %>%
head(3)

그냥 팀별로 그루핑을 한 다음 병살타 합계를 요약하고 내림차순으로 그 결과를 정렬해서 그 가운데 제일 위에 나온 세 개만 뽑으면 그만입니다.



고의사구 타석을 남겨두면서

지금까지 쓰고 보니 결국 타석과 고의사구 변수는 한 번도 사용하지 않았습니다. 이 변수를 사용하면 '타석당 고의사구가 가장 높은 팀은 어디인가?', '타석당 고의사구가 가장 높았던 시즌은?' 같은 문제를 만들어 풀 수 있을 겁니다.


그리고 위에서 사용한 함수 여섯 가지가 dplyr 패키지 기능 전부인 것도 아닙니다. 기본 원리를 공부하려고 제일 많이 쓰는 여섯 가지만 소개해 드린 겁니다. 나머지 함수는 아래 있는 dplyr 커닝 페이퍼를 통해 사용법을 살펴보시면 됩니다. (내려 받으시면 큰 크기로 보실 수 있습니다. 이미지 두 장입니다.) 



혹시 이 포스트 내용 중 이해가 가지 않거나 잘못된 게 있으면 댓글 등을 통해 언제든 알려주세요. 같이 공부하면서 고쳐나가도록 하겠습니다.


댓글, 2

  • 댓글 수정/삭제 홍 아무개
    2018.09.14 22:03 신고

    그 동안 dplyr 공부하면서 이해 안 되는 부분이 많았는데, 이 글 보고 몇 번 연습해보니 완전히 깨치게 됐어요. 감사합니다, 기자님!

account_circle
vpn_key
web

security

mode_edit
Kidult | 카테고리 다른 글 더 보기