tidyr::complete因列类型与舍入精度设置表现异常问题
和列类型无关,本质是R浮点数二进制存储的精度误差导致的匹配失败:
0.1这类十进制小数无法被二进制浮点精确表示,原始数据里拼接的深度值、和seq(min(depth), max(depth), by=0.1)生成的序列值,虽然打印出来看起来完全一样,但底层存储存在1e-16量级的微小差值。tidyr::complete做组合匹配时是按值严格全等判断的,两个看起来都是0.3的深度因为这点极小的差值会被判定为不同值,最终出现原有深度被重复识别、错误填充NA的问题。
整数场景下运行正常,是因为整数可以被二进制精确存储,不存在这类精度差。之前试的转字符再转数值能临时生效,本质是做了一次隐式的精度截断,把两个有微小差别的浮点数转成了相同值,但属于事后补救,还需要额外删重复行,效率低也不稳定。
可以跑一行代码验证这个误差:
# 原始数据中B站点第一个深度是0.3,seq生成的第一个0.3和它并不相等 b$depth[11] == seq(0.3, 0.5, 0.1)[1] # 查看差值 b$depth[11] - seq(0.3, 0.5, 0.1)[1]
运行后会看到相等判断返回FALSE,差值是一个极小的非零数,就是导致异常的直接原因。
不需要事后处理重复行,从匹配前统一精度入手即可,两个常用方案:
方案1:所有深度值统一做固定精度舍入
原始深度和seq生成的补全序列,都提前舍入到对应小数位(这里是1位小数),从根源消除浮点误差:library(dplyr) library(tidyr) b2 <- b %>% mutate(site = factor(site), depth = round(depth, 1)) %>% group_by(site) %>% complete(depth = round(seq(min(depth), max(depth), by = 0.1), 1)) %>% arrange(site, depth)运行后检查
unique(b2$depth)、单站点数据,都不会再出现重复深度、错误补NA的问题。方案2:缩放为整数做匹配
按观测精度把深度缩放为整数(比如0.1米精度就乘10转整数),整数不存在浮点匹配问题,补全完成后再缩放回原单位即可,适合更高精度要求的场景:b2 <- b %>% mutate(site = factor(site), depth_scaled = as.integer(round(depth * 10, 0))) %>% group_by(site) %>% complete(depth_scaled = seq(min(depth_scaled), max(depth_scaled), by = 1)) %>% mutate(depth = depth_scaled / 10) %>% select(-depth_scaled) %>% arrange(site, depth)
通用注意点:所有涉及小数的等值匹配场景,都不要直接用
==判断,必须先做精度统一,否则很容易出现这类看似反常的匹配错误。
内容的提问来源于stack exchange,提问作者Annika Elsie

