public bigdata

R4DS (R FOR DATASCIENCE) 8장 readr로 하는 데이터 불러오기 본문

R programming/R4DS (R for DataScience)

R4DS (R FOR DATASCIENCE) 8장 readr로 하는 데이터 불러오기

public bigdata 2019. 12. 25. 22:26

8.2

  • read_csv : 쉼표로 구분된 파일 읽기
  • read_csv2 : 세미콜론으로 구분된 파일 읽기(,가 소수점 자리로 사용되는 국가에 일반적 형태)
  • read_tsv : 탭 구분 파일을 읽는다.
  • read_fwf : 고정 너비 파일을 읽는다.
  • read_delim : 임의의 구분자로 된 파일을 읽는다.
  • read_table : 고정 너비 파일의 일반적 변형 형태인 열이 공백으로 구분된 파일을 읽는다?
  • read_log : apache 스타일의 로그 파일을 읽는다.

<read_csv>

  • skip 인자를 통해 첫 n줄을 건너 뛸 수 있다.
  • comment = "#"을 사용하여 #으로 시작하는 모든 줄을 무시할 수 있다.
  • col_names = FALSE를 사용하면 첫 행을 헤드로 취급하지 않고 X1~Xn 순차적으로 이름을 붙인다. col_names = c("x", "y", "z") 형태로 사용하여 열 이름을 전달할 수도 있다.
  • na = "."   "."을 결측으로 인식한다.

아래 처럼 작성하면 쉽게 재현 가능한 예제를 만들 수 있다. \n 대신 엔터로 구분해도 된다.

> read_csv("1, 2, 3 \n 4, 5, 6", col_names = F)
# A tibble: 2 x 3
     X1    X2    X3
  <dbl> <dbl> <dbl>
1     1     2     3
2     4     5     6

8.2.2 연습문제

2번

read_csv, read_tsv가 공통으로 가진 인수는 무엇인가?

> intersect(names(formals(read_csv)), names(formals(read_tsv)))
 [1] "file"            "col_names"       "col_types"       "locale"         
 [5] "na"              "quoted_na"       "quote"           "comment"        
 [9] "trim_ws"         "skip"            "n_max"           "guess_max"      
[13] "progress"        "skip_empty_rows"

3번

read_fwf에서 가장 중요한 인수는 무엇인가

col_positions가 가장 중요하다. 컬럼의 시작과 종료지머을 알려줘야 한다.

 

4번

csv 파일의 문자열에 쉼표가 포함되는 경우가 있다. 그것들이 문제를 일으키지 않게 하려면 "또는 '와 같은 인용 문자로 둘러쌀 필요가 있는데 read_csv는 인용 문자가 "라고 가정한다. 이를 변경할려면 read_delim을 사용해서 '가 인용 문자임을 알려주면 된다.

> text <- "x, y \n1, 'a, b'"
> read_delim(text, delim = ",", quote = "'")
# A tibble: 1 x 2
      x ` y `
  <dbl> <chr>
1     1 a, b 
> read_csv(text, quote = "'")
# A tibble: 1 x 2
      x y    
  <dbl> <chr>
1     1 a, b 

8.3 벡터 파싱하기

1) readr이 디스크에서 파일을 읽는 방법에 대해 깊이 알아보기 전에, 잠깐 벗어나 parse_*()함수에 대해 살펴볼 필요가 있다. 이 함수들은 문자형 벡터를 입력으로 하여 논리형, 정수형 또는 날짜형과 같은 좀더 특수화된 벡터를 반환한다.

이 함수들은 독립적으로도 유용하지만, readr의 중요한 구성요소이기도 하다. 개별 파서들이 어떻게 동작하는지를 우선 배우고, 다음 절에서 개별 파서들이 어떻게 구성되어 파일 전체를 파싱하는지 살펴볼 것이다.

> str(parse_logical(c("TRUE", 'FALSE', 'NA')))
 logi [1:3] TRUE FALSE NA
> str(parse_integer(c("1", '2', '3')))
 int [1:3] 1 2 3
> str(parse_date(c("2010-01-01", '1979-10-14')))
 Date[1:2], format: "2010-01-01" "1979-10-14"

 

2) 파싱에 실패하면 경고가 뜨고, 많으면 problems()함수를 이용해 실패 전체를 가져와야 한다.

> x <- parse_integer(c('123', '345', 'abc', '123.45'))
Warning: 2 parsing failures.
row col               expected actual
  3  -- an integer                abc
  4  -- no trailing characters    .45

> problems(x)
# A tibble: 2 x 4
    row   col expected               actual
  <int> <int> <chr>                  <chr> 
1     3    NA an integer             abc   
2     4    NA no trailing characters .45   

problems는 티블을 반환하여 dplyr로 작업할 수 있다.

 

3) 파서를 잘 활용하려면 어떤 종류가 있는지 잘 이해해야 한다. 특별히 중요한 8개의 파서가 있다.

  • parse_logical, parse_integer는 논리형, 정수형을 파싱한다.
  • parse_double 엄격한 수치형 파서이고, parse_number는 유연한 수치형 파서이다. 이들은 예상보다 더 복잡하다. 왜냐하면 세계 여러 지경이 각자 다른 방식으로 숫자를 쓰기 때문이다.
  • parse_character 너무 단순해서 필요 없다고 생각할 수 있지만, 문자 인코딩이라는 것 때문에 매우 복잡하다.
  • parse_factor 팩터형을 생성한다. 
  • parse_datetime, parse_date, parse_time을 사용하면 다양한 날짜와 시각 데이터를 파싱할 수 있다. 날짜를 쓰는 방법은 다양하기  때문에 이 함수들이 가장 복잡하다.

8.3.1 숫자

숫자 파싱은 세 가지 문제로 인해서 까다롭다고 한다.

  • 국가마다 숫자를 다르게 작성한다. ex) 1000.1 / 1000,1
  • 숫자는 '$', '%'등의 문자와 함께 사용된다.
  • 천 단위 구분자가 국가마다 다르다. 한국은 ',' 콤마를 사용한다. ex) 1,000만원

1) 첫 번째 문제 때문에 파싱 옵션을 지정하는 객체인 locale이라는 개념을 사용한다고 한다. 가장 중요한 옵션은 소수점으로 사용하는 문자가 무엇인지 알려주는 인자이다. 새로운 locale을 생성하고 decimal_mark인수를 설정하여 기본값인 '.'을 다른 값으로 새롭게 정의할 수 있다고 한다.

library(readr)
> parse_double("1,23", locale = locale(decimal_mark = ','))
[1] 1.23

2) 두 번째 문제를 처리하는 parse_number()는 숫자 앞뒤의 비수치 문자를 무시한다. 통화 및 백분율에는 유용하지만, 텍스트에 포함된 숫자를 추출하는 데도 사용된다고 한다.

> parse_double("1,23", locale = locale(decimal_mark = ','))
[1] 1.23
> parse_number("$100")
[1] 100
> parse_number("It cos $123.45")
[1] 123.45

3) 세 번째는 parse_number(), locale을 사용하여 천 단위 구분자(그룹화 마크)를 무시하도록 할 수 있다.

> parse_number(
+   "123.456.789",
+   locale = locale(grouping_mark = ".")
+ )
[1] 123456789

8.3.2 문자열

1) charToRaw()함수를 이용하여 문자열의 기본 표현을 확인할 수 있다.

charToRaw 문자를 ASCII 코드로 변환할 때 사용

※ ASCII 코드는 문자를 16진수 수치로 표현하며, 정보 교환을 위한 미국 표준 코드(American Standard Code for Information Interchange)의 줄임말이다. 따라서 영문자를 잘 표현한다.

> charToRaw("Hadley")
[1] 48 61 64 6c 65 79

2) 인코딩 문제로 인해서 문자가 원하는 데로 출력이 되지 않는 경우 parse_character() 함수에 인코딩을 지정해줘야 한다.

> x1 <- "El Ni\xf1o was particularly bad this year"
> x1
[1] "El Ni<f1>o was particularly bad this year"
> parse_character("El Ni\xf1o was particularly bad this year", locale = locale(encoding = "Latin1"))
[1] "El Niño was particularly bad this year"

위 코드를 보면 "El Ni\xf1o was particularly bad this year"라는 Raw 문자열을 그대로 출력하면 원하는 데로 출력이 되지 않고, parse_charater를 통해서 locale을 적절하게 지정해줬을 때 원하는 데로 출력되는 것을 볼 수 있다.

# 여기서 왜 x1을 charToRaw 함수를 통해서 ASCII 코드로 변환해주는지 이해가 안된다.
> guess_encoding(charToRaw(x1))
# A tibble: 2 x 2
  encoding   confidence
  <chr>           <dbl>
1 ISO-8859-1       0.46
2 ISO-8859-9       0.23

또한, 문자열의 인코딩을 알 수 없을 때 readr::guess_encoding 함수를 통해서 인코딩을 추측해볼 수 있지만 정확하게 알 수 있는 경우는 많지 않다. 

문자열 인코딩의 경우 매우 방대하고 복잡한 주제이기 때문에 따로 자료를 찾아 공부할 필요가 있다.

8.3.3 팩터형

팩터형은 가질 수 있는 값을 미리 알고 있는 범주형 변수를 말한다. 예상하지 못한 값이 있을 때 경고를 생성하려면 parse_factor함수에 레벨 벡터를 제공해주면 된다.

> parse_factor(c("apple", "banana", "사과"), levels = c("apple", "banana"))
경고: 1 parsing failure.
row col           expected actual
  3  -- value in level set   사과

[1] apple  banana <NA>  
attr(,"problems")
# A tibble: 1 x 4
    row   col expected           actual
  <int> <int> <chr>              <chr> 
1     3    NA value in level set 사과  
Levels: apple banana

입력값에 문제가 많은 경우에는 우선적으로 입력값을 문자형으로 입력받는 것이 편할 수 있다.

8.3.4 데이트형, 데이트-타임형, 타임형

  • parse_datetime : ISO 8601 날짜-시간을 입력으로 한다. ISO 8601은 국제 표준이며 날짜가 가장 큰 것부터 가장 작은 것(년,월, 일, 시, 분, 초)으로 구성된다.
> parse_datetime("2010-10-01T2010")
[1] "2010-10-01 20:10:00 UTC"
  • parse_date는 네 자리 연도, - 또는 /, 월, - 또는 /, 날짜를 입력으로 한다.
> parse_date("2010-10/01")
[1] "2010-10-01"
  • parse_time은 시, :, 분 그리고 추가적으로 :, 초, a.m./p.m. 표시를 입력으로 받는다.
> parse_time("20:10:01")
20:10:01
> parse_time("01:10 am")
01:10:00

위의 기본 설정들로 주어진 날짜데이터를 처리하지 못하는 경우 자신만의 날짜-시간 형식을 만들어 사용할 수 있다. [해당 링크 11.3.4 참조] 데이트 형도 문자열 인코딩처럼 복잡하기 때문에 해당 링크를 통해 살펴보길 바란다.

8.3.5 연습문제

1. locale()에서 가장 중요한 인수들은 무엇인가?

  • 날짜 및 시간 형식 : date_names, date_format, 및time_format
  • 시간대 : tz
  • 숫자 : decimal_mark,grouping_mark
  • 부호화: encoding

2. decimal_mark와 grouping_mark를 동일 문자로 설정하려고 하면 어떻게 되는가?

> 오류 발생

decimal_mark를 ','로 설정하면 grouping_mark의 기본값은 어떻게 되는가? 

> "."으로 바뀐다

grouping_mark를 '.'으로 설정하면 decimal_mark의 기본값은 어떻게 되는가

>","로 바뀐다.

 

3, 4번은 이해가 어려움 해당 링크 참조

 

5. read_csv, read_csv2의 차이점은 무엇인가?

> ","를 사용하느냐, ";"을 사용하여 데이터를 구분하느냐의 차이 유럽에서는 ";"을 데이터를 구분하고 ","를 소수점을 표기하는데 사용한다.

 

6, 7 번 마찬가지로 해당 링크 참조

8.4 파일 파싱하기

readr 패키지는 처음오는 1000행을 읽어 내부 방법을 통해서 각 열의 유형을 찾는다. guess_parser()를 통해서 readr이 해당 벡터를 어떤 유형으로 판단하는지 알 수 있고, parse_guess()를 통해서 readr이 판단하는데로 해당 열을 파싱한다. 

> guess_parser("2010-10-10")
[1] "date"
> str(parse_guess("2010-10-10"))
 Date[1:1], format: "2010-10-10"

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

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

위 코드의 col_types 인자를 통해서 컬럼을 지정할 수 있고, 문제가 발생한다면 적절한 유형으로 지정하여 하나씩 바꾸어 나가는 것이 좋다.

  • 데이터가 이미 R의 문자형 벡터인 경우에 parse_*()함수를 통해 유형을 변경해주면 된다.
  • 아직 데이터를 불러오기 전이라면 readr이 적절하게 불러오도록 col_types 인자에 col_*()를 지정하면 된다.
  • 데이터를 엄격하게 불러오고 싶다면 stop_for_problems()를 사용하면 파싱 문제가 생기는 경우 오류를 발생하며 스크립트를 중단한다. 

※ stop_for_problems 활용 예시

> challenge <- read_csv(
+   readr_example("challenge.csv"),
+   col_types = cols(
+     x = col_integer(),
+     y = col_character()
+   )
+ )
경고: 1000 parsing failures.
 row col               expected             actual                                                                file
1001   x no trailing characters .23837975086644292 'C:/Users/bok/Videos/R/win-library/4.0/readr/extdata/challenge.csv'
1002   x no trailing characters .41167997173033655 'C:/Users/bok/Videos/R/win-library/4.0/readr/extdata/challenge.csv'
.... ... ...................... .................. ...................................................................
See problems(...) for more details.

> stop_for_problems(challenge)
에러: 1000 parsing failures

8.4.3 기타 전략

  • 한 행만 더 살펴봐서 정확하게 파싱될 수도 있기에 read_csv 함수의 guess_max 인자에 1001을 지정해서 기본값인 1000개 보다 1개 더 살펴봐서 정확하게 데이터를 불러와질 수 있다.
  • 최후의 수단으로는 모든 열을 문자형 벡터로 읽으면 일단 문제없이 데이터를 불러올 수는 있다. (col_types 인자의 col_character() 활용) 그런 다음 type_convert()함수를 이용하여 전체 데이터를 활용하여 변환하는 방법도 있다.
  • 추가적으로 데이터가 매우 큰 경우 매번 데이터를 불러오고 유형을 추측해보고 다시 불러오고 하는 반복 과정이 매우 무거울 수 있기에, read_*() 함수들의 n_max 인자를 적절히 설정하여 반복 실험을 쉽게 할 수 있다.
  • 파싱에 중대한 문제가 있는 경우 read_lines()함수를 이용해 라인으로 이루어진 문자형 벡터로 읽은 다음 나중에 배울 문자열 파싱 방법을 사용하여 좀 더 특이한 포맷을 파싱하면 된다고 한다. (이 부분은 이해가 안된다.)

8.5 파일에 쓰기

1) readr에는 데이터를 내보내는데 유용한 함수 write_csv(), write_tsv()가 있다. 두 함수 모두 아래 동작을 통해 출력 파일이 올바르게 다시 읽힐 수 있도록 한다.

  • 항상 UTF-8로 문자열을 인코딩한다.
  • 데이트형과 데이터-타임형을 ISO-8601 형식으로 저장하여 어디에서든 쉽게 파싱될 수 있도록 한다.

2) CSV 파일을 엑셀로 내보내는 경우에는 write_excel_csv()를 사용하라. 파일의 시작 부분에 특수 문자를 작성하여, UTF-8인코딩을 사용한다는 것을 엑셀에 전달한다. 

 

가장 중요한 인수는 x, path, na, append 이다. append는 기존 파일이 존재할 경우 아래에 내용을 추가할 것인지를 지정한다.

 

3) csv 파일형식으로 내보내면 컬럼의 유형 정보가 사라진다. 그래서 중간 결과를 저장하기에 신뢰할 수없어서 write_rds(), read_rds()함수를 활용해 R의 커스텀 바이너리 형식으로 데이터를 저장하고 불러와서 중간 결과를 효과적으로 추적이 가능하다.

 

feather 패키지는 다른 언어와 공유할 수 있는 빠른 바이너리 파일 형식을 구현 해주는데 write_feather(), read_feather()함수를 활용해 사용하면 된다. feather형식은 rds 형식보다 대체적으로 빠르고, R외부에서도 사용이 가능하다고 한다. 단 rds 형식을 제외하고 feather, fs 등의 형식을 가지는 바이너리는  리스트 컬럼을 저장할 수 없다.

8.6 기타 데이터 불러오기

  • haven 패키지 : SPSS, Stata, SAS 파일을 읽을 수 있다.
  • readxl 패키지 : 엑셀 파일을 읽을 수 있다.
  • DBI 패키지 : 데이터베이스에 특화된 패키지로 RMySQL, RSQLite 등의 패키지와 함께 사용하여 데이터베이스에 저장된 테이블을 R의 데이터프레임 형태로 쉽게 불러올 수 있도록 지원한다.
  • jsonlite 패키지 : json 파일을 읽을 수 있다.
  • xml2 패키지 : xml 파일을 읽을 수 있다.