Python Pandas中的高级索引与筛选技巧
一、Pandas 索引基础回顾
在深入探讨高级索引与筛选技巧之前,我们先来简单回顾一下 Pandas 索引的基础知识。Pandas 主要有两种数据结构:Series 和 DataFrame。
(一)Series 的索引
Series 是一种类似于一维数组的对象,它由一组数据以及一组与之相关的数据标签(即索引)组成。创建 Series 时,如果没有显式指定索引,Pandas 会自动创建一个从 0 开始的整数索引。
import pandas as pd
data = [10, 20, 30]
s = pd.Series(data)
print(s)
上述代码创建了一个简单的 Series,其索引为 0、1、2。我们也可以自定义索引:
data = [10, 20, 30]
index = ['a', 'b', 'c']
s = pd.Series(data, index = index)
print(s)
这里通过 index
参数指定了自定义索引,使得 Series 的索引为 'a'
、'b'
、'c'
。通过索引,我们可以轻松获取 Series 中的数据,例如 s['a']
会返回 10。
(二)DataFrame 的索引
DataFrame 是一个表格型的数据结构,它含有一组有序的列,每列可以是不同的值类型(数值、字符串、布尔值等)。DataFrame 既有行索引,也有列索引。行索引通常用于标识每一行数据,列索引用于标识每一列数据。
data = {
'col1': [1, 2, 3],
'col2': [4, 5, 6]
}
df = pd.DataFrame(data)
print(df)
上述代码创建了一个简单的 DataFrame,其行索引为 0、1、2,列索引为 'col1'
和 'col2'
。我们可以通过列索引获取某一列的数据,如 df['col1']
,会返回一个 Series 对象。
二、高级索引技巧
(一)基于标签的高级索引(loc
)
loc
主要用于基于标签的索引,其语法为 df.loc[row_indexer, column_indexer]
,其中 row_indexer
和 column_indexer
可以是单个标签、标签列表、标签切片等。
- 单个标签索引
对于 DataFrame,我们可以通过
loc
使用行标签和列标签获取单个元素。
data = {
'col1': [1, 2, 3],
'col2': [4, 5, 6]
}
df = pd.DataFrame(data, index = ['a', 'b', 'c'])
print(df.loc['b', 'col2'])
上述代码中,通过 df.loc['b', 'col2']
获取了行标签为 'b'
,列标签为 'col2'
的元素,即 5。
- 标签列表索引 可以使用标签列表同时获取多个行和列的数据。
data = {
'col1': [1, 2, 3],
'col2': [4, 5, 6],
'col3': [7, 8, 9]
}
df = pd.DataFrame(data, index = ['a', 'b', 'c'])
print(df.loc[['a', 'c'], ['col1', 'col3']])
这里通过 df.loc[['a', 'c'], ['col1', 'col3']]
获取了行标签为 'a'
和 'c'
,列标签为 'col1'
和 'col3'
的数据,返回一个新的 DataFrame。
- 标签切片索引
与普通的 Python 切片不同,
loc
的切片是包含终点的。
data = {
'col1': [1, 2, 3],
'col2': [4, 5, 6],
'col3': [7, 8, 9]
}
df = pd.DataFrame(data, index = ['a', 'b', 'c'])
print(df.loc['a':'b', 'col1':'col2'])
df.loc['a':'b', 'col1':'col2']
会获取行标签从 'a'
到 'b'
(包括 'b'
),列标签从 'col1'
到 'col2'
(包括 'col2'
)的数据。
(二)基于位置的高级索引(iloc
)
iloc
用于基于整数位置的索引,语法为 df.iloc[row_indexer, column_indexer]
,其中 row_indexer
和 column_indexer
都是整数或整数列表、整数切片等。
- 单个位置索引 获取 DataFrame 中指定位置的元素。
data = {
'col1': [1, 2, 3],
'col2': [4, 5, 6]
}
df = pd.DataFrame(data)
print(df.iloc[1, 1])
这里 df.iloc[1, 1]
获取了第二行第二列的元素,即 5。
- 位置列表索引 通过位置列表获取多个元素。
data = {
'col1': [1, 2, 3],
'col2': [4, 5, 6],
'col3': [7, 8, 9]
}
df = pd.DataFrame(data)
print(df.iloc[[0, 2], [0, 2]])
df.iloc[[0, 2], [0, 2]]
获取了第一行和第三行,第一列和第三列的数据。
- 位置切片索引 使用位置切片获取数据。
data = {
'col1': [1, 2, 3],
'col2': [4, 5, 6],
'col3': [7, 8, 9]
}
df = pd.DataFrame(data)
print(df.iloc[0:2, 0:2])
df.iloc[0:2, 0:2]
获取了前两行和前两列的数据,这里的切片与 Python 普通切片一样,不包含终点。
(三)混合索引
有时候我们可能需要混合使用基于标签和基于位置的索引。虽然 Pandas 没有直接提供一个方法来实现完全混合索引,但我们可以通过一些转换来达到目的。例如,先通过 loc
获取基于标签的部分,再通过 iloc
在获取的结果上进行基于位置的索引。
data = {
'col1': [1, 2, 3],
'col2': [4, 5, 6],
'col3': [7, 8, 9]
}
df = pd.DataFrame(data, index = ['a', 'b', 'c'])
sub_df = df.loc[['a', 'b']]
result = sub_df.iloc[0, 1]
print(result)
上述代码先通过 loc
获取行标签为 'a'
和 'b'
的数据,然后在这个子 DataFrame 上通过 iloc
获取第一行第二列的元素。
三、复杂筛选技巧
(一)布尔索引筛选
布尔索引是根据布尔条件筛选数据的一种强大方式。对于 DataFrame,我们可以基于列数据创建布尔条件,然后用这个条件来筛选行。
data = {
'col1': [1, 2, 3],
'col2': [4, 5, 6]
}
df = pd.DataFrame(data)
condition = df['col1'] > 1
print(df[condition])
上述代码中,df['col1'] > 1
创建了一个布尔 Series,True
表示对应行的 col1
列值大于 1。然后将这个布尔 Series 作为索引传递给 DataFrame df
,从而筛选出 col1
列值大于 1 的行。
我们还可以使用多个条件进行筛选,通过 &
(逻辑与)、|
(逻辑或)等运算符连接条件。
data = {
'col1': [1, 2, 3],
'col2': [4, 5, 6]
}
df = pd.DataFrame(data)
condition1 = df['col1'] > 1
condition2 = df['col2'] < 6
print(df[condition1 & condition2])
这里通过 condition1 & condition2
筛选出 col1
大于 1 且 col2
小于 6 的行。
(二)query
方法筛选
query
方法提供了一种更直观的方式来筛选数据,尤其是当条件比较复杂时。它允许我们使用字符串表达式来指定筛选条件。
data = {
'col1': [1, 2, 3],
'col2': [4, 5, 6]
}
df = pd.DataFrame(data)
result = df.query('col1 > 1 and col2 < 6')
print(result)
在 query
方法中,我们直接使用 col1 > 1 and col2 < 6
这样的表达式来筛选数据,这种方式更加简洁明了,尤其适合有多个条件的情况。而且 query
方法在处理大数据集时,性能也相对较好。
(三)按条件筛选后修改数据
筛选数据后,我们常常需要对筛选出的数据进行修改。例如,我们筛选出满足某个条件的行,然后修改这些行中某一列的值。
data = {
'col1': [1, 2, 3],
'col2': [4, 5, 6]
}
df = pd.DataFrame(data)
condition = df['col1'] > 1
df.loc[condition, 'col2'] = 10
print(df)
上述代码先筛选出 col1
大于 1 的行,然后通过 loc
将这些行的 col2
列值修改为 10。这里使用 loc
是因为我们是基于标签进行修改操作,确保修改的准确性。
四、多层索引下的高级索引与筛选
(一)多层索引的创建
多层索引(也称为层次化索引)允许我们在一个轴上拥有多个索引级别。在 DataFrame 中,我们可以在行索引或列索引上创建多层索引。
import pandas as pd
index = pd.MultiIndex.from_product([['group1', 'group2'], ['a', 'b']])
data = {
'col1': [1, 2, 3, 4],
'col2': [5, 6, 7, 8]
}
df = pd.DataFrame(data, index = index)
print(df)
上述代码通过 pd.MultiIndex.from_product
创建了一个行多层索引,外层索引为 'group1'
和 'group2'
,内层索引为 'a'
和 'b'
。
(二)多层索引下的索引技巧
- 基于外层标签索引 对于多层索引的 DataFrame,我们可以先基于外层标签进行索引。
index = pd.MultiIndex.from_product([['group1', 'group2'], ['a', 'b']])
data = {
'col1': [1, 2, 3, 4],
'col2': [5, 6, 7, 8]
}
df = pd.DataFrame(data, index = index)
print(df.loc['group1'])
df.loc['group1']
会返回外层索引为 'group1'
的所有行。
- 基于内外层标签索引 也可以同时基于外层和内层标签进行索引。
index = pd.MultiIndex.from_product([['group1', 'group2'], ['a', 'b']])
data = {
'col1': [1, 2, 3, 4],
'col2': [5, 6, 7, 8]
}
df = pd.DataFrame(data, index = index)
print(df.loc[('group1', 'a')])
这里 df.loc[('group1', 'a')]
使用元组指定了外层索引为 'group1'
,内层索引为 'a'
,从而获取对应的数据。
(三)多层索引下的筛选技巧
在多层索引下进行筛选与单层索引类似,只是条件要针对多层索引的结构。
index = pd.MultiIndex.from_product([['group1', 'group2'], ['a', 'b']])
data = {
'col1': [1, 2, 3, 4],
'col2': [5, 6, 7, 8]
}
df = pd.DataFrame(data, index = index)
condition = df['col1'] > 2
print(df[condition])
上述代码同样通过布尔索引筛选出 col1
大于 2 的行,这里的条件判断与单层索引时并无本质区别,但结果会基于多层索引的结构展示。
五、索引与筛选的性能优化
(一)选择合适的索引方式
在处理大数据集时,选择合适的索引方式至关重要。loc
和 iloc
由于是基于标签和位置直接索引,性能相对较好。而布尔索引在条件简单时性能也不错,但当条件复杂且数据量很大时,query
方法可能更具优势。例如,对于一个包含数百万行数据的 DataFrame,如果要根据多个复杂条件筛选数据,query
方法可能比使用多个布尔条件通过 &
和 |
连接的布尔索引方式更快。
(二)避免重复索引操作
尽量避免在循环中进行重复的索引和筛选操作。如果需要对 DataFrame 进行多次类似的索引和筛选,可以先将数据筛选出来,然后在筛选后的结果上进行后续操作。例如,假设我们需要对一个 DataFrame 多次根据不同条件筛选并计算某些统计量,如果每次都从原始大数据集进行筛选,会浪费大量时间。可以先将可能用到的数据通过一次筛选获取,然后在这个子数据集上进行各种计算。
(三)使用合适的数据类型
确保 DataFrame 中的列使用合适的数据类型。例如,如果某一列都是整数,确保其数据类型为整数类型(如 int64
),而不是默认的 object
类型。不合适的数据类型可能会导致索引和筛选操作变慢。可以通过 astype
方法来转换列的数据类型。
data = {
'col1': ['1', '2', '3']
}
df = pd.DataFrame(data)
df['col1'] = df['col1'].astype('int64')
上述代码将 col1
列的数据类型从默认的 object
(字符串类型)转换为 int64
,这样在进行基于该列的索引和筛选操作时,性能会有所提升。
六、实际应用场景中的索引与筛选
(一)数据分析中的数据预处理
在进行数据分析时,数据预处理阶段常常需要使用索引与筛选技巧。例如,我们从数据库中获取了一份销售数据,包含销售日期、产品名称、销售数量、销售金额等列。假设我们只关心某几个特定产品在特定时间段内的销售数据,就需要通过索引与筛选来提取这些数据。可以先根据销售日期筛选出特定时间段的数据,再根据产品名称筛选出特定产品的数据。
import pandas as pd
data = {
'date': ['2023-01-01', '2023-01-02', '2023-01-01', '2023-01-02'],
'product': ['product1', 'product2', 'product1', 'product2'],
'sales_quantity': [10, 20, 15, 25],
'sales_amount': [100, 200, 150, 250]
}
df = pd.DataFrame(data)
df['date'] = pd.to_datetime(df['date'])
condition1 = df['date'] >= '2023-01-01'
condition2 = df['date'] <= '2023-01-02'
condition3 = df['product'] == 'product1'
result = df[condition1 & condition2 & condition3]
print(result)
上述代码先将 date
列转换为日期时间类型,然后通过多个条件筛选出 2023 年 1 月 1 日
到 2023 年 1 月 2 日
之间 product1
的销售数据。
(二)数据清洗中的异常值处理
在数据清洗过程中,索引与筛选可用于找出并处理异常值。例如,对于一个包含员工工资的 DataFrame,我们可以通过设定合理的工资范围来筛选出可能的异常值。
data = {
'employee': ['emp1', 'emp2', 'emp3', 'emp4'],
'salary': [5000, 6000, 100000, 7000]
}
df = pd.DataFrame(data)
condition = (df['salary'] < 1000) | (df['salary'] > 10000)
outliers = df[condition]
print(outliers)
上述代码通过设定工资小于 1000 或大于 10000 为异常值条件,筛选出可能的异常值,后续可以对这些异常值进行进一步处理,如修正、删除等。
(三)机器学习中的数据准备
在机器学习中,数据准备阶段需要对数据集进行各种索引与筛选操作。例如,将数据集按照一定比例划分为训练集和测试集时,就可以通过索引来实现。假设我们有一个包含特征和标签的 DataFrame,我们可以随机选择一定比例的行作为训练集,其余作为测试集。
import pandas as pd
import numpy as np
data = {
'feature1': np.random.randn(100),
'feature2': np.random.randn(100),
'label': np.random.randint(0, 2, 100)
}
df = pd.DataFrame(data)
train_ratio = 0.8
train_index = np.random.choice(df.index, int(len(df) * train_ratio), replace = False)
train_df = df.loc[train_index]
test_df = df.drop(train_index)
print('Train DataFrame:')
print(train_df)
print('Test DataFrame:')
print(test_df)
上述代码通过 np.random.choice
随机选择一定比例的行索引,然后通过 loc
获取训练集数据,通过 drop
方法获取测试集数据。这样就完成了机器学习数据准备中的数据集划分操作。
通过以上详细的介绍,我们对 Python Pandas 中的高级索引与筛选技巧有了全面的了解,并且知道了如何在实际应用场景中运用这些技巧来处理数据。无论是数据分析、数据清洗还是机器学习数据准备,这些技巧都能帮助我们更高效地处理数据。