dplyr的group_by()未按预期工作:浮点数精度引发分组异常
浮点数精度问题导致dplyr分组统计异常的解决方法
问题描述
在信号检测理论项目中,通过命中率(HR)和误报率(FAR)计算辨别分数(公式:discernment = HR - FAR),使用dplyr的group_by()+summarise()统计各辨别分数的出现次数时,出现视觉上相同的辨别值被分成不同行的异常情况:
- 例如预期
-0.95仅占一行,但实际结果显示两行; - 用
filter(discernment == -0.95)仅能筛选出其中一行; - 将辨别值转为因子后发现,部分看似整数位后两位的数值(如
-0.01)实际是-0.00999999999999998,本质是浮点数精度误差导致。
可复现代码
# 最小复现示例 library(tidyverse) # 生成HR和FAR的所有可能取值(步长0.01) HR_values <- seq(0, 1, by = 0.01) FAR_values <- seq(0, 1, by = 0.01) # 创建所有组合的网格数据框 combinations <- expand.grid(HR = HR_values, FAR = FAR_values) # 计算辨别分数 combinations <- combinations %>% mutate(discernment = HR - FAR) # 统计各辨别分数的出现次数 occurences <- combinations %>% group_by(discernment) %>% summarise(n_occurrences = n()) %>% arrange(discernment) # 查看前7行,可见疑似重复的辨别值 occurences %>% slice(1:7) # 筛选-0.95仅返回一行,验证数值存在细微差异 occurences %>% filter(discernment == -0.95) # 转为因子后查看数值细节,确认精度问题 occurences <- occurences %>% mutate(discernment_as_factor = as.factor(discernment)) occurences %>% slice(6:7) occurences %>% slice(310:317)
解决方法
1. 四舍五入到指定小数位
由于HR和FAR都是步长0.01的数值,辨别分数的理论精度最多为两位小数,直接对计算结果四舍五入即可消除误差:
# 方法一:计算辨别分数时直接四舍五入 combinations <- combinations %>% mutate(discernment = round(HR - FAR, digits = 2)) # 方法二:分组时对辨别分数进行四舍五入 occurences <- combinations %>% group_by(discernment = round(discernment, digits = 2)) %>% summarise(n_occurrences = n()) %>% arrange(discernment)
2. 使用近似比较函数(适用于过滤场景)
如果需要保留原始数值,仅在过滤时匹配近似值,可以使用dplyr内置的near()函数:
# 筛选近似等于-0.95的行 occurences %>% filter(near(discernment, -0.95, tol = 1e-6))
3. 使用精确十进制类型
借助decimal包存储精确的十进制小数,从根源避免二进制浮点数误差:
library(decimal) # 用decimal类型生成HR和FAR数值 HR_values <- as.decimal(seq(0, 1, by = 0.01)) FAR_values <- as.decimal(seq(0, 1, by = 0.01)) # 计算精确的辨别分数 combinations <- expand.grid(HR = HR_values, FAR = FAR_values) %>% mutate(discernment = HR - FAR) # 分组统计 occurences <- combinations %>% group_by(discernment) %>% summarise(n_occurrences = n()) %>% arrange(discernment)
原因说明
计算机中的浮点数采用二进制存储,部分十进制小数(如0.01)无法被精确转换为二进制小数,导致HR与FAR相减后产生微小的精度误差。这些误差肉眼不可见,但会被dplyr的group_by()识别为不同的分组键,最终出现看似重复的行。
内容的提问来源于stack exchange,提问作者Jan Pfänder
相关产品推荐
相关产品推荐

