资讯详情

资讯详情

NumPy与Pandas高频函数实战:从环境搭建到数据清洗聚合

数据分析这行混久了你会发现绕不开两个Python库NumPy和Pandas。一个管多维数组计算一个管结构化数据处理搭配起来几乎能把数据清洗、特征加工、筛选聚合这些环节都包圆了。这篇不打算把官方文档搬运过来而是想从实战项目里筛一筛真正高频出现的NumPy和Pandas常用函数顺带把安装环境、类型转换、版本冲突这几个每天有人踩的坑也一并交代清楚。适合读这篇的一是刚转行数据的新手想搞明白数组、表、行列、索引这些概念到底怎么落地二是写了两三年脚本但一直靠搜索引擎拼凑代码的同学值得把常用函数系统过一遍。看完你至少能搞定三件事环境能装起来、表能洗干净、数据能合并聚合。至于那些还在问“numpy安装都不会”的伙伴这篇文章就是给你们准备的。1. 环境搭建从零安装 numpy 和 pandas1.1 常规安装方式与虚拟环境先保证Python环境是3.8以后的版本新版Pandas对Python版本有硬性要求老版本Python经常在安装阶段就失败。命令行的安装方法很直接pip install numpy pandas如果电脑上同时装了Python2和Python3记得带上python3 -m前缀python3 -m pip install --user numpy pandas这里我想多说一句虚拟环境。很多人图省事把所有包都装到全局环境里一开始好像很方便等你有两个项目、用了不同版本的numpy就知道什么叫“一顿操作猛如虎一看版本全冲突”。虚拟环境相当于给每个项目单独围了个院子Python版本、第三方包版本互不干扰。我建议从第一天就养成习惯python -m venv data_env source data_env/bin/activate # Windows 是 data_env\Scripts\activate pip install numpy pandas好处是以后换电脑、换同事一个pip freeze requirements.txt就能把依赖完整带走版本问题能少一大半。1.2 PyCharm 里怎么装 pandas 包常见问题“pycharm怎么安装pandas包”其实不难打开File - Settings - Project - Python Interpreter点右侧加号搜索pandas点Install Package完事。但这里有个隐蔽的坑PyCharm安装的包是装在当前解释器对应的环境里不是系统全局。如果你新建了一个项目却发现pandas还是不能用多半是解释器变了。所以打开项目第一反应应该是看右下角的解释器路径确认虚拟环境选对了再补装缺的包。不要不管三七二十一先在命令行pip install一遍那样可能装进了另一个环境两边的包永远对不上。1.3 清华源解决“找不到版本”的报错安装报错里最经典的一条就是ERROR: Could not find a version that satisfies the requirement pandas (from versions: none) ERROR: No matching distribution found for pandas这个报错看起来像Python包不存在其实绝大多数情况不是你打错名字而是PyPI官方源访问不稳定或者网络环境根本连不上去。解决办法是换国内镜像源清华源是比较稳的选择pip install pandas -i https://pypi.tuna.tsinghua.edu.cn/simple老是重复加这串参数也挺烦可以把镜像源写进pip全局配置pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple配置完以后平时直接pip install pandas就行。如果换了镜像还是报“找不到版本”就要检查另外三个原因Python版本是不是太旧、包名有没有拼错、镜像站有没有同步完。手机端安装pandas比如Pydroid3、Termux也是同一套思路先把pip源换成清华源再装就能少折腾半天Termux还需要先pkg install python rust binutils补编译环境否则pandas装到一半就报错这个坑我当初折腾了一整晚。1.4 版本不匹配numpy 和 pandas 的兼容性装完以后启动时如果看到类似“numpy版本不匹配”的报错多半是numpy和pandas版本差距过大。pandas内部不少模块是用C语言编译的编译时绑定了一套numpy接口你单方面升级numpy不升pandas接口对不上就崩了。最省事的解法是让它们一起升级pip install -U numpy pandas如果想用conda统一管理也可以conda install numpy pandas稳妥起见我一般会在虚拟环境里固定一组可用的版本组合比如pandas2.0.3配合numpy1.24.3兼容性实测很稳。版本问题看着小实际影响很大。我在两个项目之间来回切环境时因为A项目用了新版numpyB项目还停留在旧版pandas直接加载不了排查了半天才发现是环境混了。到这里环境就算打好了。下面开始看数组和表的核心函数。2. NumPy 常用函数从建数组到数值计算2.1 创建数组的几类姿势平时最先用到的肯定是np.array从列表或者嵌套列表直接变成数组import numpy as np a np.array([1, 2, 3]) b np.array([[1, 2], [3, 4]])但写业务代码的时候更多是直接生成置零、置一的数组比如初始化权重矩阵、构造空白存储位置zeros np.zeros((3, 4)) # 3行4列全0 ones np.ones((2, 3)) # 2行3列全1 eye np.eye(4) # 4x4单位矩阵要模拟数据np.arange和np.linspace出现频率很高。arange相当于列表里的range生成的是等差数列linspace则在一个区间里均匀取点比如模拟时间轴x np.linspace(0, 1, 5) # 结果是 0, 0.25, 0.5, 0.75, 1.0再就是np.random系列。我最常用的是default_rng固定随机种子以后测试数据可复现rng np.random.default_rng(42) data rng.normal(0, 1, (100, 5))固定随机种子是新手很容易忽略的点。不固定种子的话每次跑出来的数据都不一样程序的结果跟着变来变去你根本没法判断到底是代码改出了bug还是随机数变了。2.2 数组的数学与统计函数NumPy最爽的地方在于“对整个数组做运算”而不是写循环。求和、均值、中位数、标准差、最大最小值都有一行函数arr np.array([[1, 2], [3, 4]]) np.sum(arr) # 总和 10 np.mean(arr) # 均值 2.5 np.median(arr) # 中位数 2.5 np.max(arr) # 最大值 4 np.std(arr) # 标准差这里最容易搞不清楚的是axis参数。我的记忆法很简单axis0表示第0维会被消掉也就是跨行计算最后得到的是每列的值axis1表示第1维消掉跨列计算得到每行的值。arr.sum(axis0) # 每列和: array([4, 6]) arr.sum(axis1) # 每行和: array([3, 7])理解了这个以后看任何带axis参数的函数都会轻松很多包括后面要讲的Pandas。2.3 矩阵运算与行列式计算做线性回归或者求解方程组的时候矩阵乘法必不可少。np.dot和都是矩阵乘法但注意*是逐元素相乘两者完全不同A np.array([[1, 2], [3, 4]]) B np.array([[5, 6], [7, 8]]) A B # 矩阵乘法 A * B # 对应位置相乘的结果行列式、逆矩阵、特征值这一类都在np.linalg里np.linalg.det(A) # 行列式 np.linalg.inv(A) # 逆矩阵 np.linalg.eig(A) # 特征值、特征向量有一类热搜是“python行列式计算不使用numpy”。如果是学习目的我可以理解你想自己实现一个。2x2行列式的公式很简单def det2x2(m): return m[0][0] * m[1][1] - m[0][1] * m[1][0]但一旦到3x3往上就得用代数余子式递归展开代码瞬间变长数值稳定性还差。我的看法是在公司项目里别自讨苦吃环境允许就用np.linalg.det这本来就是NumPy该干的活手写没有意义。2.4 变形、拼接与拆分数组经常需要改形状。reshape最常用但千万别忘了一件事元素总数必须对得上。一个6个元素的数组不能改成(2, 2)因为2x2只能放4个元素直接报错。想改成(2, 3)才行a np.arange(6) # [0, 1, 2, 3, 4, 5] a.reshape((2, 3))展平一个多维数组用ravel或flatten。两者的区别很实际ravel返回的可能只是原数组的视图你改了某个元素原数组也会跟着变flatten则一定是拷贝改了不影响原数组。我在项目里如果对“别误改原数据”有要求就用flatten省得后续排查半天。拼接数据时三个函数要分清np.concatenate([a, b], axis0) # 按指定轴拼接 np.vstack([a, b]) # 垂直堆叠 np.hstack([a, b]) # 水平堆叠split则按分割点拆分数组训练集和测试集划分的时候偶尔会用上。要注意拆分时总长度必须能被分割比例整除否则也要调整参数。3. Pandas 常用函数表数据处理的标准动作3.1 从 Series 和 DataFrame 说起Pandas的两个核心结构是Series和DataFrame。Series可以理解成带索引的一列数据DataFrame是一张带行索引和列名的表。日常创建DataFrame最常见的写法是用字典import pandas as pd df pd.DataFrame({ name: [Alice, Bob, Cathy], age: [25, 30, 35], city: [北京, 上海, 广州] })也可以是嵌套列表、numpy数组或者直接读文件。拿到一张表以后head、tail、sample三兄弟就派上用场了df.head() # 前5行 df.tail() # 后5行 df.sample(3) # 随机抽3行sample是我特别喜欢用的数据量大的时候随机看几行比从头看沉甸甸的数据更有“体感”。比如有几十万行head只能告诉你开头几行长什么样sample能让你对整个分布有直觉。3.2 表格体检info 和 describe拿到一张新表第一件事不是急着分析而是先“体检”。df.info()能显示每列的非空数量、数据类型df.describe()生成数值列的分布统计包括均值、标准差、分位数等。df.info() df.describe()这一步看着简单却能快速暴露好多问题某个列缺失值特别多、日期列被读成了object、金额列里混着奇怪的文本。我接过一份销售明细一开始觉得一切正常结果info()一看日期列是object金额列还有空字符串难怪后面聚合结果全不对。没有体检你就会在后续的分析里反复被错误结果折磨。3.3 数据类型转换astype、to_numeric、to_datetime类型转换是Pandas里最容易踩坑的一类操作。最直接的写法是astypedf[age] df[age].astype(int)但问题来了字符串转数字的时候如果单元格里混了一个“N/A”“unknown”astype(float)会直接报错。这时候改用pd.to_numeric并且加上errorscoerce非法值会被转成NaN而不是中断脚本df[price] pd.to_numeric(df[price], errorscoerce)日期列用pd.to_datetime它对格式的识别能力比astype强很多常规的2024-01-01、2024/01/01都能自动解析df[date] pd.to_datetime(df[date])我的习惯是三步走先info()看类型再单独抽一列试转换转完立刻用df.dtypes确认。转换类型这事一定要在数据处理的前期做完不然等到后面遇到各种“字符串减法”“日期排序错乱”再返工就晚了。3.4 缺失值与重复值处理缺失值先找出来。isna()/notna()返回布尔表统计每列缺失个数最方便的是df.isna().sum()处理方式无非删和填。dropna()删除含有缺失值的行或列fillna()用指定值填充df.dropna(subset[price]) df[price].fillna(df[price].mean())填充有个常用技巧时间序列里用ffill把前一天的值延续下来比如股票停牌日的价格沿用前一日收盘价比硬算均值更符合业务逻辑。重复值用duplicated和drop_duplicatesdf.duplicated().sum() df.drop_duplicates(subset[name, date], keepfirst)这里有个坑drop_duplicates默认是针对所有列完全重复才删除如果你想按某几列去重必须指定subset参数否则你以为去重了其实完全没用。3.5 筛选、排序与列操作筛选是Pandas的日常最高频操作。loc按行标签iloc按位置两者截然不同df.loc[0] # 第0行标签为0 df.iloc[2:5] # 第2到第4行位置 df.loc[df[age] 30] # 布尔条件筛选多条件筛选时记得每个条件都要加括号因为Python的和|优先级很容易导致“条件被拆开”的诡异问题df[(df[age] 30) (df[city] 上海)]如果条件太长query写法更清爽也更接近SQL的观感df.query(age 30 and city 上海)排序用sort_valuesdf.sort_values(age, ascendingFalse) df.sort_values([city, age], ascending[True, False])列名、删列、加列也都很常见df.rename(columns{name: 姓名}) df.drop(columnscity) df[score] df[age] * 2多练习几次这些函数完全能成为肌肉记忆用的时候完全不需要查文档。4. 数据合并、分组与聚合4.1 merge 和 concat 到底怎么选merge和concat都是合并数据但场景完全不同。很多人上来就纠结用哪个我的判断标准很简单如果是上下拼、左右拼用concat如果是像查字典一样按某个关键列去关联用merge。df1 pd.DataFrame({a: [1, 2]}) df2 pd.DataFrame({a: [3, 4]}) pd.concat([df1, df2]) # 默认 axis0纵向堆叠 pd.concat([df1, df2], axis1) # 横向拼接merge则更像SQL里的JOIN。很多搜索词是“pandas mysql常用函数”本质上大家是发现Pandas的合并和MySQL的join长得像。确实如此on指定关联列how决定连接方式写起来非常顺orders pd.DataFrame({ order_id: [1, 2, 3], customer_id: [A, B, A], amount: [100, 200, 300] }) customers pd.DataFrame({ customer_id: [A, B], name: [Alice, Bob] }) merged orders.merge(customers, oncustomer_id, howleft)howleft代表保留orders的全部行匹配不到的用户信息补NaN。inner只保留能匹配上的outer则两边都留着。一开始我建议只记left和inner因为业务里90%的情况就这两种。4.2 groupby 和 agg 完成分组聚合做报表、做统计groupby完全是绕不开的。基本用法是按某个列分组对另一列求和result df.groupby(city)[amount].sum()想一次性算均值、求和、计数用aggresult df.groupby(city)[amount].agg([sum, mean, count])更复杂一点不同列用不同聚合方式result df.groupby(city).agg({ amount: [sum, mean], age: max })透视表pivot_table本质也是分组聚合的一种变体特别适合“城市日期”这种二维交叉统计pd.pivot_table(df, valuesamount, indexcity, columnsdate, aggfuncsum)这里我有个习惯跑groupby之前先用df.groupby([city]).ngroups确认一下分组数量再动手做聚合。分组数量跟自己预期差太多说明数据清洗有问题早发现早止损。4.3 一个完整的小案例把上面的函数串起来跑通一个常见的数据处理流程读文件、清洗、关联、分组、导出。假设有两张表一张是订单表sales.csv一张是用户表user.csv。sales pd.read_csv(sales.csv) users pd.read_csv(user.csv) # 清洗金额转数值去掉非法行 sales[amount] pd.to_numeric(sales[amount], errorscoerce) sales sales.dropna(subset[amount]) sales[date] pd.to_datetime(sales[date]) # 去重按订单号去重 sales sales.drop_duplicates(subset[order_id]) # 关联补上用户城市信息 df sales.merge(users, onuser_id, howleft) # 分组聚合统计每个城市的总销售额、平均金额、订单数 result df.groupby(city).agg( total_sales(amount, sum), avg_sales(amount, mean), order_cnt(order_id, count) ).reset_index() # 导出 result.to_csv(result.csv, indexFalse)这段代码基本覆盖了日常工作里80%的数据分析动作。跑通它说明你已经把Pandas的核心用法掌握了一大半。5. 常见错误与性能优化5.1 必背的常见报错与解法Pandas报错最多的不是语法错误而是使用误区。第一个是“SettingWithCopyWarning”。这个警告通常出现在你筛选出子集再对子集赋值时。它不阻止运行但说明你可能改错了数据改的只是临时副本。我现在写代码只要打算修改筛选结果就会加.copy()sub df[df[age] 30].copy() # 明确复制避免改到视图第二个是类型转换炸掉。astype(int)碰到NaN直接报错。所以有缺失值时先dropna再用to_numeric处理永远比先转类型再填缺失值顺。第三个是merge时提示索引不对。两表合并前如果还带着默认的行号索引结果经常乱掉。最简单的办法是在合并前加reset_index()把索引拉平。5.2 性能优化不要用 iterrows 硬扛数据分析有个共识尽量别在Pandas里写Python循环。iterrows的确能遍历每一行但速度慢得离谱。我之前用iterrows处理10万行数据跑了一分多钟改成向量化写法几乎是瞬间出结果。向量化的意思是尽量做列与列之间的批量运算而不是一行一行来。比如打折计算df[discount_price] np.where(df[price] 100, df[price] * 0.9, df[price])没有循环np.where一次性处理整列。如果确实需要逐行做复杂逻辑优先考虑apply真要用循环就用itertuples速度比iterrows快不少。数据量特别大时还可以边读边处理read_csv的chunksize参数能把文件分块读进内存for chunk in pd.read_csv(big.csv, chunksize50000): process(chunk)5.3 内存不爆的小技巧数据一大了内存就是瓶颈。我的三招是第一按需读取。read_csv里加usecols参数只读需要的列别一股脑全load进来。一个200列的宽表你只需要其中5列为什么不只读5列第二优化dtype。数值列用float32代替float64整数用int32代替int6410万行数据体积能减少不少。如果精度不是要求极高这招非常划算。第三把只含少量枚举值的字符串列转成category类型比如城市名、产品类别。这样做既省内存groupby统计时速度还会更快df[city] df[city].astype(category)我用这个办法处理过几百万行数据内存占用直接砍掉一半分组聚合速度反而提升了。写数据处理代码这几年我最深的体会是能用向量化就不用循环能提前定好类型就不要反复转换能先看info就先看info。NumPy和Pandas说到底都是工具常用函数背得滚瓜烂熟固然重要但更重要的是在动手前想清楚“你想让数据变成什么样”。这篇文章里的函数你不需要一次性全记住先跑一遍案例用到哪个查哪个用不了几次就能形成自己的肌肉记忆。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →