7 数据导入
7.1 引言
本章介绍如何读取纯文本矩形数据文件,如何将数据写入文件,以及如何创建数据框。
主要学习readr包,同样是tidyverse的组成部分。
library(tidyverse)7.2 从文件中读取数据
首先重点介绍最常见的矩形数据文件类型 CSV(Comma-Separated Values)。
下面是一个简单的 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,
5,Chidiegwu Dunkel,Pizza,Breakfast and lunch,five
6,Güvenç Attila,Ice cream,Lunch only,6
使用read_csv()将文件读取到R中。其第一个参数最重要——文件路径(也可以使用URL)。
students <- read_csv("data/students.csv")
students <- read_csv("https://pos.it/r4ds-students-csv")读入数据后,通常需要先对其进行转换,以便在分析时更易使用。带着这一目的,我们再审视一下这个数据表。
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有两个问题:
- 默认情况下,
read_csv()会将空字符串""识别为 NA,但注意到原表中有个“N/A”,并未在R中显示为NA,可以单独设置将其读取为NA。 Student ID和Full Name两个列名有引号,是因为原表中列名包含空格,不合法,除非在读取时用引号标注。
> students <- read_csv("data/students.csv", na = c("N/A", ""))
> students |>
rename(
student_id = `Student ID`,
full_name = `Full Name`
)
> 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接下来考虑数据类型与数据本身的校正,有两点需要关注。
meal_plan是一个分类变量,应该在R中表示为因子(fct),而非字符(chr)。- age列中有一个数据为five而非数字5。
students |>
janitor::clean_names() |>
mutate(
meal_plan = factor(meal_plan),
age = parse_number(if_else(age == "five", "5", age))
)
#> # 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这样便基本改完了。
另外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通常,默认使用数据的第一行作为列名。但是,经常能在文件顶部看到有几行元数据,干扰列名的指定。可以使用skip = n跳过前n行,或者使用cmment = #丢弃所有以#开头的行:
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在某些情况下,数据可能没有列名。可以使用 col_names = FALSE来指出不要将第一行视为标题,而是从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一旦掌握了read_csv(),其他类似函数便迎刃而解。
read_csv2()读取以分号分隔的文件,在用逗号作为较大位数分隔符的国家很常见。read_tsv()读取制表符分隔的文件。read_delim()读入包含任何分隔符的文件,自动猜测分隔符。read_fwf()读取固定宽度的文件。read_table()读取固定宽度文件的一种常见变体,其中列由空格分隔。read_log()读取 Apache 样式的日志文件。
7.3 控制列类型
CSV文件不包含有关变量类型的信息(即它是logical、number、string 等),故readr会自己猜测类型。
但这难免会存在失误。最常见的原因是列中包含意外值,且一般会误判为chr。如果用NA之外的字符表示缺失值也称为意外值。比如:
> simple_csv <- "
x
10
.
20
30"
> read_csv(simple_csv)
#> # A tibble: 4 × 1
#> x
#> <chr>
#> 1 10
#> 2 .
#> 3 20
#> 4 30这个表很短,很快能发现预期外字符的位置,当数据特别长时需要一种特定方法。通过col_types参数自主指定每列的数据类型,然后看readr在哪报错即可。
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)现在readr指出操作存在问题,并建议我们使用problems()函数进一步确认。
problems(df)
#> # A tibble: 1 × 5
#> row col expected actual file
#> <int> <int> <chr> <chr> <chr>
#> 1 3 1 a double . /tmp/RtmpqR32wU/file2304111d9453第 3 行第 1 列存在问题,其中 readr 期望得到双精度浮点数,但得到的只是一个. 。这表明此数据集使用.表示缺失值。所以设置 na = "."即可让所有意外值回到正轨。
read_csv(simple_csv, na = ".")
#> # A tibble: 4 × 1
#> x
#> <dbl>
#> 1 10
#> 2 NA
#> 3 20
#> 4 30像上面col_double()一样的列类型函数共有九种:
col_logical()和col_double()读取逻辑量和实数。col_integer()读取整数。col_character()读取字符串。col_factor()、col_date()、col_datetime()分别创建因子、日期和时间。col_number()是一个数字解析器,它忽略非数字组件,对货币数据特别有用。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 17.4 多个文件读取数据
有时,数据被拆分为多个文件,而不是包含在单个文件中。如下例一次性读取:
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注意到id参数为表格添加了一个指定名称的新列,该列用于标识数据来自的源文件。
7.5 文件写入
要将CSV等文件保存回磁盘,,使用write_csv()和write_tsv()类型函数。有两个主要参数,一个是数据框,一个是保存的地址。
write_csv(students, "students.csv")但是这样有个弊端。我们都知道CSV文件不包含列的类型,所以即便我们已经修改过表格,而写入后再读取仍会恢复原样。有两种解决办法保留列类型:
使用
write_rds()和read_rds()函数。rds是以R自定义的二进制存储格式,所以我们要重新加载时的R对象与当时存储的是完全相同的。arrow包中的write_parquet()和read_parquet()函数。这同样是二进制存储格式,且不限于R,可以跨编程语言共享。
7.6 数据输入
有时会需要我们自己手搓一个tibble,有两个函数来实现。
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.6tribble()。在输入数据时纵向排列,更方便排版。注意输入时列标题以~开头。
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.6tribble表示:transposed tibble