MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python Pandas中的高级索引与筛选技巧

2021-11-181.3k 阅读

一、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_indexercolumn_indexer 可以是单个标签、标签列表、标签切片等。

  1. 单个标签索引 对于 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。

  1. 标签列表索引 可以使用标签列表同时获取多个行和列的数据。
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。

  1. 标签切片索引 与普通的 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_indexercolumn_indexer 都是整数或整数列表、整数切片等。

  1. 单个位置索引 获取 DataFrame 中指定位置的元素。
data = {
    'col1': [1, 2, 3],
    'col2': [4, 5, 6]
}
df = pd.DataFrame(data)
print(df.iloc[1, 1])

这里 df.iloc[1, 1] 获取了第二行第二列的元素,即 5。

  1. 位置列表索引 通过位置列表获取多个元素。
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]] 获取了第一行和第三行,第一列和第三列的数据。

  1. 位置切片索引 使用位置切片获取数据。
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'

(二)多层索引下的索引技巧

  1. 基于外层标签索引 对于多层索引的 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' 的所有行。

  1. 基于内外层标签索引 也可以同时基于外层和内层标签进行索引。
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 的行,这里的条件判断与单层索引时并无本质区别,但结果会基于多层索引的结构展示。

五、索引与筛选的性能优化

(一)选择合适的索引方式

在处理大数据集时,选择合适的索引方式至关重要。lociloc 由于是基于标签和位置直接索引,性能相对较好。而布尔索引在条件简单时性能也不错,但当条件复杂且数据量很大时,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 中的高级索引与筛选技巧有了全面的了解,并且知道了如何在实际应用场景中运用这些技巧来处理数据。无论是数据分析、数据清洗还是机器学习数据准备,这些技巧都能帮助我们更高效地处理数据。