数据清洗与业务逻辑必须分离:清洗负责空值填充、类型转换等,用pandas预处理;业务规则如客户分级需抽为独立函数,确保可测试、可复用、类型稳定且幂等。

数据清洗别碰业务规则
清洗是把脏数据变干净,比如空值填充、类型转换、去重;业务逻辑是“订单金额大于100才算有效客户”这类判断。两者混在一起,代码会迅速变成谁也不敢动的黑盒。
-
fillna()、dropna()、astype()属于清洗,该放pandas.DataFrame预处理流水线里 -
df['is_vip'] = df['order_sum'] > 100是业务逻辑,得抽到独立函数里,比如flag_vip_customers() - 常见错误:在
apply()里一边转日期格式一边计算用户等级——格式转换失败时,等级逻辑也跟着崩,但你根本看不出是清洗出错还是规则写错 - 清洗层应保证输出字段类型稳定,比如
order_date必须是datetime64,否则下游业务函数拿到object类型就可能报AttributeError: 'str' object has no attribute 'year'
清洗函数必须可重复执行
真实场景中,ETL 任务可能失败重跑,或每天增量更新。如果清洗函数依赖全局状态(比如用 counter = 0 算序号),或者直接 df.drop(inplace=True) 修改原始 DataFrame,第二次运行就会出错或漏数据。
- 所有清洗操作优先返回新 DataFrame,避免
inplace=True - 不用
random.shuffle()或np.random.seed()做确定性无关的操作(除非明确需要随机采样) - 时间字段标准化统一用
pd.to_datetime(df['ts'], errors='coerce'),errors='coerce'把非法值转成NaT,而不是抛异常中断流程 - 如果要补缺失 ID,用
df['id'].fillna(method='ffill')比用range(len(df))安全——后者在增删行后立刻失效
业务逻辑函数要能脱离 DataFrame 运行
清洗后的数据结构可能变(列名调整、字段拆分),如果业务函数硬编码 df['user_id'] 和 df['amount'],一改就报 KeyError。更麻烦的是,测试时你没法单独验证“满减券是否生效”这个逻辑,只能连着整个清洗链跑。
- 业务函数接收明确参数,比如
def apply_coupon(user_tier: str, order_amount: float) -> float: - 不直接读
df,也不调pd.cut()这类 pandas 特有接口——换成内置if/elif更易测、易 debug - 示例:
def is_eligible_for_free_shipping(weight_kg: float, is_vip: bool) -> bool: return weight_kg <= 2.0 or is_vip
- 这样的函数可以被单元测试覆盖,也能直接用在 API 或 Spark UDF 中,不绑定 pandas
清洗与业务的交接点必须加断言
清洗输出是业务逻辑的输入,但没人保证清洗一定成功。比如时间字段全变成 NaT,业务层还在按正常日期算月份环比,结果全是 NaN,最后报表看着“很稳”,实际没一条数据有效。
立即学习“Python免费学习笔记(深入)”;
- 在清洗后、业务前插入轻量检查:
assert not df['order_date'].isna().all(), "order_date 全为空,清洗失败"assert set(['user_id', 'amount']) - 不用复杂校验,重点防“全空”“全 NaN”“字段缺失”这三类静默失败
- 断言失败时抛
ValueError,比下游报TypeError: unsupported operand type(s) for +: 'float' and 'NoneType'更早暴露问题
清洗和业务逻辑之间那条线,不是靠命名约定或文档来维系的,是靠函数签名、类型提示、断言和可独立测试性来守住的。越早把边界划清楚,后面改需求时越不容易牵一发而动全身。










