1.4 파일 파싱하기

이제까지 개별 벡터를 파싱하는 방법을 배웠으므로, 처음으로 돌아가서 readr을 이용하여 파일을 파싱하는 방법을 알아볼 차례이다. 이 절에서는 다음의 두 방법을 배운다.

  1. readr이 각 열의 유형을 자동으로 추측하는 방법.
  2. 기본 사양을 재정의하는 방법.

1.4.1 전략

readr은 휴리스틱 방법을 사용하여 각 열의 유형을 파악한다. 첫 번째 1000행을 읽고 (적절히 보수적인) 휴리스틱 방법을 사용하여 각 열의 유형을 찾는다.

guess_parser()(readr의 추정을 반환)와 parse_guess() (앞의 추정을 사용하여 열을 파싱)를 사용하여 문자형 벡터에 이 과정을 재현해볼 수 있다.

guess_parser("2010-10-01")
## [1] "date"
guess_parser("15:01")
## [1] "time"
guess_parser(c("TRUE", "FALSE"))
## [1] "logical"
guess_parser(c("1", "5", "9"))
## [1] "double"
guess_parser(c("12,352,561"))
## [1] "number"
str(parse_guess("2010-10-10"))
##  Date[1:1], format: "2010-10-10"

사용해야 할 parse_*()*을 결정하기가 곤란한 경우 guess_parse() 함수를 활용하여 확인할 수 있다.

이 휴리스틱 방법은 다음 유형들을 각각 시도하여 일치하는 항목을 찾으면 멈춘다.

  • 논리형: “F,” “T,” “FALSE,” “TRUE”만 포함.
  • 정수형: 수치형 문자(와 -)만 포함.
  • 더블형: (4.5e-5와 같은 숫자를 포함하는) 유효한 더블형만 포함.
  • 수치형: 내부에 그룹화 마크가 있는 유효한 더블형을 포함.
  • 타임형: time_format의 기본형식과 일치.
  • 데이트형: date_format의 기본형식과 일치.
  • 데이트-시간형: ISO8601 날짜.

이러한 규칙 중 어느 것도 적용되지 않으면 해당 열은 문자열 벡터로 그대로 남는다.

1.4.2 문제점

큰 파일의 경우 다음과 같은 이유로 이러한 기본값이 항상 잘 작동하지는 않을 수 있다.

  1. 처음 1,000 행이 특수한 경우이어서 readr이 충분히 일반적이지 않은 유형으로 추측할 수 있다. 예를 들어 첫 번째 1,000개의 행에 정수만 있는 더블형 열이 있을 수 있다.
  2. 열에 결측값이 많이 있을 수 있다. 첫 번째 1,000 개의 행에 NA 만 있는 경우 readr 이 문자형 벡터로 추측했지만, 여러분은 좀 더 구체적으로 파싱하고 싶을 수 있다.

readr에는 이러한 두 가지 문제를 모두 보여주는 까다로운 CSV 예제 파일로 challenge.csv 파일이 포함되어 있다. 이 파일을 읽어 들이기 위해 readr_example() 함수를 이용하여 파일의 경로를 확인할 수 있다.

readr_example("challenge.csv")
## [1] "C:/Program Files/R/R-4.0.3/library/readr/extdata/challenge.csv"
  • challenge.csv 파일이 저장된 경로와 파일이름이 표시된다.
  • 참고로 패키지에 포함된 파일의 경로를 찾기 위해서는 readr_example()을 사용한다.

이를 read_csv()의 인수로 데이터 파일을 불러와서 challenge 변수에 대입하는 스크립트는 다음과 같다.

challenge <- read_csv(readr_example("challenge.csv"))
## 
## -- Column specification --------------------------------------------------------
## cols(
##   x = col_double(),
##   y = col_logical()
## )
## Warning: 1000 parsing failures.
##  row col           expected     actual                                                             file
## 1001   y 1/0/T/F/TRUE/FALSE 2015-01-16 'C:/Program Files/R/R-4.0.3/library/readr/extdata/challenge.csv'
## 1002   y 1/0/T/F/TRUE/FALSE 2018-05-18 'C:/Program Files/R/R-4.0.3/library/readr/extdata/challenge.csv'
## 1003   y 1/0/T/F/TRUE/FALSE 2015-09-05 'C:/Program Files/R/R-4.0.3/library/readr/extdata/challenge.csv'
## 1004   y 1/0/T/F/TRUE/FALSE 2012-11-28 'C:/Program Files/R/R-4.0.3/library/readr/extdata/challenge.csv'
## 1005   y 1/0/T/F/TRUE/FALSE 2020-01-13 'C:/Program Files/R/R-4.0.3/library/readr/extdata/challenge.csv'
## .... ... .................. .......... ................................................................
## See problems(...) for more details.
problems(challenge)
## # A tibble: 1,000 x 5
##      row col   expected        actual   file                                    
##    <int> <chr> <chr>           <chr>    <chr>                                   
##  1  1001 y     1/0/T/F/TRUE/F~ 2015-01~ 'C:/Program Files/R/R-4.0.3/library/rea~
##  2  1002 y     1/0/T/F/TRUE/F~ 2018-05~ 'C:/Program Files/R/R-4.0.3/library/rea~
##  3  1003 y     1/0/T/F/TRUE/F~ 2015-09~ 'C:/Program Files/R/R-4.0.3/library/rea~
##  4  1004 y     1/0/T/F/TRUE/F~ 2012-11~ 'C:/Program Files/R/R-4.0.3/library/rea~
##  5  1005 y     1/0/T/F/TRUE/F~ 2020-01~ 'C:/Program Files/R/R-4.0.3/library/rea~
##  6  1006 y     1/0/T/F/TRUE/F~ 2016-04~ 'C:/Program Files/R/R-4.0.3/library/rea~
##  7  1007 y     1/0/T/F/TRUE/F~ 2011-05~ 'C:/Program Files/R/R-4.0.3/library/rea~
##  8  1008 y     1/0/T/F/TRUE/F~ 2020-07~ 'C:/Program Files/R/R-4.0.3/library/rea~
##  9  1009 y     1/0/T/F/TRUE/F~ 2011-04~ 'C:/Program Files/R/R-4.0.3/library/rea~
## 10  1010 y     1/0/T/F/TRUE/F~ 2010-05~ 'C:/Program Files/R/R-4.0.3/library/rea~
## # ... with 990 more rows
  • challenge.csv 파일은 readr 패키지가 제공하는 예제 데이터 세트임.
  • read_csv()에서 열에 대한 데이터 타입을 지정하지 않아 y 컬럼의 값이 모두 NA로 인식함.

두 가지가 출력되었다.

  • 첫 번째 1,000 개의 행을 보고 생성된 열 상세 내용과 첫 다섯 개의 파싱 오류가 그것이다.
  • 발생한 문제들을 ‘problems()’ 로 명시적으로 추출하여 더 깊이 탐색하는 것은 좋은 방법이다.

1.4.3 문제 해결 전략

문제가 남아있지 않을 때까지 열 단위로 작업하는 것은 좋은 전략이다. x 열에 파싱 문제가 많다는 것을 알 수 있다. 정수값 다음에 따라오는 문자가 있었던 것이다. 이는 더블형 파서를 사용해야 함을 암시한다.

이 호출을 수정하기 위해 먼저 열 사양을 복사하여 원래 호출에 붙여 넣어보라.

1.4.3.1 x 컬럼 타입을 정수로, 그리고 y컬럼을 문자형으로 지정

x는 정수형, y는 문자형으로 지정해 보기로 한다.

challenge <- read_csv(
  readr_example("challenge.csv"), 
  col_types = cols(
    x = col_integer(),
    y = col_character()
  )
)
## Warning: 1000 parsing failures.
##  row col               expected              actual                                                             file
## 1001   x no trailing characters 0.23837975086644292 'C:/Program Files/R/R-4.0.3/library/readr/extdata/challenge.csv'
## 1002   x no trailing characters 0.41167997173033655 'C:/Program Files/R/R-4.0.3/library/readr/extdata/challenge.csv'
## 1003   x no trailing characters 0.7460716762579978  'C:/Program Files/R/R-4.0.3/library/readr/extdata/challenge.csv'
## 1004   x no trailing characters 0.723450553836301   'C:/Program Files/R/R-4.0.3/library/readr/extdata/challenge.csv'
## 1005   x no trailing characters 0.614524137461558   'C:/Program Files/R/R-4.0.3/library/readr/extdata/challenge.csv'
## .... ... ...................... ................... ................................................................
## See problems(...) for more details.

1.4.3.2 x 컬럼 타입을 실수형 그리고 y컬럼을 문자형으로 지정

그런 다음 x 열의 유형을 ‘col_double()’로 조정할 수 있다.

challenge <- read_csv(
  readr_example("challenge.csv"), 
  col_types = cols(
    x = col_double(),
    y = col_character()
  )
)

tail(challenge)
## # A tibble: 6 x 2
##       x y         
##   <dbl> <chr>     
## 1 0.805 2019-11-21
## 2 0.164 2018-03-29
## 3 0.472 2014-08-04
## 4 0.718 2015-08-16
## 5 0.270 2020-02-04
## 6 0.608 2019-01-06

첫 번째 문제는 해결되었지만, 마지막 몇 행을 보면 날짜문자형 벡터(<chr>)로 저장되었다.

1.4.3.3 x 컬럼 타입을 실수형 그리고 y컬럼을 날짜형으로 지정

y 열을 데이트형(col_date())으로 설정하여 이를 수정할 수 있다.

challenge <- read_csv(
  readr_example("challenge.csv"), 
  col_types = cols(
    x = col_double(),
    y = col_date()
  )
)
tail(challenge)
## # A tibble: 6 x 2
##       x y         
##   <dbl> <date>    
## 1 0.805 2019-11-21
## 2 0.164 2018-03-29
## 3 0.472 2014-08-04
## 4 0.718 2015-08-16
## 5 0.270 2020-02-04
## 6 0.608 2019-01-06

모든 parse_xyz() 함수는 해당하는 col_xyz() 함수를 가지고 있다. 데이터가 이미 R 의 문자형 벡터인 경우에는 parse_xyz() 를 사용하면 되고, readr 이 데이터를 불러오는 방법을 설정할 경우에는 col_xyz() 를 사용하면 된다.

col_types 를 항상 설정하여 readr 이 생성하는 출력물로부터 만들어 나가는 것을 강력히 추천한다. 이렇게 하면 일관되고 재현할 수 있는 데이터 불러오기 스크립트를 갖게 된다. 기본값으로 추측하여 데이터를 읽는다면 데이터 변경 시 readr 은 과거 설정으로 읽게 될 것이다. 정말로 엄격하게 하고 싶다면 stop_for_problems() 를 사용하라. 파싱 문제가 생기면 오류를 내며 스크립트를 중단할 것이다.

1.4.4 기타 전략

파일을 파싱하는 데 도움이 되는 몇 가지 다른 일반적인 전략이 있다.

  • 앞의 예제에서 우리는 단지 운이 없었다. 즉, 기본값보다 한 행만 더 살펴보면 한 번에 정확하게 파싱할 수 있다.
challenge2 <- read_csv(readr_example("challenge.csv"), guess_max = 1001)
## 
## -- Column specification --------------------------------------------------------
## cols(
##   x = col_double(),
##   y = col_date(format = "")
## )
  • 모든 열을 문자형 벡터로 읽으면 문제를 쉽게 진단할 수 있는 경우가 많다.
challenge2 <- read_csv(readr_example("challenge.csv"), 
                       col_types = cols(.default = col_character())
)

이 방법은 type_convert() 와 함께 사용하면 특히 유용한데, 이 함수는 휴리스틱한 파싱 방법을 데이터프레임의 문자형 열에 적용한다.

tribble() 함수의 활용

df <- tribble(
  ~x,  ~y,
  "1", "1.21",
  "2", "2.32",
  "3", "4.56"
)
df
## # A tibble: 3 x 2
##   x     y    
##   <chr> <chr>
## 1 1     1.21 
## 2 2     2.32 
## 3 3     4.56

#####열 유형을 주의

type_convert(df)
## 
## -- Column specification --------------------------------------------------------
## cols(
##   x = col_double(),
##   y = col_double()
## )
## # A tibble: 3 x 2
##       x     y
##   <dbl> <dbl>
## 1     1  1.21
## 2     2  2.32
## 3     3  4.56
  • 매우 큰 파일을 읽는 경우, n_max 를 10,000 또는 100,000 과 같이 작은 숫자로 설정할 수 있다. 이렇게 하면 일반적인 문제를 해결하는 동시에 반복작업을 가속화할 수 있다.
  • 파싱에 중대한 문제가 있는 경우에는 read_lines() 을 이용하여 라인으로 이루어진 문자형 벡터로 읽거나 read_file() 을 이용하여 길이가 1인 문자형 벡터로 읽는 것이 더 쉬울 수 있다. 그런 다음 나중에 배울 문자열 파싱 방법을 사용하여 좀 더 특이한 포맷을 파싱하면 된다.