news 2026/9/28 5:26:24

机器学习入门三件套:NumPy、Pandas、Matplotlib 实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
机器学习入门三件套:NumPy、Pandas、Matplotlib 实战指南

1. 内容整体设计与思路拆解

1.1 为什么机器学习入门绕不开这三个库

先说个我经常在后台收到的提问:想学机器学习,是不是直接啃算法书、跑开源项目就够了?我的回答一直是——先别急着碰模型,把numpy、pandas、matplotlib这三件套的基本操作过一遍,否则后面寸步难行。

原因很简单。机器学习项目不管用什么框架,数据流的路径永远是固定的:采集数据 → 清洗整理 → 特征分析 → 建模训练 → 结果可视化。在这条链路上,pandas负责读数据、洗数据、变换数据,numpy负责把数据变成矩阵、做数值计算,matplotlib负责把数据和分析结果画成图,让你一眼看出规律和问题。三者的关系可以类比成做菜:pandas是料理台和洗菜池,numpy是刀和砧板,matplotlib是摆盘和拍照。少哪一个,这道菜都端不上桌。

很多新人上来就装TensorFlow、PyTorch,结果连数据都读不进来,或者读进来一看全是缺失值和乱码,还没到训练那一步就已经心态爆炸。根源就在于基础数据操作不熟。磨刀不误砍柴工,这三个库学扎实了,后面跑任何算法都会顺畅得多。

1.2 学什么程度算“够用”,以及三个库的边界划分

我遇到很多读者纠结一个问题:这三个库内容那么多,官方文档一本比一本厚,到底要学到什么程度才敢开始学机器学习?

我的个人标准是:不追求面面俱到,追求“顺手能干活”。也就是说,拿到一份陌生数据,你能用pandas在五分钟内读进来、看结构、处理缺失值和类型问题;能用numpy完成矩阵的创建、切片、拼接和基本运算;能用matplotlib画出散点图、折线图、柱状图和直方图,并会调整坐标轴、图例和样式。达到这个程度,就可以大胆进入算法阶段了。剩下那些高级技巧,比如pandas的分组聚合、透视表,numpy的广播高级玩法,matplotlib的多子图布局,遇到具体需求的时候再查再补,效率要高得多。

三个库的边界划分也要清楚:numpy管纯粹的数值运算,核心是ndarray数组;pandas管结构化的表格数据,核心是Series和DataFrame;matplotlib管绘图,核心是Figure、Axes、Axis三者的关系。理解了这条主线,就不会把三个库的功能混着用。比如用pandas去做矩阵乘法,或者用numpy去读 CSV,虽然也能实现,但绕了远路不说,代码也不符合生态习惯。

1.3 三件套选型的底层逻辑

还有一个新手经常忽略的点:为什么是这三个库,而不是别的?这里有个历史沿革。numpy的前身是 1995 年诞生的Numeric,后来在 2006 年整合成今天的numpy,它定义了 Python 科学计算的数组对象和运算规范。pandas诞生于 2008 年,专门为了解决金融数据分析中的面板数据问题而设计。matplotlib则是 2002 年启动的绘图库。这三者在 Python 数据生态里各占一个生态位,互相依赖又各有侧重。

如果非要用替代品,pandas可以用polars替换(性能更强但生态没这么成熟),numpy可以用jax或cupy部分替代(分别针对自动微分和 GPU 加速),matplotlib可以用plotly或seaborn替代(但seaborn本身依然构建在matplotlib之上)。这里我给新手的建议是:先把三件套学扎实,再谈替代品。直接上手polars这种新兴库,虽然性能好,但教程少、踩坑成本高,而且网上绝大多数机器学习案例默认都是三件套,别人给的代码你至少得能读懂、能改吧。

2. numpy 核心操作:从数组创建到矩阵运算

2.1 创建数组的四种高频姿势

numpy的核心是ndarray,也就是 N 维数组对象。它的威力在于:数据在内存中连续存储,配合向量化运算,速度远超 Python 原生列表。

import numpy as np # 方式一:从列表创建 arr1 = np.array([1, 2, 3, 4, 5]) # 方式二:全零、全一数组 zeros = np.zeros((3, 4)) ones = np.ones((2, 3)) # 方式三:等差数列 seq = np.arange(0, 10, 2) # 从0到10步长为2 # 方式四:随机数数组 rand = np.random.randn(3, 3) # 标准正态分布 uniform = np.random.uniform(0, 1, (2, 5)) # 均匀分布

这四种创建方式覆盖了 80% 以上的使用场景。特别提醒一下np.arange是左闭右开的,也就是说np.arange(0, 10, 2)生成的是[0, 2, 4, 6, 8],不含 10。这一点和 Python 原生的range一致,但和np.linspace(0, 10, 5)不同,后者是在[0, 10]区间内平均取 5 个点,包含两端。新手经常在这个边界问题上踩坑,做数据切片时多了一个元素或者少了一个元素,排查半天才发现是这里的问题。

随机数这块多说一句:做机器学习实验时,为了结果可复现,一定要设置随机种子。方法是np.random.seed(42),这样每次运行生成的随机数序列都是相同的。我在做模型对比实验时,习惯在代码开头固定 seed,否则同一个模型每次跑出来的精度都不一样,根本没法判断是模型的问题还是随机性的问题。

2.2 切片与形状操作:机器学习最常用的基本功

机器学习当中,你给模型喂的数据几乎都要做形状调整。reshape是出现频率最高的操作之一,比如把一张 28×28 的图片拉平成 784 维的向量,或者把一批一维数组压成二维矩阵。

# 切片操作 arr = np.arange(12).reshape(3, 4) # 输出: # [[ 0 1 2 3] # [ 4 5 6 7] # [ 8 9 10 11]] # 取第一行 row = arr[0, :] # [0 1 2 3] # 取最后一列 col = arr[:, -1] # [3 7 11] # 取子矩阵 sub = arr[1:, 1:3] # [[5 6], [9 10]] # 展平 flat = arr.ravel() # [0 1 2 3 4 5 6 7 8 9 10 11]

这里必须强调reshape和ravel的区别。reshape返回的是视图,也就是和原数组共享内存,修改视图会影响原数组;而ravel在某些情况下会返回原数组的扁平副本,不一定共享内存。还有一个特别容易出错的点:用reshape(-1, 1)可以把一维数组变成列向量,用reshape(1, -1)变成行向量,这在后续矩阵运算中经常用到,因为一维数组和二维矩阵相加时广播规则容易让你怀疑人生。

形状操作还包括转置arr.T、维度互换np.transpose(arr, (1, 0))、拼接np.concatenate/np.stack。如果处理的是三维数组(比如一批图片数据,形状为[batch, height, width]),transpose可以指定轴的顺序,这在多维数组相乘和特征维度交换时格外有用。

2.3 广播机制与矩阵乘法:为什么别用 for 循环

刚接触numpy时,我犯过一个经典错误:用一个 Pythonfor循环给数组的每个元素加同一个数。后来才知道,numpy有一套自己的广播机制,可以让不同形状的数组直接进行算术运算:

# 广播:一维数组和标量 arr = np.array([1, 2, 3]) result = arr + 10 # [11 12 13] # 广播:二维数组加一维数组 matrix = np.array([[1, 2], [3, 4]]) vector = np.array([10, 20]) result = matrix + vector # [[11 22] # [13 24]]

广播的核心规则是:两个数组从尾部维度开始对齐,要么维度相等,要么其中一个为 1。理解这个规则后,很多看似奇怪的结果就变得合理了。比如说二维数组和行向量相加没问题,但和列向量reshape(-1, 1)相加时,形状对齐的逻辑就会发生微妙的改变,输出结果完全不同。

矩阵乘法是机器学习里真正的重头戏。np.dot()、@运算符、np.matmul()都可以做矩阵乘法,三者在二维数组上行为一致。注意区分*(逐元素乘)和@(矩阵乘),这是新手必踩的坑:

A = np.array([[1, 2], [3, 4]]) B = np.array([[5, 6], [7, 8]]) # 逐元素乘法 C = A * B # [[ 5 12] # [21 32]] # 矩阵乘法 D = A @ B # [[19 22] # [43 50]]

三维数组相乘稍微复杂一点:np.matmul会把三维数组视作一批二维矩阵,对批量中的每个矩阵分别做乘法,这在深度学习的前向传播代码里非常常见。比如一个形状为(32, 10, 5)的批次数据乘一个形状为(5, 3)的权重矩阵,numpy会把这个操作映射到 32 个矩阵各自乘以同一个权重。如果不知道这个特性,你自己写循环慢慢乘,效率至少差一个数量级。

矩阵求逆是另一个常用操作,用np.linalg.inv(A)即可。对于线性回归的解析解w = (X^T X)^{-1} X^T y,就依赖这个函数。但注意:当X^T X接近奇异时,直接求逆会得到数值上不稳定的结果,这时更稳妥的做法是用np.linalg.pinv求伪逆,或者改用np.linalg.solve(A, b)直接解线性方程组——后者在数值稳定性上比显式求逆好得多。这个问题在特征高度相关的数据集上尤其突出,我早年写线性回归代码时就被这个坑坑过一次。

2.4 numpy 比 Python list 快在哪:一个直观的实验

我经常被问到:numpy到底比list快多少?这里分享一个我课堂上常做的对比实验:生成长度为 100 万的列表,每个元素平方再求和。

import time n = 1000000 python_list = list(range(n)) start = time.time() result = sum([x ** 2 for x in python_list]) python_time = time.time() - start arr = np.arange(n) start = time.time() result_numpy = np.sum(arr ** 2) numpy_time = time.time() - start print(f"纯Python耗时: {python_time:.4f}秒") print(f"numpy耗时: {numpy_time:.4f}秒")

我自己的机器上,纯 Python 大约耗时 0.3 到 0.5 秒,numpy大约 0.002 到 0.005 秒,差距在 100 倍左右。原因有两层:第一,numpy的底层是用 C 语言实现的,循环在 C 层完成,而不是 Python 解释器逐条执行;第二,numpy数组在内存中是连续存储的,CPU 缓存命中率高,而 Python 列表存储的是对象引用,取数据还要多一层间接寻址。这就是为什么所有机器学习框架底层都依赖类似numpy的数组结构——只有把大计算量放在编译型语言层面,才能保证训练速度可以接受。

3. pandas 核心操作:从数据结构到数据清洗

3.1 Series 和 DataFrame:两个你必须吃透的概念

pandas的核心就是两个数据结构:Series和DataFrame。Series可以理解成带索引的一维数组,DataFrame是多个Series拼成的二维表格。新手学pandas最忌讳只看语法不悟结构,我建议你在脑子里建立一个模型:DataFrame 就是一张 Excel 表,每一列是一个 Series,列名就是索引,每一行由行索引定位。

import pandas as pd # 创建 Series s = pd.Series([10, 20, 30], index=['a', 'b', 'c']) # a 10 # b 20 # c 30 # 创建 DataFrame 的三种常用方式 # 方式一:字典,键是列名,值是列表 df1 = pd.DataFrame({ '姓名': ['张三', '李四', '王五'], '年龄': [25, 30, 35], '城市': ['北京', '上海', '深圳'] }) # 方式二:从 numpy 数组 + 列名 df2 = pd.DataFrame(np.random.randn(4, 3), columns=['A', 'B', 'C']) # 方式三:从列表的列表 df3 = pd.DataFrame([[1, 2], [3, 4]], columns=['x', 'y'])

这三种创建方式基本覆盖了你日常会遇到的场景。注意方式二的columns参数一旦不指定,就会默认用 0、1、2 这样的整数做列名,读起来非常难受。所以只要是从数组转 DataFrame,我强烈建议你随手把列名补上,否则后面df['0']这种反人性的代码会让你怀疑自己在写什么东西。

选择列、加列、删列是最高频的操作,这里给出一套标准操作:

# 选择单列,返回 Series df1['姓名'] # 选择多列,返回 DataFrame df1[['姓名', '城市']] # 新增列 df1['收入'] = [10000, 12000, 15000] # 删除列 df1.drop('城市', axis=1, inplace=True) # 按行选择 df1.iloc[0] # 第一行 df1.loc[df1['年龄'] > 28] # 年龄大于28的行

这里要特别区分loc和iloc。loc用的是标签索引,iloc用的是整数位置索引。比如df.loc[0]取的是行索引为 0 的那一行,而df.iloc[0]取的是第一行。如果行索引刚好是 0、1、2 这样的整数且顺序一致,两者看起来一样;但一旦你做过筛选或重排,行索引和位置就会错开,混淆loc和iloc就会取到完全错误的数据。这在数据清洗阶段是非常隐蔽的 bug。

3.2 读取 Excel 和 CSV:最实用的数据入口

学机器学习的场景里,数据大多来自 CSV、Excel、数据库,pandas的读取接口直接决定你第一步顺不顺畅。

# 读取 CSV df_csv = pd.read_csv('data.csv') # 读取 Excel,注意需要安装 openpyxl df_excel = pd.read_excel('data.xlsx', sheet_name='Sheet1') # 写入 CSV df_result.to_csv('output.csv', index=False) # 写入 Excel df_result.to_excel('output.xlsx', index=False)

有几个参数值得反复强调。read_csv里的encoding参数,国内常用utf-8和gbk两种编码,如果你读文件直接报UnicodeDecodeError,不用怀疑,大概率是编码不对,把encoding='gbk'试一遍基本能解决。read_excel如果你没装openpyxl,会报一个ImportError: Missing optional dependency 'openpyxl',解决方法是pip install openpyxl。还有一个实用参数parse_dates,可以在读取时直接把某些列解析成时间类型,省得后面再做类型转换。

读取之后要做的第一件事永远是看数据长什么样,我每个项目都固定执行这几行:

# 查看前5行 df.head() # 查看整体信息 df.info() # 查看统计描述 df.describe() # 查看列名 df.columns

df.info()输出的每一列的非空值数量和数据类型,对判断数据质量问题至关重要。df.describe()让你快速知道数值列的均值、标准差、最大最小值、四分位数——这些是特征工程阶段的基础输入。

3.3 数据清洗四板斧:缺失值、重复值、列重命名、条件筛选

数据清洗是机器学习项目里最耗时的环节,通常占整个项目 60% 以上的精力。pandas在这里的能力直接决定你是不是能在合理时间内完成项目。

处理缺失值的核心选择有两个:删除或填充。

# 检查缺失值 df.isnull().sum() # 删除有缺失值的行 df_clean = df.dropna() # 删除有缺失值的列 df_clean_cols = df.dropna(axis=1) # 用均值/中位数/指定值填充 df['年龄'].fillna(df['年龄'].mean(), inplace=True) df['城市'].fillna('未知', inplace=True)

缺失值处理不是无脑删除,要分情况。如果缺失比例低于 5%,我通常会直接dropna;如果缺失比例在 10% 到 30%,且是数值列,用中位数填充更稳妥(均值容易被极值带偏);如果是分类列,用众数或单独的“未知”类别填充。如果缺失比例超过 50%,这一列的信息量已经大打折扣,我个人的习惯是直接删除整列,除非这列是算法指定的关键特征。

重复值处理相对简单:

df.drop_duplicates(inplace=True)

如果只想针对某几列判断重复,可以传子集参数:df.drop_duplicates(subset=['姓名', '年龄'])。

列重命名和条件筛选也是高频操作:

# 重命名列 df.rename(columns={'姓名': 'name', '年龄': 'age'}, inplace=True) # 条件筛选 old_people = df[df['age'] > 40] city_filter = df[df['city'].isin(['北京', '上海'])]

筛选的核心逻辑是把布尔 Series 传给 DataFrame 的索引器,凡是结果为 True 的行保留,False 的剔除。这个逻辑是pandas筛选的精髓,后续所有的复杂筛选都是在这个基础上叠加多个条件:

df_filtered = df[(df['age'] > 25) & (df['city'] == '北京')]

注意这里必须用&而不是and,必须给每个条件加括号,否则直接报错。初学时我在这上面吃过很多亏,现在写下这段代码,希望你能少走这个弯路。

3.4 数据类型转换与正则表达式在清洗中的实战

原始数据几乎不会乖乖以正确的类型出现。年龄列可能是字符串,日期列可能是文本格式,收入列里可能混了逗号和货币符号。pandas的类型转换就是解决这些问题的心脏。

# astype 转换数值类型 df['年龄'] = df['年龄'].astype(int) df['收入'] = df['收入'].astype(float) # 转日期类型 df['购买日期'] = pd.to_datetime(df['购买日期']) # 提取年份 df['购买年份'] = df['购买日期'].dt.year # 把带逗号和货币符号的字符串转数值 df['收入'] = df['收入'].str.replace('¥', '').str.replace(',', '').astype(float)

astype是基础操作,但要注意几个隐雷:如果列里有非数值字符(比如'25岁'),astype(int)会直接抛异常;此时要先做字符串处理或者用pd.to_numeric(errors='coerce'),它会把转换失败的值变成 NaN,你再统一填充。

正则表达式在数据清洗里的地位被很多人低估了。pandas的.str访问器天然支持正则:str.contains判断是否包含指定模式,str.extract提取匹配片段,str.replace做模式替换。比如一个包含邮箱的列,你想提取用户名部分:

df['用户名'] = df['邮箱'].str.extract(r'^([^@]+)@')

再比如电话号码列格式不统一,你想把 138-1234-5678 和 13812345678 统一成无横线格式:

df['电话'] = df['电话'].str.replace('-', '')

正则表达式这个工具在文本型数据的特征工程里是绕不开的。我的建议是:先掌握几个最常用的元字符(^开头、$结尾、\d数字、+一次或多次、[]字符集),足够处理 90% 的清洗场景。别指望一次性学完,用到哪查到哪,这个策略在数据清洗这个领域效率最高。

3.5 ewm 函数参数解读:来自时间序列的补充技巧

虽然机器学习里pandas的ewm(指数加权移动平均)不算入门必修内容,但因为搜索热词里有,我就多说一句。ewm在时间序列分析里用来做平滑处理,它的核心参数是span、alpha和adjust。

# 对时间序列做指数加权平滑 df['平滑值'] = df['数值'].ewm(span=10, adjust=False).mean()

span表示窗口跨度,窗口越大平滑效果越明显,一般取 5 到 20 之间做实验对比。alpha是衰减因子,如果你是老派金融出身,可能习惯直接用alpha;它和span的关系是span = 2 / alpha - 1。adjust=False表示从序列开头就用指数加权,否则会在早期做校正处理。简单的理解就是:ewm给近期的数据更高的权重,远期的数据权重指数衰减。在预测任务里,它常被用来构造时序特征,让模型感知到数据的“近期趋势”。

4. matplotlib 核心操作:从三个核心概念到常用图形

4.1 先讲清楚 figure、axes、axis 的关系

matplotlib新手最容易卡住的就是figure、axes、axis这三个概念。我教你一个绝对忘不掉的理解方法:把figure想成整张画布,axes是画布上的一块绘图区域,axis是这块区域上的坐标轴。

画布上可以只有一块绘图区,也可以划分成多个绘图区(比如 2×2 的子图)。每个绘图区axes有自己的坐标系、标题、图例。每个axes`` 内部有两条核心坐标轴axis,分别叫x-axis和y-axis。axis控制的是轴的刻度、范围、标签这些细节;axes控制的是整个绘图区域的内容;figure` 控制的是整体布局、大小、保存设置。

import matplotlib.pyplot as plt fig, ax = plt.subplots(figsize=(8, 5)) ax.plot([1, 2, 3], [4, 5, 6]) # 操作 axis:设定刻度范围 ax.set_xlim(0, 4) ax.set_ylim(0, 7) ax.set_xticks([1, 2, 3]) # 操作 axes:设定标题和标签 ax.set_title('示例图') ax.set_xlabel('X轴') ax.set_ylabel('Y轴')

很多人写的代码里只看到plt.plot()和plt.show(),看不懂fig, ax = plt.subplots()这种写法。其实前者是pyplot接口,适合快速画图;后者是面向对象接口,适合精细控制。两者的底层逻辑完全一样,只是pyplot帮你自动创建了figure和axes,隐藏了细节而已。等你需要画多子图、调整子图间距、分别控制不同坐标轴的刻度时,面向对象接口就比pyplot灵活太多了。

子图的布局用subplots一行就能搞定:

fig, axes = plt.subplots(2, 2, figsize=(10, 8)) # axes 是一个 2x2 的数组,axes[0, 0] 是左上角子图 axes[0, 0].plot([1, 2, 3], [1, 4, 9]) axes[0, 1].scatter([1, 2, 3], [4, 5, 6]) axes[1, 0].bar(['A', 'B', 'C'], [3, 7, 5]) axes[1, 1].hist(np.random.randn(1000), bins=20)

4.2 散点图、折线图、柱状图、直方图:四类必备图形实战

机器学习分析里最常用的四个基本图形:散点图看分布和相关性、折线图看趋势、柱状图看分类对比、直方图看数值分布。

散点图在特征分析阶段是神器,尤其当你研究某两个特征之间的关系时:

# 散点图:带透明度、网格和图例 x = [1, 2, 3, 4, 5, 6, 7, 8] y = [3, 4, 2, 5, 6, 7, 5, 8] z = [50, 80, 30, 60, 90, 70, 55, 85] # 用颜色或大小映射第三个维度 fig, ax = plt.subplots(figsize=(8, 6)) scatter = ax.scatter(x, y, c=z, cmap='viridis', s=z, alpha=0.6) # 添加图例 cbar = plt.colorbar(scatter) cbar.set_label('数值大小') ax.grid(True, linestyle='--', alpha=0.5) ax.set_xlabel('特征A') ax.set_ylabel('特征B') ax.set_title('特征A与特征B的散点图')

注意这里alpha=0.6控制透明度,当数据点重叠很多时,降低透明度能看清密度分布。s=z让点的大小跟随第三个维度变化,相当于在二维散点图里编码第三维的信息。grid(True, linestyle='--', alpha=0.5)给你的网格加了虚线样式和透明度,比默认的实线网格清爽很多。

色标(colorbar)是散点图里非常实用的功能,它把颜色映射关系标注在图的右侧,读者一眼就能看出颜色对应的数值大小。这条经验来自我早期做特征分析时踩的坑:当时画了带颜色映射的散点图,没加colorbar,自己都看不懂图里颜色深浅代表什么,更别说汇报给团队了。

折线图适合观察时间序列和模型训练过程。训练神经网络时,loss 曲线就是用折线图画的——每轮迭代记录一个 loss 值,画出来就能直观看到收敛情况。加注释、加多个 Series 对比是基本操作:

import numpy as np epochs = np.arange(1, 11) train_loss = [0.8, 0.6, 0.45, 0.35, 0.28, 0.22, 0.18, 0.15, 0.12, 0.10] val_loss = [0.9, 0.7, 0.55, 0.45, 0.40, 0.36, 0.34, 0.33, 0.32, 0.31] fig, ax = plt.subplots(figsize=(8, 5)) ax.plot(epochs, train_loss, marker='o', label='训练损失', linewidth=2) ax.plot(epochs, val_loss, marker='s', label='验证损失', linewidth=2) ax.set_xlabel('迭代轮数') ax.set_ylabel('损失值') ax.legend()

当val_loss在后期不降反升时,基本可以判定模型过拟合了,这就是折线图给机器学习调参带来的直观价值。

4.3 图例、网格、透明度与配色:让图“能用”到“能看”

很多人觉得matplotlib画图“丑”,其实问题大多出在细节参数上。图例、网格、透明度、配色这四样东西调好,图的专业感立刻提升一个档次。

图例用ax.legend()就能加,但必须确保每个plot调用时都带了label参数,否则图例列表就是空的。网格用ax.grid(True, linestyle='--', alpha=0.6),虚线网格信息干扰小。透明度用alpha,范围 0 到 1,值越小越透明。配色可以用cmap参数指定,常用的有viridis、plasma、coolwarm,自己的代码里如果不特意指定,就用 matplotlib 默认的tab10色板也行。

有一个细节值得单独提醒:plt.rcParams可以全局配置图片风格,比如统一字体大小、统一 dpi。我一般在项目开头就设置好:

plt.rcParams['figure.dpi'] = 100 plt.rcParams['font.size'] = 12 plt.rcParams['axes.grid'] = True

这样全项目画出的图风格统一,省去每张图都要单独调参的重复劳动。

4.4 雷达图:一个特殊图形的实现思路

雷达图(Radar Chart)虽然在常规数据分析里不算高频,但在需要同时展现多维指标的场景里非常好用。它把多个维度放在同一个圆形坐标系上,每个维度是一个从中心向外发散的轴。

import numpy as np import matplotlib.pyplot as plt # 五个维度的评估数据 categories = ['准确率', '召回率', 'F1', '训练时间', '可解释性'] values = [0.85, 0.78, 0.80, 0.60, 0.70] # 闭合数据:首尾相连 angles = np.linspace(0, 2 * np.pi, len(categories), endpoint=False).tolist() values += values[:1] angles += angles[:1] fig, ax = plt.subplots(figsize=(6, 6), subplot_kw={'polar': True}) ax.plot(angles, values, 'o-', linewidth=2) ax.fill(angles, values, alpha=0.25) ax.set_xticks(angles[:-1]) ax.set_xticklabels(categories) ax.set_ylim(0, 1)

实现雷达图的关键有三点:一是用subplot_kw={'polar': True}指定极坐标,二是把数据首尾闭合(把第一个值复制到末尾),三是角度均匀分布在 0 到 2π 之间。fill填充了半透明区域,视觉上更饱满。机器学习模型评估里,如果你要在准确率、召回率、F1、训练时间等指标间做横向比较,雷达图比一堆数字表格直观得多。

4.5 多子图布局与图片保存:从单图到报告级图表

当你要生成一份完整的数据分析报告,单图就远远不够了。多子图的布局、子图间距调整、图片保存设置都得上手。

# 创建 2x1 子图 fig, axes = plt.subplots(2, 1, figsize=(8, 10)) # 第一个子图 axes[0].plot(epochs, train_loss, label='训练损失') axes[0].legend() # 第二个子图 axes[1].bar(['A', 'B', 'C'], [3, 7, 5]) # 调整子图间距 fig.tight_layout() # 保存图片 fig.savefig('模型训练分析.png', dpi=150, bbox_inches='tight')

fig.tight_layout()自动调整子图之间的间距,防止标题或坐标轴标签互相重叠。savefig时dpi=150保证清晰度,bbox_inches='tight'裁掉多余白边。这两个参数是我每次保存图表时必写的组合,因为默认参数保存出来的图片经常四周留白过多,放进文档或者博客里大小很难看。

5. 常见问题与排查技巧实录

5.1 安装环节的坑:pip 卡住、版本冲突、依赖缺失

这三个库的安装看似简单,实际踩坑的人不少。最常见的故障是pip install numpy时卡在Installing backend dependencies。这个问题的根源是 pip 尝试构建需要依赖的后端包,而网络环境不给力,导致长时间无响应。解决方案有两个:一是用国内镜像源加速,pip install numpy -i https://pypi.tuna.tsinghua.edu.cn/simple;二是直接装预编译的 wheel 包,不要走源码编译。

# 使用清华镜像安装三件套 pip install numpy pandas matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple

numpy版本不匹配也是高频问题。很多旧代码是numpy1.x 时代写的,如果你装了 2.x,运行时会报类似module 'numpy' has no attribute 'float'的错误。这是因为新版本移除了一些历史遗留 API。解决思路是:优先升级代码里过时的写法(把np.float改成np.float64),而不是降级numpy。除非你依赖的某个底层库(比如某个老版本pandas或scikit-learn)明确声明只支持旧版numpy,再考虑降级。

我建议的数据科学环境管理方案是:不要用系统自带的 Python 直接装这三个库,会污染全局环境。用Anaconda或者至少创建一个venv虚拟环境,在隔离环境里安装依赖。尤其是同时做多个机器学习项目时,项目 A 需要numpy 1.26、项目 B 需要numpy 2.0,如果装在同一环境里,就是无休止的版本地狱。

5.2 中文字体显示乱码:matplotlib 的经典老大难

matplotlib画图时遇到中文标题显示成方块,几乎是每个中文用户必踩的坑。原因是 matplotlib 默认字体里没有中文字符。我常用的解决方法是设置系统已有的中文字体:

import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei'] # Windows 黑体 plt.rcParams['axes.unicode_minus'] = False # 解决负号显示为方块的问题

Mac 用户可以把SimHei改成PingFang SC或Arial Unicode MS。如果实在不行,更粗暴的方案是全局指定一个系统字体路径:

import matplotlib.font_manager as fm font_path = '/System/Library/Fonts/PingFang.ttc' fm.fontManager.addfont(font_path) plt.rcParams['font.family'] = fm.FontProperties(fname=font_path).get_name()

注意axes.unicode_minus这个参数必须一起设置,否则坐标轴上的负号会被渲染成方框。这个坑的诡异之处在于,你明明设置了中文字体,负号却还是显示异常——其实两者是完全独立的渲染问题。

5.3 pandas 读取 Excel 报错与 DataFrame 显示截断

pd.read_excel报错ImportError: Missing optional dependency 'openpyxl'是新手最常见的拦路虎。解决方案很简单:

pip install openpyxl

还有一类问题是 DataFrame 打印显示不全,中间有省略号:

import pandas as pd # 显示所有列 pd.set_option('display.max_columns', None) # 显示所有行 pd.set_option('display.max_rows', 100) # 设置显示宽度 pd.set_option('display.width', 1000)

这几个set_option配置只要写在脚本开头,整个项目的打印输出都会保持完整。否则你会经常遇到df.head()后面的列被截断的情况,以为数据没读取成功,白白浪费排查时间。

5.4 numpy 数组和 pandas DataFrame 互相转换的注意点

机器学习建模时,模型通常只接受numpy数组,但数据操作又是在pandas里做的,所以两者的转换是必须掌握的基础操作:

import pandas as pd import numpy as np df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]}) # DataFrame 转 numpy 数组 arr = df.values # 或者 arr = df.to_numpy() # numpy 数组转 DataFrame df_back = pd.DataFrame(arr, columns=['A', 'B'])

这里有一个在建模阶段非常容易翻车的细节:df里有非数值列(比如字符串分类),直接df.to_numpy()会把整个数组变成字符串类型,模型根本没法算。所以转换前必须过滤掉不想要的列,或者先做特征编码。我自己一般在建模前先df[['A', 'B']].to_numpy(),只挑数值列转换,避免整个数组被"降级"成字符串。

另外还有一个小潜规则:df.values拿到的可能是视图也可能是副本,对结果做过修改时,不一定回写原df。而df.to_numpy()接口更规范,返回的数组和 DataFrame 的引用关系也更可控。新版 pandas 官方推荐to_numpy(),我也是后来才从values切到to_numpy()的,建议直接养成这个习惯。

5.5 三维数组广播与相乘的隐蔽问题

热词里有一条numpy三维数组相乘,这里值得单独讲透。三维数组arr.shape = (2, 3, 4)可以理解成两个3×4的矩阵摞在一起。对它和另一个二维数组做矩阵乘法时,广播规则会把二维数组应用到每一个“切片”上:

a3 = np.random.randn(2, 3, 4) # 两个 3x4 矩阵 b2 = np.random.randn(4, 5) # 一个 4x5 矩阵 # 结果: (2, 3, 5),即每个 3x4 矩阵各自乘以同一个 4x5 矩阵 result = a3 @ b2

但如果你写的是a3 @ b3,其中b3.shape = (2, 4, 5),那么numpy要求两个三维数组的第一个维度一致,做的是“配对矩阵乘法”:第一个矩阵乘第一个矩阵,第二个矩阵乘第二个矩阵。如果第一个维度不一致,直接抛ValueError。这个规则理解透了再去读深度学习框架里的批处理代码,就会觉得合理多了,因为神经网络的批量矩阵运算就是同一套逻辑。

6. 绕坑清单与就业实战建议

6.1 学习顺序的三点忠告

第一,不要跳步直接学算法。我见过太多人拿着《机器学习》教科书啃理论,正则化、梯度下降说得头头是道,打开电脑却连数据都读不进来。理论学习和工具学习是两条平行线,缺一条都搭不出真正的项目能力。第二,学工具时不要啃完整文档。pandas中文手册、几百页的教程看上去很全,但真的没必要通读。最有效的路径是:先学 20% 最常用的操作,然后在真实数据上反复练,遇到不会的查文档,这样比从头死磕效率高几倍。第三,手边要常备一份“查系表”,这三个库的函数非常多,靠记忆硬背是低效的,用的时候能快速想起“这个需求该用哪个函数”就够了。

6.2 用一个小项目串起三件套

学完以上所有操作之后,我强烈建议你独立完成一个“端到端的小项目”,把三个库串起来用。这里给你一个我经常推荐的入门任务:

  • 用pandas读取一份真实的 CSV 数据(比如电商订单、房价数据,网上都有公开数据集)
  • 对数据进行清洗:处理缺失值、类型转换、去重、按条件筛选
  • 用numpy计算一些统计量:均值、标准差、相关系数矩阵
  • 用matplotlib画出特征的散点图和直方图,找出特征之间的关系
  • 最后,尝试用numpy手写一个最简单的线性回归(不用scikit-learn),用梯度下降更新参数

这个任务看似简单,但覆盖了三条主线:pandas的数据预处理能力、numpy的数值计算能力、matplotlib的可视化分析能力。你能把这条链路完整跑通,机器学习的“准备工作”才算真正过关。很多招聘笔试里会被问到 “数据预处理怎么做”,你的回答如果包含了缺失值处理、类型转换、异常值过滤这些具体操作和踩坑经历,说服力比背一堆理论强得多。

6.3 三件套之外,下一步该学什么

这三件套只是地基,完事之后你自然会发现还有一批相邻工具等着你:seaborn可以基于matplotlib画出更漂亮的统计图,scikit-learn封装了几乎所有的经典机器学习算法,Jupyter Notebook/JupyterLab是交互式写代码和数据探索的标配环境。我也建议你在熟悉pandas常规操作后,抽空了解一下polars——不是说现在就要替换,而是 Python 数据处理生态这两年的趋势是向高性能靠拢,早接触不亏。

回到我自己这几年的体会:刚入门时总觉得学工具库枯燥,跟机器学习算法比起来没有“高级感”。但实际上,后期所有模型调优、特征工程、结果分析,最终都落到这三件套的操作上。你翻看任何一篇机器学习论文的复现代码,前三步一定是pandas读数据、numpy做变换、matplotlib画结果图。把这三样练成肌肉记忆,之后再面对任何新算法、新框架,你都有底气说:数据这块我能搞定。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 5:26:19

IP协议深度拆解:报文头、路由转发与GNS3抓包实验

搞数据通信这些年,我有个习惯:只要有人问我“IP协议到底是个啥”,我不急着背定义,而是先丢一个GNS3实验给他做。不是装逼,是真的只有你在抓包里亲眼看到,源IP和目的IP从头到尾不变、源MAC和目的MAC却在每一…

作者头像 李华
网站建设 2026/9/28 5:25:49

从39.7%到0%:知网AIGC检测降AI率完整实操指南

前阵子有个朋友抱着笔记本电脑来找我,说学校的预审系统给他论文标了一个数字:知网AIGC检测率39.7%。学院要求降到10%以下才能送审,他连续折腾了快两周,同义词替换、调整语序、把中文翻成英文再翻回来,能用的土办法都试…

作者头像 李华
网站建设 2026/9/28 5:25:48

知网AIGC检测率从39.7%降到0%:论文降AI率完整实践指南

39.7%这个数字,我盯了整整三天。当时论文正文已经改到第五版,知网AIGC检测结果还是稳如泰山地停在39.7%,全班都在传的“近义词替换大法”“把句子打乱重组”“删减AI标记段落”,我全试了一遍,毫无波澜。最离谱的是有一…

作者头像 李华
网站建设 2026/9/28 5:25:17

光伏逆变器绝缘检测:NB/T 32004标准解读与现场测试实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 5:25:14

JSP+Servlet+MySQL超市管理系统:从环境部署到二次开发全流程解析

简介:面向计算机专业本科毕业设计及课程设计场景的JSP超市管理系统完整资料包,围绕“源码数据库说明文档”三件套组织,帮助毕业生快速掌握基于JSP、Servlet、MySQL与Tomcat的经典Web开发模式,解决选题后无从下手、系统实现不完整、…

作者头像 李华
网站建设 2026/9/28 5:23:41

YOLO鸡蛋品质分级数据集实战:五类标签与训练避坑指南

简介:一套面向YOLO系列算法训练与验证的鸡蛋品质分级目标检测数据集,包含615张带标签图像,标注覆盖血染鸡蛋、棕色鸡蛋、脏污鸡蛋、白鸡蛋和钙沉积蛋五类典型对象,适合食品分拣、农畜产品质检、智能养殖等场景,也可作为…

作者头像 李华