基于条件众数填充DataFrame中NaN值的问题及优化咨询
问题背景
我有含大量NaN的特征列portarbre:
train_feat["portarbre"].value_counts(dropna=False)
输出结果:
portarbre NaN 12958 Libre 6070 Semi-libre 4449 Architecturé 316 Pyramidale 7 Pleureur 4 Name: count, dtype: int64
同时存在无NaN的分组列adr_secteur:
data["adr_secteur"].value_counts(dropna=False)
输出结果:
adr_secteur 6 6958 5 6728 2 6066 3 4249 4 4128 1 3376 38309 180 38151 92 38421 68 38249 41 38158 9 Name: count, dtype: int64
虽可直接删除含大量NaN的列,但作为练习尝试基于adr_secteur的条件众数填充portarbre的NaN,原代码如下:
mode_sect_df = train_feat.groupby("portarbre")["adr_secteur"].agg(lambda x: x.mode().max()).reset_index() mode_sect_df.columns = ["portarbre", "mode_sect"] for index, row in mode_sect_df.iterrows(): haut_value = row["portarbre"] mode_sect = row["mode_sect"] train_feat.loc[train_feat["adr_secteur"] == mode_sect, "portarbre"] = \ train_feat.loc[train_feat["adr_secteur"] == mode_sect,"portarbre"].fillna(value=haut_value)
填充后出现两个核心问题:
- 部分NaN未填充:原12958个NaN仅填充至剩余4878个,
adr_secteur列无NaN,未排查出代码错误; - 多类别对应同一众数:如基于
hauteurarbre分组求stadededeveloppement的众数时,多个类别众数均为Arbre adulte,循环填充会导致首次填充覆盖所有对应NaN,后续类别无法填充。
现寻求优化方案,避免仅用列众数填充的局限性。
问题根源分析
- 分组逻辑完全倒置:原代码是按
portarbre分组求adr_secteur的众数,这和"基于adr_secteur的条件众数填充portarbre"的目标完全相反,这是大量NaN未被填充的核心原因。 - 循环填充逻辑缺陷:当多个
portarbre类别对应同一adr_secteur时,循环会覆盖之前的填充结果,或者无法处理未被覆盖的NaN。
优化方案
方案1:正确分组的条件众数填充(优先推荐)
直接按adr_secteur分组,计算每组portarbre的众数,再用该众数填充组内NaN,确保每个adr_secteur组都有对应填充值,无遗漏也无覆盖问题。
import pandas as pd # 按adr_secteur分组,计算每组portarbre的众数(多众数时取第一个) sect_mode_map = train_feat.groupby("adr_secteur")["portarbre"].agg( lambda x: x.mode()[0] if not x.mode().empty else None ).to_dict() # 高效填充NaN train_feat["portarbre"] = train_feat["portarbre"].fillna(train_feat["adr_secteur"].map(sect_mode_map))
方案2:多众数场景的加权兜底填充
如果某adr_secteur组内portarbre存在多个众数,可结合全局频率选择填充值;若组内全为NaN,则用全局众数兜底。
import pandas as pd # 计算portarbre的全局众数,作为兜底值 global_mode = train_feat["portarbre"].mode()[0] # 按adr_secteur分组,取组内众数(无则用全局众数) sect_mode_map = train_feat.groupby("adr_secteur")["portarbre"].agg( lambda x: x.mode()[0] if not x.mode().empty else global_mode ).to_dict() # 填充NaN train_feat["portarbre"] = train_feat["portarbre"].fillna(train_feat["adr_secteur"].map(sect_mode_map))
方案3:修复循环逻辑的填充方式(适配原思路)
若要保留类似原代码的循环思路,需调整分组逻辑,并按portarbre全局频率降序填充,避免低频率类别覆盖高频率填充结果。
import pandas as pd # 按adr_secteur分组,获取每组portarbre的所有众数 sect_portarbre_mode = train_feat.groupby("adr_secteur")["portarbre"].agg(lambda x: x.mode().tolist()).reset_index() # 展开多众数数据,并关联全局出现频率 mode_expanded = [] for _, row in sect_portarbre_mode.iterrows(): secteur = row["adr_secteur"] modes = row["portarbre"] for m in modes: if pd.notna(m): mode_expanded.append({ "adr_secteur": secteur, "portarbre_mode": m, "global_count": train_feat["portarbre"].value_counts()[m] }) # 按全局频率降序排序,确保高频率类别先填充,去重保留每个secteur的最优填充值 mode_expanded_df = pd.DataFrame(mode_expanded).sort_values("global_count", ascending=False).drop_duplicates("adr_secteur") # 构建映射字典并填充 sect_mode_map = mode_expanded_df.set_index("adr_secteur")["portarbre_mode"].to_dict() train_feat["portarbre"] = train_feat["portarbre"].fillna(train_feat["adr_secteur"].map(sect_mode_map))
内容的提问来源于stack exchange,提问作者Silvio sjsj
相关产品推荐
相关产品推荐

