news 2026/6/6 5:31:28

pandas缺失值处理:从NaN语义到业务驱动的填充策略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
pandas缺失值处理:从NaN语义到业务驱动的填充策略

1. 项目概述:为什么缺失值处理不是“填个数”那么简单

在真实世界的数据科学项目里,我见过太多人把缺失值处理当成一个“收尾小动作”——模型跑不通?先df.fillna(0)试一试;EDA卡住了?df.dropna()一键清空。结果呢?上线后指标波动、业务方质疑结论、A/B测试结果不可复现……最后回溯才发现,问题就出在三个月前那个随手写的.fillna(method='ffill')上。这不是危言耸听,而是我在金融风控、电商用户行为、医疗时序数据三个领域踩过至少七次的坑。

“Handling Missing Values in Pandas”这个标题看似平实,但它背后是一整套数据可信度工程。pandas 不是 Excel,NaN也不是空格或零;它是一个有语义的、带传播特性的特殊浮点标记,会参与所有算术运算、比较逻辑和聚合函数,并且默认以“保守失败”方式报错(比如np.mean([1, 2, np.nan])返回nan,而不是跳过)。更关键的是:缺失不是技术故障,而是业务信号。用户没填年龄,可能是隐私顾虑;传感器某时段无读数,可能是设备休眠而非断连;订单表里shipping_address为空,大概率是虚拟商品——这些语义,没有任何自动填充算法能读懂。

所以这篇内容不是教你怎么调用isna()interpolate(),而是带你像数据医生一样思考:先诊断缺失模式(是随机丢失?还是与高收入人群强相关?),再匹配业务逻辑(该删、该补、该标记、还是该建模预测?),最后用 pandas 提供的精确控制能力落地。我会用一个真实的电商退货分析场景贯穿全文:我们有一张含 23 万行、47 列的订单明细表,其中return_reason缺失率达 18%,refund_amount缺失率达 5.2%,而customer_segment字段在 3.7% 的样本中为空。接下来的所有操作,都基于这个具体上下文展开,不讲虚的,只说你明天就能抄作业的细节。

提示:本文所有代码均基于 pandas 2.2+ 和 numpy 1.26+ 测试通过。如果你还在用 pandas 1.x,请务必升级——新版本对缺失值的类型推断(如Int64stringdtype)和链式赋值警告已大幅优化,能帮你提前发现 80% 的隐性错误。

2. 缺失值的本质解构:从NaNpd.NA,再到业务语义

2.1 三种“空”的底层差异,决定你后续每一步是否安全

很多初学者以为NaN就是“空”,其实 pandas 中存在三类本质不同的缺失标识,混用会导致静默错误:

  • np.nan:这是最常见也最危险的。它是 IEEE 754 标准定义的浮点型特殊值,任何与它的比较都返回False(包括np.nan == np.nan),唯一可靠判断方式是pd.isna(x)np.isnan(x)(后者仅限数值型)。它会强制将整数列转为 float64,导致内存翻倍、精度丢失(如 ID 列变成123456789012345.0)。

  • None:Python 原生空对象,可存在于任意 dtype 列中,但 pandas 会将其统一转换为np.nan(数值列)或保留为None(object 列)。问题在于:None在布尔上下文中为False,而np.nanTrue,这会导致df[df.col > 10]这类过滤意外包含/排除缺失行。

  • pd.NA:pandas 1.0+ 引入的“真缺失值”,专为可空类型设计(如Int64,string,boolean)。它遵循三值逻辑:True,False,Unknown。最关键的是:pd.NA参与运算时,结果恒为pd.NA(如pd.NA + 5 → pd.NA),不会污染其他值,且支持dropna(how='all')等精细控制。

我用一个实测案例说明区别:

import pandas as pd import numpy as np # 构造对比数据 df = pd.DataFrame({ 'int_col': [1, 2, None, 4], # object dtype 'int64_col': pd.array([1, 2, None, 4], dtype="Int64"), # 可空整数 'float_col': [1.0, 2.0, np.nan, 4.0], 'str_col': ['a', 'b', None, 'd'], 'string_col': pd.array(['a', 'b', None, 'd'], dtype="string") }) # 查看 dtype 和缺失值表现 print(df.dtypes) # int_col object # int64_col Int64 ← 注意大小写 # float_col float64 # str_col object # string_col string # 关键测试:sum() 行为 print(df.sum(numeric_only=True)) # int_col 7.0 ← object 列被转为 float 后求和 # int64_col 7.0 ← 正确求和,None 被忽略 # float_col 7.0 ← np.nan 被忽略 # str_col abd ← object 列字符串拼接! # string_col abd ← string 列正确拼接

看到没?object列的Nonesum()中被当作字符串拼接,而Int64列的pd.NA被正确忽略。这就是为什么我坚持要求团队:所有可能含缺失的整数字段,必须显式声明为"Int64";所有文本字段,必须用"string"dtype。这不是炫技,是避免生产环境出现TypeError: can only concatenate str (not "float") to str这类低级错误的底线。

2.2 缺失模式分类:比“有多少缺失”重要一百倍

缺失值处理的第一步,永远不是填或删,而是模式诊断。我用missingno库配合自定义统计,总结出四类高频模式(附真实数据截图逻辑):

模式类型特征业务含义pandas 诊断命令
MCAR(完全随机缺失)缺失位置与所有变量无关,均匀分布数据采集系统性故障(如某天日志服务宕机)df.isna().mean().describe()+sns.heatmap(df.isna())
MAR(随机缺失)缺失概率依赖于观测到的变量(如高客单用户更不愿填地址)用户主动选择,含行为偏好信号df.groupby('customer_tier')['address_filled'].mean()
MNAR(非随机缺失)缺失本身携带信息(如return_reason为空 = 用户拒绝说明,可能涉及投诉)最危险!直接删除会引入严重偏差df[df.return_reason.isna()]['order_value'].describe()对比全量
结构化缺失多字段联合缺失(如shipping_datetracking_number同时为空)业务流程分支(虚拟商品无需物流)df.isna().groupby(['shipping_date','tracking_number']).size()

在我们的电商退货数据中,return_reason.isna()order_value > 5000高度相关(OR=4.2),且return_reason.isna()样本的refund_amount中位数比非空样本高 37%。这明确指向 MNAR:用户对高价订单退货更抵触说明原因,而平台客服也更倾向直接全额退款息事宁人。此时若用dropna()删除,会低估高价值用户的退货率;若用众数填充("产品质量问题"),则彻底扭曲归因分析。

实操心得:我写了一个 15 行的diagnose_missing()函数(见文末工具包),它会自动输出三张表:① 各列缺失率排序;② 缺失值与关键数值列的相关系数矩阵;③ 缺失组合的频次 Top10。这个函数已帮我们团队在 3 个项目中提前识别出 MNAR 风险,避免了模型上线后的重大返工。

2.3 为什么inplace=True是反模式?链式赋值的血泪教训

新手最爱写df.fillna(0, inplace=True),但这是 pandas 官方文档明确标注为“不推荐”的用法。原因有二:

  1. 内存效率陷阱inplace=True并不节省内存。pandas 仍需创建临时副本执行操作,再将结果覆盖原对象。实测 100 万行数据,inplace=Truedf = df.fillna(0)慢 12%,内存峰值高 18%。

  2. 链式赋值灾难df[col].fillna(0, inplace=True)在某些条件下会触发SettingWithCopyWarning,且修改可能不生效。根本原因是 pandas 无法确定df[col]是视图(view)还是副本(copy)。我曾遇到一个诡异 bug:对df.query("status=='shipped'")['discount']fillna(0, inplace=True),本地环境正常,生产环境却完全没变——因为生产数据索引是非连续的,.query()返回了副本。

正确姿势永远是:显式赋值 +copy()明确意图

# ✅ 推荐:清晰、安全、可调试 df = df.copy() # 先声明你要操作副本 df['discount'] = df['discount'].fillna(0) # 显式赋值,无歧义 # ✅ 进阶:用 assign() 实现函数式编程(推荐用于 pipeline) df = (df .assign( discount=lambda x: x['discount'].fillna(0), return_reason=lambda x: x['return_reason'].fillna('unknown') ) .pipe(lambda x: x[x['order_value'] > 0]) # 后续操作 )

assign()的优势在于:它返回新 DataFrame,天然避免副作用;支持 lambda 表达式,可访问整个 DataFrame;与pipe()组合可构建可复现的数据处理流水线。我们团队所有 ETL 脚本都强制使用assign(),代码审查时只要看到inplace=True就打回重写。

3. 四大核心策略详解:从删除到建模,每一步都有取舍

3.1 删除策略:何时该删,怎么删才不丢关键信息

删除是最激进也最易误用的策略。关键原则:删除必须有业务依据,不能仅因“方便”

3.1.1 行删除:dropna()的 7 种死法与 1 条活路

df.dropna()有 5 个核心参数,但 90% 的人只用how='any'。这就像用消防斧切蛋糕——太粗暴。我们按风险等级排序:

  • how='all'(最低风险):仅删除全行为 NaN 的记录。适合清理导入错误(如 Excel 空行被读成全 NaN)。

    # ✅ 安全:只删真正无效的空行 df_clean = df.dropna(how='all')
  • subset=['col1','col2'](中等风险):指定关键列,仅当这些列全空时才删。这是我们退货分析的起点:

    # ✅ 合理:订单必须有 order_id 和 customer_id 才有效 df_clean = df.dropna(subset=['order_id', 'customer_id']) # 删除后检查:确保没误删 print(f"删除 {len(df)-len(df_clean)} 行,剩余 {len(df_clean)} 行")
  • thresh=N(高风险预警):保留至少 N 个非空值的行。看似智能,实则危险——它会保留部分缺失严重的行,导致后续分析噪声增大。

    # ⚠️ 谨慎:以下代码会让 47 列中只有 5 列有值的行也被保留 df_clean = df.dropna(thresh=5) # 错!应设为 40+
  • how='any'(最高风险):默认选项,也是最常误用的。它会删除任意一列为空的行。在我们的数据中,return_reason缺失率 18%,shipping_date缺失率 22%,dropna(how='any')会直接干掉 36% 的数据(18%+22%-重叠),而重叠部分恰恰是高价值用户集中区。

真正的活路是:用布尔索引做精准删除

# ✅ 黄金法则:删除必须有业务规则支撑 # 规则1:订单金额为0的记录无效(可能是测试单) mask_invalid_amount = df['order_value'] == 0 # 规则2:创建时间早于系统上线日(2020-01-01) mask_old_date = pd.to_datetime(df['created_at']) < '2020-01-01' # 规则3:关键字段缺失且无法补全(如无 customer_id 且无 phone) mask_no_id = df['customer_id'].isna() & df['phone'].isna() # 合并删除条件(注意:用 ~ 取反) df_clean = df[~(mask_invalid_amount | mask_old_date | mask_no_id)].copy() print(f"应用业务规则后,删除 {len(df)-len(df_clean)} 行")
3.1.2 列删除:当“留着比删了更糟”

列删除常被忽视,但它解决的是维度灾难。判断标准:该列缺失率 > 60% 且无业务强解释性。例如我们的warehouse_code字段缺失率 68%,但它是物流调度核心字段,不能删;而marketing_campaign_id缺失率 72%,且缺失样本的转化率与非缺失组无显著差异(t-test p=0.83),这就是可删列。

# ✅ 自动识别可删列(基于缺失率+业务权重) def auto_drop_columns(df, threshold=0.6, business_critical=None): if business_critical is None: business_critical = ['order_id', 'customer_id', 'order_value'] missing_rate = df.isna().mean() candidates = missing_rate[missing_rate > threshold].index.tolist() # 过滤掉业务关键列 safe_to_drop = [col for col in candidates if col not in business_critical] if safe_to_drop: print(f"建议删除列(缺失率>{threshold*100:.0f}%):{safe_to_drop}") return df.drop(columns=safe_to_drop) return df df_clean = auto_drop_columns(df_clean, threshold=0.6)

3.2 填充策略:从简单均值到业务规则驱动的智能填充

填充不是“补空”,而是注入业务知识。我按可靠性从高到低排列:

3.2.1 常量填充:最安全,但最易被滥用

fillna(0)fillna('unknown')仅适用于两类场景:

  • 数值型:该字段天然有“零值”语义(如discount_amount为空 = 未使用优惠券,填 0 合理)
  • 分类型:该字段有明确“未知”类别(如customer_segment为空 = 未打标用户,填'unlabeled''other'更准确)
# ✅ 正确:discount_amount 为空即未使用优惠 df_clean['discount_amount'] = df_clean['discount_amount'].fillna(0) # ✅ 正确:segment 为空即未打标,需与业务方确认标签体系 df_clean['customer_segment'] = df_clean['customer_segment'].fillna('unlabeled') # ❌ 错误:order_value 为空填 0 —— 这等于假设所有缺失订单都是免费的! # 正确做法:标记为缺失,后续建模处理 df_clean['order_value_missing'] = df_clean['order_value'].isna()
3.2.2 统计填充:均值/中位数/众数,必须加业务校验

均值填充对异常值敏感,中位数对长尾分布友好,众数仅适用于离散型。但关键在分组填充——全局均值毫无意义。

# ✅ 黄金实践:按业务维度分组填充 # 场景:不同城市运费差异大,用城市均值填充 shipping_cost city_mean = df_clean.groupby('city')['shipping_cost'].transform('mean') df_clean['shipping_cost'] = df_clean['shipping_cost'].fillna(city_mean) # ✅ 进阶:用分位数填充(避免均值被极端值拉偏) # 例如:高价值用户运费中位数是 25 元,普通用户是 12 元 df_clean['shipping_cost'] = df_clean.groupby('customer_tier')['shipping_cost'].apply( lambda x: x.fillna(x.quantile(0.5)) # 用中位数 ) # ⚠️ 必须校验:填充后分布是否合理? filled_data = df_clean[df_clean['shipping_cost'].isna() == False]['shipping_cost'] print(f"填充后运费均值:{filled_data.mean():.2f},标准差:{filled_data.std():.2f}") # 若 std 突增 50%,说明分组粒度太粗,需细化(如 city+tier)
3.2.3 前向/后向填充:仅适用于有序时间序列

ffill()bfill()的本质是用最近的有效观测值替代,这隐含两个强假设:① 时间上相邻的观测相似;② 缺失是短暂中断。在电商订单中,它只适用于user_session_id这类会话标识字段(同一用户连续点击,session_id 应一致)。

# ✅ 合理:按用户+时间排序后填充 session_id df_sorted = df_clean.sort_values(['customer_id', 'created_at']) df_sorted['user_session_id'] = df_sorted.groupby('customer_id')['user_session_id'].ffill() # ❌ 危险:对 order_value 用 ffill() —— 用户 A 的订单金额绝不能用来填充用户 B 的订单!
3.2.4 插值填充:interpolate()的 3 个致命误区

interpolate()常被误认为“高级填充”,但它只适用于数值型、单调递增/递减、缺失连续的场景。三大误区:

  1. 误用 method 参数method='linear'假设线性关系,但order_value在促销日会阶梯式跳变,应改用method='values'(按索引插值)或method='time'(按时间戳插值)。

  2. 忽略 limit_direction:默认limit_direction='forward',但退货分析中refund_amount缺失常出现在订单末尾(用户还没退款),需limit_direction='backward'

  3. 未限制插值长度limit=1防止跨天插值(如周一订单缺运费,周二订单有运费,但两者无业务关联)。

# ✅ 安全插值:按时间戳,向后插值,最多补 1 个 df_clean['refund_amount'] = df_clean.set_index('created_at')['refund_amount'].interpolate( method='time', limit_direction='backward', limit=1 ).reset_index()['refund_amount']

3.3 标记策略:把缺失本身变成特征

这是最被低估的策略。缺失不是缺陷,而是最强的用户行为信号之一。在风控模型中,“身份证号缺失”比“年龄=25”更能预测欺诈。

# ✅ 创建缺失指示特征(indicator features) for col in ['return_reason', 'shipping_address', 'coupon_code']: df_clean[f'{col}_is_missing'] = df_clean[col].isna().astype('boolean') # 用 boolean dtype # ✅ 组合缺失特征(业务洞察) # 高价值用户更可能因隐私拒绝填地址,但又更可能用优惠券 df_clean['high_value_privacy_flag'] = ( (df_clean['order_value'] > df_clean['order_value'].quantile(0.9)) & df_clean['shipping_address_is_missing'] & ~df_clean['coupon_code_is_missing'] ).astype('boolean') # ✅ 缺失率聚合特征(群体行为) # 计算每个用户的历史地址缺失率(需按时间排序) df_sorted = df_clean.sort_values(['customer_id', 'created_at']) df_sorted['addr_missing_rate_30d'] = df_sorted.groupby('customer_id')['shipping_address_is_missing'].apply( lambda x: x.rolling('30D', on=df_sorted.loc[x.index, 'created_at']).mean() )

3.4 建模填充:用机器学习预测缺失值

当缺失与多变量强相关时,建模是最优解。但必须遵守铁律:训练集和测试集严格分离,且仅用缺失字段的预测因子

from sklearn.ensemble import RandomForestRegressor from sklearn.preprocessing import OneHotEncoder from sklearn.compose import ColumnTransformer # 步骤1:识别可用于预测 return_reason 的特征(排除所有含 return_reason 的衍生列) predictors = ['order_value', 'customer_tier', 'product_category', 'days_since_first_order'] target = 'return_reason' # 步骤2:仅用非缺失样本训练(避免数据泄露) train_mask = ~df_clean[target].isna() X_train = df_clean.loc[train_mask, predictors] y_train = df_clean.loc[train_mask, target] # 步骤3:对分类目标,用 RandomForestClassifier(return_reason 是文本,需先编码) encoder = OneHotEncoder(handle_unknown='ignore') X_train_encoded = encoder.fit_transform(X_train.select_dtypes(include='object')) # 步骤4:预测缺失值(注意:只预测缺失行) test_mask = df_clean[target].isna() X_test = df_clean.loc[test_mask, predictors] y_pred = model.predict(X_test) # 模型已训练 # 步骤5:安全赋值(用 loc 避免 SettingWithCopyWarning) df_clean.loc[test_mask, target] = y_pred

注意事项:建模填充必须做反事实验证。例如,将预测出的return_reason为 “尺寸不合适” 的订单,与真实填写该原因的订单对比avg_refund_rate,若差异 >15%,说明模型不可靠,应回退到标记策略。

4. 实战全流程:从原始数据到可交付分析集

4.1 初始化:加载与基础清洗

我们从一个模拟的原始 CSV 开始(实际项目中可能是 Hive 表或 API 返回 JSON):

# 加载原始数据(模拟) import pandas as pd import numpy as np # 创建模拟数据(23 万行,含典型缺失模式) np.random.seed(42) n_rows = 230000 data = { 'order_id': range(1, n_rows+1), 'customer_id': np.random.choice(range(1, 50000), n_rows), 'order_value': np.random.lognormal(8, 1.2, n_rows), # 长尾分布 'created_at': pd.date_range('2023-01-01', periods=n_rows, freq='10T'), 'product_category': np.random.choice(['electronics', 'clothing', 'home'], n_rows, p=[0.4, 0.35, 0.25]), 'customer_tier': np.random.choice(['vip', 'premium', 'standard'], n_rows, p=[0.1, 0.3, 0.6]), 'return_reason': np.random.choice( ['quality', 'size', 'late_delivery', 'wrong_item', np.nan], n_rows, p=[0.25, 0.25, 0.2, 0.1, 0.2] # 20% 缺失 ), 'refund_amount': np.random.normal(150, 80, n_rows), 'shipping_address': np.random.choice(['addr1', 'addr2', np.nan], n_rows, p=[0.4, 0.4, 0.2]) } df = pd.DataFrame(data) # 引入 MNAR:高价值订单更倾向缺失 return_reason high_value_mask = df['order_value'] > df['order_value'].quantile(0.9) df.loc[high_value_mask & (np.random.rand(high_value_mask.sum()) < 0.6), 'return_reason'] = np.nan # 保存为原始文件(模拟生产环境) df.to_csv('raw_orders.csv', index=False)

4.2 第一阶段:缺失诊断与业务规则制定

# ✅ 步骤1:运行诊断函数(文末提供完整代码) from missing_diagnosis import diagnose_missing report = diagnose_missing(df) print(report['summary']) # 缺失率排序 print(report['correlation']) # 与 order_value 相关性 # 输出关键发现: # - return_reason 缺失率 22.3%,与 order_value 相关系数 0.41(强正相关) # - refund_amount 缺失率 5.2%,与 return_reason 缺失高度重合(Jaccard=0.89) # - shipping_address 与 customer_tier 强相关(vip 用户地址缺失率仅 5%,standard 达 32%) # ✅ 步骤2:制定业务规则(与产品/运营团队对齐) rules = { 'drop_rows': [ {'condition': 'order_value == 0', 'reason': '测试订单'}, {'condition': 'created_at < "2023-01-01"', 'reason': '数据迁移错误'} ], 'drop_cols': ['warehouse_code'], # 缺失率 68%,且与核心指标无关 'fill_constants': { 'discount_amount': 0, 'customer_segment': 'unlabeled' }, 'fill_groupby': { 'shipping_cost': {'group': ['customer_tier', 'product_category'], 'method': 'median'} } }

4.3 第二阶段:分层处理流水线

# ✅ 构建可复现的处理流水线 def process_orders(df_raw, rules): df = df_raw.copy() # Step 1: 删除无效行 for rule in rules['drop_rows']: mask = eval(rule['condition']) df = df[~mask].copy() print(f"删除 {rule['reason']}:{mask.sum()} 行") # Step 2: 删除无效列 if rules['drop_cols']: df = df.drop(columns=rules['drop_cols'], errors='ignore') # Step 3: 常量填充 for col, val in rules['fill_constants'].items(): if col in df.columns: df[col] = df[col].fillna(val) print(f"常量填充 {col} = {val}") # Step 4: 分组填充(核心!) for col, config in rules['fill_groupby'].items(): if col in df.columns: # 动态生成分组键 group_keys = config['group'] fill_method = config['method'] # 处理分组键中可能存在的缺失 valid_mask = df[group_keys].notna().all(axis=1) df_valid = df[valid_mask].copy() # 计算分组统计量 if fill_method == 'median': filler = df_valid.groupby(group_keys)[col].transform('median') else: filler = df_valid.groupby(group_keys)[col].transform('mean') # 安全填充(只填缺失且分组有效的行) fill_mask = df[col].isna() & df[group_keys].notna().all(axis=1) df.loc[fill_mask, col] = filler[fill_mask].values print(f"分组填充 {col}({group_keys}):{fill_mask.sum()} 处") # Step 5: 创建缺失特征 for col in ['return_reason', 'shipping_address']: if col in df.columns: df[f'{col}_is_missing'] = df[col].isna().astype('boolean') return df # 执行流水线 df_processed = process_orders(df, rules) print(f"原始 {len(df)} 行 → 处理后 {len(df_processed)} 行")

4.4 第三阶段:验证与交付

# ✅ 验证黄金指标(必须做!) def validate_processed_data(df_original, df_processed): # 1. 关键统计量漂移检查 orig_stats = df_original['order_value'].describe() proc_stats = df_processed['order_value'].describe() drift = abs(orig_stats['mean'] - proc_stats['mean']) / orig_stats['mean'] print(f"order_value 均值漂移:{drift:.2%}") # 2. 缺失率变化(应下降但不为零) orig_missing = df_original['return_reason'].isna().mean() proc_missing = df_processed['return_reason'].isna().mean() print(f"return_reason 缺失率:{orig_missing:.1%} → {proc_missing:.1%}") # 3. 业务逻辑检查:高价值用户退货率是否合理? high_value = df_processed['order_value'] > df_processed['order_value'].quantile(0.9) return_rate_high = df_processed[high_value & df_processed['return_reason'].notna()].shape[0] / high_value.sum() print(f"高价值用户退货率:{return_rate_high:.1%}") # 4. 内存优化(释放 object 列内存) for col in df_processed.select_dtypes('object').columns: if df_processed[col].nunique() / len(df_processed) < 0.5: df_processed[col] = df_processed[col].astype('category') return df_processed df_final = validate_processed_data(df, df_processed) df_final.to_parquet('processed_orders.parquet', index=False) print("✅ 处理完成!已保存为 parquet(体积减少 62%)")

5. 常见问题与避坑指南:那些没人告诉你的细节

5.1 为什么df.isna().sum()df.isnull().sum()结果不同?

答案是:它们完全相同isna()isnull()的别名,源码中isna = isnull。但很多人误以为不同,是因为混淆了pd.isna()(函数)和df.isna()(方法)。前者可作用于任意对象,后者只作用于 DataFrame。更隐蔽的坑是:np.isnan()不能用于字符串或布尔型,会报错,而pd.isna()可以。

# ✅ 安全写法:永远用 pd.isna() mask = pd.isna(df['return_reason']) # 任何 dtype 都行 # ❌ 危险写法: mask = np.isnan(df['return_reason']) # 字符串列直接报错

5.2fillna()为什么有时不生效?5 个排查步骤

这是最高频问题。按顺序排查:

  1. 检查 dtypeobject列中的'nan'字符串不是缺失值,fillna()无效。用df[col] = df[col].replace('nan', np.nan)先清洗。
  2. 检查索引对齐df1.fillna(df2['col'])要求索引完全匹配,否则填充 NaN。用df1.fillna(df2['col'].reindex(df1.index))
  3. 检查链式赋值df[df.col > 10]['col'].fillna(0)不生效。改用df.loc[df.col > 10, 'col'] = df.loc[df.col > 10, 'col'].fillna(0)
  4. 检查 inplace 参数inplace=True在某些 pandas 版本中对Series无效,统一用显式赋值。
  5. 检查 NaN 类型pd.NAnp.nan不互通。df.fillna(0)不会填充pd.NA,需df.fillna(0, downcast=False)或先转 dtype。

5.3 如何处理嵌套 JSON 中的缺失?

电商数据常含metadata字段(JSON 字符串),其中{"shipping":{"carrier":"SF"}}{"shipping":{}}都算缺失 carrier。

# ✅ 安全解析 JSON 缺失 import json def extract_json_field(series, path, default=None): def safe_get(obj, key_path): try: keys = key_path.split('.') for k in keys: if isinstance(obj, dict) and k in obj: obj = obj[k] else: return default return obj if pd.notna(obj) else default except: return default return series.apply(lambda x: safe_get(json.loads(x) if isinstance(x, str) else {}, path)) # 应用 df['carrier'] = extract_json_field(df['metadata'], 'shipping.carrier', 'unknown')

5.4 性能优化:百万行数据的缺失处理技巧

  • 避免apply()df['col'].apply(lambda x: ...)比向量化慢 100 倍。用np.where()pd.cut()替代。
  • query()替代布尔索引df.query('col > 10')df[df.col > 10]快 30%。
  • 分块处理:`
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/6 5:31:09

2026年京东云OpenClaw/Hermes Agent配置Token Plan超详细集成教程

2026年京东云OpenClaw/Hermes Agent配置Token Plan超详细集成教程。OpenClaw是开源的个人AI助手&#xff0c;Hermes Agent则是一个能自我进化的AI智能体框架。阿里云提供计算巢、轻量服务器及无影云电脑三种部署OpenClaw 与 Hermes Agent的方案、百炼Token Plan兼容主流 AI 工具…

作者头像 李华
网站建设 2026/6/6 5:30:44

WinUtil:Windows系统管理的终极图形化解决方案

WinUtil&#xff1a;Windows系统管理的终极图形化解决方案 【免费下载链接】winutil Chris Titus Techs Windows Utility - Install Programs, Tweaks, Fixes, and Updates 项目地址: https://gitcode.com/GitHub_Trending/wi/winutil 你是否曾为Windows系统的繁琐配置而…

作者头像 李华
网站建设 2026/6/6 5:30:35

MATLAB Simulink电动汽车V2G双向能量调度仿真工程包

本文还有配套的精品资源&#xff0c;点击获取 简介&#xff1a;直接可用的MATLAB/Simulink V2G仿真环境&#xff0c;含完整模型文件vvvgg.slx和编译缓存vvvgg.slxc&#xff0c;支持电动汽车向电网反向供电、功率动态调节、SOC约束控制及分时电价响应等核心功能。项目自带sl_…

作者头像 李华
网站建设 2026/6/6 5:30:09

ESP8266玩转像素动画:用TFT_eSPI的Sprite类在1.44寸屏上做游戏和仪表盘

ESP8266像素动画实战&#xff1a;用TFT_eSPI Sprite打造1.44寸屏的极客玩具当一块1.44寸的ST7735屏幕遇上NodeMCU的ESP8266&#xff0c;再配合TFT_eSPI库的Sprite类&#xff0c;这个看似简陋的组合却能迸发出令人惊喜的创意火花。不同于传统的静态界面开发&#xff0c;Sprite技…

作者头像 李华
网站建设 2026/6/6 5:29:47

AI编排:企业级LLM落地的数据调度中枢

1. 项目概述&#xff1a;当企业级集成遇上大模型&#xff0c;为什么需要“AI编排”这个新角色 我在做企业系统集成的第十个年头&#xff0c;亲手搭过上百套CRM-ERP对接流程&#xff0c;也踩过无数API调用超时、数据字段错位、权限配置失效的坑。但过去两年最让我坐不住的&#…

作者头像 李华