高效统计 Pandas DataFrame 中多列日期范围内的行数

霞舞
发布: 2025-12-03 12:40:02
原创
570人浏览过

高效统计 pandas dataframe 中多列日期范围内的行数

本文旨在解决在 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
登录后复制

步骤二:定义日期范围

设定我们要统计的起始日期和结束日期。

Cutout.Pro
Cutout.Pro

AI驱动的视觉设计平台

Cutout.Pro 331
查看详情 Cutout.Pro
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 中的每个元素进行判断。

  1. 判断是否大于等于起始日期 (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
    登录后复制
  2. 判断是否小于等于结束日期 (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
    登录后复制
  3. 结合两个条件 (& 运算符): 将上述两个布尔型 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
    登录后复制
  4. 按列求和 (.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

注意事项与性能考量

  1. 日期类型一致性: 确保所有参与比较的日期(DataFrame 列中的日期、start_date、end_date)都已正确转换为 Pandas datetime 类型。类型不一致会导致比较错误或性能下降。
  2. 日期范围的精确定义: 在定义日期范围时,要特别注意区间的开闭。例如,start_date 通常是包含的,而 end_date 是包含到该日期的最后一刻,或者为了避免边界问题,可以设置为下一个日期范围的起始日期(不包含)。例如,要统计到 2023-03-31 结束,可以将 end_date 设置为 pd.to_datetime('2023-03-31') 或 pd.to_datetime('2023-04-01') - pd.Timedelta(seconds=1)。
  3. 向量化操作的优势: 相较于使用 for 循环遍历 DataFrame 的行或列进行条件判断,Pandas 的向量化操作(如 df.ge(start)、df.le(end) 和 & 运算符)在处理大型数据集时具有显著的性能优势,因为它底层使用了优化的 C 语言实现。
  4. 处理缺失值 (NaN): 如果日期列中存在缺失值 (NaN),df_datetime.ge(start) 和 df_datetime.le(end) 会将 NaN 视为 False。如果需要对 NaN 进行特殊处理(例如排除含有 NaN 的行),可能需要在转换类型后或进行比较前进行 dropna() 或其他填充操作。

总结

本文详细介绍了如何使用 Pandas 高效且准确地统计 DataFrame 中多列日期数据在特定日期范围内的行数。通过将所有日期转换为 datetime 类型,并利用 Pandas 强大的向量化比较和求和功能,我们能够避免常见的逻辑错误,并实现高性能的数据处理。这种方法对于构建复杂的漏斗分析、时间序列报告以及其他基于日期范围的聚合任务都非常适用。掌握这些技巧将极大地提升你在 Pandas 中处理日期数据的能力。

以上就是高效统计 Pandas DataFrame 中多列日期范围内的行数的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号