《R数据科学》--千变万化的数据只需一个readr便可
[toc]
写在前面
数据导入是使用R语言分析数据的第一步,但是这部分的细节确实非常多,尤其是对于咱们微生物组数据来说,经常会遇到制表符和逗号作为分隔符的文件,其次,物种注释文件的七级注释往往使用分号和“|”作为分隔符,如何在不做修改的情况下进行文件导入,值得我们学习。其次我们经常通过biom转化的文件开头带有“#”,如何忽略或者修正这个符号也十分重要。下面我将带领大家学习本小结内容,如何调整并导入文件。
文件的导入,R会定义一个字符类型,这些类型主要包括:字符,数值,因子等我们常用的类型,如何解析是一个问题,尤其是在R语言解析错误的时候,例如常见的因子解析成了字符串,还有一些奇怪的问题,例如:美元符号,数字的科学计数法,数字之间的逗号分隔,日期等这些数据如何解析,日常遇到的比较少,但是一旦遇到这个问题几乎是致命的,那就来学习吧。
如何将纯文本格式的矩形文件读入 R?
readr 也是 tidyverse 的核心R包之一。通过 library(tidyverse)导入
数据导入
1. 读取文件-从文件读入为数据框
(1) 相关函数
read_csv() 读取逗号分隔文件
read_csv2() 读取分号分隔文件
read_tsv() 读取制表符分隔文件
read_delim() 读取使用任意分隔符的文件。
默认是制表符
read_fwf() 读取固定宽度的文件
可以用 fwf_widths()函数按照宽度来设定域
可以用 fwf_positions()函数按照位置来设定域。
- read_table() 读取固定宽度的文件,其中使用空白字符来分隔各列。
read_log() 读取 Apache 风格的日志文件。
(2/1)用法1:读取现有文件,以read_csv()函数为例。第一个参数文件路径。函数运行时会打印一份数据列说明,给出每个列的名称和类型。
(2/2)用法2:创建行内 CSV 文件
read_csv("a,b,c
1,2,3
4,5,6")
# A tibble: 2 x 3
a b c
<dbl> <dbl> <dbl>
1 1 2 3
2 4 5 6
(3)列名称
默认:
用数据的第一行作为列名称,如上面用法2中的例子;
用 skip = n 来跳过前 n行
read_csv("The first line of metadata
The second line of metadata
x,y,z
1,2,3", skip = 2)
# A tibble: 1 x 3
x y z
<dbl> <dbl> <dbl>
1 1 2 3
用comment = “#” 来丢弃所有以 # 开头的行:
read_csv("# A comment I want to skip
x,y,z
1,2,3", comment = "#")
# A tibble: 1 x 3
x y z
<dbl> <dbl> <dbl>
1 1 2 3
数据没有列名称1:
用 col_names = FALSE 来通知 read_csv() 不要将第一行作为列 标题,而是将各列依次标注为 X1 至 Xn:
read_csv("1,2,3\n4,5,6", col_names = FALSE) #"\n"是换行符
# A tibble: 2 x 3
X1 X2 X3
<dbl> <dbl> <dbl>
1 1 2 3
2 4 5 6
数据没有列名称2:
向 col_names 传递一个字符向量,以用作列名称:
read_csv("1,2,3\n4,5,6", col_names = c("x", "y", "z"))
# A tibble: 2 x 3
x y z
<dbl> <dbl> <dbl>
1 1 2 3
2 4 5 6
数据没有列名称3:
用参数na来设定使用哪个值(或哪些值)来表示文件中的缺失值:
read_csv("a,b,c\n1,2,.", na = ".")
# A tibble: 1 x 3
a b c
<dbl> <dbl> <lgl>
1 1 2 NA
2. 解析向量-解析单个向量
(1)重要函数-parse_*()函数族:接受一个字符向量,返回一个特定向量(如逻辑、整数或日期向量)
① 解析逻辑值:parse_logical()
> parse_logical(c("TRUE", "FALSE", "NA"))
[1] TRUE FALSE NA
② 解析整数:parse_integer()
> parse_integer(c("1", "2", "3"))
[1] 1 2 3
③ 解析数值
parse_double():
解析严格的数值
# 问题:世界各地书写数值的方式不同,最重要的是用来表示小数点的字符不同(有的用“.”,有的用“,”)
# 解决方法:readr使用“地区”这一概念可以按照不同地区设置解析选项,如创建一个新的地区对象并设定decimal_mark参数为“,”来覆盖默认值(“.”)
parse_double("1.23")
#> [1] 1.23
parse_double("1,23", locale = locale(decimal_mark = ","))
#> [1] 1.23
parse_number():
解析灵活的数值
# 问题:数值周围通常有其他字符(如货币$或百分比%)
# 解决方法:用parse_number()函数可以忽略数值前后的非数值嵌在文本中的数值,还可以忽略“分组符号”。> parse_number("$100")
[1] 100
> parse_number("20%")
[1] 20
> parse_number("It cost $123.45")
[1] 123.45
parse_number()和地区设置组合使用:
# 问题:世界各地用来分组的字符也不同,如美国用“,"、欧洲用“."、瑞士用“'"。# 解决方法:组合使用parse_number()和地区设置
> # 适用于美国
> parse_number("$123,456,789")
[1] 123456789
> # 适用于多数欧洲国家
> parse_number(
+ "123.456.789",
+ locale = locale(grouping_mark = ".")
+ )
[1] 123456789
> # 适用于瑞士
> parse_number(
+ "123'456'789",
+ locale = locale(grouping_mark = "'")
+ )
[1] 123456789
④ 解析字符串:parse_character()
# 问题:字符串打印出来是乱码
# 解决办法:在parse_number()函数中设定编码方式。> x1 <- "El Ni\xf1o was particularly bad this year"
> x2 <- "\x82\xb1\x82\xf1\x82\xc9\x82\xbf\x82\xcd"
> parse_character(x1, locale = locale(encoding = "Latin1"))
[1] "El Niño was particularly bad this year"
> parse_character(x2, locale = locale(encoding = "Shift-JIS"))
[1] "こんにちは"# 问题:怎么知道字符串的正确编码方式?# 解决办法:用 charToRaw() 函数获得一个字符串的底层表示;然后用guess_encoding()函数找出编码方式
# guess_encoding() 的第一个参数可以是一个文件路径,也可以是一个原始向量
> guess_encoding(charToRaw(x1))
# A tibble: 2 x 2
encoding confidence
<chr> <dbl>
1 ISO-8859-1 0.46 #Latin1是ISO-8859-1的别名,有些环境下写作Latin-1。2 ISO-8859-9 0.23
> guess_encoding(charToRaw(x2))
# A tibble: 1 x 2
encoding confidence
<chr> <dbl>
1 KOI8-R 0.42
⑤ 解析因子:parse_factor()
# R 使用因子表示取值范围是已知集合的分类变量
> fruit <- c("apple", "banana")
> parse_factor(c("apple", "banana", "bananana"))
[1] apple banana bananana
Levels: apple banana bananana
⑥ 解析时间
parse_datetime():
解析日期时间,期待的是符合 ISO 8601标准的日期时间。
parse_date():
解析日期
parse_time():
解析时间
ISO 8601:是一种国际标准, 其中日期的各个部分按从大到小的顺序排列,即年(4位数)、月(2位数)、日(2位数)、小时、分钟、秒:日期时间组合表示时在时间前加T
> parse_datetime("20101010")
[1] "2010-10-10 UTC"
> parse_date("2010-10-01")
[1] "2010-10-01"
> parse_time("20:10:01")
20:10:01如果默认设置不适合,可以提供自己的日期时间格式,格式由以下部分组成:- 年:%Y(4 位数);%y(2 位数)
- 月:%m(2 位数);%b(简写名称,如 Jan);%B(完整名称,如 January)
- 日:%d(1 位或 2 位数);%e(2 位数)
- 时间:%H(0-23 小时);%I(0-12 小时,必须和 %p 一起使用);%p(表示 a.m./p.m.);%M(分钟);%S(整数秒);%OS(实数秒);%Z(时区);%z(与国际标准时间的时差,如+0800)。- 非数值字符:%.(跳过一个非数值字符);%*(跳过所有非数值字符)。> parse_date("01/02/15", "%m/%d/%y")
[1] "2015-01-02"
# 如果对非英语月份名称使用 %b 或 %B,那么你就需要在 locale() 函数中设置 lang 参数。> parse_date("1 janvier 2015", "%d %B %Y", locale = locale("fr"))
[1] "2015-01-01"
3. 解析文件-readr如何解析文件(综合使用解析函数来解析文件)
(1)readr 如何自动猜出文件每列的数据类型?
根据前面的行猜测:先读取文件的前 1000 行,然后使用(相对保 守的)某种启发式算法确定每列的类型。
方法:# 先用 guess_parser() 函数返回 readr 最可信的猜测
> guess_parser("2010-10-01")
[1] "date"
# 接着 parse_guess() 函数使用这个猜测来解析列
> 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 标准的任何日期。- 如果以上类型均不匹配,那么这一列就还是一个字符串向量
(2)默认设置无效时如何修改默认设置?
问题1:前1000行不足以代表整个文件,如一列双精度数值的前 1000 行可能都是整数,按前1000行解析的话是整数,但实际是双精度数值。
challenge <- read_csv(readr_example("challenge.csv"))
# 用默认设置解析出来的列类型为x-integer,y-character
#> Parsed with column specification:
#> cols(
#> x = col_integer(),
#> y = col_character()
#> )
# 收到一条警告(warning),说明默认设置无效
#> Warning: 1000 parsing failures.
#> row col expected actual
#> 1001 x no trailing characters .23837975086644292
#> 1002 x no trailing characters .41167997173033655
#> 1003 x no trailing characters .7460716762579978
#> 1004 x no trailing characters .723450553836301
#> 1005 x no trailing characters .614524137461558
#> .... ... ...................... ..................
#> See problems(...) for more details.
用 problems()函数明确地列出失败记录,一列列地处理,直至解决所有问题
# 从上述警告信息中看出, x列存在大量解析问题——整数后面有拖尾字符。这说明我们应该使用双精度解析函数
# 首先,复制列类型(x-integer,y-character)并将其粘贴到初始调用中
challenge <- read_csv(
readr_example("challenge.csv"),
col_types = cols(
x = col_integer(),
y = col_character()
)
)
# 接着将x列类型由integer修改为double
challenge <- read_csv(
readr_example("challenge.csv"),
col_types = cols(
x = col_double(),
y = col_character()
)
)
> challenge
# A tibble: 2,000 x 2
x y
<int> <chr>
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
问题2:列中可能含有大量缺失值(如上面的例子)。如果前 1000 行中都是 NA,那么 readr 会猜测这是一个字符 向量,但其实想将这一列解析为更具体的值(查看后几行发现是日期数据)。
# 如果查看最后几行的话,会发现保存在字符向量中的其实是日期数据:tail(challenge)
#> # A tibble: 6 × 2
#> x y
#> <dbl> <chr>
#> 1 0.805 2019-11-21
#> 2 0.164 2018-03-29
#> 3 0.472 2014-08-04
#> 4 0.718 2015-08-16
#> 5 0.270 2020-02-04
#> 6 0.608 2019-01-06
# 解决:修改y类型为date
challenge <- read_csv(
readr_example("challenge.csv"),
col_types = cols(
x = col_double(),
y = col_date()
)
)
tail(challenge)
#> # A tibble: 6 × 2
#> x y
#> <dbl> <date>
#> 1 0.805 2019-11-21
#> 2 0.164 2018-03-29
#> 3 0.472 2014-08-04
#> 4 0.718 2015-08-16
#> 5 0.270 2020-02-04
#> 6 0.608 2019-01-06
(3)其他解析策略
# 比默认方式多检查几行
challenge2 <- read_csv(
readr_example("challenge.csv"),
guess_max = 1001
)
# 将所有列都作为字符向量读入,然后结合 type_convert() 函数使用(可以在数据框的字符列上应用启发式解析过程)
df <- tribble(
~x, ~y,
"1", "1.21",
"2", "2.32",
"3", "4.56"
)
df
#> # A tibble: 3 × 2
#> x y
#> <chr> <chr>
#> 1 1 1.21
#> 2 2 2.32
#> 3 3 4.56
type_convert(df)
#> Parsed with column specification:
#> cols(
#> x = col_integer(),
#> y = col_double()
#> )
#> # A tibble: 3 × 2
#> x y
#> <int> <dbl>
#> 1 1 1.21
#> 2 2 2.32
#> 3 3 4.56
# 读取一个非常大的文件时,将 n_max 设置为一个较小的数
# 如果遇到严重的解析问题,有时使用 read_lines() 函数按行读入字符向量会更容易,甚至可以使用 read_file() 函数读入一个长度为 1 的字符向量
4. 写入文件-将数据写回到磁盘(导出文件)
(1)重要函数
write_csv() 函数:
write_tsv() 函数:
write_excel_csv() 函数:
将 CSV 文件导为 Excel 文件
(2)重要参数
x:
要保存的数据框 path:
保存文件的位置
na:
设定如何写入缺失值
append:
追加到现有的文件
(3)问题:当保存为 CSV 文件时,类型信息会丢失
# 替代方式1:用write_rds() 和 read_rds() 函数将数据保存为 R 自定义的二进制格式,称为 RDS 格式
> write_rds(challenge, "challenge.rds")
> read_rds("challenge.rds")
# A tibble: 2,000 x 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
# 替代方式2:用feather包中的write_feather() 和 read_feather() 函数实现一种快速二进制格式,可以在多个编程语言间共享
> library(feather)
> write_feather(challenge, "challenge.feather")
> read_feather("challenge.feather")
5. 其他类型的数据
(1)对矩形数据:
haven:
可以读取 SPSS、 Stata 和 SAS 文件;
readxl:
可以读取 Excel 文件(.xls 和 .xlsx 均可)
配合专用的数据库后端程序, DBI 可以对相应数据库进行 SQL 查询,并返回一个数据框。
(2)对层次数据:
用 jsonlite(由 JeroenOoms 开发)读取 JSON 串
用 xml2 读取XML 文件
(3)对其他类型数据:
学习R 数据导入 / 导出手册(https://cran.r-project.org/doc/manuals/r-release/R-data.html),以及 rio 包(https://github.com/leeper/rio)