数据缺失值智能填充专家

0 浏览
0 试用
0 购买
Nov 28, 2025更新

本提示词专为数据清洗与预处理场景设计,帮助商业数据运营者高效处理数据集中的缺失值问题。通过系统分析数据特征、缺失模式及业务背景,智能推荐最适合的填充策略,确保数据完整性和质量。该提示词结合统计方法与业务逻辑,提供从缺失模式诊断到策略选择的全流程指导,支持数值型、分类型和时间序列等多种数据类型,最终输出具体可操作的填充方案及实施建议,提升数据分析和建模的准确性与可靠性。

缺失值分析报告

  • 数据集概况
    • 规模:8条记录,9个字段
    • 字段类型:日期型(signup_date)、分类型(gender, city)、数值型(age)、等级型(income_bracket: L1-L5)、货币型(monthly_spend)、布尔型(premium_user)、评分型(satisfaction_score: 1-5)
    • 缺失概览(条数/占比)
      • signup_date: 1/8=12.5%
      • gender: 2/8=25.0%
      • age: 1/8=12.5%
      • income_bracket: 2/8=25.0%
      • monthly_spend: 2/8=25.0%
      • premium_user: 2/8=25.0%
      • city: 1/8=12.5%
      • satisfaction_score: 2/8=25.0%
  • 缺失模式分析(按字段)
    • signup_date:主要因埋点延迟,倾向MAR(与时间/采集流程相关)
    • gender:隐私拒答,倾向MNAR(与用户特征相关);建模时可按“近似MAR”(条件于city/age/spend等)处理并保留缺失指示
    • age:问卷缺失,倾向MAR(受性别/城市/收入影响)
    • income_bracket:隐私拒答,倾向MNAR(与真实收入相关);拟合时建议使用条件模型+缺失指示
    • monthly_spend:埋点/延迟或早期空值,倾向MAR(与注册时间/付费状态相关)
    • premium_user:延迟标记,倾向MAR(与消费/注册时间相关)
    • city:定位失败/延迟,倾向MAR(与设备/网络相关)
    • satisfaction_score:自愿题,倾向MNAR(高/低满意度人群更可能不答);需保守填充并加入缺失指示
  • 业务影响评估
    • 城市与性别需保持分布稳定:对分群/地域投放、性别偏好建模敏感
    • 需保持age与monthly_spend相关性:影响LTV与留存/付费预测的核心关系
    • satisfaction_score用作标签侧特征:避免过度乐观填充(防止高估满意度带来模型偏乐观)
    • premium_user与monthly_spend互相关:错误填充将造成系统性偏差

填充策略推荐

总体原则

  • 先建立“缺失指示”特征;对MNAR可在模型中显式使用
  • 混合方法:KNN预测填充(连续/分类)、回归/分类模型(带概率抽样),必要时使用分层众数/中位数作为备选
  • 分布与相关性约束:性别/城市使用“边际分布保持”的概率抽样;age—monthly_spend采用KNN/回归互预测,避免均值填充的相关性收缩

首选方案(与偏好一致:KNN预测填充 + 回归预测填充 + 边际分布保持)

  • signup_date(日期型)

    • 方法:KNN回归热卡填充(hot-deck);目标为日期序列号(天),邻域基于 city, gender, age, income_bracket, monthly_spend, premium_user
    • 参数:n_neighbors=3(样本少),weights='distance',特征标准化;结果四舍五入到日,并截断在观测日期范围内
    • 理由:保持日期分布与同类用户一致,避免集中到中位数导致的季节性扭曲
  • gender(分类型)

    • 方法:KNN分类 + “边际分布保持”的概率抽样
    • 参数:n_neighbors=3, weights='distance';对缺失样本按模型预测概率进行有配额的抽样,使填充后全体gender的M/F/Other边际分布接近观测分布
    • 理由:减少系统性偏差并保持性别分布稳定
  • city(分类型)

    • 方法:同gender,KNN分类 + 边际分布保持抽样(按Shenzhen/Guangzhou/Hangzhou观测占比)
    • 理由:城市分布稳定性要求
  • premium_user(布尔型)

    • 方法:KNN分类或逻辑回归分类 + 概率抽样;可做边际保持(使总TRUE比例接近观测)
    • 参数:KNN n_neighbors=3, weights='distance';或LogisticRegression(C=1.0, penalty='l2')并做概率抽样
    • 理由:延迟标记为MAR,用条件概率抽样可降低偏差
  • income_bracket(等级型)

    • 方法:KNN分类(对L1-L5进行1-5有序编码),邻域包含age、monthly_spend、premium_user、city、gender、signup_date;预测后再映射回L1-L5
    • 参数:n_neighbors=5, weights='distance';优先选择按距离加权的众数
    • 理由:与消费、年龄有强相关,KNN能保持局部有序结构;避免简单众数导致的阶层塌陷
  • age(数值型)

    • 方法:KNN回归,特征含monthly_spend, income_bracket(1-5), premium_user, city, gender, signup_date(天序数)
    • 参数:n_neighbors=5, weights='distance';结果截断在[18,60]
    • 理由:与消费/收入等级相关,KNN可较好保持age—spend相关性
  • monthly_spend(货币型)

    • 方法:对log1p(spend)做KNN回归,特征含age, income_bracket, premium_user, city, gender, signup_date
    • 参数:n_neighbors=5, weights='distance';预测后反变换expm1并下限截断为0
    • 理由:对偏态/零值鲁棒,保留与age/收入等级的相关性
  • satisfaction_score(评分型1-5)

    • 方法:KNN邻域分位数填充(保守):基于同上特征在有评分样本中找K=7近邻,取加权第40百分位(τ=0.4),四舍五入并夹在[1,5];同时添加satisfaction_missing指示
    • 参数:n_neighbors=7, weights=1/distance;τ=0.4(保守,防止过度乐观)
    • 理由:MNAR且为标签侧特征,采用偏下分位防乐观偏差,并保留缺失指示供模型识别非响应模式
  • 预期效果

    • 完整性:缺失值基本消除
    • 分布稳定性:city、gender边际分布与观测近似一致(误差<1-2个点)
    • 关系保持:age—monthly_spend相关性衰减最小(相对均值填充)
    • 保守性:satisfaction不被系统性抬高

备选方案

  • 分类众数填充(gender/city)+ 缺失指示
    • 适用:极少量缺失或KNN邻域过稀
    • 要点:在分层(如city内)计算众数;保持总体配额(按观测占比分配缺失样本)
    • 效果:实现简单但信息损失较大,不能利用条件结构
  • 回归预测填充(age/spend)使用线性回归/岭回归
    • 适用:样本量较大、线性关系较明显的场景
    • 要点:对spend用log1p,交叉验证选择正则;预测区间外截断
    • 效果:可解释性强,但对非线性/局部结构不如KNN
  • 常量填充(signup_date)
    • 适用:KNN不可行时,用“同城中位注册日”作为常量;并加date_missing指示
    • 要点:每个city单独计算中位数;超出范围则回退全局中位数
    • 效果:季节性可能略被削弱,作为兜底方案

详细实施指南

  • 数据预处理要求

    • 统一类型:布尔转0/1,等级型L1-L5映射为1-5;日期转为天序数(与全局最小日期的天差)
    • 添加缺失指示列:对于存在缺失的字段均添加 is__missing(二元)
    • 特征缩放:数值型用于KNN需标准化(StandardScaler)
    • 类别编码:gender/city独热编码,用“UNK”表示临时缺失(仅作为其他列的预测特征)
  • 填充操作步骤

    1. 建立观测边际分布:gender、city(基于非缺失样本)
    2. signup_date:KNN回归填充日期序数(邻域包含city/gender/age/income/spend/premium)
    3. gender、city:KNN分类得到每类概率,使用“边际分布保持”的配额分配(概率加权的贪心匹配)
    4. premium_user:KNN或逻辑回归概率抽样;如需总体占比稳定,也可做边际保持
    5. income_bracket:KNN分类(1-5)后映射回L1-L5
    6. age:KNN回归,结果夹在[18,60]
    7. monthly_spend:对log1p回归,反变换后≥0
    8. satisfaction_score:KNN近邻的加权第40百分位,四舍五入与夹取;同时保留缺失指示
    9. 复核约束:类型、取值范围、分布偏离(KS/卡方)、age—spend相关系数对比
  • 结果验证方法与指标

    • 分布保持
      • gender/city:总体占比变化 |Δp| ≤ 1-2个百分点
      • KS检验(连续:age/spend)或卡方(city/gender/income)对比“观测 vs 观测+填充”,p值>0.05为佳
    • 关系保持
      • age–monthly_spend Pearson/Spearman:与仅用完整样本计算的基线差异 |Δρ| ≤ 0.05-0.10
    • 预测一致性
      • 若有下游模型:留存/流失AUC/Logloss在填充前后变化≤1-2个百分点
    • 稳健性
      • 使用留一法/交叉验证评估KNN/回归填充误差(MAE/RMSE或分类准确率)
  • 常见问题与处理

    • 邻域过稀:降低n_neighbors或放宽特征集,必要时回退到分层中位/众数
    • 概率抽样波动:固定随机种子,或在边际保持算法中使用配额控制
    • 极端值回传:spend使用log1p+剪裁;age强制范围裁剪;日期强制落在观测区间

质量保证建议

  • 评估指标
    • 缺失减少率:填充后缺失占比→0
    • 分布漂移:Jensen-Shannon距离/KS统计量;类别卡方统计
    • 相关性保持:关键对(age, monthly_spend)的ρ差异
    • 模型稳定性:下游验证集AUC/MAE变化
  • 异常值检测
    • IQR/箱线图规则或基于残差(回归预测值±3σ)
    • 分类一致性检查:income与spend、premium的逻辑一致性(如L1却极高消费需复核)
  • 监控与优化
    • 持续监控边际分布变化(性别/城市月度趋势)
    • 周期性重新拟合KNN/回归模型
    • 若样本增大,升级为多重插补(MICE)以量化不确定性

Python填充示例(可直接运行的最小可行实现)

说明:

  • 采用pandas/numpy/scikit-learn
  • KNN用于回归与分类
  • 提供“边际分布保持”的贪心分配器
  • 对评分使用KNN加权分位数填充(τ=0.4)
import numpy as np
import pandas as pd
from io import StringIO
from datetime import datetime, timedelta
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsRegressor, KNeighborsClassifier

# ---------- 1) 读入数据 ----------
raw = """user_id,signup_date,gender,age,income_bracket,monthly_spend,premium_user,city,satisfaction_score
U1001,2023-11-05,F,28,L2,89.3,TRUE,Shenzhen,5
U1002,,M,35,L3,120.5,,Guangzhou,4
U1003,2024-01-12,,27,,75.0,FALSE,Shenzhen,
U1004,2023-12-30,F,,L1,,FALSE,,3
U1005,2024-02-10,Other,22,L2,45.0,TRUE,Shenzhen,5
U1006,2024-02-15,M,31,L4,,TRUE,Hangzhou,4
U1007,2023-11-20,,29,L3,0.0,FALSE,Guangzhou,
U1008,2024-03-01,F,26,,68.0,,Shenzhen,5
"""
df = pd.read_csv(StringIO(raw), dtype=str)

# ---------- 2) 基础清洗与类型转换 ----------
def to_bool(x):
    if pd.isna(x): return np.nan
    x = str(x).strip().lower()
    if x in ['true','1','yes','y','t']: return 1
    if x in ['false','0','no','n','f']: return 0
    return np.nan

def parse_date(s):
    if pd.isna(s) or s=='':
        return pd.NaT
    return pd.to_datetime(s)

df['signup_date'] = df['signup_date'].apply(parse_date)
df['age'] = pd.to_numeric(df['age'], errors='coerce')
df['monthly_spend'] = pd.to_numeric(df['monthly_spend'], errors='coerce')
df['premium_user'] = df['premium_user'].apply(to_bool).astype('float')  # float to allow NaN
# 等级映射
lvl_map = {'L1':1,'L2':2,'L3':3,'L4':4,'L5':5}
df['income_level'] = df['income_bracket'].map(lvl_map).astype('float')

# 缺失指示
for col in ['signup_date','gender','age','income_level','monthly_spend','premium_user','city','satisfaction_score']:
    df[f'is_{col}_missing'] = df[col].isna().astype(int)

# 日期序数(天)
min_date = df['signup_date'].min()
if pd.isna(min_date):
    min_date = pd.Timestamp('2023-01-01')
df['signup_ordinal'] = (df['signup_date'] - min_date).dt.days

# ---------- 3) 特征构造(用于KNN的通用X) ----------
def make_features(df_local):
    # 临时用UNK填充类别、用中位数填充数值(仅作预测特征,不是最终填充值)
    tmp = df_local.copy()
    cat_cols = ['gender','city']
    for c in cat_cols:
        tmp[c] = tmp[c].fillna('UNK')
    # 数值临时填
    num_cols = ['age','income_level','monthly_spend','premium_user','signup_ordinal']
    for c in num_cols:
        med = tmp[c].median(skipna=True)
        tmp[c] = tmp[c].fillna(med)
    X = pd.get_dummies(tmp[cat_cols], dummy_na=False)
    X = pd.concat([X, tmp[num_cols],
                   tmp[['is_signup_date_missing','is_gender_missing','is_age_missing',
                        'is_income_level_missing','is_monthly_spend_missing',
                        'is_premium_user_missing','is_city_missing','is_satisfaction_score_missing']]], axis=1)
    return X

# 标准化器(仅对数值列)
def scale_fit_transform(X):
    X = X.copy()
    num_mask = X.dtypes != 'uint8'  # one-hot为uint8
    scaler = StandardScaler()
    X.loc[:, num_mask] = scaler.fit_transform(X.loc[:, num_mask])
    return X, scaler, num_mask

def scale_transform(X, scaler, num_mask):
    X = X.copy()
    X.loc[:, num_mask] = scaler.transform(X.loc[:, num_mask])
    return X

# ---------- 4) 边际分布保持的分配器 ----------
def marginal_preserving_assign(prob_mat, classes, target_counts, random_state=42):
    # prob_mat: n_missing x n_classes 的概率
    rng = np.random.default_rng(random_state)
    n_missing, n_classes = prob_mat.shape
    # 贪心:迭代选择当前剩余额度中概率最大的(样本,类)配对
    remaining = target_counts.copy()
    assigned = -np.ones(n_missing, dtype=int)
    # 排序所有对
    pairs = []
    for i in range(n_missing):
        for j in range(n_classes):
            pairs.append((i,j,prob_mat[i,j]))
    pairs.sort(key=lambda x: x[2], reverse=True)
    used_rows = set()
    for i,j,p in pairs:
        if assigned[i] == -1 and remaining[j] > 0:
            assigned[i] = j
            remaining[j] -= 1
            used_rows.add(i)
        if len(used_rows) == n_missing:
            break
    # 若配额未完全用尽/分配失败,则用剩余最大概率类补齐
    for i in range(n_missing):
        if assigned[i] == -1:
            j = int(np.argmax(prob_mat[i]))
            assigned[i] = j
    return np.array([classes[k] for k in assigned])

def build_targets_for_missing(obs_series, missing_cnt, classes):
    # 目标:填完后总体分布 ≈ 观测分布
    obs = obs_series.dropna()
    if len(obs)==0:
        # 平均分配
        base = np.array([missing_cnt // len(classes)]*len(classes))
        base[:missing_cnt - base.sum()] += 1
        return base
    counts = obs.value_counts().reindex(classes).fillna(0).values.astype(int)
    p = counts / counts.sum()
    target = np.round(p * missing_cnt).astype(int)
    # 调整四舍五入误差
    diff = missing_cnt - target.sum()
    if diff != 0:
        order = np.argsort(-(p - target/missing_cnt))  # 优先给占比高的
        for k in order[:abs(diff)]:
            target[k] += int(np.sign(diff))
    return target

# ---------- 5) 各字段填充 ----------
X_base = make_features(df)
X_scaled, scaler, num_mask = scale_fit_transform(X_base)

# 5.1 signup_date -> KNN回归日期序数
mask_sd = df['signup_ordinal'].notna()
if mask_sd.sum() >= 2 and (~mask_sd).sum() > 0:
    knn_sd = KNeighborsRegressor(n_neighbors=3, weights='distance')
    knn_sd.fit(X_scaled[mask_sd], df.loc[mask_sd, 'signup_ordinal'])
    X_miss = scale_transform(make_features(df[~mask_sd]), scaler, num_mask)
    pred = knn_sd.predict(X_miss)
    pred = np.round(np.clip(pred, df['signup_ordinal'].min(), df['signup_ordinal'].max())).astype(int)
    df.loc[~mask_sd, 'signup_ordinal'] = pred
df['signup_date'] = df['signup_ordinal'].apply(lambda d: (min_date + pd.Timedelta(days=int(d))) if pd.notna(d) else pd.NaT)

# 5.2 gender -> KNN分类 + 边际分布保持
classes_gender = ['F','M','Other']
mask_g = df['gender'].isna()
if mask_g.any():
    mask_g_obs = ~mask_g
    knn_g = KNeighborsClassifier(n_neighbors=3, weights='distance')
    knn_g.fit(X_scaled[mask_g_obs], df.loc[mask_g_obs, 'gender'])
    X_miss = scale_transform(make_features(df[mask_g]), scaler, num_mask)
    prob = knn_g.predict_proba(X_miss)
    # 重新排序为固定类别顺序
    proba_mat = np.zeros((prob.shape[0], len(classes_gender)))
    for j, cls in enumerate(knn_g.classes_):
        proba_mat[:, classes_gender.index(cls)] = prob[:, j]
    tgt_counts = build_targets_for_missing(df.loc[mask_g, 'gender'], mask_g.sum(), classes_gender)
    fill_vals = marginal_preserving_assign(proba_mat, classes_gender, tgt_counts, random_state=42)
    df.loc[mask_g, 'gender'] = fill_vals

# 5.3 city -> KNN分类 + 边际分布保持
classes_city = sorted(df['city'].dropna().unique().tolist() + ['Shenzhen','Guangzhou','Hangzhou'])
classes_city = sorted(list(set([c for c in classes_city if isinstance(c, str) and c!=''])))
mask_c = df['city'].isna()
if mask_c.any():
    mask_c_obs = ~mask_c
    knn_c = KNeighborsClassifier(n_neighbors=3, weights='distance')
    knn_c.fit(X_scaled[mask_c_obs], df.loc[mask_c_obs, 'city'])
    X_miss = scale_transform(make_features(df[mask_c]), scaler, num_mask)
    prob = knn_c.predict_proba(X_miss)
    # 对齐类别顺序
    proba_mat = np.zeros((prob.shape[0], len(classes_city)))
    for j, cls in enumerate(knn_c.classes_):
        proba_mat[:, classes_city.index(cls)] = prob[:, j]
    tgt_counts = build_targets_for_missing(df.loc[mask_c, 'city'], mask_c.sum(), classes_city)
    fill_vals = marginal_preserving_assign(proba_mat, classes_city, tgt_counts, random_state=42)
    df.loc[mask_c, 'city'] = fill_vals

# 5.4 premium_user -> KNN分类(可做边际保持)
mask_p = df['premium_user'].isna()
if mask_p.any():
    mask_p_obs = ~mask_p
    knn_p = KNeighborsClassifier(n_neighbors=3, weights='distance')
    knn_p.fit(X_scaled[mask_p_obs], df.loc[mask_p_obs, 'premium_user'].astype(int))
    X_miss = scale_transform(make_features(df[mask_p]), scaler, num_mask)
    prob = knn_p.predict_proba(X_miss)
    # 概率抽样(保持总体占比,可选)
    classes_p = [0,1]
    proba_mat = np.zeros((prob.shape[0], len(classes_p)))
    for j, cls in enumerate(knn_p.classes_):
        proba_mat[:, classes_p.index(cls)] = prob[:, j]
    # 构造总占比目标
    tgt_counts = build_targets_for_missing(df.loc[mask_p, 'premium_user'], mask_p.sum(), classes_p)
    fill_vals = marginal_preserving_assign(proba_mat, classes_p, tgt_counts, random_state=42)
    df.loc[mask_p, 'premium_user'] = fill_vals

# 5.5 income_bracket(等级)-> KNN分类(1-5)
mask_inc = df['income_level'].isna()
if mask_inc.any():
    mask_inc_obs = ~mask_inc
    knn_inc = KNeighborsClassifier(n_neighbors=5, weights='distance')
    knn_inc.fit(X_scaled[mask_inc_obs], df.loc[mask_inc_obs, 'income_level'].astype(int))
    X_miss = scale_transform(make_features(df[mask_inc]), scaler, num_mask)
    pred = knn_inc.predict(X_miss)
    df.loc[mask_inc, 'income_level'] = pred
df['income_bracket'] = df['income_level'].round().clip(1,5).map({v:k for k,v in lvl_map.items()})

# 5.6 age -> KNN回归
mask_age = df['age'].isna()
if mask_age.any():
    mask_age_obs = ~mask_age
    knn_age = KNeighborsRegressor(n_neighbors=5, weights='distance')
    knn_age.fit(X_scaled[mask_age_obs], df.loc[mask_age_obs, 'age'])
    X_miss = scale_transform(make_features(df[mask_age]), scaler, num_mask)
    pred = knn_age.predict(X_miss)
    df.loc[mask_age, 'age'] = np.clip(pred, 18, 60)

# 5.7 monthly_spend -> KNN回归(log1p)
mask_sp = df['monthly_spend'].isna()
if mask_sp.any():
    mask_sp_obs = ~mask_sp
    y = np.log1p(df.loc[mask_sp_obs, 'monthly_spend'].astype(float))
    knn_sp = KNeighborsRegressor(n_neighbors=5, weights='distance')
    knn_sp.fit(X_scaled[mask_sp_obs], y)
    X_miss = scale_transform(make_features(df[mask_sp]), scaler, num_mask)
    pred = knn_sp.predict(X_miss)
    df.loc[mask_sp, 'monthly_spend'] = np.maximum(np.expm1(pred), 0.0)

# 5.8 satisfaction_score -> KNN加权分位数(τ=0.4) + 指示
def knn_weighted_quantile_impute(df_local, target_col, tau=0.4, n_neighbors=7, eps=1e-6):
    mask_miss = df_local[target_col].isna()
    if not mask_miss.any(): return
    mask_obs = ~mask_miss
    y = df_local.loc[mask_obs, target_col].astype(float).values
    X_obs = scale_transform(make_features(df_local[mask_obs]), scaler, num_mask).values
    X_miss = scale_transform(make_features(df_local[mask_miss]), scaler, num_mask).values
    # 计算距离
    from sklearn.metrics import pairwise_distances
    D = pairwise_distances(X_miss, X_obs, metric='euclidean')
    # 对每个缺失样本取最近邻
    nn_idx = np.argsort(D, axis=1)[:, :min(n_neighbors, X_obs.shape[0])]
    filled = []
    for i in range(nn_idx.shape[0]):
        idx = nn_idx[i]
        d = D[i, idx]
        w = 1.0 / (d + eps)
        w = w / w.sum()
        yy = y[idx]
        # 计算加权分位数
        ord_idx = np.argsort(yy)
        yy_sorted = yy[ord_idx]
        w_sorted = w[ord_idx]
        cdf = np.cumsum(w_sorted)
        q_val = yy_sorted[np.searchsorted(cdf, tau, side='right')]
        filled.append(int(np.clip(np.round(q_val), 1, 5)))
    df_local.loc[mask_miss, target_col] = filled

df['satisfaction_score'] = pd.to_numeric(df['satisfaction_score'], errors='coerce')
knn_weighted_quantile_impute(df, 'satisfaction_score', tau=0.4, n_neighbors=7)
df['satisfaction_missing'] = df['is_satisfaction_score_missing']

# ---------- 6) 结果与简要评估 ----------
# 性别与城市分布
gender_dist_before = (df.loc[df['is_gender_missing']==0, 'gender'].value_counts(normalize=True)*100).round(1)
gender_dist_after = (df['gender'].value_counts(normalize=True)*100).round(1)
city_dist_before = (df.loc[df['is_city_missing']==0, 'city'].value_counts(normalize=True)*100).round(1)
city_dist_after = (df['city'].value_counts(normalize=True)*100).round(1)

# 相关性(仅演示)
corr_before = df.loc[(df['is_age_missing']==0)&(df['is_monthly_spend_missing']==0), ['age','monthly_spend']].corr().iloc[0,1]
corr_after = df[['age','monthly_spend']].corr().iloc[0,1]

print("Gender dist (obs):\n", gender_dist_before.to_dict())
print("Gender dist (all):\n", gender_dist_after.to_dict())
print("City dist (obs):\n", city_dist_before.to_dict())
print("City dist (all):\n", city_dist_after.to_dict())
print("Age-Spend corr (obs only):", round(float(corr_before),3))
print("Age-Spend corr (after):", round(float(corr_after),3))

# 输出最终数据
out_cols = ['user_id','signup_date','gender','age','income_bracket','monthly_spend','premium_user','city','satisfaction_score','satisfaction_missing']
print(df[out_cols])

运行要点与注意

  • 随机性:marginal_preserving_assign使用random_state固定;可重复
  • 小样本:n_neighbors建议3-5;大样本可增大到5-15
  • 范围/裁剪:age/score/spend/日期均已设定边界
  • 指示变量:保留is_*_missing,尤其是satisfaction_missing供下游使用

质量保证建议

  • 评估
    • 性别/城市分布差异(观测 vs 全量):绝对偏差≤1-2个百分点
    • KS/卡方检验:p>0.05优先
    • age—spend相关性Δρ≤0.05-0.10
    • 交叉验证KNN误差:age/spend的MAE、RMSE,income/gender/city/premium的准确率
  • 监控
    • 月度重跑分布对比与漂移报警
    • 样本量增长后,用迭代填充(如MICE)替代一次性KNN,以量化不确定性
  • 文档化
    • 记录每批次参数、评估指标与版本,确保可追溯

该方案遵循:

  • 统计合理性:基于KNN与概率模型,避免未经验证的启发式
  • 分布与相关性不扭曲:边际保持+KNN局部结构
  • 业务约束:性别/城市稳定、age—spend关系保持、满意度保守填充并显式标注缺失
  • 无系统性偏差:概率抽样与配额控制,防止单边偏置

缺失值分析报告

  • 数据集概况

    • 场景:电子装配产线 M03 逐小时监测数据,用于 OEE 异常检测与良率预测
    • 字段类型:时间序列+标识符+分类型(shift)+数值型(temperature_c, vibration_mm_s, oee_percent)+计数型(defect_count)+布尔型(power_on, maintenance_flag)
    • 数据规模(示例片段):10 行(08:00–17:00,Day 班次)
    • 缺失总体:8 个缺失单元/(10 行 × 6 指标)≈ 13.3%
      • temperature_c: 2/10 (10:00, 15:00)
      • vibration_mm_s: 2/10 (09:00, 16:00)
      • defect_count: 1/10 (11:00)
      • oee_percent: 1/10 (12:00)
      • power_on: 1/10 (13:00)
      • maintenance_flag: 1/10 (13:00)
  • 缺失模式分析

    • MCAR(完全随机缺失):单点孤立缺失(如 09:00 振动、10:00 温度、11:00 缺陷数、12:00 OEE),与业务状态无明显关联,符合网络丢包特征。
    • MAR(条件随机缺失):13:00 的布尔标记同时缺失,邻近时段正常,可能与临近维护窗口前的点检或临时记录异常相关,受上下文(班次/邻近状态)影响。
    • MNAR(非随机缺失):15:00 为停机(power_on=FALSE, maintenance=TRUE)下的温度缺失,与设备状态直接相关,禁止跨停机段插值。
  • 业务影响评估

    • OEE 异常检测对短时趋势敏感;跨停机/班次插值会平滑断点,掩盖异常或错误扩散状态。
    • 良率预测依赖 defect_count 与工况;不当插值会扭曲低频计数分布(过度平滑或引入非整数),形成系统性偏差。
    • 维护窗口与班次边界是结构信息,需要保留为建模特征(断点、区段),禁止用插值“抹平”。

填充策略推荐

首选方案:分段(状态感知)插值+局部稳健填充

  • 适用理由
    • 按生产段(power_on=TRUE 且 maintenance_flag=FALSE)划分“运行段”,仅在运行段内插值,避免跨停机/班次(结构边界)造成偏差。
    • 数值型用时间序列线性插值(短缺口),与工艺的缓慢变化相符,且可加边界裁剪防止越界。
    • 计数型用中心中位数(局部窗口),保持非负与离散分布特性,避免线性插值产生小数。
    • 布尔型用邻近众数(短窗口),保留状态连续性,不跨班次,不跨维护窗口。
  • 参数设置
    • 分组维度:['line_id','machine_id','shift'](不跨班次)
    • 运行段定义:power_on=TRUE 且 maintenance_flag=FALSE 的连续区间
    • 数值型(temperature_c, vibration_mm_s, oee_percent)
      • 方法:线性插值(limit_area='inside'),仅段内
      • 最大缺口:max_gap_hours=2(>2 小时缺口不插值)
      • 边界裁剪:temperature [60, 90];oee_percent [0, 1];vibration >= 0
      • 段边缘单端缺失:最近邻填充(ffill/bfill),limit=1
    • 计数型(defect_count)
      • 方法:段内居中滚动中位数(window=3, center=True, min_periods=1),四舍五入为整数
      • 兜底:ffill/bfill(limit=1),仍缺失则设为段内局部中位数(不跨段)
    • 布尔型(power_on, maintenance_flag)
      • 方法:同班次内邻近众数(window=3,中心窗口);若两侧一致则取一致值
      • 兜底:ffill/bfill(limit=1),仍不确定则保持缺失(不强行判断)
    • 禁止:对停机段(power_on=FALSE 或 maintenance=TRUE)内的数值型插值;这类缺失保持为缺失值(并输出指示列)
  • 预期效果
    • 在示例中,09:00 振动≈2.25(2.1 与 2.4 线性),10:00 温度≈69.6(69.0 与 70.2 线性),11:00 缺陷数=0(邻近中位数),12:00 OEE≈0.825(0.85 与 0.80 线性),13:00 布尔补全为 power_on=TRUE, maintenance=FALSE(邻近一致),16:00 振动=2.3(段内单端最近邻)。15:00 温度因停机保持缺失。
    • 缺失比例预计从 13.3% 降至约 1.7%(仅保留停机导致的结构性缺失),趋势与断点得到保留。

备选方案

  • 适用场景
    • A. 数据缺口较长(>2 小时)或波动较小的稳定工段:段内前向/后向填充(ffill/bfill, limit=2)
    • B. 历史数据充足时:同班次同机台的移动窗口中位数/分位数填充,适用于季节/班次稳定的变量
    • C. 大规模历史数据:模型辅助(回归/Poisson)推断缺失,但需严格交叉验证并加不确定性评估
  • 实施要点
    • A:严格段内执行;OEE 与计数的 ffill 不可跨异常或停机断点;limit 控制小范围传播
    • B:窗口建议 7–14 天、同班次聚合;优先用中位数,防异常值污染
    • C:分段建模(仅运行段),计数用 Poisson/零膨胀模型,保留缺失指示变量,防止乐观偏差
  • 效果对比
    • A 在稳态下效果接近线性插值,计算更简洁,但对趋势恢复不如线性插值自然
    • B 能抑制异常、对长期缺口更稳健,但牺牲短时趋势
    • C 潜在误差最低(数据充足时),但实现复杂,需要充分验证防偏差

详细实施指南

  • 数据预处理要求

    • 按 ['line_id','machine_id','shift','timestamp'] 排序;timestamp 转为时区一致的 pandas.DatetimeIndex
    • 校验采样频率(1 小时);若有丢小时,先插入空行(asfreq='H'),丢失的行标注为系统缺失,不参与跨段插值
    • 统一布尔型 TRUE/FALSE -> 布尔值;确保 oee_percent 在 [0,1]
    • 生成维护/停机掩码:downtime = (~power_on) | (maintenance_flag==True)
  • 填充操作具体步骤(Python 示例)

    • 说明:示例严格遵循“段内插值、不断面”的原则;对停机段数值不插值,仅补齐布尔短缺失。
import pandas as pd
import numpy as np
from io import StringIO

raw = """timestamp,line_id,machine_id,shift,temperature_c,vibration_mm_s,defect_count,oee_percent,power_on,maintenance_flag
2024-07-01 08:00,L1,M03,Day,68.5,2.1,1,0.86,TRUE,FALSE
2024-07-01 09:00,L1,M03,Day,69.0,,0,0.88,TRUE,FALSE
2024-07-01 10:00,L1,M03,Day,,2.4,0,0.87,TRUE,FALSE
2024-07-01 11:00,L1,M03,Day,70.2,2.3,,0.85,TRUE,FALSE
2024-07-01 12:00,L1,M03,Day,69.8,2.2,0,,TRUE,FALSE
2024-07-01 13:00,L1,M03,Day,71.0,2.5,2,0.80,,
2024-07-01 14:00,L1,M03,Day,70.9,2.6,1,0.82,TRUE,FALSE
2024-07-01 15:00,L1,M03,Day,,2.4,0,0.83,FALSE,TRUE
2024-07-01 16:00,L1,M03,Day,70.5,,0,0.84,TRUE,FALSE
2024-07-01 17:00,L1,M03,Day,70.1,2.3,0,0.85,TRUE,FALSE
"""
df = pd.read_csv(StringIO(raw), parse_dates=['timestamp'])
# 统一布尔
for c in ['power_on','maintenance_flag']:
    df[c] = df[c].map({'TRUE': True, 'FALSE': False})

df = df.sort_values(['line_id','machine_id','shift','timestamp'])

# 参数
group_cols = ['line_id','machine_id','shift']
num_cols = ['temperature_c','vibration_mm_s','oee_percent']
cnt_col = 'defect_count'
bool_cols = ['power_on','maintenance_flag']
max_gap_hours = 2

# Step 1: 先对布尔缺失做同班次近邻众数(不跨班次/不跨机台)
def fill_bool_mode(g):
    g = g.copy()
    for bc in bool_cols:
        s = g[bc]
        # 中心窗口众数(3 小时),优先用邻近一致
        # 先做前后向各一步
        f = s.ffill(limit=1)
        b = s.bfill(limit=1)
        agree = f.eq(b) & f.notna()
        s2 = s.copy()
        s2[agree & s2.isna()] = f[agree & s2.isna()]
        # 再用滚动众数兜底
        def _mode(x):
            x = x[~pd.isna(x)]
            if len(x)==0: return np.nan
            m = x.mode()
            return m.iloc[0] if len(m)>0 else np.nan
        s3 = s2.copy()
        s3 = s3.combine_first(s2.rolling(window=3, center=True, min_periods=2).apply(
            lambda x: _mode(pd.Series(x)), raw=False))
        # 最后一次 ffill/bfill(限制 1)
        s3 = s3.ffill(limit=1).bfill(limit=1)
        g[bc] = s3
    return g

df = df.groupby(group_cols, group_keys=False).apply(fill_bool_mode)

# Step 2: 标记运行段(power_on=True 且 maintenance=False)
df['is_run'] = (df['power_on'] == True) & (df['maintenance_flag'] == False)

# 用 run 段编号(遇到非运行行就断段)
def assign_run_segment(g):
    g = g.sort_values('timestamp').copy()
    seg_id = 0
    seg_ids = []
    prev_run = False
    for run in g['is_run']:
        if run and not prev_run:
            seg_id += 1
        seg_ids.append(seg_id if run else 0)  # 0 表示非运行段
        prev_run = run
    g['run_seg_id'] = seg_ids
    return g

df = df.groupby(group_cols, group_keys=False).apply(assign_run_segment)

# Step 3: 数值型在运行段内插值(线性,段内,不跨越)
def interp_numeric_in_segment(g):
    g = g.sort_values('timestamp').copy()
    # 辅助:检查段内缺口长度
    g = g.set_index('timestamp')
    for c in num_cols:
        # 仅对运行段行做插值,其他行保持原样
        s = g[c].copy()
        s_run = s[g['run_seg_id'] > 0]

        # 分段执行插值
        s_new = s.copy()
        for seg_id, idx in s_run.groupby(g['run_seg_id']).groups.items():
            seg = s.loc[idx].copy()

            # 限制最大缺口:如果连续 NaN 序列长度大于 max_gap_hours,不做线性插值(保留缺失)
            # 先线性插值只对内部缺口
            seg_interp = seg.interpolate(method='linear', limit_area='inside')
            # 对段边缘单端缺失,允许 1 步最近邻
            seg_interp = seg_interp.ffill(limit=1).bfill(limit=1)

            # 可选:对过长缺口还原为 NaN(这里按时间频率为小时计)
            # 统计 NaN 连续段长度,若>max_gap_hours,恢复这些位置
            na_runs = seg.isna().astype(int).groupby((seg.notna()).cumsum()).sum()
            # 上面统计方式并不直接给出位置,简化:用 rolling 限制(短样本下可不必)
            s_new.loc[idx] = seg_interp

        # 物理边界裁剪
        if c == 'temperature_c':
            s_new = s_new.clip(lower=60, upper=90)
        if c == 'oee_percent':
            s_new = s_new.clip(lower=0, upper=1)
        if c == 'vibration_mm_s':
            s_new = s_new.clip(lower=0)

        g[c] = s_new

    g = g.reset_index()
    return g

df = df.groupby(group_cols, group_keys=False).apply(interp_numeric_in_segment)

# Step 4: 计数型用段内居中中位数(window=3),兜底 ffill/bfill,取整
def fill_counts(g):
    g = g.sort_values('timestamp').copy()
    g = g.set_index('timestamp')
    s = g[cnt_col]
    # 仅对运行段内做填充,非运行段保持原值(停机时通常为 0 或缺失)
    mask = g['run_seg_id'] > 0
    s_run = s.where(mask)
    # 居中中位数
    med = s_run.rolling(window=3, center=True, min_periods=1).median()
    filled = s_run.fillna(med)
    filled = filled.ffill(limit=1).bfill(limit=1)
    # 取整并非负
    filled = np.rint(filled).clip(lower=0)
    # 回写(仅运行段)
    s_final = s.copy()
    s_final[mask] = filled[mask]
    g[cnt_col] = s_final
    g = g.reset_index()
    return g

df = df.groupby(group_cols, group_keys=False).apply(fill_counts)

# Step 5: 生成填充标记列与保留结构性缺失
for c in num_cols + [cnt_col] + bool_cols:
    flag = f'{c}_was_imputed'
    df[flag] = df[c].isna()  # 先标记
# 重做标记:填充后现值不为 NaN 且原值为 NaN 的位置标记 True
# 为简单起见,使用一个备份原始缺失掩码:
raw_df = pd.read_csv(StringIO(raw), parse_dates=['timestamp'])
for c in ['power_on','maintenance_flag']:
    raw_df[c] = raw_df[c].map({'TRUE': True, 'FALSE': False})
raw_df = raw_df.sort_values(['line_id','machine_id','shift','timestamp'])
raw_df = raw_df.merge(df[['timestamp','line_id','machine_id','shift']], on=['timestamp','line_id','machine_id','shift'], how='right')
for c in num_cols + [cnt_col] + bool_cols:
    df[f'{c}_was_imputed'] = raw_df[c].isna() & df[c].notna()

# 停机导致的结构性缺失指示(数值型)
df['is_downtime'] = (~df['power_on']) | (df['maintenance_flag'] == True)
for c in num_cols + [cnt_col]:
    df[f'{c}_downtime_nan'] = df['is_downtime'] & df[c].isna()

# 查看结果
print(df[['timestamp','temperature_c','vibration_mm_s','defect_count','oee_percent','power_on','maintenance_flag']])
  • 结果验证方法与指标

    • 数值型(temperature_c, vibration_mm_s, oee_percent)
      • 蒙版验证:随机掩蔽 20% 非缺失点,重跑填充,计算 MAE/RMSE、相对误差(sMAPE);分段统计(仅运行段)
      • 分布一致性:填充前后均值、中位数、标准差、IQR、KS 检验(运行段内)
      • 物理边界违规率:超界比例应为 0
    • 计数型(defect_count)
      • MAE、精确匹配率(filled==true)、零比例变化(zero-ratio shift)应接近 0
      • 泊松性检验(可选):均值≈方差的偏离程度是否增大
    • 布尔型(power_on, maintenance_flag)
      • 邻域一致率(与前后小时一致性)、与停机/OEE 的逻辑一致率(如 maintenance=True 时 power_on 多为 False)
    • 结构信息
      • 断点完整性:停机开始/结束时刻是否保持未被跨越插值
      • 班次界限:不跨 shift 的插值审计(groupby 检查)
  • 常见问题及处理方案

    • 长缺口(>2 小时):不做线性插值;优先使用同班次历史中位数或保持缺失并引入缺失指示变量
    • 段边缘单端缺失:仅最近邻 1 步 ffill/bfill;大于 1 步保持缺失
    • 布尔两侧不一致:保持缺失,避免强判;可在下游模型中使用缺失指示
    • 停机段的数值:不插值;如模型需要数值,可在特征工程阶段用“缺失+指示变量”策略交由模型学习

质量保证建议

  • 填充效果评估指标
    • 缺失率下降比例:目标>80%(本例可降至约 1.7%)
    • 数值型:MAE/RMSE、KS 统计量(运行段内)阈值(如 KS < 0.1)
    • 计数型:零比例变化 |Δp0| < 2%,均值和中位数变化 < 5%
    • 布尔型:逻辑一致率 > 98%(maintenance=True ⇒ power_on=False 的一致性)
  • 异常值检测方法
    • 段内 3σ/基于 MAD 的异常识别;对填充值额外加严阈值(例如 z-score>|2.5|)
    • 断点检测(如基于一阶差分阈值):确认插值未跨越停机/换班断点
  • 后续监控与优化
    • 每日/每班次质量报表:缺失率、违规边界率、断点完整性
    • 漏斗监控:网络丢包峰时段、维护窗口与缺失的相关性
    • 参数回顾:根据长期误差(蒙版验证)调整 max_gap 与窗口大小

Python 快速蒙版验证(可选)

# 简单蒙版验证(运行段内随机 20% 掩蔽)
def mask_and_eval(df_in, col, frac=0.2, seed=42):
    gcols = ['line_id','machine_id','shift']
    df_eval = df_in.copy()
    run_mask = df_eval['is_run'] & df_eval[col].notna()
    np.random.seed(seed)
    idx = df_eval[run_mask].sample(frac=frac, random_state=seed).index
    truth = df_eval.loc[idx, col].copy()
    df_eval.loc[idx, col] = np.nan
    # 仅对该列重跑段内插值(保持与主流程一致)
    def _interp_one(g):
        g = g.sort_values('timestamp').copy().set_index('timestamp')
        s = g[col]
        mask = g['run_seg_id'] > 0
        for seg_id, ids in s[mask].groupby(g['run_seg_id']).groups.items():
            seg = s.loc[ids]
            seg_interp = seg.interpolate(method='linear', limit_area='inside').ffill(limit=1).bfill(limit=1)
            s.loc[ids] = seg_interp
        g[col] = s
        return g.reset_index()
    df_eval = df_eval.groupby(gcols, group_keys=False).apply(_interp_one)
    pred = df_eval.loc[idx, col]
    mae = (pred - truth).abs().mean()
    rmse = np.sqrt(((pred - truth)**2).mean())
    return mae, rmse

mae_t, rmse_t = mask_and_eval(df, 'temperature_c')
print('Temperature MAE/RMSE:', mae_t, rmse_t)

小结(可直接应用)

  • 按机台-产线-班次分组,先用邻近众数修补短缺失的布尔标记,定义运行段。
  • 数值型仅在运行段内进行线性插值(短缺口),段边缘单端最近邻 1 步,温度/OEE 加边界裁剪;停机段数值缺失保留。
  • 计数型用段内居中中位数(window=3)并取整,兜底 ffill/bfill 限 1。
  • 生成“was_imputed”与“downtime_nan”指示,保留结构信息,避免隐藏异常。
  • 通过蒙版验证和分布一致性检查评估填充质量;监控缺失率、边界违规率与断点完整性。

该方案遵循统计可验证方法,不跨结构边界、不扭曲分布,贴合您给定的“前向/后向插值、时间序列插值、中位数填充”偏好与产线业务逻辑。

示例详情

解决的问题

用一次高效对话,快速拿到专业级的缺失值处理方案,帮助数据与业务团队在销售、用户画像、财务、生产与调研等场景中,从诊断缺失到策略选择、参数确定、落地执行与效果验收一站式完成。目标是缩短清洗周期,提升分析与建模准确性,降低人为拍脑袋填充的风险,确保业务逻辑与数据分布不被破坏;同时输出可直接用于汇报与协作的分析报告与实施指南,让策略可复用、可评估、可监控,最终把数据质量转化为可见的业务结果与决策信心。

适用用户

数据分析师(BI/数仓)

快速识别缺失字段影响范围,制定填充方案并验证效果,提升模型训练与报表稳定性。

增长/用户运营

补全关键标签与行为序列,减少人群识别偏差,优化分群、触达与投放策略的转化率。

财务数据管理与审计

修复月度报表缺项与异常空值,保持口径统一,支撑预算预测与合规审计落地。

特征总结

一键诊断缺失模式,识别随机与非随机,快速锁定风险字段与受影响业务环节。
自动匹配数值、分类、时间序列场景,推荐最合适填充策略,兼顾业务逻辑与真实分布。
面向销售、画像、财务、生产等场景,给出可落地步骤与设置建议,显著缩短试错周期。
填充前后质量评估一站式完成,提供对比指标与结论,确保分析结果稳定可信。
支持首选与备选方案灵活切换,明确适用条件与差异,应对不同规模与缺失比例。
主动规避引入偏差的做法,保证分布不被扭曲,模型训练与报表口径保持一致。
从预处理到实施全流程指导,列出注意事项与常见问题,团队协作可按清单执行。
提供持续监控与告警建议,跟踪新数据缺失情况,及时调整策略保持数据健康。

如何使用购买的提示词模板

1. 直接在外部 Chat 应用中使用

将模板生成的提示词复制粘贴到您常用的 Chat 应用(如 ChatGPT、Claude 等),即可直接对话使用,无需额外开发。适合个人快速体验和轻量使用场景。

2. 发布为 API 接口调用

把提示词模板转化为 API,您的程序可任意修改模板参数,通过接口直接调用,轻松实现自动化与批量处理。适合开发者集成与业务系统嵌入。

3. 在 MCP Client 中配置使用

在 MCP client 中配置对应的 server 地址,让您的 AI 应用自动调用提示词模板。适合高级用户和团队协作,让提示词在不同 AI 工具间无缝衔接。

AI 提示词价格
¥20.00元
先用后买,用好了再付款,超安全!

您购买后可以获得什么

获得完整提示词模板
- 共 828 tokens
- 4 个可调节参数
{ 数据集描述 } { 缺失特征类型 } { 输入数据信息 } { 填充方法偏好 }
获得社区贡献内容的使用权
- 精选社区优质案例,助您快速上手提示词
限时免费

不要错过!

免费获取高级提示词-优惠即将到期

17
:
23
小时
:
59
分钟
:
59