不扯那些“先学Python再学数学最后看西瓜书”的废话了。机器学习入门有个特别烦人的现实:算法课听懂不代表你能动手跑通,因为大部分时间你其实不是在调模型,而是在跟数据较劲。数据怎么读进来?格式不对怎么办?清洗完了怎么可视化?这些活儿全得靠Python数据科学三件套——numpy、pandas、matplotlib——来干。很多人卡在半路不是数学不行,而是这三件套的“基本操作”没练熟,拿到一份真实数据根本无从下手。
这篇东西我就按自己实际备课的经验,把三件套里最常用、机器学习准备阶段必须掌握的操作给你捋一遍。从环境搭建、数组操作、数据清洗,到画图、常见报错排查,全部是能直接照抄的代码和思路。适合刚看完Python语法、准备啃机器学习算法但还没怎么碰过真实数据的人参考。
1. 开始之前:工具链定位与安装避坑
1.1 为什么机器学习准备阶段绕不开这三个库
机器学习流程拆开看就是“数据收集 -> 数据清洗 -> 特征工程 -> 模型训练 -> 结果分析”这么几步。numpy管数值计算,是底层引擎;pandas管表格数据,清洗和处理基本靠它;matplotlib管画图,让你看到数据长什么样、模型效果如何。三个库各有分工又互相依赖,pandas底层用numpy存数据,matplotlib绘图的输入也经常是numpy数组或pandas数据结构。
很多人上来就抱着《机器学习》周志华那本书啃,把数学推导看完了,一动手写代码发现连数据长什么样都不知道。我的建议很直白:前期不追求把三个库所有API背下来,但核心操作要达到“闭着眼写出来”的程度。具体来说就是numpy的数组创建、索引、运算、形状操作;pandas的DataFrame读写、筛选、分组、缺失值处理;matplotlib的折线图、散点图、柱状图、子图布局和样式微调。这批操作练熟了,后面学sklearn、学深度学习框架都会顺很多。
1.2 环境搭建:Anaconda还是纯pip
推荐直接装Anaconda,原因很简单:它把Python解释器、numpy、pandas、matplotlib、Jupyter Notebook一次性打包好了,省去逐个安装的麻烦。我自己最初用纯Python环境,结果装库的时候经常遇到依赖冲突,装个包要折腾半天。Anaconda默认带的conda包管理器还会帮你处理依赖关系,实测下来省心太多。
如果你坚持用pip安装也行,但有个高频坑值得先说一下:执行pip install numpy卡在“Installing backend dependencies”这是Windows环境下常见问题,通常是pip版本太旧或者网络源不稳定。解决办法是升级pip并换国内镜像源:
pip install --upgrade pip pip install numpy pandas matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple换源后速度会快非常多。另外注意numpy版本兼容性,numpy 2.x发布后有些旧包还是按1.x编译的,会报类似“numpy._core.multiarray failed to import”的错误,提示numpy版本不匹配。这种问题最简单的处理方法是固定版本安装,比如pip install "numpy<2.0",或者干脆用conda建一个干净环境。我在Windows上实测,Python 3.10搭配numpy 1.26.x跑pandas和matplotlib非常稳。
Pycharm里装pandas包或者matplotlib包也是一样的逻辑,在Settings -> Project -> Python Interpreter里点加号搜索安装,如果提示超时就手动加镜像源。我见过很多人在这一步卡住,其实背后就是网络问题,跟代码没半点关系。装好之后可以跑个快速测试:
import numpy as np import pandas as pd import matplotlib.pyplot as plt print(np.__version__) print(pd.__version__) print(plt.__version__)三个版本号都正常打印出来,环境就算准备好了。
2. numpy核心操作:从创建数组到矩阵运算
2.1 ndarray的创建与基础索引
numpy的核心数据结构是ndarray(N维数组)。Python内置的list也能存数据,但机器学习动辄几十万条样本,list的运算效率完全跟不上。numpy快在哪?一是底层用C语言实现,二是向量化运算避免Python循环的开销。举个直观例子:对10万个元素每个加1,用numpy比用Python列表推导式快几十倍。这就是机器学习的现实需求,数据量大是常态,必须用专门的数值计算库。
创建数组最常用的几个方法:
import numpy as np # 从列表创建 a = np.array([1, 2, 3]) # 全零数组,shape是形状参数 zeros = np.zeros((3, 4)) # 全一数组 ones = np.ones((2, 3)) # 等差数列 arr = np.arange(0, 10, 2) # [0 2 4 6 8] # 随机数数组 rand = np.random.randn(3, 3) # 标准正态分布索引和切片的坑在于numpy的索引规则跟Python list不太一样,多维数组用逗号分隔维度:a[1, 2]取第1行第2列,a[:, 0]取所有行第0列。切片返回的是视图不是副本,什么意思?就是对切片结果修改会影响原数组。我用arr[1:3]切出来的子数组,改了子数组的值,原数组也变了。这跟Python list切片的行为不一样,list切片是复制,numpy切片是引用。如果确实要复制,得显式调用.copy()。这个特性算是新手最容易踩的暗坑之一,尤其是在做数据预处理时一不小心就把原始数据污染了。
布尔索引是个很有用的技巧:
data = np.array([10, 25, 30, 45, 60]) # 筛选大于30的元素 mask = data > 30 print(data[mask]) # [45 60]机器学习里筛掉异常值、按条件过滤样本,全靠这一招。
2.2 形状操作和广播机制
数组形状操作是numpy里最实用的技能。reshape改变形状但不改数据,flatten把多维数组拉平成1维,transpose做转置。这里有个细节值得注意:reshape(-1, 1)里的-1是自动推断的意思,比如不知道总共有多少行,让numpy根据列数自动计算。特征矩阵标准化时经常要把一维数组转成列向量,用的就是这个写法。
广播机制(broadcasting)听起来高大上,本质就是numpy允许不同形状的数组做运算时的自动扩展规则。最典型场景:一个形状为(3, 3)的矩阵要减去自己的每一列均值,均值数组形状是(3,),numpy会自动把(3,)广播成(3, 3)再逐元素相减。规则可以简化成两条:维度从后往前对齐,要么相等、要么其中一个为1、要么缺失。实际的减均值操作就长这样:
X = np.random.randn(3, 3) col_mean = X.mean(axis=0) X_centered = X - col_mean # 广播,每列减去该列均值多维数组相乘要区分清楚“逐元素乘”和“矩阵乘”。逐元素乘用*,两个数组对应位置相乘,要求形状一致;矩阵乘用np.dot()或者@运算符,要求前一个的列数等于后一个的行数。三维数组相乘时这个区别更明显,@做的是批量矩阵乘法,*做的是对应位置广播。线性回归的解析解w = (X^T X)^{-1} X^T y里,全是矩阵乘,写法就是np.linalg.inv(X.T @ X) @ X.T @ y。求矩阵逆用np.linalg.inv,但实际工程中更推荐np.linalg.pinv(伪逆)处理奇异矩阵。你要是考试或者刷题,不借助numpy手写行列式运算那是另一码事,但工程实践里直接调库就好。
3. pandas数据处理:从读取文件到数据清洗
3.1 Series和DataFrame的创建与读写
pandas的两个核心数据结构是Series(一维带标签数组)和DataFrame(二维表格,可以理解为带行索引和列名的Excel表)。DataFrame的每一列其实就是一个Series,所有列共享同一个行索引。这个设计极大方便了按列处理。创建方式很简单:
import pandas as pd df = pd.DataFrame({ '姓名': ['张三', '李四', '王五'], '年龄': [25, 30, 28], '城市': ['北京', '上海', '广州'] })真实场景更多是从文件读入。pandas读写文本文件最常用的两个函数是pd.read_csv()和df.to_csv()。这里必须提醒一个高频编码坑:中文数据用默认参数读csv经常乱码,因为Windows下csv文件可能是gbk编码。解决方法是在read_csv里指定encoding='utf-8'或encoding='gbk',根据文件实际编码来。读Excel用pd.read_excel(),但要确保安装了openpyxl或xlrd库,否则会报缺少引擎的错误。
读取后第一件事永远是看数据结构:
df.head() # 前5行 df.info() # 各列类型和非空计数 df.describe() # 数值列统计描述 df.shape # 行列数info()输出的列类型信息特别关键。比如年龄列如果被读成了object类型(字符串),往往是原始数据里有“25岁”这种带单位的脏数据。数据类型的正确性直接决定后续计算能不能做,这一步偷懒后面全是坑。
3.2 筛选、分组与缺失值处理
pandas筛选分为按行筛选和按条件筛选。按行索引用loc(按标签)和iloc(按位置),我平时用得最多的是布尔条件筛选:
# 筛选年龄大于25的行 df_filtered = df[df['年龄'] > 25] # 多条件筛选 df_filtered2 = df[(df['年龄'] > 25) & (df['城市'] == '上海')] # 选特定列 df[['姓名', '年龄']]多条件筛选的括号不要省,&和|的优先级在pandas里跟Python常规逻辑运算符不一样,不写括号会报错或者结果不对。这是新手高频报错点。
分组聚合是pandas的招牌功能,机器学习做特征工程时经常要按类别算统计量:
# 按城市分组,计算年龄均值 grouped = df.groupby('城市')['年龄'].mean()groupby('城市')相当于把数据按城市切块,然后对每一块执行聚合操作。可以同时算多个统计量:.agg(['mean', 'max', 'min'])。真实项目里这个模式用得极其高频,比如电商数据按用户分组算消费总额、按地区分组算转化率,全是groupby的活。
缺失值处理是数据清洗的重头戏。现实数据基本没有完全干净的,pandas读取时缺失值默认显示为NaN。处理方案就三条路:删除、填充、保留。删除用df.dropna(),填充用df.fillna(value),比如用均值填充df['年龄'].fillna(df['年龄'].mean())。关键原则是你得先搞清楚缺失的机制——是随机缺失还是有系统性偏差?比如一个特征是“收入”,低收入人群可能更不愿意填,直接删掉或填充都会引入偏差。这是数据科学里比较微妙的点,但准备阶段至少要做到:识别缺失、统计缺失比例、选择合理的填充策略。
pandas还内置了ewm指数加权移动平均,参数主要有span、com、alpha,作用是对时间序列做平滑处理,近期数据权重大。机器学习里做时间序列特征时这个函数很有用,比如股票价格收益率序列的波动率特征就是用ewm算出来的。
3.3 字符串正则操作与类型转换
pandas的Series有向量化的字符串处理方法,通过.str访问器调用。数据清洗中经常要从“12345”这种纯数字字符串里提取模式,或者把用户评论里的噪音去掉,靠的是正则表达式。
# 提取邮箱中的域名部分 emails = pd.Series(['test@qq.com', 'hi@163.com']) domains = emails.str.extract(r'@(\w+\.\w+)')字符串列转数值列是另一类高频需求。原始数据里“1,200”这种带千分位逗号的字符串转成整数时,直接pd.to_numeric()会失败,需要先去掉逗号再转:
df['金额'] = df['金额'].str.replace(',', '', regex=False) df['金额'] = pd.to_numeric(df['金额'], errors='coerce')errors='coerce'表示转换失败时置为NaN而不是直接报错中断。这个参数在清洗脏数据时几乎每次都用得到。还有个更隐蔽的坑:类型转换时pandas 2.0之后把字符串列默认类型改成了string类型,跟object类型之间的兼容问题偶尔会导致赋值异常,遇到奇怪报错时可以尝试用.astype(str)显式转换。
4. matplotlib可视化:从接口认知到图形美化
4.1 figure、axes、axis这三个概念到底什么关系
matplotlib的上手曲线不算陡,但figure、axes、axis这三个概念的关系经常把人绕晕。我说个生活类比:整张画布就是figure,画布上每个绘图区域是axes,坐标系里的x轴y轴是axis。所以figure是一个容器,里面可以放一个或多个axes;每个axes有自己的一对axis(x-axis和y-axis)用于显示刻度和范围。
用代码展示最清晰:
import matplotlib.pyplot as plt # 创建figure和单个axes fig, ax = plt.subplots(figsize=(8, 5)) ax.plot([1, 2, 3], [4, 5, 6]) ax.set_xlabel('X轴') ax.set_ylabel('Y轴') ax.set_title('示例图') plt.show()plt.subplots()返回两个对象:fig是画布,ax是绘图区域。后续所有操作都通过ax来调用,这是推荐写法。初学者如果直接用plt.plot(),其实用的是matplotlib自动创建的隐式figure和axes,画简单的图没问题,但涉及多子图、样式定制、坐标轴共享时会很别扭。记住一个原则:显式创建fig和ax,然后所有操作都跟ax走,代码可维护性好得多。
axis的常用操作包括设置刻度位置与标签(set_xticks)、范围(set_xlim)、对数坐标(set_xscale)。多子图布局的关键在plt.subplots(1, 2)返回的ax是一个数组,可以分别控制每个子图。网上很多人问matplotlib如何插入图例,其实就是在plot时指定label参数,然后调ax.legend()即可,位置用loc参数调节,比如loc='upper right'。
4.2 常用图形与样式细节:散点图、网格、透明度、雷达图
机器学习准备阶段必须会画四种图:折线图看趋势、散点图看分布与相关性、柱状图看类别对比、直方图看数值分布。散点图是其中最有用的,画分类数据时不同类别用不同颜色和透明度区分:
fig, ax = plt.subplots(figsize=(8, 5)) ax.scatter(X_train[:, 0], X_train[:, 1], c=y_train, alpha=0.7, s=30) ax.grid(True, linestyle='--', alpha=0.5) ax.set_xlabel('特征1') ax.set_ylabel('特征2') plt.show()这里alpha=0.7控制透明度,设置透明度的直接原因就是样本量大时重叠点太多,不透明的话黑压压一片什么都看不出来。grid(True)加网格线,配合linestyle='--'和alpha=0.5让网格线弱化,不影响主要数据展示。s=30控制点的大小,c既可以是单一颜色字符串,也可以是数值数组或类别标签,配合cmap参数做颜色映射。
雷达图(雷达图又叫蛛网图)在matplotlib里没有专门的函数,需要自己用极坐标投影画:
import numpy as np categories = ['速度', '力量', '技巧', '耐力', '战术'] values = [4, 3, 5, 2, 4] values += values[:1] # 闭合图形 angles = np.linspace(0, 2 * np.pi, len(categories), endpoint=False).tolist() angles += angles[:1] fig, ax = plt.subplots(figsize=(6, 6), subplot_kw={'projection': 'polar'}) ax.plot(angles, values, linewidth=2) ax.fill(angles, values, alpha=0.25) ax.set_xticks(angles[:-1]) ax.set_xticklabels(categories) plt.show()雷达图的原理是用极坐标替代笛卡尔坐标,每个类别占据均匀分布的弧度,数值映射到半径方向。fill填充半透明色让图形更有层次感,多组数据对比时非常直观。这类图在小红书上做数据可视化分享时很出效果,但要注意:雷达图的面积跟数值不成正比,人眼很容易被面积大小误导,实际工作中用来做综合能力评价时需谨慎。
4.3 子图布局与保存
模型对比分析时经常需要把多个图拼在一起。plt.subplots(2, 2)生成2行2列四个子图,循环往每个ax里填充内容。这个操作的关键是ax数组的索引,可以用ax[row][col]访问,也可以用axs.flatten()转成一维数组后循环遍历。
保存图片时有一个非常容易踩的坑:plt.savefig('path.png')之后直接显示一片空白,或者保存出来没有内容。原因是savefig必须在plt.show()之前调用,show之后figure会被清空。另外保存时建议加bbox_inches='tight'参数,避免四周留白过多:
fig.savefig('./output.png', dpi=150, bbox_inches='tight')dpi参数控制清晰度,一般150到200足够日常使用,投稿或报告用300以上。中文字体在matplotlib里也经常出问题,图里出现方框乱码时,全局设置中文字体:
plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False第二句是让负号正常显示,这两个配置我在每次画图前都会写上,几乎成了肌肉记忆。
5. 三件套组合实操:一个真实场景走通全流程
5.1 场景描述与数据准备
前面每个库单独讲,很多新手的真实困惑是:三件套到底怎么配合?我拿一个具体例子串一遍。假设我们有一批电影数据,包含每个电影的类型标签、评分、票房等字段,现在来了一个新电影《唐人街探案》,已知它的特征是某些数值属性,我们要用K近邻(KNN)的思路去找跟它“最相似”的已知电影,从而推断它的分类。
原始数据以csv文件存着,有电影名、类型、评分、票房、时长这几列。第一步用pandas读进来,numpy准备计算,matplotlib最后做可视化分析。这个场景跟机器学习里的分类问题非常贴合:特征提取、距离计算、类别判定,全流程都能演示。
import pandas as pd import numpy as np import matplotlib.pyplot as plt # 1. 读取数据 movies = pd.read_csv('movies.csv', encoding='utf-8') print(movies.info()) print(movies.head())5.2 数据清洗与特征提取
原始数据大概率有脏数据,比如评分列有非数值字符、票房列是“3.2亿”这种带单位的字符串。处理步骤是pandas的强项:
# 去掉票房列的非数值部分,转为数值(单位统一为万) movies['票房_万'] = movies['票房'].str.replace('亿', '', regex=False).astype(float) * 10000 movies['评分'] = pd.to_numeric(movies['评分'], errors='coerce') # 删除缺失行 movies_clean = movies.dropna(subset=['评分', '票房_万']) # 选择用于距离计算的数值特征 features = movies_clean[['评分', '票房_万', '时长']].valuesvalues这里就是pandas转numpy的桥梁,拿到了二维数组,后续距离计算就是numpy的事了。这一步完美体现三件套的配合:pandas负责清洗和提取,numpy负责计算。
5.3 距离计算与结果分析
把《唐人街探案》的特征定义为新样本,计算它跟每个已知电影样本的欧氏距离。特征量纲差异大,票房是几千万量级,评分只有几点几,直接算距离会被票房主导。所以必须先做标准化,这也是机器学习里的标准操作:
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() features_scaled = scaler.fit_transform(features) new_movie = np.array([[7.1, 33445, 136]]) # 评分、票房约3.3亿、时长136分钟 new_movie_scaled = scaler.transform(new_movie) # 欧氏距离 diffs = features_scaled - new_movie_scaled distances = np.sqrt((diffs ** 2).sum(axis=1)) # 找出最近的5个电影 k = 5 nearest_idx = np.argsort(distances)[:k] print(movies_clean.iloc[nearest_idx]['电影名'].tolist())这一段全是numpy操作,diffs ** 2逐元素平方,sum(axis=1)按行求和,np.sqrt开方,np.argsort返回排序后索引。整个过程完全向量化,没有写for循环。
可视化呈现结果时,可以用散点图把样本分布画出来,突出标记新样本和它的邻居们:
fig, ax = plt.subplots(figsize=(10, 6)) sc = ax.scatter(features_scaled[:, 0], features_scaled[:, 1], c='blue', alpha=0.6, s=40) ax.scatter(new_movie_scaled[:, 0], new_movie_scaled[:, 1], c='red', s=100, label='新电影') ax.scatter(features_scaled[nearest_idx, 0], features_scaled[nearest_idx, 1], c='green', s=80, label='最近邻') ax.grid(True, linestyle='--', alpha=0.4) ax.set_xlabel('标准化后的评分') ax.set_ylabel('标准化后的票房') ax.legend() plt.show()整个流程下来,pandas负责数据读入和清洗,numpy负责特征计算和距离度量,matplotlib负责把结果直观地展示出来。这就是机器学习最朴素的一个闭环:数据进来,特征处理,模型判断,结果可视化。
6. 常见问题与排查技巧实录
6.1 安装与环境配置问题速查
安装问题在初学者里占比最高。numpy安装卡在installing backend dependencies大概率是网络源问题,换清华源能解决九成问题。numpy版本不匹配是另一个高频问题,特征一般是import时报错,提到找不到某个特定模块的DLL。解决方法固定版本或者升级相关依赖包。Pycharm里安装pandas包失败,八成是虚拟环境没有激活或者pip源问题,在Project Interpreter里检查当前解释器路径是否符合预期。
6.2 pandas读取与数据类型问题速查
读取csv文件中文乱码,设置encoding='gbk'或encoding='utf-8',根据不同来源灵活切换。列类型不对导致计算报错,用astype或者pd.to_numeric(errors='coerce')做转换。df['列名']筛选列时返回的是Series,如果需要二维结构用df[['列名']],两者形状不同,很多人在这里栽过跟头。loc和iloc分不清楚时记住口诀:loc按名字、iloc按位置。
6.3 matplotlib画图问题速查
中文乱码配置SimHei字体;图例不显示是忘记在plot里指定label参数;多子图画到同一个图上是因为每次没有创建新的fig和ax;坐标轴刻度不理想用set_xticks手动指定。这些问题的共同特征是报错信息不够直观,纯靠肉眼检查结果图才能发现。我的经验是画图脚本里默认加上plt.rcParams的字体配置,固定模板能少踩一半坑。
6.4 我自己踩过的一个印象深刻的坑
有一回处理一个比较大维度的特征矩阵,直接用df.values转numpy数组,接着做矩阵乘法时发现结果不对。排查半天发现是原始DataFrame里有几行有重复索引,values转换时把numpy的数组顺序跟DataFrame的行顺序对应上了,但因为我之前做过几次行筛选,索引标签已经不是连续排列的了。后来养成了两个习惯:一是筛选操作后养成reset_index(drop=True)的习惯,二是拿np.allclose()验证关键计算环节的结果一致性。这个小细节我分享出去之后,不少人都说被同样的坑坑过。
三件套的学习曲线确实不算平缓,但我实际带过这么多人入门的体会是:不要追求把所有API都记住,把频率最高的几十个操作练熟,然后直接拿真实数据开始折腾,遇到问题翻文档,效果比从头到尾读教程好得多。这行当最大的门槛是“动手之前想太多”,先把csv读进来、画出第一张图,后面的东西自然就顺了。