如何对python代码进行优化,以提高运行速度

遇到个代码优化的问题。。想请教下。。这段代码,结构或是哪里该如何优化下,以提高运行速度。。
大致情况先介绍下:
1. 代码需要考虑指定文件的输出---也就是相应数据是否需要计算;代码中的if output1 / output2 in condition,即是用来判断是否需要进行计算;
2. 计算需要分步骤进行。大致是先根据原始数据,计算原始数据的x1值(该步骤已经执行)。再根据x1值,计算相应的x1x2值,和x3值。然后将x1x2和x3拼接起来。
3. 数据量是10W行,为时间数据。。分两种情况进行计算(也就是两种情况的结果输出)。一种是每行时间数据,均要输出一个结果。另一种是对某个字段进行分组,然后根据分组来输出计算结果(比如企业)。
具体代码如下:

t05 = time.time()
#计算x1x2和x3的值(x1值已计算出来)
for i in std_level:
    max_std,min_std,l_1,l_2,l_3,l_4,l_5 = std_level[i][0]  #生成标准的数据表,用于对照取值
    #对原x1的字段的空值填充为0,方便跳过数值0进行数据处理。同时便于后期与目标值比较。
    if output1 in condition: #这个情况,是根据用户需求,进行相应的输出。如用户有需求,则执行下面的代码
        data['%s_x1'%i].fillna(0,inplace =True) 
    if output2 in condition: 
        f_data['%s_x1'%i].fillna(0,inplace =True)
    #定义x1x2的函数
    #x1为原始数据对应的等级,值为1--6(如原数据缺失,则为空值。为便于判断,需将x1的缺失值填充为0)
    #原则是,如x1(数据等级)为空值,则不进行判定(返回空值)。如等级为1或6,单独计算。如等级介于1和6之间,则统一计算。
    def tran_x1x2(x):
        dj = x['%s_x1'%i]
        jc = x[i]
        if dj == 0: 
            return np.NaN  #务必要返回np.NaN,否则返回空字符串None,影响后面x2的计算。
        else:
            if dj not in [1,6]: #根据介于等级1和6之间时的x1x2的计算公式
                up = std_level[i][0][int(dj)]
                dn = std_level[i][0][int(dj)+1]
                return round(dj + (up - jc) / (up -dn),1)
            else:
                if dj == 1: 
                    return 1.0
                else: 
                    return round(dj + (l_5 - jc) / l_5 * 4,1)
    #需事先定义好x1x2的函数,方能进行apply。而x1x2的函数定义,又需要事先对x1填充。。所以需要将条件判断,分开两次重复进行
    #本来考虑可以将条件判断合并到一起。。但是这样会增加重复的代码量,而且不能保证提高速度。
    #我也想用函数的方式来处理,但是不知道如何实现。。具体结构该怎么弄。。。
    if output1 in condition:  
        data['%s_x1x2'%i] = data.apply(tran_x1x2,axis=1)  #生成实时数据的x1x2
    if output2 in condition:
        f_data['%s_x1x2'%i] = f_data.apply(tran_x1x2,axis=1) #生成综合数据的x1x2
    #注意顺序,需先计算x1x2的值,才能计算x3的值
    def tran_x3(x):
        dj = x['%s_x1'%i]
        dj_x1x2 = x['%s_x1x2'%i]
        dj_x2 = (dj_x1x2 - dj) *10  #计算x2的值。不建议单独生成x2字段,否则字段过多,影响调试查看。
        if dj == 0: #等级数据不可能为0,其值原为空值,只是为了便于比较,将空值填充为0处理(第7/9行代码中实现)。
            return np.NaN
        else:
            if dj <= x['TARGET']:  #[‘TARGET’]为目标字段,用于比较
                return 0
            else:
                if dj_x2 == 0:
                    return dj - x['TARGET'] -1
                else:
                    return dj - x['TARGET']

    #新的x3存在空值的情况,需要事先填充
    if output1 in condition: #这个情况,是根据用户需求,进行相应的输出。如用户有需求,则执行下面的代码
        data['%s_x3'%i] = data.apply(tran_x3,axis=1)  #生成实时数据的x3
        data['%s_x3'%i].fillna(-1,inplace =True) #因x3可能为0,空值需填充为-1,以便区分和格式转换
        data['%s_x1x2'%i] = data['%s_x1x2'%i].astype(str)  #转字符格式,方便拼接
        data['%s_x3'%i] = data['%s_x3'%i].astype(int).astype(str) 
        data['%s_x1x2x3'%i] = np.where(data['%s_x3'%i]!= '-1',data['%s_x1x2'%i]+data['%s_x3'%i],np.NaN) #生成单因子标识指数
        data['%s_x1x2x3'%i] = data['%s_x1x2x3'%i].astype(float)  #求X1X2前,需记得将字符串转浮点型格式,否则无法求均值。
    if output2 in condition:
        f_data['%s_x3'%i] = f_data.apply(tran_x3,axis=1) #生成综合数据的x3
        f_data['%s_x3'%i].fillna(-1,inplace=True)
        f_data['%s_x1x2'%i] = f_data['%s_x1x2'%i].astype(str)  #转字符格式,方便拼接
        f_data['%s_x3'%i] = f_data['%s_x3'%i].astype(int).astype(str)
        f_data['%s_x1x2x3'%i] = np.where(f_data['%s_x3'%i]!= '-1',f_data['%s_x1x2'%i] + f_data['%s_x3'%i],np.NaN)
        f_data['%s_x1x2x3'%i] = f_data['%s_x1x2x3'%i].astype(float)
t06 = time.time()
print(t06-t05)

这段代码,我用10W行的数据来跑,用了24秒左右。。
我发现其中比较耗时的就是要输出output1的计算。。。这个就是计算每行数据的结果,计算量可能较大。
个人觉得代码还有较大的提升空间,只是水平有限,实在不知道如何优化。。
求高手指点

1个回答

看着有点儿头大啊,一大堆……放弃理解……
不过耗时无非就是for循环里面而已,为何要在for循环里面def呢?有些不明白……
尽量用生成器而不是临时产量的变量来循环处理……
如果要封装功能,可以用类或者函数传参实现啊,这是第一点。
第二点是用线程池……未来函数:from concurrent.futures import ThreadPoolExecutor,如果你需要的是有序的结果,那就用map来提交,如果不注重顺序,那也可以用submit,多线程处理

用参数啊!
apply是可以传入额外参数的
你之所以把def放在for循环里面,应该是一下没搞懂apply函数可以传入额外的参数。

def tran_new(x,i):  # 这里进行了改造
    dj = x['%s_level'%i]
    if dj == 0:
        return np.NaN 
    else:
        if dj not in [1,6]: 
            up = std_level[i][0][int(dj)] 
            dn = std_level[i][0][int(dj)+1] 
            return round(dj + (up - x[i]) / (up -dn),1)
        else:
            if dj == 1: 
                return 1.0
            else: 
                return round((l_5 - x[i]) / l_5 * 4,1)

for i in std_level: # for循环也进行了改造,主要是apply接受两组参数了,一个是原本的axis=1,另外一个是i,通过args=()的形式来传递
    l_1,l_2,l_3,l_4,l_5 = std_level[i][0]
    data['%s_new'%i] = data.apply(tran_new,axis=1, args=(i,))

终究还是去看了……再次头疼,我不确定上面的代码能否跑得起来,但思路就是这样。请注意,apply是可以传入额外参数的。
DataFrame.apply(func, axis=0, broadcast=False, raw=False, reduce=None, args=(), **kwds)

当然了,以上都还是顺应你的思路来进行……这样也不知道能不能有提升,要看具体结果才知道

lmw0320
lmw0320 回复D720CJM: 这个CSDN真不好,后期的答复没有相应提示。。。那个jit方法是提示语法错误。。删除这句增加的@jit就没有语法错误了。。。而在论坛的实例中,就是这么做的,也不报错。。到底问题出在哪里。。搞不懂
9 个月之前 回复
D720CJM
D720CJM 回复lmw0320: 你要看错误代码,是提示语法错误,还是提示没有安装那个模块?那个不是内置的。另外,函数定义本来就是为了简洁明了,真正加速的是多线程那些
9 个月之前 回复
lmw0320
lmw0320 谢谢你的热心帮忙。。也许目前的代码,已经到时间极限了? 个人感觉应该还没有。。至少那个from numba import jit方法没用上。。我放在函数定义前,会报错,说什么语法错误。。实际删除该@jit行,代码就正常运行。。搞不懂为什么论坛的例子可以正常执行。。
9 个月之前 回复
lmw0320
lmw0320 没注意到你把答复修改了。我刚看到,就马上按照你的格式去改了下。。貌似没有提升。。我自己后面也去试了下,发现函数定义几乎不会耗费时间。。真正耗时间的是apply函数的应用。。对于10W条数据,每行都应用的话,貌似就比较久了。也就是说,我的理解是多次定义函数并不会影响时间。。
9 个月之前 回复
lmw0320
lmw0320 回复D720CJM: 没错。我是自行定义了函数。只是对于额外传入参数,不会用。。也试着去找下例子来学习,貌似都没怎么看到好点的例子。。主要就是我得关联几个数据。如果能做到不在for循环中,多次定义函数,可能这个执行效率会大大提高
9 个月之前 回复
D720CJM
D720CJM 回复lmw0320: 我没看……但把这个回答改了一下,请注意,Pandas当中的apply函数是可以传入额外参数的,你用的应该是Pandas当中的apply函数吧?
9 个月之前 回复
lmw0320
lmw0320 回复D720CJM: 我在这个论坛上,发了个帖子,里面描述了具体的情况。。这样可能比空谈所谓的效率好些。可以的话,帮忙看下我的代码是否合理。。不合理,又该如何优化呢。链接是:https://fishc.com.cn/thread-140701-1-1.html
9 个月之前 回复
lmw0320
lmw0320 回复D720CJM: 很多时候,我觉得当面沟通会更好些。。本身我的表达能力也不太好。。。对于一个不同的情况,的确我所描述的信息,可能偏少了。。所以我的代码,的确不太好阅读。。我也很头疼。。水平有限,不知道该如何改进。。
9 个月之前 回复
lmw0320
lmw0320 回复D720CJM: 我是想用同一个函数来处理。因为其计算方式其实是一样的。。只是我还要用到外部的标准数据文件,来进行必要的判定。也就是说,不同的指标,对应的等级数据是不同的。不用不同的函数,我不知道该如何去定义这个函数啊。。--直观点说,我需要用for i in x里的i,而这个i下面还有对应不同的数据。。不用for循环,如何得到这个i,又该如何定义函数呢?
9 个月之前 回复
D720CJM
D720CJM 回复lmw0320: 我建议你用同一个函数,至于不同的情况处理,只需要输入不同的参数不就行了吗?不要重复生成。顺便说句,我真的看不懂你的代码!
9 个月之前 回复
lmw0320
lmw0320 你提到的为什么用for循环里定义函数,我是这么理解的。因为我要对某列进行数据转换生成一个新的对应列(后面的计算需要该新列的结果,继续计算下去),用函数定义的方式apply下,会比较快。而这样的方法其实是适用很多其他类似列的。所以我才用定义一个函数,结合for循环来生成多列。
9 个月之前 回复
Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问