一、Pandas 解决什么问题
如果说 NumPy 更像数值计算的底层能力,那么 Pandas 更像更贴近业务语义的表格处理工具。像 Excel 报表、CSV 统计结果、数据库查询结果、接口返回的列表字典数据,都很适合交给它处理;相比原生 Python 里“列表套字典”的写法,Pandas 在筛选、排序、分组、缺失值处理、统计分析以及文件读写上都更顺手,而且和 NumPy、Matplotlib 的衔接也很自然。
二、两个核心对象:Series 和 DataFrame
Series
Series 可以理解为“带索引的一维数据”。
import pandas as pd
s = pd.Series([88, 92, 79], index=["Alice", "Bob", "Cindy"])
print(s)
print(s["Bob"])
它很像“有标签的一列数据”。
DataFrame
DataFrame 是 Pandas 最核心的数据结构,可以理解为“带行列标签的二维表格”。
import pandas as pd
df = pd.DataFrame({
"name": ["Alice", "Bob", "Cindy"],
"age": [23, 25, 22],
"score": [88, 92, 79],
})
print(df)
print(df.columns)
print(df.shape)
这里可以先记住两个最常见的标签概念:
index:行标签,用来标识每一行columns:列标签,用来标识每一列,也就是表头
前面这个示例里,"name"、"age"、"score" 本身就是列标签,所以 df.columns 会得到这些列名。
如果想手动指定列标签,也可以显式传入 columns:
import pandas as pd
scores = [
[88, 91, 95],
[76, 85, 82],
]
df = pd.DataFrame(
scores,
index=["张三", "李四"],
columns=["语文", "英语", "数学"],
)
这时:
index=["张三", "李四"]表示两行数据分别是谁columns=["语文", "英语", "数学"]表示每一列分别是哪一门科目
大多数数据分析工作,最终都围绕 DataFrame 展开。
三、创建与读取数据
除了直接写字典,Pandas 更常见的入口是读文件。
import pandas as pd
df = pd.read_csv("sales.csv")
print(df.head())
常见读写函数包括:
pd.read_csv()pd.read_excel()df.to_csv()df.to_excel()
初学阶段,至少要先掌握 CSV 的读取与导出。
四、查看数据的常用方法
import pandas as pd
df = pd.read_csv("sales.csv")
# 默认查看前 5 行,快速确认字段名和数据大致长什么样
print(df.head())
# 默认查看后 5 行,确认尾部数据是否完整
print(df.tail())
# 查看每一列的数据类型、非空数量和内存占用
df.info()
# 查看数值列的统计摘要,如均值、最大值、最小值
print(df.describe())
#这是接手一个陌生数据集时最常见的第一步。
# 1.导包
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('TkAgg')
# 2.读取数据
train = pd.read_csv('data/titanic_train.csv')
test = pd.read_csv('data/titanic_test.csv')
# 3.了解数据
print(train.shape) # (892, 12)
train.info() # 每列不为空的个数
print(train.count())
print('==================================================')
# 4.缺失值查看
# 4.1 查看是否是缺失值,结果是True或者False
print(train.isna())
print('----------------------------------')
# 4.2 获取每列有缺失值的个数(True:1,Fasle:0)
print(train.isna().sum())
print('----------------------------------')
# 4.3 获取每列缺失值的比例
print(train.isna().sum() / len(train) * 100)
print('==================================================')
# 5.了解生存和死亡的人数,以及可视化展示
# 查看Survived各个值的个数
result = train['Survived'].value_counts()
print(result, type(result))
# 可视化展示
result.plot(kind='bar') # 柱状图
plt.grid()
# 展示
plt.show()五、选择列、筛选行
import pandas as pd
df = pd.DataFrame({
"name": ["Alice", "Bob", "Cindy", "David"],
"city": ["Beijing", "Shanghai", "Beijing", "Shenzhen"],
"score": [88, 92, 79, 95],
})
print(df["name"])
print(df[["name", "score"]])
high_score = df[df["score"] >= 90]
print(high_score)
打印结果:
0 Alice
1 Bob
2 Cindy
3 David
Name: name, dtype: object
name score
0 Alice 88
1 Bob 92
2 Cindy 79
3 David 95
name city score
1 Bob Shanghai 92
3 David Shenzhen 95
最常见的两类操作就是:
- 选哪些列
- 保留哪些行
这也是数据清洗和分析的起点。
六、loc 和 iloc
Pandas 中两个特别重要的定位方式是:
loc:按标签取值iloc:按位置取值
import pandas as pd
df = pd.DataFrame({
"name": ["Alice", "Bob", "Cindy"],
"score": [88, 92, 79],
}, index=["a", "b", "c"])
print(df.loc["b", "score"])
# 按标签取值:取行index赋值的标签为 b、列标签为 score 的单元格,结果是 92
print(df.iloc[1, 1])
# 按位置取值:取第 2 行、第 2 列的单元格,结果也是 92
两者经常会被混淆,建议在练习时刻意区分“标签”和“位置”。
七、缺失值处理
真实数据几乎一定会遇到缺失值。开始处理之前,先要知道 Pandas 里常见的“空值”长什么样。
import numpy as np
import pandas as pd
print(None, type(None)) # Python 原生空值
print(np.nan, type(np.nan)) # NumPy 中常见的空值
print(pd.NA, type(pd.NA)) # Pandas 里的缺失值标记
判断空值时,通常优先使用 isna() / notna(),而不是直接写 ==。
这是因为不同类型的缺失值在比较行为上并不完全一致,而 isna() / notna() 能统一处理。
import numpy as np
import pandas as pd
print(pd.isna("null")) # False
print(pd.isna("")) # False
print(pd.isna(None)) # True
print(pd.isna(np.nan)) # True
print(pd.isna(pd.NA)) # True
print("-" * 36)
print(pd.notna("null")) # True
print(pd.notna("")) # True
print(pd.notna(None)) # False
print(pd.notna(np.nan)) # False
print(pd.notna(pd.NA)) # False
#处理表格 (keep_default_na=False,表示不保留默认的缺失值标记)
# 2.读取文件
# 方式1: 默认keep_default_na是True
# 代表自动用NaN填充所有缺失值(空,NULL,null,None,NA,nan)
df = pd.read_csv('data/survey_visited.csv')
print(df)
print('-----------------------------------------------')
# 方式2: 修改keep_default_na为False
# 代表所有缺失值(空,NULL,null,None,NA,nan)位置不填充NaN
df = pd.read_csv('data/survey_visited.csv', keep_default_na=False)
print(df)
print('-----------------------------------------------')
# 方式3: 修改keep_default_na为False,使用na_values指定要用NaN填充的值
# 代表所有缺失值(空,NULL,null,None,NA,nan)位置不填充NaN,na_values=[None,np.nan,pd.NA]
df = pd.read_csv('data/survey_visited.csv', keep_default_na=False, na_values=[None, np.nan, pd.NA])
print(df)
其中:
isna()和isnull()基本等价,常用来判断是否为空值notna()和notnull()基本等价,常用来判断是否不是空值- 在 Pandas 实战里,更推荐记
isna()/notna()这一组现代写法
接下来,再看 DataFrame 里怎么处理缺失值:
import pandas as pd
df = pd.DataFrame({
"name": ["Alice", "Bob", "Cindy"],
"score": [88, None, 79],
"city": ["Beijing", "Shanghai", None],
})
print(df.isna())
print(df.dropna())
print(df.fillna({"score": 0, "city": "Unknown"}))
打印结果:
name score city
0 False False False
1 False True False
2 False False True
name score city
0 Alice 88.0 Beijing
name score city
0 Alice 88.0 Beijing
1 Bob 0.0 Shanghai
2 Cindy 79.0 Unknown
常见处理方式有:
- 先用
isna()找出缺失位置 - 直接丢弃缺失行
dropna() - 用固定值填充
fillna() - 用均值、中位数、前值后值等策略填充
到底选哪一种,取决于业务场景,而不是单纯的语法问题。
fillna() 的常见填充策略
当数据不能直接删除时,更常见的思路是先考虑怎么填充缺失值。
import warnings
import pandas as pd
# 演示时可以临时忽略警告,正式项目里不要默认全局关闭
warnings.filterwarnings("ignore")
df = pd.DataFrame({
"name": ["Tom", "Lucy", "Anna", "Mike"],
"age": [18, None, 20, None],
"city": ["Beijing", None, "Shanghai", "Shenzhen"],
})
# 1. 直接填充 0
new_df1 = df.fillna(0)
print(new_df1)
# 2. 用 age 列平均值填充
new_df2 = df.fillna({"age": df["age"].mean()})
print(new_df2)
# 3. 用前一个非缺失值向后填充
new_df3 = df.ffill()
print(new_df3)
# 4. 用后一个非缺失值向前填充
new_df4 = df.bfill()
print(new_df4)
这几种方式各有适用场景:
fillna(0)简单直接,但不一定合理fillna({"age": df["age"].mean()})适合数值列做统计型填充ffill()适合时间顺序数据或记录延续场景bfill()适合“后一个值更可信”的补齐场景
为什么不建议看到空值就直接填 0?
- 如果
age这类字段本来不可能是 0,强行填 0 会拉低平均值 - 如果后面还要做聚合、建模或可视化,结果可能被这种“伪数据”带偏
另外补充一点:
- 你在很多旧教程里会看到
df.fillna(method="ffill")和df.fillna(method="bfill") - 现在更推荐直接写
df.ffill()和df.bfill(),语义更直观,也更符合新版 Pandas 的写法
所以,填充策略通常可以这样选:
- 能删且影响不大,就优先删
- 不能轻易删时,再考虑
fillna() - 数值型字段优先考虑均值、中位数等统计值
- 有明显顺序关系的数据,再考虑前向或后向填充
dropna() 和 drop() 的常见删除方式
很多初学者会把“删除缺失值”和“删除某一列”混在一起。可以先这样区分:
dropna():根据是否为空值来删行或删列drop():按标签直接删除指定行或指定列
下面这个例子和真实表格分析更接近:
import pandas as pd
train = pd.DataFrame({
"Name": ["Alice", "Bob", "Cindy", "David"],
"Age": [22, None, 19, None],
"Cabin": [None, None, "C85", None],
"Fare": [7.25, 71.83, 8.05, 13.0],
})
# 1. 默认 how="any":只要这一行里有任意缺失值,就删除该行
new_train1 = train.dropna()
print(new_train1)
# 2. how="all":只有整行全是缺失值时才删除
new_train2 = train.dropna(how="all")
print(new_train2)
# 3. subset=["Age"]:只检查 Age 这一列,只要 Age 缺失就删除该行
new_train3 = train.dropna(subset=["Age"])
print(new_train3)
# 4. inplace=True:直接在原对象上修改
temp = train.copy()
temp.dropna(subset=["Age"], inplace=True)
print(temp)
# 5. drop(..., axis=1):按列名直接删除某一列
new_train4 = train.drop("Cabin", axis=1)
print(new_train4)
这里最常见的几个点是:
dropna()默认等价于dropna(how="any")how="all"适合删除“整行都没值”的脏数据subset=["Age"]适合“只关心关键列是否为空”的场景inplace=True表示直接修改原对象;不写时,通常会返回一个新结果drop("Cabin", axis=1)表示按列删除,axis=1代表列方向
在像 Titanic 这种数据里,Cabin 往往缺失很多,这时直接删除这一列,有时比删除大量样本行更合理。
所以缺失值处理时,常见决策顺序往往是:
- 先判断缺失发生在哪些列、比例有多高
- 关键列少量缺失时,用
dropna(subset=...)删除对应行 - 非关键列缺失过多时,考虑
drop(..., axis=1)删除整列 - 如果数据不能轻易删,再考虑
fillna()做填充
删除没有绝对标准,重点不是“会不会语法”,而是“删掉后会不会影响分析结论”。
八、排序、去重与新增列
import pandas as pd
df = pd.DataFrame({
"name": ["Alice", "Bob", "Alice"],
"score": [88, 92, 88],
})
df["passed"] = df["score"] >= 60
print(df.sort_values("score", ascending=False))
print(df.drop_duplicates())
打印结果:
name score passed
1 Bob 92 True
0 Alice 88 True
2 Alice 88 True
name score passed
0 Alice 88 True
1 Bob 92 True
这些操作在数据清洗里非常高频:
sort_values()排序drop_duplicates()去重df["new_col"] = ...新增派生列
在 Pandas 中,我们经常将这些操作连在一起写,这种写法叫做链式操作(Chaining):
# 链式操作示例:先按 score 排序,然后去除 name 重复的行
result = df.sort_values("score", ascending=False).drop_duplicates(subset=["name"])
print(result)
九、分组统计:groupby
groupby 是 Pandas 最核心的分析能力之一。
import pandas as pd
df = pd.DataFrame({
"city": ["Beijing", "Beijing", "Shanghai", "Shanghai"],
"sales": [120, 150, 200, 180],
})
# 按 city 分组后,对每个城市的 sales 求平均值
result = df.groupby("city")["sales"].mean()
print(result)
"""
打印:
city
Beijing 135
Shanghai 190
Name: sales, dtype: int64
"""
它的直觉可以理解为:
- 先按某个字段分组
- 再对每组做聚合
常见聚合方法包括: sum() mean() count() max() min()
如果你开始接触真实数据集,很快就会遇到“按多个条件分组”“同时统计多个字段”的需求。
import numpy as np
import pandas as pd
# txt 只是一种文件后缀,只要数据是固定分隔格式,read_csv() 也一样能读
df = pd.read_csv("data/gapminder.txt", sep="\t")
print(df.shape)
# 1. 按 year 分组,求每个年份的平均寿命
print(df.groupby("year").lifeExp.mean())
print(df.groupby("year")["lifeExp"].mean())
print(df.groupby("year")[["lifeExp"]].mean())
# 2. 用 agg() 显式指定字段和聚合函数
print(df.groupby("year").agg({"lifeExp": "mean"}))
print(df.groupby("year").agg({"lifeExp": np.mean}))
# 3. 同时按 year 和 continent 分组,对多个字段做不同聚合
print(
df.groupby(["year", "continent"]).agg({
"lifeExp": "mean",
"gdpPercap": "max",
})
)
print(
df.groupby(["year", "continent"]).agg({
"lifeExp": np.mean,
"gdpPercap": np.max,
})
)
这里有几个特别容易混淆的点:
df.groupby("year").lifeExp.mean()和df.groupby("year")["lifeExp"].mean()都是在取单列后做聚合df.groupby("year")[["lifeExp"]].mean()结果仍然是DataFramedf.groupby("year")["lifeExp"].mean()结果通常是Series
也就是说,单中括号和双中括号的区别,在 groupby 后面依然成立:
["lifeExp"]更像“取一列”,结果偏向Series[["lifeExp"]]更像“取一个只含一列的表”,结果仍然是DataFrame
而 agg() 的价值在于,它更适合写成“报表式”的聚合逻辑:
{"lifeExp": "mean"}表示对lifeExp求平均值{"lifeExp": np.mean}表示把 NumPy 函数作为聚合函数传进去{"lifeExp": "mean", "gdpPercap": "max"}表示不同字段可以用不同聚合方式
多字段分组时,可以把它理解成“先按 year 分,再在每个年份里继续按 continent 分”,最后得到的是一个分层索引结果。
这类写法在实际分析里非常常见,例如:
- 按 年份 统计平均寿命
- 按 年份 + 大洲 统计平均寿命和最高人均 GDP
- 按 月份 + 城市 统计订单数、销售额、客单价
如果你有一点 SQL 基础,可以把它和下面这种思路对应起来:
SELECT year, continent, AVG(lifeExp), MAX(gdpPercap)
FROM gapminder
GROUP BY year, continent;
十、透视表:pivot_table
很多报表类需求,本质上都是“按行分组、按列展开、交叉汇总”。这正是透视表擅长的场景。
import pandas as pd
df = pd.DataFrame({
"month": ["Jan", "Jan", "Feb", "Feb", "Feb"],
"city": ["Beijing", "Shanghai", "Beijing", "Shanghai", "Beijing"],
"sales": [120, 150, 130, 160, 140],
})
table = pd.pivot_table(
df,
index="month",
columns="city",
values="sales",
aggfunc="sum",
fill_value=0,
)
print(table)
打印结果:
city Beijing Shanghai
month
Feb 270 160
Jan 120 150
这几个参数最值得先记住:
index:透视表的行分组依据columns:透视表的列分组依据values:要统计的目标值aggfunc:聚合方式,比如sum、mean、countfill_value:把缺失位置补成指定值,避免出现NaN
可以把它理解成“先 groupby,再把结果重新排成更适合看报表的二维结构”。
如果你只是做简单重排,也会看到 pivot()。但初学阶段更推荐优先掌握 pivot_table(),因为它可以直接处理重复组合并做聚合,而 pivot() 遇到重复键时通常会报错。
十一、多表合并:merge 和 concat
真实分析场景里,数据往往不会只放在一张表里。
例如:
- 订单表里有
product_id - 商品表里有
product_id和商品名称 - 不同月份的数据可能分散在多张结构相同的表里
这时就会经常用到:
merge():按共同字段把两张表“横向关联”起来concat():把多张结构相同的表“纵向拼接”起来
import pandas as pd
orders = pd.DataFrame({
"product_id": [101, 102, 101],
"sales": [1200, 800, 1500],
})
products = pd.DataFrame({
"product_id": [101, 102],
"product_name": ["Keyboard", "Mouse"],
})
jan = pd.DataFrame({
"month": ["Jan", "Jan"],
"sales": [1200, 800],
})
feb = pd.DataFrame({
"month": ["Feb", "Feb"],
"sales": [1500, 950],
})
merged = orders.merge(products, on="product_id")
combined = pd.concat([jan, feb], ignore_index=True)
print(merged)
print(combined)
打印结果:
product_id sales product_name
0 101 1200 Keyboard
1 101 1500 Keyboard
2 102 800
month sales
0 Jan 1200
1 Jan 800
2 Feb 1500
3 Feb 950
可以先这样理解:
merge(..., on="字段名")类似数据库里的关联查询concat([...])更像把多份同结构数据接在一起
初学阶段先掌握这两种最常见的合并思路,就已经能覆盖很多报表场景。
十二、一个简单示例:分析销售表
import pandas as pd
df = pd.DataFrame({
"product": ["A", "A", "B", "B"],
"month": ["Jan", "Feb", "Jan", "Feb"],
"sales": [1200, 1500, 800, 950],
})
monthly_total = df.groupby("month")["sales"].sum()
product_mean = df.groupby("product")["sales"].mean()
print("按月总销量:")
print(monthly_total)
print("按产品平均销量:")
print(product_mean)
像这种“分组后做汇总”的需求,在报表分析中非常常见。
十三、Pandas 与 NumPy、Matplotlib 的关系
把三者看成一条数据分析链路上的三个环节:NumPy 提供底层数组与数值计算能力,Pandas 负责表格数据处理,Matplotlib 负责把结果画出来。在很多日常分析里,你最常直接写的是 Pandas + Matplotlib,而 NumPy 往往作为底层基础存在。
三点最小对比:
- 核心对象上,
NumPy主要是ndarray,Pandas主要是Series和DataFrame,Matplotlib常见的是figure、axes - 数据形态上,
NumPy更适合处理同质数值数组,Pandas更适合处理带行列标签的表格数据,Matplotlib本身不负责组织数据,而是接收前两者处理后的结果来绘图 - 常见操作上,
NumPy侧重数学运算、数组运算和广播,Pandas侧重读取、筛选、转换、合并、分组统计,Matplotlib侧重图表类型选择、坐标轴设置、图例和样式调整
学习顺序可以简单记成:先学 NumPy,再学 Pandas,最后学 Matplotlib。
小结
Pandas 是 Python 数据分析里最常用、也最贴近业务问题的工具。只要你的数据长得像“表”,十有八九都能先想到 DataFrame。
先掌握读取数据、筛选清洗、分组统计、透视表和多表合并这几类核心操作,再继续深入时间序列,会更稳一些。