在 R 中创建相等和的组
Creating groups of equal sum in R
我正在尝试将我的一列数据分组。frame/data.table 分为三组,所有组的总和相等。
数据首先从小到大排序,这样第一组将由大量具有小值的行组成,而第三组将具有少量具有大值的行。这是在精神上完成的:
test <- data.frame(x = as.numeric(1:100000))
store <- 0
total <- sum(test$x)
for(i in 1:100000){
store <- store + test$x[i]
if(store < total/3){
test$y[i] <- 1
} else {
if(store < 2*total/3){
test$y[i] <- 2
} else {
test$y[i] <- 3
}
}
}
虽然成功了,但我觉得一定有更好的方法(也许是我缺少的一个非常明显的解决方案)。
- 我从不喜欢诉诸循环,尤其是嵌套 ifs,当向量化方法可用时 - 即使有 100,000 多条记录,此代码也会变得非常慢
- 如果要对更多组进行编码(不一定是循环,而是 ifs),此方法将变得异常复杂
- 需要对列进行预排序。可能无法绕过这个。
作为一个细微差别(并不是说它有什么不同)但是要求和的数据不会总是(或永远)是连续的整数。
也许与 cumsum:
test$z <- cumsum(test$x) %/% (ceiling(sum(test$x) / 3)) + 1
我认为 cumsum/modulo 除法方法非常优雅,但它确实会重新运行有点不规则的分配:
> tapply(test$x, test$z, sum)
1 2 3
1666636245 1666684180 1666729575
> sum(test)/3
[1] 1666683333
所以我会先创建一个随机排列并提供类似的东西:
test$x <- sample(test$x)
test$z2 <- cumsum(test$x)[ findInterval(cumsum(test$x),
c(0, 1666683333*(1:2), sum(test$x)+1))]
> tapply(test$x, test$z2, sum)
91099 116379 129539
1666676164 1666686837 1666686999
这也实现了更均匀的计数分布:
> table(test$z2)
91099 116379 129539
33245 33235 33520
> table(test$z)
1 2 3
57734 23915 18351
我必须承认对 z2
中条目的命名感到困惑。
这或多或少是一个 bin-packing 问题。
使用 BBmisc
包中的 binPack
函数:
library(BBmisc)
test$bins <- binPack(test$x, sum(test$x)/3+1)
3 个 bin 的总和几乎相同:
tapply(test$x, test$bins, sum)
1 2 3
1666683334 1666683334 1666683332
您可以使用 groupdata2 中的 fold() 并获得每组几乎相等数量的元素:
# Create data frame
test <- data.frame(x = as.numeric(1:100000))
# Use fold() to create 3 numerically balanced groups
test <- groupdata2::fold(k = 3, num_col = "x")
# Watch first 10 rows
head(test, 10)
## # A tibble: 10 x 2
## # Groups: .folds [3]
## x .folds
## <dbl> <fct>
## 1 1 1
## 2 2 3
## 3 3 2
## 4 4 1
## 5 5 2
## 6 6 2
## 7 7 1
## 8 8 3
## 9 9 2
## 10 10 3
# Check the sum and number of elements per group
test %>%
dplyr::group_by(.folds) %>%
dplyr::summarize(sum_ = sum(x),
n_members = dplyr::n())
## # A tibble: 3 x 3
## .folds sum_ n_members
## <fct> <dbl> <int>
## 1 1 1666690952 33333
## 2 2 1666716667 33334
## 3 3 1666642381 33333
或者你可以 cut
在 cumsum
test$z <- cut(cumsum(test$x), breaks = 3, labels = 1:3)
或使用 ggplot2::cut_interval
而不是 cut
:
test$z <- cut_interval(cumsum(test$x), n = 3, labels = 1:3)
我正在尝试将我的一列数据分组。frame/data.table 分为三组,所有组的总和相等。
数据首先从小到大排序,这样第一组将由大量具有小值的行组成,而第三组将具有少量具有大值的行。这是在精神上完成的:
test <- data.frame(x = as.numeric(1:100000))
store <- 0
total <- sum(test$x)
for(i in 1:100000){
store <- store + test$x[i]
if(store < total/3){
test$y[i] <- 1
} else {
if(store < 2*total/3){
test$y[i] <- 2
} else {
test$y[i] <- 3
}
}
}
虽然成功了,但我觉得一定有更好的方法(也许是我缺少的一个非常明显的解决方案)。
- 我从不喜欢诉诸循环,尤其是嵌套 ifs,当向量化方法可用时 - 即使有 100,000 多条记录,此代码也会变得非常慢
- 如果要对更多组进行编码(不一定是循环,而是 ifs),此方法将变得异常复杂
- 需要对列进行预排序。可能无法绕过这个。
作为一个细微差别(并不是说它有什么不同)但是要求和的数据不会总是(或永远)是连续的整数。
也许与 cumsum:
test$z <- cumsum(test$x) %/% (ceiling(sum(test$x) / 3)) + 1
我认为 cumsum/modulo 除法方法非常优雅,但它确实会重新运行有点不规则的分配:
> tapply(test$x, test$z, sum)
1 2 3
1666636245 1666684180 1666729575
> sum(test)/3
[1] 1666683333
所以我会先创建一个随机排列并提供类似的东西:
test$x <- sample(test$x)
test$z2 <- cumsum(test$x)[ findInterval(cumsum(test$x),
c(0, 1666683333*(1:2), sum(test$x)+1))]
> tapply(test$x, test$z2, sum)
91099 116379 129539
1666676164 1666686837 1666686999
这也实现了更均匀的计数分布:
> table(test$z2)
91099 116379 129539
33245 33235 33520
> table(test$z)
1 2 3
57734 23915 18351
我必须承认对 z2
中条目的命名感到困惑。
这或多或少是一个 bin-packing 问题。
使用 BBmisc
包中的 binPack
函数:
library(BBmisc)
test$bins <- binPack(test$x, sum(test$x)/3+1)
3 个 bin 的总和几乎相同:
tapply(test$x, test$bins, sum)
1 2 3
1666683334 1666683334 1666683332
您可以使用 groupdata2 中的 fold() 并获得每组几乎相等数量的元素:
# Create data frame
test <- data.frame(x = as.numeric(1:100000))
# Use fold() to create 3 numerically balanced groups
test <- groupdata2::fold(k = 3, num_col = "x")
# Watch first 10 rows
head(test, 10)
## # A tibble: 10 x 2
## # Groups: .folds [3]
## x .folds
## <dbl> <fct>
## 1 1 1
## 2 2 3
## 3 3 2
## 4 4 1
## 5 5 2
## 6 6 2
## 7 7 1
## 8 8 3
## 9 9 2
## 10 10 3
# Check the sum and number of elements per group
test %>%
dplyr::group_by(.folds) %>%
dplyr::summarize(sum_ = sum(x),
n_members = dplyr::n())
## # A tibble: 3 x 3
## .folds sum_ n_members
## <fct> <dbl> <int>
## 1 1 1666690952 33333
## 2 2 1666716667 33334
## 3 3 1666642381 33333
或者你可以 cut
在 cumsum
test$z <- cut(cumsum(test$x), breaks = 3, labels = 1:3)
或使用 ggplot2::cut_interval
而不是 cut
:
test$z <- cut_interval(cumsum(test$x), n = 3, labels = 1:3)