3.4 Parsing a file

现在我们已经学会了如何用 parse_*() 函数族解析单个向量,接下来就能回到本章的最初目标,研究readr是如何解析文件的。我们将关注以下两点:

  • readr如何自动猜出文件每列的数据类型
  • 如何修改默认设置

3.4.1 Strategies

readr 通过一种启发式过程(heuristic)来确定每列的类型:先读取文件的前1000行,然后使用(相对保守的)某种启发式算法确定每列的类型。readr 中的导入函数会先用 guess_parser() 函数返回对于所需解析函数最可信的猜测,然后尝试用可能性最大的解析函数解析该列:

这个过程会依次尝试以下每种数据类型,直到找到匹配的类型。

逻辑值(logical)
只包括FTFALSETrue
整数(integer)
只包括数值型字符(以及-
双精度浮点数(double)
只包括有效的双精度浮点数
数值(number)
只包括带有分组符号的有效双精度浮点数
时间
与默认的time_format匹配的值
日期
与默认的date_format匹配的值
日期时间
符合ISO 8601标准的任何日期

如果以上数据不符合上述要求中的任意一个,那么这一列就是一个字符串向量,readr将使用parse_character()解析它。

3.4.2 Possible challenges

这些默认设置对更大的文件并不总是有效。以下是两个可能遇到的主要问题:

  • readr 通过前 1000 行猜测数据类型,但是前 1000 行可能只是一种特殊情况,不足以代表整列。例如,一列双精度数值的前1000行有可能都是整数
  • 列中可能包含大量缺失值。如果前 1000 行都是 NA,那么readr 会认为这是一个字符向量,但你其实想将这一类解析为更具体的值。

readr的安装包里包含了一份文件challenge.csv,用来说明解析过程中可能遇到的问题。这个csv文件包含两列x,y和2001行观测。x 列的前 1001 行均为整数,但之后的值均为双精度整数。y 列的前 1001 行均为NA,后面是日期型数据:

可以看到,read_csv() 成功解析了 x,但对于 y 则无从下手,因为使用了错误的解析函数col_logical()。使用 problems() 函数明确列出这些失败记录,以便深入探究其中的问题:

可以使用spec_csv() 来直接查看 readr 在默认情况下用那种类型的解析函数解析数据:

为了解决这个问题,我们用read_csv()函数中的col_types指定每列的解析方法(column specification),之前我们向col_types传入一个字符串说明各列的类别,但这里是要直接指明解析函数了。这样做的指定必须通过cols()函数来创建(具体格式和spec_csv()或者read_csv()自动打印的说明是一样的):

每个parse_*()函数都有一个对应的col_*()函数。如果数据已经保存在R的字符向量中,那么可以使用parse_*(),如果要告诉readr如何加载数据,则应该使用col_*()

The available specifications are: (with string abbreviations in brackets)

  • col_logical() [l], containing only T, F, TRUE or FALSE.
  • col_integer() [i], integers.
  • col_double() [d], doubles.
  • col_character() [c], everything else.
  • col_factor(levels, ordered) [f], a fixed set of values.
  • col_date(format = "") [D]: with the locale’s date_format.
  • col_time(format = "") [t]: with the locale’s time_format.
  • col_datetime(format = "") [T]: ISO8601 date times
  • col_number() [n], numbers containing the grouping_mark
  • col_skip() [_, -], don’t import this column.
  • col_guess() [?], parse using the “best” type based on the input.

cols_only()代替cols()可以仅指定部分列的解析方式;.default表示未提及的所有列(read_csv()的默认设置可以表示为read_csv( col_type = cols(.default = col_guess()))

一旦我们指定了正确的解析函数,问题便迎刃而解。

3.4.3 Other tips

我们再介绍其他几种有注意解析文件的通用技巧:

  • 在前面的示例中,如果比默认方式再多检查一行,就可以解析成功:
  • type_convert() re-convert character columns in existing data frame. This is useful if you need to do some manual munging - you can read the columns in as character, clean it up with (e.g.) regular expressions and then let readr take another stab at parsing it.
  • 如果正在读取一个非常大的文件,那么应该将 n_max 设置为一个较小的数,比如10,000 或者 100,000,这可以让加速重复试验的过程。

3.4.4 Example: Dealing with metadata

https://alison.rbind.io/post/2018-02-23-read-multiple-header-rows/

This dataset is from an article published in PLOS ONE called “Being Sticker Rich: Numerical Context Influences Children’s Sharing Behavior”. In this study, children (ages 3–11) received a small (12, “sticker poor”) or large (30, “sticker rich”) number of stickers, and were then given the opportunity to share their windfall with either one or multiple anonymous recipients.

Data in a plain text editor:

To read in a .tab file, use read_tsv()

The problem here is that the second row is actually metadata or descriptions about each column header. But this make read_tsv() recognize as character type many of our columns. To verify this, see the first and last 6 rows:

To solve this, we will first create a (tidier) character vector of the column names only. Then we’ll read in the actual data and skip the multiple header rows at the top. When we do this, we lose the column names, so we use the character vector of column names we created in the first place instead.

When we set n_max = 0 and col_names = TRUE(the default), ony column headers will be read. Then we could ask janitor::clean_names() to produce a tidier set of names:

What if we want to include that meta data? Create a variable description column use pivot_longer()

3.4.5 Example: multi-row headers

https://debruine.github.io/posts/multi-row-headers/

In most cases, a header will only take up one row. And when multi-row headers present itself, readr fails to recognize all these rows as header

In demo_csv, the first three rows are intended to be an “overall” header. read_csv() outputs a message and modifes the first row for a unqiue set of column names, since it assumes this is a one-row header.

Based on what we have learned in the previous example, the solution should be easy. First set n_max to gain rows of interest, then compose a one-row header to set column names.

For reading multi-row headers in Excel, check section 3.5.1