본문 바로가기

R

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

Sean Lahman Baseball Database

 

오랜만에 이전 포스팅에 가 보았다. (https://double-d.tistory.com/18) 데이터베이스 예제로 Sean Lahman의 Baseball Database를 소개한 글이었다. 링크로 남겨두었던 웹사이트에 가보니 연결이 되지 않는다. 구글링을 해보니 다행히 웹사이트는 그대로 있다. 다만 공유하는 데이터베이스 형태가 줄어들었다. 예전에는 sql light버전 및 다양한 형태로 제공되었는데, 이제는 MS Access 형태와 CSV만 제공된다. 

윈도우 사용자의 경우 MS Access 파일인 .mdb를 다운받아 사용할 수 있다. 하지만 맥 사용자는 CSV 파일만 사용이 가능하다. 

이번 글에서는 업데이트된 Sean Lahman의 웹사이트 기준으로 데이터를 R 로 불러오는 여러 방법에 대해서 알아보자. 이는 기존에 데이터베이스 형태로 제공되는 데이터프레임을 R로 불러오는 방법이며, 추후에 다른 글에서 직접 작성한 엑셀 파일로부터 불러오는 방법, RODBC 패키지를 통해서 쿼리를 사용한 데이터 불러오는 방법 등을 다루겠다. 

 

준비: 데이터베이스 다운로드

http://www.seanlahman.com/

 

SeanLahman.com

Sean Lahman, journalist and baseball data guru

www.seanlahman.com

 

위의 Sean Lahman 웹사이트에 들어가면, 언급한 바와 같이 두 가지 형태의 데이터베이스를 다운로드할 수 있다. 이 중에서 'comma-delimited version' 을 클릭하면 baseballdatabank-2023.1.zip을 다운로드 할 수 있다.  

Sean Lahman 웹사이트

압축을 풀면 contrib, core, upstream 폴더가 나오고 각 폴더 안에 .csv형태의 데이터 테이블들을 볼 수 있다. 

 

데이터 불러오기 방법 1: CSV파일 하나씩 불러오기

가장 원시적이고 무식한 방법은 하나씩 read_csv() 함수로 불러들이는 것이다. 예를 들어, 'core' 폴더에 있는 People과 Managers라는 데이터 테이블을 데이터프레임으로 불러들일 경우, 아래와 같이 한다.

People  <- read_csv("data\core\People.csv")
Managers <- read_csv("data\core\Managers.csv")

직관적이고, 어쩌면 전체 데이터 테이블을 잘 모르는 상황에서 하나씩 확인하며 추가하기에는 좋을 수도 있다. 하지만 원하는 테이블이 여러 개라면 더 쉬운 방법도 있지 않을까?

 

데이터 불러오기 방법 2: map()함수 사용

경로값을 지정하고, map()함수로 반복해서 돌리는 방법이 있다. dir_ls()함수는 directory에 있는 파일들의 경로를 리스트로 만들어 준다. 리스트에 파일명을 포함한 경로들이 생성되고, map()함수가 지정된 함수인 read_csv()함수를 각 경로의 파일명들에 대해서 실행시켜 준다. 

이를 위해서는 'fs'와 'purrr' 패키지가 설치되어야 한다. 함수의 설치는 install.packages() 함수를 통해 할 수 있고, 설치된 후에는 'library()' 함수를 통해서 해당 패키지 안에 있는 함수들을 활성화해줘야 한다. 

# Import multiple CSV files
library(fs)
library(purrr)

csv_core_dir <- "data\baseballdatabank-2023.1\core"

baseball_data_list <- csv_core_dir %>%
	dir_ls() %>%
	map(
		.f = function (path){
			read.csv(path)		
		}
		)

폴더 경로는 ""를 쓴 후에 'Tab'키를 누르면 현재 R 의 경로에서 하나씩 선택해 들어갈 수 있다. 참고로 되도록이면 R Project를 생성한 폴더 안에 'Data' 폴더나 'Source' 폴더를 만들어서 데이터베이스 파일을 미리 넣어 놓길 권장한다. 이에 대해서는 추후에 다른 포스팅 글을 통해 안내하도록 하겠다. 

위에서 'csv_core_dir'은 csv파일들이 저장되어 있는 폴더 경로이다. 'dir_ls()'에 경로값을 입력해 주면 해당 경로 안에 있는 파일들의 full path로 list를 만들어 준다. 예를 들어, 위의 core폴더 안에 People.csv에 대해서는 'data\baseballdatabank-2023.1\core\People.csv', Managers.csv에 대해서는 'data\baseballdatabank-2023.1\core\Managers.csv' 등으로 만든 후, List 타입의 데이터로 경로값들을 변환시켜 준다. map()함수가 List에 있는 값들을 하나씩 불러들여 적용할 함수에 입력값으로 반복적으로 하나씩 넣어주는 역할을 한다. map()함수에 대해서도 다른 글을 통해 추가적으로 설명하도록 하겠다.

 

결과물인 'baseball_data_list' 를 콘솔에서 실행시켜보면 아래와 같이 각각의 데이터프레임을 요소로 갖는 List임을 확인할 수 있다. 아래는 그 결과값 화면의 일부이다. 

> baseball_data_list
$`01_data/baseballdatabank-2023.1/core/AllstarFull.csv`
# A tibble: 5,516 × 8
   playerID  yearID gameNum gameID       teamID lgID     GP startingPos
   <chr>      <dbl>   <dbl> <chr>        <chr>  <chr> <dbl>       <dbl>
 1 gomezle01   1933       0 ALS193307060 NYA    AL        1           1
 2 ferreri01   1933       0 ALS193307060 BOS    AL        1           2
 3 gehrilo01   1933       0 ALS193307060 NYA    AL        1           3
 4 gehrich01   1933       0 ALS193307060 DET    AL        1           4
 5 dykesji01   1933       0 ALS193307060 CHA    AL        1           5
 6 cronijo01   1933       0 ALS193307060 WS1    AL        1           6
 7 chapmbe01   1933       0 ALS193307060 WS1    AL        1           7
 8 simmoal01   1933       0 ALS193307060 CHA    AL        1           8
 9 ruthba01    1933       0 ALS193307060 NYA    AL        1           9
10 averiea01   1933       0 ALS193307060 CLE    AL        1          NA
# ℹ 5,506 more rows
# ℹ Use `print(n = ...)` to see more rows

$`01_data/baseballdatabank-2023.1/core/Appearances.csv`
# A tibble: 112,106 × 21
   yearID teamID lgID  playerID  G_all    GS G_batting G_defense   G_p   G_c  G_1b  G_2b  G_3b  G_ss  G_lf  G_cf  G_rf  G_of  G_dh  G_ph  G_pr
    <dbl> <chr>  <chr> <chr>     <dbl> <dbl>     <dbl>     <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
 1   1871 TRO    NA    abercda01     1     1         1         1     0     0     0     0     0     1     0     0     0     0     0     0     0
 2   1871 RC1    NA    addybo01     25    25        25        25     0     0     0    22     0     3     0     0     0     0     0     0     0
 3   1871 CL1    NA    allisar01    29    29        29        29     0     0     0     2     0     0     0    29     0    29     0     0     0
 4   1871 WS3    NA    allisdo01    27    27        27        27     0    27     0     0     0     0     0     0     0     0     0     0     0
 5   1871 RC1    NA    ansonca01    25    25        25        25     0     5     1     2    20     0     1     0     0     1     0     0     0
 6   1871 FW1    NA    armstbo01    12    12        12        12     0     0     0     0     0     0     0    11     1    12     0     0     0
 7   1871 RC1    NA    barkeal01     1     1         1         1     0     0     0     0     0     0     1     0     0     1     0     0     0
 8   1871 BS1    NA    barnero01    31    31        31        31     0     0     0    16     0    15     0     0     0     0     0     0     0
 9   1871 FW1    NA    barrebi01     1     1         1         1     0     1     0     0     1     0     0     0     0     0     0     0     0
10   1871 BS1    NA    barrofr01    18    17        18        18     0     0     0     1     0     0    13     0     4    17     0     0     0
# ℹ 112,096 more rows
# ℹ Use `print(n = ...)` to see more rows

$`01_data/baseballdatabank-2023.1/core/Batting.csv`
# A tibble: 112,184 × 22
   playerID  yearID stint teamID lgID      G    AB     R     H  `2B`  `3B`    HR   RBI    SB    CS    BB    SO   IBB   HBP    SH    SF  GIDP
   <chr>      <dbl> <dbl> <chr>  <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
 1 abercda01   1871     1 TRO    NA        1     4     0     0     0     0     0     0     0     0     0     0    NA    NA    NA    NA     0
 2 addybo01    1871     1 RC1    NA       25   118    30    32     6     0     0    13     8     1     4     0    NA    NA    NA    NA     0

 

'baseball_data_list'라는 List 안에 있는 하나의 데이터 프레임만 뽑아보려면 어떻게 하면 될까? 만약 10번째 파일인 'Managers.csv' 파일의 데이터프레임을 보고 싶다면 어떻게 할까?

# Show the 10th dataframe in the 'baseball_data_list'
baseball_data_list[10]

List의 10번째 요소값인 Managers.csv 데이터프레임만 뽑아내면 아래와 같은 결과화면을 볼 수 있다.

> baseball_data_list[10]
$`01_data/baseballdatabank-2023.1/core/Managers.csv`
# A tibble: 3,718 × 10
   playerID  yearID teamID lgID  inseason     G     W     L  rank plyrMgr
   <chr>      <dbl> <chr>  <chr>    <dbl> <dbl> <dbl> <dbl> <dbl> <chr>  
 1 wrighha01   1871 BS1    NA           1    31    20    10     3 Y      
 2 woodji01    1871 CH1    NA           1    28    19     9     2 Y      
 3 paborch01   1871 CL1    NA           1    29    10    19     8 Y      
 4 lennobi01   1871 FW1    NA           1    14     5     9     8 Y      
 5 deaneha01   1871 FW1    NA           2     5     2     3     8 Y      
 6 fergubo01   1871 NY2    NA           1    33    16    17     5 Y      
 7 mcbridi01   1871 PH1    NA           1    28    21     7     1 Y      
 8 hastisc01   1871 RC1    NA           1    25     4    21     9 Y      
 9 pikeli01    1871 TRO    NA           1     4     1     3     6 Y      
10 cravebi01   1871 TRO    NA           2    25    12    12     6 Y      
# ℹ 3,708 more rows
# ℹ Use `print(n = ...)` to see more rows

참고로 리스트 안에 있는 데이터프레임의 목록을 보기 위해서는 R Studio의 오른쪽에 있는 Environment에서 'baseball_data_list'를 클릭해 보면 된다. 아래는 클릭했을 때 볼 수 있는 목록이다. 

 

데이터 불러오기 방법 3: map()으로 불러온 데이터프레임들을 합쳐서 하나로 만든 후 사용하기

우선 위에서 설명한 map()함수를 사용하는 방법까지는 똑같다. 'baseball_data_list' 까지 만들었다면, list에 있는 데이터프레임들을 그냥 전부 붙여 버려서 'baseball_data_tbl'을 만들자. 즉, map()함수로 만들어낸 list에서 하나씩 목록을 확인하며 필요한 걸 뽑아내기 귀찮다면 이 방법을 사용할 수 있다. 

baseball_data_tbl <- baseball_data_list %>% 
    set_names(dir_ls(csv_core_dir)) %>% 
    bind_rows(.id ="file_path")

위의 방법으로 다 붙여 버렸을 때의 단점이 있다. 열이 모든 데이터프레임에 있는 열 숫자만큼 늘어나서 필요한 항목의 값을 찾기 번거로워질 수 있다. 이럴 때는 reduce(full_join) 등을 써서 열의 숫자를 최대한 줄이는 방법도 부분적으로 가능하다. 관련한 내용은 다른 글에서 다루도록 하겠다. 

> baseball_data_tbl
# A tibble: 546,875 × 143
   file_path    playerID yearID gameNum gameID teamID lgID     GP startingPos G_all    GS G_batting G_defense
   <chr>        <chr>     <dbl>   <dbl> <chr>  <chr>  <chr> <dbl>       <dbl> <dbl> <dbl>     <dbl>     <dbl>
 1 01_data/bas… gomezle…   1933       0 ALS19… NYA    AL        1           1    NA    NA        NA        NA
 2 01_data/bas… ferreri…   1933       0 ALS19… BOS    AL        1           2    NA    NA        NA        NA
 3 01_data/bas… gehrilo…   1933       0 ALS19… NYA    AL        1           3    NA    NA        NA        NA
 4 01_data/bas… gehrich…   1933       0 ALS19… DET    AL        1           4    NA    NA        NA        NA
 5 01_data/bas… dykesji…   1933       0 ALS19… CHA    AL        1           5    NA    NA        NA        NA
 6 01_data/bas… cronijo…   1933       0 ALS19… WS1    AL        1           6    NA    NA        NA        NA
 7 01_data/bas… chapmbe…   1933       0 ALS19… WS1    AL        1           7    NA    NA        NA        NA
 8 01_data/bas… simmoal…   1933       0 ALS19… CHA    AL        1           8    NA    NA        NA        NA
 9 01_data/bas… ruthba01   1933       0 ALS19… NYA    AL        1           9    NA    NA        NA        NA
10 01_data/bas… averiea…   1933       0 ALS19… CLE    AL        1          NA    NA    NA        NA        NA
# ℹ 546,865 more rows
# ℹ 130 more variables: G_p <dbl>, G_c <dbl>, G_1b <dbl>, G_2b <dbl>, G_3b <dbl>, G_ss <dbl>, G_lf <dbl>,
#   G_cf <dbl>, G_rf <dbl>, G_of <dbl>, G_dh <dbl>, G_ph <dbl>, G_pr <dbl>, stint <dbl>, G <dbl>, AB <dbl>,
#   R <dbl>, H <dbl>, `2B` <dbl>, `3B` <dbl>, HR <dbl>, RBI <dbl>, SB <dbl>, CS <dbl>, BB <dbl>, SO <dbl>,
#   IBB <dbl>, HBP <dbl>, SH <dbl>, SF <dbl>, GIDP <dbl>, round <chr>, POS <chr>, InnOuts <dbl>, PO <dbl>,
#   A <dbl>, E <dbl>, DP <dbl>, PB <dbl>, WP <dbl>, ZR <dbl>, Glf <dbl>, Gcf <dbl>, Grf <dbl>, TP <dbl>,
#   year.key <dbl>, league.key <chr>, team.key <chr>, park.key <chr>, span.first <date>, span.last <date>, …
# ℹ Use `print(n = ...)` to see more rows, and `colnames()` to see all variable names
>

 

데이터 불러오기 방법 4:  Lahman 패키지 불러와서 사용하기

사실 Lahman Baseball database 같은 경우에는 R에서 패키지로 데이터세트를 제공한다. 워낙 유명한 무료 데이터소스이기 때문이다. 이 경우, 다른 R Package와 같이, 설치 후에 library()함수로 불러오면 된다. 아래는 People이라는 데이터프레임을 불러오는 방법이다. 

People 외에 어떤 데이터프레임임 있는지 확인하려면 패키지에 대한 정보를 확인하면 된다. '?Lahman'을 입력하고 실행하면 패키지에 대한 모든 정보가 데이터프레임까지 포함해서 나온다. 원하는 데이터프레임에 대한 설명까지 확인하면, 해당 데이터프레임의 이름을 입력하고 실행해 주면 된다. 

install.packages("Lahman")
library(Lahman)

?Lahman

people

People 데이터프레임에 있는 인물정보는 이름의 알파벳 순서로 정렬되어 있다. 코딩 몇 줄을 추가하여 어린 순서대로 상위 10명을 확인해보자. 

People %>% 
    arrange(desc(birthYear)) %>% 
    slice(1:10)

결괏값은 아래와 같다. 

> People %>% 
+     arrange(desc(birthYear)) %>% 
+     slice(1:10)
    playerID birthYear birthMonth birthDay birthCountry birthState   birthCity deathYear deathMonth deathDay
1  alvarfr01      2001         11       19    Venezuela    Miranda     Guatire        NA         NA       NA
2  francwa01      2001          3        1         D.R.    Peravia        Bani        NA         NA       NA
3  grissva01      2001          1        5          USA         FL     Orlando        NA         NA       NA
4  harrimi04      2001          3        7          USA         GA      DeKalb        NA         NA       NA
5  hendegu01      2001          6       29          USA         AL  Montgomery        NA         NA       NA
6  tovarez01      2001          8        1    Venezuela     Aragua     Maracay        NA         NA       NA
7  abramcj01      2000         10        3          USA         GA  Alpharetta        NA         NA       NA
8  ariasga01      2000          2       27    Venezuela     Aragua La Victoria        NA         NA       NA
9  carroco02      2000          8       21          USA         WA     Seattle        NA         NA       NA
10 casastr01      2000          1       15          USA         FL       Miami        NA         NA       NA
   deathCountry deathState deathCity nameFirst  nameLast         nameGiven weight height bats throws
1          <NA>       <NA>      <NA> Francisco   Alvarez  Francisco Javier    242     70    R      R
2          <NA>       <NA>      <NA>    Wander    Franco     Wander Samuel    189     70    B      R
3          <NA>       <NA>      <NA>    Vaughn   Grissom    Vaughn Anthony    210     75    R      R
4          <NA>       <NA>      <NA>   Michael    Harris   Michael Machion    195     72    L      L
5          <NA>       <NA>      <NA>    Gunnar Henderson     Gunnar Randal    210     74    L      R
6          <NA>       <NA>      <NA>  Ezequiel     Tovar    Ezequiel Jesus    162     72    R      R
7          <NA>       <NA>      <NA>        CJ    Abrams  Paul Christopher    185     74    L      R
8          <NA>       <NA>      <NA>   Gabriel     Arias Gabriel Alejandro    217     73    R      R
9          <NA>       <NA>      <NA>    Corbin   Carroll   Corbin Franklin    165     70    L      L
10         <NA>       <NA>      <NA>   Triston     Casas       Triston Ray    252     76    L      R
        debut  finalGame  retroID   bbrefID deathDate  birthDate
1  2022-09-30 2022-10-05 alvaf001 alvarfr01      <NA> 2001-11-19
2  2021-06-22 2022-10-04 franw002 francwa01      <NA> 2001-03-01
3  2022-08-10 2022-10-05 grisv001 grissva01      <NA> 2001-01-05
4  2022-05-28 2022-10-04 harrm004 harrimi04      <NA> 2001-03-07
5  2022-08-31 2022-10-05 hendg002 hendegu01      <NA> 2001-06-29
6  2022-09-23 2022-10-05 tovae001 tovarez01      <NA> 2001-08-01
7  2022-04-08 2022-10-04 abrac001 abramcj01      <NA> 2000-10-03
8  2022-04-20 2022-10-05 ariag002 ariasga01      <NA> 2000-02-27
9  2022-08-29 2022-10-05 carrc005 carroco02      <NA> 2000-08-21
10 2022-09-04 2022-10-05 casat001 casastr01      <NA> 2000-01-15

 

데이터를 확인한 김에 하나 더 해보자. Join함수를 통해서 선수 Managers.cvs 와  People.csv 파일을 하나로 합쳐보자. join함수에 대해서는 추후의 글을 통해 다시 한번 설명하도록 하겠다. 

Managers %>% 
    filter(yearID > 2021,
           rank == 1) %>% 
    left_join(People %>% select(playerID, nameFirst, nameLast, nameGiven))

위의 코드에 대한 결과값은 아래와 같다. 

> Managers %>% 
+     filter(yearID > 2021,
+            rank == 1) %>% 
+     left_join(People %>% select(playerID, nameFirst, nameLast, nameGiven))
Joining with `by = join_by(playerID)`
   playerID yearID teamID lgID inseason   G   W  L rank plyrMgr nameFirst  nameLast         nameGiven
1 francte01   2022    CLE   AL        1 162  92 70    1       N     Terry  Francona         Terry Jon
2 bakerdu01   2022    HOU   AL        1 162 106 56    1       N     Dusty     Baker        Johnnie B.
3 booneaa01   2022    NYA   AL        1 162  99 63    1       N     Aaron     Boone        Aaron John
4 roberda07   2022    LAN   NL        1 162 111 51    1       N      Dave   Roberts         David Ray
5 showabu99   2022    NYN   NL        1 162 101 61    1       N      Buck Showalter William Nathaniel
6 marmool99   2022    SLN   NL        1 162  93 69    1       N    Oliver    Marmol       Oliver Jose
>

 

추가 정보: R 기본제공 데이터세트

참고로 Lahman패키지 외에, R에서 패키지 형태로 제공하는 데이터세트는 많이 있다. 업데이트가 덜 된 것 같지만, 아래 링크를 통해 더 많은 데이터세트를 확인해 보자. 

https://stat.ethz.ch/R-manual/R-devel/library/datasets/html/00Index.html

 

R: The R Datasets Package

The R Datasets Package Documentation for package ‘datasets’ version 4.4.0 Help Pages A B C D E F H I J L M N O P Q R S T U V W ability.cov Ability and Intelligence Tests airmiles Passenger Miles on Commercial US Airlines, 1937-1960 AirPassengers Monthl

stat.ethz.ch