上节我们对选择现有的列和使用mutate添加新列做了介绍。现在对数据框使用summarize()进行分组摘要进行介绍。函数功能:summarize()
可以将数据框折叠成一行:
summarize(flights, delay = mean(dep_delay, na.rm = TRUE)) #> # A tibble: 1 × 1 #> delay #> <dbl> #> 1 12.6
如果想要将分析单位从整个数据集更改为单个分组,可以使用group_by()
。group_by()
和 summarize()
的组合构成了使用 dplyr 包时最常用的操作之一:分组摘要。例如,如果对按日期分组的一个数据框应用与上面完全相同的代码,那么我们就可以得到每日平均延误时间:
by_day <- group_by(flights, year, month, day) summarize(by_day, delay = mean(dep_delay, na.rm = TRUE)) #> Source: local data frame [365 x 4] #> Groups: year, month [?] #> #> year month day delay #> <int> <int> <int> <dbl> #> 1 2013 1 1 11.55 #> 2 2013 1 2 13.86 #> 3 2013 1 3 10.99 #> 4 2013 1 4 8.95 #> 5 2013 1 5 5.73 #> 6 2013 1 6 7.15管道是一种强大的工具,可以清楚地表示由多个操作组成的一个操作序列。管道是一种强大的工具,可以清楚地表示由多个操作组成的一个操作序列。 #> # ... with 359 more rows
3.6.1 使用管道组合多种操作
管道(%>%) 是一种强大的工具,可以清楚地表示由多个操作组成的一个操作序列。为了说明管道如此有用的原因, 我们将探究同一段代码的不同编写方式。现在我们使用代码来讲述小兔福福的故事:
一只小兔叫福福蹦蹦跳跳过森林抓起一窝小田鼠每只头上打一下首先,我们定义一个对象来表示小兔福福:
foo_foo <- little_bunny()
然后,我们使用函数来表示每个动作:hop()
、scoop()
和 bop()
。
于是这首童谣可以如下表示,这种方法的最大缺点是,你必须为每个中间结果建立一个变量,在很多情况下,比如在本例中,这些变量其实是没有什么实际意义的,你还必须使用数字后缀来区分这些变量:
foo_foo_1 <- hop(foo_foo, through = forest) foo_foo_2 <- scoop(foo_foo_1, up = field_mice) foo_foo_3 <- bop(foo_foo_2, on = head)
另一种方法是将多个函数组合在一起,这样可以避免赋值语句,这种方法的缺点是,必须按照从内向外和从右向左的顺序阅读代码,而且参数太分散了:
bop( scoop( hop(foo_foo, through = forest), up = field_mice ), on = head )
使用管道可以很好解决代码冗杂的问题,它的重点在于动词,而不是名词。在阅读这一串函数组合时,你可以将它们当成一系列规定动作。福福蹦跳着,然后抓田鼠,接着打田鼠:
foo_foo %>% hop(through = forest) %>% scoop(up = field_mouse) %>% bop(on = head)
掌握管道的用法后,可以显著提高代码的可读性,代码的编写也将更加容易。
3.6.2 缺失值
聚合函数遵循缺失值的一般规则:如果输入中有缺失值,那么输出也会是缺失值。好在所有聚合函数都有一个 na.rm
参数,只需设置na.rm =TRUE
,即可在计算前除去缺失值。
3.6.3 计数
聚合操作中常用的计数操作:
n()
:给出当前分组的个数sum(!is_na())
:对非缺失值的计数n_distinct()
:计算出唯一值的数量count()
:一个简单的辅助函数,用于只需要计数的情况
3.6.4 常用的摘要函数
- 位置度量:
median(x)
,mean(x)
均值是总数除以个数;中位数则是这样一个值:50% 的 x 大于它,同时 50% 的 x 小于它。 - 分散程度度量:
sd(x)
、IQR(x)
和mad(x)
均方误差(又称标准误差,standard deviation,sd)是分散程度的标准度量方式。四分位距IQR()
和绝对中位差mad(x)
基本等价,更适合有离群点的情况。 - 秩的度量:
min(x)
、quantile(x, 0.25)
和max(x)
分位数是中位数的扩展。例如,quantile(x, 0.25)
会找出 x 中按从小到大顺序大于前25% 而小于后75% 的值: - 定位度量:
first(x)
、nth(x, 2)
和last(x)
这几个函数的作用与 x[1]、x[2] 和 x[length(x)] 相同,只是当定位不存在时(比如尝试从只有两个元素的分组中得到第三个元素),前者允许你设置一个默认值。
3.6.5 按多个变量分组
当使用多个变量进行分组时,每次的摘要统计会用掉一个分组变量。这样就可以轻松地对数据集进行循序渐进的分析:
#统计每天的航班数 daily <- group_by(flights, year, month, day) (per_day <- summarize(daily, flights = n())) #> Source: local data frame [365 x 4] #> Groups: year, month [?] #> #> year month day flights #> <int> <int> <int> <int> #> 1 2013 1 1 842 #> 2 2013 1 2 943 #> 3 2013 1 3 914 #> 4 2013 1 4 915 #> 5 2013 1 5 720 #> 6 2013 1 6 832 #> # ... with 359 more rows
#统计每月的航班数 (per_month <- summarize(per_day, flights = sum(flights))) #> Source: local data frame [12 x 3] #> Groups: year [?] #> #> year month flights #> <int> <int> <int> #> 1 2013 1 27004 #> 2 2013 2 24951 #> 3 2013 3 28834 #> 4 2013 4 28330 #> 5 2013 5 28796 #> 6 2013 6 28243 #> # ... with 6 more rows
#统计一年的航班数 (per_year <- summarize(per_month, flights = sum(flights))) #> # A tibble: 1 × 2 #> year flights #> <int> <int> #> 1 2013 336776
注意:在循序渐进地进行摘要分析时,使用求和与计数操作是没问题的,但如果想要使用加权平均和方差的话,就要仔细考虑一下,在基于秩的统计数据(如中位数)上是无法进行这些操作的。换句话说,对分组求和的结果再求和就是对整体求和,但分组中位数的中位数可不是整体的中位数。
3.6.6 取消分组
如果想要取消分组,并回到未分组的数据继续操作,那么可以使用ungroup()
函数:
daily %>% ungroup() %>% # 不再按日期分组 summarize(flights = n()) # 所有航班 #> # A tibble: 1 × 1 #> flights #> <int> #> 1 336776