7  数据导入

7.1 引言

使用 R 包提供的数据是学习数据科学工具的好方法,但总有一天,你会希望将所学知识应用到自己的数据上。 在本章中,你将学习将数据文件读入 R 的基础知识。

具体来说,本章将重点介绍如何读取纯文本矩形文件。 我们将从处理列名、类型和缺失数据等特性的实用建议开始。 然后,你将学习如何一次性从多个文件中读取数据,以及如何将数据从 R 写入文件。 最后,你将学习如何在 R 中手动创建数据框。

7.1.1 先决条件

在本章中,你将学习如何使用 readr 包将纯文本文件加载到 R 中,该包是核心 tidyverse 的一部分。

7.2 从文件中读取数据

首先,我们将重点关注最常见的矩形数据文件类型:CSV,即逗号分隔值 (comma-separated values) 的缩写。 下面是一个简单的 CSV 文件的样子。 第一行,通常称为标题行 (header row),给出了列名,接下来的六行提供了数据。 这些列由逗号分隔,也称为定界 (delimited)。

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

Table 7.1 以表格形式展示了相同的数据。

Table 7.1: 来自 students.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 NA
5 Chidiegwu Dunkel Pizza Breakfast and lunch five
6 Güvenç Attila Ice cream Lunch only 6

我们可以使用 read_csv() 将这个文件读入 R。 第一个参数是最重要的:文件的路径。 你可以将路径看作是文件的地址:文件名为 students.csv,它位于 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.

如果你的项目中的 data 文件夹里有 students.csv 文件,上面的代码就能正常工作。 你可以从 https://pos.it/r4ds-students-csv 下载 students.csv 文件,或者用下面的代码直接从那个 URL 读取它:

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

当你运行 read_csv() 时,它会打印一条消息,告诉你数据的行数和列数、使用的分隔符以及列的规格(按列所含数据类型组织的列名)。 它还会打印一些关于检索完整列规格和如何静默此消息的信息。 这条消息是 readr 的一个组成部分,我们将在 Section 7.3 中再次讨论它。

7.2.1 实用建议

一旦你读入数据,第一步通常是进行某种方式的转换,以便在后续分析中更容易处理。 让我们带着这个想法再看一次 students 数据。

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

favourite.food 列中,有一堆食物项目,然后是字符串 N/A,这本应是一个真正的 NA,R 会将其识别为“不可用”(not available)。 这是我们可以使用 na 参数来解决的问题。 默认情况下,read_csv() 在这个数据集中只识别空字符串 ("") 为 NA,我们希望它也能识别字符串 "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

你可能还会注意到 Student IDFull Name 这两列被反引号包围。 这是因为它们包含空格,破坏了 R 的常规变量命名规则;它们是非语法 (non-syntactic) 名称。 要引用这些变量,你需要用反引号 ` 将它们包围起来:

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

另一种方法是使用 janitor::clean_names(),它会运用一些启发式方法一次性将所有列名转换为蛇形命名法 (snake case)1

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

读入数据后的另一个常见任务是考虑变量类型。 例如,meal_plan 是一个分类变量,具有一组已知的可能值,在 R 中应表示为因子 (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

注意,meal_plan 变量中的值保持不变,但变量名下方表示的变量类型已从字符 (<chr>) 变为因子 (<fct>)。 你将在 Chapter 16 中学到更多关于因子的知识。

在分析这些数据之前,你可能想修复 age 列。 目前,age 是一个字符变量,因为其中一个观测值被输入为 five 而不是数字 5。 我们将在 Chapter 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

这里有一个新函数 if_else(),它有三个参数。 第一个参数 test 应该是一个逻辑向量。 当 testTRUE 时,结果将包含第二个参数 yes 的值;当 testFALSE 时,结果将包含第三个参数 no 的值。 这里我们是说,如果 age 是字符串 "five",就把它变成 "5",如果不是,就保持 age 不变。 你将在 Chapter 12 中学到更多关于 if_else() 和逻辑向量的知识。

7.2.2 其他参数

我们还需要提到几个其他重要的参数,如果我们先向你展示一个方便的技巧,会更容易演示: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

通常,read_csv() 使用数据的第一行作为列名,这是一个非常普遍的惯例。 但文件顶部包含几行元数据的情况也并不少见。 你可以使用 skip = n 来跳过前 n 行,或者使用 comment = "#" 来删除所有以(例如)# 开头的行:

read_csv(
  "元数据的第一行
  元数据的第二行
  x,y,z
  1,2,3",
  skip = 2
)
#> # A tibble: 1 × 3
#>       x     y     z
#>   <dbl> <dbl> <dbl>
#> 1     1     2     3

read_csv(
  "# 我想跳过的一条注释
  x,y,z
  1,2,3",
  comment = "#"
)
#> # A tibble: 1 × 3
#>       x     y     z
#>   <dbl> <dbl> <dbl>
#> 1     1     2     3

在其他情况下,数据可能没有列名。 你可以使用 col_names = FALSE 来告诉 read_csv() 不要将第一行作为标题,而是按顺序将它们标记为 X1Xn

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

这些参数就是你在实践中读取大多数 CSV 文件所需要知道的全部内容了。 (至于其余情况,你需要仔细检查你的 .csv 文件,并阅读 read_csv() 许多其他参数的文档。)

7.2.3 其他文件类型

一旦你掌握了 read_csv(),使用 readr 的其他函数就很直接了;这只是知道该用哪个函数的问题:

  • read_csv2() 读取分号分隔的文件。 这些文件使用 ; 而不是 , 来分隔字段,在那些使用 , 作为小数点的国家很常见。

  • read_tsv() 读取制表符分隔的文件。

  • read_delim() 读取任何分隔符的文件,如果你不指定分隔符,它会尝试自动猜测。

  • read_fwf() 读取固定宽度文件。 你可以用 fwf_widths() 按宽度指定字段,或用 fwf_positions() 按位置指定字段。

  • read_table() 读取一种常见的固定宽度文件变体,其中列由空白分隔。

  • read_log() 读取 Apache 风格的日志文件。

7.2.4 练习

  1. 你会用哪个函数来读取字段由 “|” 分隔的文件?

  2. 除了 fileskipcommentread_csv()read_tsv() 还有哪些共同的参数?

  3. read_fwf() 最重要的参数是什么?

  4. 有时 CSV 文件中的字符串包含逗号。 为了防止它们引起问题,它们需要被引号字符包围,比如 "'。默认情况下,read_csv() 假设引号字符是 "。 要将以下文本读入一个数据框,你需要为 read_csv() 指定哪个参数?

    "x,y\n1,'a,b'"
  5. 找出以下每个内联 CSV 文件有什么问题。 当你运行这些代码时会发生什么?

    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. 在下面的数据框中练习引用非语法名称:

    1. 提取名为 1 的变量。
    2. 绘制 12 的散点图。
    3. 创建一个名为 3 的新列,它是 2 除以 1 的结果。
    4. 将列重命名为 onetwothree
    annoying <- tibble(
      `1` = 1:10,
      `2` = `1` * 2 + rnorm(length(`1`))
    )

7.3 控制列类型

CSV 文件不包含关于每个变量类型的信息(即它是逻辑值、数字、字符串等),所以 readr 会尝试猜测类型。 本节描述了猜测过程是如何工作的,如何解决一些导致它失败的常见问题,以及如果需要,如何自己提供列类型。 最后,我们将提到一些通用的策略,如果 readr 彻底失败,而你需要更深入地了解你的文件结构,这些策略会很有用。

7.3.1 猜测类型

readr 使用一种启发式方法来判断列类型。 对于每一列,它从第一行到最后一行均匀地抽取 1000 行2 的值,并忽略缺失值。 然后它会按顺序考虑以下问题:

  • 它是否只包含 FTFALSETRUE(忽略大小写)?如果是,它就是一个逻辑值 (logical)。
  • 它是否只包含数字(例如,1-4.55e6Inf)?如果是,它就是一个数字 (number)。
  • 它是否符合 ISO8601 标准?如果是,它就是一个日期或日期时间。(我们将在 Section 17.2 中更详细地回到日期时间)。
  • 否则,它必须是一个字符串 (string)。

你可以在这个简单的例子中看到这种行为:

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

如果你的数据集很干净,这个启发式方法效果很好,但在现实生活中,你会遇到各种各样稀奇古怪的失败情况。

7.3.2 缺失值、列类型和问题

列检测最常见的失败方式是某一列包含了意料之外的值,导致你得到一个字符列而不是更具体的类型。 最常见的原因之一是缺失值,它被记录为 readr 不期望的其他形式,而不是 NA

以这个简单的单列 CSV 文件为例:

simple_csv <- "
  x
  10
  .
  20
  30"

如果我们不带任何额外参数来读取它,x 会变成一个字符列:

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

在这个非常小的情况下,你可以轻易看到缺失值 .。 但是,如果你有成千上万行,其中只有少数几个由 . 表示的缺失值散布其中,会发生什么呢? 一种方法是告诉 readr x 是一个数值列,然后看它在哪里失败。 你可以通过 col_types 参数来做到这一点,该参数接受一个命名列表,其中名称与 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)

现在 read_csv() 报告说有问题,并告诉我们可以用 problems() 了解更多信息:

problems(df)
#> # A tibble: 1 × 5
#>     row   col expected actual file                                           
#>   <int> <int> <chr>    <chr>  <chr>                                          
#> 1     3     1 a double .      C:/Users/14913/AppData/Local/Temp/RtmpukuEag/f…

这告诉我们,在第 3 行第 1 列有一个问题,readr 期望一个双精度数 (double),但得到了一个 .。 这表明这个数据集使用 . 来表示缺失值。 所以我们设置 na = ".",自动猜测就成功了,得到了我们想要的数值列:

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

7.3.3 列类型

readr 总共提供了九种列类型供你使用:

  • col_logical()col_double() 读取逻辑值和实数。它们相对来说很少需要(除非像上面那样),因为 readr 通常会为你猜到它们。
  • col_integer() 读取整数。本书中我们很少区分整数和双精度数,因为它们在功能上是等效的,但明确读取整数偶尔会很有用,因为它们占用的内存是双精度数的一半。
  • col_character() 读取字符串。当你的某一列是数值标识符时,明确指定它会很有用,即一长串数字,它标识一个对象,但对其进行数学运算没有意义。例子包括电话号码、社会安全号码、信用卡号码等。
  • col_factor()col_date()col_datetime() 分别创建因子、日期和日期时间;当我们在 Chapter 16Chapter 17 中讲到这些数据类型时,你将学到更多关于它们的内容。
  • col_number() 是一个宽容的数值解析器,会忽略非数值部分,对货币特别有用。你将在 Chapter 13 中学到更多关于它的知识。
  • 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 1

7.4 从多个文件中读取数据

有时你的数据分散在多个文件中,而不是包含在单个文件中。 例如,你可能有多個月的销售数据,每个月的数据都在一个单独的文件中:01-sales.csv 代表一月,02-sales.csv 代表二月,03-sales.csv 代表三月。 使用 read_csv(),你可以一次性读取这些数据,并将它们堆叠在一个单一的数据框中。

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

同样,如果你的项目中的 data 文件夹里有这些 CSV 文件,上面的代码就能正常工作。 你可以从 https://pos.it/r4ds-01-saleshttps://pos.it/r4ds-02-saleshttps://pos.it/r4ds-03-sales 下载这些文件,或者用下面的代码直接读取它们:

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

id 参数会在结果数据框中添加一个名为 file 的新列,用于标识数据来自哪个文件。 这在读取的文件本身没有标识列,无法帮助你将观测值追溯到其原始来源的情况下特别有用。

如果你有很多文件要读取,将它们的名字写成一个列表可能会很麻烦。 相反,你可以使用基础 R 的 list.files() 函数,通过匹配文件名中的一个模式来为你找到文件。 你将在 Chapter 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 写入文件

readr 也附带了两个有用的函数,用于将数据写回磁盘:write_csv()write_tsv()。 这些函数最重要的参数是 x(要保存的数据框)和 file(保存的位置)。 你还可以用 na 指定如何写入缺失值,以及如果你想 append到一个现有文件。

write_csv(students, "students.csv")

现在让我们把那个 csv 文件读回来。 注意,当你保存为 CSV 时,你刚刚设置的变量类型信息会丢失,因为你又从一个纯文本文件开始读取了:

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

这使得 CSV 对于缓存中间结果有点不可靠——你每次加载时都需要重新创建列规格。 主要有两种替代方案:

  1. write_rds()read_rds() 是对基础函数 readRDS()saveRDS() 的统一包装。 它们以 R 的自定义二进制格式 RDS 存储数据。 这意味着当你重新加载对象时,你加载的是你存储的完全相同的 R 对象。

    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. arrow 包允许你读写 parquet 文件,这是一种快速的二进制文件格式,可以在不同编程语言之间共享。 我们将在 Chapter 22 中更深入地讨论 arrow。

    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 通常比 RDS 快得多,并且可以在 R 之外使用,但需要 arrow 包。

7.6 数据录入

有时你需要“手动”组装一个 tibble,在你的 R 脚本中进行少量的数据录入。 有两个有用的函数可以帮助你做到这一点,它们的区别在于你是按列还是按行来布局 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.6

按列布局数据可能很难看出行与行之间的关系,所以另一种选择是 tribble(),即转置的 tibble (transposed tibble) 的缩写,它让你逐行布局你的数据。 tribble() 是为在代码中进行数据录入而定制的:列标题以 ~ 开头,条目由逗号分隔。 这使得可以用一种易于阅读的形式来布局少量数据:

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 总结

在本章中,你学会了如何使用 read_csv() 加载 CSV 文件,以及如何使用 tibble()tribble() 进行你自己的数据录入。 你了解了 CSV 文件的工作原理,你可能会遇到的一些问题,以及如何克服它们。 在本书中,我们会几次回到数据导入这个话题:Chapter 20 讲从 Excel 和 Google Sheets 导入,Chapter 21 将向你展示如何从数据库加载数据,Chapter 22 从 parquet 文件,Chapter 23 从 JSON,以及 Chapter 24 从网站。

我们即将结束本书的这一部分,但还有一个重要的最后话题要讲:如何获得帮助。 所以在下一章中,你将学到一些寻求帮助的好地方,如何创建一个 reprex (可复现示例) 来最大化你获得良好帮助的机会,以及一些关于跟上 R 世界发展的一般性建议。


  1. janitor 包不属于 tidyverse,但它提供了便捷的数据清理函数,并且能很好地在使 用 |> 的数据管道中工作。↩︎

  2. 你可以用 guess_max 参数覆盖默认的 1000 行。↩︎