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

目录:

1. 前言
2. 概览
3. 肮脏代码与相对改良(例)
4. @pyspark,SQL优化...

image.png

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秒


image.png

下面我们看看创建新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秒。


image.png

两者相差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']

笛卡尔积
这个笛卡尔积也可以很好的做到维度计算。

image.png

具体代码如下,用到了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)

未完待续...........


image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,711评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,079评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,194评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,089评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,197评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,306评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,338评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,119评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,541评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,846评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,014评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,694评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,322评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,026评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,257评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,863评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,895评论 2 351

推荐阅读更多精彩内容

  • pyspark.sql模块 模块上下文 Spark SQL和DataFrames的重要类: pyspark.sql...
    mpro阅读 9,449评论 0 13
  • 特别说明: 1、本文只是面对数据库应用开发的程序员,不适合专业DBA,DBA在数据库性能优化方面需要了解更多的知识...
    安易学车阅读 1,807评论 0 40
  • 转载自:https://www.cnblogs.com/easypass/archive/2010/12/08/1...
    SkTj阅读 536评论 0 1
  • ORA-00001: 违反唯一约束条件 (.) 错误说明:当在唯一索引所对应的列上键入重复值时,会触发此异常。 O...
    我想起个好名字阅读 5,267评论 0 9
  • 最近,在反省自己,工作干得怎么样。好多时候,人都对自己有个过高的评价,因为追求进步,有种向上的精神,但常常高估了自...
    大虎0726阅读 150评论 0 1