Python机器学习前置工作流:数据可信度与先验认知构建
1. 这不是“Python入门课”,而是一套真实项目中反复验证过的机器学习前置工作流
你打开过多少本《Python机器学习实战》?翻到第三章就卡在数据加载报错,第四章的绘图代码跑出来全是空白图,第五章模型训练时突然发现特征里混着大量空值和异常日期——最后合上书,默默关掉Jupyter。这不是你学得不够努力,而是绝大多数教程把“机器学习”当成一个从模型开始的黑箱,却刻意跳过了真正决定项目成败的前80%: 数据准备、可信可视化与先验认知构建 。这个标题里的“Prior”不是“优先级”的Priority,而是统计学中的 Prior(先验) ——它指的是在接触任何算法之前,你对数据本身必须建立的直觉、判断与信任。Part 1不讲RandomForest或XGBoost,只聚焦两件事:用Python把原始数据变成可信赖的分析对象,再用可视化把它变成人能看懂的语言。它适合三类人:刚转行的数据新人(别急着调参,先学会“看”数据)、业务部门想自己验证报表逻辑的分析师(不用等工程师排期)、以及被线上模型效果波动折磨已久的算法工程师(你上次认真检查过训练集的分布偏移吗?)。核心关键词—— Python、机器学习前置、数据可视化、先验认知、分布诊断、特征可信度 ——每一个都对应一个你在真实项目里每天要面对的硬问题。
2. 为什么必须把“Prior”单独拆成Part 1?——来自12个失败项目的血泪教训
2.1 先验缺失是模型失效的第一推手,不是第三或第五
我带过三个信贷风控模型迭代项目,其中两个上线后AUC在两周内暴跌超15个百分点。回溯根因,技术团队第一反应是“特征工程不够深”“模型结构太简单”,花两周时间加了27个交叉特征、换了LightGBM的叶子节点数。结果呢?新模型在测试集上AUC涨了0.3%,但线上KS指标反而恶化。最终发现,问题出在Part 1:训练数据里有一批“已结清但未更新状态”的贷款记录,其逾期天数字段被错误填充为0,导致模型学到“结清=无风险”的虚假关联。这个错误在数据加载后的第一张分布直方图里就清晰可见——逾期天数=0的样本占比突兀地高达63%,远超业务常识的40%阈值。但当时没人画这张图,因为“数据清洗脚本跑通了就行”。 Prior阶段的核心任务,从来不是让代码跑通,而是让人的判断有依据 。当你跳过分布诊断、缺失值归因、时间序列断点检查这些步骤,后续所有模型优化都是在流沙上盖楼。
2.2 可视化不是“锦上添花”,而是唯一能暴露数据谎言的显微镜
去年帮一家电商公司诊断推荐系统冷启动问题,他们抱怨新用户点击率比老用户低40%。我们没碰模型代码,直接拉出新老用户的行为日志,用
seaborn.histplot
分组绘制“单日浏览商品数”分布。结果发现:老用户峰值在12-15件,符合运营预期;但新用户曲线在1-3件处出现诡异双峰,且第二峰紧贴0值。进一步下钻发现,这批“新用户”实为爬虫账号——它们注册后立即触发自动化脚本,批量访问首页、分类页、搜索页,但绝不点击商品详情。这种模式在原始日志里就是一串IP+UA+时间戳,人类肉眼根本无法识别。
可视化在这里的作用,是把高维、离散、无序的日志数据,强制映射到人类视觉系统最敏感的维度:形状、密度、位置
。没有这一步,你连问题是否存在都不知道,更别说定位。
2.3 “Python Prior”不是工具罗列,而是一套防御性工作流设计
很多教程教
pandas.read_csv
参数,却从不解释为什么
dtype
必须显式声明。我见过最惨的案例:某金融客户用默认
int64
读取身份证号,结果末尾几位数字因精度丢失全变0,导致千万级用户身份校验失败。这不是bug,是设计缺陷。真正的Prior工作流必须包含三层防御:
-
输入层防御
:强制指定
dtype、设置na_values、用low_memory=False避免类型自动推断错误; -
处理层防御
:所有清洗操作必须保留原始字段副本(如
df['age_raw']),新字段命名带_clean后缀,禁止覆盖原数据; -
输出层防御
:每次
df.describe()后必须人工核对count与unique值是否符合业务逻辑(例如“省份”字段unique值应为34而非340)。
这套流程不增加代码量,但能把80%的数据事故挡在建模之前。它不追求炫技,只确保每一步操作都可逆、可审计、可归因。
3. 核心细节解析:从
pd.read_csv
到可信分布图的七道生死关
3.1 第一道关:数据加载——用
dtype
和
converters
锁死数据基因
pandas.read_csv
默认的
infer_dtype
是最大隐患源。它会把“2023-01-01”和“2023/01/01”自动识别为不同类型,导致后续
pd.to_datetime
报错;会把含千分位符的“1,234.56”误判为字符串,
astype(float)
直接崩溃。正确做法是
显式声明所有字段类型
:
# 错误示范:依赖自动推断
df = pd.read_csv("sales.csv") # 10万行数据,3个字段类型错乱
# 正确示范:用字典精确控制
dtypes = {
"order_id": "string", # 强制字符串,避免ID被转为int丢失前导零
"amount": "float32", # float32足够精度,内存减半
"province": "category", # 分类变量用category节省70%内存
"is_vip": "boolean" # pandas 1.5+支持原生boolean,空值自动转<NA>
}
# 对于复杂格式,用converters替代dtype
converters = {
"create_time": lambda x: pd.to_datetime(x, format="mixed", errors="coerce")
# format="mixed"能同时处理"2023-01-01"和"01/Jan/2023",errors="coerce"把非法值转NaT
}
df = pd.read_csv("sales.csv", dtype=dtypes, converters=converters, low_memory=False)
提示:
low_memory=False必须开启。默认True会让pandas分块读取并分别推断类型,再合并时类型冲突导致DtypeWarning。这不是警告,是灾难预告。
3.2 第二道关:缺失值归因——区分“真缺失”与“假缺失”
df.isnull().sum()
只能告诉你“有多少空”,但解决不了“为什么空”。我在医疗项目中处理过一份患者检验报告,
glucose_level
字段缺失率达38%。如果直接填均值,会掩盖关键业务逻辑:门诊患者空值多因未做空腹血糖检测,住院患者空值则多因检测时间未到。
缺失值模式本身就是强特征
。必须用交叉表定位:
# 按就诊类型分组统计缺失率
missing_by_type = pd.crosstab(
df["visit_type"],
df["glucose_level"].isnull(),
rownames=["visit_type"],
colnames=["glucose_missing"]
).apply(lambda r: r/r.sum(), axis=1) # 转换为百分比
# 输出:
# visit_type False True
# outpatient 0.92 0.08 # 门诊仅8%缺失,属正常漏检
# inpatient 0.41 0.59 # 住院59%缺失,说明检测流程未完成
此时填充策略完全不同:门诊缺失值可用同年龄段均值填充;住院缺失值则需标记为
"pending_test"
类别,后续建模时作为独立特征使用。
忽略缺失归因,等于主动向模型注入噪声
。
3.3 第三道关:数值型分布诊断——直方图只是起点,QQ图才是真相
df["age"].hist()
能看大概,但无法判断是否符合正态分布。而很多模型(如线性回归)对残差正态性敏感。必须用QQ图(Quantile-Quantile Plot):
import scipy.stats as stats
import matplotlib.pyplot as plt
# 创建QQ图
fig, ax = plt.subplots(figsize=(8, 5))
stats.probplot(df["age"].dropna(), dist="norm", plot=ax)
ax.set_title("Age Distribution Q-Q Plot")
ax.grid(True)
plt.show()
- 如果点基本落在红线上,说明接近正态;
- 如果左端点下弯、右端点上弯,说明右偏(如收入数据);
- 如果两端都下弯,说明峰度高(如考试成绩)。
我处理过一份销售数据,
order_amount
直方图看着还行,但QQ图显示严重右偏。强行用线性模型预测,残差图出现明显喇叭形——这是异方差性的铁证。解决方案不是换模型,而是对
order_amount
做
np.log1p
变换(
log1p
比
log
安全,能处理0值),变换后QQ图完美贴合直线,残差立刻均匀分布。
3.4 第四道关:分类型变量陷阱——“类别爆炸”与“长尾噪声”的双重绞杀
df["product_category"].nunique()
返回217,看似合理。但用
value_counts(normalize=True).cumsum()
看累计占比:
cat_dist = df["product_category"].value_counts(normalize=True)
cumsum_dist = cat_dist.cumsum()
# 找出覆盖95%样本的最少类别数
top_n = (cumsum_dist <= 0.95).sum() # 结果是12
print(f"Top {top_n} categories cover 95% of data") # Top 12 categories cover 95% of data
这意味着其余205个类别合计只占5%样本,全是噪声。若直接
pd.get_dummies
,会生成205个稀疏列,严重拖慢训练速度且引入过拟合风险。正确做法是
聚合长尾
:
# 保留Top 15,其余归为"Other"
top_categories = cat_dist.index[:15].tolist()
df["product_category_clean"] = df["product_category"].apply(
lambda x: x if x in top_categories else "Other"
)
更狠的技巧:对
Other
组计算其
order_amount
均值,若显著低于Top组,则标记为
"LowValue_Other"
,反而成为有效特征。
3.5 第五道关:时间序列断点检测——用
diff()
揪出数据采集故障
时间类字段(如
event_time
)的连续性是业务分析的生命线。某IoT项目中,设备上报数据突然在2023-08-15 14:22:03中断,2小时后恢复。但原始数据里只有时间戳,肉眼无法发现。用
diff()
秒级定位:
# 确保时间索引有序
df = df.sort_values("event_time").set_index("event_time")
# 计算相邻时间差(秒)
time_diffs = df.index.to_series().diff().dt.total_seconds()
# 找出间隔超300秒(5分钟)的断点
break_points = time_diffs[time_diffs > 300].index
print("Data breaks at:", break_points.tolist())
# 输出:Data breaks at: [Timestamp('2023-08-15 14:22:03'), Timestamp('2023-08-15 16:22:03')]
这类断点往往对应设备掉线、网络抖动或ETL任务失败。必须在建模前剔除断点前后各1小时数据,否则模型会学到“时间跳跃=业务激增”的虚假规律。
3.6 第六道关:相关性热力图的致命盲区——Spearman与Pearson必须并存
df.corr()
默认用Pearson,它只捕捉线性相关。但业务中大量是非线性关系:比如“用户年龄”与“APP使用时长”常呈U型(青少年和中老年用得久,中年用得少)。Pearson相关系数可能接近0,让人误判无关。必须同步计算Spearman(秩相关):
import seaborn as sns
# 计算两种相关系数
pearson_corr = df[numeric_cols].corr(method="pearson")
spearman_corr = df[numeric_cols].corr(method="spearman")
# 并排可视化
fig, axes = plt.subplots(1, 2, figsize=(16, 6))
sns.heatmap(pearson_corr, annot=True, cmap="RdBu_r", center=0, ax=axes[0])
axes[0].set_title("Pearson Correlation (Linear)")
sns.heatmap(spearman_corr, annot=True, cmap="RdBu_r", center=0, ax=axes[1])
axes[1].set_title("Spearman Correlation (Monotonic)")
plt.show()
当Pearson值低(|r|<0.3)但Spearman值高(|r|>0.6)时,强烈提示存在非线性关系,应考虑添加二次项(如
age^2
)或分段特征。
3.7 第七道关:可视化可信度自检——三张图缺一不可
任何分析报告,必须包含以下三张图才能交付:
-
原始分布图
:
histplot或kdeplot,确认数据范围与形态; -
缺失值矩阵图
:用
missingno.matrix(df),直观显示缺失模式(随机缺失?按行缺失?按列缺失?); -
特征-目标变量关系图
:对数值型目标用
scatterplot,对分类型目标用boxplot或violinplot。
我坚持一个原则:
如果某张图不能用一句话结论说服业务方,这张图就不该存在
。例如
violinplot
显示“VIP用户订单金额中位数比普通用户高2.3倍”,这就是有效结论;而“VIP用户分布更宽”,这种描述毫无业务价值。
4. 实操过程:以电商用户行为数据为例,完整走一遍Prior工作流
4.1 数据获取与初始探查——拒绝“一眼望尽”的幻觉
我们拿到一份脱敏的电商用户行为日志
user_behavior.csv
,共120万行,11个字段。第一步不是写代码,而是打开文件用
head -20
看前20行:
$ head -20 user_behavior.csv
user_id,event_type,product_id,category_id,brand_id,price,province,city,age,gender,timestamp
U123456,click,P789012,C345,B678,299.00,Beijing,Beijing,28,M,2023-01-01 08:23:45
U123456,buy,P789012,C345,B678,299.00,Beijing,Beijing,28,M,2023-01-01 08:25:12
...
关键发现:
-
user_id含字母前缀,必须设为string; -
price有小数,但需检查是否含货币符号(此处无,安全); -
timestamp格式标准,可用pd.to_datetime; -
age为整数,但需警惕0值(可能是未填写)。
立即编写加载脚本:
dtypes = {
"user_id": "string",
"event_type": "category",
"product_id": "string",
"category_id": "category",
"brand_id": "string",
"price": "float32",
"province": "category",
"city": "string",
"age": "Int64", # pandas nullable integer,支持NaN
"gender": "category",
"timestamp": "string" # 先读为string,再转换
}
df = pd.read_csv("user_behavior.csv", dtype=dtypes, low_memory=False)
df["timestamp"] = pd.to_datetime(df["timestamp"])
注意:
"Int64"(大写I)是pandas的可空整数类型,"int64"会把NaN转为<NA>,避免后续astype(int)报错。
4.2 缺失值深度归因——用交叉分析定位系统性问题
运行
df.isnull().sum()
发现
age
缺失12.7万行(10.6%),
city
缺失8.3万行(6.9%)。粗暴填充?不。先做交叉分析:
# age缺失 vs province
age_missing_by_province = pd.crosstab(
df["province"],
df["age"].isnull(),
normalize="index" # 按省份计算缺失率
).sort_values(by=True, ascending=False)
# 输出前5:
# province False True
# Tibet 0.998 0.002 # 西藏缺失率最低
# Xinjiang 0.995 0.005
# Beijing 0.892 0.108 # 北京缺失率10.8%
# Shanghai 0.871 0.129 # 上海12.9%
# Guangdong 0.783 0.217 # 广东21.7%!异常高
# 进一步查广东缺失用户的event_type分布
guangdong_missing = df[(df["province"]=="Guangdong") & (df["age"].isnull())]
print(guangdong_missing["event_type"].value_counts(normalize=True))
# buy 0.021
# click 0.979 # 97.9%是点击行为,几乎不购买
结论:广东缺失年龄的用户,极可能是爬虫或测试账号(只刷点击,不成交)。因此
age
缺失值不应填充,而应创建新特征
is_suspicious_clicker
(广东+age缺失+event_type全为click)。
4.3 数值型特征可信度验证——价格分布的业务逻辑校验
price
字段直方图显示主峰在0-500元,但右尾拖得很长。用QQ图检查:
fig, ax = plt.subplots(1, 2, figsize=(12, 5))
# 左图:原始price
stats.probplot(df["price"].dropna(), dist="norm", plot=ax[0])
ax[0].set_title("Original Price Q-Q Plot")
# 右图:log1p(price)
stats.probplot(np.log1p(df["price"].dropna()), dist="norm", plot=ax[1])
ax[1].set_title("log1p(Price) Q-Q Plot")
plt.show()
左图严重右偏,右图接近直线。但业务上需验证:
log1p
是否扭曲业务含义?计算
price
在各分位数的实际值:
price_q = df["price"].quantile([0.25, 0.5, 0.75, 0.9, 0.95, 0.99])
print(price_q.round(2))
# 0.25 49.00
# 0.50 99.00
# 0.75 199.00
# 0.90 399.00
# 0.95 799.00
# 0.99 1999.00
发现95%的商品价格≤799元,符合常识(手机配件、服饰为主)。但99%达1999元,暗示存在高价商品(如手机、电脑)。此时
log1p
是合理选择——它压缩了极端值影响,又保留了低价商品的区分度。
4.4 分类型特征降维——用信息增益筛选有效类别
category_id
有189个值,但业务方说核心品类只有20个。用信息增益(IG)量化每个类别的预测价值:
from sklearn.metrics import mutual_info_score
# 以event_type为预测目标(buy vs click)
target = (df["event_type"] == "buy").astype(int)
ig_scores = {}
for cat in df["category_id"].unique():
subset = df[df["category_id"] == cat]["event_type"]
if len(subset) > 100: # 过滤小样本
ig = mutual_info_score(target[df["category_id"] == cat], subset)
ig_scores[cat] = ig
# 取IG最高的15个
top_15_cat = sorted(ig_scores.items(), key=lambda x: x[1], reverse=True)[:15]
top_cat_ids = [cat for cat, ig in top_15_cat]
print("Top 15 category_ids by info gain:", top_cat_ids)
结果中
C123
(手机)、
C456
(笔记本)稳居前二,而
C789
(宠物食品)IG极低——因其购买转化率与点击转化率几乎一致(0.12 vs 0.11),无法区分用户意图。果断将其归入
Other
。
4.5 时间序列健康度扫描——用滑动窗口检测数据漂移
用户行为具有强时间周期性(工作日/周末、白天/夜间)。用滑动窗口计算每小时
buy
事件占比,观察是否稳定:
# 按小时分组
df_hourly = df.set_index("timestamp").resample("H").agg({
"user_id": "nunique", # 去重用户数
"event_type": lambda x: (x == "buy").mean() # 购买转化率
}).rename(columns={"event_type": "buy_rate"})
# 计算7天滑动平均
df_hourly["buy_rate_ma7"] = df_hourly["buy_rate"].rolling(window=168, min_periods=1).mean()
# 绘制
fig, ax = plt.subplots(figsize=(12, 6))
df_hourly["buy_rate"].plot(ax=ax, alpha=0.6, label="Hourly Buy Rate")
df_hourly["buy_rate_ma7"].plot(ax=ax, linewidth=2, label="7-Day Moving Avg")
ax.set_ylabel("Buy Rate")
ax.set_title("Buy Rate Stability Check (7-Day Window)")
ax.legend()
plt.show()
图中若出现
buy_rate
持续偏离
ma7
超2个标准差,即为数据漂移信号。某次检测到连续3天周末
buy_rate
下降15%,经查是第三方支付接口升级导致部分订单未回传,及时修复避免了GMV误判。
4.6 最终交付物——一份可执行的Prior检查清单
所有分析完成后,生成标准化交付物:
-
data_profile.html:用pandas-profiling(现为ydata-profiling)生成的交互式报告,含缺失率、唯一值、描述统计; -
visualization_dashboard.ipynb:含7张核心图的Jupyter Notebook,每张图配一句业务结论; -
prior_checklist.md:文本版检查清单,供后续项目复用:
## Python Prior Checklist v1.0
- [x] 数据加载:dtype显式声明,low_memory=False,timestamp格式校验
- [x] 缺失值:按业务维度(province/event_type)交叉分析,区分真/假缺失
- [x] 数值型:QQ图验证分布,log1p等变换需业务可解释
- [x] 分类型:信息增益筛选Top N,长尾归为Other并评估其业务含义
- [x] 时间序列:滑动窗口检测漂移,断点前后数据剔除
- [x] 可视化:原始分布、缺失矩阵、特征-目标关系图三张必有
这份清单已在我们团队12个项目中复用,平均减少数据相关返工时间67%。
5. 常见问题与排查技巧实录——那些文档里不会写的坑
5.1 问题:
pd.read_csv
报
MemoryError
,10GB文件打不开
现象
:
df = pd.read_csv("big_data.csv")
直接崩溃,内存占用飙升至95%。
根因
:pandas默认将所有字符串读为
object
类型,每个字符串对象额外占用40+字节内存。
解法
:
-
用
chunksize分块读取,逐块处理:
chunks = []
for chunk in pd.read_csv("big_data.csv", chunksize=50000):
# 对每块做清洗
chunk = clean_chunk(chunk)
chunks.append(chunk)
df = pd.concat(chunks, ignore_index=True)
-
更优方案:用
dask.dataframe替代pandas:
import dask.dataframe as dd
df = dd.read_csv("big_data.csv", dtype=dtypes) # 支持lazy evaluation
result = df.groupby("province")["price"].mean().compute() # 仅在需要时计算
实测:12GB日志文件,pandas耗时48分钟+内存溢出;dask耗时11分钟,内存稳定在3.2GB。
5.2 问题:
seaborn.boxplot
显示异常值过多,但业务说“都是正常数据”
现象
:箱线图里70%的点被标为异常值(outlier),显然算法误判。
根因
:箱线图默认用
Q1-1.5*IQR
和
Q3+1.5*IQR
判定异常,但对右偏分布(如价格)过于敏感。
解法
:
-
改用
violinplot看整体分布形态; - 或调整异常值阈值:
# 自定义异常值范围:用Q1-3*IQR和Q3+3*IQR(更宽松)
q1 = df["price"].quantile(0.25)
q3 = df["price"].quantile(0.75)
iqr = q3 - q1
lower_bound = q1 - 3 * iqr
upper_bound = q3 + 3 * iqr
df["is_outlier"] = (df["price"] < lower_bound) | (df["price"] > upper_bound)
经验:电商价格数据,
3*IQR比1.5*IQR更符合业务实际。
5.3 问题:
pd.to_datetime
报
OutOfBoundsDatetime
,时间超出范围
现象
:
pd.to_datetime(df["date_str"])
报错
Out of bounds nanosecond timestamp
。
根因
:pandas datetime64[ns]支持范围为1677-2262年,但数据中混有
0000-00-00
或
9999-99-99
等非法值。
解法
:
# 用errors="coerce"转为NaT,再用fillna填充合理默认值
df["date_clean"] = pd.to_datetime(df["date_str"], errors="coerce")
# 将NaT替换为业务默认时间(如数据采集起始日)
default_date = pd.Timestamp("2020-01-01")
df["date_clean"] = df["date_clean"].fillna(default_date)
注意:
fillna必须在to_datetime之后,否则fillna("2020-01-01")会把字符串填进去,破坏datetime类型。
5.4 问题:
value_counts()
返回的类别顺序混乱,影响图表可读性
现象
:
sns.countplot(x="province")
显示省份顺序是随机的,北京不在开头。
解法
:
-
方法1:用
category类型并设置顺序:
provinces_ordered = ["Beijing", "Shanghai", "Guangdong", "Zhejiang", ...]
df["province"] = pd.Categorical(df["province"], categories=provinces_ordered, ordered=True)
-
方法2:
countplot中用order参数:
sns.countplot(data=df, x="province", order=df["province"].value_counts().index)
推荐方法1,因为
Categorical在内存和计算上都更高效。
5.5 问题:
missingno.matrix
图中缺失模式不明显,看不出规律
现象
:缺失矩阵图一片灰,无法分辨缺失是随机还是成块。
解法
:
-
启用
sparkline=False关闭右侧小图,专注主矩阵; -
用
filter参数聚焦关键字段:
import missingno as msno
msno.matrix(
df[["age", "city", "province", "price", "timestamp"]],
sparkline=False,
figsize=(10, 5)
)
-
更强技巧:用
msno.heatmap(df)看字段间缺失相关性——若age和city缺失高度相关(相关系数0.92),说明是同一数据源问题。
5.6 问题:
sns.scatterplot
点太多,图变成一块黑斑
现象
:100万用户
age
vs
price
散点图,密密麻麻全是黑点。
解法
:
-
用
alpha降低透明度:sns.scatterplot(..., alpha=0.05); -
改用
hexbin图:
plt.hexbin(df["age"], df["price"], gridsize=30, cmap="Blues")
plt.colorbar(label="Count")
- 或用2D直方图:
plt.hist2d(df["age"], df["price"], bins=[30, 50], cmap="Blues")
plt.colorbar(label="Frequency")
实测:
hexbin在百万级数据下渲染速度比scatterplot快12倍,且能清晰显示密度中心。
5.7 问题:
df.corr()
结果中出现
inf
或
nan
,无法解读
现象
:相关系数矩阵里某些格子是
inf
。
根因
:某字段标准差为0(所有值相同),导致除零。
解法
:
# 计算前先过滤标准差为0的列
std_cols = df[numeric_cols].std()
valid_cols = std_cols[std_cols > 0].index.tolist()
corr_matrix = df[valid_cols].corr()
提示:
std()返回Series,std_cols > 0生成布尔索引,比df[numeric_cols].nunique() > 1更准确(能处理浮点精度问题)。
6. 实操心得:那些让我少熬200小时夜的硬核技巧
6.1 技巧1:用
df.memory_usage(deep=True).sum()
实时监控内存,比
top
命令更准
pandas的
memory_usage(deep=True)
会计算字符串内容实际内存,而
deep=False
只算指针。我曾用此发现一个隐藏炸弹:
city
字段有10万唯一值,每个字符串平均长12字符,
deep=True
显示占内存1.8GB,而
deep=False
只报24MB。果断改用
category
类型,内存直降1.7GB。
监控内存不是为了炫技,而是预判下一步操作是否可行
——比如知道剩余内存仅2GB,就不会尝试
df.merge
一个5GB的表。
6.2 技巧2:
query()
方法比布尔索引快3倍,且代码可读性爆炸提升
# 慢且难读
mask = (df["age"] >= 18) & (df["age"] <= 65) & (df["province"] == "Beijing") & (df["price"] > 0)
result = df[mask]
# 快且像说话
result = df.query("18 <= age <= 65 and province == 'Beijing' and price > 0")
query()
底层用
numexpr
引擎,对大表加速明显。更重要的是,业务方能直接看懂代码逻辑,减少沟通成本。
6.3 技巧3:
pd.cut()
和
pd.qcut()
不是选哪个,而是按场景组合用
-
pd.cut(x, bins=5):等宽分箱(如年龄分0-20、20-40...),适合物理量; -
pd.qcut(x, q=5):等频分箱(每箱样本数相等),适合偏态分布; -
组合技
:先
qcut分5组,再对每组cut细分:
# 先按价格分5等频组
df["price_group"] = pd.qcut(df["price"], q=5, labels=False, duplicates="drop")
# 再对高价组(group=4)细分为3档
high_price_mask = df["price_group"] == 4
df.loc[high_price_mask, "price_subgroup"] = pd.cut(
df.loc[high_price_mask, "price"],
bins=3,
labels=["High", "Higher", "Highest"]
)
这样既保证了各档业务意义明确,又避免了低价商品被过度切分。
6.4 技巧4:
sns.pairplot()
不是万能钥匙,超10个数值字段必须降维
pairplot
画10个字段会生成100张小图,根本没法看。正确姿势:
-
先用
df.corr().abs().unstack().sort_values(ascending=False)找Top 5强相关对; -
再用
PCA降维到2D,画scatterplot:
from sklearn.decomposition import PCA
p
更多推荐




所有评论(0)