6 tidyr

6.1 简介

“Happy families are all alike; every unhappy family is unhappy in its own way.” –– Leo Tolstoy

“Tidy datasets are all alike, but every messy dataset is messy in its own way.” –– Hadley Wickham

整洁的数据(Tidy data)是进行数据操作和 ggplot2 可视化的基础,所谓数据整理(清洗、清理),就是把 messy data 转换为 tidy data 的过程。在 tidyverse 生态中,tidyr 便负责数据的整理和变型:

如果一个数据集是整洁的,需要满足以下三个要素:
1. 每个变量有一个专属列(Each variable must have its own column)
2. 每个观测有一个专属行(Each observation must have its own row)
3. 每个值必须都有一个专属的存储单元(Each value must its own cell)

这三条规则是互相关联的,你不可能只满足三条规则中的两条,所以我们可以更简化地把清洁数据的要求写成:
1. 每列是一个变量(Variables go in columns)
2. 每行是一个观测(Observatiosn go in rows)

同样的数据可以有不同的表现形式,但只有满足整洁数据的三个条件的数据集才是最容易使用的,这也是。以下的3个数据集背后的均来自1999年和2000年世界卫生组织在阿富汗、巴西和中国的一次肺结核病例调查,都有countryyearcasespopulation四个变量,但采用了不同的组织方式:

table4atable4b分别是以 cases 和 population 为值的数据透视表:

在上面的例子中,只有table1 符合清洁数据的标准。在table2 中,type不是一个变量,它的值 casespopulation 才是变量,进而导致了每一行不是一个完整的观测。在 table3 中,rate 同样不是一个变量,casespopulation 的值被挤在了一个单元里。至于 table4atable4b19992000不是变量,而是一个表示年份的变量的值。

为什么要为获得清洁的数据如此大费周折呢?主要有两个优点:

  1. 清洁数据的规则使得我们可以遵从一个一致、明确的结构存储数据。学习处理这些数据的工具变得很容易,因为你的对象在底层是一致的。
  2. 把变量存储在列中可以把R的向量化函数优势发挥到极致。我们已经学过 mutate()summarize() 函数,许多内置的 R 函数都是对向量进行操作的。只要有了清洁的数据,后面的数据变换工作就很容易:

6.1.1 练习

  1. table2计算rate(\(\frac{cases}{population}\))。提示:需要进行以下四步操作:
  • 得到每个国家每年的cases
  • 得到每个国家每年的population
  • 计算rate = cases / population
  • 把算好的数据存储到正确的位置

首先,分别对casespopulation建立一张表,并且确保两张表的排列顺序相同:

计算rate

6.2 spread()gather()

请看下面的两张表格:

细看两表,不难发现它们实质上相同的数据(第二张表是以x为行字段,y为列字段,z为值的数据透视表)。第一种形式成为长数据(long data,indexed data,指标型数据),在长数据(指标型)数据汇总,你需要看指标来找到需要变量的数值(变量x,y,z的值)。第二种称为宽数据(wide data,Cartesian data,笛卡尔型数据),需要看行和列的交叉点来找到对应的值。我们不能简单的说哪一种格式梗优,因为两种形式都有可能是整洁的,这取决于值“A”、“B”、“C”、“D”的含义。

注意到上面的确实值:在一种形式下显示存在的缺失值在另一种格式下不一定能直接看得出来。NA代表了一种缺失情况,但有时数值确实单纯是因为那里没有值。(而不是因为记录失误,没有获取到等原因)

数据整理常需要化宽为长,称为聚集(gathering),但偶尔也需要化长为宽,称为扩散(spreading)。tidyr包分别提供了gather()spread()函数来实现以上操作。

6.2.1 gather()

gather()函数用来处理messy data 的一个常见症状:部分列名不是变量的名子,而是变量的值(some of the column names are not names of variables, but values of a variale),或者说一行中有多个观测。例如,在table4a中,19992000不是某个变量的名字,而是一个表示年份的变量的不同取值:

可以将之与清洁数据table1相比对,不难看出,在table4a中的3行数据在table1中需要6行来表示,这就需要“化宽为长”的gather()函数。

gather()有四个主要参数:

  • data: 需要调用的数据集
  • key: 存放原来各列名的新变量的变量名(键列)
  • value 存放原来单元格中的值的新变量的变量名(值列)
  • ...: 指定的要聚集(融合)的变量,可以通过枚举指定: A,B,C 或者通过范围进行指定A:D,也可以同过-号 指定不需要聚集的列:-E,-F。不管要聚集多少列,gather()函数都把它们聚集为一个键列和一个值列。

接下来我们整理table4a数据集,这里需要聚集的列是19992000:

图示变换过程:

另一个例子:美国劳工市场的月度数据,首先创建一个messy data:

下面,将除了month列的所有列聚集为一个键列和一个值列

为了让数据更好用,我们还可以增加两个额外的参数:

convert = TRUE将键列year变量从字符串转换为数值型(在练习里会谈到为什么year变成了字符串),na.rm = TRUE则可以自动移除没有值的月份(其实这个确实并不是数据的丢失,而只是因为那个时间还有到而已)。

以上数据整理好之后,就很容易用ggplot2作的。如我们可以关注长期趋势,或者查看季节性变化:

6.2.2 spread()

spread()函数是gather()的逆运算,当某个变量的值实际上是其他变量的名字,就需要将数据集化长为宽,也可以说这种“错误”的表现形式是一个观测分散到了多行中(an observation is scattered across multiple rows)。例如table2():

例如,前两列结合起来,才能得到对1999年阿富汗在casespopulation两个变量上的观测。

spread()中,你只需要指定两个参数(除了data以外),keyvalue,这里分别是typecount:

观察table2经过扩散的结果,不难看出spread(key,value)其实是构造透视表的过程。key列将被用作列字段,value列被用作透视表中的值字段,其他列将被当做行字段。

变换的图示:

6.2.3 练习

  1. 在下面的例子中,研究为什么spread()gather()不是完美对称的。

先后使用spread()gather()无法得到一个相同的数据集(除了列的顺序)是因为,数据整理有时会丢失列的类型信息。当spread()将变量year的值20152016用作列的名字时,它们自然被转化为了字符串"2015""2016";随后gather()把列名用作键列year的值,从而year自然变成了一个字符向量。

如果想要复原这个数据集,可以在gather()中使用convert = T,不过这时返回的数据类型是R经过猜测的结果,并不总保证和原数据一致,数据整理带来的不可避免的信息损耗。

2.为什么下面的数据框不能应用spread()?可以添加一列解决这个问题吗?

因为这个数据集里有两个对于“Phillip Woods”在变量age上年龄的观测,spread()就要把由(Phillips Woods,age)确定的单元格里“塞进两个值”。本质上因为namekey这两个变量上的值不能唯一确定一行,所以我们只要添加一列,让namekey和新列可以唯一确定一行即可:

6.3 separate()untie()

spread()函数和gather()函数可以帮你解决数据中的变量放错了位置的问题。而separate()untie()函数则是为了解决以下问题:多个变量挤在了同一列中,或者一个变量分散到了不同列中。

6.3.1 separate()

现在我们知道了如何用spread()gather()table2table4整理为tidy data,现在要学会如何用separate()处理table3了:

table3中,rate同时包含了casespopulation两个变量,我们需要把它拆分(separate)为两列,separate()函数可以将这一混杂的列拆分成多个变量,它包含以下四个主要参数:

  • data: 需要调整的数据框
  • col: 需要进行拆分的列的列名
  • into: 拆分后新生成变量的列名,格式为字符串向量
  • sep: 对如何拆分原变量的描述,其可以是正则表达式,如_表示通过下划线拆分,或[^a-z]表示通过任意非字符字母拆分,或一个指定位置的整数。默认情况下,sep将认定一个非字符字母进行划分

整理的图示:

注意,以上输出的tibble中,casespopulation被设定为字符串类型,使用convert = T将其转换为数值变量

6.3.2 unite()

unite()函数是separate()的逆运算——它可以将多列合并为一列。尽管它不太常用,但是知道这个函数还是很重要的。

table5中,原来的year变量被拆成了两个列,可以用unite(),只需要指定要合并后的列名和要合并的列。默认情况下,新列中将用_分隔符

设置sep参数可以取消分隔符:

整理的图示:

6.3.3 练习

  1. separate()中的extrafill参数的作用是什么?用下面两个数据框进行实验:

extra用来告诉separate()函数如何处理分列过程中多出来的元素(too many pieces,即into指定的列数小于原数据中某行可分的元素个数),fill负责如何处理元素不够的情况(not enough pieces,即into指定的列数大于原数据中某行可分的元素个数)。默认情况下,extra = "drop"separate()将丢弃多余的元素,并生成一条警告信息:

extra = "merge"将把多余的元素和前一个元素当做一个整体:

对于元素过少的情况,默认的fill = "warn"将会用NA进行填充,但会生成一条警告。fill = "right"会尽可能让靠左的列拥有可用的元素,用NA填充右边的列;fill = "left"正好相反。这两种手动设置都不会产生warning:

2.unite()separate()均有一个remove参数,它的作用是什么?

remove控制是否在unite()separate()输出的数据框中保留原来的列,默认remove = T。如果想保留原来未合并/分离的格列,可以设置remove = F

  1. 探究tidyr中一个与separate()类似的函数extract()的用法

separate()函数的分列操作是基于参数sep的,无论是给sep传入字符串指定分隔符,还是用数值指定分隔的位置,separate()必须要有一个分隔符才能正常运作(可以把sep = n看做第n个和第n+1个元素之间的一个空白分隔符)

extract()用一个正则表达式regex描述要分隔的列col中存在的模式,在正则表达式中的每个子表达式(用()定义)将被认为是into中的一个元素,因此,extract()separate()使用起来更加广泛灵活。例如下面的数据集无法用separate()分列,因为无法用一个各行的分隔符(的位置)不一样,但用extract()中的正则表达式就很简单:

适当设计regex,实现的效果可以与设置sep完全一致:

6.4 缺失值

数据整理改变了数据的呈现方式,随之而来的一个话题便是缺失值。通常当我们泛泛地使用“缺失值 (missing value)” 这个名词的时候,其实是指以下两种“缺失”方式中的某一种:

  • 显式缺失(Explicitly missing): 在数据中用NA标识
  • 隐式缺失(Implicitly missing): 未出现在数据中的值

R for Data Science中对这两种缺失的概括:

An explicit missing value is the presence of an absence; an implicit missing value is the absence of a presence.

通过一个简单的数据框区分两种数据缺失的方式:

我们很容易找到stocks第四条观测在变量return上的一个NA,因为它是显式缺失的。另一个隐式缺失的值是(year = 2016,qtr = 1)对应的观测,它没有出现在数据集中。

数据呈现方式上的改变可以将隐式缺失值变成显式。比如,用spread()函数构造以year为行字段,以return为值的透视表,这样就会产生一个属于水平(year = 2016,qtr = 1)的单元格:

现在,再使用gather()不能得到原来的数据框,因为将比原来多出一行显示的缺失值

如果研究者认为这些缺失值是无足轻重的,na.rm = T将在gather()生成的数据框中移除含有缺失值的行:

另一个用于处理确实值的有用工具是complete()函数,它将生成一个指定列集合里面所有的水平组合,并自动将原本隐式的缺失值填充为NA

fill() 函数专门用来填充缺失值,它接受一些需要填充缺失值的列,并用最近的值调换 NA.direction 参数控制用填充的方向:direction = “up" 将由下往上填充,NA 将被替换为它下面那一列的值;direction = "donw" 反之

6.5 Case Study

To finish off the chapter, let’s pull together everything you’ve learned to tackle a realistic data tidying problem. The tidyr::who dataset contains tuberculosis (TB) cases broken down by year, country, age, gender, and diagnosis method. The data comes from the 2014 World Health Organization Global Tuberculosis Report, available at http://www.who.int/tb/country/data/download/en/.

There’s a wealth of epidemiological information in this dataset, but it’s challenging to work with the data in the form that it’s provided:

who
#> # A tibble: 7,240 x 60
#>   country iso2  iso3   year new_sp_m014 new_sp_m1524 new_sp_m2534 new_sp_m3544
#>   <chr>   <chr> <chr> <int>       <int>        <int>        <int>        <int>
#> 1 Afghan… AF    AFG    1980          NA           NA           NA           NA
#> 2 Afghan… AF    AFG    1981          NA           NA           NA           NA
#> 3 Afghan… AF    AFG    1982          NA           NA           NA           NA
#> 4 Afghan… AF    AFG    1983          NA           NA           NA           NA
#> 5 Afghan… AF    AFG    1984          NA           NA           NA           NA
#> 6 Afghan… AF    AFG    1985          NA           NA           NA           NA
#> # … with 7,234 more rows, and 52 more variables: new_sp_m4554 <int>,
#> #   new_sp_m5564 <int>, new_sp_m65 <int>, new_sp_f014 <int>,
#> #   new_sp_f1524 <int>, new_sp_f2534 <int>, new_sp_f3544 <int>,
#> #   new_sp_f4554 <int>, new_sp_f5564 <int>, new_sp_f65 <int>,
#> #   new_sn_m014 <int>, new_sn_m1524 <int>, new_sn_m2534 <int>,
#> #   new_sn_m3544 <int>, new_sn_m4554 <int>, new_sn_m5564 <int>,
#> #   new_sn_m65 <int>, new_sn_f014 <int>, new_sn_f1524 <int>,
#> #   new_sn_f2534 <int>, new_sn_f3544 <int>, new_sn_f4554 <int>,
#> #   new_sn_f5564 <int>, new_sn_f65 <int>, new_ep_m014 <int>,
#> #   new_ep_m1524 <int>, new_ep_m2534 <int>, new_ep_m3544 <int>,
#> #   new_ep_m4554 <int>, new_ep_m5564 <int>, new_ep_m65 <int>,
#> #   new_ep_f014 <int>, new_ep_f1524 <int>, new_ep_f2534 <int>,
#> #   new_ep_f3544 <int>, new_ep_f4554 <int>, new_ep_f5564 <int>,
#> #   new_ep_f65 <int>, newrel_m014 <int>, newrel_m1524 <int>,
#> #   newrel_m2534 <int>, newrel_m3544 <int>, newrel_m4554 <int>,
#> #   newrel_m5564 <int>, newrel_m65 <int>, newrel_f014 <int>,
#> #   newrel_f1524 <int>, newrel_f2534 <int>, newrel_f3544 <int>,
#> #   newrel_f4554 <int>, newrel_f5564 <int>, newrel_f65 <int>

This is a very typical real-life example dataset. It contains redundant columns, odd variable codes, and many missing values. In short, who is messy, and we’ll need multiple steps to tidy it. Like dplyr, tidyr is designed so that each function does one thing well. That means in real-life situations you’ll usually need to string together multiple verbs into a pipeline.

The best place to start is almost always to gather together the columns that are not variables. Let’s have a look at what we’ve got:

  • It looks like country, iso2, and iso3 are three variables that redundantly specify the country.
  • year is also a variable
  • We don’t know what all the other columns are yet, but given the structure in the variable names (e.g. new_sp_m014, new_ep_m014, new_ep_f014) these are likely to be values, not variables.

So we need to gather together all the columns from new_sp_m014 to newrel_f65. We don’t know what those values represent yet, so we’ll give them the generic name "key". We know the cells represent the count of cases, so we’ll use the variable cases. There are a lot of missing values in the current representation, so for now we’ll use na.rm just so we can focus on the values that are present.

We can get some hint of the structure of the values in the new key column by counting them:

You might be able to parse this out by yourself with a little thought and some experimentation, but luckily we have the data dictionary handy. It tells us:
1. The first three letters of each column denote whether the column contains new or old cases of TB. In this dataset, each column contains new cases.
2. The next two letters describe the type of TB: * rel stands for cases of relapse
* ep stands for cases of extrapulmonary TB
* sn stands for cases of pulmonary TB that could not be diagnosed by a pulmonary smear (smear negative)
* sp stands for cases of pulmonary TB that could be diagnosed be a pulmonary smear (smear positive)

  1. The sixth letter gives the sex of TB patients. The dataset groups cases by males (m) and females (f).

  2. The remaining numbers gives the age group. The dataset groups cases into seven age groups:

  • 014 = 0 – 14 years old
  • 1524 = 15 – 24 years old
  • 2534 = 25 – 34 years old
  • 3544 = 35 – 44 years old
  • 4554 = 45 – 54 years old
  • 5564 = 55 – 64 years old
  • 65 = 65 or older

We need to make a minor fix to the format of the column names: unfortunately the names are slightly inconsistent because instead of new_rel we have newrel (it’s hard to spot this here but if you don’t fix it we’ll get errors in subsequent steps). You’ll learn about str_replace() in strings, but the basic idea is pretty simple: replace the characters “newrel” with “new_rel”. This makes all variable names consistent.

We can separate the values in each code with two passes of separate(). The first pass will split the codes at each underscore.

Then we might as well drop the new column because it’s constant in this dataset. While we’re dropping columns, let’s also drop iso2 and iso3 since they’re redundant.

Next we’ll separate sexage into sex and age by splitting after the first character:

The who dataset is now tidy!

I’ve shown you the code a piece at a time, assigning each interim result to a new variable. This typically isn’t how you’d work interactively. Instead, you’d gradually build up a complex pipe:

6.5.1 练习

  1. 在清理who数据框时,我们说iso2iso3在有了country之后是冗余的,证明这一点

如果iso2iso3相对于country是冗余的,则在数据集中对于变量country的每个值,仅有一个iso2iso3的水平组合(country能唯一确定一条观测)。

这里要用到distinct()函数,它将返回数据框中某些列出现的的全部不重复的水平组合(注意complete()是”制造出“全部可能的水平组合),和unique()类似,但速度更快:

6.6 拓展:None-tidy data

Before we continue on to other topics, it’s worth talking briefly about non-tidy data. Earlier in the chapter, I used the pejorative term “messy” to refer to non-tidy data. That’s an oversimplification: there are lots of useful and well-founded data structures that are not tidy data. There are two main reasons to use other data structures:

  • Alternative representations may have substantial performance or space advantages.
  • Specialised fields have evolved their own conventions for storing data that may be quite different to the conventions of tidy data.

Either of these reasons means you’ll need something other than a tibble (or data frame). If your data does fit naturally into a rectangular structure composed of observations and variables, I think tidy data should be your default choice. But there are good reasons to use other structures; tidy data is not the only way.

If you’d like to learn more about non-tidy data, I’d highly recommend this thoughtful blog post by Jeff Leek: http://simplystatistics.org/2016/02/17/non-tidy-data/

6.7 tidyr 1.0.0

tidyr 于 2019 年 9 月 14 日发布了版本 1.0.0,有以下重大变化:

  • New pivot_longer() and pivot_wider() provide improved tools for reshaping, superceding spread() and gather().

  • New unnest_auto(), unnest_longer(), unnest_wider(), and hoist() provide new tools for rectangling, converting deeply nested lists into tidy data frames.

  • nest() and unnest() have been changed to match an emerging principle for the design of ... interfaces. Four new functions (pack()/unpack(), and chop()/unchop()) reveal that nesting is the combination of two simpler steps.

  • New expand_grid(), a variant of base::expand.grid(). This is a useful function to know about, but also serves as a good reason to discuss the important role that vctrs plays behind the scenes. You shouldn’t ever have to learn about vctrs, but it brings improvements to consistency and performance.

参考 https://www.tidyverse.org/articles/2019/09/tidyr-1-0-0/

文档:

6.7.1 pivot_longerpivot_wider

pivot_longer()pivot_wider() 分别对应原来的 gather()spread(),如今 API 更加容易理解:

6.7.1.1 pivot_longer()

基本用法:

names_tovalues_to 参数相当于原来 gather() 中的 keyvalue,其中 “键” 列的默认名称变为 “name”


Numeric data in column names

pivot_longer() 现在提供了 names_ptypevalues_ptypes 两个参数调整数据集变长后键列和值列的数据类别。看一下 billboard 数据集:

显然,我们希望将所有以 "wk"开头的列聚合以得到整洁数据,键列和值列分别命名为 “week” 和 “rank”。另外要考虑的一点是,我们很可能之后想计算歌曲保持在榜单上的周数,故需要将 “week” 列转换为数值类型:

names_prefix 去除前缀 "wk"names_ptype 以列表的形式转换键列的数据类型


Many variables in column names

pivot_longer() 现在可以方便地拆分键列,之前曾处理过 who 数据集:

who
#> # A tibble: 7,240 x 60
#>   country iso2  iso3   year new_sp_m014 new_sp_m1524 new_sp_m2534 new_sp_m3544
#>   <chr>   <chr> <chr> <int>       <int>        <int>        <int>        <int>
#> 1 Afghan… AF    AFG    1980          NA           NA           NA           NA
#> 2 Afghan… AF    AFG    1981          NA           NA           NA           NA
#> 3 Afghan… AF    AFG    1982          NA           NA           NA           NA
#> 4 Afghan… AF    AFG    1983          NA           NA           NA           NA
#> 5 Afghan… AF    AFG    1984          NA           NA           NA           NA
#> 6 Afghan… AF    AFG    1985          NA           NA           NA           NA
#> # … with 7,234 more rows, and 52 more variables: new_sp_m4554 <int>,
#> #   new_sp_m5564 <int>, new_sp_m65 <int>, new_sp_f014 <int>,
#> #   new_sp_f1524 <int>, new_sp_f2534 <int>, new_sp_f3544 <int>,
#> #   new_sp_f4554 <int>, new_sp_f5564 <int>, new_sp_f65 <int>,
#> #   new_sn_m014 <int>, new_sn_m1524 <int>, new_sn_m2534 <int>,
#> #   new_sn_m3544 <int>, new_sn_m4554 <int>, new_sn_m5564 <int>,
#> #   new_sn_m65 <int>, new_sn_f014 <int>, new_sn_f1524 <int>,
#> #   new_sn_f2534 <int>, new_sn_f3544 <int>, new_sn_f4554 <int>,
#> #   new_sn_f5564 <int>, new_sn_f65 <int>, new_ep_m014 <int>,
#> #   new_ep_m1524 <int>, new_ep_m2534 <int>, new_ep_m3544 <int>,
#> #   new_ep_m4554 <int>, new_ep_m5564 <int>, new_ep_m65 <int>,
#> #   new_ep_f014 <int>, new_ep_f1524 <int>, new_ep_f2534 <int>,
#> #   new_ep_f3544 <int>, new_ep_f4554 <int>, new_ep_f5564 <int>,
#> #   new_ep_f65 <int>, newrel_m014 <int>, newrel_m1524 <int>,
#> #   newrel_m2534 <int>, newrel_m3544 <int>, newrel_m4554 <int>,
#> #   newrel_m5564 <int>, newrel_m65 <int>, newrel_f014 <int>,
#> #   newrel_f1524 <int>, newrel_f2534 <int>, newrel_f3544 <int>,
#> #   newrel_f4554 <int>, newrel_f5564 <int>, newrel_f65 <int>

## tidyr 一章中使用的方法
who %>% 
  gather(starts_with("new"), 
         key = key, 
         value = value,
         na.rm = T) %>% 
  extract(key,
          into = c("diagnosis", "gender", "age"),
          regex = "new_?(.*)_(.)(.*)")
#> # A tibble: 76,046 x 8
#>   country     iso2  iso3   year diagnosis gender age   value
#>   <chr>       <chr> <chr> <int> <chr>     <chr>  <chr> <int>
#> 1 Afghanistan AF    AFG    1997 sp        m      014       0
#> 2 Afghanistan AF    AFG    1998 sp        m      014      30
#> 3 Afghanistan AF    AFG    1999 sp        m      014       8
#> 4 Afghanistan AF    AFG    2000 sp        m      014      52
#> 5 Afghanistan AF    AFG    2001 sp        m      014     129
#> 6 Afghanistan AF    AFG    2002 sp        m      014      90
#> # … with 7.604e+04 more rows

由于聚合后的键列包含了多个变量,还要再用一次 extract() 使之分离,现在可以直接在 names_to 中传入一个向量表示分裂后的各个键列,并在 names_pattern 中用正则表达式指定分裂的模式:

更进一步,顺便设定好整理后 genderage 的类型:


Multiple observations per row

(多个值列)

So far, we have been working with data frames that have one observation per row, but many important pivotting problems involve multiple observations per row. You can usually recognise this case because name of the column that you want to appear in the output is part of the column name in the input. In this section, you’ll learn how to pivot this sort of data.

理想中的数据格式

family child dob gender
1 1 1998-11-26 1
1 2 2000-01-29 2
2 1 1996-06-22 2
3 1 2002-07-11 2
3 2 2004-04-05 2
4 1 2004-10-10 1
4 2 2009-08-27 1
5 1 2000-12-05 2
5 2 2005-02-28 1

Note that we have two pieces of information (or values) for each child: their gender and their dob (date of birth). These need to go into separate columns in the result. Again we supply multiple variables to names_to, using names_sep to split up each variable name. Note the special name .value: this tells pivot_longer() that that part of the column name specifies the “value” being measured (which will become a variable in the output)

在这里,dob_child1dob_child2gender_child1gender_child2四个列名的后半部分被当做键列的值。例如,可以认为对于 family == 1的观测,首先生成了如下的结构:

family child dob dob gender gender
1 child1 1998-11-16 2000-01-29 1 2
2 child2

而后名称相同的值列合并:

family child dob gender
1 child1 1998-11-26 1
1 child2 2000-01-29 2

另一个例子:

叕一个例子:


Duplicated column names

如果某个数据框中的变量有重复的名字,用 gather()聚合这些变量所在的列时会返回一条错误:

这是因为被聚合的列名被当做 key 列的值,又因这些值是重复的,故不能唯一标识一条记录。pivot_longer 针对这一点做了优化,尝试聚合这些列时,会自动生成一个标识列: