整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

用Python数据分析时,他将Pandas循环提速7

用Python数据分析时,他将Pandas循环提速7万多倍

明 编译整理

量子位 报道 | 公众号 QbitAI

用Python和Pandas进行数据分析,很快就会用到循环。

但在这其中,就算是较小的DataFrame,使用标准循环也比较耗时。

遇到较大的DataFrame时,需要的时间会更长,会让人更加头疼。

现在,有人忍不了了。他是一位来自德国的数据分析师,名叫Benedikt Droste。

他说,当自己花了大半个小时等待代码执行的时候,决定寻找速度更快的替代方案。

在给出的替代方案中,使用Numpy向量化,与使用标准循环相比,速度提升了71803倍。



他是怎么实现的?我们一起来看看~

标准循环处理3年足球赛数据:20.7秒

DataFrame是具有行和列的Pandas对象。如果使用循环,需要遍历整个对象。

Python不能利用任何内置函数,而且速度很慢。在Benedikt Droste的提供的示例中,是一个包含65列和1140行的Dataframe,包含了2016-2019赛季的足球赛结果。

需要解决的问题是:创建一个新的列,用于指示某个特定的队是否打了平局。可以这样开始:

def soc_loop(leaguedf,TEAM,):
 leaguedf['Draws']=99999
 for row in range(0, len(leaguedf)):
 if ((leaguedf['HomeTeam'].iloc[row]==TEAM) & (leaguedf['FTR'].iloc[row]=='D')) | \
 ((leaguedf['AwayTeam'].iloc[row]==TEAM) & (leaguedf['FTR'].iloc[row]=='D')):
 leaguedf['Draws'].iloc[row]='Draw'
 elif ((leaguedf['HomeTeam'].iloc[row]==TEAM) & (leaguedf['FTR'].iloc[row] !='D')) | \
 ((leaguedf['AwayTeam'].iloc[row]==TEAM) & (leaguedf['FTR'].iloc[row] !='D')):
 leaguedf['Draws'].iloc[row]='No_Draw'
 else:
 leaguedf['Draws'].iloc[row]='No_Game'



在这个案例中是阿森纳,在实现目标之前要确认阿森纳参加了哪些场比赛,是主队还是客队。但使用标准循环非常慢,执行时间为20.7秒。

那么,怎么才能更有效率?

Pandas 内置函数: iterrows ()ー快321倍

在第一个示例中,循环遍历了整个DataFrame。iterrows()为每一行返回一个Series,它以索引对的形式遍历DataFrame,以Series的形式遍历感兴趣的列。这使得它比标准循环更快:

def soc_iter(TEAM,home,away,ftr):
 #team, row['HomeTeam'], row['AwayTeam'], row['FTR']
 if [((home==TEAM) & (ftr=='D')) | ((away==TEAM) & (ftr=='D'))]:
 result='Draw'
 elif [((home==TEAM) & (ftr !='D')) | ((away==TEAM) & (ftr !='D'))]:
 result='No_Draw'
 else:
 result='No_Game'
 return result



代码运行时间为68毫秒,比标准循环快321倍。但是,许多人建议不要使用它,因为仍然有更快的选项,而且iterrows()不能跨行保存dtype。

这意味着,如果你在DataFrame dtypes上使用iterrows(),可以更改它,但这会导致很多问题。

一定要保存dtypes的话,你还可以使用itertuples()。这里我们不详细讨论 ,你可以在这里找到官方文件:

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.itertuples.html

apply ()方法ー快811倍

apply 本身并不快,但与DataFrame结合使用时,它具有优势。这取决于 apply 表达式的内容。如果可以在 Cython 空间中执行,那么apply要快得多,这里的示例就是这种情况。

大家可以在Lambda函数中使用apply。所要做的就是指定这个轴。在本文的示例中,想要执行按列操作,要使用 axis 1:



这段代码甚至比之前的方法更快,完成时间为27毫秒。

Pandas向量化—快9280倍

此外,也可以利用向量化的优点来创建非常快的代码。

重点是避免像之前的示例中的Python级循环,并使用优化后的C语言代码,这将更有效地使用内存。只需要稍微修改一下函数:

def soc_iter(TEAM,home,away,ftr):
 df['Draws']='No_Game'
 df.loc[((home==TEAM) & (ftr=='D')) | ((away==TEAM) & (ftr=='D')), 'Draws']='Draw'
 df.loc[((home==TEAM) & (ftr !='D')) | ((away==TEAM) & (ftr !='D')), 'Draws']='No_Draw'

现在,可以用 Pandas 列作为输入创建新列:



在这种情况下,甚至不需要循环。所要做的就是调整函数的内容。现可以直接将Pandas 列传递给函数,从而获得巨大的速度增益。

Numpy向量化—快71803倍

在上面的示例中,将将Pandas 列传递给函数。通过添加.values,可以得到一个Numpy数组:



因为引用了局部性的好处,Numpy数组的速度非常快,代码运行时间仅为0.305毫秒,比一开始使用的标准循环快71803倍。

谁更强一目了然

最后,Benedikt Droste对上述方案进行了总结。

他说,如果你使用Python、Pandas和Numpy进行数据分析,总会有改进代码的空间。

在对上述五种方法进行比较之后,哪个更快一目了然:



从这个图中,可以得出两个结论:

  • 1、如果要使用循环,则应始终选择apply方法。
  • 2、否则,使用向量化是最好的,因为它更快!

原文链接:

https://towardsdatascience.com/how-to-make-your-pandas-loop-71-803-times-faster-805030df4f06

诚挚招聘

量子位正在招募编辑/记者,工作地点在北京中关村。期待有才气、有热情的同学加入我们!相关细节,请在量子位公众号(QbitAI)对话界面,回复“招聘”两个字。

量子位 QbitAI · 头条号签约作者

?'?' ? 追踪AI技术和产品新动态

读:Pandas是一个基于Numpy库开发的更高级的结构化数据分析工具,提供了Series、DataFrame、Panel等数据结构,可以很方便地对序列、截面数据(二维表)、面板数据进行处理。

作者:张秋剑 张浩 周大川 常国珍

来源:华章科技

  • DataFrame是我们常见的二维数据表,包含多个变量(列)和样本(行),通常被称为数据框。
  • Series是一个一维结构的序列,包含指定的索引信息,可以被视作DataFrame中的一列或一行。其操作方法与DataFrame十分相似。
  • Panel是包含序列及截面信息的三维结构,通常被称为面板数据。

我们可通过限定时间ID和样本ID获得对应的Series和DataFrame。

由于这些对象的常用操作方法十分相似,因此本文主要使用DataFrame进行演示。

01 读取文件

Pandas库提供了便捷读取本地结构化数据的方法。这里主要以csv数据为例,read_csv函数可以读取csv数据,代码如下:

import pandas as pd
csv=pd.read_csv('data/sample.csv')
csv
id name scores
0 1 小明 78.0
1 2 小红 87.0
2 3 小白 99.0
3 4 小青 99999.0
4 5 小兰 NaN

按照惯例,Pandas会以pd为别名,以read_csv函数读取指定路径下的文件,然后返回一个DataFrame对象。如果在命令行中打印DataFrame对象,可读性可能会略差一些;如果在Jupyter Notebook中打印的话,可读性会大幅提升。

打印出来的DataFrame包含索引(第一列),列名(第一行)及数据内容(除第一行和第一列之外的部分)。

此外,read_csv函数有很多参数可以设置,如下所示。

  • filepath_or_buffer csv文件的路径
  • sep=',' 分隔符,默认为逗号
  • header=0 int类型,0代表第一行为列名,若设定为None将使用数值列名
  • names=[] list,重新定义列名,默认为None
  • usecols=[] list,定义读取的列,设定后将缩短读取数据的时间,并减小内存消耗,适合读取大量数据,默认为None
  • dtype={} dict,定义读取列的数据类型,默认为None
  • nrows=None int类型,指定读取数据的前n行,默认为None
  • na_values=... str类型,list或dict,指定缺失值的填充值
  • na_filter=True bool类型,自动发现数据中的缺失值,默认值为True,若确定数据无缺失,可以设定值为False,以提高数据载入的速度
  • chunksize=1000 int类型,分块读取,当数据量较大时,可以设定分块读取的行数,默认为None
  • encoding='utf-8' str类型,数据的编码,Python3默认编码为UTF-8,Python2默认编码为ASCII

Pandas除了可以直接读取csv、excel、json、html等文件生成的DataFrame,也可以在列表、元组、字典等数据结构中创建DataFrame。

02 读取指定行和指定列

使用参数usecol和nrows读取指定的列和前n行,这样可以加快数据读取速度。读取原数据的两列、两行示例如下。

csv=pd.read_csv('data/sample.csv',\
usecols=['id','name'],\
nrows=2) #读取'id'和'name'两列,仅读取前两行
csv
id name
0 1 小明
1 2 小红

03 分块读取

参数chunksize可以指定分块读取的行数,并返回一个可迭代对象。这里,big.csv是一个4500行、4列的csv数据,设定chunksize=900,分5块读取数据,每块900行,4个变量,如下所示:

csvs=pd.read_csv('data/big.csv',chunksize=900)
for i in csvs:
print (i.shape)
(900, 4)
(900, 4)
(900, 4)
(900, 4)
(900, 4)

可以使用pd.concat函数读取全部数据:

csvs=pd.read_csv('data/big.csv',chunksize=900)
dat=pd.concat(csvs,ignore_index=True)
dat.shape
(4500, 4)

04 将不合理数据读取为缺失值

在数据sample.csv中,“小青”的分数中有的取值为99999,这里令其读取为缺失值,操作如下:

csv=pd.read_csv('data/sample.csv',
na_values='99999')
csv
id name scores
0 1 小明 78.0
1 2 小红 87.0
2 3 小白 99.0
3 4 小青 NaN
4 5 小兰 NaN

05 以指定编码方式读取

读取数据时,乱码情况经常出现。这里需要先弄清楚原始数据的编码形式,再以指定的编码形式读取,例如sample.csv编码为UTF-8,这里以指定编码(参数encoding)方式读取。

csv=pd.read_csv('data/sample.csv',
encoding='utf-8')
csv
id name scores
0 1 小明 78.0
1 2 小红 87.0
2 3 小白 99.0
3 4 小青 99999.0
4 5 小兰 NaN

关于作者:张秋剑,就职于腾讯云金融拓展中心,从事微信财富营销管理、数据中台、AI应用等解决方案拓展工作,研究方向包括数字化转型、创新实践等。

张浩,曾任腾讯云金融首席架构师和星环科技金融行业技术总监,主要从事大数据、人工智能、云计算、区块链、联邦学习等相关技术研发与产品设计,具有丰富的企业架构设计、企业数字化战略转型运营与业务咨询经验。

周大川,就职于某中央金融企业金融科技研发中心,主要从事企业级数据平台开发、核心业务平台建设、AI赋能金融科技创新等工作,具有丰富的新一代金融业务系统建设经验。

常国珍,曾任毕马威咨询大数据总监,具有近20年数据挖掘、精益数据治理、数字化运营咨询经验,是金融信用风险、反欺诈和反洗钱算法领域的专家。

本文摘编自《金融商业数据分析:基于Python和SAS》,经出版方授权发布。(ISBN:9787111695837)

《金融商业数据分析:基于Python和SAS》

推荐语:腾讯云等资深数据架构师、商业分析师20年经验,全流程讲解金融数据分析思路、方法、技巧,快速入门到精通。

来看一个实例问题。

?如下销售数据中展现了三笔订单,每笔订单买了多种商品,求每种商品销售额占该笔订单总金额的比例。例如第一条数据的最终结果为:235.83 / (235.83+232.32+107.97)=40.93%。

后台回复“transform”获取本文全部代码和pdf版本。

思路一:

常规的解法是,先用对订单id分组,求出每笔订单的总金额,再将源数据和得到的总金额进行“关联”。最后把相应的两列相除即可。相应的代码如下:

1.对订单id分组,求每笔订单总额。由于有三个order,因此最终会产生三条记录表示三个总金额。

2.数据关联合并

为了使每行都出现相应order的总金额,需要使用“左关联”。我们使用源数据在左,聚合后的总金额数据在右(反过来也可)。不指定连接key,则会自动查找相应的关联字段。由于是多行对一行的关联,关联上的就会将总金额重复显示多次,刚好符合我们后面计算的需要。结果如上图所示。

3.计算占比

有了前面的基础,就可以进行最终计算了:直接用商品金额ext_price除以订单总额sum_price。并赋值给新的列pct即可。

4.格式调整

为了美观,可以将小数形式转换为百分比形式,自定义函数即可实现。

思路二:

对于上面的过程,pandas中的transform函数提供了更简洁的实现方式,如下所示:

可以看到,这种方法把前面的第一步和第二步合成了一步,直接得到了sum_price列。这就是transform的核心:作用于groupby之后的每个组的所有数据。可以参考下面的示意图帮助理解:

后面的步骤和前面一致。

这种方法在需要对多列分组的时候同样适用。

多列分组使用transform

为演示效果,我们虚构了如下数据,id,name,cls为维度列。

我们想求:以(id,name,cls)为分组,每组stu的数量占各组总stu的比例。使用transform处理如下:

同样再次计算占比和格式化,得到最终结果:

总结transform的用法

transform函数的官方文档签名为:DataFrame.transform(func,axis=0,*args,**kwargs),表示调用func函数进行转换,返回转换后的值,且返回值与原来的数据在相同的轴上具有相同的长度。func可以是函数,字符串,列表或字典。具体可以参考官方文档:

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.transform.html#pandas.DataFrame.transform。

transform既可以和groupby一起使用,也可以单独使用。

1.单独使用

此时,在某些情况下可以实现和apply函数类似的结果。

2.与groupby一起使用

此时,transform函数返回与原数据一样数量的行,并将函数的结果分配回原始的dataframe。也就是说返回的shape是(len(df),1)。本文开头的例子就是这样。而apply函数返回聚合后的行数。例如:

transform和apply的另一个区别是,apply函数可以同时作用于多列,而transform不可以。下面用例子说明:

上图中的例子,定义了处理两列差的函数,在groupby之后分别调用apply和transform,transform并不能执行。如果不采用groupby,直接调用,也会有问题,参见下面的第二种调用方式。

第三种调用调用方式修改了函数,transform依然不能执行。以上三种调用apply的方式处理两列的差,换成transform都会报错。

利用transform填充缺失值

transform另一个比较突出的作用是用于填充缺失值。举例如下:

在上面的示例数据中,按照name可以分为三组,每组都有缺失值。用平均值填充是一种处理缺失值常见的方式。此处我们可以使用transform对每一组按照组内的平均值填充缺失值。

小结:

transform函数经常与groupby一起使用,并将返回的数据重新分配到每个组去。利用这一点可以方便求占比和填充缺失值。但需要注意,相比于apply,它的局限在于只能处理单列的数据。

后台回复“transform”获取本文全部代码和pdf版本。

Reference:

https://www.codenong.com/19966018/

https://www.cnblogs.com/junge-mike/p/12761227.html

https://blog.csdn.net/qq_40587575/article/details/81204514

https://pbpython.com/pandas_transform.html