将数字向量舍入为整数,同时保留它们的总和

Round vector of numerics to integer while preserving their sum

How to round floats to integers while preserving their sum? has the below answer 用伪代码编写,它将向量四舍五入为整数值,使得元素之和保持不变并且舍入误差最小。我想在 R.

中有效地实现这个(即如果可能的话矢量化)

例如,四舍五入这些数字会产生不同的总数:

set.seed(1)
(v <- 10 * runif(4))
# [1] 2.655087 3.721239 5.728534 9.082078
(v <- c(v, 25 - sum(v)))
# [1] 2.655087 3.721239 5.728534 9.082078 3.813063
sum(v)
# [1] 25
sum(round(v))
# [1] 26

answer复制伪代码以供参考

// Temp array with same length as fn.
tempArr = Array(fn.length)

// Calculate the expected sum.
arraySum = sum(fn)

lowerSum = 0
-- Populate temp array.
for i = 1 to fn.lengthf
    tempArr[i] = { result: floor(fn[i]),              // Lower bound
                   difference: fn[i] - floor(fn[i]),  // Roundoff error
                   index: i }                         // Original index

    // Calculate the lower sum
    lowerSum = lowerSum + tempArr[i] + lowerBound
end for

// Sort the temp array on the roundoff error
sort(tempArr, "difference")

// Now arraySum - lowerSum gives us the difference between sums of these
// arrays. tempArr is ordered in such a way that the numbers closest to the
// next one are at the top.
difference = arraySum - lowerSum

// Add 1 to those most likely to round up to the next number so that
// the difference is nullified.
for i = (tempArr.length - difference + 1) to tempArr.length
    tempArr.result = tempArr.result + 1
end for

// Optionally sort the array based on the original index.
array(sort, "index")

用更简单的形式,我会说这个算法是:

  1. 从四舍五入开始
  2. 将小数部分最高的数字四舍五入,直到达到所需的总和。

这可以通过以下方式在 R 中以矢量化方式实现:

  1. 向下舍入 floor
  2. 订单号的小数部分(使用order
  3. 使用tail获取具有k个最大小数部分的元素的索引,其中k是我们需要增加总和以达到目标值的数量
  4. 将每个索引中的输出值增加 1

在代码中:

smart.round <- function(x) {
  y <- floor(x)
  indices <- tail(order(x-y), round(sum(x)) - sum(y))
  y[indices] <- y[indices] + 1
  y
}
v
# [1] 2.655087 3.721239 5.728534 9.082078 3.813063
sum(v)
# [1] 25
smart.round(v)
# [1] 2 4 6 9 4
sum(smart.round(v))
# [1] 25

感谢这个有用的功能!只是为了补充答案,如果四舍五入到指定的小数位数,可以修改函数:

smart.round <- function(x, digits = 0) {
  up <- 10 ^ digits
  x <- x * up
  y <- floor(x)
  indices <- tail(order(x-y), round(sum(x)) - sum(y))
  y[indices] <- y[indices] + 1
  y / up
}

运行 与 @josliber 的 smartRound 相比,基于总体和差异的方法要快得多:

diffRound <- function(x) { 
  diff(c(0, round(cumsum(x)))) 
}

这里是如何比较 100 万条记录的结果(请在此处查看详细信息:):

res <- microbenchmark(
  "diff(dww)" = x$diff.rounded <- diffRound(x$numbers) ,
  "smart(josliber)"= x$smart.rounded <- smartRound(x$numbers),
  times = 100
)

Unit: milliseconds
expr            min       lq        mean       median     uq       max       neval
diff(dww)       38.79636  59.70858  100.6581   95.4304    128.226  240.3088   100
smart(josliber) 466.06067 719.22723 966.6007   1106.2781  1177.523 1439.9360  100