pandas代码优化--"大"数据算法效率(一) (tag:升维度,遍历,循环,内置函数,数据结构)

目录:

1. 前言
2. 概览
3. 肮脏代码与相对改良(例)
4. @pyspark,SQL优化…
在这里插入图片描述

1.前言

笔者小白,从事SAAS开发工作(其实是个写脚本的初级菜鸟),在工作中饱受挫折,于是下定决心,更新自己操作中的心德,一来可以分享给其他朋友让大家避免我的歧途,二来可以随时总结,加深记忆。文章如有问题,欢迎大神及时指正,互相学习进步~!

2.概览

当下主流操作亿兆级别大数据主要还是Apache他们家的Spark,Hadoop等,通过内存直接运算数据,速度明显优于其他方式。但是很多情况下,我们还是老老实实写一些本职工作的东西(毕竟也就只有千万级别数据),所以要求我们满足客户需求的前提,准确的前提下,提高程序运行效率(时间、空间),保持代码整洁干净,方便他人阅读

3. 肮脏代码(例)【A为差,B为好】

我刚开始接触的时候,写的一坨SHI(虽然现在也不是很干净),具体表现为,胡乱定义变量,重复的function,重复语句,重复定义赋值,满篇循环,通篇遍历,简直辣眼睛,下面请“欣赏”!
I-A.“无限月读(循环)"

def stage0():
    for i in range(len(df_NEW2['StoreCode'])):
        if pd.isnull(df_NEW2.loc[i,'CloseDate_y']) and df_NEW2.loc[i,'OpenDate']>DAY1 and df_NEW2.loc[i,'OpenDate']<=DAY2:
            num11=((df_NEW2.loc[i,'OpenDate']-DAY1).days)//7
            num1_1=(num11)%7
            if num11==52:
                df_NEW2.loc[i,12:]=0
            if num11==0:
                df_NEW2.loc[i,12:]=7
            else:
                for j in range(num11):
                    df_NEW2.loc[i,str('week')+str(j+1)]=0
                df_NEW2.loc[i,str('week')+str(num11+1)]=7-num1_1
                for t in range(num11+1,Weeks):
                    df_NEW2.loc[i,str('week')+str(t+1)]=7
        else:
            pass
    return df_NEW2

首先说这样写可不可以达到预期的结果呢?答案是可以的。但是会很耗时。我记得当时光跑这一段,就花费了将近1分钟。很多时候设计的程序,需要很快的运行速度,才能给用户最好的体验。设想下,如果你点一下“提交”按钮,网页卡了一个小时不能动的尴尬场景,估计5秒钟就烦了吧。
昂,回归正题,第一种恶心的写法就是过多的循环,看下这段代码遍历了多少次,时间复杂度是2n^2+2,大概是n方级别的,这很可怕,如果需要读取的主键元素很多很多,电脑就会直接崩掉。
尽量减少不必要的遍历,会极大的优化程序的运行效率。

I-B.减少不必要的遍历

其实减少不要的遍历,可以做到以下几点:
(1)多用py的内置函数。python毕竟是高级语言,写源代码固然锻炼码子能力,可是很多时候多是徒劳,遇到情况可以先check(github, csdn,简书,博客园,身边大神…)下有无更加方便的思路、语法是你实现不了解的,这样既节省时间,还提高效率。python的内置函数远远优于你自己在Py上写源代码,之前学了很多排序算法,什么冒泡排序,希尔排序,插入排序,堆排序…其实内置语法一句话sorted()就解决了(除非你需要针对数组去做运算,或者对稳定性没有要求)。如果是用Pandas, 那么更加不需要自己去写算法。
(2)计量避免针对元素计算,选择分模块整体运算大大提升效率。比如遍历一列中每一个元素去做数学运算,那比如事先groupy好,直接apply(lambda x:…),这样避免了循环还要快得多。
(3)事先整理好逻辑,避免不必要的步骤。很多时候我是老老实实按照客户的思路去做,但是客户可能用vba,用excel,用oracle做,他的思路是他那个途径所必须的,而我不一定完完全全按照他的思路step1-2-3…只要最终得到一样的结果就好,完全可以选择最适合自己的思路和数据结构。
下面简单介绍下比较好的方式,顺便整理下我用过比较好的内置函数。

dataframe的列直接运算

如果只是简单的A列+B列-C列=新的一列,就可以直接写成如下。切勿循环每一行去做,这样如果你有几千万行,会卡死。

##新建一列你想要的resut,记得调整数据类型。
df_1['A','B','C']=pd.to_numeric(df_1['A','B','C'])-
df_1['result']=df_1['A']+df_1['B']-df_1['C']
print(df_1)

apply/map/applymap(lambda x: x…)

对于一些附带条件的运算,或者转换数据类型拆分字符串,总而言之就是我们希望主键之间的任何加工,我们都可以尝试以上语法。(详细见代码注释)

##对dataframe本身进行apply,x为dataframe本身,条件if...elif..else
df_temp_p['ACTUAL_AREA']=df_temp_p.apply(lambda x: 0 if x['COMPLETION_DATE'] > x['PERIOD_TIME'] else x['OCCUPIED_LAND_AREA'],axis=1)

##当然也可以针对groupby之后的dataframe进行apply,此外x也可以是sum某个主键的所有value并不附加条件。
df_temp['SUM_AREA_COMPANY']=df_sum.groupby(['ENTITY_CODE','PERIOD_TIME']).apply(lambda x: x['ACTUAL_AREA'].sum())

##也可以针对某一个主键,转换数据类型。
df_e['ENTITY_CODE'] = df_e['ENTITY_CODE'].apply(lambda x: str(x))

##applymap,map与apply不同。apply是针对整个数据矩阵,而map是针对个别元素。
temp_go[w_list[i]]=temp_go[w_list[i]].map(lambda x: 7 if x>=7 else (x if x>=0 else 0))

eval(‘数学公式字符串’)

隆重引入我们的高能函数eval()。这个短短的一句话可以胜过千万行代码,真的是大神为我们铺好了所有的路。
它有什么用呢?举个简单的例子,上面我们想运用的某些简单的数学公式,你大可把公式直接写进去,直接出结果。

##创建一个新列A
df1['A']=0
df1=df1.eval('A=C*B+D-E')

当然,你也可以写更复杂的数学公式,甚至数据模型。举例:比如这个标准正态分布反函数。
R= 𝜱((𝜱^(−𝟏) (Rate)+√𝝆 *Z )/√(𝟏−𝝆))

import math
from scipy import stats
from sklearn import datasets
df=df.eval('R=norm.cdf((norm.ppf(Rate+math.sqrt(B)*Z)/math.sqrt(1-B))')

另外,如果你脚本运行的是系统传给你的字符串,也可以对字符串进行拆分加工,作为一个变量传入eval函数。

##传入的字符串
Formula='Acc{AS970100}+Acc{AISM0101}+Acc{AS0451}'
##加工字符串
##拆分算是字符串
def split_it():
    operator=list()
    split=formula.split('Acc{')
    for i in range(len(split)):
        if i !=0 and i !=(len(split)):
            a=split[i]
            b=a.split('}')
            operator.append(b)
    return [a for a in operator]
####得到公式字符串
def append_strings():
    for j in range(0,len(M),1):
        Q=M[j][0]
        T=M[j][1]
        FF=Q+T
        F.append(FF)
    return [v for v in F]
def make_formula():
    K=Formula_1()
    K.insert(0,'Data=')
    Key=''.join(K)
    return Key
##将成型的公式作为key变量,传入eval函数
key=make_formula()
DF=DF.eval(key)

II. 尽量减少耗时的函数

如同SQL中,HAVING的使用,会影响效率,对于python,有一些函数本来就会比较复杂。我个人发现while loop很可怕,但是并不敢一棒子打死。遇到while,我都会用创建新list和append/ for loop if else判断结果的方式去进行。虽然while和for各有意义,但是我还是害怕回避while的使用。while循环中,只要为true则记录,if为单一条件判断。

下面我们来对比,让这两种方法干同一件事,速度的对比。干什么事呢?很简单,从0数到1000万。

II-A

import time
##while loop方式,用时107秒。
start=time.clock()
count=0
while (count < 10000000):
    print('the loop is %s' %count)
    count+=1
end=time.clock()
print(end-start)

while loop结果如图:107秒
在这里插入图片描述
II-B

下面我们看看创建新list和append/ for loop if else判断,的表现

import time
##创建新list和append/ for loop if else判断,结果为4秒。
start=time.clock()
alist=list()
for i in range(0,10000000):
    if (i < 10000000):
        alist.append(i)
    else:
        pass
print(alist)
end=time.clock()
print(end-start)

for+if+appendlist方法结果如图:4秒。
在这里插入图片描述

两者相差27倍

所以我还是很害怕使用while loop的。当然只有几十个数,无所谓啦~
但是,某些语言,好像并无大碍,所以我刚才说不清楚是不是只有py这样。。。下面看一段笔者之前写的PHP代码,while无伤大雅…当然有可能是数据库里元素不多?…

<?php
$fetchVideos = mysqli_query($con, "SELECT location FROM videos ORDER BY id DESC");
while ($row = mysqli_fetch_assoc($fetchVideos)){
$location = $row['location'];

echo "<div >";
echo "<video src='".$location."' controls width='320px' height='200px' >";
echo "</div>";
}
?>

III. 应用更高效的数据结构

进一步提升代码运行效率,我们可以考虑更加高效的数据结构,是更宏观的层面,举个例子。
假如现在我们想要将一个千万行的dataframe的某一个主键的某几类值,转为新的主键。(数据升维后再降维)
一般我们要学会在结构中找特点,根据特点来改善算法。
比如我们可以用pd.merge, concat 来进行多组数据整合,用groupby filter的方法来将大数据先分类再运算,避免逐条运算。基本语法如下:

先filter分类,再每块整体运算, 在merge回去。

class Merge():
    def __init__(self,accountlist,data,content):
        self.a = accountlist
        self.d = data
        self.c = content
    ##选取需要筛选的account
    def filter_account(self,iaccount):
        df1=self.d[self.d['Account']==iaccount]
        df1.columns = df1.columns.str.replace('Data', iaccount) ##给每一个新的data以其account名字命名
        return df1
    ##将需要参与运算的account拿出来合并在一个数据矩阵
    def merge_account(self):
        for i in range(len(self.a)):
            df_temp=self.filter_account(self.a[i])
            if i == 0:
                df_merge_final=df_temp
            if i > 0:
                df_merge_final=pd.merge(df_merge_final,df_temp,how='outer',on=self.c)
        return df_merge_final
 ###传入参数
 if __name__=='__main__':
    content=['Entity','Years','Period','View','Currency','Type','Product','Misc','id','Scenario','Version']
     ##与filter不同在于,filter是维度划分,这个则是固定维度(如果不固定,一边会出现重复值)
##一级维度筛选product类型
    data=pd.read_excel('data.xlsx')
    accountlist=['AAAAAA','BBBBBB','CCCCCC']

笛卡尔积
这个笛卡尔积也可以很好的做到维度计算。
在这里插入图片描述

具体代码如下,用到了numpy里面的np.diag,取对角线为一个新的List。

###用到了numpy里面的np.diag,取对角线为一个新的List.
def Cartesian():
    list_bill=list()
    for i in range(len(K1_list)):
        df1=HGdf[K1_list[i]]
        df1_N=np.array(df1)
        df1_nn=np.diag(df1_N)
        list_bill.append(df1_nn)
    return [a for a in list_bill]
CART=Cartesian()

IV. 打包,避免重复赋值。
这个应该很好理解,只是很多时候在写的时候因为偷懒,就换命名变量,赋值。要养成写注释,有顺序的命名变量。
此外,不要重复引用function,能打包的就写一次。
这里有一个小的心德,以前总喜欢用conda-base,逐段代码运行,很舒服方便,但是不利于整体的全局观和整体逻辑规划。学会写完整project,将能够多次利用的功能、对象放在一个def或者class里,传入对应参数,会使得代码很美观,别人看了也舒服,自己清bug也舒服~~!

4. @pyspark,SQL优化…

提供个新的思路,也是我自己在学习琢磨的。spark也有pyspark的包,不用直接装java的jdk环境,也不用Hadoop, 被称为伪分布式。可以直接

pip install pyspark。

但是国内的朋友注意,可能会中断,因为外网比较慢,这里推荐利用清华大学tuna镜像库,然后设置default time out。

pip --default-timeout=100 install -i https://pypi.tuna.tsinghua.edu.cn/simple pyspark

SQL优化,我个人理解在SQL读入数据和Insert回去的时候,有些逻辑运算可以再SQL里做,但是要去试,比如:(以读取为例)

此处sql2为SQL查询语句,可以再其中加入运算,比如排个序,选个表,union下等等…

from pymysql import *
##读取SQL数据
class MysqlHelper:
    def __init__(self, host, user, passwd, port, db):
        self.host = host
        self.user = user
        self.passwd = passwd
        self.port = port
        self.db = db
    def open(self):
        self.conn = connect(host=self.host,
                            user=self.user,
                            passwd=self.passwd,
                            port=self.port,
                            db=self.db,)
        self.cursor = self.conn.cursor()
    def close(self):
        self.cursor.close()
        self.conn.close()
    def cud(self, sql):
        try:
            self.open()
            self.cursor.execute(sql)
            self.conn.commit()
            self.close()
        except Exception as e:
            print(e)
    def all(self, sql):
        try:
            self.open()
            self.cursor.execute(sql)
            result = self.cursor.fetchall()
            self.close()
            return result
        except Exception as e:
            print(e)
##输入参数
sql2 = "SELECT * FROM as_data WHERE zzz=qqq'"
result2 = MysqlHelper('XXXXX', 'YYYY', 'ZZZZ', 3306, 'QQQQ').all(sql2)

未完待续…
在这里插入图片描述

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值