Python 数据分析实战:从脏数据到决策报告的完整链路

cover

一、真实项目的第一步:面对脏数据

很多教程会把"导入数据"列为分析第一步,但实际工作中,拿到数据后往往要先面对一堆乱码般的字段名、混杂的日期格式和数值列里的"暂无"标记。去年处理电商数据时,我见过最离谱的情况是:同一列日期里同时存在"2023-01-01"、"01/01/2023"和"2023年1月1日"三种格式。跳过清洗直接建模?那结果基本可以扔进垃圾桶。

本文不教基础操作,而是完整演示如何用 Python 工具链处理真实业务数据:从 50 万条电商订单中识别流失用户,分析原因并给出挽回方案。全程使用 pandas 清洗数据、NumPy 计算指标、matplotlib/plotly 可视化、scikit-learn 建模。

二、工具链协作逻辑

先理清各工具的职责边界。pandas 负责数据清洗和结构化,NumPy 专注数值计算,scikit-learn 处理建模,最后用可视化工具产出报告。数据流转统一用 DataFrame 格式——它就像工具链的"通用货币",pandas 底层用 NumPy 数组存储数据,转换成本几乎为零。

flowchart LR
    A[原始数据] --> B[pandas 清洗]
    B --> C[NumPy 计算]
    C --> D[探索分析]
    D --> E[scikit-learn 建模]
    E --> F[可视化报告]

关键原则是"单一职责":pandas 不做复杂计算,NumPy 不碰数据 I/O,scikit-learn 的预处理逻辑尽量放在 pandas 层。这样每个环节都能独立测试,出问题也容易定位。

三、完整代码实现

3.1 数据清洗

def load_and_clean(data_path: str) -> pd.DataFrame:
    dtype_spec = {
        "user_id": "string",
        "order_amount": "float64",
        "channel": "category",
    }
    df = pd.read_csv(
        data_path,
        dtype=dtype_spec,
        parse_dates=["order_date"],
        na_values=["暂无", "NA"],
    )
    
    # 统一字段名:中文转英文、空格替换为下划线
    df.columns = df.columns.str.replace(r"\s+", "_", regex=True)
    
    # 异常值处理:用同渠道中位数替换
    amount_mask = (df["order_amount"] < 0) | (df["order_amount"] > 100000)
    if amount_mask.any():
        median_val = df.groupby("channel")["order_amount"].transform("median")
        df.loc[amount_mask, "order_amount"] = median_val[amount_mask]
    
    return df.reset_index(drop=True)

几个实用技巧:读取时直接指定 dtype 能避免类型混乱;异常值用分组中位数替换比直接删除更合理;Int64(大写 I)能正确处理含缺失值的整数列。

3.2 特征工程

def build_features(df: pd.DataFrame) -> pd.DataFrame:
    reference_date = df["order_date"].max() + pd.Timedelta(days=1)
    
    user_features = (
        df.groupby("user_id")
        .agg(
            order_count=("order_id", "nunique"),
            total_amount=("order_amount", "sum"),
            complaint_count=("complaint_flag", "sum"),
        )
        .assign(
            recency=lambda x: (reference_date - x["order_date"].max()).dt.days,
            has_complaint=lambda x: (x["complaint_count"] > 0).astype(int),
        )
    )
    
    user_features["is_churned"] = (user_features["recency"] > 90).astype(int)
    return user_features

基于 RFM 模型扩展:投诉用户流失概率更高,单一渠道用户粘性更弱。这些特征来自业务经验,不是凭空想象。

3.3 建模分析

def train_churn_model(features: pd.DataFrame):
    feature_cols = ["recency", "frequency", "monetary", "has_complaint"]
    X = features[feature_cols].values
    y = features["is_churned"].values
    
    pipeline = Pipeline([
        ("scaler", StandardScaler()),
        ("clf", RandomForestClassifier(
            n_estimators=200,
            class_weight="balanced",
        )),
    ])
    
    scores = cross_val_score(pipeline, X, y, cv=5, scoring="f1")
    print(f"F1 分数: {scores.mean():.3f}")
    
    pipeline.fit(X, y)
    importances = pd.Series(
        pipeline.named_steps["clf"].feature_importances_,
        index=feature_cols,
    ).sort_values(ascending=False)
    
    return pipeline, importances

选随机森林是因为它能自动捕捉特征交互(比如"高消费 + 有投诉"的组合效应),class_weight="balanced" 解决了流失用户占比低的问题。

四、工具链的局限性

内存限制:pandas 把数据全加载到内存,50 万条订单约占 300MB。超过 1GB 时建议用 Polars 或分块读取。

单线程瓶颈:pandas 的 groupby 操作在百万级数据上可能慢,NumPy 的矩阵运算能利用多线程,但 pandas 层并行化需要额外配置。

可视化短板:matplotlib 做复杂看板代码量大,plotly 渲染大数据集性能一般。生产级 BI 看板还是专业工具更靠谱。

五、实战建议

  • 数据清洗通常占项目 60% 以上时间,别跳过这步
  • 1GB 以内数据用全家桶足够,超过 1GB 优先考虑 Polars
  • 核心逻辑封装成模块,Notebook 只留探索代码
  • 最终报告建议用专业 BI 工具产出,Python 专注数据处理

去年有个项目因为跳过清洗,模型把"暂无"当成了 0 值,导致结论完全错误。记住:脏数据只会产出脏结论。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐