python - 精進(jìn) - DataFrame和Series賦值的性能優(yōu)化

DataFrame和Series賦值的性能優(yōu)化

結(jié)論

DataFrame最好直接進(jìn)行重構(gòu)賦值新變量,而不做修改刪除等操作。因?yàn)閮烧吡考?jí)一旦起來(lái)存在極大時(shí)間差異。

背景

工作場(chǎng)景中,生產(chǎn)環(huán)境的linux系統(tǒng) 與 本地windows對(duì)比,發(fā)現(xiàn)有時(shí)間方面差異。本身0.3s能在windows匹配出來(lái)的數(shù)據(jù),在linux中卻1s匹配。

那么,在生產(chǎn)環(huán)境的服務(wù)器性能優(yōu)于自己電腦,卻產(chǎn)生這樣子情況,故進(jìn)行問(wèn)題查找。

時(shí)間裝飾器

首先排查問(wèn)題是需要找到每一個(gè)函數(shù)所使用的時(shí)間,但是每次都寫

import time

start = time.time()
df(xxx)
print("耗費(fèi)時(shí)間是:{}".format(time.time()-start))

會(huì)十分浪費(fèi)空間大小,所以可以用裝飾器解決。

# 裝飾器
def ctime(func):
    def warpper(*arsg, **kwargs):
        start_time = time.time()
        res = func(*arsg, **kwargs)
        end_time = time.time()
        print("%s cost %ss" % (func.__name__, end_time - start_time))
        return res

    return warpper

用法是

import time

@ctime
df(xxx)

查找時(shí)間分布

1、找到耗時(shí)函數(shù)

利用裝飾器找到一個(gè)函數(shù)的耗時(shí),兩者差異較大,如果在linux耗時(shí)0.4s,在windows只需0.03s-0.10s(pycharm有編譯器,用pycharm 0.03s,但是python xxx.py時(shí)卻0.10s)

2、分析哪一步耗時(shí)慢

image-20200724141548533

函數(shù)內(nèi)部使用的就是一個(gè)個(gè)表達(dá)式,無(wú)法使用裝飾器,那么只能夠

start = time.time()
df_tmp['num_get'] = df_tmp.apply(lambda x: self.interval_treat(str(x['info_match'])), axis=1)
print('1-------{}'.format(time.time()-start))
df_tmp['num_one'], df_tmp['num_two'] = df_tmp['num_get'].str.split('plus').str
print('2-------{}'.format(time.time()-start))
df_tmp.loc[:, 'num_get'] = df_tmp.apply(lambda x: x['num_one'] if int(x['num_two']) == 0 else x['num_get'], axis=1)
print('3-------{}'.format(time.time()-start))
df_tmp['search_num'] = df_tmp.apply(
    lambda x: int(x['num_one']) % 2 if int(x['num_one']) != 0 else 'common', axis=1)
print('4-------{}'.format(time.time()-start))

逐步輸出。

發(fā)現(xiàn)這些耗時(shí)的函數(shù),有一個(gè)共同特點(diǎn),就是在已有的DataFrame中進(jìn)行添加列的操作。

查找解決辦法

假設(shè)試驗(yàn)

一開始以為是warning問(wèn)題

df_tmp['num_get'] = df_tmp.apply(lambda x: self.interval_treat(str(x['info_match'])), axis=1)
改為
df_tmp.loc[:, 'num_get'] = df_tmp.apply(lambda x: self.interval_treat(str(x['info_match'])), axis=1)

并不是這個(gè)問(wèn)題

經(jīng)過(guò)不斷假設(shè),發(fā)現(xiàn)

df_tmp['num_get'] = df_tmp.apply(lambda x: self.interval_treat(str(x['info_match'])), axis=1)

改為

df_tmp.apply(lambda x: self.interval_treat(str(x['info_match'])), axis=1)

兩者差異 從 0.11s 變?yōu)?0.02s,推測(cè)可能原因是 dataframe的賦值問(wèn)題。

驗(yàn)證想法

結(jié)合搜索找到一個(gè)對(duì)比試驗(yàn)

import pandas as pd
import random
import timeit


def func1():
    aa = []
    for x in range(200):
        aa.append([random.randint(0, 1000) for r in range(5)])
    pdaa = pd.DataFrame(aa)


def func2():
    pdbb = pd.DataFrame()
    for y in range(200):
        pdbb[y] = pd.Series([random.randint(0, 1000) for r in range(5)])


def func3():
    aa = {}
    for x in range(200):
        aa[str(x)] = random.randint(0, 1000)
        psaa = pd.Series(aa)


def func4():
    psbb = pd.Series()
    for y in range(200):
        psbb[str(y)] = random.randint(0, 1000)


t1 = timeit.timeit(stmt =func1, number=100)
t2 = timeit.timeit(stmt =func2, number=100)
print(t1, t2)
t3 = timeit.timeit(stmt =func3, number=100)
t4 = timeit.timeit(stmt =func4, number=100)
print(t3, t4)

這個(gè)函數(shù)比較出來(lái)的結(jié)果是

print(t1,t2)
0.7337615000014921 30.031491499999902
===========================
print(t3, t4)
18.894987499999843 47.094585599999846

可以發(fā)現(xiàn),直接重新從list構(gòu)建新的DataFrame輸出,速度會(huì)提高。

按照這個(gè)思路,我將所有賦值的一些判斷,全部丟到同一函數(shù),傳入的參數(shù)從 某個(gè)值, 變成直接 dataframe的每一行,讓其返回的數(shù)據(jù),從一個(gè)值變成一個(gè)列表。

def interval_treat(df_tmp_info):
    addr = str(df_tmp_info['info_match'])
    #addr 輸出 num_1 num_2
    num = str(num_1)+ 'plus' + str(num_2)

    result = []
    result.extend(df_tmp_info.tolist())
    result.extend([num, int(num_1), int(num_2)])

    return result
df_tmp['num_get'] = df_tmp.apply(lambda x: interval_treat(str(x['info_match'])), axis=1)

改為

df_tmp = df_tmp.apply(lambda x: pd.Series(interval_treat(x),index = [list(df_tmp.index)+[需要的新增字段]]), axis=1)

調(diào)用的邏輯就從賦新列的值變?yōu)橹苯又亟M成一個(gè)新DataFrame

最后實(shí)踐效果:linux該函數(shù)從0.4s變?yōu)?.07s。

將pandas DataFrame列擴(kuò)展為多行

推演繼續(xù)

代碼中很多函數(shù)需要用到一列轉(zhuǎn)多行的操作,本來(lái)是使用

def split_vartical_shape(database_deal, _name):
    database_deal = database_deal.drop(_name, axis=1).join(
        database_deal[_name].str.split('|', expand=True).stack().reset_index(level=1, drop=True).rename(_name))

    return database_deal

進(jìn)行列轉(zhuǎn)多行操作,可以發(fā)現(xiàn)它使用了join方法操作

后面更改成

def using_repeat(df, col_name_lst, repeat):
    col_name_lst.remove(repeat) if repeat in col_name_lst else col_name_lst
    lens = [len(item) for item in df[repeat]]
    dataframe_dict = {}
    for col_name in col_name_lst:
        dataframe_dict[col_name] = np.repeat(df[col_name].values, lens)
    dataframe_dict[repeat] = np.concatenate(df[repeat].values)
    return pd.DataFrame(dataframe_dict)

50萬(wàn)的數(shù)據(jù),90s執(zhí)行時(shí)間優(yōu)化為35s

總結(jié)

1、不管做什么,都要有對(duì)比思維,換產(chǎn)品經(jīng)理就叫AB測(cè)試、數(shù)學(xué)就叫控制變量、生活就叫分類對(duì)比。

2、對(duì)專業(yè)方面的事情,需要有足夠敏感性,發(fā)現(xiàn) 條件足夠好,表現(xiàn)卻不理想,需要尋找原因。

3、最基本、最蠢的方法就是最有效的手段,不要“認(rèn)為”、“感覺(jué)”,要“比較”、“測(cè)試”。

4、學(xué)會(huì)一法通萬(wàn)法,不斷復(fù)用,及時(shí)總結(jié)。比如:找到是賦值問(wèn)題,那么所有代碼中賦值操作是否可以優(yōu)化,是否值得優(yōu)化。一個(gè)數(shù)據(jù)提高0.01s速度,一百萬(wàn)數(shù)據(jù)就提高 1萬(wàn)秒(2.77h)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容