
本文旨在解决在 Pandas DataFrame 中,针对多个流程阶段(列)统计特定日期范围内的数据行数,以支持漏斗分析。我们将探讨常见错误方法及其局限性,并提供一种基于 Pandas 向量化操作的优化解决方案,实现对 DataFrame 中各列日期数据的准确、高效筛选与计数,从而生成符合分析需求的统计结果。
在数据分析中,我们经常需要追踪业务流程中不同阶段(如销售漏斗、产品开发周期)的进展。当这些阶段以日期时间戳的形式存储在 DataFrame 的不同列中时,一个常见的需求是统计在特定时间范围内,每个阶段有多少条记录进入或处于该阶段。这对于构建漏斗图、比较不同时间段的转化效率至关重要。然而,如果不采用恰当的 Pandas 操作,很容易导致统计结果不准确或效率低下。
假设我们有一个 DataFrame,其中包含多个表示流程阶段的列(例如 stage 1, stage 2, stage 3),每一行代表一个记录,列中的值是该记录进入对应阶段的日期。
输入数据示例:
| stage 1 | stage 2 | stage 3 | |
|---|---|---|---|
| row 1 | 1/3/2023 | 4/3/2023 | 5/7/2023 |
| row 2 | 2/5/2023 | 2/6/2023 | 3/4/2023 |
| row 3 | 1/15/2023 | 6/3/2023 | 7/8/2023 |
我们的目标是,对于给定的一个或多个日期范围,统计每个阶段(列)中,有多少条记录的日期落在这个范围内。
期望输出示例(针对日期范围 1/1/2023 - 4/1/2023):
| stage 1 | stage 2 | stage 3 | |
|---|---|---|---|
| 1/1/2023 - 4/1/2023 | 2 | 1 | 1 |
常见误区分析:
一种直观但错误的尝试是先判断一行中是否存在任何一个日期落在范围内,然后进行计数。例如:
import pandas as pd
# 示例数据
data = {
'stage 1': ['1/3/2023', '2/5/2023', '1/15/2023'],
'stage 2': ['4/3/2023', '2/6/2023', '6/3/2023'],
'stage 3': ['5/7/2023', '3/4/2023', '7/8/2023']
}
df = pd.DataFrame(data, index=['row 1', 'row 2', 'row 3'])
# 转换日期列为datetime类型
df_datetime = df.apply(pd.to_datetime)
start = pd.to_datetime('2023-1-1')
end = pd.to_datetime('2023-3-30')
stage_cols = ['stage 1', 'stage 2', 'stage 3']
# 错误的尝试:
# (df_datetime[stage_cols] >= start).any(axis=1) 会检查一行中是否有任何一个日期 >= start
# (df_datetime[stage_cols] <= end).any(axis=1) 会检查一行中是否有任何一个日期 <= end
# 这种方式会误判,例如 row 3 的 stage 1 在范围内,但 stage 2 和 stage 3 不在,
# 这种方法可能导致整行被包含进来,然后对所有列进行计数。
# 实际需求是:针对每个stage列单独判断其日期是否在范围内。这种方法的问题在于,any(axis=1) 会在进行列级别的判断之前,将行内的所有列聚合成一个布尔值。这意味着如果 row 3 的 stage 1 在范围内,即使 stage 2 和 stage 3 不在,整行也可能被选中,从而在后续计数时错误地增加了 stage 2 和 stage 3 的计数。我们需要的是对每个阶段(列)独立进行日期范围判断和计数。
正确的思路是先对 DataFrame 中的每个元素进行日期范围判断,生成一个布尔型的 DataFrame,然后对这个布尔型 DataFrame 按列进行求和。
首先,确保 DataFrame 中所有涉及日期的列都已转换为 Pandas 的 datetime 类型。这是进行日期比较的基础。
import pandas as pd
# 示例数据
data = {
'stage 1': ['1/3/2023', '2/5/2023', '1/15/2023'],
'stage 2': ['4/3/2023', '2/6/2023', '6/3/2023'],
'stage 3': ['5/7/2023', '3/4/2023', '7/8/2023']
}
df = pd.DataFrame(data, index=['row 1', 'row 2', 'row 3'])
# 将所有日期列转换为 datetime 类型
df_datetime = df.apply(pd.to_datetime)
print("转换后的 DataFrame (df_datetime):")
print(df_datetime)
print("\n")输出 df_datetime 如下:
转换后的 DataFrame (df_datetime):
stage 1 stage 2 stage 3
row 1 2023-01-03 00:00:00 2023-04-03 00:00:00 2023-05-07 00:00:00
row 2 2023-02-05 00:00:00 2023-02-06 00:00:00 2023-03-04 00:00:00
row 3 2023-01-15 00:00:00 2023-06-03 00:00:00 2023-07-08 00:00:00设定我们要统计的起始日期和结束日期。
start_date = pd.to_datetime('2023-1-1')
end_date = pd.to_datetime('2023-3-30') # 注意:结束日期通常包含到该日期的最后一刻,这里是3月30日
# 如果要包含到3月31日,可以设置为'2023-3-31 23:59:59' 或 '2023-4-1'
# 在日期比较中,通常使用 < next_day_start 来表示到前一天结束利用 Pandas 的向量化比较操作 ge (greater than or equal to) 和 le (less than or equal to),对 df_datetime 中的每个元素进行判断。
判断是否大于等于起始日期 (start_date):
is_ge_start = df_datetime.ge(start_date)
print("df_datetime >= start_date:")
print(is_ge_start)
print("\n")输出:
df_datetime >= start_date:
stage 1 stage 2 stage 3
row 1 True True True
row 2 True True True
row 3 True True True判断是否小于等于结束日期 (end_date):
is_le_end = df_datetime.le(end_date)
print("df_datetime <= end_date:")
print(is_le_end)
print("\n")输出:
df_datetime <= end_date:
stage 1 stage 2 stage 3
row 1 True False False
row 2 True True True
row 3 True False False结合两个条件 (& 运算符): 将上述两个布尔型 DataFrame 使用逻辑与 (&) 运算符结合,得到一个最终的布尔型 DataFrame,其中 True 表示该单元格的日期在指定范围内。
is_in_range = is_ge_start & is_le_end
print("日期在范围内的布尔型 DataFrame:")
print(is_in_range)
print("\n")输出:
日期在范围内的布尔型 DataFrame:
stage 1 stage 2 stage 3
row 1 True False False
row 2 True True True
row 3 True False False按列求和 (.sum()): 最后,对这个布尔型 DataFrame 进行列方向的求和。在 Pandas 中,True 被视为 1,False 被视为 0,因此求和结果就是每个列中满足条件的日期数量。
counts_per_stage = is_in_range.sum()
print("每个阶段在指定日期范围内的计数:")
print(counts_per_stage)
print("\n")输出:
每个阶段在指定日期范围内的计数: stage 1 2 stage 2 1 stage 3 1 dtype: int64
将上述步骤整合到一起,可以得到一个简洁高效的单日期范围计数方法:
# 核心代码
start_date = pd.to_datetime('2023-1-1')
end_date = pd.to_datetime('2023-3-30')
# 直接进行条件判断并求和
counts_for_range = (df_datetime.ge(start_date) & df_datetime.le(end_date)).sum()
print(f"日期范围 {start_date.strftime('%Y-%m-%d')} - {end_date.strftime('%Y-%m-%d')} 的计数:")
print(counts_for_range)为了实现对多个日期范围的漏斗分析,我们可以将上述核心逻辑封装到一个函数中,并遍历所有目标日期范围。
def funnel_by_time_optimized(df: pd.DataFrame, date_ranges: list):
"""
根据给定的多个日期范围,统计DataFrame中每个阶段(列)的记录数。
参数:
df (pd.DataFrame): 包含日期数据的原始DataFrame。
date_ranges (list): 包含元组的列表,每个元组代表一个日期范围 (start_date, end_date)。
返回:
pd.DataFrame: 统计结果,索引为日期范围字符串,列为阶段名称。
"""
# 确保所有日期列为 datetime 类型
df_datetime = df.apply(pd.to_datetime)
results = {}
stage_cols = df.columns.tolist() # 获取所有阶段列名
for start_dt, end_dt in date_ranges:
# 对每个日期范围应用向量化筛选和计数
counts = (df_datetime.ge(start_dt) & df_datetime.le(end_dt)).sum()
# 将结果存储到字典中,键为日期范围的字符串表示
range_str = f"{start_dt.strftime('%m/%d/%Y')} - {end_dt.strftime('%m/%d/%Y')}"
results[range_str] = counts.tolist() # 将Series转换为列表以便DataFrame.from_dict处理
# 将结果字典转换为DataFrame
return pd.DataFrame.from_dict(results, orient='index', columns=stage_cols)
# 示例使用
# 准备原始 DataFrame
data = {
'stage 1': ['1/3/2023', '2/5/2023', '1/15/2023'],
'stage 2': ['4/3/2023', '2/6/2023', '6/3/2023'],
'stage 3': ['5/7/2023', '3/4/2023', '7/8/2023']
}
df_input = pd.DataFrame(data, index=['row 1', 'row 2', 'row 3'])
# 定义多个日期范围
# 注意:为了匹配期望输出,这里调整了结束日期,使其包含到下一个范围的开始日期前一天
# 例如,'2023-4-1'作为end_date,意味着包含到3月31日
date_ranges_list = [
(pd.to_datetime('2023-1-1'), pd.to_datetime('2023-3-31')), # 1/1/2023 - 4/1/2023 实际上是 1/1 - 3/31
(pd.to_datetime('2023-4-1'), pd.to_datetime('2023-6-30')), # 4/1/2023 - 7/1/2023 实际上是 4/1 - 6/30
(pd.to_datetime('2023-7-1'), pd.to_datetime('2023-9-30')) # 7/1/2023 - 10/1/2023 实际上是 7/1 - 9/30
]
# 运行优化后的函数
output_df = funnel_by_time_optimized(df_input, date_ranges_list)
print("最终输出结果:")
print(output_df)期望输出表(与问题描述中的期望输出匹配):
| stage 1 | stage 2 | stage 3 | |
|---|---|---|---|
| 01/01/2023 - 03/31/2023 | 2 | 1 | 1 |
| 04/01/2023 - 06/30/2023 | 0 | 2 | 1 |
| 07/01/2023 - 09/30/2023 | 0 | 1 | 1 |
本文详细介绍了如何使用 Pandas 高效且准确地统计 DataFrame 中多列日期数据在特定日期范围内的行数。通过将所有日期转换为 datetime 类型,并利用 Pandas 强大的向量化比较和求和功能,我们能够避免常见的逻辑错误,并实现高性能的数据处理。这种方法对于构建复杂的漏斗分析、时间序列报告以及其他基于日期范围的聚合任务都非常适用。掌握这些技巧将极大地提升你在 Pandas 中处理日期数据的能力。
以上就是高效统计 Pandas DataFrame 中多列日期范围内的行数的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号