2.5 分组操作
group_by()
是最重要的分组动词,需要一个数据框和一个或多个变量进行分组:
详情: https://cloud.r-project.org/web/packages/dplyr/vignettes/grouping.html
2.5.1 添加分组
<- starwars %>% group_by(species)
by_species <- starwars %>% group_by(sex, gender) by_sex_gender
在添加分组后,print()打印时可以看到分组:
by_species#> # A tibble: 87 x 14
#> # Groups: species [38]
#> name height mass hair_color skin_color eye_color birth_year sex gender
#> <chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
#> 1 Luke Sk~ 172 77 blond fair blue 19 male mascu~
#> 2 C-3PO 167 75 <NA> gold yellow 112 none mascu~
#> 3 R2-D2 96 32 <NA> white, bl~ red 33 none mascu~
#> 4 Darth V~ 202 136 none white yellow 41.9 male mascu~
#> 5 Leia Or~ 150 49 brown light brown 19 fema~ femin~
#> 6 Owen La~ 178 120 brown, grey light blue 52 male mascu~
#> # ... with 81 more rows, and 5 more variables: homeworld <chr>, species <chr>,
#> # films <list>, vehicles <list>, starships <list>
计算每个分组的行数,可以通过sort
参数控制排序方式。
%>% tally()
by_species #> # A tibble: 38 x 2
#> species n
#> <chr> <int>
#> 1 Aleena 1
#> 2 Besalisk 1
#> 3 Cerean 1
#> 4 Chagrian 1
#> 5 Clawdite 1
#> 6 Droid 6
#> # ... with 32 more rows
%>% tally(sort = TRUE)
by_sex_gender #> # A tibble: 6 x 3
#> # Groups: sex [5]
#> sex gender n
#> <chr> <chr> <int>
#> 1 male masculine 60
#> 2 female feminine 16
#> 3 none masculine 5
#> 4 <NA> <NA> 4
#> 5 hermaphroditic masculine 1
#> 6 none feminine 1
在数据探索时比较有用。
除了按照现有变量分组外,还可以按照函数处理后的变量分组,等效在mutate()
之后执行group_by
:
<- c(0, 18.5, 25, 30, Inf)
bmi_breaks %>%
starwars group_by(bmi_cat = cut(mass/(height/100)^2, breaks=bmi_breaks)) %>%
tally()
#> # A tibble: 5 x 2
#> bmi_cat n
#> <fct> <int>
#> 1 (0,18.5] 10
#> 2 (18.5,25] 24
#> 3 (25,30] 13
#> 4 (30,Inf] 12
#> 5 <NA> 28
group_by()可通过在group_by()计算产生的新字段分组
2.5.2 查看分组
使用group_keys()
查看数据的分组,每个组一行,每个分组变量占一列:
%>% group_keys()
by_species #> # A tibble: 38 x 1
#> species
#> <chr>
#> 1 Aleena
#> 2 Besalisk
#> 3 Cerean
#> 4 Chagrian
#> 5 Clawdite
#> 6 Droid
#> # ... with 32 more rows
%>% group_keys()
by_sex_gender #> # A tibble: 6 x 2
#> sex gender
#> <chr> <chr>
#> 1 female feminine
#> 2 hermaphroditic masculine
#> 3 male masculine
#> 4 none feminine
#> 5 none masculine
#> 6 <NA> <NA>
也可以使用命令group_indices()
查看每行属于哪个组:
%>% group_indices()
by_species #> [1] 11 6 6 11 11 11 11 6 11 11 11 11 34 11 24 12 11 11 36 11 11 6 31 11 11
#> [26] 18 11 11 8 26 11 21 11 10 10 10 38 30 7 38 11 37 32 32 33 35 29 11 3 20
#> [51] 37 27 13 23 16 4 11 11 11 9 17 17 11 11 11 11 5 2 15 15 11 1 6 25 19
#> [76] 28 14 34 11 38 22 11 11 11 6 38 11
该特性方便增加组别列。
<- tibble(a = c('a','a','a','b','b','b','d','e','f'))
df %>%
df group_by(a) %>%
mutate(组别列 = group_indices() )
#> Warning: `group_indices()` was deprecated in dplyr 1.0.0.
#> Please use `cur_group_id()` instead.
#> # A tibble: 9 x 2
#> # Groups: a [5]
#> a 组别列
#> <chr> <int>
#> 1 a 1
#> 2 a 1
#> 3 a 1
#> 4 b 2
#> 5 b 2
#> 6 b 2
#> # ... with 3 more rows
上述用法在dplyr 1.0.0 中弃用,用cur_group_id()
代替,如下所示:
%>%
df group_by(a) %>%
mutate(组别列 = cur_group_id())
#> # A tibble: 9 x 2
#> # Groups: a [5]
#> a 组别列
#> <chr> <int>
#> 1 a 1
#> 2 a 1
#> 3 a 1
#> 4 b 2
#> 5 b 2
#> 6 b 2
#> # ... with 3 more rows
dplyr 在我看来 API 变化较快,所以我个人习惯使用 data.table 包处理数据。所以在学习的时候用最新的版本学习。
group_rows()
每个组包含哪些行:
%>% group_rows() %>% head()
by_species #> <list_of<integer>[6]>
#> [[1]]
#> [1] 72
#>
#> [[2]]
#> [1] 68
#>
#> [[3]]
#> [1] 49
#>
#> [[4]]
#> [1] 56
#>
#> [[5]]
#> [1] 67
#>
#> [[6]]
#> [1] 2 3 8 22 73 85
group_vars()
返回分组变量的名称,请使用:
%>% group_vars()
by_species #> [1] "species"
%>% group_vars()
by_sex_gender #> [1] "sex" "gender"
2.5.3 更改和添加分组变量
如果将group_by()
应用已经分组的数据集,将覆盖现有的分组变量。例如,下面的分组变量是homeworld
而不是之前的species
。
%>%
by_species group_by(homeworld) %>%
tally()
#> # A tibble: 49 x 2
#> homeworld n
#> <chr> <int>
#> 1 Alderaan 3
#> 2 Aleen Minor 1
#> 3 Bespin 1
#> 4 Bestine IV 1
#> 5 Cato Neimoidia 1
#> 6 Cerea 1
#> # ... with 43 more rows
要增加分组变量,使用.add=TRUE
参数即可。例如:
%>%
by_species group_by(homeworld,.add = TRUE) %>%
tally()
#> # A tibble: 58 x 3
#> # Groups: species [38]
#> species homeworld n
#> <chr> <chr> <int>
#> 1 Aleena Aleen Minor 1
#> 2 Besalisk Ojom 1
#> 3 Cerean Cerea 1
#> 4 Chagrian Champala 1
#> 5 Clawdite Zolan 1
#> 6 Droid Naboo 1
#> # ... with 52 more rows
2.5.4 删除分组变量
要删除所有分组变量,使用ungroup()
:
%>%
by_species ungroup() %>%
tally()
#> # A tibble: 1 x 1
#> n
#> <int>
#> 1 87
还可以通过列出要删除的变量来有选择的删除分组变量:
%>%
by_sex_gender ungroup(sex) %>%
tally()
#> # A tibble: 3 x 2
#> gender n
#> <chr> <int>
#> 1 feminine 17
#> 2 masculine 66
#> 3 <NA> 4
2.5.5 动词影响
group_by()
是如何影响 dplyr 的主要动词。
- summarise
summarise()
计算每个组的汇总,表示从group_keys()
开始,在右侧添加summarise()
的汇总变量。
%>%
by_species summarise(
n = n(),
height = mean(height, na.rm = TRUE)
)#> # A tibble: 38 x 3
#> species n height
#> <chr> <int> <dbl>
#> 1 Aleena 1 79
#> 2 Besalisk 1 198
#> 3 Cerean 1 198
#> 4 Chagrian 1 196
#> 5 Clawdite 1 168
#> 6 Droid 6 131.
#> # ... with 32 more rows
该.groups=
参数控制输出的分组结构。删除右侧分组变量的历史行为对应于.groups =
“drop_last”没有提示消息,或.groups = NULL有消息(默认值)。
%>%
by_sex_gender summarise(n = n()) %>%
group_vars()
#> `summarise()` has grouped output by 'sex'. You can override using the `.groups` argument.
#> [1] "sex"
%>%
by_sex_gender summarise(n = n(),.groups = 'drop_last') %>%
group_vars()
#> [1] "sex"
从1.0.0版开始,分组信息可以保留(.groups = "keep")
或删除 (.groups = 'drop)
<- by_species %>%
a summarise(
n = n(),
height = mean(height, na.rm = TRUE),.groups='drop')
<- by_species %>%
b summarise(
n = n(),
height = mean(height, na.rm = TRUE),.groups='keep')
object.size(a)
#> 4088 bytes
object.size(b)
#> 10896 bytes
以上,可以看到保留分组信息的比没保留的对象大了两倍多,在实际使用中,当数据较大时会占据更多内存,所以我们需要根据实际情况决定是否保留分组信息,我在大部分时候都会删除分组信息。
- arrange
默认情况下,分组和不分组的数据集应用在在arrange()
效果相同。除非设置.by_group = TRUE
,这时首先按照分组变量排序。
<- tibble(a = c('a','b','a','a','d','b','b','b','d','e','f'),b=1:11) %>%
df group_by(a)
%>%
df arrange(desc(b))
#> # A tibble: 11 x 2
#> # Groups: a [5]
#> a b
#> <chr> <int>
#> 1 f 11
#> 2 e 10
#> 3 d 9
#> 4 b 8
#> 5 b 7
#> 6 b 6
#> # ... with 5 more rows
%>%
df arrange(desc(b),.by_group = TRUE)
#> # A tibble: 11 x 2
#> # Groups: a [5]
#> a b
#> <chr> <int>
#> 1 a 4
#> 2 a 3
#> 3 a 1
#> 4 b 8
#> 5 b 7
#> 6 b 6
#> # ... with 5 more rows
实际上就是,默认情况下arrange()
会忽略分组变量,除非通过设置参数.by_group
参数。
- mutate and transmute
根据 mutate 中函数不同,返回值视情况而定。
<- tibble(a = c('a','b','a','a','d','b','b','b','d','e','f'),b=1:11)
df
<- df %>%
res1 mutate( d = mean(b))
<- df %>%
res2 group_by(a) %>%
mutate(d = mean(b))
# 分组后返回不同值,此时的 mutate 相当于汇总函数
identical(res1,res2)
#> [1] FALSE
- filter
filter 受 group_by 影响。
%>%
df group_by(a) %>%
filter(b == max(b))
#> # A tibble: 5 x 2
#> # Groups: a [5]
#> a b
#> <chr> <int>
#> 1 a 4
#> 2 b 8
#> 3 d 9
#> 4 e 10
#> 5 f 11
以上代码本质是先执行 mutate(max(b))
,再filter,最后只保留 TRUE 所在行。如下所示:
%>%
df group_by(a) %>%
mutate(max_num = max(b)) %>%
filter(b==max_num) %>%
select(-max_num)
#> # A tibble: 5 x 2
#> # Groups: a [5]
#> a b
#> <chr> <int>
#> 1 a 4
#> 2 b 8
#> 3 d 9
#> 4 e 10
#> 5 f 11
想想以下代码的含义?
%>%
df group_by(a) %>%
filter(n()!=1)
#> # A tibble: 9 x 2
#> # Groups: a [3]
#> a b
#> <chr> <int>
#> 1 a 1
#> 2 b 2
#> 3 a 3
#> 4 a 4
#> 5 d 5
#> 6 b 6
#> # ... with 3 more rows
删除只有一行记录的数据行
- slice 系列
选择每个分组变量的第一个观测值。
%>%
by_species relocate(species) %>%
slice(1)
#> # A tibble: 38 x 14
#> # Groups: species [38]
#> species name height mass hair_color skin_color eye_color birth_year sex
#> <chr> <chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr>
#> 1 Aleena Ratts~ 79 15 none grey, blue unknown NA male
#> 2 Besalisk Dexte~ 198 102 none brown yellow NA male
#> 3 Cerean Ki-Ad~ 198 82 white pale yellow 92 male
#> 4 Chagrian Mas A~ 196 NA none blue blue NA male
#> 5 Clawdite Zam W~ 168 55 blonde fair, gree~ yellow NA fema~
#> 6 Droid C-3PO 167 75 <NA> gold yellow 112 none
#> # ... with 32 more rows, and 5 more variables: gender <chr>, homeworld <chr>,
#> # films <list>, vehicles <list>, starships <list>
同样,我同样可以使用slice_min()
来选择 变量的最大值。
%>%
by_species filter(!is.na(height)) %>%
slice_max(height,n=3)
#> # A tibble: 51 x 14
#> # Groups: species [38]
#> name height mass hair_color skin_color eye_color birth_year sex gender
#> <chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
#> 1 Ratts ~ 79 15 none grey, blue unknown NA male mascu~
#> 2 Dexter~ 198 102 none brown yellow NA male mascu~
#> 3 Ki-Adi~ 198 82 white pale yellow 92 male mascu~
#> 4 Mas Am~ 196 NA none blue blue NA male mascu~
#> 5 Zam We~ 168 55 blonde fair, green~ yellow NA fema~ femin~
#> 6 IG-88 200 140 none metal red 15 none mascu~
#> # ... with 45 more rows, and 5 more variables: homeworld <chr>, species <chr>,
#> # films <list>, vehicles <list>, starships <list>