ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Numpy与Pandas实战:高频函数与数据处理全攻略

Numpy与Pandas实战:高频函数与数据处理全攻略 做数据分析和Python开发的朋友应该都有体会只要一碰数据Numpy和Pandas基本就是绕不开的两座山。Numpy干的是底层的数值计算——数组、矩阵、线性代数Pandas干的是更贴近业务的表格处理——读取、清洗、筛选、分组、合并。一个偏计算一个偏数据操作配合起来几乎能覆盖日常90%的数据处理场景。这篇内容算是把自己实战里最常用、最高频的函数从头到尾捋一遍也会把安装环境、版本冲突、类型转换这些经常卡人的点一起讲透。无论你是刚把Python装好的新手还是想系统补一补Numpy和Pandas常用API的开发者按顺序过一遍并且动手敲一敲基本就能覆盖日常数据处理的大部分需求。1. 先把环境搞定Numpy和Pandas的安装与版本坑1.1 最简单的安装方式pip一行到位装Numpy和Pandas其实没那么复杂绝大多数情况下一条命令就能搞定pip install numpy pandas如果网络状况不理想或者一直卡在timeout最简单粗暴的办法是走国内镜像源。清华源是我用得最多的pip install numpy pandas -i https://pypi.tuna.tsinghua.edu.cn/simple国内其他源还有阿里云、中科大等不过清华源胜在稳定、更新快踩坑最少。有个细节如果安装时报了类似could not find a version that satisfies the requirement pandas (from versions: none)的错误多半不是源的问题而是pip版本太旧对当前Python版本的支持不全。先升级pip再装库能解决一大半问题python -m pip install --upgrade pip然后用上面的命令重新安装。顺便说一句手机端的Python环境比如在Termux里装Python也能正常pip install numpy pandas基本流程是先更新源并安装Python再走pip安装。不过手机环境下预编译包受限较多遇到build失败时多检查是不是缺了编译依赖。这种方案应急查数据可以日常开发还是建议用电脑。1.2 PyCharm里安装包最容易踩坑很多人喜欢在PyCharm里直接点界面装包但明明在终端执行过pip install进PyCharm之后import pandas还是报错。这种情况太常见了原因基本都是解释器不一致——PyCharm可能创建了虚拟环境终端用的系统Python两边是两套完全独立的包环境。判断方法很直接在PyCharm的Python Console里执行import sys print(sys.executable)这会打印出当前解释器的路径。只要你之前是在系统终端里pip install的而这里显示的路径带venv或虚拟环境目录名那就说明包装到了两个不同的环境。正确的做法是在PyCharm里打开Settings - Project - Python Interpreter点右上角的加号搜索numpy或pandas然后Install Package。或者统一在PyCharm自带的Terminal里执行pip install这样会自动装进PyCharm当前选择的解释器环境。如果项目里同时有多个解释器在界面右下角状态栏切换环境后注意观察包列表变化别装错。1.3 版本不匹配问题怎么破Numpy和Pandas存在依赖关系Pandas在某个大版本上往往依赖指定范围的Numpy。最典型的报错是ValueError: numpy.dtype size changed, may indicate binary incompatibility. Expected 96 from C header, got 88 from PyObject看到这个基本就是Numpy和Pandas版本不兼容常见于你单独升级了numpy但pandas没跟着升。解决思路就一个把两个库一起升级到兼容的最新版本。pip install --upgrade numpy pandas如果某个项目锁定了pandas1.5.3但环境里numpy被升级到2.x直接import时也会报错。这时候不要盲目升级pandas先看项目依赖文件里锁的版本再根据报错信息回退或升级对应的库pip install numpy1.26.4 pandas2.1.1注意遇到Numpy和Pandas版本冲突时一定不要手动去site-packages目录里替换动态链接库文件大概率越改越乱。优先用pip在项目层面解决或者用conda建独立环境。个人建议在项目初期就把requirements.txt里的版本写死避免昨天还能跑今天同事clone后跑不了的尴尬。对于涉及大量科学计算的项目如果条件允许用conda隔离环境管理科学计算依赖会更省心。2. Numpy常用函数从数组创建到矩阵运算2.1 高频数组创建方式Numpy最核心的对象是ndarrayN维数组它不是Python列表的简单升级而是固定类型、连续内存存储的数据结构计算效率比普通列表高出一个量级。我平时创建数组最常用的几个函数import numpy as np a np.array([1, 2, 3, 4]) # 从列表创建 b np.zeros((3, 3)) # 全0矩阵 c np.ones((2, 4)) # 全1矩阵 d np.arange(0, 10, 2) # 类似range结果是[0, 2, 4, 6, 8] e np.linspace(0, 1, 5) # 等差数列5个数把[0,1]均分 f np.random.rand(2, 3) # [0,1)均匀分布的随机数组 g np.eye(3) # 单位阵这些函数就像是搭积木的零件基本能覆盖90%的初期数据构造需求。需要注意arange和linspace的区别很典型arange指定步长linspace指定个数后者在画图横轴、构造均匀采样点时特别方便比如要生成横坐标从0到10一共分成50个点一个linspace(0, 10, 50)就结束了。另一个超级实用的函数是np.random.randint和np.random.choice生成随机整数、做随机采样时非常常用。比如想从0到9里随机抽出5个不重复的数字h np.random.choice(np.arange(10), size5, replaceFalse)replaceFalse表示不重复抽样这在做训练集和验证集切分、随机抽检时是常规操作比起自己写循环要简洁得多。2.2 数组形状变换与索引切片数组的形状操作有两个词特别容易混淆reshape和resize。reshape返回一个新数组不改变原数组resize则是在原数组上直接修改。很多新手在这里吃过亏以为调用了reshape就会改掉原来的变量结果一打印原数组还是老样子。arr np.arange(12) # [0..11] r arr.reshape((3, 4)) # r是(3,4)arr本身没变 arr.resize((3, 4)) # arr本身变成了(3,4)日常绝大多数场景用reshape就够了因为它不会意外改动原数据配合链式写法也更安全。至于flatten和ravel两者都是把多维数组展平成一维区别同样在于ravel返回视图、flatten返回副本这个细节了解即可。索引和切片也是基本功。二维数组arr[行, 列]冒号取区间省略冒号表示这一维度全取arr np.random.rand(4, 5) print(arr[1, :]) # 第二行所有列 print(arr[:, 2]) # 第三列所有行 print(arr[1:3, 2:4]) # 第2到3行第3到4列这里有一个新手容易栽跟头的点Numpy切片返回的是视图view不是副本。也就是说你对切片结果做的修改会直接影响原数组。比如sub arr[:2, :2] sub[0, 0] 99 # 注意arr[0, 0]也变成了99重要提示Numpy的切片返回的是视图修改会影响原数组想要独立副本必须显式调用copy()。这一点和Python原生的list切片行为完全不同用惯了list的人最容易在这里犯迷糊。2.3 聚合统计与条件筛选聚合函数是数据分析的基础Numpy提供了很齐全的统计函数arr np.array([[1, 2, 3], [4, 5, 6]]) print(arr.sum()) # 所有元素求和 21 print(arr.mean()) # 均值 print(arr.std()) # 标准差 print(arr.min()) # 最小值 print(arr.max(axis0)) # 每列最大值 - [4, 5, 6] print(arr.argmax()) # 最大值的索引注意axis参数的含义axis0通常是沿着行方向跨行结果是逐列的统计值axis1是沿着列方向结果是逐行的统计值。这一条特别容易记反我自己的记忆方式是axis等于几就表示计算后这个维度会被压缩掉比如形状是(4,5)的数组max(axis0)会得到长度为5的向量因为第0个维度被压掉了。条件筛选里np.where是真正的神器本质上等价于向量化的三目运算data np.array([1, 5, 8, 3, 9]) result np.where(data 5, 1, 0) # 大于5的变成1否则变成0这在数据分析里做标签构造、阈值分割的时候用得极多。还可以配合np.any和np.all做批量判断比如想快速检查某个数组里是否包含NaN直接np.isnan(arr).any()一行搞定。2.4 矩阵运算与行列式计算很多人用Numpy做科学计算最关心的是矩阵乘法、转置、求逆和行列式。矩阵乘法在Numpy里有两种等价写法A np.array([[1, 2], [3, 4]]) B np.array([[5, 6], [7, 8]]) C np.dot(A, B) # 传统写法 D A B # 更简洁的写法都是矩阵乘法注意*在Numpy里是逐元素乘法不是矩阵乘法。这是最容易搞混的地方A * B的结果是每个对应位置相乘而A B才是线性代数里的矩阵乘法。做科研计算的时候搞混这两个直接结果就错了。线性代数运算集中在numpy.linalg子模块里print(np.linalg.det(A)) # 行列式 print(np.linalg.inv(A)) # 逆矩阵 transposed A.T # 转置如果你在刷面试题时被要求不用Numpy实现行列式计算那基本就是递归展开或者高斯消元法手写纯粹是为了练算法思维。正式工程里完全没必要自己造轮子——Numpy底层调用的是优化过的LAPACK库几万阶的矩阵也能快速算完准确性远不是自己写的三重循环能比的。我对Numpy函数的使用频率排序大致是这样arange、array、reshape、where、sum、mean、std、、np.linalg这几个占到了日常80%以上的使用量剩下的都是按需查文档即可。3. Pandas常用函数数据读取、类型转换与处理3.1 数据从哪里来read_csv与read_sqlPandas的核心是Series一维带标签数组和DataFrame二维表格。大多数实战场景不是手动创建DataFrame而是从文件或数据库读取。最常用的是read_csvimport pandas as pd df pd.read_csv(sales.csv, encodingutf-8)有两点必须提醒第一文件路径含中文或空格时偶尔会遇到编码问题一般指定encodingutf-8或gbk能解决第二很多从业务系统导出的CSV其实不是utf-8编码不指定encoding会直接抛UnicodeDecodeError改成encodinggbk基本就好了。如果数据在MySQL里Pandas连接数据库也很方便。先安装驱动库再搭配read_sqlpip install pymysqlimport pymysql conn pymysql.connect(hostlocalhost, userroot, password123456, databaseshop, charsetutf8mb4) sql SELECT order_id, user_id, amount FROM orders WHERE create_date 2024-01-01 df pd.read_sql(sql, conn) conn.close()这一套组合拳能让MySQL取数分析变得非常高效尤其适合把数据在数据库和DataFrame之间来回倒腾的场景。配合上分组聚合很多以前要写复杂嵌套SQL的逻辑改成先用read_sql拉出来再用Pandas处理反而更灵活、更容易调试。3.2 看一眼数据info、describe和head拿到DataFrame的第一件事是什么先看结构和基本统计。我固定的三连调用df.head() # 前5行快速预览 df.info() # 列名、非空计数、数据类型 df.describe() # 数值列的均值、标准差、分位数df.info()会告诉你每一列有多少非空值很快能发现哪一列缺失严重。df.describe()默认只统计数值列如果加参数includeall可以加入分类列。数据量特别大的时候head()还可以传更大的数字比如head(20)多看一些样本再动手清洗能减少很多意料之外的坑。有一个新手很容易忽视的细节df.shape拿到的是(行数, 列数)元组写代码时要注意顺序是行在前不是列在前。这个顺序和很多人的直觉相反尤其在和Matplotlib画图、reshape矩阵的时候行和列搞反会得到莫名其妙的报错。3.3 数据类型转换说三遍都不多Pandas里最烦人、报错最多的就是数据类型问题。CSV读进来的数值列经常被识别成object字符串或者日期变成了纯字符串这时候就涉及类型转换。我最常用的三个函数第一astype适用于明确的数值转换df[price] df[price].astype(float)如果某列存在3,200这种带逗号的字符串直接astype(float)会报错。先要清洗把逗号、货币符号去掉再用astype。第二pd.to_numeric处理含脏数据的数值列更安全df[price] pd.to_numeric(df[price], errorscoerce)errorscoerce的意思是转换失败的位置变成NaN而不是抛异常。这个参数在数据清洗阶段极其有用可以先完成转换再用dropna或fillna处理那些没转成功的脏值。整套流程比逐个排查异常值快得多。第三pd.to_datetime处理日期列df[order_date] pd.to_datetime(df[order_date], format%Y-%m-%d)format参数要按真实数据写清楚比如CSV里如果是2024/01/05 14:30:00就得写成format%Y/%m/%d %H:%M:%S。不传format也能自动推断但日期格式一多推断容易出错尤其遇到01/02/2024这种月和日分不清的数据显式指定格式是最稳的做法。日期转完之后威力就出来了可以直接提取年份、月份、星期df[year] df[order_date].dt.year df[month] df[order_date].dt.month df[weekday] df[order_date].dt.dayofweek # 0周一这一整套类型转换流程几乎每个数据清洗项目里都会用到可以说掌握了astype、to_numeric、to_datetime这三个就已经解决了“pandas数据类型转换”这个高频搜索词背后80%的日常问题。3.4 手动创建DataFrame与Series虽然大部分数据来自文件但偶尔也需要手工构造小型DataFrame做测试或组装中间结果。最直接的方式是用字典df pd.DataFrame({ name: [Tom, Jerry, Spike], age: [20, 18, 30], score: [88.5, 92.0, 75.5] })Series相当于DataFrame的一列单独操作时也常用s pd.Series([10, 20, 30], index[a, b, c])有一点经验字典key会自动作为列名而字典插入顺序在Python 3.7之后可以保证所以列顺序就是字典里的key顺序不用担心乱序。Series的index相当于每行的标签很多操作的本质都是按index对齐理解这一点会对后续loc、groupby等操作的理解帮助很大。4. Pandas进阶操作筛选、分组、合并与缺失值处理4.1 按条件筛选loc和iloc的区别Pandas数据筛选绕不开loc和iloc。loc按标签列名、索引名取iloc按位置取。这个区别看起来简单但实际写代码时错用的情况不少。# loc按标签筛选行和列 df.loc[df[age] 18, [name, score]] # iloc按位置筛选 df.iloc[0:2, 0:2]第一行代码的意思是筛选age大于18的行只取name和score两列。这种写法在业务数据分析里几乎每天都要用逻辑上等价于SQL里的SELECT name, score FROM df WHERE age 18读起来非常有直觉感。除了locquery方法可读性更强df.query(age 18 and score 90)query的表达式是字符串实现效率未必比loc高但胜在表达清晰、可以整段写成配置文件适合用在线上的自动化脚本里。对于多人协作的团队这种方式还能减少代码里的魔法数字让筛选条件更直观。4.2 排序、去重与重命名排序函数sort_values太常用了价格排序、时间排序都靠它df_sorted df.sort_values(price, ascendingFalse)注意sort_values默认返回新对象原来的df顺序不变。如果想把改动用回原对象可以用inplaceTrue参数不过现在Pandas官方已经在逐步计划弃用一些inplace参数我更推荐直接用返回值接收这样代码语义更清晰也不容易出现原地操作没生效的困惑。去重用drop_duplicates按某一列或多列去重df_clean df.drop_duplicates(subset[user_id])如果想先看一下哪些行重复可以用df.duplicated()返回布尔序列快速定位重复记录。重命名列则用renamedf df.rename(columns{user_id: uid, amount: amt})字典映射列名非常灵活配合批量加前缀后缀也容易扩展。如果想把所有列名统一改成小写甚至可以直接df.columns [col.lower() for col in df.columns]。4.3 分组聚合groupby、agg与pivot_table分组聚合是Pandas最强大的功能之一。比如要按品类统计销售额总和、平均价格和订单数result df.groupby(category)[amount].agg([sum, mean, count])agg函数里可以传列表同时算多个指标比先算一个再算一个效率高得多代码也清爽。还可以用命名聚合让多列、多指标各归其位result df.groupby(category).agg( total_amount(amount, sum), avg_price(price, mean), max_amount(amount, max) )这种方式让结果列名直接清晰可读非常推荐。如果要做类似Excel数据透视表的效果用pivot_tablepivot pd.pivot_table(df, valuesamount, indexcategory, columnsregion, aggfuncsum, fill_value0)一个透视表函数基本把分类汇总、交叉统计的活全包了。它比groupby更直观的地方在于行和列可以同时展开两个维度非常适合输出报表。fill_value0会把没有数据的格子填成0避免透视结果里出现一堆NaN让阅读者心累。4.4 数据合并merge、concat与join多表合并是实战绕不开的环节。merge等价于SQL里的join可以指定连接键和连接方式pd.merge(df_orders, df_users, onuser_id, howleft)how参数可选left、right、inner、outer语义跟SQL完全相同。left join保左表全部记录匹配不到的右表字段为NaN。理解了这一条再用Python做多表操作时基本不需要硬啃SQL语句甚至能把两张结构完全不同的表快速拼接成想要的宽表。concat则用于纵向或横向拼接df_all pd.concat([df_q1, df_q2, df_q3, df_q4], axis0) # 行拼接axis0表示上下拼增加行axis1表示左右拼增加列。拼接时如果列名不完全一致pandas会用NaN补齐缺失列不会直接报错但这有时会掩盖问题务必拼接后检查一下columns是否一致。4.5 缺失值处理dropna、fillna与isnull数据清洗里缺失值的处理策略直接影响分析质量。第一步永远先看缺失分布print(df.isnull().sum())isnull对每个元素判断是否为NaN再用sum按列统计一眼看出每一列缺多少。如果列很多还可以再加一步筛选出有缺失的列missing df.isnull().sum() print(missing[missing 0])只打印有缺失的列信息量更大控制台也不至于刷屏。处理方式无非删除和填充。删除用dropnadf_sub df.dropna(subset[score]) # 缺失score的行删除 df_all df.dropna() # 有任何缺失的行全部删除填充用fillnadf[score] df[score].fillna(0) # 用固定值填 df[score] df[score].fillna(df[score].mean()) # 用均值填 df[score] df[score].fillna(methodffill) # 用上一个有效值向前填充fillna(methodffill)处理时间序列的场景特别有用比如传感器数据间歇性丢失用前一条记录填空是常见策略。注意当整列全是NaN时df.mean()也会返回NaNfillna(df.mean())并不会真正填充成功。这种情况要先检查数据源看为什么会造成整列全空。4.6 apply大法把函数往每一行、列上怼apply可能是Pandas里最灵活的函数。它可以对每一行或每一列应用任意自定义函数df[full_price] df.apply(lambda row: row[price] * (1 - row[discount]), axis1)axis1表示把函数应用在每个行上这里的row是一个Series对象axis0表示按列应用。apply适用范围很广凡是需要跨列计算的都能交给它比如根据年龄和城市组合生成新的用户分群标签。如果只是对单列做批量计算更推荐直接用向量化运算或mapdf[discount] df[discount].map(lambda x: x * 0.9)如果是把整个DataFrame的每个元素都做转换用applymapdf_str df.applymap(str)不过apply的性能并不算好尤其对几十万行的大DataFrame在循环里滥用apply会明显拖慢程序。处理较大的数据时优先考虑向量化写法或者先转成Numpy数组计算完再转回DataFrame速度提升往往非常显著。5. 常见报错与排查实录5.1 读取CSV中文乱码Pandas读取中文CSV文件最常见的报错是UnicodeDecodeError。一开始我以为加encodingutf-8就行结果遇到从业务系统手动下载的CSV是GBK编码又报错。后来学乖了遇到乱码优先试两个参数df pd.read_csv(file.csv, encodinggbk) df pd.read_csv(file.csv, encodingutf-8)万一两个都不行还可以用encodinggb18030它比gbk覆盖的汉字范围更广。不建议用errorsignore强行跳过无法解码的字符那样会有丢数据风险。最稳的方法是用记事本或VS Code打开文件后看右下角编码提示确认后填对应参数。5.2 SettingWithCopyWarning总在吓人使用Pandas时经常看到一行黄色警告A value is trying to be set on a copy of a slice from a DataFrame.这个Warning的意思是你正在对一个DataFrame的切片视图做赋值这个修改可能不会同步到原DataFrame。最常见的引发场景是df_sub df[df[age] 18] df_sub[group] adult # 出现警告不要看它只是警告就忽略因为结果可能不可预期有时候赋值能生效有时候悄无声息地丢掉了。安全的写法是先复制一份副本彻底切断与原对象的关联df_sub df[df[age] 18].copy() df_sub[group] adult用.copy()是最简单也最保险的解决方案没有之一。5.3 数字列转float总失败脏数据里最烦人的就是数字串带着千分位逗号、百分号、货币符号。直接astype(float)必报错。我的标准处理流程三步走df[amount] df[amount].astype(str).str.replace(,, ).str.replace(¥, ) df[amount] pd.to_numeric(df[amount], errorscoerce)先全部转成字符串用str.replace去掉逗号和货币符号再用to_numeric转数值errorscoerce保证转不了的变成NaN。最后一步根据业务决定是dropna还是fillna(0)。这个方法基本可以处理90%的金额字段脏数据场景。5.4 大文件内存暴涨读取几千万行的数据Pandas非常吃内存。最有效的三个手段第一读取时只取需要的列df pd.read_csv(huge.csv, usecols[id, amount, date])第二限定数值列类型用dtype参数df pd.read_csv(huge.csv, dtype{id: int32, amount: float32})第三分块读取chunks pd.read_csv(huge.csv, chunksize100000) for chunk in chunks: # 每块处理100000行 pass这三条组合用能显著减少内存占用。我处理过一次3亿行的日志表就是因为没用分块直接把16G内存的机器拖到卡死后来老老实实加上chunksize才跑完。别看这些参数平时不起眼关键时刻能救命。5.5 版本兼容问题复发Numpy和Pandas的版本匹配问题在旧项目里尤其容易复发。比如某个现场环境里pandas是1.5.3numpy却是2.ximport时直接异常。处理步骤很简单查项目依赖确定预期版本再执行对应安装命令。如果项目里既有科学计算又有机器学习框架建议所有依赖都写在requirements里并且定期用pip freeze requirements.txt同步锁定版本。6. 一张速查表和几个让我少走弯路的习惯6.1 常用函数速查表这么多函数记不住也没关系我把最高频的几个整理成一张速查表贴在手边非常实用场景推荐函数一句话说明创建数组np.arange()指定步长生成等差数列生成等间隔序列np.linspace()指定数量均分区间矩阵乘法A B用写法最简洁条件赋值np.where()类似向量化if-else读取CSVpd.read_csv()注意encoding参数数值类型转换pd.to_numeric(errorscoerce)脏数据安全转换日期解析pd.to_datetime(format...)显式指定格式更稳分组统计df.groupby().agg()一次算多个指标条件筛选df.loc[df[col] x]按标签筛选多表连接pd.merge(howleft)语义与SQL一致缺失计数df.isnull().sum()按列统计缺失数填缺失df.fillna()固定值/均值/前向填充删除重复df.drop_duplicates()按列子集去重自定义计算df.apply(axis1)逐行/逐列应用函数6.2 每次处理数据都先复制除非确定要原地操作且不会被其他变量引用否则我在修改DataFrame前都会习惯性地.copy()一份。这个习惯看似多了一步但能避免掉大量令人抓狂的引用连环坑。Pandas和Numpy一样很多操作返回的是视图而不是副本尤其是切片和布尔索引后的子集。6.3 别在循环里逐行处理数据Pandas的循环逐行处理性能极差。我曾经因为习惯性用for加iterrows方式处理几十万行数据跑了好几分钟。后来改成groupby加agg加apply的组合写法之后速度提升了十倍以上。凡是能用向量化操作、能用内建聚合函数解决的问题都不要自己写Python循环去遍历每一行。6.4 养成先看dtype再动手的好习惯每次拿到一个新DataFrame最浪费时间的事就是用错数据类型导致各种报错。我的固定检查项是先看df.info()确认各列类型再看isnull().sum()确认缺失分布最后再决定转换策略。顺序固定、有条不紊排查问题的时间能省一大半。用Numpy和Pandas这些年最大的体会就是常用函数其实就二三十个真正难的是理解背后的数据结构和索引机制。只要搞清楚了视图、轴方向、数据类型这三件事绝大多数报错你扫一眼就能定位。希望这篇整理能让你少踩几个我已经踩过的坑遇到问题的时候也能更快找到头绪。
返回列表