如何使用 dplyr 对列进行范围分组?
How to do range grouping on a column using dplyr?
我想根据列的 范围 值对 data.table 进行分组,如何使用 dplyr 库执行此操作?
例如,我的数据table如下:
library(data.table)
library(dplyr)
DT <- data.table(A=1:100, B=runif(100), Amount=runif(100, 0, 100))
现在想把DT按B列0.05的间隔分成20组,统计每组有多少行。例如,B 列值在 [0, 0.05) 范围内的任何行将组成一个组; B 列值在 [0.05, 0.1) 范围内的任何行将组成另一组,依此类推。有没有一种有效的方法来执行此组功能?
非常感谢。
----------------------------关于 akrun 回答的更多问题。
感谢阿克伦的回答。我有一个关于 "cut" 函数的新问题。如果我的 DT 如下所示:
DT <- data.table(A=1:10, B=c(0.01, 0.04, 0.06, 0.09, 0.1, 0.13, 0.14, 0.15, 0.17, 0.71))
通过使用以下代码:
DT %>%
group_by(gr=cut(B, breaks= seq(0, 1, by = 0.05), right=F) ) %>%
summarise(n= n()) %>%
arrange(as.numeric(gr))
我希望看到这样的结果:
gr n
1 [0,0.05) 2
2 [0.05,0.1) 2
3 [0.1,0.15) 3
4 [0.15,0.2) 2
5 [0.7,0.75) 1
但是我得到的结果是这样的:
gr n
1 [0,0.05) 2
2 [0.05,0.1) 2
3 [0.1,0.15) 4
4 [0.15,0.2) 1
5 [0.7,0.75) 1
看起来值 0.15 分配不正确。对此有什么想法吗?
我们可以使用cut
来进行分组。我们在 group_by
中创建 'gr' 列,使用 summarise
创建每个组中的元素数 (n()
),并对输出进行排序 (arrange
) 基于 'gr'.
library(dplyr)
DT %>%
group_by(gr=cut(B, breaks= seq(0, 1, by = 0.05)) ) %>%
summarise(n= n()) %>%
arrange(as.numeric(gr))
由于初始对象是 data.table
,这可以使用 data.table
方法完成(包括@Frank 使用 keyby
的建议)
library(data.table)
DT[,.N , keyby = .(gr=cut(B, breaks=seq(0, 1, by=0.05)))]
编辑:
根据 OP post 中的更新,我们可以将 seq
减去一个小数字
lvls <- levels(cut(DT$B, seq(0, 1, by =0.05)))
DT %>%
group_by(gr=cut(B, breaks= seq(0, 1, by = 0.05) -
.Machine$double.eps, right=FALSE, labels=lvls)) %>%
summarise(n=n()) %>%
arrange(as.numeric(gr))
# gr n
#1 (0,0.05] 2
#2 (0.05,0.1] 2
#3 (0.1,0.15] 3
#4 (0.15,0.2] 2
#5 (0.7,0.75] 1
添加另一个替代data.table解决方案:
我通常更喜欢使用 round_any
(来自 plyr)而不是 cut
:
例如
DT[, .N, keyby = round_any(B, 0.05, floor)]
这实质上将数据四舍五入为数字的任意倍数(即 0.05
)。第三个参数表示在舍入时使用 floor
(即 0.04 将被分组为 (0,0.05] 而不是 (0.05,0.1])。您还可以将第三个参数设置为 ceiling
和 round
(默认值)。
对于大表,此解决方案比 akrun 的 data.table 解决方案更快(对于小表,它们的速度大致相同)。
需要注意的是,这两个命令的输出是不同的 - cut
的组列是一个范围,而 round_any 的组列值是单个数字(即楼层数)。
1000 万行数据集的基准:
DT <- data.table(A=1:10000000, B=runif(10000000), Amount=runif(100, 0, 10000000))
bench::mark(
dplyr = DT %>%
group_by(gr = cut(B, breaks = seq(0, 1, by = 0.05))) %>%
summarise(n = n()) %>%
arrange(as.numeric(gr)),
data_table_cut = DT[, .N, keyby = .(gr = cut(B, breaks = seq(0, 1, by = 0.05)))],
data_table_round_any = DT[, .N, keyby = round_any(B, 0.05, floor)],
check = FALSE
)
输出:
# A tibble: 3 × 13
expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time
<bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl> <int> <dbl> <bch:tm>
1 dplyr 654ms 654ms 1.53 445MB 0 1 0 654ms
2 data_table_cut 573ms 573ms 1.75 534MB 0 1 0 573ms
3 data_table_round_any 234ms 236ms 4.21 343MB 0 3 0 712ms
所以 round_any
大约比 data.table cut
解决方案快 2.5 倍(比 dplyr 解决方案快 2.7 倍)...
我想根据列的 范围 值对 data.table 进行分组,如何使用 dplyr 库执行此操作?
例如,我的数据table如下:
library(data.table)
library(dplyr)
DT <- data.table(A=1:100, B=runif(100), Amount=runif(100, 0, 100))
现在想把DT按B列0.05的间隔分成20组,统计每组有多少行。例如,B 列值在 [0, 0.05) 范围内的任何行将组成一个组; B 列值在 [0.05, 0.1) 范围内的任何行将组成另一组,依此类推。有没有一种有效的方法来执行此组功能?
非常感谢。
----------------------------关于 akrun 回答的更多问题。 感谢阿克伦的回答。我有一个关于 "cut" 函数的新问题。如果我的 DT 如下所示:
DT <- data.table(A=1:10, B=c(0.01, 0.04, 0.06, 0.09, 0.1, 0.13, 0.14, 0.15, 0.17, 0.71))
通过使用以下代码:
DT %>%
group_by(gr=cut(B, breaks= seq(0, 1, by = 0.05), right=F) ) %>%
summarise(n= n()) %>%
arrange(as.numeric(gr))
我希望看到这样的结果:
gr n
1 [0,0.05) 2
2 [0.05,0.1) 2
3 [0.1,0.15) 3
4 [0.15,0.2) 2
5 [0.7,0.75) 1
但是我得到的结果是这样的:
gr n
1 [0,0.05) 2
2 [0.05,0.1) 2
3 [0.1,0.15) 4
4 [0.15,0.2) 1
5 [0.7,0.75) 1
看起来值 0.15 分配不正确。对此有什么想法吗?
我们可以使用cut
来进行分组。我们在 group_by
中创建 'gr' 列,使用 summarise
创建每个组中的元素数 (n()
),并对输出进行排序 (arrange
) 基于 'gr'.
library(dplyr)
DT %>%
group_by(gr=cut(B, breaks= seq(0, 1, by = 0.05)) ) %>%
summarise(n= n()) %>%
arrange(as.numeric(gr))
由于初始对象是 data.table
,这可以使用 data.table
方法完成(包括@Frank 使用 keyby
的建议)
library(data.table)
DT[,.N , keyby = .(gr=cut(B, breaks=seq(0, 1, by=0.05)))]
编辑:
根据 OP post 中的更新,我们可以将 seq
lvls <- levels(cut(DT$B, seq(0, 1, by =0.05)))
DT %>%
group_by(gr=cut(B, breaks= seq(0, 1, by = 0.05) -
.Machine$double.eps, right=FALSE, labels=lvls)) %>%
summarise(n=n()) %>%
arrange(as.numeric(gr))
# gr n
#1 (0,0.05] 2
#2 (0.05,0.1] 2
#3 (0.1,0.15] 3
#4 (0.15,0.2] 2
#5 (0.7,0.75] 1
添加另一个替代data.table解决方案:
我通常更喜欢使用 round_any
(来自 plyr)而不是 cut
:
例如
DT[, .N, keyby = round_any(B, 0.05, floor)]
这实质上将数据四舍五入为数字的任意倍数(即 0.05
)。第三个参数表示在舍入时使用 floor
(即 0.04 将被分组为 (0,0.05] 而不是 (0.05,0.1])。您还可以将第三个参数设置为 ceiling
和 round
(默认值)。
对于大表,此解决方案比 akrun 的 data.table 解决方案更快(对于小表,它们的速度大致相同)。
需要注意的是,这两个命令的输出是不同的 - cut
的组列是一个范围,而 round_any 的组列值是单个数字(即楼层数)。
1000 万行数据集的基准:
DT <- data.table(A=1:10000000, B=runif(10000000), Amount=runif(100, 0, 10000000))
bench::mark(
dplyr = DT %>%
group_by(gr = cut(B, breaks = seq(0, 1, by = 0.05))) %>%
summarise(n = n()) %>%
arrange(as.numeric(gr)),
data_table_cut = DT[, .N, keyby = .(gr = cut(B, breaks = seq(0, 1, by = 0.05)))],
data_table_round_any = DT[, .N, keyby = round_any(B, 0.05, floor)],
check = FALSE
)
输出:
# A tibble: 3 × 13
expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time
<bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl> <int> <dbl> <bch:tm>
1 dplyr 654ms 654ms 1.53 445MB 0 1 0 654ms
2 data_table_cut 573ms 573ms 1.75 534MB 0 1 0 573ms
3 data_table_round_any 234ms 236ms 4.21 343MB 0 3 0 712ms
所以 round_any
大约比 data.table cut
解决方案快 2.5 倍(比 dplyr 解决方案快 2.7 倍)...