做数据分析和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如果某个项目锁定了pandas==1.5.3,但环境里numpy被升级到2.x,直接import时也会报错。这时候不要盲目升级pandas,先看项目依赖文件里锁的版本,再根据报错信息回退或升级对应的库:
pip install numpy==1.26.4 pandas==2.1.1注意:遇到Numpy和Pandas版本冲突时,一定不要手动去site-packages目录里替换动态链接库文件,大概率越改越乱。优先用pip在项目层面解决,或者用conda建独立环境。
个人建议在项目初期就把requirements.txt里的版本写死,避免"昨天还能跑,今天同事clone后跑不了"的尴尬。对于涉及大量科学计算的项目,如果条件允许,用conda隔离环境管理科学计算依赖会更省心。
2. Numpy常用函数:从数组创建到矩阵运算
2.1 高频数组创建方式
Numpy最核心的对象是ndarray(N维数组),它不是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), size=5, replace=False)replace=False表示不重复抽样,这在做训练集和验证集切分、随机抽检时是常规操作,比起自己写循环要简洁得多。
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(axis=0)) # 每列最大值 -> [4, 5, 6] print(arr.argmax()) # 最大值的索引注意axis参数的含义:axis=0通常是沿着行方向(跨行),结果是逐列的统计值;axis=1是沿着列方向,结果是逐行的统计值。这一条特别容易记反,我自己的记忆方式是:axis等于几,就表示计算后这个维度会被"压缩"掉,比如形状是(4,5)的数组,max(axis=0)会得到长度为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_sql
Pandas的核心是Series(一维带标签数组)和DataFrame(二维表格)。大多数实战场景不是手动创建DataFrame,而是从文件或数据库读取。最常用的是read_csv:
import pandas as pd df = pd.read_csv('sales.csv', encoding='utf-8')有两点必须提醒:第一,文件路径含中文或空格时,偶尔会遇到编码问题,一般指定encoding='utf-8'或gbk能解决;第二,很多从业务系统导出的CSV其实不是utf-8编码,不指定encoding会直接抛UnicodeDecodeError,改成encoding='gbk'基本就好了。
如果数据在MySQL里,Pandas连接数据库也很方便。先安装驱动库再搭配read_sql:
pip install pymysqlimport pymysql conn = pymysql.connect(host='localhost', user='root', password='123456', database='shop', charset='utf8mb4') 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()默认只统计数值列,如果加参数include='all'可以加入分类列。数据量特别大的时候,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'], errors='coerce')errors='coerce'的意思是转换失败的位置变成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,读起来非常有直觉感。
除了loc,query方法可读性更强:
df.query('age > 18 and score >= 90')query的表达式是字符串,实现效率未必比loc高,但胜在表达清晰、可以整段写成配置文件,适合用在线上的自动化脚本里。对于多人协作的团队,这种方式还能减少代码里的魔法数字,让筛选条件更直观。
4.2 排序、去重与重命名
排序函数sort_values太常用了,价格排序、时间排序都靠它:
df_sorted = df.sort_values('price', ascending=False)注意sort_values默认返回新对象,原来的df顺序不变。如果想把改动用回原对象,可以用inplace=True参数,不过现在Pandas官方已经在逐步计划弃用一些inplace参数,我更推荐直接用返回值接收,这样代码语义更清晰,也不容易出现"原地操作没生效"的困惑。
去重用drop_duplicates,按某一列或多列去重:
df_clean = df.drop_duplicates(subset=['user_id'])如果想先看一下哪些行重复,可以用df.duplicated()返回布尔序列,快速定位重复记录。重命名列则用rename:
df = 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_table:
pivot = pd.pivot_table(df, values='amount', index='category', columns='region', aggfunc='sum', fill_value=0)一个透视表函数基本把"分类汇总、交叉统计"的活全包了。它比groupby更直观的地方在于行和列可以同时展开两个维度,非常适合输出报表。fill_value=0会把没有数据的格子填成0,避免透视结果里出现一堆NaN让阅读者心累。
4.4 数据合并:merge、concat与join
多表合并是实战绕不开的环节。merge等价于SQL里的join,可以指定连接键和连接方式:
pd.merge(df_orders, df_users, on='user_id', how='left')how参数可选left、right、inner、outer,语义跟SQL完全相同。left join保左表全部记录,匹配不到的右表字段为NaN。理解了这一条,再用Python做多表操作时基本不需要硬啃SQL语句,甚至能把两张结构完全不同的表快速拼接成想要的宽表。
concat则用于纵向或横向拼接:
df_all = pd.concat([df_q1, df_q2, df_q3, df_q4], axis=0) # 行拼接axis=0表示上下拼(增加行),axis=1表示左右拼(增加列)。拼接时如果列名不完全一致,pandas会用NaN补齐缺失列,不会直接报错,但这有时会掩盖问题,务必拼接后检查一下columns是否一致。
4.5 缺失值处理:dropna、fillna与isnull
数据清洗里,缺失值的处理策略直接影响分析质量。第一步永远先看缺失分布:
print(df.isnull().sum())isnull对每个元素判断是否为NaN,再用sum按列统计,一眼看出每一列缺多少。如果列很多,还可以再加一步筛选出有缺失的列:
missing = df.isnull().sum() print(missing[missing > 0])只打印有缺失的列,信息量更大,控制台也不至于刷屏。
处理方式无非删除和填充。删除用dropna:
df_sub = df.dropna(subset=['score']) # 缺失score的行删除 df_all = df.dropna() # 有任何缺失的行全部删除填充用fillna:
df['score'] = df['score'].fillna(0) # 用固定值填 df['score'] = df['score'].fillna(df['score'].mean()) # 用均值填 df['score'] = df['score'].fillna(method='ffill') # 用上一个有效值向前填充fillna(method='ffill')处理时间序列的场景特别有用,比如传感器数据间歇性丢失,用前一条记录填空是常见策略。
注意:当整列全是NaN时,df.mean()也会返回NaN,fillna(df.mean())并不会真正填充成功。这种情况要先检查数据源,看为什么会造成整列全空。
4.6 apply大法:把函数往每一行、列上怼
apply可能是Pandas里最灵活的函数。它可以对每一行或每一列应用任意自定义函数:
df['full_price'] = df.apply(lambda row: row['price'] * (1 - row['discount']), axis=1)axis=1表示把函数应用在每个行上,这里的row是一个Series对象;axis=0表示按列应用。apply适用范围很广,凡是需要跨列计算的都能交给它,比如根据年龄和城市组合生成新的用户分群标签。
如果只是对单列做批量计算,更推荐直接用向量化运算或map:
df['discount'] = df['discount'].map(lambda x: x * 0.9)如果是把整个DataFrame的每个元素都做转换,用applymap:
df_str = df.applymap(str)不过apply的性能并不算好,尤其对几十万行的大DataFrame,在循环里滥用apply会明显拖慢程序。处理较大的数据时,优先考虑向量化写法,或者先转成Numpy数组计算完再转回DataFrame,速度提升往往非常显著。
5. 常见报错与排查实录
5.1 读取CSV中文乱码
Pandas读取中文CSV文件最常见的报错是UnicodeDecodeError。一开始我以为加encoding='utf-8'就行,结果遇到从业务系统手动下载的CSV是GBK编码,又报错。后来学乖了,遇到乱码优先试两个参数:
df = pd.read_csv('file.csv', encoding='gbk') df = pd.read_csv('file.csv', encoding='utf-8')万一两个都不行,还可以用encoding='gb18030',它比gbk覆盖的汉字范围更广。不建议用errors='ignore'强行跳过无法解码的字符,那样会有丢数据风险。最稳的方法是用记事本或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'], errors='coerce')先全部转成字符串,用str.replace去掉逗号和货币符号,再用to_numeric转数值,errors='coerce'保证转不了的变成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', chunksize=100000) for chunk in chunks: # 每块处理100000行 pass这三条组合用,能显著减少内存占用。我处理过一次3亿行的日志表,就是因为没用分块直接把16G内存的机器拖到卡死,后来老老实实加上chunksize才跑完。别看这些参数平时不起眼,关键时刻能救命。
5.5 版本兼容问题复发
Numpy和Pandas的版本匹配问题在旧项目里尤其容易复发。比如某个现场环境里pandas是1.5.3,numpy却是2.x,import时直接异常。处理步骤很简单:查项目依赖,确定预期版本,再执行对应安装命令。如果项目里既有科学计算又有机器学习框架,建议所有依赖都写在requirements里,并且定期用pip freeze > requirements.txt同步锁定版本。
6. 一张速查表和几个让我少走弯路的习惯
6.1 常用函数速查表
这么多函数记不住也没关系,我把最高频的几个整理成一张速查表,贴在手边非常实用:
| 场景 | 推荐函数 | 一句话说明 |
|---|---|---|
| 创建数组 | np.arange() | 指定步长生成等差数列 |
| 生成等间隔序列 | np.linspace() | 指定数量均分区间 |
| 矩阵乘法 | A @ B | 用@写法最简洁 |
| 条件赋值 | np.where() | 类似向量化if-else |
| 读取CSV | pd.read_csv() | 注意encoding参数 |
| 数值类型转换 | pd.to_numeric(errors='coerce') | 脏数据安全转换 |
| 日期解析 | pd.to_datetime(format=...) | 显式指定格式更稳 |
| 分组统计 | df.groupby().agg() | 一次算多个指标 |
| 条件筛选 | df.loc[df['col'] > x] | 按标签筛选 |
| 多表连接 | pd.merge(how='left') | 语义与SQL一致 |
| 缺失计数 | df.isnull().sum() | 按列统计缺失数 |
| 填缺失 | df.fillna() | 固定值/均值/前向填充 |
| 删除重复 | df.drop_duplicates() | 按列子集去重 |
| 自定义计算 | df.apply(axis=1) | 逐行/逐列应用函数 |
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这些年,最大的体会就是:常用函数其实就二三十个,真正难的是理解背后的数据结构和索引机制。只要搞清楚了视图、轴方向、数据类型这三件事,绝大多数报错你扫一眼就能定位。希望这篇整理能让你少踩几个我已经踩过的坑,遇到问题的时候也能更快找到头绪。