数据分析神器Pandas,从入门到精通,这一篇就够了!
还在用Excel处理几十万行数据?还在为数据清洗焦头烂额?Pandas带你飞!
如果你和数据打交道,无论是数据分析师、数据科学家,还是做后端开发的工程师,你一定听说过Pandas。这个基于Python的开源库,已经成为结构化数据处理的事实标准。它到底有多强?简单说,它能把Excel、SQL、Numpy三者的优点集于一身——既有电子表格的直观,又有数据库的灵活,还有Numpy的运算速度。
今天,我就带你从头到尾把Pandas的核心吃透。不管你是刚入门的新手,还是想查漏补缺的老手,这篇文章都能给你实实在在的帮助。我们不讲虚的,直接上代码,边敲边理解。
一、初识Pandas:它到底解决了什么问题?
Pandas这个名字来源于Panel Data(面板数据)和Python data analysis(Python数据分析)的组合。它最核心的两个数据结构是Series(一维带标签数组)和DataFrame(二维表格)。你可以把Series想象成带行标签的Excel列,把DataFrame想象成整个Excel工作表。
为什么需要Pandas?举个例子:你用Python自带的列表和字典处理一份销售数据,想按日期分组求和,可能要写十几行循环。而在Pandas里,一行groupby加sum就搞定了。更关键的是,它处理十万行数据的速度比Excel快几个数量级,而且不会卡死。
二、Series:一维数据的超级增强版
1. 创建Series的多种姿势
Series可以看作是一个带索引的一维数组。我们先从最简单的列表创建开始:
import pandas as pd
# 不指定索引,自动生成0,1,2,3...
s = pd.Series([4, 7, -5, 3])
print(s)
输出左边是索引,右边是值。默认索引从0开始,这和Python列表很像,但Series的索引可以不是数字,这就有意思了。
# 自定义索引,让数据更有意义
s = pd.Series([4, 7, -5, 3], index=["a", "b", "c", "d"], name="my_series")
print(s)
这里我们给每个数据点起了一个字母标签,还给了整个Series一个名字my_series。当你打印它时,会看到名称显示在dtype上方,这在大项目中特别有用,一眼就知道这个Series代表什么。
创建Series还有一种更Pythonic的方式——直接用字典。字典的键会成为索引,值成为数据:
dic = {"a": 4, "b": 7, "c": -5, "d": 3}
s = pd.Series(dic)
print(s)
这种方式特别适合你已经有了一个字典,想快速转换成带标签的一维数据。你甚至可以在创建时只挑选字典中的部分键:
s1 = pd.Series(dic, index=["a", "c"], name="aacc")
print(s1) # 只保留a和c两行
2. 核心属性:学会“看穿”你的数据
创建完Series后,最常用的操作就是查看它的各种属性。这些属性不改变数据,但能帮你快速了解数据的结构。
arrs = pd.Series([11, 22, 33, 44, 55], name="atguigu", index=["a","b","c","d","e"])
print(arrs.index) # Index(['a', 'b', 'c', 'd', 'e'], dtype='object')
print(arrs.values) # [11 22 33 44 55]
print(arrs.ndim) # 1,因为Series是一维的
print(arrs.shape) # (5,),告诉你一共有5个元素
print(arrs.size) # 5
print(arrs.dtype) # int64
print(arrs.name) # atguigu
索引和值是Series的两大命脉。index返回的是索引对象,你可以遍历它;values返回的是底层的Numpy数组,这意味着你可以享受Numpy的高效计算。
3. 访问元素的四种方式:loc、iloc、at、iat
Pandas提供了两套索引系统:基于标签的和基于位置的。初学时容易混淆,我们逐一理清。
- loc[]:按标签(索引名)访问,包含结束位置。
- iloc[]:按位置(整数下标)访问,不包含结束位置(类似Python切片)。
- at[]:loc的快速版,只用于访问单个元素。
- iat[]:iloc的快速版,只用于访问单个元素。
# loc按标签:支持切片,且包含终点
print(arrs.loc["c"]) # 33,单个元素
print(arrs.loc["c":"d"]) # c 33, d 44(注意"d"被包含)
# iloc按位置:从0开始,切片不包含终点
print(arrs.iloc[0]) # 11
print(arrs.iloc[0:3]) # 索引0,1,2,不包含3
# at和iat:单元素高效访问
print(arrs.at["a"]) # 11
print(arrs.iat[3]) # 44
为什么需要两套?因为实际数据中,索引往往是有意义的字符串(如日期、产品ID),用loc可以直接按名称取数。而iloc在循环或算法中更常用,因为它基于绝对位置。记住一个口诀:loc带名字,iloc带数字。
4. 常用方法:数据清洗和统计一把抓
Series提供了大量方法,我们挑最实用的边写边讲。先构造一个带缺失值的Series:
import numpy as np
arrs = pd.Series([11, 22, np.nan, None, 44, 22], index=['a','b','c','d','e','f'])
注意np.nan和None在Pandas中都会被当作缺失值处理。这一点很重要,因为现实数据几乎都有缺失。
查看数据边界:
print(arrs.head(3)) # 前3行,默认5行
print(arrs.tail(2)) # 后2行
判断元素是否在集合中:
print(arrs.isin([11])) # 检查哪些元素等于11,返回布尔Series
缺失值检测:
print(arrs.isna()) # 哪些位置是缺失值?True表示缺失
统计运算(自动忽略缺失值):
print(arrs.sum()) # 11+22+44+22 = 99,NaN和None被跳过
print(arrs.mean()) # 99/4 = 24.75
print(arrs.min()) # 11
print(arrs.max()) # 44
print(arrs.var()) # 方差
print(arrs.std()) # 标准差
这里有一个细节:sum()、mean()等方法默认跳过缺失值,所以分母不是6而是4。如果你想要包含缺失值(结果会变成NaN),可以传入skipna=False。
中位数与众数:
中位数是排序后中间的数。我们的数据有效值是[11,22,44,22],排序后[11,22,22,44],中间两个是22和22,平均还是22。
print(arrs.median()) # 22.0
print(arrs.mode()) # 众数:22出现两次,11和44各一次,所以返回22
分位数(quantile): 这是一个稍微进阶但非常强大的功能。假设你想知道“前25%的数据是多少”,就用0.25分位数。
# 计算0.25分位数,使用midpoint插值
print(arrs.quantile(0.25, interpolation="midpoint"))
为什么要插值?因为有效数据有4个,位置计算为(4-1)*0.25 = 0.75,介于第0个(11)和第1个(22)之间。midpoint取两者平均,得16.5。你可以试试interpolation="lower",它会取11。
去重相关:
print(arrs.drop_duplicates()) # 返回去重后的Series,22只保留第一个
print(arrs.unique()) # 返回去重后的数组(Numpy数组),[11,22,nan,44]
print(arrs.nunique()) # 去重后的元素个数(不含缺失值),这里3个
注意unique()会把NaN也作为一个独特值返回,而nunique()默认不统计NaN。
随机采样:
print(arrs.sample()) # 随机抽一个元素,每次运行可能不同
排序:
print(arrs.sort_index()) # 按索引字母顺序 a,b,c,d,e,f
print(arrs.sort_values()) # 按值大小排序,缺失值放最后
替换:
print(arrs.replace(22, "haha")) # 把所有22替换成字符串"haha"
转换为DataFrame:
print(arrs.to_frame()) # 把一列变成一张表
判断相等:
arr1 = pd.Series([1,2,3])
arr2 = pd.Series([1,2,3])
print(arr1.equals(arr2)) # True,注意要用equals,不能用==
相关系数与协方差: 这两个是统计学的核心概念。相关系数衡量两个变量线性关系的强弱和方向,取值范围[-1,1]。
arr1 = pd.Series([1,2,3])
arr3 = pd.Series([3,2,1])
arr5 = pd.Series([1,-1,1,-1])
arr6 = pd.Series([1,1,-1,-1])
print(arr1.corr(arr3)) # -1,完全负相关
print(arr5.corr(arr6)) # 0,无线性相关
print(arr1.cov(arr3)) # 协方差为负,说明变化方向相反
协方差的符号告诉你是同向还是反向,但绝对值大小受量纲影响,所以实际分析中更常用相关系数。
绘制直方图: 需要先安装matplotlib。直方图能直观展示数据分布。
arr7 = pd.Series([3,2,1,1,1,2,2])
arr7.hist(bins=3) # 分成3个区间
遍历: 用items()方法可以同时拿到索引和值。
for i, v in arr7.items():
print(i, v)
5. 布尔索引:像用Excel筛选一样优雅
布尔索引是Pandas最常用的筛选方式。它的核心思想是:先得到一个布尔值的Series(True/False),然后用这个布尔Series去索引原数据,True对应的行被保留。
s = pd.Series({'a': -1.2, 'b': 3.5, 'c': 6.8, 'd': 2.9})
booleans = s > s.mean() # 计算每个元素是否大于平均值
print(booleans)
print(s[booleans]) # 只保留大于平均值的元素
这段代码先计算平均值 (-1.2+3.5+6.8+2.9)/4 = 3.0,然后比较每个元素是否大于3.0,得到[False, True, True, False],最后用这个布尔数组筛选出b和c。整个过程不需要写循环,非常直观。
6. Series的运算:自动对齐是亮点
与标量运算: 很简单,标量会与每个元素进行计算。
s = pd.Series({'a': -1.2, 'b': 3.5, 'c': 6.8, 'd': 2.9})
print(s * 10) # 每个值乘以10
Series与Series运算: 这里体现Pandas的强大之处——基于索引自动对齐。即使两个Series的索引顺序不同甚至不完全重合,Pandas也能正确匹配。
s1 = pd.Series([1, 1, 1, 1]) # 索引 0,1,2,3
s2 = pd.Series([2, 2, 2, 2], index=[1, 2, 3, 4]) # 索引 1,2,3,4
print(s1 + s2)
结果中,索引0只在s1中有,s2中没有,所以和为NaN;索引4只在s2中有,结果也是NaN;索引1,2,3两边都有,正常相加得3.0。这个特性在处理时间序列或不完整数据时极其有用。
三、DataFrame:二维表格的终极形态
如果说Series是一列数据,那DataFrame就是多列数据共享同一个行索引。你可以把DataFrame看作由多个Series组成的字典,或者看作带行列索引的Excel表格。
1. 创建DataFrame
最常用的方式是传入字典,字典的键成为列名,值(列表或数组)成为该列的数据。
df = pd.DataFrame({
"id": [101, 102, 103],
"name": ["张三", "李四", "王五"],
"age": [20, 30, 40]
})
print(df)
默认行索引是从0开始的整数。你可以通过columns参数调整列的顺序,通过index参数指定行索引:
df = pd.DataFrame(
data={"age": [20, 30, 40], "name": ["张三", "李四", "王五"]},
columns=["name", "age"], # 先显示name列,再显示age列
index=[101, 102, 103] # 行索引用员工编号
)
print(df)
2. 常用属性:快速了解DataFrame全貌
df = pd.DataFrame(data={"id": [101,102,103], "name": ["张三","李四","王五"], "age": [20,30,40]}, index=["aa","bb","cc"])
print(df.index) # Index(['aa', 'bb', 'cc'], dtype='object')
print(df.columns) # Index(['id', 'name', 'age'], dtype='object')
print(df.values) # 返回二维Numpy数组
print(df.ndim) # 2
print(df.shape) # (3, 3)
print(df.size) # 9
print(df.dtypes) # id int64, name object, age int64
print(df.T) # 转置,行变列,列变行
df.T是转置操作,当你需要交换行和列时非常方便。比如原始数据每行是一个时间点,每列是一个指标,转置后每行变成一个指标,每列是一个时间点。
3. 访问数据:loc、iloc、at、iat的二维版本
DataFrame的索引比Series多一个维度,所以loc和iloc的用法是df.loc[行, 列]。
# 按标签
print(df.loc["aa":"cc"]) # 所有行(aa到cc),所有列
print(df.loc["aa", "name"]) # 单个元素:aa行的name列
# 按位置
print(df.iloc[0:1]) # 第一行(注意0:1只取一行)
print(df.iloc[0:3, 2]) # 所有行的第2列(索引从0开始,所以2是第三列)
# at和iat:快速单元素访问
print(df.at["aa", "name"]) # 张三
print(df.iat[0, 1]) # 张三(第0行第1列)
这里有一个常见坑:df.loc["aa":"cc"]包含终点cc,而df.iloc[0:1]不包含终点1。习惯就好。
4. 常用方法:与Series类似,但针对二维
我们构造一个稍微复杂的DataFrame来演示:
df = pd.DataFrame(
data={'id': [101,102,103,104,105,106,101],
'name': ["张三","李四","王五","赵六","冯七","周八","张三"],
'age': [10,20,30,40,None,60,10]},
index=['aa','bb','cc','dd','ee','ff','aa']
)
查看数据:
print(df.head()) # 前5行
print(df.tail(3)) # 后3行
print(df.info()) # 查看每列的非空数量、数据类型、内存占用
info()是数据探索的第一步,它能快速告诉你哪些列有缺失值。
统计方法: 对单列操作和Series一样,对多列操作时默认按列(axis=0)计算。
print(df["age"].sum()) # 10+20+30+40+60+10 = 170,None跳过
print(df["age"].mean()) # 170/6 ≈ 28.33
print(df["age"].median()) # 排序后[10,10,20,30,40,60],中位数(20+30)/2=25
print(df["age"].mode()) # 10出现两次,是众数
print(df.describe()) # 一次性输出count,mean,std,min,25%,50%,75%,max
describe()是神器,对于数值列自动计算八项统计量,对于字符串列则给出count、unique等。
缺失值判断:
print(df.isna()) # 每个元素是否为缺失值,age列的ee位置为True
重复值处理:
print(df.duplicated(subset="age")) # 判断age列是否有重复行(返回布尔Series)
print(df.drop_duplicates()) # 删除重复行(基于所有列)
注意subset参数可以指定只检查某些列是否重复。不指定时,所有列完全相同才算重复。
排序:
print(df.sort_index()) # 按行索引排序(aa会出现两次,顺序不变)
print(df.sort_values(by="age")) # 按age列升序,缺失值放最后
print(df.nlargest(2, columns="age")) # age最大的两行
print(df.nsmallest(1, columns="age"))# age最小的一行
替换:
print(df.replace(20, "haha")) # 把age中的20替换成"haha"
累计计算与差分: 这在金融分析中很常用。
df3 = pd.DataFrame({"A": [2,5,3,7,4], "B": [1,6,2,8,3]})
print(df3.cumsum()) # 每列的累计和:[2,7,10,17,21] 和 [1,7,9,17,20]
print(df3.cumprod()) # 累计积
print(df3.diff()) # 一阶差分:当前行减上一行,第一行是NaN
5. 布尔索引:筛选行就像写SQL的WHERE
df = pd.DataFrame(
data={"age": [20,30,40,10], "name": ["张三","李四","王五","赵六"]},
columns=["name","age"],
index=[101,104,103,102]
)
print(df[df["age"] > 25]) # 选出年龄大于25的行
这段代码中,df["age"] > 25生成了一个布尔Series,然后作为行索引传入df[],只保留True的行。你可以组合多个条件:df[(df["age"] > 25) & (df["name"] != "李四")]。
6. DataFrame运算:同样自动对齐
与标量运算: 每个元素都参与。
print(df * 2) # 数值翻倍,字符串会重复(如"张三"变成"张三张三")
DataFrame与DataFrame运算: 基于行索引和列索引同时对齐。
df1 = pd.DataFrame(data={"age":[10,20,30,40], "name":["张三","李四","王五","赵六"]}, index=[101,102,103,104])
df2 = pd.DataFrame(data={"age":[10,20,30,40], "name":["张三","李四","王五","田七"]}, index=[102,103,104,105])
print(df1 + df2)
结果中,只有同时存在相同行索引和相同列名的位置才会相加。比如name列是字符串,相加会变成字符串拼接("李四"+"张三"?其实会报错,因为字符串相加会拼接,但这里示例输出是李四张三这种奇怪结果)。实际数值列age在索引102、103、104上对齐相加,其他位置为NaN。
7. 更改DataFrame结构:增、删、改、查
设置和重置行索引:
df = pd.DataFrame({"age": [20,30,40,10], "name": ["张三","李四","王五","赵六"], "id": [101,102,103,104]})
df.set_index("id", inplace=True) # 把id列变成行索引
print(df)
df.reset_index(inplace=True) # 把行索引变回普通列,并生成默认整数索引
set_index和reset_index是数据重塑的常用操作。注意inplace=True表示直接修改原DataFrame,否则会返回新对象。
修改索引名和列名:
df = pd.DataFrame({"age": [20,30,40,10], "name": ["张三","李四","王五","赵六"], "id": [101,102,103,104]})
df.set_index("id", inplace=True)
df.rename(index={101:"一", 102:"二", 103:"三", 104:"四"}, columns={"age":"年龄","name":"姓名"}, inplace=True)
更直接的方式是给df.index和df.columns重新赋值:
df.index = ["I", "II", "III", "IV"]
df.columns = ["年龄", "名称"]
添加新列: 直接赋值即可,新列会自动添加到最右边。
df["phone"] = ["13333333333", "14444444444", "15555555555", "16666666666"]
删除列: 两种方式,推荐用drop。
df.drop("phone", axis=1, inplace=True) # axis=1表示列,axis=0表示行
# 或者
del df["phone"]
插入列到指定位置: insert(loc, column, value)。
df.insert(loc=0, column="phone", value=df["age"] * 100) # 在第一列位置插入phone列
8. 数据的导入与导出:打通各种数据源
Pandas支持几十种文件格式的读写。最常用的是CSV、Excel、Pickle。
导出:
df.to_csv("data/df.csv") # 默认带行索引和列名
df.to_csv("data/df_noindex.csv", index=False) # 不保存行索引
df.to_csv("data/df.tsv", sep="\t") # 制表符分隔
df.to_excel("data/df.xlsx") # 需要openpyxl
df.to_pickle("data/df.pkl") # Python专用,速度快
df.to_json("data/df.json")
导入:
df_csv = pd.read_csv("data/df.csv", index_col=0) # 指定第0列为行索引
df_tsv = pd.read_csv("data/df.tsv", sep="\t")
df_excel = pd.read_excel("data/df.xlsx", index_col="id")
df_pkl = pd.read_pickle("data/df.pkl")
注意read_csv的index_col参数:如果你保存时没有用index=False,那么CSV文件的第一列就是原来的行索引,读取时应该用index_col=0把它设回索引。
四、日期数据处理:时间序列分析的第一步
日期和时间在数据分析中经常出现。Pandas的日期处理能力非常强大。
1. 用to_datetime转换字符串为日期类型
df = pd.DataFrame({
"gmv": [100,200,300,400],
"trade_date": ["2025-01-06", "2023-10-31", "2023-12-31", "2023-01-05"]
})
df["ymd"] = pd.to_datetime(df["trade_date"])
print(df)
print(df.dtypes) # ymd列显示为datetime64[ns]
转换后,你就可以对这个列进行各种日期运算了,比如计算两个日期的差值、提取年月日等。
2. 使用dt访问器提取日期属性
Series的dt访问器提供了丰富的日期属性。
df["year"] = df["ymd"].dt.year
df["month"] = df["ymd"].dt.month
df["day"] = df["ymd"].dt.day
df["weekday"] = df["ymd"].dt.day_name() # 星期几,如Monday
df["quarter"] = df["ymd"].dt.quarter # 季度1-4
df["is_month_end"] = df["ymd"].dt.is_month_end # 是否月底
df["is_year_end"] = df["ymd"].dt.is_year_end # 是否年底
3. 使用to_period转换统计周期
当你需要按周、月、季度或年做分组统计时,to_period非常有用。
df["year_period"] = df["ymd"].dt.to_period("Y") # 年度周期,如2023
df["month_period"] = df["ymd"].dt.to_period("M") # 月度周期,如2023-01
df["quarter_period"] = df["ymd"].dt.to_period("Q")# 季度周期,如2023Q1
df["week_period"] = df["ymd"].dt.to_period("W") # 周周期
之后你就可以用groupby(df["month_period"])来按月汇总数据了。
五、总结
Pandas之所以成为数据科学领域的标配,是因为它解决了数据处理中的三大痛点:易用性(像Excel一样直观)、高性能(底层Numpy向量化运算)、灵活性(自动对齐、缺失值处理、时间序列)。
通过本文,你应该已经掌握了:
- Series和DataFrame的核心概念与区别。
- 创建、索引、筛选、修改数据的方法。
- 常用的统计运算和缺失值处理。
- 数据的导入导出技巧。
- 日期数据的快速转换和周期提取。
学习Pandas最好的方式不是死记硬背,而是遇到实际问题时——比如清洗一份日志、分析销售数据——立刻打开Jupyter Notebook,边写代码边查文档。你会发现,那些曾经需要几十行Python循环才能完成的任务,现在只需要一两行Pandas代码。
最后送给你一句话:Pandas不是用来写循环的,而是用来消灭循环的。 当你发现自己还在用for i in range(len(df))时,停下来想一想,Pandas大概率已经为你准备好了向量化的方法。
希望这篇文章能成为你Pandas学习路上的一个好伙伴。如果你在实际应用中遇到任何问题,欢迎随时回来查阅。数据之路,道阻且长,行则将至。
更多推荐
所有评论(0)