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

Python数据处理库Pandas的高级用法

2024-08-094.1k 阅读

数据聚合与分组运算

在数据处理中,分组与聚合操作是非常常见的需求。Pandas 提供了强大的 groupby 机制来实现这些操作。

基本的分组聚合

假设我们有一个销售数据的 DataFrame,包含产品名称、销售地区和销售额:

import pandas as pd

data = {
    'product': ['A', 'B', 'A', 'B', 'A', 'B'],
   'region': ['North', 'South', 'North', 'South', 'North', 'South'],
   'sales': [100, 150, 200, 180, 120, 220]
}
df = pd.DataFrame(data)

我们想要按产品计算总销售额,可以这样做:

grouped = df.groupby('product')
result = grouped['sales'].sum()
print(result)

上述代码中,首先使用 groupby 方法按 product 列进行分组,然后对分组后的 sales 列进行求和操作。

多列分组

如果我们想要同时按产品和地区进行分组,计算每个组的平均销售额:

grouped = df.groupby(['product','region'])
result = grouped['sales'].mean()
print(result)

这里通过传入一个列名列表 ['product','region'] 实现多列分组。

复杂的聚合函数

除了常见的 summean 等函数,groupby 还支持使用自定义的聚合函数。比如,我们想计算每个组销售额的中位数和标准差:

def custom_aggregation(x):
    return {
       'median': x.median(),
       'std': x.std()
    }

grouped = df.groupby('product')
result = grouped['sales'].agg(custom_aggregation)
print(result)

在这个例子中,定义了 custom_aggregation 函数,它接受一个 Series(即分组后的 sales 列数据),返回一个包含中位数和标准差的字典。agg 方法会对每个组应用这个函数。

分组后的转换操作

transform 方法与 agg 方法类似,但它返回的结果与原始 DataFrame 具有相同的索引,其值是经过特定函数转换后的结果。例如,我们想计算每个销售记录相对于其所在组平均销售额的差值:

grouped = df.groupby('product')
df['sales_diff'] = grouped['sales'].transform(lambda x: x - x.mean())
print(df)

这里使用 transform 方法,并传入一个匿名函数,该函数计算每个值与所在组平均值的差值,并将结果赋值给新的列 sales_diff

数据透视表与交叉表

数据透视表是一种用于分析数据的强大工具,它可以将数据重新组织,以便更好地观察数据的各种关系。

创建基本的数据透视表

还是以上述销售数据为例,我们想创建一个数据透视表,行是产品,列是地区,值是销售额的总和:

pivot_table = df.pivot_table(values='sales', index='product', columns='region', aggfunc='sum')
print(pivot_table)

pivot_table 方法的 values 参数指定要聚合的值列,index 参数指定行索引,columns 参数指定列索引,aggfunc 参数指定聚合函数。

多层次索引的数据透视表

如果我们想在产品下再按季度进行细分,假设有一个新的 quarter 列:

data['quarter'] = ['Q1', 'Q1', 'Q2', 'Q2', 'Q3', 'Q3']
df = pd.DataFrame(data)

pivot_table = df.pivot_table(values='sales', index=['product', 'quarter'], columns='region', aggfunc='sum')
print(pivot_table)

这里通过在 index 参数中传入一个列表,实现了多层次索引的数据透视表。

交叉表

交叉表是一种特殊的数据透视表,用于计算分组频率。例如,我们想统计每个产品在每个地区的销售记录数量:

cross_table = pd.crosstab(df['product'], df['region'])
print(cross_table)

crosstab 函数直接根据指定的两个列生成交叉表,非常方便快捷。

时间序列处理

Pandas 在时间序列处理方面也表现出色,它提供了丰富的工具来处理时间相关的数据。

时间序列数据的创建

我们可以创建一个简单的日期范围:

date_rng = pd.date_range(start='1/1/2020', end='1/10/2020', freq='D')
ts = pd.Series(range(len(date_rng)), index=date_rng)
print(ts)

这里使用 date_range 函数创建了一个从 2020 年 1 月 1 日到 2020 年 1 月 10 日,频率为每天(freq='D')的日期范围,并将其作为 Series 的索引。

时间序列的重采样

假设我们有一个按天记录的销售额数据,现在想将其重采样为按周统计销售额总和:

import numpy as np

date_rng = pd.date_range(start='1/1/2020', end='1/31/2020', freq='D')
sales = np.random.randint(100, 200, size=len(date_rng))
sales_ts = pd.Series(sales, index=date_rng)

weekly_sales = sales_ts.resample('W').sum()
print(weekly_sales)

resample 方法用于对时间序列进行重采样,这里 'W' 表示按周重采样,并对销售额进行求和。

时间序列的移动窗口计算

移动窗口计算可以帮助我们分析时间序列数据的局部特征。例如,计算过去 3 天销售额的移动平均值:

rolling_mean = sales_ts.rolling(window=3).mean()
print(rolling_mean)

rolling 方法创建一个移动窗口对象,window 参数指定窗口大小为 3,然后调用 mean 方法计算移动平均值。

缺失数据处理

在实际数据处理中,缺失数据是常见的问题。Pandas 提供了多种方法来处理缺失数据。

检测缺失值

我们可以使用 isnullnotnull 方法来检测 DataFrame 或 Series 中的缺失值:

data = {
    'col1': [1, None, 3],
    'col2': [4, 5, None]
}
df = pd.DataFrame(data)

print(df.isnull())
print(df.notnull())

isnull 方法返回一个与原数据结构相同的布尔值 DataFrame,其中缺失值位置为 True,非缺失值位置为 Falsenotnull 则相反。

删除缺失值

要删除包含缺失值的行或列,可以使用 dropna 方法:

# 删除包含缺失值的行
df_dropped = df.dropna(axis=0)
print(df_dropped)

# 删除包含缺失值的列
df_dropped_col = df.dropna(axis=1)
print(df_dropped_col)

axis=0 表示按行删除,axis=1 表示按列删除。默认情况下,只要该行或列中有一个缺失值就会被删除。

填充缺失值

我们可以使用 fillna 方法来填充缺失值:

# 用 0 填充缺失值
df_filled = df.fillna(0)
print(df_filled)

# 用前一个值填充缺失值(向前填充)
df_filled_ffill = df.fillna(method='ffill')
print(df_filled_ffill)

# 用后一个值填充缺失值(向后填充)
df_filled_bfill = df.fillna(method='bfill')
print(df_filled_bfill)

fillna 方法可以传入具体的值进行填充,也可以使用 method 参数指定填充方式,如 'ffill'(向前填充)和 'bfill'(向后填充)。

高性能数据处理技巧

在处理大规模数据时,提高性能至关重要。以下是一些 Pandas 的高性能数据处理技巧。

向量化操作

尽量避免使用循环,而是使用 Pandas 的向量化操作。例如,计算 DataFrame 某一列的平方:

data = {
    'col1': [1, 2, 3, 4]
}
df = pd.DataFrame(data)

# 向量化操作
df['col1_squared'] = df['col1'] ** 2
print(df)

这里直接使用 ** 运算符对整个列进行操作,而不是通过循环逐个计算。

分块读取数据

当处理非常大的文件时,可以分块读取数据,而不是一次性将整个文件读入内存:

chunk_size = 10000
for chunk in pd.read_csv('large_file.csv', chunksize=chunk_size):
    # 在这里对每个数据块进行处理
    processed_chunk = chunk[chunk['column'] > 100]
    # 可以将处理后的结果保存或进一步操作
    print(processed_chunk.head())

read_csvchunksize 参数指定每个数据块的大小,通过循环逐块读取和处理数据,大大减少内存占用。

使用高效的数据类型

确保 DataFrame 中的列使用合适的数据类型。例如,如果某列是整数且范围较小,可以使用 Int8Int16 等较小的数据类型,而不是默认的 Int64

data = {
    'col1': [1, 2, 3, 4]
}
df = pd.DataFrame(data)

df['col1'] = df['col1'].astype('Int8')
print(df.dtypes)

通过 astype 方法将列的数据类型转换为更高效的类型,减少内存占用。

数据合并与连接

在实际数据处理中,常常需要将多个数据集合并在一起。Pandas 提供了多种方法来实现数据的合并与连接。

合并(Merge)

类似于 SQL 中的 JOIN 操作,merge 方法可以根据一个或多个键将两个 DataFrame 合并。假设我们有两个 DataFrame,一个包含产品信息,另一个包含销售记录:

products = {
    'product_id': [1, 2, 3],
    'product_name': ['A', 'B', 'C']
}
products_df = pd.DataFrame(products)

sales = {
   'sale_id': [1, 2, 3],
    'product_id': [1, 2, 1],
   'sales_amount': [100, 150, 120]
}
sales_df = pd.DataFrame(sales)

merged_df = pd.merge(products_df, sales_df, on='product_id')
print(merged_df)

这里通过 pd.merge 方法,根据 product_id 列将 products_dfsales_df 合并在一起。

连接(Concat)

concat 方法用于沿着一个轴(行或列)连接多个 DataFrame。例如,我们有两个具有相同列结构的销售记录 DataFrame,想将它们按行连接起来:

sales1 = {
   'sale_id': [1, 2],
    'product_id': [1, 2],
   'sales_amount': [100, 150]
}
sales1_df = pd.DataFrame(sales1)

sales2 = {
   'sale_id': [3, 4],
    'product_id': [1, 2],
   'sales_amount': [120, 180]
}
sales2_df = pd.DataFrame(sales2)

concatenated_df = pd.concat([sales1_df, sales2_df])
print(concatenated_df)

concat 方法接受一个 DataFrame 列表,默认按行(axis=0)连接。如果想按列连接,可以设置 axis=1

合并重叠数据(Combine First)

当两个 DataFrame 有部分重叠的数据时,可以使用 combine_first 方法来合并数据。假设我们有两个关于产品销售信息的 DataFrame,其中一些产品的销售数据在不同的 DataFrame 中有不同的记录:

df1 = {
    'product': ['A', 'B', 'C'],
   'sales': [100, None, 150]
}
df1 = pd.DataFrame(df1)

df2 = {
    'product': ['B', 'C', 'D'],
   'sales': [120, 180, 200]
}
df2 = pd.DataFrame(df2)

result = df1.set_index('product').combine_first(df2.set_index('product')).reset_index()
print(result)

这里先将 product 列设置为索引,然后使用 combine_first 方法合并两个 DataFrame,以 df1 为主,遇到缺失值时用 df2 的值填充,最后再重置索引。

数据可视化集成

Pandas 可以与其他可视化库(如 Matplotlib 和 Seaborn)很好地集成,方便对数据进行可视化分析。

简单的折线图

假设我们有一个时间序列数据,想绘制销售额随时间的变化折线图:

import matplotlib.pyplot as plt

date_rng = pd.date_range(start='1/1/2020', end='1/10/2020', freq='D')
sales = np.random.randint(100, 200, size=len(date_rng))
sales_ts = pd.Series(sales, index=date_rng)

sales_ts.plot()
plt.show()

Pandas 的 Series 和 DataFrame 都有 plot 方法,这里直接调用 plot 方法绘制折线图,matplotlib 会在后台进行绘图操作,最后通过 plt.show() 显示图形。

柱状图

对于分组数据,我们可以绘制柱状图来比较不同组的统计信息。例如,按产品统计销售额的柱状图:

data = {
    'product': ['A', 'B', 'A', 'B', 'A', 'B'],
   'sales': [100, 150, 200, 180, 120, 220]
}
df = pd.DataFrame(data)

grouped = df.groupby('product')['sales'].sum()
grouped.plot(kind='bar')
plt.show()

首先对数据按产品分组并求和,然后调用 plot 方法并设置 kind='bar' 绘制柱状图。

箱线图

箱线图可以帮助我们了解数据的分布情况。例如,绘制不同地区销售额的箱线图:

import seaborn as sns

data = {
    'product': ['A', 'B', 'A', 'B', 'A', 'B'],
   'region': ['North', 'South', 'North', 'South', 'North', 'South'],
   'sales': [100, 150, 200, 180, 120, 220]
}
df = pd.DataFrame(data)

sns.boxplot(x='region', y='sales', data=df)
plt.show()

这里使用 Seaborn 库的 boxplot 函数,通过传入 DataFrame 和相应的列名,绘制出按地区划分的销售额箱线图,直观展示不同地区销售额的分布特征。

通过以上这些高级用法,我们可以更高效、更深入地利用 Pandas 进行数据处理和分析,无论是面对复杂的数据结构,还是大规模的数据量,都能应对自如。在实际应用中,需要根据具体的数据特点和需求,灵活选择和组合这些方法,以达到最佳的数据处理效果。