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 以表格形式展示了相同的数据。
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 ID
和 Full 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
应该是一个逻辑向量。 当 test
为 TRUE
时,结果将包含第二个参数 yes
的值;当 test
为 FALSE
时,结果将包含第三个参数 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 = "#"
来删除所有以(例如)#
开头的行:
在其他情况下,数据可能没有列名。 你可以使用 col_names = FALSE
来告诉 read_csv()
不要将第一行作为标题,而是按顺序将它们标记为 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
传递一个字符向量,它将被用作列名:
这些参数就是你在实践中读取大多数 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 练习
你会用哪个函数来读取字段由 “|” 分隔的文件?
除了
file
、skip
和comment
,read_csv()
和read_tsv()
还有哪些共同的参数?read_fwf()
最重要的参数是什么?-
有时 CSV 文件中的字符串包含逗号。 为了防止它们引起问题,它们需要被引号字符包围,比如
"
或'
。默认情况下,read_csv()
假设引号字符是"
。 要将以下文本读入一个数据框,你需要为read_csv()
指定哪个参数?"x,y\n1,'a,b'"
-
找出以下每个内联 CSV 文件有什么问题。 当你运行这些代码时会发生什么?
-
在下面的数据框中练习引用非语法名称:
- 提取名为
1
的变量。 - 绘制
1
与2
的散点图。 - 创建一个名为
3
的新列,它是2
除以1
的结果。 - 将列重命名为
one
、two
和three
。
- 提取名为
7.3 控制列类型
CSV 文件不包含关于每个变量类型的信息(即它是逻辑值、数字、字符串等),所以 readr 会尝试猜测类型。 本节描述了猜测过程是如何工作的,如何解决一些导致它失败的常见问题,以及如果需要,如何自己提供列类型。 最后,我们将提到一些通用的策略,如果 readr 彻底失败,而你需要更深入地了解你的文件结构,这些策略会很有用。
7.3.1 猜测类型
readr 使用一种启发式方法来判断列类型。 对于每一列,它从第一行到最后一行均匀地抽取 1000 行2 的值,并忽略缺失值。 然后它会按顺序考虑以下问题:
- 它是否只包含
F
、T
、FALSE
或TRUE
(忽略大小写)?如果是,它就是一个逻辑值 (logical)。 - 它是否只包含数字(例如,
1
、-4.5
、5e6
、Inf
)?如果是,它就是一个数字 (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 16 和 Chapter 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-sales、https://pos.it/r4ds-02-sales 和 https://pos.it/r4ds-03-sales 下载这些文件,或者用下面的代码直接读取它们:
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 对于缓存中间结果有点不可靠——你每次加载时都需要重新创建列规格。 主要有两种替代方案:
-
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
-
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()
是按列工作的:
按列布局数据可能很难看出行与行之间的关系,所以另一种选择是 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 世界发展的一般性建议。