Pandas的apply, map, transform介绍和性能测试

news2024/9/20 9:22:35

apply函数是我们经常用到的一个Pandas操作。虽然这在较小的数据集上不是问题,但在处理大量数据时,由此引起的性能问题会变得更加明显。虽然apply的灵活性使其成为一个简单的选择,但本文介绍了其他Pandas函数作为潜在的替代方案。

在这篇文章中,我们将通过一些示例讨论apply、agg、map和transform的预期用途。

我们一个学生分数为例

 df_english=pd.DataFrame(
     {
         "student": ["John", "James", "Jennifer"],
         "gender": ["male", "male", "female"],
         "score": [20, 30, 30],
         "subject": "english"
     }
 )
 
 df_math=pd.DataFrame(
     {
         "student": ["John", "James", "Jennifer"],
         "gender": ["male", "male", "female"],
         "score": [90, 100, 95],
         "subject": "math"
     }
 )
 df=pd.concat(
     [df_english, df_math],
     ignore_index=True
 )

map

 Series.map(arg, na_action=None) -> Series

map方法适用于Series,它基于传递给函数的参数将每个值进行映射。arg可以是一个函数——就像apply可以取的一样——也可以是一个字典或一个Series。

na_action是指定序列的NaN值如何处理。当设置为"ignore "时,arg将不会应用于NaN值。

例如想用映射替换性别的分类表示时:

 GENDER_ENCODING= {
     "male": 0,
     "female": 1
 }
 df["gender"].map(GENDER_ENCODING)

虽然apply不接受字典,但也可以完成同样的操作。

 df["gender"].apply(lambdax:
     GENDER_ENCODING.get(x, np.nan)
 )

性能对比

在对包含一百万条记录的gender序列进行编码的简单测试中,map比apply快10倍。

 random_gender_series=pd.Series([
     random.choice(["male", "female"]) for_inrange(1_000_000)
 ])
 
 random_gender_series.value_counts()
 
 """
 >>>
 female    500094
 male      499906
 dtype: int64
 """

看看对比结果

 """
 map performance
 """
 %%timeit
 random_gender_series.map(GENDER_ENCODING)
 
 # 41.4 ms ± 4.24 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
 
 """
 apply performance
 """
 %%timeit
 random_gender_series.apply(lambdax:
     GENDER_ENCODING.get(x, np.nan)
 )
 
 # 417 ms ± 5.32 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

因为map也可以接受函数,所以任何不依赖于其他元素的转换操作都可以使用。比如使用map(len)或map(upper)这样的东西可以让预处理变得更容易。

applymap

 DataFrame.applymap(func, na_action=None, **kwargs) -> DataFrame

applymap与map非常相似,并且是使用apply内部实现的。applymap就像map一样,但是是在DataFrame上以elementwise的方式工作,但由于它是由apply内部实现的,所以它不能接受字典或Series作为输入——只允许使用函数。

 try: 
     df.applymap(dict())
 
 exceptTypeErrorase:
     print("Only callables are valid! Error:", e)
 
 """
 Only callables are valid! Error: the first argument must be callable
 """

na_action的工作原理和map中的一样。

transform

 DataFrame.transform(func, axis=0, *args, **kwargs) -> DataFrame

前两个函数工作在元素级别,而transform工作在列级别。我们可以通过transform来使用聚合逻辑。

假设要标准化数据:

 df.groupby("subject")["score"] \
     .transform(
         lambdax: (x-x.mean()) /x.std()
     )
 
 """
 0   -1.154701
 1    0.577350
 2    0.577350
 3   -1.000000
 4    1.000000
 5    0.000000
 Name: score, dtype: float64

我们需要做的是从每个组中获取分数,并用其标准化值替换每个元素。这肯定不能用map来实现,因为它需要按列计算,而map只能按元素计算。

如果使用熟悉apply,那么实现很简单。

 df.groupby("subject")["score"] \
     .apply(
         lambdax: (x-x.mean()) /x.std()
     )
 
 """
 0   -1.154701
 1    0.577350
 2    0.577350
 3   -1.000000
 4    1.000000
 5    0.000000
 Name: score, dtype: float64
 """

不仅本质上,代码基本上都是一样的。那么transform有什么意义呢?

Transform必须返回一个与它所应用的轴长度相同的数据框架。

也就是说即使transform与返回聚合值的groupby操作一起使用,它会将这些聚合值赋给每个元素。

例如,假设我们想知道每门课所有学生的分数之和。我们可以像这样使用apply:

 df.groupby("subject")["score"] \
     .apply(
         sum
     )
 
 """
 subject
 english     80
 math       285
 Name: score, dtype: int64
 """

但我们按学科汇总了分数,丢失了学生个体与其分数之间的关联信息。用transform做同样的事情,我们会得到更有趣的东西:

 df.groupby("subject")["score"] \
     .transform(
         sum
     )
 
 """
 0     80
 1     80
 2     80
 3    285
 4    285
 5    285
 Name: score, dtype: int64
 """

因此,尽管我们在分组上操作,但仍然能够得到组级信息与行级信息的关系。

所以任何形式的聚合都会报错,如果逻辑没有返回转换后的序列,transform将抛出ValueError。

 try:
     df["score"].transform("mean")
 exceptValueErrorase:
     print("Aggregation doesn't work with transform. Error:", e)
 
 """
 Aggregation doesn't work with transform. Error: Function did not transform
 """

而Apply的灵活性确保它即使使用聚合也能很好地工作。

 df["score"].apply("mean")
 
 """
 60.833333333333336
 """

性能对比

就性能而言,transform的速度是apply的2倍。

 random_score_df=pd.DataFrame({
     "subject": random.choices(["english", "math", "science", "history"], k=1_000_000),
     "score": random.choices(list(np.arange(1, 100)), k=1_000_000)
 })

 """
 Transform Performance Test
 """
 %%timeit
 random_score_df.groupby("subject")["score"] \
     .transform(
         lambdax: (x-x.mean()) /x.std()
     )
 
 """
 202 ms ± 5.37 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
 """
 """
 Apply Performance Test
 """
 %%timeit
 random_score_df.groupby("subject")["score"] \
     .apply(
         lambdax: (x-x.mean()) /x.std()
     )
 
 """
 401 ms ± 5.37 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
 """

agg

 DataFrame.agg(func=None, axis=0, *args, **kwargs) 
     -> scalar | pd.Series | pd.DataFrame

agg函数更容易理解,因为它只是返回传递给它的数据的聚合。所以无论自定义聚合器是如何实现的,结果都将是传递给它的每一列的单个值。

来看看一个简单的聚合——计算每个组在得分列上的平均值。

 df.groupby("subject")["score"].agg(mean_score="mean").round(2)

多个聚合器也可以作为列表传递。

 df.groupby("subject")["score"].agg(
     ["min", "mean", "max"]
 ).round(2)

Agg提供了更多执行聚合的选项。我们还可以构建自定义聚合器,并对每一列执行多个特定的聚合,例如计算一列的平均值和另一列的中值。

性能对比

就性能而言,agg比apply稍微快一些,至少对于简单的聚合是这样。

 random_score_df=pd.DataFrame({
     "subject": random.choices(["english", "math", "science", "history"], k=1_000_000),
     "score": random.choices(list(np.arange(1, 100)), k=1_000_000)
 })

 """
 Agg Performance Test
 """
 
 %%timeit
 random_score_df.groupby("subject")["score"].agg("mean")
 
 """
 74.2 ms ± 5.02 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
 """
 
 """
 Apply Performance Test
 """
 
 %%timeit
 random_score_df.groupby("subject")["score"].apply(lambda x: x.mean())
 """
 102.3 ms ± 1.16 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
 """

可以看到大约30%的性能提升。当对多个聚合进行测试时,我们会得到类似的结果。

 """
 Multiple Aggregators Performance Test with agg
 """
 %%timeit
 random_score_df.groupby("subject")["score"].agg(
     ["min", "mean", "max"]
 )
 
 """
 90.5 ms ± 16.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
 """
 """
 Multiple Aggregators Performance Test with apply
 """
 %%timeit
 random_score_df.groupby("subject")["score"].apply(
     lambda x: pd.Series(
         {"min": x.min(), "mean": x.mean(), "max": x.max()}
     )
 ).unstack()
 
 """
 104 ms ± 5.78 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
 """

apply

我们用它是因为它灵活。上面的每个例子都可以用apply实现,但这种灵活性是有代价的:就像性能测试所证明的那样,它明显变慢了。

apply的一些问题

apply灵活性是非常好的,但是它也有一些问题,比如

从 2014 年开始,这个问题就一直困扰着 pandas。当整个列中只有一个组时,就会发生这种情况。 在这种情况下,即使 apply 函数预期返回一个Series,但最终会产生一个DataFrame。

结果类似于额外的拆栈操作。 我们这里尝试重现它。 我们将使用我们的原始数据框并添加一个城市列。 假设我们的三个学生 John、James 和 Jennifer 都来自波士顿。

 df_single_group=df.copy()
 df_single_group["city"] ="Boston"

让我们计算两组组的组均值:一组基于subject 列,另一组基于city。

在subject 列上分组,我们得到了我们预期的多索引。

 df_single_group.groupby("subject").apply(lambda x: x["score"])

但当我们按city列分组时,只有一个组(对应于“波士顿”),我们得到:

 df_single_group.groupby("city").apply(lambda x: x["score"])

看到结果是如何旋转的吗?如果我们把这些叠起来,我们就会得到预期的结果。

 df_single_group.groupby("city").apply(lambda x: x["score"]).stack()

在撰写本文时,这个问题仍然没有得到解决。

总结

apply提供的灵活性使其在大多数场景中成为非常方便的选择,所以如果你的数据不大,或者对处理时间没有硬性的要求,那就直接使用apply吧。如果真的对时间有要求,还是找到优化的方式来操作,这样可以省去大量的时间。

本文代码:
https://avoid.overfit.cn/post/9917bf07402b473c909248dbb5cdebef

作者:Aniruddha Karajgi

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/197147.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软测(基础)· 软件测试的生命周期 · 如何描述一个 Bug · Bug 的级别 · Bug 的生命周期 · 争执 · Bug 评审

一、软件测试的生命周期软件测试的生命周期 & 软件开发的生命周期二、如何描述一个 Bug三、如何定义 Bug 的级别四、Bug 的生命周期五、发生争执了怎么办?Bug 评审一、软件测试的生命周期 软件测试的生命周期:需求分析 → 测试计划 → 测试设计、测…

《巫师3:狂猎》4.01版更新 PC端已上线

去年12月,《巫师3》免费升级次世代版,加入DLSS 3支持,RTX 40系显卡的用户能直接提升体验感,RTX 30系用户能通过DLSS 2获得更稳定的帧数。 目前。《巫师3:狂猎》4.01版已更新上线,在PC、PlayStation 和 Xbo…

【配电网规划】配电网N-1扩展规划研究(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

OJ万题详解––[NOIP2010 提高组] 机器翻译(C++详解)

题目背景 小晨的电脑上安装了一个机器翻译软件,他经常用这个软件来翻译英语文章。 题目描述 这个翻译软件的原理很简单,它只是从头到尾,依次将每个英文单词用对应的中文含义来替换。对于每个英文单词,软件会先在内存中查找这个单词…

openstack cinder对接两个ceph后端配置

需求 需要做卷迁移的工作,从一个ceph集群迁移到另一个集群,因此需要配置两个ceph后端。由此开展后续工作,将配置过程及出现的问题做一记录。 另外两套ceph后端的访问用户都是cinder用户,网上找的资料均为两个用户,当为…

电子技术——BJT的物理结构

电子技术——BJT的物理结构 本节我们介绍另一种基本三端元件,BJT。 物理结构 下图展示了NPN型和PNP型BJT的物理结构简图。 从图中看出,BJT主要由三个区域组成,发射极(n类型),基极(p类型&#…

如何跑起一个Python Flask 项目

最近做项目迁移,从Google cloud 迁移到 AWS项目:Python Flask ORM是Alembic(我不是搞python的 这边看到这个了)python 是docker 跑起来的,一个docker-compose up就完事但我要进行数据库迁移测试,所以本地要跑起来我是mac先安装pyt…

财报解读:大裁员后Meta的元宇宙还有新故事吗?

美股科技巨头Facebook自更名为Meta Platforms后全面发力元宇宙,作为美国第一大社交平台以及全球流量池,转型后的Meta一度被市场寄予厚望,但同样受累于其元宇宙策略,年初至今,Meta的股价累计一度下跌近65%,也…

【超详细】一文看懂如何在PyCharm中集成Git

PyCharm环境集成Git 当我们在官网下载好Git后,按照要求进行安装,就可以通过快捷方式对本地仓库进行版本控制啦。但是这种方式处理整个工作环境还是比较麻烦的,接下来,我们将在PyCharm环境中配置Git。 基础配置 在设置中&#xf…

IPV6基本了解

参考:https://support.huawei.com/enterprise/zh/doc/EDOC1100116138#ZH-CN_TOPIC_0204809629, https://www.w3cschool.cn/ipv6/ipv6_address_types.html IPv6地址结构 和IPv4的10进制的表示方式不同,IPv6使用的是16进制的表示方式。 首先基…

FreeRTOS内存管理

内存管理是一个系统基本组成部分,FreeRTOS 中大量使用到了内存管理,比 如创建任务、信号量、队列等会自动从堆中申请内存。用户应用层代码也可以使 用 FreeRTOS 提供的内存管理函数来申请和释放内存。本章要实现的功能是:使 用 heap_4.c 方案…

剑指Offer 第21天 不用加减乘除做加法 二进制中1的个数

剑指 Offer 65. 不用加减乘除做加法 写一个函数&#xff0c;求两个整数之和&#xff0c;要求在函数体内不得使用 “”、“-”、“*”、“/” 四则运算符号。 int add(int a, int b) {while(b ! 0){unsigned int c (unsigned)(a & b)<<1;a a ^ b;b c;}return a;} 剑…

Linux安装Mysql5.5

链接&#xff1a;https://pan.baidu.com/s/146KA6VfB4NW6mWSRRwXsMg 提取码&#xff1a;ib17 rpm安装Mysql5.5 检测Mysql是否安装 强制卸载原来的Mysql 安装Mysql服务端 安装Mysql客户端 启动Mysql------> service mysql start 连接Mysql------->mysql -u ro…

时序数据库

时序数据库(TSDB) 接下来就到了&#xff0c;自己所适应行业的数据库了&#xff0c;时许数据库&#xff0c;这类对物联网传感器数据有着很好的支持。 https://blog.csdn.net/firewater23/article/details/125697248 时序数据是随时间不断产生的一系列数据&#xff0c;简单来说…

AD936x_增益控制AGC详解

增益控制概述 所有AGC模式都可用于TDD和FDD场景。AD936x具有手动增益控制选项&#xff0c;允许基带处理器控制接收机的增益。 上图为AD936x接收信号路径示意图&#xff0c;每个接收机都有自己的增益表&#xff0c;将增益控制字映射到每个可变增益块。无论使用AGC还是手动增益控…

ABAP IDOC 测试及使用相关事务代码

WE02:查看IDOC日志和清单 WE19:测试IDOC 可以进入debug模式 WE20:维护伙伴的一些属性&#xff0c;比如如果加了增强结构&#xff0c;在这里可以增加 WE30:查看并且修改IDOC types 结构 WE31:查看SEGMENT 内的字段和版本。也可以新建segment WE82: 新增输出类型和assignment…

中间件Canal之Canal简单使用

一. 简单介绍 Canal是Java开发的基于数据库增量日志解析&#xff0c;提供增量数据订阅&消费的中间件。目前&#xff0c;Canal主要支持了MySQL的Binlog解析&#xff0c;解析完成后才能利用Canal Client来处理获得的相关数据。 二. MySQL的Binlog 2.1. Binlog是什么&#…

代码随想录算法训练营第37天 回溯算法 java :134. 加油站 135. 分发糖果 1005.K次取反后最大化的数组和

文章目录LeetCode 134. 加油站思路AC代码LeetCode135. 分发糖果思路AC代码LeetCode 1005.K次取反后最大化的数组和思路AC代码总结LeetCode 134. 加油站 思路 两个数组一个是 增加汽油量 gas[ ] 一个耗费汽油量 cost[ ] 可以换一个思路&#xff0c;首先如果总油量减去总消耗大…

OpenStack Yoga安装使用kolla-ansible

基本上是按照官网文档快速入门进行安装&#xff0c;不过还有很多地方需要换源。重点在换源这块。如果说你的网关有魔法&#xff0c;那就不用看这篇文章了&#xff0c;直接复制官网命令安装。 支持的操作系统 注意&#xff1a;不再支持 CentOS 7 作为主机操作系统。Train 版本同…

Java 的 IDEA 神级插件!

安装插件 1. Codota 代码智能提示插件 只要打出首字母就能联想出一整条语句&#xff0c;这也太智能了&#xff0c;还显示了每条语句使用频率。原因是它学习了我的项目代码&#xff0c;总结出了我的代码偏好。 如果让它再加上机器学习&#xff0c;人工智能写代码的时代还会远吗…