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万用户agevsprice散点图,密密麻麻全是黑点。
解法:
- 用
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