像搭积木一样思考:数据科学中的“自下而上”之道

最近几年,基本已经不做应用系统的开发了,主要做一些数据分析和机器学习相关的应用(业务复杂度不高),因此,对于以前熟悉的各种软件模式也逐渐生疏。

今天,偶尔又翻到保罗·格雷厄姆(Paul Graham)之前写过的一篇关于 Lisp 和编程本质的文章《自下而上的编程》,感觉这种源于黑客文化的"自下而上"的哲学,似乎也可以拯救陷入"意大利面条式代码"泥潭的数据分析师和机器学习工程师的。

在软件开发的世界里,最常见的错误就是过早地决定了程序的形状。而在数据科学领域,这个错误被放大了很多倍。

1. 现状:大多数人是如何做数据分析的?

如果不加干预,大部分初学者(甚至很多资深从业者)在处理数据时,采取的都是严格的 **"自上而下" **模式。

想象一下,老板交给你一个任务:"分析一下上个季度25岁以下用户的复购率。"

你的大脑立刻开始将其拆解:

  1. 读取 orders.csv
  2. 过滤出 date 在上个季度的行。
  3. 过滤出 age < 25 的用户。
  4. 按用户 ID 分组,计算购买次数。
  5. 输出结果。

于是,你打开了一个 Jupyter Notebook,从第一个 Cell 写到第十个 Cell

代码像瀑布一样流下来,任务完成了,你很开心。

但这有什么问题?

问题在于,这是一种一次性的编程。如果老板明天说:"再看看30岁以上,最近半年的流失率。"你会怎么做?

你会把昨天的 Notebook 复制一份,然后把里面的数字改一改。

久而久之,我们的电脑里充满了 analysis_v1.ipynb, analysis_final.ipynb, analysis_final_really.ipynb。我们的代码变成了为了解决特定单一问题而存在的"脚本"。

这不是在 "开发软件" ,只是在 "操作计算器"

2. 什么是"自下而上"的设计?

格雷厄姆在《自下而上的编程》中提到过:"与其为了解决问题而设计程序,不如设计一种语言来描述这个问题。"

在数据分析和机器学习中, **"自下而上" **意味着你先忽略具体的业务目标(比如计算复购率),转而先构建一套能让你轻松处理数据的 "基础词汇"

当我们采用自上而下的方法时,就像是在用大石头堆砌金字塔,每一块石头的位置都是固定的。

当我们采用自下而上的方法时,则是在通过造积木(或者说创造新的操作符)来提升基础语言的能力。当语言的能力提升到一定程度,解决具体问题就变得像说话一样简单。

PythonR 中,这通常意味着利用函数式编程的思想,将通用的数据转换逻辑抽象成独立的小工具。

3. 场景模拟:自上而下 vs 自下而上

让我们通过一个具体的机器学习预处理场景来看看两者的区别。

假设我们要处理一份电商数据,目的是为模型准备特征。

场景:处理用户行为日志。我们需要做三件事:

  1. 去除异常值(比如购买金额为负数)。
  2. 填充缺失的年龄数据。
  3. 对分类数据(如城市)进行编码。

3.1. 模式 A:传统的自上而下(脚本式)

这是典型的"面条代码":

python 复制代码
import pandas as pd

# 读取数据
df = pd.read_csv("data.csv")

# 1. 业务逻辑直接写死在流程里
df = df[df['amount'] > 0] 

# 2. 处理缺失值
mean_age = df['age'].mean()
df['age'] = df['age'].fillna(mean_age)

# 3. 独热编码
df = pd.get_dummies(df, columns=['city'])

# 喂给模型
model.fit(df)

这段代码完全是为了"这一份数据"服务的。

如果换了一份数据,或者你想尝试"用中位数填充"而不是"均值填充",你就得侵入这段代码去修改它。

这使得实验变得极其痛苦。

3.2. 模式 B:自下而上的设计(语言构建式)

在这种模式下,我们先不看具体的数据,而是问自己:我经常需要做什么?我需要过滤、填充、编码。

于是,我们要为此创造一些"动词"。

python 复制代码
# 第一层:构建基础词汇(这些是通用的积木)
def remove_outliers(col_name, threshold=0):
    """返回一个过滤函数"""
    def _filter(df):
        return df[df[col_name] > threshold]
    return _filter

def fill_missing(col_name, strategy='mean'):
    """返回一个填充函数"""
    def _filler(df):
        data = df.copy()
        if strategy == 'mean':
            val = data[col_name].mean()
        elif strategy == 'median':
            val = data[col_name].median()
        data[col_name] = data[col_name].fillna(val)
        return data
    return _filler

def encode_categorical(col_names):
    """返回一个编码函数"""
    def _encoder(df):
        return pd.get_dummies(df, columns=col_names)
    return _encoder

# 第二层:组合积木(使用管道)
# 即使你是初学者,看到这里也能明白,我们创造了一种"新语言"
from functools import reduce

def apply_pipeline(df, functions):
    return reduce(lambda d, f: f(d), functions, df)

# 第三层:解决具体问题
# 你看,现在的代码读起来就像是英语句子
pipeline = [
    remove_outliers('amount', 0),
    fill_missing('age', strategy='mean'),
    encode_categorical(['city'])
]

clean_df = apply_pipeline(raw_data, pipeline)

哪怕你没学过高深的编程,也能看出模式 B 的美妙之处。

如果不想要均值填充了?只需要把 strategy='mean' 改成 'median'

如果想给另一个数据集做同样的预处理?直接复用 pipeline 列表。

最重要的是,底层的函数(积木)一旦写好并测试通过,你就再也不用去动它们了。

你的思考层级从 **"如何写 Pandas 代码" **上升到了 "如何组合数据处理逻辑"

4. 自下而上模式的优缺点

当然,自下而上的模式也不是万能的。

它的优点是:

  1. 代码会越来越短: 随着你积累的基础函数(积木)越来越多,解决新问题所需的代码行数会越来越少。就像 Lisp 程序员常说的,你在让语言向问题靠拢。
  2. 极强的复用性 : 你的 fill_missing 函数可以在一百个不同的项目中使用,而不需要重写一行代码。
  3. 易于测试: 测试一个小的、独立的工具函数,比测试一个几百行的脚本要容易得多。
  4. 表达力: 顶层的代码读起来就是业务逻辑本身,而不是复杂的语法细节。

为此,它的的缺点是:

  1. 初期成本高 : 当你只想画一条线时,自下而上的方法要求你先造一支笔。对于非常简单、一次性的任务,这可能显得 "过度设计"
  2. 思维门槛: 初学者往往习惯于线性思维(第一步做啥,第二步做啥)。要学会抽象出通用的操作符,需要一定的训练和对函数式编程的理解。

5. 总结

自下而上 的设计模式,核心在于抽象

在数据分析和机器学习中,这意味着不要总是盯着眼前的这一行数据,而是去思考:"我该如何构建一套工具,让这类型的问题以后都不再是问题?"

当你开始这样思考时,就不仅是在做分析,而是在像黑客一样创造。

相关推荐
luoluoal2 小时前
基于python的医疗问句中的实体识别算法的研究(源码+文档)
python·mysql·django·毕业设计·源码
wang_yb2 小时前
像搭积木一样思考:数据科学中的“自下而上”之道
数据分析·databook
啊阿狸不会拉杆2 小时前
《机器学习导论》第 9 章-决策树
人工智能·python·算法·决策树·机器学习·数据挖掘·剪枝
喵手2 小时前
Python爬虫实战:城市停车收费标准自动化采集系统 - 让停车费透明化的技术实践(附CSV导出 + SQLite持久化存储)!
爬虫·python·爬虫实战·零基础python爬虫教学·城市停车收费标准·采集城市停车收费数据·采集停车数据csv文件导出
无水先生2 小时前
python函数的参数管理(01)*args和**kwargs
开发语言·python
py小王子2 小时前
dy评论数据爬取实战:基于DrissionPage的自动化采集方案
大数据·开发语言·python·毕业设计
Pyeako2 小时前
opencv计算机视觉--LBPH&EigenFace&FisherFace人脸识别
人工智能·python·opencv·计算机视觉·lbph·eigenface·fisherface
小陶的学习笔记2 小时前
python~基础
开发语言·python·学习
多恩Stone2 小时前
【3D AICG 系列-9】Trellis2 推理流程图超详细介绍
人工智能·python·算法·3d·aigc·流程图