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 NA
10.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.23
10.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.32
10.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,如:
<- c("apple", "banana")
fruit parse_factor(c("apple", "banana"), levels = fruit)
## [1] apple banana
## Levels: apple banana
如果輸入非預期的值會得到 warning(factors 必須是已知變數值的類別變數),如:
<- c("apple", "banana")
fruit 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
會把它猜成字元向量,但也許應該是別的型態。
以下有一個出問題的範例:
<- read_csv(readr_example("challenge.csv")) challenge
## 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 要載入什麼類型的資料,如:
<- read_csv(
challenge readr_example("challenge.csv"),
col_types = cols(
x = col_integer(),
y = col_character()
) )
看起來解決了前幾列的問題,但我們用 tail()
一看就知道 x
最後面幾列都是 doubles,且 y
最後面幾列都是日期,所以應該改成:
<- read_csv(
challenge readr_example("challenge.csv"),
col_types = cols(
x = col_double(),
y = col_date()
) )
10.3.3 其他策略
我們也可以改變
readr
要一開始要檢查的列數,如:<- read_csv( challenge2 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.
全部都先讀取成字元向量,有時候也很便於診斷出問題出在哪,如:
<- read_csv(readr_example("challenge.csv"), challenge2 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。