7  Nhập dữ liệu

7.1 Giới thiệu

Làm việc với dữ liệu được cung cấp bởi các package R là một cách tuyệt vời để học các công cụ khoa học dữ liệu, nhưng đến một lúc nào đó bạn sẽ muốn áp dụng những gì đã học vào dữ liệu của chính mình. Trong chương này, bạn sẽ học những kiến thức cơ bản về việc đọc các file dữ liệu vào R.

Cụ thể, chương này sẽ tập trung vào việc đọc các file văn bản thuần (plain-text) dạng hình chữ nhật (rectangular). Chúng ta sẽ bắt đầu với các lời khuyên thực tế để xử lý các đặc điểm như tên column, kiểu dữ liệu và dữ liệu khuyết. Sau đó bạn sẽ học cách đọc dữ liệu từ nhiều file cùng lúc và ghi dữ liệu từ R ra file. Cuối cùng, bạn sẽ học cách tạo data frame thủ công trong R.

7.1.1 Điều kiện tiên quyết

Trong chương này, bạn sẽ học cách tải các file phẳng (flat file) vào R với package readr, là một phần của tidyverse cốt lõi.

7.2 Đọc dữ liệu từ file

Để bắt đầu, chúng ta sẽ tập trung vào loại file dữ liệu hình chữ nhật phổ biến nhất: CSV, viết tắt của comma-separated values (giá trị phân tách bằng dấu phẩy). Đây là một file CSV đơn giản trông như thế nào. Hàng đầu tiên, thường được gọi là row tiêu đề (header row), chứa tên các column, và sáu row tiếp theo cung cấp dữ liệu. Các column được phân tách, hay còn gọi là phân cách (delimited), bằng dấu phẩy.

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

Bảng 7.1 hiển thị biểu diễn của cùng dữ liệu đó dưới dạng bảng.

Bảng 7.1: Dữ liệu từ file students.csv dưới dạng bảng.
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

Chúng ta có thể đọc file này vào R bằng read_csv(). Đối số đầu tiên là quan trọng nhất: đường dẫn (path) đến file. Bạn có thể hình dung đường dẫn như địa chỉ của file: file có tên students.csv và nằm trong thư mục 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.

Đoạn mã trên sẽ hoạt động nếu bạn có file students.csv trong thư mục data của dự án. Bạn có thể tải file students.csv từ https://pos.it/r4ds-students-csv hoặc bạn có thể đọc trực tiếp từ URL đó với:

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

Khi bạn chạy read_csv(), nó in ra một thông báo cho bạn biết số row và column dữ liệu, ký tự phân cách được sử dụng, và thông số kỹ thuật của các column (tên các column được tổ chức theo kiểu dữ liệu mà column chứa). Nó cũng in ra một số thông tin về cách lấy thông số kỹ thuật đầy đủ của column và cách tắt thông báo này. Thông báo này là một phần không thể thiếu của readr, và chúng ta sẽ quay lại nó trong Phần 7.3.

7.2.1 Lời khuyên thực tế

Sau khi đọc dữ liệu vào, bước đầu tiên thường liên quan đến việc biến đổi nó theo cách nào đó để dễ dàng làm việc hơn trong phần còn lại của phân tích. Hãy xem lại dữ liệu students với suy nghĩ đó.

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

Trong column favourite.food, có một loạt các món ăn, và sau đó là string (string) N/A, đáng lẽ phải là một NA thực sự mà R sẽ nhận ra là “không có sẵn” (not available). Đây là điều chúng ta có thể xử lý bằng argument na. Theo mặc định, read_csv() chỉ nhận dạng string rỗng ("") trong tập dữ liệu này là NA, và chúng ta muốn nó cũng nhận dạng string "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

Bạn cũng có thể nhận thấy rằng các column Student IDFull Name được bao quanh bởi dấu backtick. Đó là vì chúng chứa dấu cách, vi phạm các quy tắc thông thường của R cho tên biến (variable); chúng là các tên không hợp cú pháp (non-syntactic). Để tham chiếu đến các biến này, bạn cần bao quanh chúng bằng dấu backtick, `:

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

Một cách tiếp cận khác là sử dụng janitor::clean_names() để dùng một số phương pháp heuristic chuyển tất cả thành dạng snake case cùng lúc1.

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

Một tác vụ phổ biến khác sau khi đọc dữ liệu vào là xem xét các kiểu biến. Ví dụ, meal_plan là một biến phân loại (categorical variable) với một tập hợp các giá trị có thể đã biết, mà trong R nên được biểu diễn dưới dạng factor (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

Lưu ý rằng các giá trị trong biến meal_plan vẫn giữ nguyên, nhưng kiểu biến được hiển thị bên dưới tên biến đã thay đổi từ ký tự (<chr>) sang factor (<fct>). Bạn sẽ tìm hiểu thêm về factor trong Chương 16.

Trước khi phân tích dữ liệu này, bạn có thể sẽ muốn sửa column age. Hiện tại, age là một biến ký tự vì một trong các quan sát được nhập là five thay vì số 5. Chúng ta sẽ thảo luận chi tiết về cách khắc phục vấn đề này trong Chương 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

Một function mới ở đây là if_else(), có ba argument. Đối số đầu tiên test phải là một vector logic (logical vector). Kết quả sẽ chứa giá trị của argument thứ hai, yes, khi testTRUE, và giá trị của argument thứ ba, no, khi nó là FALSE. Ở đây chúng ta đang nói rằng nếu age là string "five", hãy biến nó thành "5", và nếu không thì giữ nguyên age. Bạn sẽ tìm hiểu thêm về if_else() và các vector logic trong Chương 12.

7.2.2 Các argument khác

Có một vài argument quan trọng khác mà chúng ta cần đề cập, và chúng sẽ dễ minh họa hơn nếu trước tiên chúng tôi cho bạn thấy một mẹo tiện lợi: read_csv() có thể đọc các string văn bản mà bạn đã tạo và định dạng như một file CSV:

read_csv(
  "a,b,c
  1,2,3
  4,5,6"
)
#> Warning: The `file` argument of `read_csv()` should use `I()` for literal data as of
#> readr 2.2.0.
#>   
#>   # Bad (for example):
#>   read_csv("x,y\n1,2")
#>   
#>   # Good:
#>   read_csv(I("x,y\n1,2"))
#> # A tibble: 2 × 3
#>       a     b     c
#>   <dbl> <dbl> <dbl>
#> 1     1     2     3
#> 2     4     5     6

Thông thường, read_csv() sử dụng dòng đầu tiên của dữ liệu làm tên column, đây là một quy ước rất phổ biến. Nhưng không phải là hiếm khi có một vài dòng siêu dữ liệu (metadata) được đặt ở đầu file. Bạn có thể sử dụng skip = n để bỏ qua n dòng đầu tiên hoặc sử dụng comment = "#" để loại bỏ tất cả các dòng bắt đầu bằng (ví dụ) #:

read_csv(
  "The first line of metadata
  The second line of metadata
  x,y,z
  1,2,3",
  skip = 2
)
#> # A tibble: 1 × 3
#>       x     y     z
#>   <dbl> <dbl> <dbl>
#> 1     1     2     3

read_csv(
  "# A comment I want to skip
  x,y,z
  1,2,3",
  comment = "#"
)
#> # A tibble: 1 × 3
#>       x     y     z
#>   <dbl> <dbl> <dbl>
#> 1     1     2     3

Trong các trường hợp khác, dữ liệu có thể không có tên column. Bạn có thể sử dụng col_names = FALSE để yêu cầu read_csv() không coi row đầu tiên là tiêu đề và thay vào đó đặt tên tuần tự từ X1 đến 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

Ngoài ra, bạn có thể truyền cho col_names một vector ký tự sẽ được sử dụng làm tên column:

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

Những argument này là tất cả những gì bạn cần biết để đọc phần lớn các file CSV mà bạn sẽ gặp trong thực tế. (Đối với phần còn lại, bạn sẽ cần kiểm tra cẩn thận file .csv của mình và đọc tài liệu về nhiều argument khác của read_csv().)

7.2.3 Các loại file khác

Khi bạn đã thành thạo read_csv(), việc sử dụng các function khác của readr rất đơn giản; chỉ là vấn đề biết nên dùng function nào:

  • read_csv2() đọc các file phân tách bằng dấu chấm phẩy. Chúng sử dụng ; thay vì , để phân tách các trường và phổ biến ở các quốc gia sử dụng , làm dấu thập phân.

  • read_tsv() đọc các file phân tách bằng tab.

  • read_delim() đọc các file với bất kỳ ký tự phân cách nào, cố gắng tự động đoán ký tự phân cách nếu bạn không chỉ định.

  • read_fwf() đọc các file có độ rộng cố định (fixed-width). Bạn có thể chỉ định các trường theo độ rộng với fwf_widths() hoặc theo vị trí với fwf_positions().

  • read_table() đọc một biến thể phổ biến của file có độ rộng cố định trong đó các column được phân tách bằng khoảng trắng.

  • read_log() đọc các file log kiểu Apache.

7.2.4 Bài tập

  1. Bạn sẽ sử dụng function nào để đọc một file trong đó các trường được phân tách bằng “|”?

  2. Ngoài file, skip, và comment, những argument nào khác mà read_csv()read_tsv() có chung?

  3. Những argument quan trọng nhất của read_fwf() là gì?

  4. Đôi khi các string trong file CSV chứa dấu phẩy. Để ngăn chúng gây ra vấn đề, chúng cần được bao quanh bởi một ký tự trích dẫn (quoting character), như " hoặc '. Theo mặc định, read_csv() giả định rằng ký tự trích dẫn sẽ là ". Để đọc đoạn văn bản sau vào một data frame, bạn cần chỉ định argument nào cho read_csv()?

    "x,y\n1,'a,b'"
  5. Xác định điều gì sai với mỗi file CSV nội tuyến (inline) sau đây. Điều gì xảy ra khi bạn chạy đoạn mã?

    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. Thực hành tham chiếu đến các tên không hợp cú pháp trong data frame sau bằng cách:

    1. Trích xuất biến có tên 1.
    2. Vẽ biểu đồ phân tán (scatterplot) của 1 so với 2.
    3. Tạo một column mới có tên 3, bằng 2 chia cho 1.
    4. Đổi tên các column thành one, two, và three.
    annoying <- tibble(
      `1` = 1:10,
      `2` = `1` * 2 + rnorm(length(`1`))
    )

7.3 Kiểm soát kiểu column

Một file CSV không chứa bất kỳ thông tin nào về kiểu của mỗi biến (tức là liệu nó là logic, số, string, v.v.), vì vậy readr sẽ cố gắng đoán kiểu. Phần này mô tả cách quá trình đoán hoạt động, cách giải quyết một số vấn đề phổ biến khiến nó thất bại, và nếu cần, cách tự cung cấp kiểu column. Cuối cùng, chúng ta sẽ đề cập đến một vài chiến lược chung hữu ích khi readr thất bại nghiêm trọng và bạn cần hiểu rõ hơn về cấu trúc file của mình.

7.3.1 Đoán kiểu dữ liệu

readr sử dụng phương pháp heuristic để xác định kiểu column. Với mỗi column, nó lấy các giá trị của 1.000 hàng2 được phân bố đều từ row đầu tiên đến row cuối cùng, bỏ qua các missing value. Sau đó nó kiểm tra qua các câu hỏi sau:

  • Nó chỉ chứa F, T, FALSE, hoặc TRUE (không phân biệt chữ hoa chữ thường)? Nếu vậy, đó là kiểu logic (logical).
  • Nó chỉ chứa các số (ví dụ, 1, -4.5, 5e6, Inf)? Nếu vậy, đó là kiểu số (number).
  • Nó có khớp với chuẩn ISO8601 không? Nếu vậy, đó là ngày (date) hoặc ngày-giờ (date-time). (Chúng ta sẽ quay lại ngày-giờ chi tiết hơn trong Phần 17.2).
  • Nếu không, nó phải là string.

Bạn có thể thấy hành vi đó trong thực tế qua ví dụ đơn giản này:

read_csv("
  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

Phương pháp heuristic này hoạt động tốt nếu bạn có một tập dữ liệu sạch, nhưng trong thực tế, bạn sẽ gặp nhiều trường hợp thất bại kỳ lạ và đa dạng.

7.3.2 Giá trị khuyết, kiểu column, và các vấn đề

Cách phổ biến nhất mà việc phát hiện kiểu column thất bại là khi một column chứa các giá trị không mong đợi, và bạn nhận được một column ký tự thay vì một kiểu cụ thể hơn. Một trong những nguyên nhân phổ biến nhất cho điều này là missing value, được ghi bằng thứ gì đó khác với NA mà readr mong đợi.

Lấy file CSV đơn giản 1 column này làm ví dụ:

simple_csv <- "
  x
  10
  .
  20
  30"

Nếu chúng ta đọc nó mà không có bất kỳ argument bổ sung nào, x trở thành một column ký tự:

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

Trong trường hợp rất nhỏ này, bạn có thể dễ dàng nhìn thấy missing value .. Nhưng điều gì xảy ra nếu bạn có row nghìn row với chỉ một vài missing value được biểu diễn bằng . rải rác trong đó? Một cách tiếp cận là yêu cầu readr rằng x là một column số, và sau đó xem nó thất bại ở đâu. Bạn có thể làm điều đó với argument col_types, nhận một list có tên (named list) trong đó các tên khớp với tên column trong file CSV:

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)

Bây giờ read_csv() báo cáo rằng có một vấn đề, và cho chúng ta biết có thể tìm hiểu thêm với 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/ty/sykxy97538dgq4gwn7s7qx…

Điều này cho chúng ta biết rằng có một vấn đề ở row 3, column 1, nơi readr mong đợi một số thực (double) nhưng nhận được .. Điều đó gợi ý rằng tập dữ liệu này sử dụng . cho các missing value. Vì vậy chúng ta đặt na = ".", việc đoán tự động thành công, cho chúng ta column số mà chúng ta muốn:

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

7.3.3 Kiểu cột

readr cung cấp tổng cộng chín kiểu column để bạn sử dụng:

  • col_logical()col_double() đọc giá trị logic và số thực. Chúng hiếm khi cần thiết (ngoại trừ như trên), vì readr thường sẽ đoán chúng cho bạn.
  • col_integer() đọc số nguyên. Chúng ta hiếm khi phân biệt số nguyên và số thực trong cuốn sách này vì chúng tương đương về mặt chức năng, nhưng đọc số nguyên một cách tường minh đôi khi có thể hữu ích vì chúng chiếm một nửa bộ nhớ so với số thực.
  • col_character() đọc string. Điều này có thể hữu ích khi chỉ định tường minh khi bạn có một column là mã định danh số (numeric identifier), tức là string dài các chữ số dùng để xác định một đối tượng nhưng không có ý nghĩa khi áp dụng các phép toán. Ví dụ bao gồm số điện thoại, số an sinh xã hội, số thẻ tín dụng, v.v.
  • col_factor(), col_date(), và col_datetime() tạo factor, ngày, và ngày-giờ tương ứng; bạn sẽ tìm hiểu thêm về chúng khi chúng ta đến các kiểu dữ liệu đó trong Chương 16Chương 17.
  • col_number() là một bộ phân tích số linh hoạt (permissive numeric parser) sẽ bỏ qua các thành phần không phải số, và đặc biệt hữu ích cho tiền tệ. Bạn sẽ tìm hiểu thêm về nó trong Chương 13.
  • col_skip() bỏ qua một column để nó không được bao gồm trong kết quả, điều này có thể hữu ích để tăng tốc việc đọc dữ liệu nếu bạn có một file CSV lớn và bạn chỉ muốn sử dụng một số column.

Cũng có thể ghi đè phương pháp heuristic mặc định để đoán kiểu bằng cách chuyển từ list() sang cols() và chỉ định .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

Một function trợ giúp hữu ích khác là cols_only(), chỉ đọc vào các column mà bạn chỉ định:

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

7.4 Đọc dữ liệu từ nhiều file

Đôi khi dữ liệu của bạn được chia thành nhiều file thay vì nằm trong một file duy nhất. Ví dụ, bạn có thể có dữ liệu bán row cho nhiều tháng, với dữ liệu của mỗi tháng trong một file riêng: 01-sales.csv cho tháng Một, 02-sales.csv cho tháng Hai, và 03-sales.csv cho tháng Ba. Với read_csv() bạn có thể đọc các dữ liệu này cùng lúc và xếp chồng chúng lên nhau trong một data frame duy nhất.

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

Một lần nữa, đoạn mã trên sẽ hoạt động nếu bạn có các file CSV trong thư mục data của dự án. Bạn có thể tải các file này từ https://pos.it/r4ds-01-sales, https://pos.it/r4ds-02-sales, và https://pos.it/r4ds-03-sales hoặc bạn có thể đọc chúng trực tiếp với:

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")

Đối số id thêm một column mới có tên file vào data frame kết quả, xác định file mà dữ liệu đến từ đó. Điều này đặc biệt hữu ích trong các trường hợp mà các file bạn đang đọc vào không có column nhận dạng có thể giúp bạn truy nguyên các quan sát về nguồn gốc ban đầu của chúng.

Nếu bạn có nhiều file muốn đọc vào, việc viết tên của chúng dưới dạng list có thể trở nên cồng kềnh. Thay vào đó, bạn có thể sử dụng function cơ sở list.files() để tìm các file cho bạn bằng cách khớp một mẫu (pattern) trong tên file. Bạn sẽ tìm hiểu thêm về các mẫu này trong Chương 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 Ghi ra file

readr cũng đi kèm với hai function hữu ích để ghi dữ liệu trở lại ổ đĩa: write_csv()write_tsv(). Các argument quan trọng nhất của những function này là x (data frame cần lưu) và file (vị trí lưu). Bạn cũng có thể chỉ định cách ghi các missing value với na, và liệu bạn có muốn append (nối thêm) vào một file hiện có hay không.

write_csv(students, "students.csv")

Bây giờ hãy đọc lại file csv đó. Lưu ý rằng thông tin kiểu biến mà bạn vừa thiết lập sẽ bị mất khi bạn lưu thành CSV vì bạn đang bắt đầu lại với việc đọc từ một file văn bản thuần:

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

Điều này khiến CSV hơi không đáng tin cậy cho việc lưu trữ tạm (caching) các kết quả trung gian—bạn cần tái tạo thông số kỹ thuật column mỗi khi tải vào. Có hai lựa chọn thay thế chính:

  1. write_rds()read_rds() là các function bọc (wrapper) thống nhất xung quanh các function cơ sở readRDS()saveRDS(). Chúng lưu trữ dữ liệu trong định dạng nhị phân tùy chỉnh của R gọi là RDS. Điều này có nghĩa là khi bạn tải lại đối tượng, bạn đang tải chính xác cùng một đối tượng R mà bạn đã lưu.

    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. Gói arrow cho phép bạn đọc và ghi các file parquet, một định dạng file nhị phân nhanh có thể được chia sẻ giữa các ngôn ngữ lập trình. Chúng ta sẽ quay lại arrow chi tiết hơn trong Chương 22.

    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 có xu hướng nhanh hơn nhiều so với RDS và có thể sử dụng được ngoài R, nhưng yêu cầu package arrow.

7.6 Nhập dữ liệu thủ công

Đôi khi bạn sẽ cần tạo một tibble “bằng tay” bằng cách nhập một ít dữ liệu trong script R của mình. Có hai function hữu ích để giúp bạn làm điều này, khác nhau ở chỗ bạn bố trí tibble theo column hay theo row. tibble() hoạt động theo column:

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

Bố trí dữ liệu theo column có thể khiến việc thấy mối quan hệ giữa các row trở nên khó khăn, vì vậy một lựa chọn thay thế là tribble(), viết tắt của transposed tibble (tibble chuyển vị), cho phép bạn bố trí dữ liệu theo từng row. tribble() được tùy chỉnh cho việc nhập dữ liệu trong mã: tiêu đề column bắt đầu bằng ~ và các mục được phân tách bằng dấu phẩy. Điều này giúp có thể bố trí lượng dữ liệu nhỏ ở dạng dễ đọc:

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 Tóm tắt

Trong chương này, bạn đã học cách tải các file CSV với read_csv() và tự nhập dữ liệu với tibble()tribble(). Bạn đã tìm hiểu cách các file csv hoạt động, một số vấn đề bạn có thể gặp phải, và cách khắc phục chúng. Chúng ta sẽ quay lại theme nhập dữ liệu vài lần trong cuốn sách này: Chương 20 từ Excel và Google Sheets, Chương 21 sẽ chỉ cho bạn cách tải dữ liệu từ database, Chương 22 từ các file parquet, Chương 23 từ JSON, và Chương 24 từ các trang web.

Chúng ta gần như đã đến cuối phần này của cuốn sách, nhưng có một theme quan trọng cuối cùng cần đề cập: cách tìm kiếm sự giúp đỡ. Vì vậy trong chương tiếp theo, bạn sẽ tìm hiểu một số nơi tốt để tìm kiếm sự giúp đỡ, cách tạo một reprex để tối đa hóa cơ hội nhận được sự giúp đỡ tốt, và một số lời khuyên chung về việc theo kịp thế giới R.


  1. Gói janitor không phải là một phần của tidyverse, nhưng nó cung cấp các function tiện lợi cho việc dọn dẹp dữ liệu và hoạt động tốt trong các pipeline dữ liệu sử dụng |>.↩︎

  2. Bạn có thể ghi đè giá trị mặc định 1000 bằng argument guess_max.↩︎