10 以 readr 匯入資料
本章為 Wickham and Grolemund (2016) 第 7 章內容。
需要的套件
library(tidyverse)
library(readr)10.1 簡介
本章要使用 tidyverse 中的 readr 來匯入資料。使用 read_csv() 可以匯入 .csv 檔,或是直接匯入 inline 的 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 會把第一行的資料當成 column 的名稱,所以我們可以看到上面的 tibble 中,column 的名稱分別是 a、b 與 c。如果我們不想要這樣的話,可以透過幾種方式:
- 如果情況是檔案的前幾行不希望匯入的話,可以透過引數
skip = n,而或者指定comment = "#"直接註解掉,如:
read_csv("不想讀入第一行
第二行也不想
x,y,z
1,2,3", skip = 2)
read_csv("#不想讀入第一行
#第二行也不想
x,y,z
1,2,3", comment = "#")
# 以上兩種做法都會得到
## # A tibble: 1 × 3
## x y z
## <dbl> <dbl> <dbl>
## 1 1 2 3- 如果是因為資料沒有 column name 的話,可以使用
col_names = FALSE,這樣read_csv()就不會把第一行當作是 column names,而是使用X1、X2、⋯⋯;所以反過來說,也可以使用col_names來指定 column names,如:
read_csv("1,2,3\n4,5,6", col_names = FALSE)
## # A tibble: 2 × 3
## X1 X2 X3
## <dbl> <dbl> <dbl>
## 1 1 2 3
## 2 4 5 6
read_csv("1,2,3\n4,5,6", col_names = c("A", "B", "C"))
## # A tibble: 2 × 3
## A B C
## <dbl> <dbl> <dbl>
## 1 1 2 3
## 2 4 5 6此外,如果資料中的變數值有 NA,但以其他方式表示怎麼辦?我們可以使用引數 na 來指定資料中所用以表示缺漏值的符號,讀入時就將其轉換成 NA,如以下的資料將 NA 用 . 來表示:
read_csv("a,b,c\n1,2,.", na = ".")
## # A tibble: 1 x 3
## a b c
## <dbl> <dbl> <lgl>
## 1 1 2 NA10.1.1 與 base R 比較
為何不用 base R 的 read.csv()?
readr中的read_csv()速度快上 10 倍。單純追求速度的話可以使用data.table套件(可見節 4.1),速度更快,但跟tidyverse就有一點點不搭。直接讀入成為 tibbles、不會把 character vectors 轉成 factors、不會使用 row names、不會破壞 column names。
更可複製(reproducible)。
10.2 Parsing a Vector
parse_*() 函數可以幫助我們把字母向量轉成特定型態的向量,如 logical、integer 或 date,其中,第一個引數為要 parse 的字母向量:
str(parse_logical(c("TRUE", "FALSE", "NA")))
# logi [1:3] TRUE FALSE NA
str(parse_integer(c("3", "5", "6")))
# int [1:3] 3 5 6
str(parse_date(c("2020-11-03", "2000-04-23")))
# Date[1:2], format: "2020-11-03" "2000-04-23"
# 注意:date 預設格式為 yyyy-mm-dd
# 也可以使用 na =
parse_integer(c("1", "231", ".", "456"), na = ".")
# [1] 1 231 NA 456
# 當然,如果輸入錯的格式就會得到 warning如果有 parsing failures,我們可以透過 problem() 來檢視出問題的地方。
常用的 parse_*() 如下:
parse_logical():用來 parse logical。parse_integer()、parse_double()與parse_number():看似類似,實則大相徑庭。parse_integer()已經示範過了,即用來 parse 整數;parse_double()用來 parse 實數;parse_number()則會丟掉數字前後的非數字字元,如 $1000 就會變成 1000,euro1,000 就會變成 1000,詳細的使用方法與範例可以參見節 10.2.1 或 Parse numbers, flexibly。parse_chracter():看似非必要,卻還是有其用處,見節 10.2.2。parse_factor():用來 parse factors,見節 10.2.3。parse_date()、parse_datetime()與parse_time():用來 parse 日期時間,見節 10.2.4。
10.2.1 Numbers
之所以 parse 數字會成為問題,是因為數字有非常多種不同的寫法。readr 為了解決這個問題,有引數 locale,可以用來指定 parsing options。
10.2.1.1 小數點
一般而言,我們熟知的小數點是 .,但也是有人會寫成 ,。parse_double() 中,預設就是 .,但如果是 , 或其他符號怎麼辦?要處理這個問題,我們可以在 locale 的設定中,使用引數 decimal_mark,如:
parse_double("1.23")
# [1] 1.23
parse_double("1,23", locale = locale(decimal_mark = ","))
# # [1] 1.2310.2.1.2 數字前後有非數字的字母
有時候,數字前後就是有非數字的字母,例如 $1000 或 20% 或 1,300,那怎麼辦?我們可以改用 parse_number()。此外,美國通常以 , 作為 grouping mark,像是 20000 會寫成 20,000;但歐洲各國並不一定,因此我們也可以在 locale 中設置引數 grouping_mark,如:
parse_number("20,044,620")
# [1] 20044620
parse_number("20%")
# [1] 20
parse_number("#20")
# [1] 20
parse_number("It costs $817.")
# [1] 817
parse_number("552.255.123", locale = locale(grouping_mark = "."))
# [1] 552255123
parse_number("552'255'123", locale = locale(grouping_mark = "'"))
# [1] 552255123
parse_number("33.457,32", locale = locale(decimal_mark = ",", grouping_mark = "."))
# [1] 33457.3210.2.2 Strings
貌似無用的函數,其實不然。問題在,同樣的字串也有許多種不同的表示方式。例如我們使用 chatToRaw() 函數,如:
charToRaw("Politics")
# [1] 50 6f 6c 69 74 69 63 73這種編碼(encoding)方式即 ASCII,每 byte 的資訊,都會被表成十六進位的數字,如 50 即 P、6f 即 o 等。ASCII 用來表示英文字母是不錯,但問題在對其他的語言顯然力不從心。
今日,多數的編碼方式都是 UTF-8,可以表示各種語言,甚至表情符號。因此,readr 也是使用 UTF-8,即假設資料是以 UTF-8 的方式來編碼。可是又會反過來遇到一個問題,即如果我們的資料是由古老的電腦系統所生成,並非以 UTF-8 來編碼的話,那怎麼辦?
這時候 parse_character() 就派上用場了。我們可以在引數 locale 中指定編碼的格式,如:
parse_character("El Ni\xf1o was particularly bad this year", locale = locale(encoding = "Latin1"))
# [1] "El Niño was particularly bad this year"下一個問題又來了:如果我們不知道它的編碼格式,那怎麼辦?我們可以使用 guess_encoding() 函數。當然文字數量多的時候它會猜得比較準,但現在這裡就是一個範例。
guess_encoding(charToRaw("El Ni\xf1o was particularly bad this year"))## # A tibble: 2 × 2
## encoding confidence
## <chr> <dbl>
## 1 ISO-8859-1 0.46
## 2 ISO-8859-9 0.23
10.2.3 Factors
以 parse_factor() 搭配 levels 可以解析 factors,如:
fruit <- c("apple", "banana")
parse_factor(c("apple", "banana"), levels = fruit)## [1] apple banana
## Levels: apple banana
如果輸入非預期的值會得到 warning(factors 必須是已知變數值的類別變數),如:
fruit <- c("apple", "banana")
parse_factor(c("apple", "banana", "bannn"), levels = fruit)## Warning: 1 parsing failure.
## row col expected actual
## 3 -- value in level set bannn
## [1] apple banana <NA>
## attr(,"problems")
## # A tibble: 1 × 4
## row col expected actual
## <int> <int> <chr> <chr>
## 1 3 NA value in level set bannn
## Levels: apple banana
10.2.4 Dates, Date-Times, and Times
關於日期與時間,有三種 parser:
parse_datetime():要輸入 ISO8601 的 date-time,即依序輸入年、月、日、時、分、秒(可參照維基百科說明),如:parse_datetime("20201223") # [1] "2020-12-23 UTC" # 如果沒有輸入時間的話就會被設置為午夜 parse_datetime("2020-03-14") # [1] "2020-03-14 UTC" parse_datetime("2021-09-01T14:17:28") # [1] "2021-09-01 14:17:28 UTC"parse_date():輸入年、月、日,中間以-或/隔開,如:parse_date("2021-04-23") # [1] "2021-04-23" parse_date("2021/08/15") # [1] "2021-08-15"parse_time():輸入時、分、秒、am或pm(後兩者為選擇性的),前三者之間以冒號隔開,時間與上下午之間以空格隔開,如:library(hms) parse_time("01:23") # 01:23:00 parse_time("02:24:25") # 02:24:25 parse_time("03:23:56 pm") # 15:23:56
自訂年月日時間的格式方式如下。
Year
%Y(4 digits).%y(2 digits; 00-69 → 2000-2069, 70-99 → 1970-1999).
Month
%m(2 digits).%b(abbreviated name, like “Jan”).%B(full name, “January”).
Day
%d(2 digits).%e(optional leading space).
Time
%H(0–23 hour format).%I(0–12, must be used with%p).%p(a.m./p.m. indicator).%M(minutes).%S(integer seconds).%OS(real seconds).%Z(time zone [a name, e.g.,America/Chicago]).%z(as offset from UTC, e.g.,+0800).
Nondigits
%.(skips one nondigit character).%*(skips any number of nondigits).
parse_date("01/02/15", "%m/%d/%y")
# [1] "2015-01-02"
parse_date("01/02/15", "%d/%m/%y")
# [1] "2015-02-01"
parse_date("01/02/15", "%y/%m/%d")
# [1] "2001-02-15"10.3 Parsing a File
所以 readr 到底怎麼解析檔案?它如何猜測各個 column 的型態?如果 readr 所猜測的並非我們所要的,那該怎麼辦?
10.3.1 策略
readr 的方式是先讀入前 1000 列的資料,並運用捷思來猜測各行的型態。我們可以使用 guess_parser() 來模擬 readr 的猜測過程,其將會回傳 readr 的猜測;而使用 parse_guess() 可以解析向量,如:
guess_parser("2021-09-22")
# [1] "date"
guess_parser("04:23")
# [1] "time"
guess_parser("2021-04-12T04:12")
# [1] "datetime"
guess_parser(c(TRUE, NA))
# [1] "logical"
guess_parser(c("TRUE", NA))
# [1] "logical"
guess_parser(c(2, 4.5, 6))
# [1] "double"
str(parse_guess("2021-04-12"))
# Date[1:1], format: "2021-04-12"詳細而言,捷思法就如:
- logical:只包含 “F”、“T”、“FALSE” 或 “TRUE”。
- integer:只包含數字與
-。 - double:只包含合法的 doubles。
- number:包含有 grouping mark 的合法 doubles。
- time:包含預設的時間格式。
- date:包含預設的日期格式。
- date-time:包含 ISO8601 格式者。
如果都不滿足,那就還是會是字串向量。
10.3.2 問題
問題在於如果檔案很大,那這招是行不通的:
前 1000 列可能只是特例。
如果有太多缺漏值,
readr會把它猜成字元向量,但也許應該是別的型態。
以下有一個出問題的範例:
challenge <- read_csv(readr_example("challenge.csv"))## Rows: 2000 Columns: 2
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## dbl (1): x
## date (1): y
##
## ℹ 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.
problems(challenge)## # A tibble: 0 × 5
## # … with 5 variables: row <int>, col <int>, expected <chr>, actual <chr>,
## # file <chr>
此時,我們應該是逐行檢視應該如何解析,其中 col_type 即告訴 R 要載入什麼類型的資料,如:
challenge <- read_csv(
readr_example("challenge.csv"),
col_types = cols(
x = col_integer(),
y = col_character()
)
)看起來解決了前幾列的問題,但我們用 tail() 一看就知道 x 最後面幾列都是 doubles,且 y 最後面幾列都是日期,所以應該改成:
challenge <- read_csv(
readr_example("challenge.csv"),
col_types = cols(
x = col_double(),
y = col_date()
)
)10.3.3 其他策略
我們也可以改變
readr要一開始要檢查的列數,如:challenge2 <- read_csv( readr_example("challenge.csv"), guess_max = 1001 )## Rows: 2000 Columns: 2## ── Column specification ──────────────────────────────────────────────────────── ## Delimiter: "," ## dbl (1): x ## date (1): y## ## ℹ 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.全部都先讀取成字元向量,有時候也很便於診斷出問題出在哪,如:
challenge2 <- read_csv(readr_example("challenge.csv"), col_types = cols(.default = col_character()) )然後我們也可以配合使用
type_convert(),如:type_convert(challenge2)## ## ── Column specification ──────────────────────────────────────────────────────── ## cols( ## x = col_double(), ## y = col_date(format = "") ## )## # A tibble: 2,000 × 2 ## x y ## <dbl> <date> ## 1 404 NA ## 2 4172 NA ## 3 3004 NA ## 4 787 NA ## 5 37 NA ## 6 2332 NA ## 7 2489 NA ## 8 1449 NA ## 9 3665 NA ## 10 3863 NA ## # … with 1,990 more rows如果有很嚴重的解析問題,可以先以
read_lines讀字元向量進來,甚至以read_file()讀長度為 1 的字元向量進來,再使用其他解析字串的技巧解決這個問題。
10.4 寫入檔案
readr 提供了兩個函數可以幫助我們把資料寫入磁碟:write_csv() 與 write_tsv()。兩個函數所輸出的檔案都會以 UTF-8 編碼,且日期時間會以 ISO8601 格式,如:
write_csv(challenge, "challenge.csv")想要輸出成 Excel 格式,則可以使用 write_excel_csv()。
不過以上這種做法就有一個風險:要重新讀入檔案時還要重新煩惱解析的問題。
10.5 其他類型的資料
表格類型的資料可以使用:
haven:用於讀取 SPSS、Stata 與 SAS 檔。readxl:用以讀取 Excel 檔(.xls與.xlsx檔)。DBI:與RMySQL、RSQLite或RPostgreSQL配合使用,可以跑 SQL queries,然後從資料庫回傳一個 data frame。
階層資料(hierarchical data)可以使用:
jsonlite:用以讀取 JSON 檔。xml2:用以讀取 XML 檔。
其他類型的檔案,可以參考 R Data Import/Export 或 rio。
其他關於編碼的問題,詳情可見 What Every Programmer Absolutely, Positively Needs To Know About Encodings And Character Sets To Work With Text。