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索引操作的完整知识体系已经梳理完毕。熟练掌握这些索引技巧,能够让我们在数据分析工作中更加游刃有余地处理各种数据筛选、定位和对齐的需求。建议在实际项目中多加练习,将各种索引方式内化为编程本能。