pandas性能百倍提升之用字典索引或ndarray替换DataFrame索引以及内存占用分析

62 篇文章 5 订阅
10 篇文章 1 订阅

       在利用pandas进行数据分析时,DataFrame是其基本的数据结构,当数据量较小时还好,一旦数据量较大,比如几十万上百万时,这时DataFrame就会变得笨重,笨重主要体现在对其索引的操作上,而对DataFrame的索引操作又是基本的操作,所以这时,在性能上就会有很大的损失;对pandas的使用可以让我们可以直观简单的进行数据分析,但是往往会在性能上有较大的损失。当然,对于性能的损失不能一概而论,pandas具有很多的内置函数,这些函数的底层很多是c语言的python封装,如果我们可以灵活的使用这些内置函数,那么性能上的损失其实是很小的,甚至会提升性能,只是有时候在一些比较复杂的操作中,并没有对应的内置函数供我们直接调用,这时,就需要从其他的角度去考虑提升性能。

性能对比和分析

       在数据分析中,往往最消耗时间的是在loop上,一旦数据量较大,比如一个50万行的DataFrame,如果我们需要进行逐行的loop,那么时间的消耗就相当于是一个loop的50万倍,这种量级的放大是很恐怖的,所以,当我们要不可避免的使用loop的时候,就需要非常的谨慎,尽量的减少一个loop需要消耗的时间。

       在pandas中,一个loop中比较消耗时间的地方往往有两方面:一是对很少的数据量用pandas的内置函数,起步时间消耗过多,对此可以转而用python原生的方式实现,具体可以看笔者的这篇文章;二是对DataFrame的索引操作上。本文就是解决第二个问题带来的时间消耗。

       pandas中对于DataFrame的索引操作是相对低效的,所以我们不应该在一个几十万的循环中使用DataFrame的索引操作,否则会造成程序效率极其低下。为了保持DataFrame的这种数据的相对结构,我们可以有两种方式去替换DataFrame的频繁的索引操作:一,替换成numpy的ndarray;二,替换成python的字典数据结构。下面我们通过一个简单的例子来对比下这三种方式的效率,如下所示。

import time
import pandas as pd
import numpy as np

df=pd.DataFrame(np.arange(800000).reshape(200000,4),columns=list('abcd'))
t1=time.time()
s1=df.apply(lambda x:x['a']+x['d'],axis=1)
t2=time.time()
print(t2-t1)

arr=df.values
t3=time.time()
s=np.apply_along_axis(lambda x:x[0]+x[3],axis=1,arr=arr)
t4=time.time()
print(t4-t3)

dic=df.to_dict()
l=[]
t5=time.time()
for i in range(len(df)):
    l.append(dic['a'][i]+dic['d'][i])
t6=time.time()
print(t6-t5)

# output: 10.034282922744751
#         1.6413774490356445
#         0.11075925827026367

       上述例子仅仅只是一个例子,只是为了传达本文的思想而已,因为实际上可以完全可以通过df[['a','d']].sum(axis=1)函数来快速实现。从例子中我们可以看到,三种方式的运行效率相差很大。首先第一种方式,我们是直接利用DataFrame的apply方法实现逐行的loop,对于每一个loop,通过直接对Series的索引来实现两列的相加;第二种方式,我们是先将DatFrame转为ndarray,然后在numpy中采取类似的做法;第三种方式,我们先将DataFrame转为python的字典对象,然后通过for loop实现,内一个loop中直接通过字典索引实现两列的相加。

       通过对比,第一种和第二种方式之间,后者相当于把DataFrame转为numpy的ndarray再进行处理,可知numpy的ndarray的效率更高,虽然pandas也是基于numpy的,但是由于pandas进行了进一步的封装,所以效率自然更低,因此,我们总是可以用numpy来处理比较耗时的pandas任务,特别是在数据量很大的时候,且无法通过内置函数直接办到的任务,那么numpy提升的效率可以很显著。我们再看第一种和第三种方式的对比,后者是先把DataFrame先转为字典,然后通过for loop实现,这里两者的区别还在于前者是apply,后者是for loop,但其实apply和for loop在非内置函数的简单操作上的效率是差不多的,甚至apply会更慢些,但是在pandas内置函数的操作上apply会快很多,具体可看笔者的这篇文章;所以这里两者的差距我们可以认为是由索引造成的,明显的,字典索引相对于DataFrame的索引,前者的效率会高很多,在python中,字典索引几乎是最为高效的索引方式了,因此,我们把DataFrame转为字典并对字典进行操作,这种方式提升的效率效果是最好的,性能几乎提升近百倍!

内存占用分析

        上面只是单纯的从性能上进行分析对比,下面还要对比一下这三种方式在内存占用上的区别。首先,对于DataFrame和ndarray,由于前者是基于后者的,因此两者的内存占用其实是产不多的,而且由于前者对后者进行了封装,所以严格来讲,DataFrame的内存占用会大一些,ndarray的内存占用会小一些,但是差别不大。由于ndarray是对内存结构进行了优化的,所以相比于python的字典对象,储存相同的信息,字典对象的内存占用会大很多,当然,这并不是说就是字典对象的缺点,因为字典对象可以存储不同类型的对象,而ndarray只可以存储同一类型的元素,因此,不同的设计方式使得两者在内存占用上不太一样,各有优劣。但是当我们通过把DataFrame转为dict时,相比于DataFrame,dict可储存多类型对象的优势不再,这时dict对象显著变大了,这就是其一个缺点了。具体的可看如下结果。

import sys

size=total_size(dic)
print(sys.getsizeof(df))
print(arr.nbytes)
print(size)

# output: 3200104
#         3200000
#         86715124

       可以看到,df占据了3200104字节,arr占据了3200000字节,而dic则占据了86715124字节。这里对于arr和dic不能直接用sys.getsizeof获取其实际内存,因为sys.getsizeof只能获取对象的本身的内存,如果对象是个容器,容器内部的内容内存占用是无法获取到的;字典实际上是个容器,而ndarray又有自己的内存设计,可以通过其nbytes属性获取,而df则兼容了sys.getsizeof这个接口。所以这里对于dic,笔者用的是total_size这个函数,关于该函数的定义,可以查看笔者的这篇文章

       Anyway,最后我们看到的是,字典占据的内存暴涨,相当于是df和ndarray的二十多倍。虽然字典在访问速度上极快,但是内存占用也是极高的,典型的用空间换时间。所以,当我们考虑性能的时候,也要兼考虑内存占用,特别是在大数据量的情况下,如果我们盲目的将df转为字典,那么可能内存就承受不住了。所以如果内存充分的足够,那么可以转为字典,但是如果内存并不是那么充裕,那么我们可以采用转为ndarray的方式去提高性能,因为ndarray的内存占用是三者里最佳的,而且在性能上也是很不错的,因此,在大数据量的情形下,考虑到内存占用,ndarray往往是一个更佳的选择!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值