“Tidy datasets are all alike, but every messy dataset is messy in its own way.” –– Hadley Wickham
本节中,我们将学习整理组织数据使其成为R可处理的整洁数据。
library(tidyverse)
1. Tidy data
可以通过多种方式表示相同的基础数据。 下面的示例显示了以四种不同方式组织的相同数据。
table1
#> # A tibble: 6 x 4
#> country year cases population
#> <chr> <int> <int> <int>
#> 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 x 4
#> country year type count
#> <chr> <int> <chr> <int>
#> 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
#> # … with 6 more rows
table3
#> # A tibble: 6 x 3
#> country year rate
#> * <chr> <int> <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
# Spread across two tibbles
table4a # cases
#> # A tibble: 3 x 3
#> country `1999` `2000`
#> * <chr> <int> <int>
#> 1 Afghanistan 745 2666
#> 2 Brazil 37737 80488
#> 3 China 212258 213766
table4b # population
#> # A tibble: 3 x 3
#> country `1999` `2000`
#> * <chr> <int> <int>
#> 1 Afghanistan 19987071 20595360
#> 2 Brazil 172006362 174504898
#> 3 China 1272915272 1280428583
这些都是相同基础数据的表示,整洁数据集在tidyverse内部更容易使用。
以下规则使数据集更整洁:
所以上面的三个数据集中只有table1是整洁的,dplyr,ggplot2和tidyverse中的所有其他软件包均设计为可处理整齐的数据。以下是几个示例,它们显示了如何使用table1。
# Compute rate per 10,000
table1 %>%
mutate(rate = cases / population * 10000)
#> # A tibble: 6 x 5
#> country year cases population rate
#> <chr> <int> <int> <int> <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
# Compute cases per year
table1 %>%
count(year, wt = cases)
#> # A tibble: 2 x 2
#> year n
#> <int> <int>
#> 1 1999 250740
#> 2 2000 296920
# Visualise changes over time
library(ggplot2)
ggplot(table1, aes(year, cases)) +
geom_line(aes(group = country), colour = "grey50") +
geom_point(aes(colour = country))
2. Pivoting(旋转)
大部分原始数据集需要进行一些整理。第一步要弄清楚什么是变量和观测值。。第二步是解决两个常见问题之一:
-
一个变量可能分布在多列中。
-
一种观察可能分散在多行中。
要解决这些问题,需要在tidyr中使用两个最重要的功能:
pivot_longer()
和
pivot_wider()
。
(1) Longer
一个常见的问题是数据集中某些列名不是变量名,而是变量值。 以表4a为例:列名1999和2000代表year变量的值,1999和2000列中的值代表case变量的值,并且每一行代表两个观察值,而不是一个观察值。
table4a
#> # A tibble: 3 x 3
#> country `1999` `2000`
#> * <chr> <int> <int>
#> 1 Afghanistan 745 2666
#> 2 Brazil 37737 80488
#> 3 China 212258 213766
为了整理这样的数据集,我们需要将有问题的列旋转到一对新的变量中。
-
名称为值而不是变量的列的集合。
-
要将列名称移至的变量的名称。
-
要将列值移动到的变量的名称。
调用pivot_longer():
table4a %>%
pivot_longer(c(`1999`, `2000`), names_to = "year", values_to = "cases")
#> # A tibble: 6 x 3
#> country year cases
#> <chr> <chr> <int>
#> 1 Afghanistan 1999 745
#> 2 Afghanistan 2000 2666
#> 3 Brazil 1999 37737
#> 4 Brazil 2000 80488
#> 5 China 1999 212258
#> 6 China 2000 213766
表4a中不存在年份和大小写,因此我们将其名称用引号引起来。
在最终结果中,删除了枢轴显示的列,我们得到了“新年”和“案例”列。
ivot_longer()
通过增加行数和减少列数来使数据集更长。
我们可以使用
pivot_longer()
以类似的方式整理table4b。 唯一的区别是存储在单元格值中的变量:
table4b %>%
pivot_longer(c(`1999`, `2000`), names_to = "year", values_to = "population")
#> # A tibble: 6 x 3
#> country year population
#> <chr> <chr> <int>
#> 1 Afghanistan 1999 19987071
#> 2 Afghanistan 2000 20595360
#> 3 Brazil 1999 172006362
#> 4 Brazil 2000 174504898
#> 5 China 1999 1272915272
#> 6 China 2000 1280428583
要将整理后的table4a和table4b版本合并为一个小标题,我们需要使用
dplyr :: left_join()
:
tidy4a <- table4a %>%
pivot_longer(c(`1999`, `2000`), names_to = "year", values_to = "cases")
tidy4b <- table4b %>%
pivot_longer(c(`1999`, `2000`), names_to = "year", values_to = "population")
left_join(tidy4a, tidy4b)
#> Joining, by = c("country", "year")
#> # A tibble: 6 x 4
#> country year cases population
#> <chr> <chr> <int> <int>
#> 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
(2) Wider
ivot_wider()
与
pivot_longer()
相反。 当观察值分散在多行中时,可以使用它。 例如,以table2为例:一个观测值是一年中的一个国家,但是每个观测值分布在两行中。
table2
#> # A tibble: 12 x 4
#> country year type count
#> <chr> <int> <chr> <int>
#> 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
#> # … with 6 more rows
为了解决这个问题,我们首先以与
pivot_longer()
类似的方式分析表示形式。 我们只需要两个参数:
-
从中获取变量名称的列。
-
要从中获取值的列。
一旦确定了这一点,就可以使用
pivot_wider()
,如下面以编程方式显示:
table2 %>%
pivot_wider(names_from = type, values_from = count)
#> # A tibble: 6 x 4
#> country year cases population
#> <chr> <int> <int> <int>
#> 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
3. Separating and uniting
到目前为止,已经学习了如何整理table2和table4。 table3有一个不同的问题:我们有一个包含两个变量(个案和总体)的列(比率)。 要解决此问题,我们需要
eparate()
函数。 还将了解
eparate():unite()
,如果单个变量分布在多列中,则可以使用它们。
(1) Separate
通过在出现分隔符的位置进行拆分,
split()
将一列拆分为多列。
table3
#> # A tibble: 6 x 3
#> country year rate
#> * <chr> <int> <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
费率列包含案例和总体变量,我们需要将其分为两个变量。
split()
将要分离的列的名称和要分离成的列的名称.
table3 %>%
separate(rate, into = c("cases", "population"))
#> # A tibble: 6 x 4
#> country year cases population
#> <chr> <int> <chr> <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
默认情况下,
split()
会在看到非字母数字字符(即不是数字或字母的字符)的任何地方拆分值。 例如,在上面的代码中,
split()
在正斜杠字符处分割rate的值。 如果希望使用特定字符分隔一列,则可以将该字符传递给
sepak()
的
sep
参数。 例如,我们可以将上面的代码重写为:
table3 %>%
separate(rate, into = c("cases", "population"), sep = "/")
仔细查看列类型:您会注意到案例和填充是字符列。 这是
sepeparate()
中的默认行为:保留列的类型不变。我们可以要求
split()
尝试使用
convert = TRUE
转换为更好的类型:
table3 %>%
separate(rate, into = c("cases", "population"), convert = TRUE)
#> # A tibble: 6 x 4
#> country year cases population
#> <chr> <int> <int> <int>
#> 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
还可以将整数向量传递给
sep
。
split()
会将整数解释为要拆分的位置。 正值从字符串最左侧的1开始;否则为0。 负值从字符串最右边的-1开始。 使用整数分隔字符串时,sep的长度应比in中的名称数少一。
可以使用这种方式将每年的后两位数字分开。
table3 %>%
separate(year, into = c("century", "year"), sep = 2)
#> # A tibble: 6 x 4
#> country century year rate
#> <chr> <chr> <chr> <chr>
#> 1 Afghanistan 19 99 745/19987071
#> 2 Afghanistan 20 00 2666/20595360
#> 3 Brazil 19 99 37737/172006362
#> 4 Brazil 20 00 80488/174504898
#> 5 China 19 99 212258/1272915272
#> 6 China 20 00 213766/1280428583
(2) Unite
unite()
是
split()
的反函数:它将多个列合并为一个列。
我们可以使用
unite()
重新加入在上一个示例中创建的
Century
和
Year
列。 该数据另存为tidyr :: table5。
unite()
带有一个数据框,要创建的新变量的名称以及要组合的一组列,这些列再次以
dplyr :: select()
样式指定:
table5 %>%
unite(new, century, year)
#> # A tibble: 6 x 3
#> country new rate
#> <chr> <chr> <chr>
#> 1 Afghanistan 19_99 745/19987071
#> 2 Afghanistan 20_00 2666/20595360
#> 3 Brazil 19_99 37737/172006362
#> 4 Brazil 20_00 80488/174504898
#> 5 China 19_99 212258/1272915272
#> 6 China 20_00 213766/1280428583
在这种情况下,我们还需要使用sep参数。 默认值将在不同列的值之间放置一个下划线(_)。 在这里,我们不需要任何分隔符,因此我们使用"":
table5 %>%
unite(new, century, year, sep = "")
#> # A tibble: 6 x 3
#> country new rate
#> <chr> <chr> <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
4. Missing values
更改数据集的表示形式会带来缺失值,可以通过以下两种可能的方式之一来处理缺失值:
-
明确地,即标记为NA。
-
隐式地,即删除。
让我们用一个非常简单的数据集来说明这个想法:
stocks <- tibble(
year = c(2015, 2015, 2015, 2015, 2016, 2016, 2016),
qtr = c( 1, 2, 3, 4, 2, 3, 4),
return = c(1.88, 0.59, 0.35, NA, 0.92, 0.17, 2.66)
)
此数据集中缺少两个值:
显式缺少2015年第四季度的回报,因为应将其值改为的单元格包含NA。
隐式缺少2016年第一季度的回报,因为它根本没有出现在数据集中。
明确的缺失值是缺少的存在; 隐含的缺失值是不存在。
数据集的表示方式可以使隐式值明确。 例如,我们可以通过在列中输入年份来使隐式明确缺失值:
stocks %>%
pivot_wider(names_from = year, values_from = return)
#> # A tibble: 4 x 3
#> qtr `2015` `2016`
#> <dbl> <dbl> <dbl>
#> 1 1 1.88 NA
#> 2 2 0.59 0.92
#> 3 3 0.35 0.17
#> 4 4 NA 2.66
由于这些显式缺失值在数据的其他表示形式中可能并不重要,因此可以在
pivot_longer()
中将
values_drop_na = TRUE
设置为将显式缺失值变为隐式:
stocks %>%
pivot_wider(names_from = year, values_from = return) %>%
pivot_longer(
cols = c(`2015`, `2016`),
names_to = "year",
values_to = "return",
values_drop_na = TRUE
)
#> # A tibble: 6 x 3
#> qtr year return
#> <dbl> <chr> <dbl>
#> 1 1 2015 1.88
#> 2 2 2015 0.59
#> 3 2 2016 0.92
#> 4 3 2015 0.35
#> 5 3 2016 0.17
#> 6 4 2016 2.66
在整齐的数据中明确显示缺失值的另一个重要工具是
complete()
:
stocks %>%
complete(year, qtr)
#> # A tibble: 8 x 3
#> year qtr return
#> <dbl> <dbl> <dbl>
#> 1 2015 1 1.88
#> 2 2015 2 0.59
#> 3 2015 3 0.35
#> 4 2015 4 NA
#> 5 2016 1 NA
#> 6 2016 2 0.92
#> # … with 2 more rows
complete()
接受一组列,并找到所有唯一的组合。 然后,它确保原始数据集包含所有这些值,并在必要时填充显式的NA。
有时,当主要使用数据源进行数据输入时,缺少值表示应将前一个值结转:
treatment <- tribble(
~ person, ~ treatment, ~response,
"Derrick Whitmore", 1, 7,
NA, 2, 10,
NA, 3, 9,
"Katherine Burke", 1, 4
)
可以使用
fill()
填写这些缺失的值。 它使用一组列,其中您希望将缺失值替换为最新的非缺失值(有时称为结转的最后一个观察值)。
treatment %>%
fill(person)
#> # A tibble: 4 x 3
#> person treatment response
#> <chr> <dbl> <dbl>
#> 1 Derrick Whitmore 1 7
#> 2 Derrick Whitmore 2 10
#> 3 Derrick Whitmore 3 9
#> 4 Katherine Burke 1 4
5. Case Study
tidyr :: who
数据集包含按年龄,国家,年龄,性别和诊断方法细分的结核病(TB)病例。 数据来自《 2014年世界卫生组织全球结核病报告》,网址为
http://www.who.int/tb/country/data/download/en/
。
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>
这是一个非常典型的现实示例数据集。它包含冗余列,奇数变量代码和许多缺失值。
-
看起来country,iso2和iso3是三个冗余地指定国家/地区的变量。
-
年份显然也是一个变量。
-
鉴于变量名称中的结构(例如new_sp_m014,new_ep_m014,new_ep_f014),这些很可能是值,而不是变量。
因此,我们需要将从new_sp_m014到newrel_f65的所有列汇总在一起。我们尚不知道这些值代表什么,因此我们将其通用名称称为“键”。我们知道单元格代表案例数,因此我们将使用可变案例。当前表示形式中有很多缺失的值,所以现在我们将使用values_drop_na只是为了专注于存在的值。
who1 <- who %>%
pivot_longer(
cols = new_sp_m014:newrel_f65,
names_to = "key",
values_to = "cases",
values_drop_na = TRUE
)
who1
#> # A tibble: 76,046 x 6
#> country iso2 iso3 year key cases
#> <chr> <chr> <chr> <int> <chr> <int>
#> 1 Afghanistan AF AFG 1997 new_sp_m014 0
#> 2 Afghanistan AF AFG 1997 new_sp_m1524 10
#> 3 Afghanistan AF AFG 1997 new_sp_m2534 6
#> 4 Afghanistan AF AFG 1997 new_sp_m3544 3
#> 5 Afghanistan AF AFG 1997 new_sp_m4554 5
#> 6 Afghanistan AF AFG 1997 new_sp_m5564 2
#> # … with 76,040 more rows
通过对它们进行计数,我们可以得到一些关于值结构的提示:
who1 %>%
count(key)
#> # A tibble: 56 x 2
#> key n
#> <chr> <int>
#> 1 new_ep_f014 1032
#> 2 new_ep_f1524 1021
#> 3 new_ep_f2534 1021
#> 4 new_ep_f3544 1021
#> 5 new_ep_f4554 1017
#> 6 new_ep_f5564 1017
#> # … with 50 more rows
每列的前三个字母表示该列包含新的还是旧的TB病例。在此数据集中,每一列都包含新案例。
接下来的两个字母描述了结核的类型:
-
rel代表复发病例
-
ep代表肺外结核病例
-
sn代表无法通过肺部涂片诊断(涂片阴性)的肺结核病例。
-
sp代表可通过肺部涂片(涂片阳性)诊断出的肺结核病例
-
第六字母表示结核病患者的性别。数据集按男性(m)和女性(f)对病例进行分组。
其余数字给出了年龄段。数据集将案例分为七个年龄组:
-
014 = 0 – 14岁
-
1524 = 15 – 24岁
-
2534 = 25 – 34岁
-
3544 = 35 – 44岁
-
4554 = 45 – 54岁
-
5564 = 55 – 64岁
-
65 = 65岁或以上
我们需要对列名的格式进行较小的修正:名称有些不一致,因为我们使用newrel来代替new_rel。将了解字符串中的str_replace(),但基本思路非常简单:将字符“ newrel”替换为“ new_rel”。这使所有变量名保持一致。
who2 <- who1 %>%
mutate(key = stringr::str_replace(key, "newrel", "new_rel"))
who2
#> # A tibble: 76,046 x 6
#> country iso2 iso3 year key cases
#> <chr> <chr> <chr> <int> <chr> <int>
#> 1 Afghanistan AF AFG 1997 new_sp_m014 0
#> 2 Afghanistan AF AFG 1997 new_sp_m1524 10
#> 3 Afghanistan AF AFG 1997 new_sp_m2534 6
#> 4 Afghanistan AF AFG 1997 new_sp_m3544 3
#> 5 Afghanistan AF AFG 1997 new_sp_m4554 5
#> 6 Afghanistan AF AFG 1997 new_sp_m5564 2
#> # … with 76,040 more rows
我们可以通过两次单独的·separate()·来分隔每个代码中的值。 第一遍将在每个下划线处拆分代码。
who3 <- who2 %>%
separate(key, c("new", "type", "sexage"), sep = "_")
who3
#> # A tibble: 76,046 x 8
#> country iso2 iso3 year new type sexage cases
#> <chr> <chr> <chr> <int> <chr> <chr> <chr> <int>
#> 1 Afghanistan AF AFG 1997 new sp m014 0
#> 2 Afghanistan AF AFG 1997 new sp m1524 10
#> 3 Afghanistan AF AFG 1997 new sp m2534 6
#> 4 Afghanistan AF AFG 1997 new sp m3544 3
#> 5 Afghanistan AF AFG 1997 new sp m4554 5
#> 6 Afghanistan AF AFG 1997 new sp m5564 2
#> # … with 76,040 more rows
然后,我们最好删除新列,因为它在此数据集中是恒定的。 在删除列时,我们还要删除iso2和iso3,因为它们是多余的。
who3 %>%
count(new)
#> # A tibble: 1 x 2
#> new n
#> <chr> <int>
#> 1 new 76046
who4 <- who3 %>%
select(-new, -iso2, -iso3)
接下来,我们将第一个字符分开,将性别分为年龄和性别:
who5 <- who4 %>%
separate(sexage, c("sex", "age"), sep = 1)
who5
#> # A tibble: 76,046 x 6
#> country year type sex age cases
#> <chr> <int> <chr> <chr> <chr> <int>
#> 1 Afghanistan 1997 sp m 014 0
#> 2 Afghanistan 1997 sp m 1524 10
#> 3 Afghanistan 1997 sp m 2534 6
#> 4 Afghanistan 1997 sp m 3544 3
#> 5 Afghanistan 1997 sp m 4554 5
#> 6 Afghanistan 1997 sp m 5564 2
#> # … with 76,040 more rows
who数据集现在很整洁!
逐步构建一个复杂的管道:
who %>%
pivot_longer(
cols = new_sp_m014:newrel_f65,
names_to = "key",
values_to = "cases",
values_drop_na = TRUE
) %>%
mutate(
key = stringr::str_replace(key, "newrel", "new_rel")
) %>%
separate(key, c("new", "var", "sexage")) %>%
select(-new, -iso2, -iso3) %>%
separate(sexage, c("sex", "age"), sep = 1)
参考:
https://r4ds.had.co.nz/tidy-data.html