- 机器学习
- 深度学习
- AutoML
- 大数据
- 后端
【免费下载链接】h2o-3
H2O is an Open Source, Distributed, Fast & Scalable Machine Learning Platform: Deep Learning, Gradient Boosting (GBM) & XGBoost, Random Forest, Generalized Linear Modeling (GLM with Elastic Net), K-Means, PCA, Generalized Additive Models (GAM), RuleFit, Support Vector Machine (SVM), Stacked Ensembles, Automatic Machine Learning (AutoML), etc.
导读
在机器学习建模前,数据清洗中最常见的任务之一就是处理缺失值(NA)。H2O 的impute函数提供了一种**原地(in-place)**的缺失值填补机制:用na.rm之后的聚合统计量填充缺失值,并支持按数据集内部若干列分组后分别插补,同时支持使用预先计算好的分组框架(group by frame)复用插补结果。本文以 h2o-3 仓库中的 imputing-data.rst 为骨架,结合底层 Java 实现(AstImpute.java)、Python 绑定(frame.py)与 R 绑定(frame.R)的源码细节,系统讲解impute的全部参数语义、调用语法、分组插补原理与测试验证方式,读完即可在 Python 与 R 中正确完成各类缺失值插补。
H2Oimpute是什么:面向分布式 Frame 的原地插补
H2O 的插补(imputation)功能与 pandas、R 中常见的"返回新数据"式插补不同,它的核心设计是原地修改:impute直接修改传入的 H2OFrame,用聚合统计量(默认情况下数值列用均值、分类列用众数)填充该列中的 NA 值。从底层源码看,插补操作最终被编译为 Rapids 语言中的一个 AST 节点h2o.impute,其签名如下(见 AstImpute.java):
args(): {"ary", "col", "method", "combineMethod", "groupByCols", "groupByFrame", "values"} str(): "h2o.impute" nargs(): 1 + 7即 Rapids 层面的完整调用形式为(h2o.impute data col method combine_method groupby groupByFrame values),Python 与 R 客户端最终都会把用户友好的 API 调用翻译成这一 AST 表达式发给集群执行。值得注意的是,源码注释明确说明:
- 插补可作用于整个 Frame(
col = -1,数值列用均值、分类列用众数)或Frame 中的某个 Vec(特定列); - String、Time、UUID 类型的列永远不会被插补;
- 所有插补都是**原地(in place)**完成的;
- 若指定了分组列但没有指定待插补列,或把待插补列放进了分组列中,会抛出
IllegalArgumentException。
这解释了文档中反复强调"Revert imputations"的原因——因为修改是直接作用在原 Frame 上的,若想保留原始数据,需要重新导入数据。
参数全解:impute的七个核心参数
原文档 imputing-data.rst 列出了以下参数,结合源码可得到更精确的语义:
| 参数 | Python 端 | R 端 | 语义与默认值 |
|---|---|---|---|
by | by | by | 分组列,可传列索引或列名;R 端传 1 基索引,Python 端传 0 基索引或列名 |
column | column | column | 指定待插补列;默认0(Python)/-1(内部)表示插补整个 Frame |
combine_method | combine_method | combine_method | 仅当method="median"时生效:偶数样本量下如何组合分位数,可选average、high、interpolate、low |
dataset | self(隐式) | data | 包含待插补列的数据集 |
group_by_frame | group_by_frame | groupByFrame | 用预计算的分组框架来插补列 |
method | method | method | 插补方式:mean、median、mode |
values | values | values | 插补值向量(每列一个值);NaN表示跳过该列 |
method:三种插补策略及其适用列类型
mean:用列均值(na.rm = TRUE)替换 NA。仅适用于数值列。源码中对应AstMean节点,res[col] = vec.mean()(见 AstImpute.java)。median:用列中位数替换 NA。仅适用于数值列。底层调用AstMedian.median(new Frame(vec), combine),其中combine就是QuantileModel.CombineMethod枚举(见 AstImpute.java)。mode:用出现次数最多的因子水平替换 NA。仅适用于分类列。底层调用AstMode.mode(vec)(AstImpute.java),而ArrayUtils.maxIndex(vec.bins())用于整个 Frame 的众数计算(AstImpute.java)。
重要限制(原文档也强调):如果待插补列是分类(factor)列,则 method 必须是
mode;同理,源码中也只会对数值列计算 mean/median、对分类列计算 mode。在 R 端,h2o.impute还会在 method 传入默认的三选一候选向量c("mean","median","mode")时自动选择"mean",默认插补策略按列类型决定:数值列选"mean",分类列选"mode"。
combine_method:中位数在偶数样本下的分位数组合规则
当method="median"时,偶数样本量下中位数的取值需要定义。combine_method提供四种规则,其底层直接映射为 H2O 分位数模型(QuantileModel)中的CombineMethod枚举(INTERPOLATE, AVERAGE, LOW, HIGH,见 AstImpute.java):
interpolate(默认):在相邻分位数之间插值,是统计意义上最常见的中位数定义;average:取两个中间值的平均;low:取两个中间值中较小的那个;high:取两个中间值中较大的那个。
需要留意两点:该参数仅在中位数插补时生效,其余情况全部忽略;且在 R 端h2o.impute中,"lo"与"hi"会被分别规整为"low"与"high"再传给后端(见 frame.R)。
by 与 column:指定插补范围与分组方式
column决定"插哪一列",by决定"按什么分组后分别计算插补值"。两者配合的语义为:对by指定的每一组,使用该组内部非 NA 数据计算聚合值,再回填该组内的 NA。
- Python 端支持按**列名(字符串)或 0 基索引(整数)**传参,
by可以是列名列表;传入字符串时会先通过self.names.index(column)转换为索引(frame.py)。 - R 端
column默认值为0(内部转换为-1,表示整帧插补),传数值时按1 基索引处理(col.id <- column - 1L),传字符串时按列名匹配(frame.R)。 - R 端目前对
median与by的组合会直接报错:"Unimplemented: No by and median. Please select a different method."(frame.R),这是 R 绑定侧的限制,Python 端无此限制。
从底层看,by的分组列在 Rapids 侧既可以传数字列表(AstNumList),也可以传单个数(AstNum)或字符串列表(AstStrList,会通过fr.find(name)解析为列索引)(AstImpute.java)。分组插补的执行分两步:先用AstGroup(Rapids 的 group-by 原语)对原始 Frame 按by分组并求聚合(数值列mean、分类列mode,"rm"表示 na.rm),得到一个"组 → 插补值"的映射表;随后启动一个MRTask遍历原始 Frame,对非分组列中的每个 NA 单元格,从映射表中取出对应组的聚合值回填(AstImpute.java)。
group_by_frame:复用预计算的分组插补结果
当by对应的分组插补已经被执行过一次(并返回了分组聚合结果 Frame)后,可以把这个结果 Frame 通过group_by_frame传给下一次impute,从而对另一列复用同一套分组边界,避免重复计算。源码中groupByFrame = null(R 端)/None(Python 端)会被替换成"_"占位符(frame.R、frame.py),后端根据是否为"_"判断是否需要现场执行 GroupBy(AstImpute.java)。若使用group_by_frame而未提供by且结果列数超过 2,后端会抛出"Ambiguous group-by frame"异常,提示需要补充by列来消歧(AstImpute.java)。
values:逐列指定插补值
values接受一个长度等于列数的向量,为每一列显式指定插补值,NaN表示跳过该列。它适合用已知业务规则(而非统计量)填充的场景。源码中values缺省时为null,此时后端会为数值列计算mean()、为分类列计算众数索引(ArrayUtils.maxIndex(vec.bins()))来填充res数组(AstImpute.java);随后启动MRTask,对每个 chunk 中所有 NA 单元格写入对应列的值(AstImpute.java)。Python 端会校验len(values) == len(self.columns),并把分类列的字符串值转换成对应的枚举序号(找不到水平时抛出H2OValueError)(frame.py);R 端也会在 values 长度不匹配或 factor 值不在既有水平中时报错(frame.R)。
Python 实战:三种插补方式的完整演示
原文档使用航空公司数据集(allyears2k)给出了可直接运行的 Python 示例,以下是完整还原并附注释的版本:
import h2o h2o.init() # 导入航空公司数据集 air_path = "https://s3.amazonaws.com/h2o-public-test-data/smalldata/airlines/allyears2k.zip" air = h2o.import_file(path=air_path) air.dim # [43978, 31] # 1) 按 Origin 与 Distance 分组,对 DepTime 列做均值插补 DeptTime_impute = air.impute("DepTime", method="mean", by=["Origin", "Distance"]) DeptTime_impute # 返回分组聚合结果:Origin / Distance / mean_DepTime,共 1497 行 # Origin Distance mean_DepTime # ABE 253 1149.7 # ABE 481 812 # ABQ 223 1229.33 # ...(共 1497 行 x 3 列) # 原地插补已修改 air,若需保留原始数据请重新导入 air = h2o.import_file(path=air_path) # 2) 对 TailNum 分类列做众数插补 mode_impute = air.impute("TailNum", method="mode") # 返回值为逐列的插补值列表,除第 10 列(TailNum)为 3499.0 外其余均为 nan # [nan, nan, ..., 3499.0, nan, ...] # 3) 按 Month 与 Year 分组对 TailNum 做众数插补 air = h2o.import_file(path=air_path) mode_impute = air.impute("TailNum", method="mode", by=["Month", "Year"]) # 返回分组结果:Year / Month / mode_TailNum,共 22 行 # 1987 10 3499 # 1988 1 3499 # ...几个要点:
- 返回值语义:不带
by/group_by_frame时,Python 端通过_eager_scalar()返回一个标量值列表(每个被插补列一个值,未被插补列为 NaN);带分组时返回分组聚合结果 Frame(_frame()),也就是后续可复用于group_by_frame的那个框架(frame.py)。 - 原地修改确认:Python 端在调用后执行
self._ex._cache.flush()并重新fill(10)刷新本地缓存,确保调用方能立即读到修改后的数据(frame.py)。 - 其余等价写法:
air.impute(8, method="mean")(0 基索引)与air.impute("VOL", method="mean")(列名)等价;method="median"时可配combine_method。这些均被仓库测试 pyunit_impute.py 覆盖验证。
R 实战:h2o.impute的完整调用示例
原文档同样给出了 R 侧示例,并补充了"分组结果中若某组全部为 NA 则该组不插补(仍为 NA)"的重要语义。完整还原如下:
library(h2o) h2o.init() # 上传航空公司数据集 file_path <- "https://s3.amazonaws.com/h2o-public-test-data/smalldata/airlines/allyears2k.zip" air <- h2o.importFile(file_path, "air") print(dim(air)) # 43978 31 # 查看 DepTime 列的 NA 数量与均值 print(numNAs <- sum(is.na(air$DepTime))) # [1] 1086 DepTime_mean <- mean(air$DepTime, na.rm = TRUE) print(DepTime_mean) # [1] 1345.847 # 1) 均值插补 DepTime 列 h2o.impute(air, "DepTime", method = "mean") # 返回逐列插补值向量,DepTime 位置为 1345.847,其余为 NaN # 2) 按 Dest 分组对 DepTime 做均值插补 air <- h2o.importFile(file_path, "air") h2o.impute(air, "DepTime", method = "mean", by = c("Dest")) # Dest mean_DepTime # 1 ABE 1671.795 # 2 ABQ 1308.074 # ...(共 134 行 x 2 列) # 注意:若 Origin/Distance 的组合在分组后整组为 NA,则该组不插补,NA 保留 # 3) 对 TailNum 分类列做众数插补 air <- h2o.importFile(file_path, "air") h2o.impute(air, "TailNum", method = "mode") # [1] NaN ... NaN 3499 NaN ...(TailNum 位置为 3499) # 4) 按 Month 分组对 TailNum 做众数插补 air <- h2o.importFile(file_path, "air") h2o.impute(air, "TailNum", method = "mode", by = c("Month")) # Month mode_TailNum # 1 1 3499 # 2 10 3499R 端的内部处理流程(见 frame.R)与 Python 端保持一致:
- 列索引统一转换为0 基索引后拼装 AST 表达式
(h2o.impute data col.id method combine_method gb.cols groupByFrame values); - 无分组时走
.eval.scalar()返回标量,有分组(gb.cols != "[]"或传入 groupByFrame)时走.eval.frame()返回分组结果 Frame; - 调用结束后通过
.flush.data(data)与.fetch.data(data, 10L)刷新客户端缓存。
R 端特有限制提醒:
method="median"时暂不支持与by组合使用,会直接报错;combine_method传"lo"/"hi"会被自动转为"low"/"high"。
分组插补 + 结果复用的组合用法(group_by_frame)
仓库 R 测试 runit_h2oimpute.R 演示了一个非常有价值的进阶模式:先用by做一次分组插补并保存返回的 group by 结果 Frame,再把这个结果传给groupByFrame去插补另一列,从而复用同一套分组聚合,避免重复计算:
fr <- as.h2o(iris) h2o.insertMissingValues(fr) # 随机向 iris 中注入缺失值 # 第一次:按第 5 列(Species)分组,插补第 1 列,并保存分组结果 grpdRes <- h2o.impute(fr, 1, by = 5) # 第二次:复用 grpdRes 的分组聚合,插补第 2 列! h2o.impute(fr, 2, groupByFrame = grpdRes) # 第三次:用显式 values 插补整帧剩余缺失 h2o.impute(fr, values = c(1.2, 2.2, 1.3, 0.2, "setosa"))这个模式对应源码中的"双路径"设计:当groupByFrame为null时后端现场执行AstGroup生成分组聚合;当传入预计算 Frame 时直接Gather该 Frame 建立group_impute_map(IcedHashMap<AstGroup.G, Freezable[]>),再以 MRTask 原地回填(AstImpute.java)。对同一数据集多列做同分组插补时,这种复用能显著减少集群端的聚合计算开销。
分布式实现的底层原理:Aggregate + MRTask 原地回填
从实现角度看,一次impute调用在 H2O 集群内部经历如下阶段(可结合 AstImpute.java 全文件阅读):
- 参数解析与校验:解析
col、method、combine_method、by、groupByFrame、values;校验col是否越界、method 是否为 mean/median/mode(ffill/bfill已定义枚举但在当前版本中通过H2O.unimpl抛"未实现"异常,见 AstImpute.java)。column为 -1 时doAllVecs = true,即整帧插补。 - 非分组路径:未传
by/groupByFrame时,直接计算聚合值(数值列vec.mean()、中位数AstMedian.median(...)、分类列AstMode.mode(vec)),或在values已给定情况下直接用用户值,构造res[]数组;然后一个MRTask并行遍历所有 chunk,对每个isNA(row)的单元格set(row, res[c])(AstImpute.java)。 - 分组路径:
AstGroup执行分布式 group-by 聚合(na.rm为"rm");Gather内部类把分组结果 Frame 摊平为IcedHashMap(组 key → 每列插补值),通过reduce()合并各节点局部结果(AstImpute.java);最终 MRTask 依据bycols构造AstGroup.G组键,对非分组列中的 NA 用组内聚合值回填。 - 结果返回:非分组路径返回
ValNums(插补值列表),分组路径返回ValFrame(分组聚合结果 Frame,可继续作为groupByFrame复用)。
正因为聚合与回填都被拆成可并行化的 MRTask,impute在数百 GB、多节点的分布式 Frame 上依然能高效执行——这也是 H2O 插补与单机 pandas 插补在设计上的根本差异。
测试与验证:仓库中的插补用例如何印证行为
仓库为impute提供了多语言测试,可作为验证理解的参考:
- Python:pyunit_impute.py 使用
prostate_missing.csv,验证了均值插补后DPROS列 NA 数量归零,并覆盖了"0 基索引、列名"等不同参数写法的等价性,以及median插补VOL列。 - R:runit_h2oimpute.R 使用
insertMissingValues注入缺失后验证:整帧按 values 插补、按by=5(Species)分组插补第 1 列、用groupByFrame复用分组结果插补第 2 列、再以 values 收尾整帧插补——完整覆盖了本文介绍的全部四种调用形态。 - API 测试:pyunit_h2oH2OFrame_impute.py 从 H2OFrame API 层面进一步验证了
impute的公开接口行为。
如需在自己环境中复现,可以运行python h2o-py/tests/testdir_munging/pyunit_impute.py(Python)或对应的 R 测试脚本,观察插补前后 NA 数量的变化。
关键注意事项速查
- 原地修改:
impute直接改写原 Frame,想保留原始数据务必在插补前备份(重新导入或h2o.assign复制)。 - 列类型约束:String/Time/UUID 列从不参与插补;分类列只能用
mode;数值列可用mean/median。 - 分组整组为 NA 时不插补:若某分组在目标列上全部缺失,则聚合值为 NA,该组 NA 不会被填充。
combine_method只影响median:且仅在偶数样本量下体现差异;其他场景一律忽略。- R 端限制:R 绑定暂不支持
median + by组合;R 端列索引为 1 基,Python 端为 0 基(也支持列名)。 values需与列数匹配:分类列传字符串值时需是该列既有水平,否则客户端直接报错。- 分组结果可复用:带
by的插补返回的分组 Frame 可作为下一次调用的group_by_frame,多列同分组插补时优先复用以省去重复聚合计算。
总结
H2O 的impute是一个兼具"简单统计插补"与"分组上下文插补"能力的分布式数据清洗原语。本文以 imputing-data.rst 为主线,完整覆盖了by、column、combine_method、dataset、group_by_frame、method、values七个参数的语义与边界条件,并下沉到 AstImpute.java 解释了聚合计算、分组映射与 MRTask 原地回填的底层机制,同时给出了 Python、R 两端可复现的实战示例与仓库测试佐证。理解这些细节后,无论是单列均值插补、按业务维度分组插补,还是跨列复用分组结果的高效插补,都能在 H2O 集群上正确且高效地完成。
- 机器学习
- 深度学习
- AutoML
- 大数据
- 后端
【免费下载链接】h2o-3
H2O is an Open Source, Distributed, Fast & Scalable Machine Learning Platform: Deep Learning, Gradient Boosting (GBM) & XGBoost, Random Forest, Generalized Linear Modeling (GLM with Elastic Net), K-Means, PCA, Generalized Additive Models (GAM), RuleFit, Support Vector Machine (SVM), Stacked Ensembles, Automatic Machine Learning (AutoML), etc.
相关推荐
Vega Impute Transform 完全指南:缺失数据的补全原理与实战
Vega Impute Transform 完全指南:缺失数据的补全原理与实战 Vega 的 impute transform 用于对数据集中缺失的数据对象进行
数据可视化Altair Impute 插补变换实战指南:用 transform_impute 补齐缺失数据、修复折线图断点
Altair Impute 插补变换实战指南:用 transform_impute 补齐缺失数据、修复折线图断点 本指南围绕 Altair(Python 声明式
数据可视化TDengine 缺失数据补值(imputation):基于 moment 时序基础模型的自动补齐实战指南
TDengine 缺失数据补值(imputation):基于 moment 时序基础模型的自动补齐实战指南 导读 TDengine 自 v3.3.8.0 起提供
数据库时序数据库物联网大数据实时分析云原生
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考