简介
tidyverse是一套数据分析套件包,它极大地简化和拓展了使用R语言进行数据分析的操作,涵盖了数据导入、数据处理和可视化等多方面的功能。
dplyr和tidyr是tidyverse的重要组成部分,前者主要用于数据的处理,后者则主要用于数据的格式调整。
两者的主要处理的对象是data.frame(及衍生的data.table和tibble),这是一种用于存储高度结构化数据的对象。你可以将data.frame视作一张表格,其每一行为一个条目,每一列为一项属性。同一列中的所有数据类型一致,而同一行则不一定。data.frame中的每列都可以被视为一个向量,在dplyr的许多操作中可以体会到这一点。
magrittr的pipe操作符%>%是一种可以很好地简化和美化链式函数调用的语法工具。函数表达式function(value, args)可以用pipe改写为value %>% function(args)。dplyr和tidyr的data.frame操作函数都很好地支持了pipe语法。在后文介绍dplyr和tidyr函数的参数格式时,我们默认使用pipe中的参数格式,省去函数的第一个参数,即要操作的data.frame对象。
示例数据
r
iris
plaintext
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 5.1 3.5 1.4 0.2 setosa
2 4.9 3.0 1.4 0.2 setosa
3 4.7 3.2 1.3 0.2 setosa
4 4.6 3.1 1.5 0.2 setosa
5 5.0 3.6 1.4 0.2 setosa
6 5.4 3.9 1.7 0.4 setosa
7 4.6 3.4 1.4 0.3 setosa
8 5.0 3.4 1.5 0.2 setosa
9 4.4 2.9 1.4 0.2 setosa
10 4.9 3.1 1.5 0.1 setosa
[ reached 'max' / getOption("max.print") -- omitted 140 rows ]
r
storms
plaintext
# A tibble: 19,537 × 13
name year month day hour lat long status category wind pressure tropicalstorm_force_...¹
<chr> <dbl> <dbl> <int> <dbl> <dbl> <dbl> <fct> <dbl> <int> <int> <int>
1 Amy 1975 6 27 0 27.5 -79 tropical de... NA 25 1013 NA
2 Amy 1975 6 27 6 28.5 -79 tropical de... NA 25 1013 NA
3 Amy 1975 6 27 12 29.5 -79 tropical de... NA 25 1013 NA
4 Amy 1975 6 27 18 30.5 -79 tropical de... NA 25 1013 NA
5 Amy 1975 6 28 0 31.5 -78.8 tropical de... NA 25 1012 NA
6 Amy 1975 6 28 6 32.4 -78.7 tropical de... NA 25 1012 NA
7 Amy 1975 6 28 12 33.3 -78 tropical de... NA 25 1011 NA
8 Amy 1975 6 28 18 34 -77 tropical de... NA 30 1006 NA
9 Amy 1975 6 29 0 34.4 -75.8 tropical st... NA 35 1004 NA
10 Amy 1975 6 29 6 34 -74.8 tropical st... NA 40 1002 NA
# ℹ 19,527 more rows
# ℹ abbreviated name: ¹tropicalstorm_force_diameter
# ℹ 1 more variable: hurricane_force_diameter <int>
r
data.frame(Titanic)
plaintext
Class Sex Age Survived Freq
1 1st Male Child No 0
2 2nd Male Child No 0
3 3rd Male Child No 35
4 Crew Male Child No 0
5 1st Female Child No 0
6 2nd Female Child No 0
7 3rd Female Child No 17
8 Crew Female Child No 0
9 1st Male Adult No 118
10 2nd Male Adult No 154
[ reached 'max' / getOption("max.print") -- omitted 22 rows ]
简单操作:行列操作、连接和调整
dplyr列操作
列操作包括选取(select)、排序(relocate)、重命名(rename)和编辑(mutate),列操作不会改变行的数量或顺序。
select用于从表中选取部分列。其参数格式为select(colname)或select(new_name=old_name),只有参数中指定的列会被保留。
r
iris %>% select(Species, Width = Petal.Width, Length = Petal.Length)
plaintext
Species Width Length
1 setosa 0.2 1.4
2 setosa 0.2 1.4
3 setosa 0.2 1.3
4 setosa 0.2 1.5
5 setosa 0.2 1.4
6 setosa 0.4 1.7
7 setosa 0.3 1.4
8 setosa 0.2 1.5
9 setosa 0.2 1.4
10 setosa 0.1 1.5
[ reached 'max' / getOption("max.print") -- omitted 140 rows ]
relocate用于改变列的顺序,而不改变列的数目。其参数格式和select类似,为relocate(colname)或relocate(new_name=old_name)。.before/.after参数可以指定要移动到的位置,如果没有指定,参数中指定的列将会被移动到表的最前面(左侧)。
r
iris %>% relocate(epithet=Species)
plaintext
epithet Sepal.Length Sepal.Width Petal.Length Petal.Width
1 setosa 5.1 3.5 1.4 0.2
2 setosa 4.9 3.0 1.4 0.2
3 setosa 4.7 3.2 1.3 0.2
4 setosa 4.6 3.1 1.5 0.2
5 setosa 5.0 3.6 1.4 0.2
6 setosa 5.4 3.9 1.7 0.4
7 setosa 4.6 3.4 1.4 0.3
8 setosa 5.0 3.4 1.5 0.2
9 setosa 4.4 2.9 1.4 0.2
10 setosa 4.9 3.1 1.5 0.1
[ reached 'max' / getOption("max.print") -- omitted 140 rows ]
rename用于对列进行重命名,而不会改变列的数量和位置,其参数格式为rename(new_name=old_name)。
r
iris %>% rename(V1=Sepal.Length, V2=Sepal.Width)
plaintext
V1 V2 Petal.Length Petal.Width Species
1 5.1 3.5 1.4 0.2 setosa
2 4.9 3.0 1.4 0.2 setosa
3 4.7 3.2 1.3 0.2 setosa
4 4.6 3.1 1.5 0.2 setosa
5 5.0 3.6 1.4 0.2 setosa
6 5.4 3.9 1.7 0.4 setosa
7 4.6 3.4 1.4 0.3 setosa
8 5.0 3.4 1.5 0.2 setosa
9 4.4 2.9 1.4 0.2 setosa
10 4.9 3.1 1.5 0.1 setosa
[ reached 'max' / getOption("max.print") -- omitted 140 rows ]
mutate用于创建新列,也可以用来修改或移除已有的列。其参数格式为mutate(new_col=fn(old_col1, old_col2,...)),其中old_col<n>是列名,其值会作为向量传入函数fn,fn的计算结果则作为新列new_col的值。fn结果向量的长度应当与表的行数一致或为1。如果新列名new_col已存在,那么列值会被覆盖。值被赋为NULL的列会则被移除。
r
iris %>% mutate(Genus="Iris", epithet=Species, Species=paste(Genus, epithet))
plaintext
Sepal.Length Sepal.Width Petal.Length Petal.Width Species Genus epithet
1 5.1 3.5 1.4 0.2 Iris setosa Iris setosa
2 4.9 3.0 1.4 0.2 Iris setosa Iris setosa
3 4.7 3.2 1.3 0.2 Iris setosa Iris setosa
4 4.6 3.1 1.5 0.2 Iris setosa Iris setosa
5 5.0 3.6 1.4 0.2 Iris setosa Iris setosa
6 5.4 3.9 1.7 0.4 Iris setosa Iris setosa
7 4.6 3.4 1.4 0.3 Iris setosa Iris setosa
8 5.0 3.4 1.5 0.2 Iris setosa Iris setosa
9 4.4 2.9 1.4 0.2 Iris setosa Iris setosa
10 4.9 3.1 1.5 0.1 Iris setosa Iris setosa
[ reached 'max' / getOption("max.print") -- omitted 140 rows ]
dplyr行操作
行操作包括筛选(filter)、排序(arrange)、分片(slice)、汇总(summarize)和重构(reframe),以及简写操作计数(count和tally)和去重(distinct)
filter用于筛选满足指定条件的列。其参数为filter(fn(col1,col2,...)),其中col<n>是列名,其值会作为向量传入函数fn,只有fn的计算结果为TRUE的列会被保留。
r
iris %>% filter(Species=="virginica")
plaintext
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 6.3 3.3 6.0 2.5 virginica
2 5.8 2.7 5.1 1.9 virginica
3 7.1 3.0 5.9 2.1 virginica
4 6.3 2.9 5.6 1.8 virginica
5 6.5 3.0 5.8 2.2 virginica
6 7.6 3.0 6.6 2.1 virginica
7 4.9 2.5 4.5 1.7 virginica
8 7.3 2.9 6.3 1.8 virginica
9 6.7 2.5 5.8 1.8 virginica
10 7.2 3.6 6.1 2.5 virginica
[ reached 'max' / getOption("max.print") -- omitted 40 rows ]
arrange用于按指定条件排序行。其参数格式为arrange(fn(...)),fn的计算结果将作为排序的依据。
r
iris %>% arrange(desc(Species), -Sepal.Length)
plaintext
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 7.9 3.8 6.4 2.0 virginica
2 7.7 3.8 6.7 2.2 virginica
3 7.7 2.6 6.9 2.3 virginica
4 7.7 2.8 6.7 2.0 virginica
5 7.7 3.0 6.1 2.3 virginica
6 7.6 3.0 6.6 2.1 virginica
7 7.4 2.8 6.1 1.9 virginica
8 7.3 2.9 6.3 1.8 virginica
9 7.2 3.6 6.1 2.5 virginica
10 7.2 3.2 6.0 1.8 virginica
[ reached 'max' / getOption("max.print") -- omitted 140 rows ]
slice用于选取指定的行,参数格式为slice(fn(...)),fn的计算结果应当为一个整数向量,对应行数的行将会被保留。除了slice外,还有取首行(slice_head)、取尾行(slice_tail)、取随机行(slice_sample)、取最大行(slice_max)和取最小行(slice_min)几个常用的辅助函数
r
iris %>% slice_max(Petal.Width, n=10)
plaintext
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 6.3 3.3 6.0 2.5 virginica
2 7.2 3.6 6.1 2.5 virginica
3 6.7 3.3 5.7 2.5 virginica
4 5.8 2.8 5.1 2.4 virginica
5 6.3 3.4 5.6 2.4 virginica
6 6.7 3.1 5.6 2.4 virginica
7 6.4 3.2 5.3 2.3 virginica
8 7.7 2.6 6.9 2.3 virginica
9 6.9 3.2 5.7 2.3 virginica
10 7.7 3.0 6.1 2.3 virginica
[ reached 'max' / getOption("max.print") -- omitted 4 rows ]
summarise用于将多行汇总为一行。其参数格式为summarise(col_name=fn(col1,col2,...)),其中col<n>是原表格中的列名,函数fn的计算结果应当是一个长度为1的向量,col_name是汇总后的列名。
r
iris %>% summarise(Petal.Width.Max=max(Petal.Width),Petal.Width.Min=min(Petal.Width))
plaintext
Petal.Width.Max Petal.Width.Min
1 2.5 0.1
reframe与summarise类似,但是不限定fn计算结果的长度。如果fn计算结果的长度超过1,那么会生成多行。
r
iris %>% reframe(Petal.Width=quantile(Petal.Width),Petal.Length=quantile(Petal.Length))
plaintext
Petal.Width Petal.Length
1 0.1 1.00
2 0.3 1.60
3 1.3 4.35
4 1.8 5.10
5 2.5 6.90
dplyr表连接
r
# 示例数据
band_members
plaintext
# A tibble: 3 × 2
name band
<chr> <chr>
1 Mick Stones
2 John Beatles
3 Paul Beatles
r
band_instruments
plaintext
# A tibble: 3 × 2
name plays
<chr> <chr>
1 John guitar
2 Paul bass
3 Keith guitar
表连接函数包括根据指定列合并表的inner_join、left_join、right_join、full_join和生成所有条目对枚举(笛卡尔积)的cross_join,以及用于筛选数据的semi_join和anti_join。
r
band_members %>% inner_join(band_instruments)
plaintext
# A tibble: 2 × 3
name band plays
<chr> <chr> <chr>
1 John Beatles guitar
2 Paul Beatles bass
r
band_members %>% left_join(band_instruments)
plaintext
# A tibble: 3 × 3
name band plays
<chr> <chr> <chr>
1 Mick Stones <NA>
2 John Beatles guitar
3 Paul Beatles bass
r
band_members %>% right_join(band_instruments)
plaintext
# A tibble: 3 × 3
name band plays
<chr> <chr> <chr>
1 John Beatles guitar
2 Paul Beatles bass
3 Keith <NA> guitar
r
band_members %>% full_join(band_instruments)
plaintext
# A tibble: 4 × 3
name band plays
<chr> <chr> <chr>
1 Mick Stones <NA>
2 John Beatles guitar
3 Paul Beatles bass
4 Keith <NA> guitar
r
band_members %>% cross_join(band_instruments)
plaintext
# A tibble: 9 × 4
name.x band name.y plays
<chr> <chr> <chr> <chr>
1 Mick Stones John guitar
2 Mick Stones Paul bass
3 Mick Stones Keith guitar
4 John Beatles John guitar
5 John Beatles Paul bass
6 John Beatles Keith guitar
7 Paul Beatles John guitar
8 Paul Beatles Paul bass
9 Paul Beatles Keith guitar
r
band_members %>% semi_join(band_instruments)
plaintext
# A tibble: 2 × 2
name band
<chr> <chr>
1 John Beatles
2 Paul Beatles
r
band_members %>% anti_join(band_instruments)
plaintext
# A tibble: 1 × 2
name band
<chr> <chr>
1 Mick Stones
tidyr表调整
基础的表调整包括多列转单列的gather和单列转多列的spread。
gather可以将表格的列名转换为独立的列,同时保留列值和其他列数据的对应关系。其参数格式为gather("name_of_key_col", "name_of_value_col", cols_to_gather)
r
iris %>% gather("measurement","value",Sepal.Length:Petal.Width)
plaintext
Species measurement value
1 setosa Sepal.Length 5.1
2 setosa Sepal.Length 4.9
3 setosa Sepal.Length 4.7
4 setosa Sepal.Length 4.6
5 setosa Sepal.Length 5.0
6 setosa Sepal.Length 5.4
7 setosa Sepal.Length 4.6
8 setosa Sepal.Length 5.0
9 setosa Sepal.Length 4.4
10 setosa Sepal.Length 4.9
[ reached 'max' / getOption("max.print") -- omitted 590 rows ]
spread是gather的逆操作,能够根据指定的索引列,将一列数据转换为以索引列的值为列名的多列数据。其参数格式为spread(key_col, value_col)。spread不支持多列key_col或value_col,对于更高级的操作,参见pivot_wider
r
data.frame(Titanic) %>% spread(Class, Freq)
plaintext
Sex Age Survived 1st 2nd 3rd Crew
1 Male Child No 0 0 35 0
2 Male Child Yes 5 11 13 0
3 Male Adult No 118 154 387 670
4 Male Adult Yes 57 14 75 192
5 Female Child No 0 0 17 0
6 Female Child Yes 1 13 14 0
7 Female Adult No 4 13 89 3
8 Female Adult Yes 140 80 76 20
进阶操作:分组和批处理
批量处理
across用于在mutate、summarize和reframe中对多列应用同一函数
r
iris %>% mutate(across(Sepal.Length:Petal.Width, ~.x-mean(.x)))
plaintext
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 -0.7433333 0.44266667 -2.358 -0.9993333 setosa
2 -0.9433333 -0.05733333 -2.358 -0.9993333 setosa
3 -1.1433333 0.14266667 -2.458 -0.9993333 setosa
4 -1.2433333 0.04266667 -2.258 -0.9993333 setosa
5 -0.8433333 0.54266667 -2.358 -0.9993333 setosa
6 -0.4433333 0.84266667 -2.058 -0.7993333 setosa
7 -1.2433333 0.34266667 -2.358 -0.8993333 setosa
8 -0.8433333 0.34266667 -2.258 -0.9993333 setosa
9 -1.4433333 -0.15733333 -2.358 -0.9993333 setosa
10 -0.9433333 0.04266667 -2.258 -1.0993333 setosa
[ reached 'max' / getOption("max.print") -- omitted 140 rows ]
across默认不会修改列名,如果要修改列名可以通过.names参数指定,可以用"{.col}"和"{.fn}"指代列名和函数名。
r
iris %>% reframe(q=seq(0,1,0.25),across(
Sepal.Length:Petal.Width, list(quantile=~quantile(.x,q),percentage=~quantile(range(.x),q)), .names="{.col}_{.fn}"
))
plaintext
q Sepal.Length_quantile Sepal.Length_percentage Sepal.Width_quantile Sepal.Width_percentage
1 0.00 4.3 4.3 2.0 2.0
2 0.25 5.1 5.2 2.8 2.6
3 0.50 5.8 6.1 3.0 3.2
4 0.75 6.4 7.0 3.3 3.8
5 1.00 7.9 7.9 4.4 4.4
Petal.Length_quantile Petal.Length_percentage Petal.Width_quantile Petal.Width_percentage
1 1.00 1.000 0.1 0.1
2 1.60 2.475 0.3 0.7
3 4.35 3.950 1.3 1.3
4 5.10 5.425 1.8 1.9
5 6.90 6.900 2.5 2.5
if_any和if_all用于在filter中对多列应用同一函数,并对筛选结果取并集或交集
r
iris %>% filter(if_any(Sepal.Length:Petal.Width,~.x>median(.x)))
plaintext
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 5.1 3.5 1.4 0.2 setosa
2 4.7 3.2 1.3 0.2 setosa
3 4.6 3.1 1.5 0.2 setosa
4 5.0 3.6 1.4 0.2 setosa
5 5.4 3.9 1.7 0.4 setosa
6 4.6 3.4 1.4 0.3 setosa
7 5.0 3.4 1.5 0.2 setosa
8 4.9 3.1 1.5 0.1 setosa
9 5.4 3.7 1.5 0.2 setosa
10 4.8 3.4 1.6 0.2 setosa
[ reached 'max' / getOption("max.print") -- omitted 113 rows ]
r
iris %>% filter(if_all(Sepal.Length:Petal.Width,~.x>median(.x)))
plaintext
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 7.0 3.2 4.7 1.4 versicolor
2 6.4 3.2 4.5 1.5 versicolor
3 6.9 3.1 4.9 1.5 versicolor
4 6.3 3.3 4.7 1.6 versicolor
5 6.7 3.1 4.4 1.4 versicolor
6 5.9 3.2 4.8 1.8 versicolor
7 6.0 3.4 4.5 1.6 versicolor
8 6.7 3.1 4.7 1.5 versicolor
9 6.3 3.3 6.0 2.5 virginica
10 7.2 3.6 6.1 2.5 virginica
[ reached 'max' / getOption("max.print") -- omitted 15 rows ]
rename_with可以使用自定义函数对多列进行重命名
r
iris %>% rename_with(~str_replace(.x, "(.*)\\.(.).*", "\\2_\\1"))
plaintext
L_Sepal W_Sepal L_Petal W_Petal Species
1 5.1 3.5 1.4 0.2 setosa
2 4.9 3.0 1.4 0.2 setosa
3 4.7 3.2 1.3 0.2 setosa
4 4.6 3.1 1.5 0.2 setosa
5 5.0 3.6 1.4 0.2 setosa
6 5.4 3.9 1.7 0.4 setosa
7 4.6 3.4 1.4 0.3 setosa
8 5.0 3.4 1.5 0.2 setosa
9 4.4 2.9 1.4 0.2 setosa
10 4.9 3.1 1.5 0.1 setosa
[ reached 'max' / getOption("max.print") -- omitted 140 rows ]
多列赋值
通常,mutate函数的一个参数一次只能返回一列。如果想要一次返回多列,可以向将表转换为tibble对象,然后让fn返回一个data.frame:
r
as_tibble(iris) %>%
mutate(as.data.frame(prcomp(t(pick(Sepal.Length:Petal.Width)))$rotation))
plaintext
# A tibble: 150 × 9
Sepal.Length Sepal.Width Petal.Length Petal.Width Species PC1 PC2 PC3 PC4
<dbl> <dbl> <dbl> <dbl> <fct> <dbl> <dbl> <dbl> <dbl>
1 5.1 3.5 1.4 0.2 setosa -0.0771 -0.121 0.00327 -0.757
2 4.9 3 1.4 0.2 setosa -0.0754 -0.0995 0.0824 -0.282
3 4.7 3.2 1.3 0.2 setosa -0.0709 -0.110 0.0110 -0.0304
4 4.6 3.1 1.5 0.2 setosa -0.0701 -0.0968 -0.0312 -0.0110
5 5 3.6 1.4 0.2 setosa -0.0751 -0.124 -0.0436 0.0337
6 5.4 3.9 1.7 0.4 setosa -0.0785 -0.126 -0.0463 0.00279
7 4.6 3.4 1.4 0.3 setosa -0.0671 -0.112 -0.0580 0.00412
8 5 3.4 1.5 0.2 setosa -0.0760 -0.112 -0.0166 0.0501
9 4.4 2.9 1.4 0.2 setosa -0.0670 -0.0914 -0.00383 0.00380
10 4.9 3.1 1.5 0.1 setosa -0.0769 -0.0999 0.0104 0.0773
# ℹ 140 more rows
分组操作
mutate和行操作支持按特定列分组(group_by和rowwise,或by/.by参数),分组后每组会独立应用函数。
可以使用group_by将表格转换为grouped_df,对grouped_df的所有操作均会自动按组进行。ungroup可以取消分组。
r
iris %>% group_by(Species) %>%
summarize(mean.L_Sepal=mean(Sepal.Length), mean.L_Petal=mean(Petal.Length), n.Sample=n()) %>%
ungroup()
plaintext
# A tibble: 3 × 4
Species mean.L_Sepal mean.L_Petal n.Sample
<fct> <dbl> <dbl> <int>
1 setosa 5.01 1.46 50
2 versicolor 5.94 4.26 50
3 virginica 6.59 5.55 50
也可以在操作函数中直接指定.by参数
r
iris %>% reframe(q=seq(0,1,0.25), L_Sepal=quantile(Sepal.Length, q), .by=Species)
plaintext
Species q L_Sepal
1 setosa 0.00 4.3
2 setosa 0.25 4.8
3 setosa 0.50 5.0
4 setosa 0.75 5.2
5 setosa 1.00 5.8
6 versicolor 0.00 4.9
7 versicolor 0.25 5.6
8 versicolor 0.50 5.9
9 versicolor 0.75 6.3
10 versicolor 1.00 7.0
[ reached 'max' / getOption("max.print") -- omitted 5 rows ]
r
iris %>% filter(if_all(Sepal.Length:Petal.Width,~.x>median(.x)),.by=Species)
plaintext
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 5.4 3.9 1.7 0.4 setosa
2 5.7 3.8 1.7 0.3 setosa
3 5.1 3.8 1.9 0.4 setosa
4 7.0 3.2 4.7 1.4 versicolor
5 6.4 3.2 4.5 1.5 versicolor
6 6.9 3.1 4.9 1.5 versicolor
7 6.3 3.3 4.7 1.6 versicolor
8 6.1 2.9 4.7 1.4 versicolor
9 6.7 3.1 4.4 1.4 versicolor
10 6.6 3.0 4.4 1.4 versicolor
[ reached 'max' / getOption("max.print") -- omitted 12 rows ]
arrange函数默认忽略分组,且不支持.by/by参数。指定了.by_group=TRUE后,arrage会先对grouped_df的分组列进行排序,然后在各个组内分别按指定的列或计算结果进行排序。
r
data.frame(Titanic) %>% group_by(Class) %>% arrange(Freq, .by_group=TRUE)
plaintext
# A tibble: 32 × 5
# Groups: Class [4]
Class Sex Age Survived Freq
<fct> <fct> <fct> <fct> <dbl>
1 1st Male Child No 0
2 1st Female Child No 0
3 1st Female Child Yes 1
4 1st Female Adult No 4
5 1st Male Child Yes 5
6 1st Male Adult Yes 57
7 1st Male Adult No 118
8 1st Female Adult Yes 140
9 2nd Male Child No 0
10 2nd Female Child No 0
# ℹ 22 more rows
对grouped_df分组列的排序不支持自定义函数,如果要根据计算结果排列组,可以先用mutate生产索引列,或者利用ave函数:
r
data.frame(Titanic) %>% mutate(Class.Freq=sum(Freq),.by=Class) %>% arrange(Class.Freq)
plaintext
Class Sex Age Survived Freq Class.Freq
1 2nd Male Child No 0 285
2 2nd Female Child No 0 285
3 2nd Male Adult No 154 285
4 2nd Female Adult No 13 285
5 2nd Male Child Yes 11 285
6 2nd Female Child Yes 13 285
7 2nd Male Adult Yes 14 285
8 2nd Female Adult Yes 80 285
9 1st Male Child No 0 325
10 1st Female Child No 0 325
[ reached 'max' / getOption("max.print") -- omitted 22 rows ]
r
data.frame(Titanic) %>% arrange(ave(Freq,Class,FUN=sum))
plaintext
Class Sex Age Survived Freq
1 2nd Male Child No 0
2 2nd Female Child No 0
3 2nd Male Adult No 154
4 2nd Female Adult No 13
5 2nd Male Child Yes 11
6 2nd Female Child Yes 13
7 2nd Male Adult Yes 14
8 2nd Female Adult Yes 80
9 1st Male Child No 0
10 1st Female Child No 0
[ reached 'max' / getOption("max.print") -- omitted 22 rows ]
表调整
pivot_longer和pivot_wider可以实现比gather和spread更为精细和复杂的列转换操作。
pivot_longer的参数格式为pivot_longer(cols_to_gather, names_to="name_of_key_col", values_to="name_of_value_col"),另外还提供names_pattern、names_transform等可选参数对要转换的列名进行拆分和格式转换。
r
iris %>% pivot_longer(
Sepal.Length:Petal.Width,
names_to=c("component","measure"), names_pattern="(.*)\\.(.*)",
values_to=c("value")
)
plaintext
# A tibble: 600 × 4
Species component measure value
<fct> <chr> <chr> <dbl>
1 setosa Sepal Length 5.1
2 setosa Sepal Width 3.5
3 setosa Petal Length 1.4
4 setosa Petal Width 0.2
5 setosa Sepal Length 4.9
6 setosa Sepal Width 3
7 setosa Petal Length 1.4
8 setosa Petal Width 0.2
9 setosa Sepal Length 4.7
10 setosa Sepal Width 3.2
# ℹ 590 more rows
如果只想将列名中的一部分内容转移为新行,可以使用names_to中的保留字".value"。匹配该部分的内容将继续保留为列名。
r
iris %>% pivot_longer(
Sepal.Length:Petal.Width,
names_to=c("component",".value"), names_pattern="(.*)\\.(.*)"
)
plaintext
# A tibble: 300 × 4
Species component Length Width
<fct> <chr> <dbl> <dbl>
1 setosa Sepal 5.1 3.5
2 setosa Petal 1.4 0.2
3 setosa Sepal 4.9 3
4 setosa Petal 1.4 0.2
5 setosa Sepal 4.7 3.2
6 setosa Petal 1.3 0.2
7 setosa Sepal 4.6 3.1
8 setosa Petal 1.5 0.2
9 setosa Sepal 5 3.6
10 setosa Petal 1.4 0.2
# ℹ 290 more rows
pivot_wider的参数格式为pivot_wider(id_cols=id_col, names_from=key_col, values_from=value_col),其中id_col是转换后仍要保留的列(如果留空,则使用key_col和value_col以外所有的列),key_col是要转换为列名的列,value_col列的数据则会转换为对应列的值。可选参数names_glue可以设置列名格式,values_fill可以设置缺失值的替换值。
r
data.frame(Titanic) %>% pivot_wider(
names_from=c(Sex,Class), names_glue="{Sex}({Class})",
values_from=Freq
)
plaintext
# A tibble: 4 × 10
Age Survived `Male(1st)` `Male(2nd)` `Male(3rd)` `Male(Crew)` `Female(1st)` `Female(2nd)`
<fct> <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 Child No 0 0 35 0 0 0
2 Adult No 118 154 387 670 4 13
3 Child Yes 5 11 13 0 1 13
4 Adult Yes 57 14 75 192 140 80
# ℹ 2 more variables: `Female(3rd)` <dbl>, `Female(Crew)` <dbl>
如果一组id_col和key_col对应于多条value_col,可以通过values_fn参数指定数据的汇总方式
r
data.frame(Titanic) %>% pivot_wider(
id_cols=c(Sex,Survived),
names_from=Class,
values_from=Freq, values_fn=sum
)
plaintext
# A tibble: 4 × 6
Sex Survived `1st` `2nd` `3rd` Crew
<fct> <fct> <dbl> <dbl> <dbl> <dbl>
1 Male No 118 154 422 670
2 Female No 4 13 106 3
3 Male Yes 62 25 88 192
4 Female Yes 141 93 90 20
高级用法:变量替换
如前文所见,dplyr函数的一大特征是:在函数中,列名可以像变量一样使用。这为简单操作带来方便的同时,也给更进一步的高级操作带来了很大的麻烦。
一个最大的问题是:如何告诉函数某个变量指代的是列名还是来自全局环境中的变量呢?
这样的操作主要通过rlang包来实现。
预备知识:<data-masking>和<tidy-select>
在进一步演示之前,先指出dplyr中的两种看起来相似但又略有不同的参数类型:<data-masking>和<tidy-select>。
<data-masking>被用于filter()、mutate()、summarize()、reframe()、arrange()、slice()、count()、tally()、distinct()等可以对列做计算的函数中,用于将列作为参数传递给函数;而<tidy-select>则主要被用在select()、rename()、relocate()、gather()、spread()函数,以及前述函数的.by参数中,用于对列进行选取。
我们首先介绍相对简单的<tidy-select>的用法。
<tidy-select>选取列的语法有两种风格,一种类似于base-r中的向量操作,另一种类似于集合操作符。
base-r向量操作风格:表示连续范围c()表示并集-表示排除
- 集合操作风格
&表示交集|表示并集!表示排除
这些算符/函数既接受数字,也接受列名:
r
storms %>% select(c(2:5, status:pressure&!category), 1, latitude = lat, longitude = long)
plaintext
# A tibble: 19,537 × 10
year month day hour status wind pressure name latitude longitude
<dbl> <dbl> <int> <dbl> <fct> <int> <int> <chr> <dbl> <dbl>
1 1975 6 27 0 tropical depression 25 1013 Amy 27.5 -79
2 1975 6 27 6 tropical depression 25 1013 Amy 28.5 -79
3 1975 6 27 12 tropical depression 25 1013 Amy 29.5 -79
4 1975 6 27 18 tropical depression 25 1013 Amy 30.5 -79
5 1975 6 28 0 tropical depression 25 1012 Amy 31.5 -78.8
6 1975 6 28 6 tropical depression 25 1012 Amy 32.4 -78.7
7 1975 6 28 12 tropical depression 25 1011 Amy 33.3 -78
8 1975 6 28 18 tropical depression 30 1006 Amy 34 -77
9 1975 6 29 0 tropical storm 35 1004 Amy 34.4 -75.8
10 1975 6 29 6 tropical storm 40 1002 Amy 34 -74.8
# ℹ 19,527 more rows
如果<tidy-select>以...的形式传入函数(如select()、relocate())中,,分隔项会自动用|(并)连接;如果<tidy-select>以单个参数的形式传入函数,则需要用c()或|将各项手动连起来。另外,<tidy-select>,支持用=修改选取列的列名,而且会保留列的顺序,因此也可以用来对列进行重命名和重排。
<tidy-select>还支持一些函数来指定要选取的列,包括:
| 函数 | 作用 |
|---|---|
everything() |
选取所有列 |
last_col(n) |
选取倒数第n+1列 |
starts_with("pattern") |
列名以"pattern"开头的列 |
ends_with("pattern") |
列名以"pattern"结尾的列 |
contains("pattern") |
列名包含"pattern"的列 |
matches("pattern") |
列名匹配正则表达式regex("pattern")的列 |
where(fn) |
fn(列内容)为TRUE的列 |
注意与其他函数不同,where可以通过列的内容 而不是列名或者顺序来选取列,以下是一个where函数的使用示例:
r
airquality %>% select(where(~ is.integer(.x) && !any(is.na(.x))))
plaintext
Temp Month Day
1 67 5 1
2 72 5 2
3 74 5 3
4 62 5 4
5 56 5 5
6 66 5 6
7 65 5 7
8 59 5 8
9 61 5 9
10 69 5 10
[ reached 'max' / getOption("max.print") -- omitted 143 rows ]
其中~和.x是purrr匿名函数简写格式,详细可见?purrr::as_mapper。
<data-masking>则是一个(或多个)以列名为变量名的表达式,用于对列表进行修改或汇总。
在<data-masking>表达式中,列名指定的列将以向量的形式传入函数或算符中参与运算。
例如,
r
iris %>% mutate(LW.ratio_Petal = Petal.Length / Petal.Width)
plaintext
Sepal.Length Sepal.Width Petal.Length Petal.Width Species LW.ratio_Petal
1 5.1 3.5 1.4 0.2 setosa 7.000000
2 4.9 3.0 1.4 0.2 setosa 7.000000
3 4.7 3.2 1.3 0.2 setosa 6.500000
4 4.6 3.1 1.5 0.2 setosa 7.500000
5 5.0 3.6 1.4 0.2 setosa 7.000000
6 5.4 3.9 1.7 0.4 setosa 4.250000
7 4.6 3.4 1.4 0.3 setosa 4.666667
8 5.0 3.4 1.5 0.2 setosa 7.500000
9 4.4 2.9 1.4 0.2 setosa 7.000000
10 4.9 3.1 1.5 0.1 setosa 15.000000
[ reached 'max' / getOption("max.print") -- omitted 140 rows ]
就是计算iris$Petal.Length/iris$Petal.Width的结果并保存到LW.ratio_Petal列中。
如果使用的是一些非向量化的函数,那么得到会得到一些非预期的结果:
r
iris %>% mutate(new_col=sum(Petal.Length, Petal.Width))
plaintext
Sepal.Length Sepal.Width Petal.Length Petal.Width Species new_col
1 5.1 3.5 1.4 0.2 setosa 743.6
2 4.9 3.0 1.4 0.2 setosa 743.6
3 4.7 3.2 1.3 0.2 setosa 743.6
4 4.6 3.1 1.5 0.2 setosa 743.6
5 5.0 3.6 1.4 0.2 setosa 743.6
6 5.4 3.9 1.7 0.4 setosa 743.6
7 4.6 3.4 1.4 0.3 setosa 743.6
8 5.0 3.4 1.5 0.2 setosa 743.6
9 4.4 2.9 1.4 0.2 setosa 743.6
10 4.9 3.1 1.5 0.1 setosa 743.6
[ reached 'max' / getOption("max.print") -- omitted 140 rows ]
可以看到,new_col的值均为743.6,这是因为实际进行的运算变成了sum(iris$Petal.Length, iris$Petal.Width),得到的单一值被填充为一列,得到了全为743.6的new_col
通过辅助函数c_across()和pick()可以在<data-masking>中以<tidy-select>的格式选取列,前者会选取列拼接为向量,而后者会选取列为表:
r
iris %>% reframe(
len_c_across=length(c_across(c(Petal.Length,Petal.Width))),
nrow_pick=nrow(pick(Petal.Length,Petal.Width)),
ncol_pick=ncol(pick(Petal.Length,Petal.Width)),
.by=Species)
plaintext
Species len_c_across nrow_pick ncol_pick
1 setosa 100 50 2
2 versicolor 100 50 2
3 virginica 100 50 2
在<tidy-select>中进行变量替换
<tidy-select>通常接受"裸露"的列名作为参数(如iris %>% select(Species, Petal.Width)中的Species和Petal.Width)。如果列名存储在变量中,则需要借助辅助函数或通过"注入"的方式将变量中的内容插入到参数中。
变量可以分为普通变量(var)和函数参数(arg)两种,后者还包括值传递(f("str")中的"str")、变量传递(f(var)中的var)和字面量("裸露"的列名)三种情况。此外<tidy-select>还可以接受列号(数字)作为参数。分别可以通过不同的方式将这些内容插入到<tidy-select>参数中:
r
var1 <- 4 # 普通变量,列号(数字)
sp <- "Species" # 普通变量,列名(字符串)
iris %>% select(any_of(var1), !!sp)
plaintext
Petal.Width Species
1 0.2 setosa
2 0.2 setosa
3 0.2 setosa
4 0.2 setosa
5 0.2 setosa
6 0.4 setosa
7 0.3 setosa
8 0.2 setosa
9 0.2 setosa
10 0.1 setosa
[ reached 'max' / getOption("max.print") -- omitted 140 rows ]
r
(function(arg1, arg2, arg3, arg4){
iris %>% select(enexpr(arg1), all_of(arg2), sym(arg3), {{arg4}})
})(2, var1, sp, Sepal.Length) # 函数参数,分别为 值传递列号(数字),变量传递列号(数字),变量传递列名(字符串),字面量
plaintext
Sepal.Width Petal.Width Species Sepal.Length
1 3.5 0.2 setosa 5.1
2 3.0 0.2 setosa 4.9
3 3.2 0.2 setosa 4.7
4 3.1 0.2 setosa 4.6
5 3.6 0.2 setosa 5.0
6 3.9 0.4 setosa 5.4
7 3.4 0.3 setosa 4.6
8 3.4 0.2 setosa 5.0
9 2.9 0.2 setosa 4.4
10 3.1 0.1 setosa 4.9
[ reached 'max' / getOption("max.print") -- omitted 140 rows ]
虽然看上去花样很多,但实际用到的只有三类语法,分别是:
tidyselect::辅助函数any_of()和all_of()rlang引用格式sym()、ensym()、enexpr()和注入符!!- 以及这一格式的简写形式
{{ }}
- 以及这一格式的简写形式
any_of()和all_of()与前面提到的starts_with()、ends_with()等函数类似,接受一个字符串向量并选取相应列名的列。如果列名不存在于表中,any_of()会忽略这些列,而all_of()则会报错。
sym()、ensym()和enexpr()都是expr()的变体,它们都属于rlang的defuse函数家族。这个函数家族主要用于以字面形式存储表达式(而不是运行后的结果)。例如,expr(1+1)返回+, 1, 1,而不是1+1的结果2。defuse后的表达式可以赋值给变量,用来传递给其他函数,或者在需要的时候运行(通过eval()函数)。
sym()的功能是将字符串转化为符号。符号是一类简单的表达式,它不包含任何运算,仅仅指代一个字面值。例如,sym("sp")的结果为sp,而sym(sp)的结果则为Species(因为之前给sp赋值了sp <- "Species")。注意,即使传入的字符串中包含了函数或运算,也会被强行转化为符号,而不是运算式。
enexpr()的功能是将函数参数转化为表达式。例如,(function(arg){expr(arg)})(1+1)返回的结果是arg,而(function(arg){enexpr(arg)})(1+1)则返回+, 1, 1。ensym()与enexpr()类似,但要求参数的内容本身是一个符号(如(function(arg){enexpr(arg)})(var)得到var,而(function(arg){enexpr(arg)})(1+1)会报错);ensym()也支持传入参数的内容是字符串,所以在一些情况下可以和sym()通用。
"注入符"!!在向函数传递变量时使用,功能是将变量的内容注入变量所在位置。例如,(function(arg){ensym(arg)})(!!sp)会得到(function(arg){ensym(arg)})(!!sp),而不是(function(arg){ensym(arg)})(sp)的结果sp。这可以用来向一些默认接受变量字面量的函数传递变量的内容。
{{ }}是!!enexpr()的简写,可以简化书写,但有些情况下会导致不需要的注入。
下面的表格展示了各种情况下可以使用的变量替换操作:
变量类型(<-表示变量,=表示函数参数) |
辅助函数 | 表达式 | 注入 | {{ }} |
|---|---|---|---|---|
var <- "Species" |
all_of(var) |
sym(var) |
!!var |
{{var}} |
var <- 5 |
all_of(var) |
enexpr(var) |
!!var |
{{var}} |
arg = "Species" |
all_of(arg) |
sym(arg) |
!!arg |
{{arg}} |
arg = 5 |
all_of(arg) |
enexpr(arg) |
!!arg |
{{arg}} |
var <- "Species";arg = var |
all_of(arg) |
sym(arg) |
!!arg |
any_of({{arg}}) |
var <- 5;arg = var |
all_of(arg) |
enexpr(arg) |
!!arg |
any_of({{arg}}) |
arg = Species |
/ | ensym(arg) |
/ | {{arg}} |
在<data-masking>中进行变量替换
在<data-masking>中注入变量名的主要用到的函数是c_across(),该函数接受一个<tidy-select>表达式,将其转换为一组<data-masking>列。
虽然这不是c_across()最初被设计用作的目的(它最初是用来配合rowwise()让一些非向量化函数实现向量化操作的),但是确是一种有效的手段。
前面提到,c_across()会将选取列按向量方式拼接。因此,如果c_across()选取了超过一列,其返回结果的长度也会变成多倍,因此选取前最好确认一下需要替换的变量只包含一列的列号/列名。
另一种做法是利用sym()/ensym()和!!,与<tidy-select>可以灵活使用引用格式和注入符号不同,在<data-masking>中两者必须连用,而且只支持列名:
r
iris %>% transmute(!!sym(sp))
plaintext
Species
1 setosa
2 setosa
3 setosa
4 setosa
5 setosa
6 setosa
7 setosa
8 setosa
9 setosa
10 setosa
[ reached 'max' / getOption("max.print") -- omitted 140 rows ]
r
(function(arg){
iris %>% transmute(!!ensym(arg))
})(Petal.Length)
plaintext
Petal.Length
1 1.4
2 1.4
3 1.3
4 1.5
5 1.4
6 1.7
7 1.4
8 1.5
9 1.4
10 1.5
[ reached 'max' / getOption("max.print") -- omitted 140 rows ]
!!ensym()/!!enexpr()和{{ }}格式不支持直接通过列号选取列,而且如果传入的是字符串变量,可能会得到不正确的结果(而且不会报错):
r
iris %>% transmute(!!enexpr(sp))
plaintext
"Species"
1 Species
2 Species
3 Species
4 Species
5 Species
6 Species
7 Species
8 Species
9 Species
10 Species
[ reached 'max' / getOption("max.print") -- omitted 140 rows ]
r
(function(arg1, arg2, arg3){
iris %>% transmute(!!enexpr(arg1), !!enexpr(arg2), !!enexpr(arg3))
})(var1, sp, Petal.Width)
plaintext
var1 sp Petal.Width
1 4 Species 0.2
2 4 Species 0.2
3 4 Species 0.2
4 4 Species 0.2
5 4 Species 0.2
6 4 Species 0.4
7 4 Species 0.3
8 4 Species 0.2
9 4 Species 0.2
10 4 Species 0.1
[ reached 'max' / getOption("max.print") -- omitted 140 rows ]
如果传入!!enexpr()的是字面列名,但列名同时和一个变量名冲突(如Petal.Width<-"Petal.Length"),则列名会被优先解析(所以用Petal.Width传入列名的做法是安全的)。但如果传入!!enexpr()的是一个字符串变量,那么变量的内容会被注入,如果想要按照变量的内容选取列,请使用!!ensym()或c_across()。
下面的表格展示了各种情况下可以使用的变量替换操作:
变量类型(<-表示变量,=表示函数参数) |
c_across() |
表达式注入 |
|---|---|---|
var <- "Species" |
c_across({{var}}) |
!!sym(var) |
var <- 5 |
c_across({{var}}) |
/ |
arg = "Species" |
c_across({{arg}}) |
!!sym(arg) |
arg = 5 |
c_across({{arg}}) |
/ |
var <- "Species";arg = var |
c_across(any_of(arg)) |
/ |
var <- 5;arg = var |
c_across(any_of(arg)) |
/ |
arg = Species |
/ | !!ensym(arg) |
针对参数名的变量替换
变量替换的最后一种情况是用来指定新列的名称,可以用在rename()、select()、mutate()、summarize()、reframe()等支持。这可以通过引用注入(及简写{{ }})或glue字符串两种格式实现。
glue是一类模板字符串,用{}括起的变量名标记变量应当插入的位置。({}内也支持表达式,详见?glue::glue)
当列名需要通过变量替换指定时,赋值符号需要修改为:=。
r
new_col <- "L_Petal"
iris %>% select("{str_to_lower(sp)}_name" := Species, {{new_col}} := Petal.Length)
plaintext
species_name L_Petal
1 setosa 1.4
2 setosa 1.4
3 setosa 1.3
4 setosa 1.5
5 setosa 1.4
6 setosa 1.7
7 setosa 1.4
8 setosa 1.5
9 setosa 1.4
10 setosa 1.5
[ reached 'max' / getOption("max.print") -- omitted 140 rows ]
r
(function(arg1, arg2, arg3, arg4){
iris %>% select({{arg1}} := 5, !!ensym(arg2) := 1, "{arg1}" := Sepal.Width, !!sym(arg4) := 3)
# arg1会被替换为"sp",而不是"Species"
})(sp, L_Sepal, "W_Sepal", new_col)
plaintext
sp L_Sepal Species L_Petal
1 setosa 5.1 3.5 1.4
2 setosa 4.9 3.0 1.4
3 setosa 4.7 3.2 1.3
4 setosa 4.6 3.1 1.5
5 setosa 5.0 3.6 1.4
6 setosa 5.4 3.9 1.7
7 setosa 4.6 3.4 1.4
8 setosa 5.0 3.4 1.5
9 setosa 4.4 2.9 1.4
10 setosa 4.9 3.1 1.5
[ reached 'max' / getOption("max.print") -- omitted 140 rows ]
注意,由于!!ensym()和{{ }}优先解析字面量,因此传入函数的变量名("sp")而不是变量的值"Species"会被使用。如果要注入变量的内容,请使用!!sym()。
变量类型(<-表示变量,=表示函数参数) |
glue字符串 |
表达式注入 | {{ }} |
|---|---|---|---|
var <- "Species" |
"{var}" |
!!sym(var) |
{{var}} |
arg = "Species" |
"{arg}" |
!!sym(arg) |
{{arg}} |
var <- "Species";arg = var |
"{arg}" |
!!sym(arg) |
/ |
arg = Species |
/ | !!ensym(arg) |
{{arg}} |
另一种做法
pipe支持通过代码块执行复杂的运算。
r
storms %>% filter(any(wind>=160), .by=c(name, year)) %>%
mutate(id=paste(year, name)) %>% {
id <- unique(.$id)
n <- length(id)
title <- paste("tracks for storms", paste0(id[-n], collapse = ", "), "and", id[n])
ggplot(., aes(lat, long)) +
geom_point(aes(color=id, size=wind)) +
geom_path(aes(group=id)) +
ggtitle(title)
}
在这个例子中,函数ggplot()和``ggtitle()都需要用到由%>%传递过来的数据,但无法在一个管道符表达式中写下,此时就可以通过代码块来实现。在代码块中,可以(也需要)用.`显式指定传入的数据,返回的是最后一句代码运行的结果。
%T>%是一类特殊的的管道符,与普通的%>%不同,它在函数执行后返回的不是函数执行的结果,而是数据本身。这一操作符通常用来在pipe的中间打印数据或绘图。与代码块配合后可以简写对数据的原地修改。
r
iris %T>%
{.$species_Name <- .[,sp]} %T>% # 相当于 mutate(species_Name=!!sym(sp))
{.[,sp] <- NULL} %T>% # 相当于 mutate({{sp}}:=NULL)
{. <- .[, 3:5]} # 相当于 select(3:5)
plaintext
Petal.Length Petal.Width species_Name
1 1.4 0.2 setosa
2 1.4 0.2 setosa
3 1.3 0.2 setosa
4 1.5 0.2 setosa
5 1.4 0.2 setosa
6 1.7 0.4 setosa
7 1.4 0.3 setosa
8 1.5 0.2 setosa
9 1.4 0.2 setosa
10 1.5 0.1 setosa
[ reached 'max' / getOption("max.print") -- omitted 140 rows ]
代码块不会修改iris本身的值,只会修改每次%T>%传递过来的值。上述的每个代码块都用base-r格式进行了mutate或select操作。该做法不局限于用来进行tidyverse操作,具有很高的自由度。