使用Pandas计算历史同期值及变化率的通用方法

使用Pandas计算历史同期值及变化率的通用方法

本文详细阐述了如何利用pandas库高效地计算dataframe中指定指标的历史同期值,并进一步分析其绝对变化量和百分比变化率。通过构建可复用的函数,我们能够灵活地获取任意前n个月的数据,并将其与当前数据进行合并,为时间序列分析提供强大的数据支持。

引言

在数据分析领域,特别是对时间序列数据进行分析时,经常需要将当前数据与历史同期数据进行比较,以评估增长、下降趋势或季节性影响。例如,我们可能需要将本月销售额与上月或去年同月销售额进行对比。Pandas的pct_change()方法虽然可以计算百分比变化,但它通常用于计算连续周期(如上一行)的变化,且直接获取精确的历史同期值并不直接。本教程将介绍一种基于pd.DateOffset和merge操作的通用方法,以精确获取任意历史周期的值及其变化。

核心思路

解决此问题的核心在于以下两步:

计算目标历史日期:对于DataFrame中的每一行,根据当前日期和所需回溯的月份数,计算出对应的历史日期。这可以通过pd.DateOffset轻松实现。合并历史数据:将原始DataFrame与自身进行合并(自连接),使用当前日期的历史目标日期作为连接键,将历史数据(如指标值)引入当前行的上下文。

数据准备

首先,我们需要一个包含日期和相关指标的DataFrame。以下是一个示例数据集,我们将用它来演示。

import pandas as pdimport io# 示例输入数据INPUT_CSV = """URL,Organic Keywords,Organic Traffic,Datehttps://www.example-url.com/,1315,11345,20231115https://www.example-url.com/,1183,5646,20231015https://www.example-url.com/,869,5095,20230915https://www.example-url.com/,925,4574,20230815https://www.example-url.com/,899,4580,20230715https://www.example-url.com/,1382,5720,20230615https://www.example-url.com/,1171,5544,20230515https://www.example-url.com/,1079,5041,20230415https://www.example-url.com/,734,3855,20230315https://www.example-url.com/,853,3455,20230215https://www.example-url.com/,840,2343,20230115https://www.example-url.com/,325,2318,20221215https://www.example-url.com/,156,1981,20221115https://www.example-url.com/,166,2059,20221015https://www.example-url.com/,124,1977,20220915https://www.example-url.com/,98,1919,20220815https://www.example-url.com/,167,1796,20220715https://www.example-url.com/,140,1596,20220615https://www.example-url.com/,168,1493,20220515https://www.example-url.com/,171,1058,20220415https://www.example-url.com/,141,1735,20220315https://www.example-url.com/,129,1836,20220215https://www.example-url.com/,141,746,20220115https://www.example-url.com/,129,1076,20211215"""# 读取CSV数据df = pd.read_csv(io.StringIO(INPUT_CSV))# 定义常量,方便管理INITIAL_COL_REORDER = ['URL', 'Date', 'Organic Keywords', 'Organic Traffic']METRIC_COLS = ['Organic Keywords', 'Organic Traffic']DIMENSION_COLS = ['URL'] # 如果有多个维度,可以添加DATE_COL = 'Date'# 预处理:重排、转换日期格式、按日期降序排序df = df[INITIAL_COL_REORDER]df[DATE_COL] = pd.to_datetime(df[DATE_COL], format='%Y%m%d')df = df.sort_values(by=DATE_COL, ascending=False)print("原始数据(部分):")print(df.head())

实现 get_last_period_values 函数

这个函数是核心,它接收DataFrame、回溯月份数以及指标和维度列,并返回一个包含历史同期值的新DataFrame。

def get_last_period_values(df, months_prior, metric_cols, dimension_cols, date_col):    df_copy = df.copy() # 避免修改原始DataFrame    # 1. 计算目标历史日期    # 为当前日期创建一个对应的历史日期列    df_copy[f'{date_col}_Prior'] = df_copy[date_col] - pd.DateOffset(months=months_prior)    # 2. 合并历史数据    # 将原始DataFrame与自身进行左连接,根据计算出的历史日期和维度列进行匹配    # suffixes 参数用于区分合并后的同名列,例如 'Organic Keywords' 会变成 'Organic Keywords_1mo_Prior'    df_copy = df_copy.merge(        df_copy[[date_col] + dimension_cols + metric_cols],        left_on=[f'{date_col}_Prior'] + dimension_cols, # 连接键:历史日期 + 维度列        right_on=[date_col] + dimension_cols,        how='left', # 左连接保留所有当前行,没有匹配的历史数据则为NaN        suffixes=('', f'_{months_prior}mo_Prior')    )    # 清理:删除临时创建的历史日期列和合并时产生的多余维度列    df_copy = df_copy.drop(columns=[f'{date_col}_Prior'] + [col + f'_{months_prior}mo_Prior' for col in dimension_cols])    # 3. 计算绝对变化量和百分比变化率    for metric in metric_cols:        # 绝对变化 = 当前值 - 历史值        df_copy[f'{metric}_{months_prior}mo_Abs_Change'] = df_copy[metric] - df_copy[f'{metric}_{months_prior}mo_Prior']        # 百分比变化 = (当前值 / 历史值) - 1        df_copy[f'{metric}_{months_prior}mo_Pct_Change'] = df_copy[metric] / df_copy[f'{metric}_{months_prior}mo_Prior'] - 1        # 对百分比变化进行四舍五入        df_copy[f'{metric}_{months_prior}mo_Pct_Change'] = df_copy[f'{metric}_{months_prior}mo_Pct_Change'].round(2)    return df_copy

函数详解:

df_copy = df.copy():进行操作前,创建一个DataFrame的副本,以避免对原始数据造成意外修改。df_copy[f'{date_col}_Prior’] = df_copy[date_col] – pd.DateOffset(months=months_prior):这一行是关键。pd.DateOffset(months=months_prior)会从date_col中的每个日期减去指定的月份数,得到对应的历史日期。merge()操作:left_on 和 right_on:指定了连接的键。left_on使用当前DataFrame的{date_col}_Prior和dimension_cols,而right_on使用原始DataFrame的date_col和dimension_cols。这样可以确保我们找到的是同一维度(例如URL)在指定历史日期的指标值。how=’left’:这是一个左连接,意味着DataFrame中的所有当前行都会被保留。如果某个历史日期没有匹配的数据,则相应的历史指标列会填充NaN。suffixes=(”, f’_{months_prior}mo_Prior’):这个参数非常重要,它用于处理合并后出现的同名列。原始DataFrame的列保持不变,而从右侧(历史数据)合并过来的列会加上指定的后缀,例如_1mo_Prior。清理:合并完成后,{date_col}_Prior列和合并时产生的历史维度列(如URL_1mo_Prior)已经完成了它们的使命,可以安全地删除,保持DataFrame的整洁。计算变化率:最后,我们遍历所有指标列,计算其与历史同期值的绝对变化和百分比变化。百分比变化率通常会进行四舍五入以提高可读性。

泛化到多个周期

为了方便地计算多个历史周期的值,我们可以再封装一个函数 get_period_values。

def get_period_values(df, periods, metric_cols, dimension_cols, date_col):    df_copy = df.copy()    for period in periods:        df_copy = get_last_period_values(df_copy, period, metric_cols, dimension_cols, date_col)    return df_copy

这个函数接收一个periods列表(例如[1, 3, 12]),然后循环调用get_last_period_values函数,将不同历史周期的数据逐步添加到DataFrame中。

完整脚本示例

将上述所有部分整合,形成一个完整的、可运行的Python脚本。

import pandas as pdimport io## 常量定义INITIAL_COL_REORDER = ['URL', 'Date', 'Organic Keywords', 'Organic Traffic']METRIC_COLS = ['Organic Keywords', 'Organic Traffic']DIMENSION_COLS = ['URL']DATE_COL = 'Date'PERIODS = [1, 3, 12] # 需要计算的历史周期(月)INPUT_CSV = """URL,Organic Keywords,Organic Traffic,Datehttps://www.example-url.com/,1315,11345,20231115https://www.example-url.com/,1183,5646,20231015https://www.example-url.com/,869,5095,20230915https://www.example-url.com/,925,4574,20230815https://www.example-url.com/,899,4580,20230715https://www.example-url.com/,1382,5720,20230615https://www.example-url.com/,1171,5544,20230515https://www.example-url.com/,1079,5041,20230415https://www.example-url.com/,734,3855,20230315https://www.example-url.com/,853,3455,20230215https://www.example-url.com/,840,2343,20230115https://www.example-url.com/,325,2318,20221215https://www.example-url.com/,156,1981,20221115https://www.example-url.com/,166,2059,20221015https://www.example-url.com/,124,1977,20220915https://www.example-url.com/,98,1919,20220815https://www.example-url.com/,167,1796,20220715https://www.example-url.com/,140,1596,20220615https://www.example-url.com/,168,1493,20220515https://www.example-url.com/,171,1058,20220415https://www.example-url.com/,141,1735,20220315https://www.example-url.com/,129,1836,20220215https://www.example-url.com/,141,746,20220115https://www.example-url.com/,129,1076,20211215"""## 辅助函数 - 获取指定历史周期的值及其变化def get_last_period_values(df, months_prior, metric_cols, dimension_cols, date_col):    df_copy = df.copy()    df_copy[f'{date_col}_Prior'] = df_copy[date_col] - pd.DateOffset(months=months_prior)    df_copy = df_copy.merge(        df_copy[[date_col] + dimension_cols + metric_cols],        left_on=[f'{date_col}_Prior'] + dimension_cols,        right_on=[date_col] + dimension_cols,        how='left',        suffixes=('', f'_{months_prior}mo_Prior')    )    df_copy = df_copy.drop(columns=[f'{date_col}_Prior'] + [col + f'_{months_prior}mo_Prior' for col in dimension_cols])    for metric in metric_cols:        df_copy[f'{metric}_{months_prior}mo_Abs_Change'] = df_copy[metric] - df_copy[f'{metric}_{months_prior}mo_Prior']        df_copy[f'{metric}_{months_prior}mo_Pct_Change'] = df_copy[metric] / df_copy[f'{metric}_{months_prior}mo_Prior'] - 1        df_copy[f'{metric}_{months_prior}mo_Pct_Change'] = df_copy[f'{metric}_{months_prior}mo_Pct_Change'].round(2)    return df_copy## 辅助函数 - 迭代计算多个历史周期的值def get_period_values(df, periods, metric_cols, dimension_cols, date_col):    df_copy = df.copy()    for period in periods:        df_copy = get_last_period_values(df_copy, period, metric_cols, dimension_cols, date_col)    return df_copy## 主脚本if __name__ == '__main__':    # 1. 读取数据    df = pd.read_csv(io.StringIO(INPUT_CSV))    # 2. 数据预处理    df = df[INITIAL_COL_REORDER]    df[DATE_COL] = pd.to_datetime(df[DATE_COL], format='%Y%m%d')    df = df.sort_values(by=DATE_COL, ascending=False) # 按日期降序排序    # 3. 计算历史同期值及变化    df_final = get_period_values(df, PERIODS, METRIC_COLS, DIMENSION_COLS, DATE_COL)    # 4. 显示结果    print("n最终结果 DataFrame:")    print(df_final.to_string()) # 使用 to_string() 避免截断显示

输出示例(部分):

最终结果 DataFrame:                         URL       Date  Organic Keywords  Organic Traffic  Organic Keywords_1mo_Prior  Organic Traffic_1mo_Prior  Organic Keywords_1mo_Abs_Change  Organic Traffic_1mo_Abs_Change  Organic Keywords_1mo_Pct_Change  Organic Traffic_1mo_Pct_Change  Organic Keywords_3mo_Prior  Organic Traffic_3mo_Prior  Organic Keywords_3mo_Abs_Change  Organic Traffic_3mo_Abs_Change  Organic Keywords_3mo_Pct_Change  Organic Traffic_3mo_Pct_Change  Organic Keywords_12mo_Prior  Organic Traffic_12mo_Prior  Organic Keywords_12mo_Abs_Change  Organic Traffic_12mo_Abs_Change  Organic Keywords_12mo_Pct_Change  Organic Traffic_12mo_Pct_Change0    https://www.example-url.com/ 2023-11-15              1315            11345                        1183                       5646                              132                             5699                             0.11                            1.01                         869                       5095                              446                             6250                             0.51                             1.23                          156                       1981                             1159                              9364                             7.43                             4.731    https://www.example-url.com/ 2023-10-15              1183             5646                         869                       5095                              314                              551                             0.36                             0.11                         925                       4574                              258                             1072                             0.28                             0.23                          166                       2059                             1017                              3587                             6.13                             1.74...22   https://www.example-url.com/ 2022-01-15               141              746                        129                       1076                               12                             -330                             0.09                            -0.31                          NaN                        NaN                               NaN                              NaN                                NaN                              NaN                          141                        746                                0                                0                             0.00                             0.0023   https://www.example-url.com/ 2021-12-15               129             1076                         NaN                        NaN                               NaN                              NaN                                NaN

以上就是使用Pandas计算历史同期值及变化率的通用方法的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1377425.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月14日 17:45:52
下一篇 2025年12月14日 17:46:20

相关推荐

  • 理解Go语言切片与接口:实现通用随机元素选择的挑战与泛型解决方案

    本文深入探讨了在Go语言中尝试使用[]interface{}实现通用切片随机元素选择时遇到的类型转换问题。我们将解释Go语言切片与接口的类型系统差异,展示传统Go语言中针对具体类型切片的简洁高效选择方法,并重点介绍Go 1.18+泛型如何提供类型安全且可复用的通用解决方案,同时强调处理空切片等注意事…

    2025年12月16日
    000
  • Golang如何测试错误返回值

    答案:Go测试中需检查error是否为nil或符合预期。通过errors.Is、strings.Contains等判断错误类型,用t.Errorf输出上下文,确保各类错误场景被有效覆盖。 在Go语言中,测试函数的错误返回值是保证代码健壮性的重要环节。关键在于调用被测函数后,检查其返回的 error …

    2025年12月16日
    000
  • Go语言中len内置函数的使用:正确获取数组与切片长度

    在Go语言中,获取数组、切片、字符串等集合类型的长度应使用内置的len函数,而非尝试调用x.len()方法。len是一个语言级别的函数,而非特定类型的方法,理解这一点是Go编程中的一个基础且重要概念,能有效避免常见的编译错误。 理解Go语言的len内置函数 go语言的设计哲学之一是简洁和一致性。为了…

    2025年12月16日
    000
  • Go语言中生成UUID的规范方法与实践

    本文旨在指导Go语言开发者如何规范、准确地生成通用唯一标识符(UUID)。文章首先分析了手动生成UUID的常见误区及其中位操作的含义,随后重点介绍了如何利用Google官方推荐的github.com/google/uuid库来生成符合RFC 4122标准的UUID,并通过示例代码展示了其简洁高效的使…

    2025年12月16日
    000
  • Go语言TCP连接的写超时与断开检测:原理与实践

    本文深入探讨了Go语言中TCP连接写操作的错误处理机制,特别是当客户端意外断开时TCPConn.Write和SetWriteDeadline行为的复杂性。我们将揭示TCP底层协议的工作原理,解释为何错误不会立即显现,并提供一个健壮的Go语言解决方案,通过连接状态管理和错误通道实现可靠的断开检测与消息…

    2025年12月16日
    000
  • Go语言中带缓冲通道的使用场景与实践

    Go语言的带缓冲通道提供了一种非阻塞的并发通信机制,允许发送者在接收者未准备好时将数据存入缓冲区,从而实现生产者与消费者之间的解耦。它特别适用于构建任务队列、平滑处理突发负载以及优化并发流程中的响应速度,是实现高效并发模式的关键工具。 1. 理解Go语言通道与并发通信 在go语言中,通道(chann…

    2025年12月16日
    000
  • Golang Kubernetes Pod资源限制与调度优化实践

    合理设置Golang应用的资源requests和limits可提升Kubernetes集群稳定性与调度效率。requests决定调度资源,limits防止资源滥用;Golang因GC和协程特性需特别关注内存与CPU配置,避免OOMKilled或性能下降。典型配置如memory: requests 6…

    2025年12月16日
    000
  • 如何在Golang中实现服务告警和通知

    答案:在Golang中实现服务告警需捕获panic、采集指标、健康检查并推送通知。通过defer+recover捕获异常,统一错误处理触发告警;使用Prometheus暴露请求延迟、错误数等指标,结合Alertmanager设置告警规则;集成钉钉、企业微信等Webhook接口发送通知;提供/heal…

    2025年12月16日
    000
  • Golang如何实现静态资源管理

    使用embed包将静态资源嵌入二进制文件,实现单一可执行文件部署。1. Go 1.16+推荐使用embed包,通过//go:embed指令嵌入assets/目录;2. 配合http.FileServer和http.FS提供服务,挂载至/static路径;3. 开发阶段可用http.Dir直接服务本地…

    2025年12月16日
    000
  • Golang 字符串索引:获取字符而非字节值

    在 Golang 中,字符串是一个不可变的字节序列,它使用 UTF-8 编码来表示 Unicode 字符。这意味着一个字符可能由一个或多个字节组成。直接使用索引操作符 [] 访问字符串中的元素时,实际上获取的是对应位置的字节值,而不是 Unicode 字符。这在处理包含非 ASCII 字符的字符串时…

    2025年12月16日
    000
  • 如何在Golang中实现简单FTP客户端

    使用github.com/jlaffaye/ftp库可实现Go语言FTP客户端,支持连接、登录、上传下载及目录操作。示例代码展示连接至服务器、认证、列出文件、上传test.txt并下载验证内容,最后关闭连接。注意FTP无加密,敏感数据应使用SFTP或FTPS。 在Golang中实现一个简单FTP客户…

    2025年12月16日
    000
  • 如何在Golang中使用bytes操作字节切片

    bytes包提供操作字节切片的高效函数,适用于处理二进制数据;包含查找(Contains、HasPrefix、HasSuffix)、比较(Equal)、搜索(Index)、替换(Replace)、分割(Split)、连接(Join)、大小写转换(ToLower/ToUpper)和修剪(TrimSpa…

    2025年12月16日
    000
  • Golang bytes字节操作与处理示例

    Go语言bytes包提供高效字节切片操作,支持比较、查找、替换、大小写转换、修剪、拼接及分割合并等功能,适用于二进制数据处理与字符串转换。通过bytes.Equal、bytes.Index、bytes.ReplaceAll、bytes.TrimSpace、bytes.ToUpper/ToLower、…

    2025年12月16日
    000
  • 如何使用Golang实现RPC客户端调用

    使用net/rpc实现Go的RPC调用,通过HTTP传输。1. 定义共享结构体和方法(如Multiply);2. 服务端注册实例并暴露HTTP服务;3. 客户端连接后调用远程方法;4. 运行服务端和客户端,输出7 * 8 = 56。需注意方法导出、参数规范及gob编码限制。 在Golang中实现RP…

    2025年12月16日
    000
  • I/O密集型程序优化示例

    答案:优化I/O密集型程序需减少等待时间并提高并发效率。示例中,同步下载多个网页时,传统方式逐个请求导致I/O空闲;采用异步I/O(asyncio + aiohttp)可在等待响应时发起其他请求,提升吞吐量;使用多线程(ThreadPoolExecutor)则适合阻塞式操作或不支持异步的场景。关键点…

    2025年12月16日
    000
  • 云原生应用安全策略与访问控制实践

    云原生安全需以身份为核心,采用零信任架构实现持续验证。通过OIDC对接身份提供商,结合RBAC、mTLS和OPA实施细粒度访问控制,利用eBPF与SIEM进行运行时监控,并在CI/CD中集成自动化策略检查,确保从代码到运行的全周期安全防护。 云原生应用的安全策略与访问控制是保障系统稳定运行和数据安全…

    2025年12月16日
    000
  • Golang Flyweight对象复用享元模式实践

    享元模式通过分离内部与外部状态实现对象复用,Go利用结构体和映射创建共享池,如样式对象被多个文本复用,减少内存开销,适用于大量细粒度对象场景,需注意并发安全与状态管理。 在Go语言开发中,当程序需要创建大量相似或重复的对象时,容易造成内存浪费和性能下降。享元模式(Flyweight Pattern)…

    2025年12月16日
    000
  • Golang单元测试日志轮转功能示例

    答案:使用lumberjack库结合log包实现日志轮转,通过单元测试验证日志写入、文件存在性及配置正确性,利用临时目录隔离确保测试纯净,重点检查MaxSize、MaxBackups、Compress等配置生效,并确认日志内容正确写入。 在 Go 语言中实现日志轮转功能时,通常会结合 lumberj…

    2025年12月16日
    000
  • Golang TemplateMethod模板方法模式流程控制示例

    答案:Go语言通过接口和组合实现模板方法模式,定义FileBuilder接口和Template结构体,封装构建文件的固定流程。具体步骤由JSONBuilder和XMLBuilder等实现,分别准备数据、生成内容并保存文件。在main函数中,Template实例复用Build()流程,依次调用不同构建…

    2025年12月16日
    000
  • Golang Web JSON数据解析与验证技巧

    使用结构体绑定JSON字段并结合validator库进行验证,可提升Go Web服务的健壮性。定义含json标签的结构体接收数据,通过json.NewDecoder解析请求体,利用validator.New().Struct()执行字段规则校验,对可选字段使用指针区分零值与未提供,最后封装统一错误响…

    2025年12月16日
    000

发表回复

登录后才能评论
关注微信