
本文旨在解决如何在Pandas DataFrame中,为每笔交易查找同一客户在当前交易日期之前发生的、不同交易类型的上一笔交易金额。我们将探讨常见低效方法的局限性,并提供一种基于分组迭代的优化解决方案,通过维护状态变量来高效处理时间序列数据,确保结果的准确性和性能。
在数据分析领域,我们经常需要处理包含时间序列信息的交易数据。一个常见的需求是根据当前事件,回溯查找之前发生的特定相关事件。例如,在客户交易数据中,我们可能需要找出每个客户在当前交易之前,其不同类型的上一笔交易的金额。这对于分析客户行为模式、识别交叉销售机会或进行风险评估都至关重要。
考虑一个包含客户ID (KEY_ID)、交易类型 (TYPE)、交易金额 (AMOUNT) 和交易日期 (DATE) 的DataFrame。我们的目标是添加一个新列 PREV_AMOUNT,其中包含针对当前交易,同一客户在之前进行的、类型与当前交易相反的最近一笔交易的金额。如果不存在此类交易,则应为 NaN。
首先,我们定义一个示例DataFrame来演示这个问题。假设交易类型只有两种,例如“Motor”和“Tool”。
输入数据结构:
| KEY_ID | TYPE | AMOUNT | DATE |
|---|---|---|---|
| 1 | Motor | 5000 | 2020-01-01 |
| 1 | Tool | 3000 | 2020-02-01 |
| 1 | Tool | 7000 | 2020-03-01 |
| 2 | Tool | 2000 | 2020-01-15 |
| 2 | Motor | 6000 | 2020-02-15 |
| 2 | Tool | 4000 | 2020-03-15 |
期望输出:
| KEY_ID | TYPE | AMOUNT | DATE | PREV_AMOUNT |
|---|---|---|---|---|
| 1 | Motor | 5000 | 2020-01-01 | NaN |
| 1 | Tool | 3000 | 2020-02-01 | 5000 |
| 1 | Tool | 7000 | 2020-03-01 | 5000 |
| 2 | Tool | 2000 | 2020-01-15 | NaN |
| 2 | Motor | 6000 | 2020-02-15 | 2000 |
| 2 | Tool | 4000 | 2020-03-15 | 6000 |
在开始处理之前,确保DataFrame已按 KEY_ID 和 DATE 升序排序是至关重要的,这能保证我们在遍历时始终处理的是按时间顺序排列的交易。
import pandas as pd
import numpy as np
# 示例数据
data = {
'KEY_ID': [1, 1, 1, 2, 2, 2],
'TYPE': ['Motor', 'Tool', 'Tool', 'Tool', 'Motor', 'Tool'],
'AMOUNT': [5000, 3000, 7000, 2000, 6000, 4000],
'DATE': pd.to_datetime(['2020-01-01', '2020-02-01', '2020-03-01',
'2020-01-15', '2020-02-15', '2020-03-15'])
}
df = pd.DataFrame(data)
# 确保按 KEY_ID 和 DATE 排序
df = df.sort_values(by=['KEY_ID', 'DATE']).reset_index(drop=True)
print("原始DataFrame (已排序):")
print(df)在处理这类问题时,初学者常会尝试以下方法,但它们往往存在效率或逻辑上的问题:
行迭代与全局筛选 (df.apply): 这种方法通常涉及定义一个函数,该函数接收每一行作为输入,然后在函数内部对整个DataFrame进行筛选以找到符合条件的上一笔交易。
# 示例低效函数 (可能导致内核崩溃)
def find_previous_request_inefficient(row, dataframe):
previous_requests = dataframe[
(dataframe['KEY_ID'] == row['KEY_ID']) &
(dataframe['TYPE'] != row['TYPE']) &
(dataframe['DATE'] < row['DATE'])
]
if not previous_requests.empty:
return previous_requests.iloc[-1]['AMOUNT']
return np.nan
# df['PREV_AMOUNT'] = df.apply(lambda row: find_previous_request_inefficient(row, df), axis=1)
# 此方法在大型数据集上因重复的全表扫描而极其低效,可能导致内存溢出或内核崩溃。这种方法的问题在于,对于DataFrame中的每一行,都会进行一次对整个DataFrame的筛选操作,其时间复杂度为O(N^2 * M),其中N是行数,M是列数。在大规模数据集上,这会导致性能急剧下降,甚至造成内核崩溃。
groupby().shift() 误用:shift() 函数常用于获取同一组内的前一个或后一个值。然而,直接使用 groupby(['KEY_ID', 'TYPE'])['AMOUNT'].shift() 只能获取到同一客户、同一类型的前一笔交易金额,无法满足“不同类型”的要求。
# 示例错误逻辑 # df['prev_amount_wrong'] = df.groupby(['KEY_ID', 'TYPE'])['AMOUNT'].shift() # 这只会获取到同一类型的前一笔交易,不符合“不同类型”的要求。
解决此问题的有效方法是利用Pandas的 groupby 功能,结合在每个组内进行迭代并维护状态变量的策略。对于每个客户 (KEY_ID),我们跟踪其最近一次“Motor”类型交易的金额和最近一次“Tool”类型交易的金额。
核心思想:
这种方法的时间复杂度更接近O(N log N)(主要来自排序和分组),远优于O(N^2)的 apply 方法。
代码实现:
# 初始化 PREV_AMOUNT 列
df['PREV_AMOUNT'] = np.nan
# 按 KEY_ID 分组
grouped = df.groupby('KEY_ID')
# 遍历每个客户组
for key_id, group in grouped:
# 为每个客户初始化最近一次 Motor 和 Tool 交易的金额
last_motor_amount = np.nan
last_tool_amount = np.nan
# 遍历组内的每一行(已按日期排序)
for index, row in group.iterrows():
current_type = row['TYPE']
current_amount = row['AMOUNT']
if current_type == 'Motor':
# 如果当前是 Motor 交易,则其 PREV_AMOUNT 是上一次 Tool 交易的金额
df.loc[index, 'PREV_AMOUNT'] = last_tool_amount
# 更新上一次 Motor 交易的金额
last_motor_amount = current_amount
elif current_type == 'Tool':
# 如果当前是 Tool 交易,则其 PREV_AMOUNT 是上一次 Motor 交易的金额
df.loc[index, 'PREV_AMOUNT'] = last_motor_amount
# 更新上一次 Tool 交易的金额
last_tool_amount = current_amount
print("\n最终结果DataFrame:")
print(df)代码解析:
本文介绍了一种在Pandas DataFrame中高效计算客户不同类型前一笔交易金额的方法。通过结合 groupby 和组内迭代,并在循环中维护状态变量,我们能够准确且高效地解决这一常见的时间序列数据分析问题,避免了低效的全表扫描和不正确的 shift 逻辑。这种模式对于需要根据历史事件进行条件性计算的场景非常有用,体现了Pandas处理复杂数据关系的强大能力。
以上就是在Pandas DataFrame中高效计算客户不同类型前一笔交易金额的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号