5 数据整理
5.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) 的系统来在 R 中一致地组织数据的方法。将数据转换成这种格式需要一些前期工作,但从长远来看,这些工作是值得的。一旦你有了整洁的数据和 tidyverse 中各个包提供的整洁工具,你将花更少的时间在不同表示形式之间转换数据,从而能将更多时间用于你所关心的数据问题上。
在本章中,你将首先学习整洁数据的定义,并看到它如何应用于一个简单的示例数据集。然后,我们将深入探讨你将用于整理数据的主要工具:转换 (pivoting)。转换可以让你在不改变任何值的情况下改变数据的形态。
5.1.1 先决条件
在本章中,我们将重点关注 tidyr,这是一个提供了大量工具来帮助你整理凌乱数据集的包。tidyr 是核心 tidyverse 的成员之一。
从本章开始,我们将抑制 library(tidyverse)
加载时显示的消息。
5.2 整洁数据
你可以用多种方式来表示相同的基础数据。下面的例子展示了用三种不同方式组织的相同数据。每个数据集都显示了四个变量的相同值:country (国家)、year (年份)、population (人口) 和记录在案的 cases (结核病案例) 数量,但每个数据集以不同的方式组织这些值。
table1
#> # A tibble: 6 × 4
#> country year cases population
#> <chr> <dbl> <dbl> <dbl>
#> 1 Afghanistan 1999 745 19987071
#> 2 Afghanistan 2000 2666 20595360
#> 3 Brazil 1999 37737 172006362
#> 4 Brazil 2000 80488 174504898
#> 5 China 1999 212258 1272915272
#> 6 China 2000 213766 1280428583
table2
#> # A tibble: 12 × 4
#> country year type count
#> <chr> <dbl> <chr> <dbl>
#> 1 Afghanistan 1999 cases 745
#> 2 Afghanistan 1999 population 19987071
#> 3 Afghanistan 2000 cases 2666
#> 4 Afghanistan 2000 population 20595360
#> 5 Brazil 1999 cases 37737
#> 6 Brazil 1999 population 172006362
#> # ℹ 6 more rows
table3
#> # A tibble: 6 × 3
#> country year rate
#> <chr> <dbl> <chr>
#> 1 Afghanistan 1999 745/19987071
#> 2 Afghanistan 2000 2666/20595360
#> 3 Brazil 1999 37737/172006362
#> 4 Brazil 2000 80488/174504898
#> 5 China 1999 212258/1272915272
#> 6 China 2000 213766/1280428583
这些都是相同基础数据的表示形式,但它们的使用便利性并不相同。其中之一,table1
,在 tidyverse 中使用起来会容易得多,因为它是整洁的 (tidy)。
有三条相互关联的规则可以使一个数据集变得整洁:
- 每个变量是一列;每列是一个变量。
- 每个观测是一行;每行是一个观测。
- 每个值是一个单元格;每个单元格是一个值。
Figure 5.1 直观地展示了这些规则。

为什么需要确保你的数据是整洁的呢?主要有两个优点:
选择一种一致的方式来存储数据具有普遍的优势。如果你有了一致的数据结构,学习使用与之配套的工具就会更容易,因为它们具有内在的一致性。
将变量放在列中有其特殊的优势,因为这能让 R 的向量化特性大放异彩。正如你在 Section 3.3.1 和 Section 3.5.2 中学到的,大多数内置的 R 函数都处理值的向量。这使得转换整洁数据感觉特别自然。
dplyr、ggplot2 以及 tidyverse 中的所有其他包都是为处理整洁数据而设计的。以下是一些小例子,展示了你可能会如何使用 table1
。
# 计算每万人的比率
table1 |>
mutate(rate = cases / population * 10000)
#> # A tibble: 6 × 5
#> country year cases population rate
#> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 Afghanistan 1999 745 19987071 0.373
#> 2 Afghanistan 2000 2666 20595360 1.29
#> 3 Brazil 1999 37737 172006362 2.19
#> 4 Brazil 2000 80488 174504898 4.61
#> 5 China 1999 212258 1272915272 1.67
#> 6 China 2000 213766 1280428583 1.67
# 计算每年的总病例数
table1 |>
group_by(year) |>
summarize(total_cases = sum(cases))
#> # A tibble: 2 × 2
#> year total_cases
#> <dbl> <dbl>
#> 1 1999 250740
#> 2 2000 296920
# 可视化随时间的变化
ggplot(table1, aes(x = year, y = cases)) +
geom_line(aes(group = country), color = "grey50") +
geom_point(aes(color = country, shape = country)) +
scale_x_continuous(breaks = c(1999, 2000)) # x 轴刻度在 1999 和 2000
5.2.1 练习
对于每个示例表格,描述每个观测和每列代表什么。
-
简要描述你将如何为
table2
和table3
计算rate
的过程。你需要执行四个操作:- 提取每个国家每年的结核病病例数。
- 提取每个国家每年匹配的人口数。
- 将病例数除以人口数,然后乘以 10000。
- 将结果存回适当的位置。
你还没有学到实际执行这些操作所需的所有函数,但你应该能够思考出你需要的转换过程。
5.3 拉长数据
整洁数据的原则可能看起来如此显而易见,以至于你可能会怀疑自己是否会遇到不整洁的数据集。然而,不幸的是,大多数真实数据都是不整洁的。主要有两个原因:
数据的组织方式通常是为了方便某些分析之外的目标。例如,为了方便数据录入而非分析而组织数据是很常见的。
大多数人并不熟悉整洁数据的原则,除非你花大量时间处理数据,否则很难自己推导出这些原则。
这意味着大多数真实的分析至少需要一些整理工作。你将从弄清楚基础的变量和观测是什么开始。有时这很容易;其他时候你可能需要咨询最初生成数据的人。接下来,你将转换 (pivot) 你的数据,使其成为变量在列、观测在行的整洁形式。
tidyr 提供了两个用于转换数据的函数:pivot_longer()
和 pivot_wider()
。我们先从 pivot_longer()
开始,因为这是最常见的情况。让我们来看一些例子。
5.3.1 列名中包含数据
billboard
数据集记录了 2000 年歌曲的广告牌排名:
billboard
#> # A tibble: 317 × 79
#> artist track date.entered wk1 wk2 wk3 wk4 wk5
#> <chr> <chr> <date> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 2 Pac Baby Don't Cry (Ke… 2000-02-26 87 82 72 77 87
#> 2 2Ge+her The Hardest Part O… 2000-09-02 91 87 92 NA NA
#> 3 3 Doors Down Kryptonite 2000-04-08 81 70 68 67 66
#> 4 3 Doors Down Loser 2000-10-21 76 76 72 69 67
#> 5 504 Boyz Wobble Wobble 2000-04-15 57 34 25 17 17
#> 6 98^0 Give Me Just One N… 2000-08-19 51 39 34 26 26
#> # ℹ 311 more rows
#> # ℹ 71 more variables: wk6 <dbl>, wk7 <dbl>, wk8 <dbl>, wk9 <dbl>, …
在这个数据集中,每个观测是一首歌。前三列 (artist
, track
和 date.entered
) 是描述歌曲的变量。然后我们有 76 列 (wk1
-wk76
) 描述了歌曲在每周的排名1。在这里,列名是一个变量 (周,week
),而单元格的值是另一个变量 (排名,rank
)。
为了整理这个数据,我们将使用 pivot_longer()
:
billboard |>
pivot_longer(
cols = starts_with("wk"),
names_to = "week",
values_to = "rank"
)
#> # A tibble: 24,092 × 5
#> artist track date.entered week rank
#> <chr> <chr> <date> <chr> <dbl>
#> 1 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk1 87
#> 2 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk2 82
#> 3 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk3 72
#> 4 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk4 77
#> 5 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk5 87
#> 6 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk6 94
#> 7 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk7 99
#> 8 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk8 NA
#> 9 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk9 NA
#> 10 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk10 NA
#> # ℹ 24,082 more rows
在数据之后,有三个关键参数:
-
cols
指定哪些列需要被转换,即哪些列不是变量。这个参数使用与select()
相同的语法,所以在这里我们可以使用!c(artist, track, date.entered)
或starts_with("wk")
。 -
names_to
为存储在列名中的变量命名,我们将其命名为week
。 -
values_to
为存储在单元格值中的变量命名,我们将其命名为rank
。
请注意,在代码中 "week"
和 "rank"
是带引号的,因为它们是我们正在创建的新变量,在运行 pivot_longer()
调用时它们还不存在于数据中。
现在让我们把注意力转向结果中这个更长的数据框。如果一首歌在前 100 名的时间少于 76 周会发生什么?以 2 Pac 的 “Baby Don’t Cry” 为例。上面的输出表明它只在前 100 名中待了 7 周,所有剩余的周都用缺失值填充。这些 NA
并不真正代表未知的观测;它们是被数据集的结构强制存在的2,所以我们可以通过设置 values_drop_na = TRUE
来让 pivot_longer()
移除它们:
billboard |>
pivot_longer(
cols = starts_with("wk"),
names_to = "week",
values_to = "rank",
values_drop_na = TRUE
)
#> # A tibble: 5,307 × 5
#> artist track date.entered week rank
#> <chr> <chr> <date> <chr> <dbl>
#> 1 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk1 87
#> 2 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk2 82
#> 3 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk3 72
#> 4 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk4 77
#> 5 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk5 87
#> 6 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk6 94
#> # ℹ 5,301 more rows
现在行数少了很多,这表明许多带有 NA
的行被删除了。
你可能还会想,如果一首歌在前 100 名的时间超过 76 周会发生什么?我们无法从这些数据中得知,但你可能会猜到,数据集中会添加额外的列 wk77
、wk78
……
这个数据现在是整洁的了,但我们可以通过使用 mutate()
和 readr::parse_number()
将 week
的值从字符字符串转换为数字,来使未来的计算更容易一些。parse_number()
是一个方便的函数,它会从字符串中提取第一个数字,忽略所有其他文本。
billboard_longer <- billboard |>
pivot_longer(
cols = starts_with("wk"),
names_to = "week",
values_to = "rank",
values_drop_na = TRUE
) |>
mutate(
week = parse_number(week)
)
billboard_longer
#> # A tibble: 5,307 × 5
#> artist track date.entered week rank
#> <chr> <chr> <date> <dbl> <dbl>
#> 1 2 Pac Baby Don't Cry (Keep... 2000-02-26 1 87
#> 2 2 Pac Baby Don't Cry (Keep... 2000-02-26 2 82
#> 3 2 Pac Baby Don't Cry (Keep... 2000-02-26 3 72
#> 4 2 Pac Baby Don't Cry (Keep... 2000-02-26 4 77
#> 5 2 Pac Baby Don't Cry (Keep... 2000-02-26 5 87
#> 6 2 Pac Baby Don't Cry (Keep... 2000-02-26 6 94
#> # ℹ 5,301 more rows
现在我们把所有的周数都放在一个变量里,所有的排名值都放在另一个变量里,我们就很方便地可以可视化歌曲排名随时间的变化了。代码如下所示,结果在 Figure 5.2 中。我们可以看到,很少有歌曲在前 100 名中停留超过 20 周。
billboard_longer |>
ggplot(aes(x = week, y = rank, group = track)) +
geom_line(alpha = 0.25) +
scale_y_reverse()

5.3.2 转换是如何工作的?
现在你已经看到了我们如何使用转换来重塑数据,让我们花一点时间来直观地理解转换对数据做了什么。让我们从一个非常简单的数据集开始,以便更容易地看到发生了什么。假设我们有三个病人,id
分别是 A、B 和 C,我们对每个病人进行了两次血压测量。我们将用 tribble()
创建数据,这是一个方便手动构建小型 tibble 的函数:
df <- tribble(
~id, ~bp1, ~bp2,
"A", 100, 120,
"B", 140, 115,
"C", 120, 125
)
我们希望我们的新数据集有三个变量:id
(已存在)、measurement
(测量,即列名) 和 value
(值,即单元格值)。为了实现这一点,我们需要将 df
拉长:
df |>
pivot_longer(
cols = bp1:bp2,
names_to = "measurement",
values_to = "value"
)
#> # A tibble: 6 × 3
#> id measurement value
#> <chr> <chr> <dbl>
#> 1 A bp1 100
#> 2 A bp2 120
#> 3 B bp1 140
#> 4 B bp2 115
#> 5 C bp1 120
#> 6 C bp2 125
重塑是如何工作的呢?如果我们逐列思考,就更容易理解了。如 Figure 5.3 所示,原始数据集中已经是变量的列中的值 (id
) 需要被重复,每个被转换的列重复一次。

列名会成为一个新变量中的值,该新变量的名称由 names_to
定义,如 Figure 5.4 所示。它们需要为原始数据集中的每一行重复一次。

单元格的值也成为一个新变量中的值,其名称由 values_to
定义。它们被逐行展开。Figure 5.5 展示了这个过程。

5.3.3 列名中包含多个变量
一个更具挑战性的情况是,当你的列名中塞入了多条信息,而你希望将这些信息存储在不同的新变量中时。例如,拿 who2
数据集来说,这是你上面看到的 table1
及其他表格的来源:
who2
#> # A tibble: 7,240 × 58
#> country year sp_m_014 sp_m_1524 sp_m_2534 sp_m_3544 sp_m_4554
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 Afghanistan 1980 NA NA NA NA NA
#> 2 Afghanistan 1981 NA NA NA NA NA
#> 3 Afghanistan 1982 NA NA NA NA NA
#> 4 Afghanistan 1983 NA NA NA NA NA
#> 5 Afghanistan 1984 NA NA NA NA NA
#> 6 Afghanistan 1985 NA NA NA NA NA
#> # ℹ 7,234 more rows
#> # ℹ 51 more variables: sp_m_5564 <dbl>, sp_m_65 <dbl>, sp_f_014 <dbl>, …
这个由世界卫生组织收集的数据集记录了关于结核病诊断的信息。有两列已经是变量并且很容易解释:country
和 year
。它们后面跟着 56 列,如 sp_m_014
、ep_m_4554
和 rel_m_3544
。如果你盯着这些列足够长的时间,你会注意到一个模式。每个列名都由三部分组成,用 _
分隔。第一部分,sp
/rel
/ep
,描述了诊断所用的方法;第二部分,m
/f
是 gender
(性别,在这个数据集中编码为二元变量);第三部分,014
/1524
/2534
/3544
/4554
/5564
/65
是 age
(年龄) 范围 (例如,014
代表 0-14 岁)。
所以在这种情况下,我们在 who2
中记录了六条信息:国家和年份 (已经是列);诊断方法、性别类别和年龄范围类别 (包含在其他列名中);以及该类别中的患者计数 (单元格值)。为了将这六条信息组织在六个独立的列中,我们使用 pivot_longer()
,并为 names_to
提供一个列名向量,为 names_sep
提供将原始变量名拆分成块的指令,以及为 values_to
提供一个列名:
who2 |>
pivot_longer(
cols = !(country:year),
names_to = c("diagnosis", "gender", "age"),
names_sep = "_",
values_to = "count"
)
#> # A tibble: 405,440 × 6
#> country year diagnosis gender age count
#> <chr> <dbl> <chr> <chr> <chr> <dbl>
#> 1 Afghanistan 1980 sp m 014 NA
#> 2 Afghanistan 1980 sp m 1524 NA
#> 3 Afghanistan 1980 sp m 2534 NA
#> 4 Afghanistan 1980 sp m 3544 NA
#> 5 Afghanistan 1980 sp m 4554 NA
#> 6 Afghanistan 1980 sp m 5564 NA
#> # ℹ 405,434 more rows
names_sep
的一个替代方案是 names_pattern
,在你学习了 Chapter 15 中的正则表达式后,可以用它从更复杂的命名场景中提取变量。
从概念上讲,这只是你已经看过的更简单情况的一个小变种。Figure 5.6 展示了基本思想:现在,列名不再是转换成单个列,而是转换成多个列。你可以想象这分两步发生 (先转换再分离),但实际上它是在一个步骤中完成的,因为这样更快。

5.3.4 列标题中包含数据和变量名
复杂性的下一个台阶是当列名中混合了变量值和变量名。例如,拿 household
数据集来说:
household
#> # A tibble: 5 × 5
#> family dob_child1 dob_child2 name_child1 name_child2
#> <int> <date> <date> <chr> <chr>
#> 1 1 1998-11-26 2000-01-29 Susan Jose
#> 2 2 1996-06-22 NA Mark <NA>
#> 3 3 2002-07-11 2004-04-05 Sam Seth
#> 4 4 2004-10-10 2009-08-27 Craig Khai
#> 5 5 2000-12-05 2005-02-28 Parker Gracie
这个数据集包含了五个家庭的数据,以及最多两个孩子的姓名和出生日期。这个数据集中的新挑战是,列名包含了两个变量的名称 (dob
、name
) 和另一个变量 (child
,值为 1 或 2) 的值。为了解决这个问题,我们再次需要向 names_to
提供一个向量,但这次我们使用特殊的 ".value"
指示符;这不是一个变量名,而是一个告诉 pivot_longer()
做些不同事情的唯一值。这会覆盖通常的 values_to
参数,转而使用被转换列名的第一部分作为输出中的变量名。
household |>
pivot_longer(
cols = !family,
names_to = c(".value", "child"),
names_sep = "_",
values_drop_na = TRUE
)
#> # A tibble: 9 × 4
#> family child dob name
#> <int> <chr> <date> <chr>
#> 1 1 child1 1998-11-26 Susan
#> 2 1 child2 2000-01-29 Jose
#> 3 2 child1 1996-06-22 Mark
#> 4 3 child1 2002-07-11 Sam
#> 5 3 child2 2004-04-05 Seth
#> 6 4 child1 2004-10-10 Craig
#> # ℹ 3 more rows
我们再次使用 values_drop_na = TRUE
,因为输入的形状强制创建了显式的缺失变量 (例如,对于只有一个孩子的家庭)。
Figure 5.7 用一个更简单的例子阐述了基本思想。当你在 names_to
中使用 ".value"
时,输入中的列名同时贡献了输出中的值和变量名。

names_to = c(".value", "num")
进行转换会将列名分成两个部分:第一部分决定输出列的名称(x
或 y
),第二部分决定 num
列的值。
5.4 加宽数据
到目前为止,我们已经使用 pivot_longer()
来解决一类常见的问题,即值最终出现在列名中。接下来,我们将转向 pivot_wider()
,它通过增加列数和减少行数来使数据集变宽,并在一个观测分布在多行时提供帮助。这种情况在现实世界中似乎不那么常见,但在处理政府数据时似乎经常出现。
我们将从查看 cms_patient_experience
开始,这是一个来自医疗保险和医疗补助服务中心的数据集,收集了关于患者体验的数据:
cms_patient_experience
#> # A tibble: 500 × 5
#> org_pac_id org_nm measure_cd measure_title prf_rate
#> <chr> <chr> <chr> <chr> <dbl>
#> 1 0446157747 USC CARE MEDICAL GROUP INC CAHPS_GRP_1 CAHPS for MIPS… 63
#> 2 0446157747 USC CARE MEDICAL GROUP INC CAHPS_GRP_2 CAHPS for MIPS… 87
#> 3 0446157747 USC CARE MEDICAL GROUP INC CAHPS_GRP_3 CAHPS for MIPS… 86
#> 4 0446157747 USC CARE MEDICAL GROUP INC CAHPS_GRP_5 CAHPS for MIPS… 57
#> 5 0446157747 USC CARE MEDICAL GROUP INC CAHPS_GRP_8 CAHPS for MIPS… 85
#> 6 0446157747 USC CARE MEDICAL GROUP INC CAHPS_GRP_12 CAHPS for MIPS… 24
#> # ℹ 494 more rows
被研究的核心单位是一个组织,但每个组织都分布在六行中,每一行对应于在该组织调查中进行的一次测量。我们可以通过使用 distinct()
来查看 measure_cd
和 measure_title
的完整值集:
cms_patient_experience |>
distinct(measure_cd, measure_title)
#> # A tibble: 6 × 2
#> measure_cd measure_title
#> <chr> <chr>
#> 1 CAHPS_GRP_1 CAHPS for MIPS SSM: Getting Timely Care, Appointments, and In…
#> 2 CAHPS_GRP_2 CAHPS for MIPS SSM: How Well Providers Communicate
#> 3 CAHPS_GRP_3 CAHPS for MIPS SSM: Patient's Rating of Provider
#> 4 CAHPS_GRP_5 CAHPS for MIPS SSM: Health Promotion and Education
#> 5 CAHPS_GRP_8 CAHPS for MIPS SSM: Courteous and Helpful Office Staff
#> 6 CAHPS_GRP_12 CAHPS for MIPS SSM: Stewardship of Patient Resources
这两列都不会成为特别好的变量名:measure_cd
没有暗示变量的含义,而 measure_title
是一个包含空格的长句子。我们现在将使用 measure_cd
作为新列名的来源,但在实际分析中,你可能想要创建既简短又有意义的自己的变量名。
pivot_wider()
的接口与 pivot_longer()
相反:我们不是选择新的列名,而是需要提供定义值的现有列 (values_from
) 和定义列名的列 (names_from
):
cms_patient_experience |>
pivot_wider(
names_from = measure_cd,
values_from = prf_rate
)
#> # A tibble: 500 × 9
#> org_pac_id org_nm measure_title CAHPS_GRP_1 CAHPS_GRP_2
#> <chr> <chr> <chr> <dbl> <dbl>
#> 1 0446157747 USC CARE MEDICAL GROUP … CAHPS for MIPS… 63 NA
#> 2 0446157747 USC CARE MEDICAL GROUP … CAHPS for MIPS… NA 87
#> 3 0446157747 USC CARE MEDICAL GROUP … CAHPS for MIPS… NA NA
#> 4 0446157747 USC CARE MEDICAL GROUP … CAHPS for MIPS… NA NA
#> 5 0446157747 USC CARE MEDICAL GROUP … CAHPS for MIPS… NA NA
#> 6 0446157747 USC CARE MEDICAL GROUP … CAHPS for MIPS… NA NA
#> # ℹ 494 more rows
#> # ℹ 4 more variables: CAHPS_GRP_3 <dbl>, CAHPS_GRP_5 <dbl>, …
输出看起来不太对;我们似乎每个组织仍然有多行。这是因为,我们还需要告诉 pivot_wider()
哪个或哪些列的值唯一标识每一行;在这种情况下,是那些以 "org"
开头的变量:
cms_patient_experience |>
pivot_wider(
id_cols = starts_with("org"),
names_from = measure_cd,
values_from = prf_rate
)
#> # A tibble: 95 × 8
#> org_pac_id org_nm CAHPS_GRP_1 CAHPS_GRP_2 CAHPS_GRP_3 CAHPS_GRP_5
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 0446157747 USC CARE MEDICA… 63 87 86 57
#> 2 0446162697 ASSOCIATION OF … 59 85 83 63
#> 3 0547164295 BEAVER MEDICAL … 49 NA 75 44
#> 4 0749333730 CAPE PHYSICIANS… 67 84 85 65
#> 5 0840104360 ALLIANCE PHYSIC… 66 87 87 64
#> 6 0840109864 REX HOSPITAL INC 73 87 84 67
#> # ℹ 89 more rows
#> # ℹ 2 more variables: CAHPS_GRP_8 <dbl>, CAHPS_GRP_12 <dbl>
这样我们就得到了我们想要的输出。
5.4.1 pivot_wider()
是如何工作的?
为了理解 pivot_wider()
是如何工作的,让我们再次从一个非常简单的数据集开始。这次我们有两个病人,id
分别是 A 和 B,我们对病人 A 进行了三次血压测量,对病人 B 进行了两次:
df <- tribble(
~id, ~measurement, ~value,
"A", "bp1", 100,
"B", "bp1", 140,
"B", "bp2", 115,
"A", "bp2", 120,
"A", "bp3", 105
)
我们将从 value
列取值,从 measurement
列取名:
df |>
pivot_wider(
names_from = measurement,
values_from = value
)
#> # A tibble: 2 × 4
#> id bp1 bp2 bp3
#> <chr> <dbl> <dbl> <dbl>
#> 1 A 100 120 105
#> 2 B 140 115 NA
为了开始这个过程,pivot_wider()
首先需要弄清楚行和列中将放什么。新的列名将是 measurement
的唯一值。
默认情况下,输出中的行由所有不进入新名称或值的变量决定。这些被称为 id_cols
。这里只有一个列,但通常可以有任意数量。
然后 pivot_wider()
结合这些结果生成一个空的数据框:
然后它用输入中的数据填充所有缺失值。在这种情况下,并非输出中的每个单元格在输入中都有对应的值,因为病人 B 没有第三次血压测量,所以那个单元格保持缺失。我们将在 Chapter 18 中回到 pivot_wider()
可以“制造”缺失值的这个概念。
你可能还会想,如果输入中有多个行对应于输出中的一个单元格会发生什么。下面的例子有两行对应于 id
“A” 和 measurement
“bp1”:
df <- tribble(
~id, ~measurement, ~value,
"A", "bp1", 100,
"A", "bp1", 102,
"A", "bp2", 120,
"B", "bp1", 140,
"B", "bp2", 115
)
如果我们尝试转换这个,我们会得到一个包含列表列 (list-columns) 的输出,你将在 Chapter 23 中学到更多关于它的知识:
df |>
pivot_wider(
names_from = measurement,
values_from = value
)
#> Warning: Values from `value` are not uniquely identified; output will contain
#> list-cols.
#> • Use `values_fn = list` to suppress this warning.
#> • Use `values_fn = {summary_fun}` to summarise duplicates.
#> • Use the following dplyr code to identify duplicates.
#> {data} |>
#> dplyr::summarise(n = dplyr::n(), .by = c(id, measurement)) |>
#> dplyr::filter(n > 1L)
#> # A tibble: 2 × 3
#> id bp1 bp2
#> <chr> <list> <list>
#> 1 A <dbl [2]> <dbl [1]>
#> 2 B <dbl [1]> <dbl [1]>
因为你还不知道如何处理这种数据,你可能需要根据警告中的提示来找出问题所在:
然后就得由你来弄清楚你的数据出了什么问题,要么修复底层的数据损坏,要么使用你的分组和汇总技能来确保行和列值的每个组合只有一个单行。
5.5 小结
在本章中,你学习了关于整洁数据的知识:即变量在列、观测在行的数据。整洁数据使得在 tidyverse 中工作更容易,因为它是一个被大多数函数所理解的一致结构,主要的挑战是将你收到的任何结构的数据转换成整洁的格式。为此,你学习了 pivot_longer()
和 pivot_wider()
,它们可以让你整理许多不整洁的数据集。我们在这里展示的例子是从 vignette("pivot", package = "tidyr")
中挑选的一部分,所以如果你遇到的问题本章没有帮助你解决,那么那个小品文是一个值得尝试的下一个地方。
另一个挑战是,对于一个给定的数据集,可能无法将更长或更宽的版本标记为“整洁”的那个。这部分反映了我们对整洁数据的定义,我们说整洁数据每个列中有一个变量,但我们实际上没有定义什么是变量 (而且定义它出奇地困难)。务实地说,一个变量可以是任何使你的分析最容易的东西,这是完全可以接受的。所以如果你在 figuring out 如何进行某些计算时卡住了,考虑改变你的数据组织方式;不要害怕按需进行非整洁化、转换和重新整洁化!
如果你喜欢本章并想了解更多关于其底层理论的知识,你可以在发表于《统计软件杂志》(Journal of Statistical Software) 的论文 Tidy Data 中了解更多关于其历史和理论基础。
现在你正在编写大量的 R 代码,是时候学习更多关于将你的代码组织到文件和目录中的知识了。在下一章中,你将学习到脚本和项目的所有优点,以及它们提供的许多使你的生活更轻松的工具。
只要一首歌在 2000 年的某个时间点进入过前 100 名,它就会被收录,并且在出现后最多被追踪 72 周。↩︎
我们将在 Chapter 18 中回到这个概念。↩︎