본문 바로가기

R

불러온 데이터들 합치기 : Join() 함수로 류현진 선수 데이터보기

여러 가지 방법으로 데이터세트나 데이터프레임을 불러오는 방법을 알아봤었다. 이제 불러온 데이터들을 합치는 방법을 알아보자.

데이터분석 업무를 할 때, 한 개의 데이터 테이블만 가지고 하는 경우는 매우 드물다. 여러 테이블들을 합쳐서 사용해야 하는데, 그때 사용하는 함수 중 하나가 join() 함수이다. join() 함수 외에도 다른 방법이 있긴 하다. mapvalue() 함수를 사용하는 것인데, 다른 포스팅에서 다루도록 하겠다. 

키 값 : Primary Key 와 Foreign Key

join()을 할 때 반드시 알아야 할 개념이 있다. Primary KeyForeign Key이다. 기본키 및 외래키로 번역이 되는데, 그냥 영어로 자체로 보는 게 개념을 이해하는데 더 도움이 되므로, 영어를 사용하겠다.

  • Primary Key : 기초가 되고, 기본이 되는 항목이다. 중복되지 않고, 다른 테이블에서도 그대로 사용될 수 있는 주요 식별자 역할을 한다. 즉, primary key를 사용해서 다른 테이블들을 연결한다.
  • Foreign Key : 다른 데이터테이블에서 primary key와 연결되는 항목이다. 

아래 예제를 보면서 다시 한 번 설명하도록 하겠다.

join의 종류

join()함수를 하기 전에 알아야 할게 하나 더 있다. 바로 어떤 타입의 join을 사용할지를 이해하고 있어야 한다. 이는 R 뿐만 아니라, SQL 등에서도 동일한 개념이다. 예전에 중학 수학의 첫 시작이 집합이었는데 요즘은 어떤지 모르겠다. 집합에서 교집합, 부분집합, 합집합 등이 있었는데, 그 내용들을 떠올리며 보자. 한 가지 더 추가될 게 있다면, 어디를 기준으로 할 것인가이다. 즉, 데이터테이블 A와 B가 있을 때, A의 Primary Key를 사용해서 B와 합치는 상황을 가정해 보자.

  • left_join(): A에는 B에 있지 않은 primary Key값들도 존재할 수 있는데, 그걸 그대로 가져오면 Left Join이다. 즉, left(첫 테이블)이 우선권을 갖는다.
  • right_join(): 반대로 B에도 A에 없는 foreign key값들이 존재할 수 있는데, 그걸 그대로 가져오면 right join이다. 즉, right(두번째 테이블)이 우선권을 갖는다.
  • Inner_join():  만약 A나 B에 공통으로 있는 primary Key에 해당되는 것들만 남긴다면 inner join이다. 즉, 교집합이다.
  • full_join(): 만약 공통으로 있지 않더라도, A와 B의 primary key로 연결하고, 연결이 안된 값들도 전부 가져온다면 full join이다. 즉, 합집합이다.

 

데이터베이스 및 패키지 준비하기


이제 본격적으로 예제를 통해 알아보도록 하자. 예제로 사용할 데이터베이스를 우선 불러오자. 다시 Sean Lahman Baseball Database를 사용하려 한다. 이를 위해서는 지난 포스팅에서 언급한 'Lahman' 패키지를 활용하도록 하자.

https://double-d.tistory.com/27

 

R: 데이터프레임 불러오기 1 - CSV파일, R기본 데이터세트

오랜만에 이전 포스팅에 가 보았다. (https://double-d.tistory.com/18) 데이터베이스 예제로 Sean Lahman의 Baseball Database를 소개한 글이었다. 링크로 남겨두었던 웹사이트에 가보니 연결이 되지 않는다. 구

double-d.tistory.com

 

People 테이블에서 류현진 선수 찾기

최근 류현진 선수가 한화 이글스로 돌아온다는 뉴스가 연일 나왔다. 한화 이글스의 팬으로서 매우 반가운 일이다. 기사가 나오고 있는 김에 류현진 선수의 메이저리그 기록을 한 번 찾아보자. Lahman 패키지에서 기본으로 제공하는 'People'이라는 데이터테이블에 사람 이름이 있었던 듯하니 더 자세히 알아보자. 

‘People‘에 어떤 데이터가 있는지 알아보는 방법은 여러가지이지만 가장 많이 사용하는 방법 중 하나는 'glimpse()'함수이다. 이 함수를 사용하면 데이터프레임에 어떤 항목이 있고, 각 항목은 어떤 데이터타입이며 어떤 값들이 들어가 있는지 쉽게 알아볼 수 있다. 따라서 단순히 항목들의 이름을 확인할 수 있는 ‘colnames()’ 함수나 테이블 형태로 보여주는 ‘View()’ 함수보다 더 자주 사용된다. 

 

library(Lahman)

# Checking column name with last name
People %>% glimpse()

위를 실행시키면 아래와 같이 나온다. 선수 ID, 생년월일, 출생지, 사망연도, 체중 등등 많은 정보가 나온다. 

> People %>% glimpse()
Rows: 20,676
Columns: 26
$ playerID     <chr> "aardsda01", "aaronha01", "aaronto01", "aasedo01", "abadan01", "abadfe01", "abadijo01", "abbated01", "abbeybe01", "abbeych01", "abbotco01"…
$ birthYear    <int> 1981, 1934, 1939, 1954, 1972, 1985, 1850, 1877, 1869, 1866, 1995, 1862, 1874, 1951, 1972, 1967, 1969, 1968, 1886, 1967, 1927, 1850, 1980, …
$ birthMonth   <int> 12, 2, 8, 9, 8, 12, 11, 4, 11, 10, 9, 3, 10, 2, 8, 9, 6, 2, 9, 9, 7, 1, 7, 1, 9, 10, 3, 2, 8, 10, 6, 3, 10, 11, 7, 9, 3, 4, 5, 1, 4, 11, 4…
$ birthDay     <int> 27, 5, 5, 8, 25, 17, 4, 15, 11, 14, 20, 16, 22, 16, 17, 19, 2, 18, 5, 15, 31, 2, 15, 30, 23, 30, 6, 1, 28, 4, 17, 2, 3, 9, 4, 26, 11, 22, …
$ birthCountry <chr> "USA", "USA", "USA", "USA", "USA", "D.R.", "USA", "USA", "USA", "USA", "USA", "USA", "USA", "USA", "USA", "USA", "USA", "USA", "USA", "USA…
$ birthState   <chr> "CO", "AL", "AL", "CA", "FL", "La Romana", "PA", "PA", "VT", "NE", "CA", "OH", "OH", "AR", "GA", "MI", "OH", "MA", "PA", "CA", "OH", "OK",…
$ birthCity    <chr> "Denver", "Mobile", "Mobile", "Orange", "Palm Beach", "La Romana", "Philadelphia", "Latrobe", "Essex", "Falls City", "San Diego", "Portage…
$ deathYear    <int> NA, 2021, 1984, NA, NA, NA, 1905, 1957, 1962, 1926, NA, 1930, 1935, NA, NA, NA, NA, NA, 1933, NA, 1993, 1939, NA, 2006, NA, 2001, 2004, 19…
$ deathMonth   <int> NA, 1, 8, NA, NA, NA, 5, 1, 6, 4, NA, 2, 6, NA, NA, NA, NA, NA, 4, NA, 5, 11, NA, 2, NA, 11, 12, 12, 6, 2, NA, 2, NA, 12, NA, NA, NA, NA, …
$ deathDay     <int> NA, 22, 16, NA, NA, NA, 17, 6, 11, 27, NA, 13, 11, NA, NA, NA, NA, NA, 13, NA, 20, 11, NA, 19, NA, 16, 16, 5, 23, 8, NA, 25, NA, 5, NA, NA…
$ deathCountry <chr> NA, "USA", "USA", NA, NA, NA, "USA", "USA", "USA", "USA", NA, "USA", "USA", NA, NA, NA, NA, NA, "USA", NA, "USA", "USA", NA, "USA", NA, "U…
$ deathState   <chr> NA, "GA", "GA", NA, NA, NA, "NJ", "FL", "VT", "CA", NA, "MI", "CA", NA, NA, NA, NA, NA, "DC", NA, "OH", "PA", NA, "CA", NA, "NC", "NC", "K…
$ deathCity    <chr> NA, "Atlanta", "Atlanta", NA, NA, NA, "Pemberton", "Fort Lauderdale", "Colchester", "San Francisco", NA, "Ottawa Lake", "Los Angeles", NA,…
$ nameFirst    <chr> "David", "Hank", "Tommie", "Don", "Andy", "Fernando", "John", "Ed", "Bert", "Charlie", "Cory", "Dan", "Fred", "Glenn", "Jeff", "Jim", "Kur…
$ nameLast     <chr> "Aardsma", "Aaron", "Aaron", "Aase", "Abad", "Abad", "Abadie", "Abbaticchio", "Abbey", "Abbey", "Abbott", "Abbott", "Abbott", "Abbott", "A…
$ nameGiven    <chr> "David Allan", "Henry Louis", "Tommie Lee", "Donald William", "Fausto Andres", "Fernando Antonio", "John W.", "Edward James", "Bert Wood",…
$ weight       <int> 215, 180, 190, 190, 184, 235, 192, 170, 175, 169, 220, 190, 180, 200, 190, 200, 180, 200, 165, 185, 195, NA, 215, 190, 185, 210, 215, 170,…
$ height       <int> 75, 72, 75, 75, 73, 74, 72, 71, 71, 68, 74, 71, 70, 78, 74, 75, 71, 76, 69, 75, 74, NA, 75, 70, 73, 74, 76, 72, 72, 74, 73, 72, 74, 69, 72…
$ bats         <fct> R, R, R, R, L, L, R, R, R, L, R, R, R, R, R, L, R, L, L, R, L, NA, R, R, R, R, R, L, R, R, R, L, L, R, R, R, L, R, R, R, R, B, R, R, R, R,…
$ throws       <fct> R, R, R, R, L, L, R, R, R, L, R, R, R, R, L, L, R, L, R, R, L, NA, R, R, R, L, R, L, R, L, R, L, R, R, R, R, R, R, R, R, R, R, R, R, R, R,…
$ debut        <chr> "2004-04-06", "1954-04-13", "1962-04-10", "1977-07-26", "2001-09-10", "2010-07-28", "1875-04-26", "1897-09-04", "1892-06-14", "1893-08-16"…
$ finalGame    <chr> "2015-08-23", "1976-10-03", "1971-09-26", "1990-10-03", "2006-04-13", "2021-10-01", "1875-06-10", "1910-09-15", "1896-09-23", "1897-08-19"…
$ retroID      <chr> "aardd001", "aaroh101", "aarot101", "aased001", "abada001", "abadf001", "abadj101", "abbae101", "abbeb101", "abbec101", "abboc001", "abbod…
$ bbrefID      <chr> "aardsda01", "aaronha01", "aaronto01", "aasedo01", "abadan01", "abadfe01", "abadijo01", "abbated01", "abbeybe01", "abbeych01", "abbotco01"…
$ deathDate    <date> NA, 2021-01-22, 1984-08-16, NA, NA, NA, 1905-05-17, 1957-01-06, 1962-06-11, 1926-04-27, NA, 1930-02-13, 1935-06-11, NA, NA, NA, NA, NA, 1…
$ birthDate    <date> 1981-12-27, 1934-02-05, 1939-08-05, 1954-09-08, 1972-08-25, 1985-12-17, 1850-11-04, 1877-04-15, 1869-11-11, 1866-10-14, 1995-09-20, 1862-…

‘nameLast’라는 항목이 성이므로 류현진의 성인 ‘Ryu’로 필터링해 보자. 류현진 선수 외에도 류재국 선수도 같이 나온다. 일단 류현진 선수에 집중하기 위해 ‘bbrefID’인 ‘ryuhy01’을 적어두자. 그리고 이 'bbrefID'가 primary Key로 활용될 것이다. 

여담이지만, 미국을 포함한 서구 사회에서는 출생지를 중요하게 생각한다. 어느 관공서를 가든 출생증명서를 제출해야 하고, 대부분의 문서에 출생도시를 기입해야 한다. 류재국 선수의 경우 ‘birthState’는 서울인데 ‘birthCity’는 충청도이다. 틀린 정보이지만 이해가 된다. 한국은 출생지를 기입할 일이 거의 없으니, 그냥 생각나는 대로 대충 적은 듯하다.

류현진 선수의 데이터만 필터링하기 위해서는 아래와 같이 조건을 하나 더 추가하자. 

People %>%
	filter(nameLast ==‘Ryu’,
	bbrefID == ‘ryuhy01’)

아래는 실행 시 콘솔에서 나오는 내용이다. 

> People %>% 
+     filter(nameLast == 'Ryu',
+            bbrefID == 'ryuhy01')
  playerID birthYear birthMonth birthDay birthCountry birthState birthCity deathYear deathMonth deathDay deathCountry deathState deathCity nameFirst nameLast
1  ryuhy01      1987          3       25  South Korea    Incheon   Incheon        NA         NA       NA         <NA>       <NA>      <NA>  Hyun Jin      Ryu
  nameGiven weight height bats throws      debut  finalGame  retroID bbrefID deathDate  birthDate
1  Hyun Jin    250     75    R      L 2013-04-02 2022-06-01 ryu-h001 ryuhy01      <NA> 1987-03-25

 

Pitching에서 류현진 선수 찾기

류현진 선수는 투수이므로 투수 관련 테이블에서 찾아보자. 정확한 테이블 이름을 알 수 없을 때는 아래와 같이 'LahmanData' 테이블을 확인해 보면 된다.

콘솔에 표시되는 내용은 아래와 같다. 'LahmanData'테이블에는 'Lahman' 패키지에 있는 데이터세트에 관한 정보가 들어있다.

  • 'file' : 각 데이터세트 이름
  • 'class': 각 데이터세트의 데이터타입,
  • 'nobs': Number of Observations, 행의 수
  • 'nvar' : Number of Variable,  칼럼 또는 항목의 수
  • ' title': 데이터세트의 Full name
> LahmanData
                  file      class   nobs nvar                     title
1          AllstarFull data.frame   5454    8         AllstarFull table
2          Appearances data.frame 110423   21         Appearances table
3       AwardsManagers data.frame    179    6      AwardsManagers table
4        AwardsPlayers data.frame   6531    6       AwardsPlayers table
5  AwardsShareManagers data.frame    425    7 AwardsShareManagers table
6   AwardsSharePlayers data.frame   6879    7  AwardsSharePlayers table
7              Batting data.frame 110495   22             Batting table
8          BattingPost data.frame  15879   22         BattingPost table
9       CollegePlaying data.frame  17350    3      CollegePlaying table
10            Fielding data.frame 147080   18            Fielding table
11          FieldingOF data.frame  12028    6          FieldingOF table
12     FieldingOFsplit data.frame  34563   18     FieldingOFsplit table
13        FieldingPost data.frame  15063   17         FieldingPost data
14          HallOfFame data.frame   4191    9  Hall of Fame Voting Data
15           HomeGames data.frame   3195    9           HomeGames table
16            Managers data.frame   3684   10            Managers table
17        ManagersHalf data.frame     93   10        ManagersHalf table
18               Parks data.frame    255    6               Parks table
19              People data.frame  20370   26              People table
20            Pitching data.frame  49430   30            Pitching table
21        PitchingPost data.frame   6308   30        PitchingPost table
22            Salaries data.frame  26428    5            Salaries table
23             Schools data.frame   1207    5             Schools table
24          SeriesPost data.frame    367    9          SeriesPost table
25               Teams data.frame   2985   48               Teams table
26     TeamsFranchises data.frame    120    4      TeamFranchises table
27           TeamsHalf data.frame     52   10           TeamsHalf table

'Pitching'이라는 테이블이 왠지 투수와 관련된 테이블 것 같으므로 'glimpse()' 함수를 사용해서 확인해 보자. 

Pitching %>%
	glimpse()

선수를 필터링하기 위해서 'playerID'를 쓰면 될 것처럼 보이므로, People 테이블에서 류현진 선수의 'bbrefID' 였던 'ryuhy01'로 필터링해 보겠다. 

Pitching %>%
	filter(playerID == ‘ryuhy01’)

콘솔에 나오는 결괏값은 아래와 같다. 데뷔한 해인 2013년부터 Lahman 패키지에서 제공하는 가장 최신 데이터인 2021년까지의 데이터가 들어있다. 

> Pitching %>% 
+   filter(playerID == 'ryuhy01')
  playerID yearID stint teamID lgID  W  L  G GS CG SHO SV IPouts   H
1  ryuhy01   2013     1    LAN   NL 14  8 30 30  2   1  0    576 182
2  ryuhy01   2014     1    LAN   NL 14  7 26 26  0   0  0    456 152
3  ryuhy01   2016     1    LAN   NL  0  1  1  1  0   0  0     14   8
4  ryuhy01   2017     1    LAN   NL  5  9 25 24  0   0  1    380 128
5  ryuhy01   2018     1    LAN   NL  7  3 15 15  0   0  0    247  68
6  ryuhy01   2019     1    LAN   NL 14  5 29 29  1   1  0    548 160
7  ryuhy01   2020     1    TOR   AL  5  2 12 12  0   0  0    201  60
8  ryuhy01   2021     1    TOR   AL 14 10 31 31  1   1  0    507 170
  ER HR BB  SO BAOpp   ERA IBB WP HBP BK BFP GF  R SH SF GIDP
1 64 15 49 154 0.252  3.00   4  5   1  0 783  0 67  7  3   26
2 57  8 29 139 0.257  3.38   2  2   3  0 631  0 60  6  2   12
3  6  1  2   4 0.364 11.57   1  0   0  0  24  0  6  0  0    0

 

수상이력 확인

혹시 류현진 선수가 메이저리그에서 수상이력이 있는지 알아보자. 조금 전에 확인했던 LahmanData에서 ‘AwardsPlayers’라는 테이블이 있다. 

AwardsPlayers %>%
	filter(playerID == ‘ryuhy01’)

아쉽게도 류현진 선수는 수상이력이 없다. 실제로 2013년부터 잘 던져오다가, 2015년 5월에 왼쪽 어깨 수술을 받고 한동안 등판하지 못했다. 2019년, 2020년에 연속으로 사이영상 후보로 올랐으나, 수상하지는 못했다. 2020년에는 토론토로 팀을 옮긴 해였는데, 팀 성적이 부진했다. 다행히 좌완 투수에게 주어지는 워렌 스판 상을 수상하였으나, 'AwardsPlayers' 테이블에는 해당 기록이 안 나온다. 

류현진 선수 연봉확인

류현진 선수가 올해 한화로 복귀하면서 받은 연봉은 8년 170억 원이라고 알려져 있다. 미화로 하면 1,200만 불 정도이니 KBO에서는 적지 않은 돈이다. 메이저 리그에서는 얼마나 받았었을지 확인해 보자. 

Salaries %>%
	filter(playerID == ‘ryuhy01’)

아래와 같은 결괏값을 얻을 수 있다. 아쉽게도 토론토에 간 이후의 연봉 정보가 나오지 않는다.  참고로, 기사에 따르면 2013년 다저스와의 계약은 6년 3,600만 달러, 2020년 토론토와 계약은 4년 8,000달러로 계약했다. 계약 마지막 해였던, 작년 토론토로부터 받은 연봉은 2,000만 달러로 메이저리그 전체 4%에 해당하는 공동 46위에 해당하는 금액이다. 

> Salaries %>% 
+     filter(playerID == "ryuhy01")
  yearID teamID lgID playerID  salary
1   2013    LAN   NL  ryuhy01 3333333
2   2014    LAN   NL  ryuhy01 4333000
3   2016    LAN   NL  ryuhy01 7833333

일단 있는 연봉 정보만 활용하자. 

 

데이터테이블 합치기

이제 류현진 선수가 있는 3 테이블을 합쳐보자. Salaries와 Pitching은 두 개의 Primary Key 값으로 연결될 수 있다. yearID와 playerID이다. People과 Pitching은 Primary Key 값으로 bbrefID와 playerID를 사용할 수 있다. 키 값의 이름은 다르지만 하나로 보고 사용하자. 

People 테이블에서 류현진 선수의 데이터만을 필터링해서 첫 번째 데이터세트로 사용한다. 그러면 Pitching이나 Salaries 테이블에서는 류현진 선수 외의 데이터는 전부 필요 없으니 left_join() 함수를 사용해서 다른 선수/감독 등의 데이터는 남겨두지 않도록 하자. 불필요한 항목들도 정리하고, 류현진 선수임을 알아볼 수 있는 기본 정보와, 년도, 팀, 승, 패, 연봉만 남기자.

People %>% 
    filter(nameLast == 'Ryu',
           bbrefID == 'ryuhy01')%>%
    left_join(Pitching, by = c("bbrefID" = "playerID")) %>% 
    left_join(Salaries) %>% 
    select(playerID, nameFirst, nameLast, yearID, teamID, W, L, salary)

위에 대한 결괏값은 아래와 같다. 

> People %>% 
+     filter(nameLast == 'Ryu',
+            bbrefID == 'ryuhy01')%>%
+     left_join(Pitching, by = c("bbrefID" = "playerID")) %>% 
+     left_join(Salaries) %>% 
+     select(playerID, nameFirst, nameLast, yearID, teamID, W, L, salary)
Joining with `by = join_by(playerID, yearID, teamID, lgID)`
  playerID nameFirst nameLast yearID teamID  W  L  salary
1  ryuhy01  Hyun Jin      Ryu   2013    LAN 14  8 3333333
2  ryuhy01  Hyun Jin      Ryu   2014    LAN 14  7 4333000
3  ryuhy01  Hyun Jin      Ryu   2016    LAN  0  1 7833333
4  ryuhy01  Hyun Jin      Ryu   2017    LAN  5  9      NA
5  ryuhy01  Hyun Jin      Ryu   2018    LAN  7  3      NA
6  ryuhy01  Hyun Jin      Ryu   2019    LAN 14  5      NA
7  ryuhy01  Hyun Jin      Ryu   2020    TOR  5  2      NA
8  ryuhy01  Hyun Jin      Ryu   2021    TOR 14 10      NA
9  ryuhy01  Hyun Jin      Ryu   2022    TOR  2  0      NA

 

추가로 알아볼 함수 cbind/ rbind

데이터테이블 병합을 할 수 있는 다른 방법 중 하나이다. rbind는 항목의 개수와 순서가 동일할 경우 하나의 테이블 밑에 다른 테이블을 그대로 붙일 때 사용한다. 즉, Primary Key가 필요 없다. 비슷한 개념으로 cbind는 행이 같은 테이블들을 붙일 때 사용한다. 

  • rbind() : row bind
  • cbind() : column bind


애초에 다른 테이블로 작업하지 않고, 하나의 테이블을 가지고 가공한 여러 결과를 붙일 때 사용한다. 또는 다른 테이블에서 나왔어도 목적 테이블의 형태가 동일할 때 붙이기 위해서 사용하기도 한다.