有用的工具
summarise()有时需要和别的函数一起协作,这类函数可以对n个值的向量进行计算并返回一个单值,比如:
- 计数 n() n_distinct(x)
- 中间值 mean(x) median(x)
- 离散程度 sd(x) mad(x) IQR(x)
- 极端值 quartile(x) min(x) max(x)
- 位置 first(x) last(x) nth(x, 2)
另外一个十分有用的方法是对逻辑向量使用sum()或mean()。当逻辑向量作为数值处理时,TRUE计为1,FALSE计为0,所以sum()表示TRUE的数量而mean()则可表示TRUE的比例。比如下面的代码就计算了克拉数大于等于4的钻石个数和$1000以下钻石所占的比例
summarise(diamonds,
n_big = sum(carat >= 4),
prop_cheap = mean(price < 1000))
大多数汇总函数都包含na.rm的参数,na.rm = TRUE意味着在汇总前删去缺失值。这是个很好用的缺失值,不必再手动删除缺失值再汇总,而可以一步到位
统计上的考虑
当利用均值或中位数进行汇总时,顺手记一下数并衡量一下离散程度,往往可以帮助做出结论——如果不计算它们,可能会低估数据的波动性,从而有可能得出极不可靠的结论
下面对刚才的分净度平均价格的汇总做一个补充:增加每组的计数和上下四分位点。这显示出均值对这个数据的汇总效果并不好——价格的分布时严重偏态的:在某些组内,均值甚至比上四分位点还高!
by_clarity <- diamonds %>%
group_by(clarity) %>%
summarise(
n = n(), mean = mean(price),
lq = quantile(price, 0.25, uq = quantile(price, 0.75)))
by_clarity
ggplot(by_clarity, aes(clarity, mean)) +
geom_linerange(aes(ymin = lq, ymax = uq)) +
geom_line(aes(group = 1), color = "grey50") +
geom_point(aes(size = n))
下面的例子来自棒球比赛。计算一下安打率:安打(击球上垒)的次数除以在棒数。在这个标准下,谁是最好的击球手?
data(Batting, package = "Lahman")
batters <- filter(Batting, AB > 0)
per_player <- group_by(batters, palyerID)
ba <- summarise(per_player,
ba = sum(H, na.rm = TRUE) / sum(AB, na.rm = TRUE))
ggplot(ba, aes(ba)) +
geom_histogram(binwidth = 0.01)
有些球员几乎次次命中!通过计算一下在棒数,再仔细确认一遍他们是否真的那么好
ba <- summarise(per_player,
ba = sum(H, na.rm = TRUE)/ sum(AB, na.rm = TRUE),
ab = sum(AB, na.rm = TRUE))
ggplot(ba, aes(ab, ba)) +
geom_bin2d(bins = 100) +
geom_smooth()
安打率最高的球员就是那些在棒最少的球员——如果仅有一两次击球机会,每次都命中当然不难。如果去掉那些在棒补足10次的球员,可以看到更清晰的规律
ggplot(filter(ba, ab >= 10), aes(ab, ba)) +
geom_bin2d() + geom_smooth()
当比较观测数对平均值的图时,总是会发现类似的情况。所以一定要谨慎
管道操作
多数实际操作中,需要用一连串的mutate()、filter()、group_by()和summarise()。比如之前就组合了全部四个函数手动创造了频率多边形
# 通过中介变量
cut_depth <- group_by(diamonds, cut, depth)
cut_depth <- summarise(cut_depth, n = n())
cut_depth <- filter(cut_depth, depth > 55, depth < 70)
cut_depth <- mutate(cut_depth, prop = n / sum(n))
这一系列操作略显繁琐,因为需要多次重复数据框的名称,一个代替方法是用函数调用序列来完成它
# 通过“组合”函数
mutate(filter(summarise(group_by(diamonds, cut, depth),
n = n()), depth > 55, depth < 70), prop = n / sum(n)))
但是这个方法会增加代码阅读的难度,因为操作顺序是由内而外的,且各个函数的参数设置可能相去甚远。dplyr提供了另一种替代方案:利用管道函数%>%。通过这种方法,可以将上述一系列操作写为:
cut_depth <- diamonds %>%
group_by(cut, depth) %>%
summarise(n = n()) %>%
filter(depth > 55, depth < 70) %>%
mutate(prop = n / sum(n))
现在要理解做了什么事就很容易了,就和对一个句子一样:首先分组,再汇总,然后筛选,最后建立新的变量。事实上,读代码序列时,%>%符号最好的读法就是“然后”。%>% 来时Stefan Milton Bache的magrittr包,它还提供了许多dplyr默认情况下不显示的其它工具,所以强烈推荐参阅magrittr的官方网站
%>%的运作原理是将其左边(left hand side, LHS)的内容作为其右边(right hand side, RHS)函数的第一个参数。以下的表达是等价的:
f(x, y)
# 等价于
x %>% f(x)
g(f(x, y), z)
# 等价于
x %>% f(y) %>% g(z)
了解更多
dplyr包提供了很多其它函数,它们虽然再可视化中不如之前几个常用,但也十分重要
- arrange() 函数可以将观测依据某(些)变量排序。当从控制台(console)看数据时,这个函数是最有用的。在绘图时,它还可以帮助控制哪些带你绘制在最上面
- select()函数可以基于变量名选择变量,当希望从大量变量中只选取几个作分析时尤其有用
- rename()函数可以更改变量名
- 分组建立变量和分组筛选也是有用的,并且更为高级。详情见vignette(“window-functions”, package = “dplyr”)
- 还有一些用于同时处理两套数据的函数。它们包含了SQL接口(如R自导的mergge()函数)和集合操作。详情见vignette(“two-table”, package = “dplyr”)
- dplyr可以直接对数据库中的数据进行操作——用和本地操作时一样的R代码,然后dplyr能处理成SQL语言并发送至数据库。详情见vignette(“databases”, package = “dplyr”)
最后RStudio还提供了一个便捷的dplyr备忘录