资讯动态

Pandas生态贯通:NumPy、Matplotlib与Scikit-learn数据流程实战

发布时间:2026/10/14 4:50:46 来源:尧图企业网站定制
很多人学数据分析喜欢一个库一个库地啃先学NumPy再学Pandas又学Matplotlib最后还报了个课学Scikit-learn。单看每个库的教程都能看懂可真到项目里一份CSV文件摆在面前要清洗、要统计、要画图、要建模动作一多就乱了。这种断档我太熟悉了。11.15那天我做了一个小复盘把Pandas生态里最常用的四样东西——NumPy、Pandas、Matplotlib、Scikit-learn——从头到尾串了一遍发现最大的难点根本不是某个API记不住而是数据格式和调用关系没打通。这篇就把我的串联经验和踩过的坑写出来尤其适合已经会用Pandas做基本数据处理、但对完整数据流程还缺一根主线的人。1. 四大神器各司其职用一条流水线理解Pandas生态1.1 为什么是这四位从一份数据的完整旅程看分工很多教程喜欢把Pandas、NumPy、Matplotlib、Scikit-learn分成四门课讲这在入门阶段没问题但一到实战就会觉得割裂。我后来换了个方式不按库学按流程学。一份数据从原始文件到最终结论通常要经过几个固定环节读取数据Pandas 的read_csv、read_excel把外部文件变成 DataFrame。清洗整理处理缺失值、去重、改列名、调索引这些是 Pandas 的主场。数值计算做统计、分组聚合、条件赋值底层真正干活的是 NumPy 的向量化计算。可视化把结果画成走势图、柱状图、箱线图Matplotlib 负责渲染。建模预测把整理好的特征矩阵交给 Scikit-learn训练模型并评估效果。这一套流程里Pandas 是中枢因为几乎所有中间产物都是 DataFrame。而 NumPy 是 Pandas 的地基DataFrame 的单元格本质上就是 NumPy 数组里的数据Matplotlib 是输出端能把表格变成眼睛能看懂的样子Scikit-learn 是决策端能基于表格学习规律、预测未来。四者合起来才是一个完整的数据分析闭环。只学其中一两个就像只有发动机没有方向盘或者有方向盘没有轮胎车是动不起来的。1.2 数据流转的任督二脉格式互转的三种典型链路所谓“打通任督二脉”在我的理解里就是让数据在这四样工具之间顺畅地流转。最常见的流转有三条DataFrame → NumPy 数组把一列或多列数据转成ndarray交给建模算法或底层计算。DataFrame → Matplotlib 图形Pandas 的.plot()方法直接消费 DataFrame/Series底层自动完成坐标轴映射。DataFrame → Scikit-learn 模型模型接口只认二维数组但经过预处理管道后我们可以让数据带着列名进去再带着预测结果出来。这三条链路里最容易断的地方分别是索引错位、数据类型不统一、缺失值没清干净。我后面会用一份带坑的数据挨个演示怎么接上这些断点。提示学 Pandas 生态时别只盯着“某个函数怎么用”多问一句“这一步执行完之后数据还在哪个容器里、行数和列数是多少”。只要这条主线清晰其他细节都是补丁。2. 准备一份带坑的数据环境依赖与演示数据构造2.1 环境准备版本别乱装先对齐再干活我见过几个朋友代码能跑但结果不对最后发现是版本差异。Pandas 1.x 和 2.x 在某些行为上不一样Scikit-learn 老版本也不支持 DataFrame 直接输出。这里给出一套我实测的版本组合兼容性不错conda create -n dataflow python3.10 -y conda activate dataflow pip install pandas2.0.3 numpy1.24.3 matplotlib3.7.2 scikit-learn1.3.0为什么选这几个版本Pandas 2.0 开始对copy-on-write有更明确的控制很多“赋值不生效”的坑在新版本里会有更清楚的提示Scikit-learn 1.3 之后ColumnTransformer和Pipeline都支持set_output(transformpandas)这就让模型预处理结果能够保留列名不再是一堆没有含义的数字列。NumPy 用 1.24 是因为它和当前的 Pandas、Scikit-learn 都经过了较好的适配不用追求最新版本所谓稳定不是靠堆版本号堆出来的。如果你已经在别的环境里装了一堆包也不要直接pip install --upgrade大升级建议用虚拟环境隔离。虚拟环境的好处在于你可以在里面折腾实验装错了直接删掉重建不影响日常开发环境。2.2 构造一份“看起来正常”的演示数据先埋几个坑纸上谈兵没意思我构造了一份模拟某公司 2024 年销售流水故意加入真实数据中最常见的三类问题缺失值、分类文本、带货币符号的金额字符串。import pandas as pd import numpy as np rng np.random.default_rng(42) df pd.DataFrame({ order_date: pd.date_range(2024-01-01, periods300, freqD), region: rng.choice([华东, 华北, 华南, 西南], size300), category: rng.choice([数码, 家电, 服饰, 食品], size300), sales: np.round(rng.normal(5000, 800, size300), 2), }) # 人为制造缺失值 df.loc[10:12, sales] np.nan df.loc[20, region] None # 人为制造文本型金额列 df[raw_amount] df[sales].apply( lambda x: f¥{x:,.2f} if not pd.isna(x) else np.nan )现在这份数据表面上能打印但直接拿去建模会当场翻车sales里有 NaNregion是文本模型不认识raw_amount虽然看起来是数字其实是带符号、带逗号的字符串对象。我的处理路线就是先用 Pandas 清洗类型再用 NumPy 计算派生特征用 Matplotlib 看规律最后把干净特征交给 Scikit-learn 训练。3. 打通第一步NumPy和Pandas怎么互转才高效3.1 DataFrame的核心是NumPy数组先搞清楚底层格式很多时候我们调用df[sales]拿到的是一个 Series但它内部其实是一个一维 NumPy 数组加一个索引。想验证这一点很简单arr_sales df[sales].to_numpy() print(arr_sales.shape) # (300,) print(arr_sales.dtype) # float64如果取多列得到的是一个二维数组arr_multi df[[sales, raw_amount]].to_numpy() print(arr_multi.shape) # (300, 2) print(arr_multi.dtype) # object因为 raw_amount 是字符串这里有个重要细节当 DataFrame 里的列类型不一致时to_numpy()会把整个二维数组升级成object类型。这种数组没法参与矩阵计算拿去模型 fit 时也容易报错。所以不要急着转数组先确认每一列的类型是不是数值型。我一般会在做任何转换前先跑一句df.dtypes把不合规的列先修一遍。反过来NumPy 数组也能包装成 DataFramearr_random rng.normal(0, 1, size(5, 3)) df_random pd.DataFrame(arr_random, columns[a, b, c])这样做的实际意义是当你的数据来自底层计算库、图像特征提取或者某个只输出数组的接口时把它变成 DataFrame 后就能享受列名、索引、切片这些 Pandas 的便利。3.2 用向量化计算代替遍历把NumPy的武功注入PandasPandas 里的apply很常用但如果数据量一上来按行遍历会慢得让人怀疑人生。更合理的做法是优先使用向量化运算。所谓向量化就是把“对每个元素做同样操作”的任务交给底层 C 语言循环去做而不是在 Python 里一层层 for。举个简单例子我想在销售数据里标记“是否高于平均销售额”。# 低效写法遍历 sales_list df[sales].fillna(0).tolist() mean_sales df[sales].mean() flag_list [] for v in sales_list: if v mean_sales: flag_list.append(1) else: flag_list.append(0) df[high_flag_loop] flag_list # 推荐写法NumPy where df[high_flag] np.where(df[sales] df[sales].mean(), 1, 0)两句代码的结果一样但后者更快也更简洁。多条件的时候用np.selectconditions [ df[sales] 6000, df[sales] 4000, ] choices [高, 低] df[sales_segment] np.select(conditions, choices, default中)这里df[sales]虽然是 Series但 NumPy 函数能直接识别它原因还是那句老话Pandas 基于 NumPy 构建np.where、np.select、np.clip、np.log1p这类函数天然和 Pandas 兼容。遇到偏态分布时还可以对正数销售额做np.log1p(df[sales])压缩量纲让模型训练更顺畅。4. 打通第二步Matplotlib与Pandas的可视化闭环4.1 一条命令画出趋势图plot接口和底层API可视化这一步我见过不少人绕远路明明手里是 DataFrame非要把列拆成数组再手动去plt.plot()。其实 Pandas 自带绘图接口能直接消费 DataFrame 和 Series。先解决中文显示问题很多教程会漏掉这一步import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [Microsoft YaHei, SimHei, Arial Unicode MS] plt.rcParams[axes.unicode_minus] False然后按月份汇总销售额画出趋势线df df.sort_values(order_date) monthly df.set_index(order_date)[sales].resample(MS).sum() monthly.plot(kindline, figsize(10, 4), title月度销售走势) plt.tight_layout() plt.show()代码里没有出现plt.plot(x, y)但不代表不需要 Matplotlib。这里的.plot()是 Pandas 对 Matplotlib 的封装底层仍然调用 pyplot 完成渲染。好处是少了手动传坐标轴的麻烦索引和值直接映射到 x 轴和 y 轴。如果要把多个分组放在一起对比可以用子图fig, axes plt.subplots(1, 2, figsize(12, 4)) df.groupby(region)[sales].mean().plot(kindbar, axaxes[0], title各区域平均销售额) df.groupby(category)[sales].mean().plot(kindbar, axaxes[1], title各品类平均销售额) plt.tight_layout() plt.show()子图模式下需要注意每个plot里都要传入ax否则会画到同一个默认坐标轴上导致图面混乱。4.2 画图前的数据整形宽表和长表不能拍脑袋画图最容易被卡住的地方不是绘图函数本身而是“数据到底要按什么形状传给绘图函数”。比如我想画每个月各区域的销售额折线原始数据是订单级的长表每一行是一笔订单。这种情况下有两种选择用 Pandas 先做数据透视得到一个“宽表”行是月份列是区域值是销售额。再把宽表melt成长表方便 Matplotlib 按分组画线。实操代码如下pivot_df df.pivot_table( indexdf[order_date].dt.to_period(M), columnsregion, valuessales, aggfuncsum ) plot_df pivot_df.reset_index().melt( id_varsorder_date, var_nameregion, value_namesales ) fig, ax plt.subplots(figsize(10, 5)) for region, data in plot_df.groupby(region): ax.plot(data[order_date].astype(str), data[sales], labelregion) ax.legend() plt.xticks(rotation45) plt.tight_layout() plt.show()为什么要转成长表因为 Matplotlib 画多条线时每一条线都需要一组 x 和 y 序列。长表经过groupby后天然可以按区域循环出曲线。如果你给它的数据结构不对画出来要么数据串线要么图例对不上。4.3 可视化阶段的另一个小坑坐标轴刻度和错误数据画完图之后先别急着截图我习惯用眼睛扫一遍图形这一步能发现很多统计指标看不出来的问题。比如某个月份销量突然变成 0那很可能不是业务暴跌而是数据缺失导致的聚合结果又比如某根柱子高出其他柱子一大截那可能是异常值也可能是分组标签没清理干净。如果发现日期坐标轴显示得很乱可以手动调整import matplotlib.dates as mdates ax monthly.plot(kindline, figsize(10, 4)) ax.xaxis.set_major_locator(mdates.MonthLocator()) ax.xaxis.set_major_formatter(mdates.DateFormatter(%Y-%m)) plt.tight_layout() plt.show()这一招在时间序列项目里特别管用。要让图真正成为分析工具而不是作业里凑数用的插图就得把坐标轴、单位、图例全部校准。5. 打通第三步把Pandas结果送给Scikit-learn建模5.1 从DataFrame到模型输入先分离特征与标签数据处理完毕终于到了建模环节。Scikit-learn 的模型接口很死板特征必须是二维数组或能转成二维数组的 DataFrame标签必须是一维数组或 Series。所以建模前第一件事就是确定哪几列是特征哪一列是目标。df[sales_clean] pd.to_numeric(df[sales], errorscoerce) df[sales_filled] df[sales_clean].fillna(df[sales_clean].mean()) X df[[region, category, sales_filled, high_flag]].copy() y df[sales_filled].copy() from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 )注意到region和category是文本分类列此时还不能直接送进随机森林。你不能直接对它们做astype(float)正常做法是编码成数值。5.2 让预处理后的结果保留列名用ColumnTransformer接住Pandas最方便的方式是把 OneHotEncoder、StandardScaler 等预处理模块和模型放进同一个Pipeline让整个流程从原始 DataFrame 开始到预测结果结束。from sklearn.compose import ColumnTransformer from sklearn.preprocessing import OneHotEncoder, StandardScaler from sklearn.pipeline import Pipeline from sklearn.ensemble import RandomForestRegressor categorical_cols [region, category] numeric_cols [sales_filled, high_flag] preprocessor ColumnTransformer( transformers[ (cat, OneHotEncoder(handle_unknownignore), categorical_cols), (num, StandardScaler(), numeric_cols), ] ) pipeline Pipeline(steps[ (prep, preprocessor), (model, RandomForestRegressor(n_estimators200, random_state42)), ]) pipeline.fit(X_train, y_train) y_pred pipeline.predict(X_test)这段代码和信息是完整的你不需要单独去transform测试集因为Pipeline在predict时会自动对测试集应用同一个预处理流程。这能避免我在实际项目中踩过的一个大坑如果我先在全体数据上fit编码器再切训练集和测试集等于让模型偷看了测试集的类别分布评估结果会虚高。如果你想查看预处理后的数据长什么样可以用preprocessor.set_output(transformpandas) X_train_transformed preprocessor.fit_transform(X_train) print(X_train_transformed.head())这样输出的 DataFrame 会保留类似region_华东、sales_filled这样的列名排查特征含义和编码顺序时一目了然。不过要注意fit_transform用的还是同一个 preprocessor真正的建模仍然建议走Pipeline避免手动处理时写错对象。5.3 模型结果回到Pandas误差分析才真正开始预测完成后很多人打印一个 RMSE 就结束了。但我更建议把预测值和真实值放进同一个 DataFrame 里做残差分析from sklearn.metrics import mean_squared_error rmse float(np.sqrt(mean_squared_error(y_test, y_pred))) print(fRMSE: {rmse:.2f}) result_df pd.DataFrame({ actual: y_test, pred: y_pred }) result_df[residual] result_df[actual] - result_df[pred]这一步又让数据回到了 Pandas。你可以接着分析哪些样本残差最大、哪些区域预测偏差最严重甚至把残差画成散点图。一个完整的数据分析项目不是以模型 fit 结束而是以“模型结果回到业务理解”结束。所以我说四大神器是循环NumPy 算底子Pandas 做容器Matplotlib 做放大镜Scikit-learn 做预测器缺一个都不完整。6. 常见问题与排查技巧索引、类型和副本视图6.1 索引对齐的暗坑为什么一merge就变NaNPandas 最强大的特性之一是索引对齐但这也是最容易被忽略的坑。你可能会遇到这种情况两个 DataFrame 明明行数一样用concat合并后却出现大量 NaN。原因往往是它们的索引没有重置或索引顺序不一样。a pd.DataFrame({v: [1, 2, 3]}, index[0, 1, 2]) b pd.DataFrame({w: [4, 5, 6]}, index[2, 1, 0]) # 索引不匹配加法结果会非常奇怪 print(a[v] b[w])这个操作不是按行号相加而是按索引对齐。索引为 0 的那一行a[v]是 1b[w]里索引为 0 的值是 6所以结果是 7但看着像第一行加第一行实际是交叉对齐的。更隐蔽的是一旦你对 DataFrame 做过dropna或条件筛选后没有重置索引再用join或concat就很容易出现 NaN。解决办法很粗暴在合并之前先reset_index(dropTrue)或者老老实实指定on列进行合并。我自己的习惯是数据清洗流程进入建模阶段之前一定会保证索引是干净的 0..n-1 顺序。6.2 看似数字其实是object字符串金额的整型陷阱前面构造的raw_amount列就是典型例子。它打印出来像¥5,123.45dtype却是 object。直接.astype(float)会报错因为 Python 没法把“¥”和“逗号”去掉再转换。处理方式分两步df[amount_clean] ( df[raw_amount] .astype(string) .str.replace(¥, , regexFalse) .str.replace(,, , regexFalse) ) df[amount_clean] pd.to_numeric(df[amount_clean], errorscoerce)先移除货币符号和千分位再用严格转换。如果转换后仍有解析失败的值errorscoerce会让它变成 NaN方便后续统一处理。遇到日期列被读成字符串是同类问题可以直接pd.to_datetime(df[order_date])转换。现象原因排查方法dtype 是 object但内容看起来是数字列里混入逗号、空格、货币符号用df[col].str.contains检查脏字符astype(float)直接报错存在无法解析的字符串先清洗符号再用pd.to_numeric(errorscoerce)日期列参与时间聚合时报错日期是字符串不是 datetime64用pd.to_datetime转换并检查errors参数建模时提示输入含字符串还没做分类编码用 OneHotEncoder 或类别数值映射6.3 SettingWithCopyWarning链式赋值的隐形脏数据很多人第一次看到SettingWithCopyWarning会慌但也有人选择直接忽略。这两个选择都不太好。这个警告的意思是你正在尝试在一个“可能是副本”的 DataFrame 上赋值结果可能不会写回原来的 DataFrame。一个典型错误写法sub_df df[df[region] 华东] sub_df[flag] 1 # 可能只改了副本原 df 没变正确写法是直接用.loc指定条件df.loc[df[region] 华东, flag] 1这样写语义非常明确在原 DataFrame 上按索引和列名定位。如果业务上确实需要子集独立处理那就明确复制一份再操作sub_df df[df[region] 华东].copy() sub_df[flag] 1加了.copy()之后修改子集不会再影响原数据也不会触发警告。这个习惯在数据量大、分步操作多的时候尤其重要。7. 从会用到会用打通任督二脉的心法与练习建议7.1 以Pandas为轴把其他库当成两端的插座我见过不少同学学完了所有 API却依然不会做项目。问题不在于知识量而在于没有形成“中枢意识”。我的建议是把 Pandas 当成整个数据分析流程的轴其他三个库都只是这个轴上的外设。读入数据时要么用 Pandas 读成 DataFrame要么用其他工具读完后立刻转成 DataFrame。做计算时优先用 NumPy 向量化函数操作 Series/DataFrame别在 Python 层循环。做输出时让 DataFrame 直接调用.plot或者把 DataFrame 里的列传给 Matplotlib。做建模时用 Pipeline 管理 DataFrame 到模型输入之间的转换模型输出结果再转回 DataFrame。每次操作完在心里默念一遍当前数据在哪是 DataFrame、Series、NumPy 数组还是列表如果答不上来多半就是断链了。7.2 一个可以复用的练习把同一份数据跑通五个步骤光看不如上手。我推荐你用任意一份销售明细或成绩数据按下面五个步骤跑一遍用 Pandas 读取数据查看缺失值、类型和行数。对至少一个文本列做分类编码对至少一个数值列做标准化。创建一个派生特征比如“分组均值差”或“是否高于中位数”并用 NumPy 实现。用 Matplotlib 画一张趋势图或分布图标出异常区间。用 Scikit-learn 训练一个简单模型并让预测结果回到 DataFrame 里做残差分析。每完成一步记录一次当前数据的形状。比如第一步你发现 shape 是 (300, 5)做完缺失值删除后变成 (297, 5)做完编码后变成 (297, 8)。如果某一步形状变化和你预期不符立刻停下检查。坚持这样做几轮之后你会发现自己对数据流的掌控感会明显变强。我个人在实际操作中的体会是Pandas 生态大融合本质上不是引入更多花哨的功能而是把四样工具之间的接口练熟。NumPy 管数值底层Pandas 管表格结构Matplotlib 管视觉呈现Scikit-learn 管规律学习。你只要把索引、类型和缺失值这三件事管住四者之间的数据传输就会顺畅很多。最后分享一个小经验每次做数据预处理之前先花五秒钟记录df.shape所有操作结束后再看一眼 shape如果行数和预期不一致说明中间一定有索引或过滤问题。这个习惯帮我省掉的排查时间远比想象中多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑