7  데이터 가져오기

7.1 소개

R 패키지에서 제공하는 데이터로 작업하는 것은 데이터 과학 도구를 배우는 좋은 방법이지만, 언젠가는 배운 내용을 자신의 데이터에 적용하고 싶을 것입니다. 이 장에서는 데이터 파일을 R로 읽어오는 기본 사항을 배웁니다.

구체적으로 이 장에서는 일반 텍스트 사각형 파일(plain-text rectangular files)을 읽는 데 중점을 둘 것입니다. 열 이름, 유형, 결측 데이터와 같은 기능을 처리하기 위한 실용적인 조언으로 시작하겠습니다. 그런 다음 한 번에 여러 파일에서 데이터를 읽고 R에서 파일로 데이터를 쓰는 방법에 대해 배울 것입니다. 마지막으로 R에서 데이터 프레임을 직접 만드는 방법을 배울 것입니다.

7.1.1 선수 지식

이 장에서는 핵심 tidyverse의 일부인 readr 패키지를 사용하여 R에서 플랫 파일(flat files)을 로드하는 방법을 배웁니다.

library(tidyverse)
#> Warning: package 'ggplot2' was built under R version 4.5.2
#> Warning: package 'readr' was built under R version 4.5.2

7.2 파일에서 데이터 읽기

먼저 가장 일반적인 사각형 데이터 파일 유형인 CSV(comma-separated values, 쉼표로 구분된 값)에 초점을 맞출 것입니다. 간단한 CSV 파일의 모습은 다음과 같습니다. 일반적으로 헤더 행이라고 하는 첫 번째 행은 열 이름을 제공하고 다음 6개 행은 데이터를 제공합니다. 열은 쉼표로 구분(또는 구분자로 사용)됩니다.

Student ID,Full Name,favourite.food,mealPlan,AGE
1,Sunil Huffmann,Strawberry yoghurt,Lunch only,4
2,Barclay Lynn,French fries,Lunch only,5
3,Jayendra Lyne,N/A,Breakfast and lunch,7
4,Leon Rossini,Anchovies,Lunch only,
5,Chidiegwu Dunkel,Pizza,Breakfast and lunch,five
6,Güvenç Attila,Ice cream,Lunch only,6

Table 7.1 은 동일한 데이터를 표로 나타낸 것입니다.

Table 7.1: students.csv 파일의 데이터를 표로 나타낸 것.
Student ID Full Name favourite.food mealPlan AGE
1 Sunil Huffmann Strawberry yoghurt Lunch only 4
2 Barclay Lynn French fries Lunch only 5
3 Jayendra Lyne N/A Breakfast and lunch 7
4 Leon Rossini Anchovies Lunch only NA
5 Chidiegwu Dunkel Pizza Breakfast and lunch five
6 Güvenç Attila Ice cream Lunch only 6

read_csv()를 사용하여 이 파일을 R로 읽을 수 있습니다. 첫 번째 인수가 가장 중요합니다. 파일의 경로(path)입니다. 경로는 파일의 주소라고 생각할 수 있습니다. 파일 이름은 students.csv이고 data 폴더에 있습니다.

students <- read_csv("data/students.csv")
#> Rows: 6 Columns: 5
#> ── Column specification ─────────────────────────────────────────────────────
#> Delimiter: ","
#> chr (4): Full Name, favourite.food, mealPlan, AGE
#> dbl (1): Student ID
#> 
#> ℹ Use `spec()` to retrieve the full column specification for this data.
#> ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

위의 코드는 프로젝트의 data 폴더에 students.csv 파일이 있는 경우 작동합니다. https://pos.it/r4ds-students-csv에서 students.csv 파일을 다운로드하거나 다음을 사용하여 해당 URL에서 직접 읽을 수 있습니다:

students <- read_csv("https://pos.it/r4ds-students-csv")

read_csv()를 실행하면 데이터의 행과 열 수, 사용된 구분자, 열 사양(열에 포함된 데이터 유형별로 구성된 열 이름)을 알려주는 메시지가 출력됩니다. 또한 전체 열 사양을 검색하는 방법과 이 메시지를 끄는 방법에 대한 정보도 출력합니다. 이 메시지는 readr의 필수적인 부분이며 Section 7.3 에서 다시 다룰 것입니다.

7.2.1 실용적인 조언

데이터를 읽은 후 첫 번째 단계는 일반적으로 나머지 분석에서 작업하기 쉽도록 데이터를 어떤 식으로든 변형하는 것입니다. 그 점을 염두에 두고 students 데이터를 다시 살펴봅시다.

students
#> # A tibble: 6 × 5
#>   `Student ID` `Full Name`      favourite.food     mealPlan            AGE  
#>          <dbl> <chr>            <chr>              <chr>               <chr>
#> 1            1 Sunil Huffmann   Strawberry yoghurt Lunch only          4    
#> 2            2 Barclay Lynn     French fries       Lunch only          5    
#> 3            3 Jayendra Lyne    N/A                Breakfast and lunch 7    
#> 4            4 Leon Rossini     Anchovies          Lunch only          <NA> 
#> 5            5 Chidiegwu Dunkel Pizza              Breakfast and lunch five 
#> 6            6 Güvenç Attila    Ice cream          Lunch only          6

favourite.food 열에는 많은 음식 항목이 있고 그 다음 문자열 N/A가 있는데, 이는 R이 “사용할 수 없음(not available)”으로 인식하는 실제 NA였어야 합니다. 이것은 na 인수를 사용하여 해결할 수 있는 문제입니다. 기본적으로 read_csv()는 이 데이터셋의 빈 문자열("")만 NA로 인식하지만, 문자열 "N/A"도 인식하도록 하고 싶습니다.

students <- read_csv("data/students.csv", na = c("N/A", ""))

students
#> # A tibble: 6 × 5
#>   `Student ID` `Full Name`      favourite.food     mealPlan            AGE  
#>          <dbl> <chr>            <chr>              <chr>               <chr>
#> 1            1 Sunil Huffmann   Strawberry yoghurt Lunch only          4    
#> 2            2 Barclay Lynn     French fries       Lunch only          5    
#> 3            3 Jayendra Lyne    <NA>               Breakfast and lunch 7    
#> 4            4 Leon Rossini     Anchovies          Lunch only          <NA> 
#> 5            5 Chidiegwu Dunkel Pizza              Breakfast and lunch five 
#> 6            6 Güvenç Attila    Ice cream          Lunch only          6

Student IDFull Name 열이 역따옴표(backticks)로 둘러싸여 있는 것을 눈치챘을 수도 있습니다. 공백이 포함되어 있어 변수 이름에 대한 R의 일반적인 규칙을 위반하기 때문입니다. 이를 비구문적(non-syntactic) 이름이라고 합니다. 이러한 변수를 참조하려면 역따옴표 `로 감싸야 합니다:

students |> 
  rename(
    student_id = `Student ID`,
    full_name = `Full Name`
  )
#> # A tibble: 6 × 5
#>   student_id full_name        favourite.food     mealPlan            AGE  
#>        <dbl> <chr>            <chr>              <chr>               <chr>
#> 1          1 Sunil Huffmann   Strawberry yoghurt Lunch only          4    
#> 2          2 Barclay Lynn     French fries       Lunch only          5    
#> 3          3 Jayendra Lyne    <NA>               Breakfast and lunch 7    
#> 4          4 Leon Rossini     Anchovies          Lunch only          <NA> 
#> 5          5 Chidiegwu Dunkel Pizza              Breakfast and lunch five 
#> 6          6 Güvenç Attila    Ice cream          Lunch only          6

대안적인 접근 방식은 janitor::clean_names()를 사용하여 몇 가지 휴리스틱을 사용해 모든 이름을 한 번에 스네이크 케이스(snake case)로 바꾸는 것입니다1.

students |> janitor::clean_names()
#> # A tibble: 6 × 5
#>   student_id full_name        favourite_food     meal_plan           age  
#>        <dbl> <chr>            <chr>              <chr>               <chr>
#> 1          1 Sunil Huffmann   Strawberry yoghurt Lunch only          4    
#> 2          2 Barclay Lynn     French fries       Lunch only          5    
#> 3          3 Jayendra Lyne    <NA>               Breakfast and lunch 7    
#> 4          4 Leon Rossini     Anchovies          Lunch only          <NA> 
#> 5          5 Chidiegwu Dunkel Pizza              Breakfast and lunch five 
#> 6          6 Güvenç Attila    Ice cream          Lunch only          6

데이터를 읽은 후의 또 다른 일반적인 작업은 변수 유형을 고려하는 것입니다. 예를 들어 meal_plan은 가능한 값 집합이 알려진 범주형 변수이며, R에서는 팩터(factor)로 표현되어야 합니다:

students |>
  janitor::clean_names() |>
  mutate(meal_plan = factor(meal_plan))
#> # A tibble: 6 × 5
#>   student_id full_name        favourite_food     meal_plan           age  
#>        <dbl> <chr>            <chr>              <fct>               <chr>
#> 1          1 Sunil Huffmann   Strawberry yoghurt Lunch only          4    
#> 2          2 Barclay Lynn     French fries       Lunch only          5    
#> 3          3 Jayendra Lyne    <NA>               Breakfast and lunch 7    
#> 4          4 Leon Rossini     Anchovies          Lunch only          <NA> 
#> 5          5 Chidiegwu Dunkel Pizza              Breakfast and lunch five 
#> 6          6 Güvenç Attila    Ice cream          Lunch only          6

meal_plan 변수의 값은 그대로 유지되었지만 변수 이름 아래에 표시된 변수 유형이 문자(<chr>)에서 팩터(<fct>)로 변경되었습니다. 팩터에 대해서는 Chapter 16 에서 더 자세히 배울 것입니다.

이 데이터를 분석하기 전에 아마도 age 열을 수정하고 싶을 것입니다. 현재 age는 관측값 중 하나가 숫자 5 대신 five로 입력되어 있기 때문에 문자 변수입니다. 이 문제를 해결하는 세부 사항은 Chapter 20 에서 논의합니다.

students <- students |>
  janitor::clean_names() |>
  mutate(
    meal_plan = factor(meal_plan),
    age = parse_number(if_else(age == "five", "5", age))
  )

students
#> # A tibble: 6 × 5
#>   student_id full_name        favourite_food     meal_plan             age
#>        <dbl> <chr>            <chr>              <fct>               <dbl>
#> 1          1 Sunil Huffmann   Strawberry yoghurt Lunch only              4
#> 2          2 Barclay Lynn     French fries       Lunch only              5
#> 3          3 Jayendra Lyne    <NA>               Breakfast and lunch     7
#> 4          4 Leon Rossini     Anchovies          Lunch only             NA
#> 5          5 Chidiegwu Dunkel Pizza              Breakfast and lunch     5
#> 6          6 Güvenç Attila    Ice cream          Lunch only              6

여기서 새로운 함수는 if_else()로, 세 가지 인수가 있습니다. 첫 번째 인수 test는 논리형 벡터여야 합니다. 결과는 testTRUE일 때 두 번째 인수 yes의 값을, FALSE일 때 세 번째 인수 no의 값을 포함합니다. 여기서는 age가 문자열 "five"이면 "5"로 만들고, 그렇지 않으면 age 그대로 두라고 말하고 있습니다. if_else()와 논리형 벡터에 대해서는 Chapter 12 에서 더 자세히 배울 것입니다.

7.2.2 기타 인수

언급해야 할 다른 중요한 인수가 몇 가지 있는데, 편리한 트릭을 먼저 보여드리면 시연하기가 더 쉬울 것입니다. read_csv()는 CSV 파일처럼 만들고 서식을 지정한 텍스트 문자열을 읽을 수 있습니다:

read_csv(
  "a,b,c
  1,2,3
  4,5,6"
)
#> # A tibble: 2 × 3
#>       a     b     c
#>   <dbl> <dbl> <dbl>
#> 1     1     2     3
#> 2     4     5     6

일반적으로 read_csv()는 데이터의 첫 번째 줄을 열 이름으로 사용하는데, 이는 매우 일반적인 관례입니다. 하지만 파일 상단에 몇 줄의 메타데이터가 포함되는 경우도 드물지 않습니다. skip = n을 사용하여 처음 n 줄을 건너뛰거나 comment = "#"을 사용하여 (예를 들어) #로 시작하는 모든 줄을 삭제할 수 있습니다:

read_csv(
  "메타데이터 첫 번째 줄
  메타데이터 두 번째 줄
  x,y,z
  1,2,3",
  skip = 2
)
#> # A tibble: 1 × 3
#>       x     y     z
#>   <dbl> <dbl> <dbl>
#> 1     1     2     3

read_csv(
  "# 건너뛰고 싶은 주석
  x,y,z
  1,2,3",
  comment = "#"
)
#> # A tibble: 1 × 3
#>       x     y     z
#>   <dbl> <dbl> <dbl>
#> 1     1     2     3

다른 경우에는 데이터에 열 이름이 없을 수도 있습니다. col_names = FALSE를 사용하여 read_csv()에 첫 번째 행을 헤더로 처리하지 말고 대신 X1에서 Xn까지 순차적으로 레이블을 지정하도록 지시할 수 있습니다:

read_csv(
  "1,2,3
  4,5,6",
  col_names = FALSE
)
#> # A tibble: 2 × 3
#>      X1    X2    X3
#>   <dbl> <dbl> <dbl>
#> 1     1     2     3
#> 2     4     5     6

또는 col_names에 문자 벡터를 전달하여 열 이름으로 사용할 수 있습니다:

read_csv(
  "1,2,3
  4,5,6",
  col_names = c("x", "y", "z")
)
#> # A tibble: 2 × 3
#>       x     y     z
#>   <dbl> <dbl> <dbl>
#> 1     1     2     3
#> 2     4     5     6

이러한 인수들은 실제로 마주칠 대부분의 CSV 파일을 읽는 데 알아야 할 전부입니다. (나머지는 .csv 파일을 주의 깊게 검사하고 read_csv()의 다른 많은 인수에 대한 설명서를 읽어야 합니다.)

7.2.3 기타 파일 유형

read_csv()를 마스터하면 readr의 다른 기능을 사용하는 것은 간단합니다. 어떤 기능을 사용해야 하는지 알면 됩니다:

  • read_csv2()는 세미콜론으로 구분된 파일을 읽습니다. 이들은 필드를 구분하는 데 , 대신 ;을 사용하며 소수점 표시에 ,를 사용하는 국가에서 일반적입니다.

  • read_tsv()는 탭으로 구분된 파일을 읽습니다.

  • read_delim()은 구분자가 있는 파일을 읽으며, 지정하지 않으면 구분자를 자동으로 추측하려고 시도합니다.

  • read_fwf()는 고정 너비 파일을 읽습니다. fwf_widths()로 너비로 필드를 지정하거나 fwf_positions()로 위치로 필드를 지정할 수 있습니다.

  • read_table()은 열이 공백으로 구분되는 고정 너비 파일의 일반적인 변형을 읽습니다.

  • read_log()는 Apache 스타일의 로그 파일을 읽습니다.

7.2.4 연습문제

  1. 필드가 “|”로 구분된 파일을 읽으려면 어떤 함수를 사용하겠습니까?

  2. file, skip, comment 외에 read_csv()read_tsv()가 공통적으로 갖는 다른 인수는 무엇입니까?

  3. read_fwf()의 가장 중요한 인수는 무엇입니까?

  4. 때때로 CSV 파일의 문자열에 쉼표가 포함되어 있습니다. 문제를 일으키지 않으려면 " 또는 '와 같은 인용 문자로 둘러싸야 합니다. 기본적으로 read_csv()는 인용 문자가 "일 것이라고 가정합니다. 다음 텍스트를 데이터 프레임으로 읽으려면 read_csv()의 어떤 인수를 지정해야 합니까?

    "x,y\n1,'a,b'"
  5. 다음 인라인 CSV 파일 각각에 무엇이 잘못되었는지 식별하세요. 코드를 실행하면 어떻게 됩니까?

    read_csv("a,b\n1,2,3\n4,5,6")
    read_csv("a,b,c\n1,2\n1,2,3,4")
    read_csv("a,b\n\"1")
    read_csv("a,b\n1,2\na,b")
    read_csv("a;b\n1;3")
  6. 다음 데이터 프레임에서 비구문적 이름을 참조하는 연습을 하세요:

    1. 1이라는 변수 추출하기.
    2. 12의 산점도 그리기.
    3. 21로 나눈 3이라는 새 열 만들기.
    4. 열 이름을 one, two, three로 바꾸기.
    annoying <- tibble(
      `1` = 1:10,
      `2` = `1` * 2 + rnorm(length(`1`))
    )

7.3 열 유형 제어

CSV 파일에는 각 변수의 유형(즉, 논리형인지, 숫자인지, 문자열인지 등)에 대한 정보가 포함되어 있지 않으므로 readr은 유형을 추측하려고 시도합니다. 이 섹션에서는 추측 프로세스가 작동하는 방식, 실패를 유발하는 몇 가지 일반적인 문제를 해결하는 방법, 필요한 경우 열 유형을 직접 제공하는 방법에 대해 설명합니다. 마지막으로 readr이 치명적으로 실패하고 파일 구조에 대한 더 많은 통찰력이 필요한 경우 유용한 몇 가지 일반적인 전략을 언급할 것입니다.

7.3.1 유형 추측

readr은 휴리스틱을 사용하여 열 유형을 파악합니다. 각 열에 대해 첫 번째 행에서 마지막 행까지 균등하게 간격을 둔 1,000개 행2의 값을 가져오며 결측값은 무시합니다. 그런 다음 다음 질문을 통해 작업합니다:

  • F, T, FALSE, TRUE만 포함합니까(대소문자 무시)? 그렇다면 논리형입니다.
  • 숫자만 포함합니까(예: 1, -4.5, 5e6, Inf)? 그렇다면 숫자입니다.
  • ISO8601 표준과 일치합니까? 그렇다면 날짜 또는 날짜-시간입니다. (Section 17.2 에서 날짜-시간에 대해 더 자세히 다시 다룰 것입니다).
  • 그렇지 않으면 문자열이어야 합니다.

이 간단한 예제에서 해당 동작을 실제로 볼 수 있습니다:

read_csv(
  "\n  logical,numeric,date,string
  TRUE,1,2021-01-15,abc
  false,4.5,2021-02-15,def
  T,Inf,2021-02-16,ghi
")
#> # A tibble: 3 × 4
#>   logical numeric date       string
#>   <lgl>     <dbl> <date>     <chr> 
#> 1 TRUE        1   2021-01-15 abc   
#> 2 FALSE       4.5 2021-02-15 def   
#> 3 TRUE      Inf   2021-02-16 ghi

이 휴리스틱은 깨끗한 데이터셋이 있는 경우 잘 작동하지만, 실제로는 기이하고 아름다운 실패들을 마주하게 될 것입니다.

7.3.2 결측값, 열 유형 및 문제

열 감지가 실패하는 가장 일반적인 방법은 열에 예기치 않은 값이 포함되어 있어 더 구체적인 유형 대신 문자 열을 얻는 것입니다. 가장 일반적인 원인 중 하나는 readr이 예상하는 NA가 아닌 다른 것으로 기록된 결측값입니다.

이 간단한 1열 CSV 파일을 예로 들어보겠습니다:

simple_csv <- "
  x
  10
  .
  20
  30"

추가 인수 없이 읽으면 x는 문자 열이 됩니다:

read_csv(simple_csv)
#> # A tibble: 4 × 1
#>   x    
#>   <chr>
#> 1 10   
#> 2 .    
#> 3 20   
#> 4 30

이 아주 작은 경우에는 결측값 .를 쉽게 볼 수 있습니다. 하지만 수천 개의 행이 있고 그 사이에 .로 표시된 결측값이 몇 개만 흩어져 있다면 어떻게 될까요? 한 가지 접근 방식은 readr에 x가 숫자 열이라고 알린 다음 어디에서 실패하는지 확인하는 것입니다. CSV 파일의 열 이름과 이름이 일치하는 명명된 리스트를 취하는 col_types 인수로 그렇게 할 수 있습니다:

df <- read_csv(
  simple_csv, 
  col_types = list(x = col_double())
)
#> Warning: One or more parsing issues, call `problems()` on your data frame for
#> details, e.g.:
#>   dat <- vroom(...)
#>   problems(dat)

이제 read_csv()는 문제가 있었다고 보고하고 problems()를 통해 더 많은 정보를 얻을 수 있다고 알려줍니다:

problems(df)
#> # A tibble: 1 × 5
#>     row   col expected actual file                                           
#>   <int> <int> <chr>    <chr>  <chr>                                          
#> 1     3     1 a double .      /private/var/folders/qc/wsq_wkqn6c37nz9rfqqpvp…

이것은 3행 1열에 문제가 있었는데 readr은 double을 예상했지만 .을 얻었음을 알려줍니다. 이는 이 데이터셋이 결측값에 .을 사용한다는 것을 시사합니다. 그래서 na = "."을 설정하면 자동 추측이 성공하여 우리가 원하는 숫자 열을 얻게 됩니다:

read_csv(simple_csv, na = ".")
#> # A tibble: 4 × 1
#>       x
#>   <dbl>
#> 1    10
#> 2    NA
#> 3    20
#> 4    30

7.3.3 열 유형

readr은 사용자가 사용할 수 있는 총 9가지 열 유형을 제공합니다:

  • col_logical()col_double()은 논리형 및 실수를 읽습니다. readr이 일반적으로 추측하므로 상대적으로 거의 필요하지 않습니다(위와 같은 경우 제외).
  • col_integer()는 정수를 읽습니다. 이 책에서는 정수와 double이 기능적으로 동일하기 때문에 거의 구별하지 않지만, 정수가 double의 절반 메모리를 차지하므로 명시적으로 정수를 읽는 것이 가끔 유용할 수 있습니다.
  • col_character()는 문자열을 읽습니다. 숫자 식별자, 즉 객체를 식별하지만 수학 연산을 적용하는 것이 의미가 없는 긴 숫자 시리즈인 열이 있을 때 명시적으로 지정하는 데 유용할 수 있습니다. 예로는 전화번호, 주민등록번호, 신용카드 번호 등이 있습니다.
  • col_factor(), col_date(), col_datetime()은 각각 팩터, 날짜, 날짜-시간을 생성합니다. Chapter 16Chapter 17 에서 해당 데이터 유형에 도달했을 때 자세히 배울 것입니다.
  • col_number()는 숫자가 아닌 구성 요소를 무시하는 관대한 숫자 파서이며 통화에 특히 유용합니다. Chapter 13 에서 자세히 배울 것입니다.
  • col_skip()은 열을 건너뛰어 결과에 포함되지 않도록 합니다. 이는 대용량 CSV 파일이 있고 일부 열만 사용하려는 경우 데이터 읽기 속도를 높이는 데 유용할 수 있습니다.

list()에서 cols()로 전환하고 .default를 지정하여 유형을 추측하는 기본 휴리스틱을 재정의하는 것도 가능합니다:

another_csv <- "
x,y,z
1,2,3"

read_csv(
  another_csv, 
  col_types = cols(.default = col_character())
)
#> # A tibble: 1 × 3
#>   x     y     z    
#>   <chr> <chr> <chr>
#> 1 1     2     3

또 다른 유용한 도우미는 지정한 열만 읽어들이는 cols_only()입니다:

read_csv(
  another_csv,
  col_types = cols_only(x = col_character())
)
#> # A tibble: 1 × 1
#>   x    
#>   <chr>
#> 1 1

7.4 여러 파일에서 데이터 읽기

때로는 데이터가 단일 파일에 포함되어 있는 대신 여러 파일로 나뉘어 있습니다. 예를 들어, 여러 달의 판매 데이터가 있고 각 달의 데이터가 별도 파일(01-sales.csv(1월), 02-sales.csv(2월), 03-sales.csv(3월))에 있을 수 있습니다. read_csv()를 사용하면 이 데이터를 한 번에 읽어서 단일 데이터 프레임에 서로 쌓을 수 있습니다.

sales_files <- c("data/01-sales.csv", "data/02-sales.csv", "data/03-sales.csv")
read_csv(sales_files, id = "file")
#> # A tibble: 19 × 6
#>   file              month    year brand  item     n
#>   <chr>             <chr>   <dbl> <dbl> <dbl> <dbl>
#> 1 data/01-sales.csv January  2019     1  1234     3
#> 2 data/01-sales.csv January  2019     1  8721     9
#> 3 data/01-sales.csv January  2019     1  1822     2
#> 4 data/01-sales.csv January  2019     2  3333     1
#> 5 data/01-sales.csv January  2019     2  2156     9
#> 6 data/01-sales.csv January  2019     2  3987     6
#> # ℹ 13 more rows

다시 말하지만, 위의 코드는 프로젝트의 data 폴더에 CSV 파일이 있는 경우 작동합니다. https://pos.it/r4ds-01-sales, https://pos.it/r4ds-02-sales, https://pos.it/r4ds-03-sales에서 이 파일들을 다운로드하거나 다음을 사용하여 직접 읽을 수 있습니다:

sales_files <- c(
  "https://pos.it/r4ds-01-sales",
  "https://pos.it/r4ds-02-sales",
  "https://pos.it/r4ds-03-sales"
)
read_csv(sales_files, id = "file")

id 인수는 데이터가 어떤 파일에서 왔는지 식별하는 file이라는 새 열을 결과 데이터 프레임에 추가합니다. 이는 읽어들이는 파일에 관측값을 원래 소스로 추적하는 데 도움이 되는 식별 열이 없는 경우 특히 유용합니다.

읽고 싶은 파일이 많으면 이름을 목록으로 작성하는 것이 번거로울 수 있습니다. 대신 기본 list.files() 함수를 사용하여 파일 이름의 패턴을 일치시켜 파일을 찾을 수 있습니다. Chapter 15 에서 이러한 패턴에 대해 더 자세히 배울 것입니다.

sales_files <- list.files("data", pattern = "sales\\.csv$", full.names = TRUE)
sales_files
#> [1] "data/01-sales.csv" "data/02-sales.csv" "data/03-sales.csv"

7.5 파일에 쓰기

readr에는 데이터를 디스크에 다시 쓰는 두 가지 유용한 함수인 write_csv()write_tsv()도 함께 제공됩니다. 이 함수의 가장 중요한 인수는 x(저장할 데이터 프레임)와 file(저장할 위치)입니다. 또한 na로 결측값을 어떻게 쓸지 지정할 수 있고, 기존 파일에 append(추가)하고 싶은지 지정할 수 있습니다.

write_csv(students, "students.csv")

이제 그 csv 파일을 다시 읽어 봅시다. CSV에 저장할 때 방금 설정한 변수 유형 정보가 손실된다는 점에 유의하세요. 일반 텍스트 파일에서 다시 읽기 시작하기 때문입니다:

students
#> # A tibble: 6 × 5
#>   student_id full_name        favourite_food     meal_plan             age
#>        <dbl> <chr>            <chr>              <fct>               <dbl>
#> 1          1 Sunil Huffmann   Strawberry yoghurt Lunch only              4
#> 2          2 Barclay Lynn     French fries       Lunch only              5
#> 3          3 Jayendra Lyne    <NA>               Breakfast and lunch     7
#> 4          4 Leon Rossini     Anchovies          Lunch only             NA
#> 5          5 Chidiegwu Dunkel Pizza              Breakfast and lunch     5
#> 6          6 Güvenç Attila    Ice cream          Lunch only              6
write_csv(students, "students-2.csv")
read_csv("students-2.csv")
#> # A tibble: 6 × 5
#>   student_id full_name        favourite_food     meal_plan             age
#>        <dbl> <chr>            <chr>              <chr>               <dbl>
#> 1          1 Sunil Huffmann   Strawberry yoghurt Lunch only              4
#> 2          2 Barclay Lynn     French fries       Lunch only              5
#> 3          3 Jayendra Lyne    <NA>               Breakfast and lunch     7
#> 4          4 Leon Rossini     Anchovies          Lunch only             NA
#> 5          5 Chidiegwu Dunkel Pizza              Breakfast and lunch     5
#> 6          6 Güvenç Attila    Ice cream          Lunch only              6

이로 인해 CSV는 중간 결과를 캐싱하는 데 약간 신뢰할 수 없습니다. 로드할 때마다 열 사양을 다시 생성해야 하기 때문입니다. 두 가지 주요 대안이 있습니다:

  1. write_rds()read_rds()는 기본 함수인 readRDS()saveRDS()를 감싸는 균일한 래퍼입니다. 이들은 RDS라는 R의 사용자 정의 이진 형식으로 데이터를 저장합니다. 즉, 객체를 다시 로드할 때 저장한 것과 정확히 같은 R 객체를 로드하게 됩니다.

    write_rds(students, "students.rds")
    read_rds("students.rds")
    #> # A tibble: 6 × 5
    #>   student_id full_name        favourite_food     meal_plan             age
    #>        <dbl> <chr>            <chr>              <fct>               <dbl>
    #> 1          1 Sunil Huffmann   Strawberry yoghurt Lunch only              4
    #> 2          2 Barclay Lynn     French fries       Lunch only              5
    #> 3          3 Jayendra Lyne    <NA>               Breakfast and lunch     7
    #> 4          4 Leon Rossini     Anchovies          Lunch only             NA
    #> 5          5 Chidiegwu Dunkel Pizza              Breakfast and lunch     5
    #> 6          6 Güvenç Attila    Ice cream          Lunch only              6
  2. arrow 패키지를 사용하면 프로그래밍 언어 간에 공유할 수 있는 빠른 이진 파일 형식인 parquet 파일을 읽고 쓸 수 있습니다. Chapter 22 에서 arrow에 대해 더 깊이 다룰 것입니다.

    library(arrow)
    write_parquet(students, "students.parquet")
    read_parquet("students.parquet")
    #> # A tibble: 6 × 5
    #>   student_id full_name        favourite_food     meal_plan             age
    #>        <dbl> <chr>            <chr>              <fct>               <dbl>
    #> 1          1 Sunil Huffmann   Strawberry yoghurt Lunch only              4
    #> 2          2 Barclay Lynn     French fries       Lunch only              5
    #> 3          3 Jayendra Lyne    NA                 Breakfast and lunch     7
    #> 4          4 Leon Rossini     Anchovies          Lunch only             NA
    #> 5          5 Chidiegwu Dunkel Pizza              Breakfast and lunch     5
    #> 6          6 Güvenç Attila    Ice cream          Lunch only              6

Parquet는 RDS보다 훨씬 빠르고 R 외부에서 사용할 수 있는 경향이 있지만 arrow 패키지가 필요합니다.

7.6 데이터 입력

때로는 R 스크립트에서 약간의 데이터 입력을 수행하여 “손으로” 티블을 조립해야 할 수도 있습니다. 이를 수행하는 데 도움이 되는 두 가지 유용한 함수가 있는데, 티블을 열별로 배치할지 행별로 배치할지에 따라 다릅니다. tibble()은 열별로 작동합니다:

tibble(
  x = c(1, 2, 5), 
  y = c("h", "m", "g"),
  z = c(0.08, 0.83, 0.60)
)
#> # A tibble: 3 × 3
#>       x y         z
#>   <dbl> <chr> <dbl>
#> 1     1 h      0.08
#> 2     2 m      0.83
#> 3     5 g      0.6

열별로 데이터를 배치하면 행이 어떻게 관련되어 있는지 보기 어려울 수 있으므로 대안은 transposed tibble(전치된 티블)의 줄임말인 tribble()로, 데이터를 행별로 배치할 수 있습니다. tribble()은 코드 내 데이터 입력에 맞춤화되어 있습니다: 열 머리글은 ~로 시작하고 항목은 쉼표로 구분됩니다. 이를 통해 읽기 쉬운 형식으로 소량의 데이터를 배치할 수 있습니다:

tribble(
  ~x, ~y, ~z,
  1, "h", 0.08,
  2, "m", 0.83,
  5, "g", 0.60
)
#> # A tibble: 3 × 3
#>       x y         z
#>   <dbl> <chr> <dbl>
#> 1     1 h      0.08
#> 2     2 m      0.83
#> 3     5 g      0.6

7.7 요약

이 장에서는 read_csv()로 CSV 파일을 로드하는 방법과 tibble()tribble()로 직접 데이터를 입력하는 방법을 배웠습니다. csv 파일이 작동하는 방식, 발생할 수 있는 문제, 극복 방법에 대해 배웠습니다. 이 책에서 데이터 가져오기에 대해 몇 번 더 다룰 것입니다: Chapter 20 에서는 Excel 및 Google Sheets에서, Chapter 21 에서는 데이터베이스에서 데이터를 로드하는 방법을, Chapter 22 에서는 parquet 파일에서, Chapter 23 에서는 JSON에서, Chapter 24 에서는 웹사이트에서 데이터를 로드하는 방법을 보여줄 것입니다.

이 책의 이 섹션은 거의 끝났지만 다뤄야 할 중요한 마지막 주제가 하나 있습니다: 도움을 받는 방법입니다. 따라서 다음 장에서는 도움을 찾을 수 있는 좋은 곳, 좋은 도움을 받을 가능성을 극대화하기 위해 reprex(재현 가능한 예제)를 만드는 방법, R의 세계를 따라잡기 위한 몇 가지 일반적인 조언을 배울 것입니다.


  1. janitor 패키지는 tidyverse의 일부는 아니지만 편리한 데이터 정리 기능을 제공하며 |>를 사용하는 데이터 파이프라인 내에서 잘 작동합니다.↩︎

  2. guess_max 인수로 기본값 1000을 재정의할 수 있습니다.↩︎