You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于条件众数填充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,后续类别无法填充。

现寻求优化方案,避免仅用列众数填充的局限性。


问题根源分析
  1. 分组逻辑完全倒置:原代码是按portarbre分组求adr_secteur的众数,这和"基于adr_secteur的条件众数填充portarbre"的目标完全相反,这是大量NaN未被填充的核心原因。
  2. 循环填充逻辑缺陷:当多个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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 22:29:56