제 5강 : 데이터 랭글링 II

데이터과학 입문

원중호

서울대학교 통계학과

March 2024

시작하기 전에

다음의 패키지가 설치되어 있지 않으면 설치한다.

# install.packages("tidyverse")
# install.packages("mdsr")
# install.packages("nycflights13")
# install.packages("Lahman")
# install.packages("knitr")
library(tidyverse)
library(mdsr)
library(nycflights13)
library(Lahman)
library(knitr)
sessionInfo()
R version 4.3.3 (2024-02-29)
Platform: x86_64-apple-darwin20 (64-bit)
Running under: macOS Sonoma 14.2.1

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.3-x86_64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.3-x86_64/Resources/lib/libRlapack.dylib;  LAPACK version 3.11.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Asia/Seoul
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] knitr_1.45         Lahman_11.0-0      nycflights13_1.0.2 mdsr_0.2.7        
 [5] lubridate_1.9.3    forcats_1.0.0      stringr_1.5.1      dplyr_1.1.4       
 [9] purrr_1.0.2        readr_2.1.5        tidyr_1.3.1        tibble_3.2.1      
[13] ggplot2_3.5.0      tidyverse_2.0.0   

loaded via a namespace (and not attached):
 [1] gtable_0.3.4      jsonlite_1.8.8    compiler_4.3.3    tidyselect_1.2.0 
 [5] scales_1.3.0      yaml_2.3.8        fastmap_1.1.1     R6_2.5.1         
 [9] generics_0.1.3    skimr_2.1.5       munsell_0.5.0     pillar_1.9.0     
[13] tzdb_0.4.0        rlang_1.1.3       utf8_1.2.4        stringi_1.8.3    
[17] repr_1.1.6        xfun_0.42         timechange_0.3.0  cli_3.6.2        
[21] withr_3.0.0       magrittr_2.0.3    digest_0.6.34     grid_4.3.3       
[25] rstudioapi_0.15.0 base64enc_0.1-3   hms_1.1.3         lifecycle_1.0.4  
[29] vctrs_0.6.5       evaluate_0.23     glue_1.7.0        fansi_1.0.6      
[33] colorspace_2.1-0  rmarkdown_2.25    tools_4.3.3       pkgconfig_2.0.3  
[37] htmltools_0.5.7  

복수 개의 자료표를 이용한 데이터 랭글링

nycflights13 데이터

  • 이 패키지는 뉴욕 시의 3개 주요 공항(존 F. 케네디 국제공항(JFK), 라과디아 공항(LGA), 뉴왁 리버티 국제공항(EWR))에서 2013년에 출발한 모든 비행기에 대한 자료(flights)를 담고 있다. 또한 airlines, airports, weather, planes에 대한 참조자료(metadata) 또한 찾을 수 있다.
pillar::glimpse(flights)
Rows: 336,776
Columns: 19
$ year           <int> 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2…
$ month          <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
$ day            <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
$ dep_time       <int> 517, 533, 542, 544, 554, 554, 555, 557, 557, 558, 558, …
$ sched_dep_time <int> 515, 529, 540, 545, 600, 558, 600, 600, 600, 600, 600, …
$ dep_delay      <dbl> 2, 4, 2, -1, -6, -4, -5, -3, -3, -2, -2, -2, -2, -2, -1…
$ arr_time       <int> 830, 850, 923, 1004, 812, 740, 913, 709, 838, 753, 849,…
$ sched_arr_time <int> 819, 830, 850, 1022, 837, 728, 854, 723, 846, 745, 851,…
$ arr_delay      <dbl> 11, 20, 33, -18, -25, 12, 19, -14, -8, 8, -2, -3, 7, -1…
$ carrier        <chr> "UA", "UA", "AA", "B6", "DL", "UA", "B6", "EV", "B6", "…
$ flight         <int> 1545, 1714, 1141, 725, 461, 1696, 507, 5708, 79, 301, 4…
$ tailnum        <chr> "N14228", "N24211", "N619AA", "N804JB", "N668DN", "N394…
$ origin         <chr> "EWR", "LGA", "JFK", "JFK", "LGA", "EWR", "EWR", "LGA",…
$ dest           <chr> "IAH", "IAH", "MIA", "BQN", "ATL", "ORD", "FLL", "IAD",…
$ air_time       <dbl> 227, 227, 160, 183, 116, 150, 158, 53, 140, 138, 149, 1…
$ distance       <dbl> 1400, 1416, 1089, 1576, 762, 719, 1065, 229, 944, 733, …
$ hour           <dbl> 5, 5, 5, 5, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 6, 6, 6…
$ minute         <dbl> 15, 29, 40, 45, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 0…
$ time_hour      <dttm> 2013-01-01 05:00:00, 2013-01-01 05:00:00, 2013-01-01 0…
  • 각 행은 하나의 항공편. 매년 수십만 개의 행을 포함
flights %>% 
  filter(month == 1 & day == 1, abs(dep_delay) > 30) %>%
  select(dep_time,arr_time,carrier:dest) %>% slice(1:4) 
# A tibble: 4 × 7
  dep_time arr_time carrier flight tailnum origin dest 
     <int>    <int> <chr>    <int> <chr>   <chr>  <chr>
1      732     1011 UA        1111 N37456  EWR    MCO  
2      749      939 MQ        3737 N508MQ  EWR    ORD  
3      811     1047 MQ        4576 N531MQ  LGA    CLT  
4      826     1136 AA         443 N3GVAA  JFK    MIA  
  • 자료의 값이 코드로 되어 있어서 쉽게 뜻을 알 수가 없다.

  • 큰 자료표의 경우 코드를 사용하면 디스크의 저장 공간을 절약하여 검색 속도를 눈에 띄게 향상시킬 수 있음.

원 자료와 참조자료

  • 위 자료에서 carrier == "AA"가 무슨 뜻일까?

  • 참조자료 airlines를 보면 알 수 있다.

    head(airlines,2)
    # A tibble: 2 × 2
      carrier name                  
      <chr>   <chr>                 
    1 9E      Endeavor Air Inc.     
    2 AA      American Airlines Inc.
  • AA가 American Airlines Inc.임을 원 자료 flightscarrier 변수의 값 AA와 참조자료 airlinescarrier 변수의 값 AA에 해당하는 열을 대조하여 알 수 있다.

  • 두 자료표의 변수 carrier 와 carrier 가 (이름이 다를 수도 있다) 두 자료표를 연결하는 열쇠(key)이다.

inner_join()

  • flights 자료표과 airlines 자료표 각각에서 carrier 변수가 같은 행들을 연결
flights_joined <- flights %>%
    inner_join(airlines, by = c("carrier" = "carrier"))
glimpse(flights_joined)
Rows: 336,776
Columns: 20
$ year           <int> 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2…
$ month          <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
$ day            <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
$ dep_time       <int> 517, 533, 542, 544, 554, 554, 555, 557, 557, 558, 558, …
$ sched_dep_time <int> 515, 529, 540, 545, 600, 558, 600, 600, 600, 600, 600, …
$ dep_delay      <dbl> 2, 4, 2, -1, -6, -4, -5, -3, -3, -2, -2, -2, -2, -2, -1…
$ arr_time       <int> 830, 850, 923, 1004, 812, 740, 913, 709, 838, 753, 849,…
$ sched_arr_time <int> 819, 830, 850, 1022, 837, 728, 854, 723, 846, 745, 851,…
$ arr_delay      <dbl> 11, 20, 33, -18, -25, 12, 19, -14, -8, 8, -2, -3, 7, -1…
$ carrier        <chr> "UA", "UA", "AA", "B6", "DL", "UA", "B6", "EV", "B6", "…
$ flight         <int> 1545, 1714, 1141, 725, 461, 1696, 507, 5708, 79, 301, 4…
$ tailnum        <chr> "N14228", "N24211", "N619AA", "N804JB", "N668DN", "N394…
$ origin         <chr> "EWR", "LGA", "JFK", "JFK", "LGA", "EWR", "EWR", "LGA",…
$ dest           <chr> "IAH", "IAH", "MIA", "BQN", "ATL", "ORD", "FLL", "IAD",…
$ air_time       <dbl> 227, 227, 160, 183, 116, 150, 158, 53, 140, 138, 149, 1…
$ distance       <dbl> 1400, 1416, 1089, 1576, 762, 719, 1065, 229, 944, 733, …
$ hour           <dbl> 5, 5, 5, 5, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 6, 6, 6…
$ minute         <dbl> 15, 29, 40, 45, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 0…
$ time_hour      <dttm> 2013-01-01 05:00:00, 2013-01-01 05:00:00, 2013-01-01 0…
$ name           <chr> "United Air Lines Inc.", "United Air Lines Inc.", "Amer…
  • flights_joinedname이라는 변수가 추가됨.
flights_joined %>%
    select(carrier, name,flight,origin,dest) %>%
    head(3)
# A tibble: 3 × 5
  carrier name                   flight origin dest 
  <chr>   <chr>                   <int> <chr>  <chr>
1 UA      United Air Lines Inc.    1545 EWR    IAH  
2 UA      United Air Lines Inc.    1714 LGA    IAH  
3 AA      American Airlines Inc.   1141 JFK    MIA  
  • inner_join()은 두 자료표 모두에서 일치하는 행만을 포함함
  • 위 예시의 경우, flights의 모든 행은 airlines에서 정확히 하나의 해당 항목을 가지므로 flights_joined의 행 수는 flights의 행 수와 동일함.
nrow(flights)
[1] 336776
nrow(flights_joined)
[1] 336776

Inner join (source: R Studio)

nycflights13 패키지의 자료와 연결 열쇠들

Relational database

left_join()

  • 뉴욕 시에서 미국 서해안으로 가는 항공편에만 관심이 있다고 가정하자.

  • 그 중 태평양 표준시간대(UTC -8)에 있는 공항만 사용하기 위해서 airports 자료표를 필터링하여 해당 공항들만 포함.

airports_pt <- airports %>%
filter(tz == -8)
nrow(airports_pt)
[1] 178
  • 이제, flightsairports_ptinner_join()을 이용하여 flights의 목적지 공항 코드를 airportsfaa와 일치시키면, 태평양 표준시간대에 있는 항공편만 검색할 수 있음.
nyc_dests_pt <- flights %>%
  inner_join(airports_pt, by = c("dest" = "faa"))
#nrow(nyc_dests_pt)
glimpse(nyc_dests_pt)
Rows: 46,324
Columns: 26
$ year           <int> 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2…
$ month          <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
$ day            <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
$ dep_time       <int> 558, 558, 559, 611, 628, 646, 651, 655, 658, 702, 724, …
$ sched_dep_time <int> 600, 600, 600, 600, 630, 645, 655, 700, 700, 700, 725, …
$ dep_delay      <dbl> -2, -2, -1, 11, -2, 1, -4, -5, -2, 2, -1, -1, -3, 13, 1…
$ arr_time       <int> 924, 923, 854, 945, 1016, 1023, 936, 1037, 1027, 1058, …
$ sched_arr_time <int> 917, 937, 902, 931, 947, 1030, 942, 1045, 1025, 1014, 1…
$ arr_delay      <dbl> 7, -14, -8, 14, 29, -7, -6, -8, 2, 44, -10, -26, -26, 7…
$ carrier        <chr> "UA", "UA", "UA", "UA", "UA", "UA", "B6", "DL", "VX", "…
$ flight         <int> 194, 1124, 1187, 303, 1665, 1496, 203, 1865, 399, 671, …
$ tailnum        <chr> "N29129", "N53441", "N76515", "N532UA", "N33289", "N387…
$ origin         <chr> "JFK", "EWR", "EWR", "JFK", "EWR", "EWR", "JFK", "JFK",…
$ dest           <chr> "LAX", "SFO", "LAS", "SFO", "LAX", "SNA", "LAS", "SFO",…
$ air_time       <dbl> 345, 361, 337, 366, 366, 380, 323, 362, 361, 381, 338, …
$ distance       <dbl> 2475, 2565, 2227, 2586, 2454, 2434, 2248, 2586, 2475, 2…
$ hour           <dbl> 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8…
$ minute         <dbl> 0, 0, 0, 0, 30, 45, 55, 0, 0, 0, 25, 30, 37, 30, 30, 45…
$ time_hour      <dttm> 2013-01-01 06:00:00, 2013-01-01 06:00:00, 2013-01-01 0…
$ name           <chr> "Los Angeles Intl", "San Francisco Intl", "Mc Carran In…
$ lat            <dbl> 33.94254, 37.61897, 36.08006, 37.61897, 33.94254, 33.67…
$ lon            <dbl> -118.4081, -122.3749, -115.1522, -122.3749, -118.4081, …
$ alt            <dbl> 126, 13, 2141, 13, 126, 56, 2141, 13, 126, 126, 433, 13…
$ tz             <dbl> -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8, -8,…
$ dst            <chr> "A", "A", "A", "A", "A", "A", "A", "A", "A", "A", "A", …
$ tzone          <chr> "America/Los_Angeles", "America/Los_Angeles", "America/…
  • 동일한 조건으로 left_join()을 사용하면 항공편의 모든 행을 검색하고, 일치하는 데이터가 없는 열에는 NA가 삽입됨.
nyc_dests <- flights %>%
left_join(airports_pt, by = c("dest" = "faa"))
nyc_dests %>%
    summarize(
        num_flights = n(),
        num_flights_pt = sum(!is.na(name)),
        num_flights_not_pt = sum(is.na(name))
    )
# A tibble: 1 × 3
  num_flights num_flights_pt num_flights_not_pt
        <int>          <int>              <int>
1      336776          46324             290452
  • left_join()은 첫 번째 자료표의 행을 항상 반환하며, 두 번째 자료표에 일치하는 항목이 있는지 여부와 관계없이 반환함.

Left join (source: R Studio)

다양한 관계형 데이터베이스 연결기법

  • Mutating joins: 여러 개의 자료표에서 변수들을 결합-원자료에 새 변수 생성 (mutate)

    • left_join(), inner_join(), full_join(), right_join().
  • Filtering joins: 참조자료와 대조하여 원자료의 관측단위들을 거름 (filter)

    • semi_join(), anti_join()
  • 집합 연산: 양쪽 자료표가 같은 변수로 이루졌을 때, 관측단위들의 집합연산

    • union(), intersect(), setdiff()
  • 무작정 붙이기

    • bind_rows(), bind_cols()

Mutating joins

Source: R Studio Cheatsheet

Filtering joins

Semi-join (source: R Studio)

Anti-join (source: R Studio)

집합 연산

Union (source: R Studio)

Intersection (source: R Studio)

Difference (source: R Studio)

무작정 붙이기

Bind columns (source: R Studio)

Bind rows (source: R Studio)

심화 예제 : 매니 라미레즈

Batting 데이터

  • 선수들의 타격 통계가 팀별, 시즌별로 기록된 데이터
manny <- Lahman::Batting %>%
    filter(playerID == "ramirma02")
#    nrow(manny)
manny
    playerID yearID stint teamID lgID   G  AB   R   H X2B X3B HR RBI SB CS  BB
1  ramirma02   1993     1    CLE   AL  22  53   5   9   1   0  2   5  0  0   2
2  ramirma02   1994     1    CLE   AL  91 290  51  78  22   0 17  60  4  2  42
3  ramirma02   1995     1    CLE   AL 137 484  85 149  26   1 31 107  6  6  75
4  ramirma02   1996     1    CLE   AL 152 550  94 170  45   3 33 112  8  5  85
5  ramirma02   1997     1    CLE   AL 150 561  99 184  40   0 26  88  2  3  79
6  ramirma02   1998     1    CLE   AL 150 571 108 168  35   2 45 145  5  3  76
7  ramirma02   1999     1    CLE   AL 147 522 131 174  34   3 44 165  2  4  96
8  ramirma02   2000     1    CLE   AL 118 439  92 154  34   2 38 122  1  1  86
9  ramirma02   2001     1    BOS   AL 142 529  93 162  33   2 41 125  0  1  81
10 ramirma02   2002     1    BOS   AL 120 436  84 152  31   0 33 107  0  0  73
11 ramirma02   2003     1    BOS   AL 154 569 117 185  36   1 37 104  3  1  97
12 ramirma02   2004     1    BOS   AL 152 568 108 175  44   0 43 130  2  4  82
13 ramirma02   2005     1    BOS   AL 152 554 112 162  30   1 45 144  1  0  80
14 ramirma02   2006     1    BOS   AL 130 449  79 144  27   1 35 102  0  1 100
15 ramirma02   2007     1    BOS   AL 133 483  84 143  33   1 20  88  0  0  71
16 ramirma02   2008     1    BOS   AL 100 365  66 109  22   1 20  68  1  0  52
17 ramirma02   2008     2    LAN   NL  53 187  36  74  14   0 17  53  2  0  35
18 ramirma02   2009     1    LAN   NL 104 352  62 102  24   2 19  63  0  1  71
19 ramirma02   2010     1    LAN   NL  66 196  32  61  15   0  8  40  1  1  32
20 ramirma02   2010     2    CHA   AL  24  69   6  18   1   0  1   2  0  0  14
21 ramirma02   2011     1    TBA   AL   5  17   0   1   0   0  0   1  0  0   0
    SO IBB HBP SH SF GIDP
1    8   0   0  0  0    3
2   72   4   0  0  4    6
3  112   6   5  2  5   13
4  104   8   3  0  9   18
5  115   5   7  0  4   19
6  121   6   6  0 10   18
7  131   9  13  0  9   12
8  117   9   3  0  4    9
9  147  25   8  0  2    9
10  85  14   8  0  1   13
11  94  28   8  0  5   22
12 124  15   6  0  7   17
13 119   9  10  0  6   20
14 102  16   1  0  8   13
15  92  13   7  0  8   21
16  86   8   8  0  0   12
17  38  16   3  0  4    5
18  81  21   7  0  1    7
19  38   4   1  0  3    4
20  23   0   5  0  0    2
21   4   0   0  0  0    0

Manny의 주요 타격 통계

manny %>%
    summarize(
    span = paste(min(yearID), max(yearID), sep = "-"),
    num_years = n_distinct(yearID),
    num_teams = n_distinct(teamID),
    BA = sum(H)/sum(AB),
    tH = sum(H),
    tHR = sum(HR),
    tRBI = sum(RBI)
)
       span num_years num_teams        BA   tH tHR tRBI
1 1993-2011        19         5 0.3122271 2574 555 1831
  • 여러 변수의 결과를 새 변수로 결합하기 위해 paste() 함수를 사용
  • 유일한 시즌과 팀의 개수를 계산하기 위해 n_distinct() 함수를 사용

Manny는 5개의 팀에서 비슷하게 활약을 했을까?

manny %>%
    group_by(teamID) %>%
    summarize(
        span = paste(min(yearID), max(yearID), sep = "-"),
        num_years = n_distinct(yearID),
        num_teams = n_distinct(teamID),
        BA = sum(H)/sum(AB),
        tH = sum(H),
        tHR = sum(HR),
        tRBI = sum(RBI)
        ) %>%
    arrange(span)
# A tibble: 5 × 8
  teamID span      num_years num_teams     BA    tH   tHR  tRBI
  <fct>  <chr>         <int>     <int>  <dbl> <int> <int> <int>
1 CLE    1993-2000         8         1 0.313   1086   236   804
2 BOS    2001-2008         8         1 0.312   1232   274   868
3 LAN    2008-2010         3         1 0.322    237    44   156
4 CHA    2010-2010         1         1 0.261     18     1     2
5 TBA    2011-2011         1         1 0.0588     1     0     1
  • Cleveland, Boston, Los Angeles Dodgers에서는 활약을 많이 했지만 Chicago White Sox와 Tampa Bay Rays에선 큰 활약을 하지 못함.

  • 다음 분석을 통해서 통산 성적의 대부분을 아메리칸 리그에서 달성한 것을 알 수 있음.

manny %>%
    group_by(lgID) %>%
    summarize(
        span = paste(min(yearID), max(yearID), sep = "-"),
        num_years = n_distinct(yearID),
        num_teams = n_distinct(teamID),
        BA = sum(H)/sum(AB),
        tH = sum(H),
        tHR = sum(HR),
        tRBI = sum(RBI)
    ) %>%
    arrange(span)
# A tibble: 2 × 8
  lgID  span      num_years num_teams    BA    tH   tHR  tRBI
  <fct> <chr>         <int>     <int> <dbl> <int> <int> <int>
1 AL    1993-2011        18         4 0.311  2337   511  1675
2 NL    2008-2010         3         1 0.322   237    44   156

summarize()

Manny가 19시즌밖에 뛰지 않았는데 왜 21개의 행이 있을까?

  • 30개 이상의 홈런을 친 시즌의 수
manny %>%
    filter(HR >= 30) %>%
    nrow()
[1] 11
  • 2008년에 보스턴에서 홈런 20개를 친 후 트레이드되어 다저스에서 17개의 홈런을 더 친 기록이 반영되지 않았기 때문에 틀린 결과

  • 연도별 집계가 필요

manny %>%
    group_by(yearID) %>%
    summarize(tHR = sum(HR)) %>%
    filter(tHR >= 30) %>%
    nrow()
[1] 12

열쇠를 이용한 연결

  • 지금까지의 예제는 playerIDramirma02인 선수에 대한 타격 자료표를 필터링하는 것으로 시작

  • 이 ID를 어떻게 알았을까?

  • playerIDPeople 자료표에 정의된 기본 열쇠(primary key)

    • People 자료표의 모든 행은 playerID 값으로 유일하게 식별됨.
    • playerID == "ramirma02"인 행은 유일
  • Q: 이 ID가 매니 라미레즈와 일치한다는 것을 어떻게 알 수 있나?
  • A: 검색
People %>%
    filter(nameLast == "Ramirez" & nameFirst == "Manny") %>%
    kable()
playerID birthYear birthMonth birthDay birthCountry birthState birthCity deathYear deathMonth deathDay deathCountry deathState deathCity nameFirst nameLast nameGiven weight height bats throws debut finalGame retroID bbrefID deathDate birthDate
ramirma02 1972 5 30 D.R. Distrito Nacional Santo Domingo NA NA NA NA NA NA Manny Ramirez Manuel Aristides 225 72 R R 1993-09-02 2011-04-06 ramim002 ramirma02 NA 1972-05-30
  • 시즌에 따라 변하지 않는 매니 라미레즈에 대한 특성이 포함

inner_join()

  • playerID를 열쇠로 Batting 자료표와 People 자료표를 결합해보자.
Batting %>%
    filter(playerID == "ramirma02") %>%
    inner_join(People, by = c("playerID" = "playerID")) %>%
    group_by(yearID) %>%
    summarize(
        Age = max(yearID - birthYear),
        num_teams = n_distinct(teamID),
        BA = sum(H)/sum(AB),
        tH = sum(H),
        tHR = sum(HR),
        tRBI = sum(RBI)
    ) %>%
    arrange(yearID)
  • Batting 자료표에서 playerID 열은 다른 자료표의 기본 열쇠를 참조한다는 의미에서 외래 열쇠(foreign key)라고 함
    • Batting$playerID는 행을 유일하게 결정하지 않음 (왜 그런가?)
# A tibble: 19 × 7
   yearID   Age num_teams     BA    tH   tHR  tRBI
    <int> <int>     <int>  <dbl> <int> <int> <int>
 1   1993    21         1 0.170      9     2     5
 2   1994    22         1 0.269     78    17    60
 3   1995    23         1 0.308    149    31   107
 4   1996    24         1 0.309    170    33   112
 5   1997    25         1 0.328    184    26    88
 6   1998    26         1 0.294    168    45   145
 7   1999    27         1 0.333    174    44   165
 8   2000    28         1 0.351    154    38   122
 9   2001    29         1 0.306    162    41   125
10   2002    30         1 0.349    152    33   107
11   2003    31         1 0.325    185    37   104
12   2004    32         1 0.308    175    43   130
13   2005    33         1 0.292    162    45   144
14   2006    34         1 0.321    144    35   102
15   2007    35         1 0.296    143    20    88
16   2008    36         2 0.332    183    37   121
17   2009    37         1 0.290    102    19    63
18   2010    38         2 0.298     79     9    42
19   2011    39         1 0.0588     1     0     1

Manny의 영광의 시대는?

  • On-Base Plus Slugging Percentage (OPS) = OBP + SLG
    • On-Base Percentage (OBP): 출루율
    • Slugging Percentage (SLG) : 타수당 평균적으로 진루한 베이스 수 (1루타, 2루타, 3루타, 홈런)
manny_by_season <- Batting %>%
    filter(playerID == "ramirma02") %>%
    inner_join(People, by = c("playerID" = "playerID")) %>%
    group_by(yearID) %>%
    summarize(
        Age = max(yearID - birthYear),
        num_teams = n_distinct(teamID),
        BA = sum(H)/sum(AB),
        tH = sum(H),
        tHR = sum(HR),
        tRBI = sum(RBI),
        OBP = sum(H + BB + HBP) / sum(AB + BB + SF + HBP),
        SLG = sum(H + X2B + 2 * X3B + 3 * HR) / sum(AB)
    ) %>%
    mutate(OPS = OBP + SLG) %>%
    arrange(desc(OPS))
manny_by_season
# A tibble: 19 × 10
   yearID   Age num_teams     BA    tH   tHR  tRBI    OBP    SLG   OPS
    <int> <int>     <int>  <dbl> <int> <int> <int>  <dbl>  <dbl> <dbl>
 1   2000    28         1 0.351    154    38   122 0.457  0.697  1.15 
 2   1999    27         1 0.333    174    44   165 0.442  0.663  1.11 
 3   2002    30         1 0.349    152    33   107 0.450  0.647  1.10 
 4   2006    34         1 0.321    144    35   102 0.439  0.619  1.06 
 5   2008    36         2 0.332    183    37   121 0.430  0.601  1.03 
 6   2003    31         1 0.325    185    37   104 0.427  0.587  1.01 
 7   2001    29         1 0.306    162    41   125 0.405  0.609  1.01 
 8   2004    32         1 0.308    175    43   130 0.397  0.613  1.01 
 9   2005    33         1 0.292    162    45   144 0.388  0.594  0.982
10   1996    24         1 0.309    170    33   112 0.399  0.582  0.981
11   1998    26         1 0.294    168    45   145 0.377  0.599  0.976
12   1995    23         1 0.308    149    31   107 0.402  0.558  0.960
13   1997    25         1 0.328    184    26    88 0.415  0.538  0.953
14   2009    37         1 0.290    102    19    63 0.418  0.531  0.949
15   2007    35         1 0.296    143    20    88 0.388  0.493  0.881
16   1994    22         1 0.269     78    17    60 0.357  0.521  0.878
17   2010    38         2 0.298     79     9    42 0.409  0.460  0.870
18   1993    21         1 0.170      9     2     5 0.2    0.302  0.502
19   2011    39         1 0.0588     1     0     1 0.0588 0.0588 0.118

스테로이드 시대

  • OPS가 2000년에 가장 높은 것을 알 수 있음.
  • 그러나 2000년은 스테로이드 시대의 절정으로 많은 강타자들이 엄청난 수치를 기록함.
  • 따라서 OPS를 각 시즌의 리그 평균 OPS와 비교할 필요가 있음 (OPS+).

리그 평균 OPS 계산

mlb <- Batting %>%
    filter(yearID %in% 1993:2011) %>%
    group_by(yearID) %>%
    summarize(
        lg_OBP = sum(H + BB + HBP, na.rm = TRUE) /
        sum(AB + BB + SF + HBP, na.rm = TRUE),
        lg_SLG = sum(H + X2B + 2*X3B + 3*HR, na.rm = TRUE) /
        sum(AB, na.rm = TRUE)
    ) %>%
mutate(lg_OPS = lg_OBP + lg_SLG)

리그 평균 OPS를 Manny의 해당 항목과 시즌별로 결합, OPS+를 계산

manny_ratio <- manny_by_season %>%
    inner_join(mlb, by = c("yearID" = "yearID")) %>%
    mutate(OPS_plus = OPS / lg_OPS) %>%
    select(yearID, Age,OPS,lg_OPS,OPS_plus) %>%
    arrange(desc(OPS_plus))
manny_ratio
# A tibble: 19 × 5
   yearID   Age   OPS lg_OPS OPS_plus
    <int> <int> <dbl>  <dbl>    <dbl>
 1   2000    28 1.15   0.782    1.48 
 2   2002    30 1.10   0.748    1.47 
 3   1999    27 1.11   0.778    1.42 
 4   2006    34 1.06   0.768    1.38 
 5   2008    36 1.03   0.749    1.38 
 6   2003    31 1.01   0.755    1.34 
 7   2001    29 1.01   0.759    1.34 
 8   2004    32 1.01   0.763    1.32 
 9   2005    33 0.982  0.749    1.31 
10   1998    26 0.976  0.755    1.29 
11   1996    24 0.981  0.767    1.28 
12   1995    23 0.960  0.755    1.27 
13   2009    37 0.949  0.751    1.26 
14   1997    25 0.953  0.756    1.26 
15   2010    38 0.870  0.728    1.19 
16   2007    35 0.881  0.758    1.16 
17   1994    22 0.878  0.763    1.15 
18   1993    21 0.502  0.736    0.682
19   2011    39 0.118  0.720    0.163
  • 2000년은 여전히 최고의 시즌으로 평가할 수 있지만 1999년 시즌은 2위에서 3위로 떨어짐을 알 수 있음.

  • Manny는 메이저리그 전체 평균보다 적어도 15% 더 나은 OPS를 17년 연속으로 기록함.

칼 립켄

Cal Ripken Jr.: 1981–2001

  • 9년과 Manny와 겹침
ripken <- Batting %>%
  filter(playerID == "ripkeca01")
ripken %>%
  inner_join(mlb, by = c("yearID" = "yearID")) %>%
nrow()
[1] 9
  • Manny와 겹치지 않는 시즌
ripken %>%
    left_join(mlb, by = c("yearID" = "yearID")) %>%
    select(yearID, playerID,lg_OPS)%>%
    head(3)
  yearID  playerID lg_OPS
1   1981 ripkeca01     NA
2   1982 ripkeca01     NA
3   1983 ripkeca01     NA
  • left_join()에서 순서를 바꿈으로써 리그 평균을 계산한 19개의 시즌을 반환함.
mlb %>%
    left_join(ripken, by = c("yearID" = "yearID")) %>%
    select(yearID, playerID,lg_OPS)
# A tibble: 19 × 3
   yearID playerID  lg_OPS
    <int> <chr>      <dbl>
 1   1993 ripkeca01  0.736
 2   1994 ripkeca01  0.763
 3   1995 ripkeca01  0.755
 4   1996 ripkeca01  0.767
 5   1997 ripkeca01  0.756
 6   1998 ripkeca01  0.755
 7   1999 ripkeca01  0.778
 8   2000 ripkeca01  0.782
 9   2001 ripkeca01  0.759
10   2002 <NA>       0.748
11   2003 <NA>       0.755
12   2004 <NA>       0.763
13   2005 <NA>       0.749
14   2006 <NA>       0.768
15   2007 <NA>       0.758
16   2008 <NA>       0.749
17   2009 <NA>       0.751
18   2010 <NA>       0.728
19   2011 <NA>       0.720