← 返回数据分析目录
← 返回学习笔记首页
Pandas索引操作详解
数据分析专题 · 灵活的数据定位与筛选
专题: Python数据分析系统学习
关键词: 数据分析, Pandas, 索引, loc, iloc, query, reindex, set_index, reset_index
一、索引概述
索引(Index)是Pandas中最核心的概念之一。如果把DataFrame比作一张Excel电子表格,那么索引就是行标签和列标签。索引决定了我们如何定位、筛选和操作数据。Pandas的索引体系非常丰富,提供了多种方式来灵活地访问数据子集。
Pandas中的索引可以分为三大类:标签索引(loc)、位置索引(iloc)和布尔索引。此外还有query()方法提供类似SQL的查询语法。理解这些索引方式及其适用场景,是高效使用Pandas的必备技能。
在开始之前,我们先创建一个示例数据集,后续的所有操作都将基于这个数据集展开。
import pandas as pd
import numpy as np
# 创建示例数据集
df = pd.DataFrame({
'name' : ['Alice' , 'Bob' , 'Charlie' , 'David' ,
'Eve' , 'Frank' , 'Grace' , 'Henry' ],
'age' : [25 , 32 , 18 , 47 , 36 , 29 , 22 , 41 ],
'city' : ['Beijing' , 'Shanghai' , 'Guangzhou' , 'Beijing' ,
'Shanghai' , 'Guangzhou' , 'Beijing' , 'Shanghai' ],
'salary' : [15000 , 22000 , 8000 , 35000 ,
28000 , 18000 , 12000 , 30000 ],
'score' : [85.5 , 92.0 , 78.5 , 88.0 ,
95.5 , 70.0 , 82.5 , 91.0 ]
})
print (df)
输出结果会显示一个8行5列的数据表,默认的行索引是从0到7的RangeIndex。在实际工作中,数据的索引可能是有意义的标签(如日期、ID等),也可能是从0开始的整数序列。不同的索引类型和索引方式决定了我们如何高效地操作数据。
核心原则: Pandas索引的设计哲学是"显示优于隐式"。无论数据如何排列,通过显式的标签索引总能准确定位数据,这大大提升了代码的可读性和健壮性。
二、索引类型详解
Pandas支持多种索引类型,每种类型针对特定的数据类型和应用场景进行了优化。了解不同索引类型的特点,有助于我们选择最适合的数据结构。
2.1 RangeIndex(范围索引)
RangeIndex是默认创建的索引类型,类似于Python的range对象。它具有内存效率高的特点,因为只存储起始值、结束值和步长,而不是所有索引值。
# 默认创建的DataFrame使用RangeIndex
df_default = pd.DataFrame({'x' : [10 , 20 , 30 ]})
print (df_default.index)
# 输出: RangeIndex(start=0, stop=3, step=1)
# 显式创建RangeIndex
idx = pd.RangeIndex(0 , 100 , 2 ) # 0, 2, 4, ..., 98
print (idx)
# 输出: RangeIndex(start=0, stop=100, step=2)
2.2 Int64Index 与 Float64Index(数值索引)
当索引被显式设置为整数或浮点数列表时,Pandas会使用Int64Index或Float64Index。与RangeIndex不同,它们会存储每一个索引值。
# Int64Index
df_int = pd.DataFrame({'val' : [1 , 2 , 3 ]},
index=[10 , 20 , 30 ])
print (df_int.index)
# 输出: Int64Index([10, 20, 30], dtype='int64')
# Float64Index
df_float = pd.DataFrame({'val' : [1 , 2 , 3 ]},
index=[1.5 , 2.5 , 3.5 ])
print (df_float.index)
# 输出: Float64Index([1.5, 2.5, 3.5], dtype='float64')
2.3 DatetimeIndex(日期时间索引)
DatetimeIndex是时间序列分析中最常用的索引类型。它提供了丰富的日期操作功能,如年、月、日、周等属性的访问,以及日期范围的生成。
# 创建DatetimeIndex
dates = pd.date_range('2024-01-01' , periods=5 , freq='ME' )
print (dates)
# DatetimeIndex(['2024-01-31', '2024-02-29', '2024-03-31',
# '2024-04-30', '2024-05-31'],
# dtype='datetime64[ns]', freq='ME')
df_ts = pd.DataFrame({'value' : [100 , 120 , 115 , 130 , 125 ]},
index=dates)
print (df_ts)
# 按年份、月份筛选
print (df_ts['2024-01' ]) # 筛选1月数据
print (df_ts['2024-Q1' ]) # 筛选第一季度数据
# 访问日期属性
print (df_ts.index.year)
# Int64Index([2024, 2024, 2024, 2024, 2024], dtype='int64')
print (df_ts.index.month_name())
# Index(['January', 'February', 'March', 'April', 'May'], dtype='object')
2.4 PeriodIndex(时段索引)
PeriodIndex用于表示时间区间,而非时间点。它特别适合处理以月、季度、年为单位的数据。
# 创建PeriodIndex
periods = pd.period_range('2024-01' , periods=4 , freq='Q' )
print (periods)
# PeriodIndex(['2024Q1', '2024Q2', '2024Q3', '2024Q4'],
# dtype='period[Q-DEC]', freq='Q-DEC')
df_period = pd.DataFrame({'revenue' : [500 , 620 , 580 , 700 ]},
index=periods)
print (df_period)
# PeriodIndex的算术运算
print (periods + 1 ) # 向后推一个季度
# PeriodIndex(['2024Q2', '2024Q3', '2024Q4', '2025Q1'], ...)
2.5 CategoricalIndex(分类索引)
CategoricalIndex基于Pandas的Categorical类型构建,适用于索引值重复性高的场景,可以显著节省内存并提升性能。
# 创建CategoricalIndex
cat_idx = pd.CategoricalIndex(['A' , 'B' , 'A' , 'C' , 'B' , 'A' ])
df_cat = pd.DataFrame({'val' : 1 }, index=cat_idx)
print (df_cat)
# 分类索引的独特优势:按类别快速分组
print (df_cat.groupby(level=0 ).sum())
索引类型选择指南:
RangeIndex :默认无意义整数索引,内存最优
DatetimeIndex :时间序列数据,支持日期切片
PeriodIndex :区间数据(月/季/年),适合财务数据
CategoricalIndex :重复值多的分类数据,节省内存
Int64Index :自定义整数标签
三、loc标签索引
loc是基于标签的索引器,使用行标签和列名来定位数据。loc的语法格式为 df.loc[行选择器, 列选择器]。loc的核心特点是"闭区间"——当使用切片语法时,结束标签是包含在内的。
3.1 单标签选择
传入单个标签,返回对应的行(Series)。
# 先设置一个更有意义的索引
df_named = df.set_index('name' )
print (df_named)
# 单标签选择(按行)
print (df_named.loc['Alice' ])
# age 25
# city Beijing
# salary 15000
# score 85.5
# Name: Alice, dtype: object
# 同时选择行和列
print (df_named.loc['Alice' , 'salary' ])
# 15000
3.2 标签列表索引
传入标签列表,返回多行组成的DataFrame。
# 选择多行
print (df_named.loc[['Alice' , 'Charlie' , 'Eve' ]])
# 选择多行和多列
print (df_named.loc[['Alice' , 'Bob' ], ['age' , 'city' , 'score' ]])
3.3 切片索引(闭区间)
loc的切片是闭区间的,与Python惯用的左闭右开切片不同,这是loc最容易出错的地方。
# 字符串标签切片(闭区间)
print (df_named.loc['Alice' :'David' ])
# 返回 Alice, Bob, Charlie, David 四行
# 注意:包含结束标签 'David'
# 列方向切片(按列名)
print (df_named.loc[:, 'age' :'salary' ])
# 返回 age, city, salary 三列(包含salary)
# 行和列同时切片
print (df_named.loc['Bob' :'Frank' , 'city' :'score' ])
重要提醒: loc的切片是闭区间 ,包含起始和结束标签。这与Python列表的切片(左闭右开)和iloc的切片(左闭右开)都不同。如果不确定标签是否存在,建议先用isin或query来筛选。
3.4 布尔数组索引
传入一个布尔Series或布尔列表,返回所有为True的行。
# 创建布尔条件
condition = df_named['age' ] > 30
print (condition)
# name
# Alice False
# Bob True
# Charlie False
# David True
# Eve True
# Frank False
# Grace False
# Henry True
# Name: age, dtype: bool
# 使用布尔数组索引
print (df_named.loc[condition])
# 复合条件
print (df_named.loc[(df_named['age' ] > 30 ) & (df_named['salary' ] > 25000 )])
性能提示: 使用loc的布尔数组索引比链式索引(如df[df['age']>30]['salary'])更快、更安全。链式索引可能触发SettingWithCopyWarning,而loc始终返回视图或副本的明确行为。
3.5 loc与callable
loc支持传入可调用对象(函数或lambda),使得索引逻辑可以动态计算。
# 使用lambda动态选择
print (df_named.loc[lambda x: x['score' ] > 90 ])
# 返回 score > 90 的行:Bob, Eve, Henry
# 更复杂的动态选择
print (df_named.loc[lambda x: x.index.str.len() > 4 ])
# 返回名字长度大于4的行:Alice, Charlie, David, Frank, Grace, Henry
四、iloc位置索引
iloc是基于整数位置的索引器,与Python列表的索引规则完全一致:从0开始,切片为左闭右开。iloc完全不关心行标签或列标签是什么,只关心数据在内存中的位置。
4.1 整数位置选择
# 选择第一行
print (df.iloc[0 ])
# name Alice
# age 25
# city Beijing
# salary 15000
# score 85.5
# Name: 0, dtype: object
# 选择最后一行
print (df.iloc[-1 ]) # 与Python列表一致,支持负索引
# 选择特定位置的元素
print (df.iloc[2 , 3 ]) # 第3行第4列(salary列第3个值)
# 8000
4.2 整数列表/切片选择
iloc支持整数列表和切片(左闭右开)。
# 选择第1、3、5行
print (df.iloc[[0 , 2 , 4 ]])
# 选择第2到第5行(左闭右开,不含第5行)
print (df.iloc[1 :5 ])
# 返回第1、2、3、4行(索引从0开始)
# 选择所有行,第1到第3列
print (df.iloc[:, 1 :4 ])
# 返回 age, city, salary 三列
# 步长切片
print (df.iloc[::2 ]) # 每隔一行选取
loc vs iloc 对比:
loc :基于标签,闭区间切片,适合有意义的索引
iloc :基于位置,左闭右开切片,适合数值位置操作
常见陷阱:
混淆loc和iloc的切片边界
对非整数索引使用iloc传入标签
忘记iloc不支持负步长
4.3 带整数标签的DataFrame注意事项
当DataFrame的索引是整数时,loc和iloc的行为差异尤为明显,需要格外注意。
# 创建一个整数索引的DataFrame
df_int_idx = pd.DataFrame({'val' : [10 , 20 , 30 , 40 , 50 ]},
index=[1 , 3 , 5 , 7 , 9 ])
print (df_int_idx)
# loc[3] 按标签选择,返回索引为3的行
print (df_int_idx.loc[3 ]) # val: 20
# iloc[3] 按位置选择,返回第4行(索引0开始)
print (df_int_idx.iloc[3 ]) # val: 40
# loc切片:闭区间
print (df_int_idx.loc[1 :5 ])
# 返回索引1、3、5的行(包含5)
# iloc切片:左闭右开
print (df_int_idx.iloc[0 :3 ])
# 返回位置0、1、2的行(不含位置3)
五、布尔索引
布尔索引是Pandas中最灵活、最强大的数据筛选方式。它通过一个布尔Series或布尔数组来选择数据,可以组合任意多个条件。
5.1 基本比较操作
Pandas支持所有常见的比较运算符:==、!=、>、<、>=、<=。
# 等于条件
print (df[df['city' ] == 'Beijing' ])
# 不等于条件
print (df[df['city' ] != 'Shanghai' ])
# 数值比较
print (df[df['salary' ] >= 20000 ])
5.2 多条件组合
多条件组合需要特别注意:每个条件必须用括号括起来,逻辑运算符使用 &(与)、|(或)、~(非),而不是Python的and、or、not。
# AND 条件:年龄大于25 且 薪资大于15000
print (df[(df['age' ] > 25 ) & (df['salary' ] > 15000 )])
# OR 条件:北京 或 上海
print (df[(df['city' ] == 'Beijing' ) | (df['city' ] == 'Shanghai' )])
# NOT 条件:不是广州的
print (df[~(df['city' ] == 'Guangzhou' )])
# 组合三个条件
print (df[(df['age' ] > 25 ) &
(df['salary' ] > 15000 ) &
(df['city' ] == 'Beijing' )])
易错点: 必须使用小括号包裹每个条件,否则会引发运算符优先级错误。例如 df['age'] > 25 & df['salary'] > 15000 会报错,因为 & 的优先级高于 >。正确写法是 df[(df['age'] > 25) & (df['salary'] > 15000)]。
5.3 isin方法
isin用于检查值是否属于一个集合,是处理多值筛选的高效方式。
# isin:值属于某个集合
cities = ['Beijing' , 'Shanghai' ]
print (df[df['city' ].isin(cities)])
# isin也支持对多个列同时筛选
print (df[df[['city' , 'name' ]].isin({
'city' : ['Beijing' , 'Shanghai' ],
'name' : ['Alice' , 'Bob' ]
}).all(axis=1 )])
# 与 ~ 组合使用:排除某些值
print (df[~df['city' ].isin(['Guangzhou' ])])
# 返回除广州外的所有行
5.4 isnull 与 notnull
缺失值处理是数据分析中的常见任务,isnull和notnull提供了检测缺失值的标准方式。
# 创建包含缺失值的数据
df_na = df.copy()
df_na.loc[1 , 'salary' ] = np.nan
df_na.loc[3 , 'score' ] = np.nan
df_na.loc[5 , 'city' ] = np.nan
print (df_na)
# 筛选有缺失值的行
print (df_na[df_na.isnull().any(axis=1 )])
# 筛选没有缺失值的行(完整数据)
print (df_na[df_na.notnull().all(axis=1 )])
# 筛选特定列有缺失值的行
print (df_na[df_na['salary' ].isnull()])
# 筛选特定列没有缺失值的行
print (df_na[df_na['score' ].notnull()])
5.5 between方法
between方法提供了一种简洁的区间筛选语法。
# 筛选年龄在25到40之间(包含边界)
print (df[df['age' ].between(25 , 40 )])
# between的可选参数:inclusive控制边界
print (df[df['age' ].between(25 , 40 , inclusive='left' )])
# inclusive='left':左闭右开,包含25但不包含40
# 字符串也可以使用between
print (df[df['name' ].between('B' , 'E' )])
# 返回名字在B到E之间的行:Bob, Charlie, David
六、query()方法
query()方法提供了一种类似SQL的字符串查询语法,使得复杂条件的书写更加简洁直观。它特别适合在多条件组合、列名包含空格等场景下使用。
6.1 基础查询语法
# 单条件查询
print (df.query('age > 30' ))
# 多条件组合(使用and/or/not,无需括号包裹条件)
print (df.query('age > 25 and salary > 15000' ))
# OR 条件
print (df.query('city == "Beijing" or city == "Shanghai"' ))
# NOT 条件
print (df.query('not city == "Guangzhou"' ))
query()优势: 相比于布尔索引,query()不需要重复写df前缀,也不需要用小括号包裹每个条件,代码更加简洁。在大型DataFrame上,query()还利用了numexpr库进行加速,性能优于纯Python的布尔索引。
6.2 字符串与变量引用
# 字符串值需要加引号
print (df.query('city == "Beijing"' ))
# 在query中引用Python变量(使用@符号)
min_age = 25
min_salary = 15000
print (df.query('age >= @min_age and salary >= @min_salary' ))
# 引用列表变量(结合@)
target_cities = ['Beijing' , 'Shanghai' ]
print (df.query('city in @target_cities' ))
6.3 query中的高级操作
# 使用in运算符
print (df.query('city in ["Beijing", "Shanghai"]' ))
# 使用not in
print (df.query('city not in ["Guangzhou"]' ))
# 索引操作(使用index关键字)
df_named = df.set_index('name' )
print (df_named.query('index in ["Alice", "Bob", "Eve"]' ))
# 列名包含空格的场景
df_spaces = df.rename(columns={'salary' : '月 薪' })
# 使用反引号包裹列名
print (df_spaces.query('`月 薪` > 20000' ))
# 使用Python表达式(exp变量)
print (df.query('(age > 30) & (salary / age > 600)' ))
# 查询年龄大于30且时薪(salary/age近似)大于600的人
query()适用场景:
多条件组合查询
需要引用外部变量
查询逻辑需要动态构建
追求代码简洁性
大型DataFrame性能优化
query()局限性:
不能引用列名中的特殊字符(需反引号)
调试不如布尔索引直观
某些复杂条件表达能力有限
七、set_index 与 reset_index
set_index和reset_index是索引管理中最重要的两个方法,分别用于将一个或多个列设置为行索引,以及将行索引重置为普通列。
7.1 set_index:设置索引
set_index将一个或多个列转换为DataFrame的行索引。设置后,这些列将从数据中移除,成为索引的一部分。
# 单列设置为索引
df_idx = df.set_index('name' )
print (df_idx)
# age city salary score
# name
# Alice 25 Beijing 15000 85.5
# Bob 32 Shanghai 22000 92.0
# ...
# 多列层级索引(MultiIndex)
df_multi = df.set_index(['city' , 'name' ])
print (df_multi)
# age salary score
# city name
# Beijing Alice 25 15000 85.5
# Shanghai Bob 32 22000 92.0
# ...
# 不删除原列(drop=False)
df_idx_keep = df.set_index('name' , drop=False )
print (df_idx_keep)
# name列既作为索引,又保留为数据列
7.2 reset_index:重置索引
reset_index是set_index的逆操作,将索引转换为一列或多列,恢复默认的RangeIndex。
# 重置索引(索引变为普通列)
df_reset = df_idx.reset_index()
print (df_reset)
# name age city salary score
# 0 Alice 25 Beijing 15000 85.5
# 1 Bob 32 Shanghai 22000 92.0
# ...
# 不保留原索引列(drop=True)
df_reset_drop = df_idx.reset_index(drop=True )
print (df_reset_drop)
# name列被丢弃,只有纯整数索引
# age city salary score
# 0 25 Beijing 15000 85.5
# 1 32 Shanghai 22000 92.0
# ...
# 层级索引的部分重置(level参数)
print (df_multi.reset_index(level='city' ))
# 只将city层级重置为列,name仍保留为索引
7.3 实际应用:分组统计后重建DataFrame
在分组聚合操作中,set_index和reset_index经常配合使用。
# 分组聚合默认将分组键作为索引
grouped = df.groupby('city' )['salary' ].agg(['mean' , 'max' , 'min' ])
print (grouped)
# mean max min
# city
# Beijing 20666.67 35000 12000
# Guangzhou 13000.00 18000 8000
# Shanghai 26666.67 30000 22000
# reset_index将分组键从索引变为普通列
grouped_flat = grouped.reset_index()
print (grouped_flat)
# city mean max min
# 0 Beijing 20666.666667 35000 12000
# 1 Guangzhou 13000.000000 18000 8000
# 2 Shanghai 26666.666667 30000 22000
最佳实践: 在需要将分组结果导出到CSV或进行进一步合并操作时,建议使用reset_index()将分组键恢复为普通列,这样可以避免后续操作中的索引对齐问题。
7.4 set_index与reset_index的性能考量
频繁的set_index和reset_index操作会带来一定的性能开销,尤其是在大型DataFrame上。以下是一些优化建议。
# 批量操作代替多次调用
# 不推荐:多次设置索引
# df_temp = df.set_index('city')
# df_temp = df_temp.set_index('name', append=True)
# 推荐:一步创建MultiIndex
df_best = df.set_index(['city' , 'name' ])
# 使用inplace参数减少拷贝
# 注意:inplace在未来版本中可能被弃用,建议直接赋值
# df.set_index('name', inplace=True) # 不推荐
df = df.set_index('name' ) # 推荐:显式赋值
八、reindex重新索引
reindex是Pandas中一个非常重要但容易被忽视的方法。它可以让DataFrame的索引按照指定的顺序重新排列,同时还能处理缺失值的填充。reindex的本质是"根据新索引重新排列数据"。
8.1 基本用法
reindex接受一个新的索引序列,按照新索引的顺序重新排列数据。如果新索引中有原索引不存在的标签,对应的数据将被填充为NaN(或指定的填充值)。
# 重新索引:改变行的顺序
new_order = [3 , 7 , 0 , 4 , 1 , 6 , 2 , 5 ]
print (df.reindex(new_order))
# 重新索引时添加新索引(缺失值自动填充为NaN)
new_order_with_new = [0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]
print (df.reindex(new_order_with_new))
# 索引8、9对应的行全为NaN
# 对列进行reindex
print (df.reindex(columns=['name' , 'score' , 'age' , 'new_col' ]))
# 列顺序改变,新增的new_col全为NaN
8.2 缺失值填充策略
reindex提供了多种填充方法(method参数)来处理缺失值,这在时间序列数据的对齐中特别有用。
# 创建一个时间序列DataFrame
ts = pd.Series(
[100 , 120 , 115 , 130 ],
index=pd.to_datetime(['2024-01-01' , '2024-01-03' ,
'2024-01-06' , '2024-01-10' ])
)
print (ts)
# 创建完整日期序列
full_dates = pd.date_range('2024-01-01' , '2024-01-10' , freq='D' )
# 前向填充(ffill):用前一个有效值填充
print (ts.reindex(full_dates, method='ffill' ))
# 2024-01-01 100.0
# 2024-01-02 100.0 ← 前向填充
# 2024-01-03 120.0
# 2024-01-04 120.0 ← 前向填充
# ...
# 后向填充(bfill):用后一个有效值填充
print (ts.reindex(full_dates, method='bfill' ))
# 限制填充步数(limit参数)
print (ts.reindex(full_dates, method='ffill' , limit=2 ))
# 最多连续填充2个缺失值
8.3 reindex vs set_index
很多初学者容易混淆reindex和set_index,实际上它们的用途完全不同。
# set_index:将列变为索引(改变结构)
df_set = df.set_index('name' ) # 'name'列变成索引
# reindex:按新索引重新排列(改变顺序)
df_re = df.reindex([7 , 6 , 5 , 4 , 3 , 2 , 1 , 0 ]) # 反转行顺序
# reindex与align对齐
df_a = df.iloc[:5 ]
df_b = df.iloc[3 :]
print (df_a.reindex(range (8 ))) # 对齐到完整索引
reindex典型应用场景:
时间序列对齐到完整日历
自定义数据展示顺序
合并前确保两个DataFrame的索引一致
填充缺失的时间点数据
九、sort_index索引排序
sort_index根据索引的值对数据进行排序。它是数据分析中常用的预处理步骤,尤其是在时间序列分析和分层索引操作中。
9.1 基本排序
# 按索引升序排序(默认)
df_shuffled = df.sample(frac=1 , random_state=42 ) # 打乱顺序
print (df_shuffled)
print (df_shuffled.sort_index())
# 索引恢复为0,1,2,3,...的顺序
# 降序排序
print (df_shuffled.sort_index(ascending=False ))
9.2 MultiIndex排序
对于分层索引(MultiIndex),sort_index可以指定排序的层级。
# 创建MultiIndex DataFrame
arrays = [['A' , 'A' , 'A' , 'B' , 'B' , 'B' ],
[3 , 1 , 2 , 6 , 4 , 5 ]]
mi = pd.MultiIndex.from_arrays(arrays, names=['group' , 'id' ])
df_mi = pd.DataFrame({'value' : [10 , 20 , 30 , 40 , 50 , 60 ]}, index=mi)
print (df_mi)
# 按所有层级排序
print (df_mi.sort_index())
# 只按第二层级排序
print (df_mi.sort_index(level=1 ))
# 按指定层级名称排序
print (df_mi.sort_index(level='id' ))
9.3 sort_index vs sort_values
sort_index按索引排序,sort_values按值排序。两者的区别和使用场景如下。
# sort_values:按数据列的值排序
print (df.sort_values(by='salary' , ascending=False ))
# 按薪资从高到低排序
# sort_index:按索引排序
print (df.set_index('name' ).sort_index())
# 按姓名字母顺序排序
# 多列排序
print (df.sort_values(by=['city' , 'salary' ],
ascending=[True , False ]))
# 先按城市升序排序,同一城市内按薪资降序排序
排序性能建议: sort_index通常比sort_values快,因为索引是B-tree结构存储的。如果你需要频繁按某一列排序,考虑将该列设置为索引后使用sort_index。
9.4 排序与NA值处理
# 创建包含缺失索引的数据
df_nan_idx = df.copy()
df_nan_idx.index = pd.Index([0 , 1 , 2 , 3 , 4 , 5 , np.nan, 7 ])
# NA值的位置控制
print (df_nan_idx.sort_index(na_position='last' )) # NA放最后(默认)
print (df_nan_idx.sort_index(na_position='first' )) # NA放最前
十、综合实战案例
下面通过一个完整的实战案例,综合运用本节所学的各种索引操作。案例场景:对一份销售数据进行多维度分析和清洗。
10.1 数据准备
# 生成模拟销售数据
np.random.seed(42 )
dates = pd.date_range('2024-01-01' , periods=100 , freq='D' )
cities = ['北京' , '上海' , '广州' , '深圳' ]
products = ['A' , 'B' , 'C' ]
sales_data = {
'date' : np.random.choice(dates, 500 ),
'city' : np.random.choice(cities, 500 ),
'product' : np.random.choice(products, 500 , p=[0.5 , 0.3 , 0.2 ]),
'quantity' : np.random.randint(1 , 50 , 500 ),
'price' : np.round(np.random.uniform(10 , 100 , 500 ), 2 )
}
df_sales = pd.DataFrame(sales_data)
df_sales['revenue' ] = df_sales['quantity' ] * df_sales['price' ]
print (df_sales.shape)
print (df_sales.head())
print (df_sales.dtypes)
10.2 多维度筛选与切片
# 1. 时间范围筛选(使用布尔索引)
mask_date = (df_sales['date' ] >= '2024-01-15' ) & \
(df_sales['date' ] <= '2024-02-15' )
# 2. 城市筛选(使用isin)
mask_city = df_sales['city' ].isin(['北京' , '上海' ])
# 3. 产品筛选与金额条件
mask_product = (df_sales['product' ] == 'A' ) & \
(df_sales['revenue' ] > 500 )
# 组合所有条件
df_filtered = df_sales[mask_date & mask_city & mask_product]
print (f'符合条件的记录数:{len(df_filtered)}' )
print (df_filtered.head())
10.3 时间序列重采样
# 设置日期索引
df_ts = df_sales.set_index('date' )
# 按周统计销售额
weekly_sales = df_ts.resample('W' )['revenue' ].sum()
print (weekly_sales.head())
# 使用reindex填充缺失周
full_weeks = pd.date_range('2024-01-01' , '2024-04-09' , freq='W' )
weekly_sales_full = weekly_sales.reindex(full_weeks, fill_value=0 )
print (weekly_sales_full.head())
10.4 分层索引分析
# 创建城市+产品的分层索引
df_pivot = df_sales.groupby(['city' , 'product' ]).agg({
'quantity' : 'sum' ,
'revenue' : 'sum' ,
'price' : 'mean'
}).round(2 )
print (df_pivot)
# 使用loc选择特定分层
print (df_pivot.loc['北京' ]) # 北京所有产品
print (df_pivot.loc[('北京' , 'A' )]) # 北京的A产品
print (df_pivot.loc[['北京' , '上海' ]]) # 北京和上海
# sort_index排序后进行分析
df_sorted = df_pivot.sort_index(level='revenue' , ascending=False )
print (df_sorted)
# 使用query筛选高收入组合
print (df_pivot.query('revenue > 10000' ))
十一、总结与实践要点
索引操作核心要点回顾:
loc vs iloc :loc是标签索引(闭区间),iloc是位置索引(左闭右开)
布尔索引 :多条件用 & / | / ~,每个条件必须加括号
query() :字符串查询语法,用 @ 引用变量,用 and/or 替代 &/|
set_index/reset_index :列与索引的相互转换
reindex :按新索引重新排列,支持前向/后向填充
sort_index :按索引排序,MultiIndex可指定层级
选择正确的索引类型 可以显著提升数据操作效率
进阶阅读: 掌握了基础索引操作后,可以进一步学习MultiIndex的高级操作(xs、swaplevel、stack/unstack)、Index的set操作(intersection、union、difference)、以及自定义Index子类等进阶主题。这些内容将在后续的学习笔记中逐步展开。
索引操作速查表
操作
方法/语法
说明
单标签行 df.loc['Alice']选择标签为Alice的行
多标签行 df.loc[['A','B','C']]选择多个指定行
标签行切片 df.loc['A':'D']闭区间切片
单位置行 df.iloc[0]选择第1行
位置行切片 df.iloc[0:3]左闭右开切片
布尔条件 df[df['age'] > 30]布尔Series筛选
多条件 df[(a>1) & (b<2)]用()包裹每个条件
集合包含 df[df['c'].isin([x,y])]属于集合
缺失值检测 df[df['c'].isnull()]筛选缺失值
字符串查询 df.query('age>30 and city=="BJ"')SQL风格查询
设置索引 df.set_index('col')列变为索引
重置索引 df.reset_index()索引变为列
重新索引 df.reindex(new_idx)按新索引排列
索引排序 df.sort_index()按索引值排序
至此,Pandas索引操作的完整知识体系已经梳理完毕。熟练掌握这些索引技巧,能够让我们在数据分析工作中更加游刃有余地处理各种数据筛选、定位和对齐的需求。建议在实际项目中多加练习,将各种索引方式内化为编程本能。