python - 精进 - DataFrame和Series赋值的性能优化

DataFrame和Series赋值的性能优化

结论

DataFrame最好直接进行重构赋值新变量,而不做修改删除等操作。因为两者量级一旦起来存在极大时间差异。

背景

工作场景中,生产环境的linux系统 与 本地windows对比,发现有时间方面差异。本身0.3s能在windows匹配出来的数据,在linux中却1s匹配。

那么,在生产环境的服务器性能优于自己电脑,却产生这样子情况,故进行问题查找。

时间装饰器

首先排查问题是需要找到每一个函数所使用的时间,但是每次都写

import time

start = time.time()
df(xxx)
print("耗费时间是:{}".format(time.time()-start))

会十分浪费空间大小,所以可以用装饰器解决。

# 装饰器
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)

查找时间分布

1、找到耗时函数

利用装饰器找到一个函数的耗时,两者差异较大,如果在linux耗时0.4s,在windows只需0.03s-0.10s(pycharm有编译器,用pycharm 0.03s,但是python xxx.py时却0.10s)

2、分析哪一步耗时慢

image-20200724141548533

函数内部使用的就是一个个表达式,无法使用装饰器,那么只能够

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))

逐步输出。

发现这些耗时的函数,有一个共同特点,就是在已有的DataFrame中进行添加列的操作。

查找解决办法

假设试验

一开始以为是warning问题

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)

并不是这个问题

经过不断假设,发现

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 变为 0.02s,推测可能原因是 dataframe的赋值问题。

验证想法

结合搜索找到一个对比试验

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)

这个函数比较出来的结果是

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

可以发现,直接重新从list构建新的DataFrame输出,速度会提高。

按照这个思路,我将所有赋值的一些判断,全部丢到同一函数,传入的参数从 某个值, 变成直接 dataframe的每一行,让其返回的数据,从一个值变成一个列表。

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)

调用的逻辑就从赋新列的值变为直接重组成一个新DataFrame

最后实践效果:linux该函数从0.4s变为0.07s。

将pandas DataFrame列扩展为多行

推演继续

代码中很多函数需要用到一列转多行的操作,本来是使用

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

进行列转多行操作,可以发现它使用了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万的数据,90s执行时间优化为35s

总结

1、不管做什么,都要有对比思维,换产品经理就叫AB测试、数学就叫控制变量、生活就叫分类对比。

2、对专业方面的事情,需要有足够敏感性,发现 条件足够好,表现却不理想,需要寻找原因。

3、最基本、最蠢的方法就是最有效的手段,不要“认为”、“感觉”,要“比较”、“测试”。

4、学会一法通万法,不断复用,及时总结。比如:找到是赋值问题,那么所有代码中赋值操作是否可以优化,是否值得优化。一个数据提高0.01s速度,一百万数据就提高 1万秒(2.77h)

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