【问题标题】:How can I make my python program run faster?如何让我的 python 程序运行得更快?
【发布时间】:2021-05-11 02:22:03
【问题描述】:

我正在读取一个 .csv 文件并创建一个 pandas 数据框。该文件是股票文件。我只对日期、公司和成交成本感兴趣。我希望我的程序找到开始日期、结束日期和公司的最大利润。它需要使用分而治之的算法。我只知道如何使用 for 循环,但它需要永远运行。 .csv 文件有 200,000 行。我怎样才能让它快速运行?

import pandas as pd
import numpy as np
import math

def cleanData(file):
    df = pd.read_csv(file)
    del df['open']
    del df['low']
    del df['high']
    del df['volume']
    return np.array(df)
    
df = cleanData('prices-split-adjusted.csv')

bestStock = [None, None, None, float(-math.inf)]

def DAC(data):
    global bestStock

    if len(data) > 1:
        mid = len(data)//2
        left = data[:mid]
        right = data[mid:]
    
        DAC(left)
        DAC(right)
    
        for i in range(len(data)):
            for j in range(i+1,len(data)):
                if data[i,1] == data[j,1]:
                    profit = data[j,2] - data[i,2]
                    if profit > bestStock[3]:
                        bestStock[0] = data[i,0]
                        bestStock[1] = data[j,0]
                        bestStock[2] = data[i,1]
                        bestStock[3] = profit
                    
                    print(bestStock)
    print('\n')
    return bestStock
    
print(DAC(df))

【问题讨论】:

  • 干得好!干得好!
  • 导致系统性能下降的主要问题是 1)您在嵌套循环中手动迭代 2 列而不使用使用快速 ndarray 函数的 pandas 操作; 2)你使用递归调用,看起来不错,简单但速度慢。尝试使用熊猫功能,例如。 groupby("company").agg(...) 获得公司最佳收益的新列。然后在这个新栏目上使用idxmax,得到所有公司中收益最好的公司的条目。
  • 我好像迷路了!代码是否甚至到达嵌套的 for 循环(假设发布的代码中没有缩进错误)?
  • @sai 是的,确实如此。但是,嵌套循环逻辑(如冒泡排序)正在完成所有任务。代码并没有真正利用分而治之的结果。

标签: python pandas numpy recursion divide-and-conquer


【解决方案1】:

我有两件事供您考虑(我的回答尽量不改变您的算法方法,即嵌套循环和递归函数,并首先解决低洼的果实):

  1. 除非您正在调试,否则请尝试避免 print() 在循环内。 (在您的情况下 .. print(bestStock) ..) I/O 开销可能会增加,尤其是。如果您在大型数据集之间循环并经常打印到屏幕上。一旦你对你的代码没问题,注释掉它以在你的完整数据集上运行,并仅在调试会话期间取消注释。您可以期望看到速度有所提高,而无需在循环中打印到屏幕。

  2. 如果您寻求更多“加快速度”的方法,我发现在我的案例中(类似于我经常遇到的,尤其是在搜索/排序问题中),只需切换昂贵的部分(python 'For'循环)到 Cython (并静态定义变量类型..这是关键!到 SPEEEEDDDDDD)甚至在优化实现之前给了我几个数量级的加速。检查 Cython https://cython.readthedocs.io/en/latest/index.html。如果这还不够,那么 parrelism 是你的下一个最好的朋友,需要重新考虑你的代码实现。

【讨论】:

    【解决方案2】:

    导致系统性能变慢的主要问题有:

    1. 您在嵌套循环中手动迭代 2 列,而不使用使用快速 ndarray 函数的 pandas 操作;
    2. 您使用递归调用,看起来不错且简单但速度很慢。

    设置样本数据如下:

              Date  Company         Close
    0   2019-12-31     AAPL     73.412498
    1   2019-12-31       FB    205.250000
    2   2019-12-31     NFLX    323.570007
    3   2020-01-02     AAPL     75.087502
    4   2020-01-02       FB    209.779999
    ...        ...      ...           ...
    184 2020-03-30       FB    165.949997
    185 2020-03-30     NFLX    370.959991
    186 2020-03-31     AAPL     63.572498
    187 2020-03-31       FB    166.800003
    188 2020-03-31     NFLX    375.500000
    
    189 rows × 3 columns
    

    然后使用以下代码(如果不同,请将列标签修改为您的标签):

    df_result = df.groupby('Company').agg(Start_Date=pd.NamedAgg(column='Date', aggfunc="first"), End_Date=pd.NamedAgg(column='Date', aggfunc="last"), bestGain=pd.NamedAgg(column='Close', aggfunc=lambda x: x.max() - x.iloc[0]))
    

    结果输出:

            Start_Date    End_Date   bestGain
    Company         
    AAPL    2019-12-31  2020-03-31   8.387505
    FB      2019-12-31  2020-03-31  17.979996
    NFLX    2019-12-31  2020-03-31  64.209991
    

    获得最大收益的条目:

    df_result.loc[df_result['bestGain'].idxmax()]
    

    结果输出:

    Start_Date    2019-12-31 00:00:00
    End_Date      2020-03-31 00:00:00
    bestGain                64.209991
    Name: NFLX, dtype: object
    

    执行时间比较

    根据我在 3 个月内缩减 3 只股票的数据,使用 pandas 函数的代码(需要 8.9 毫秒)大约是原始代码执行时间的一半,使用嵌套循环和递归手动迭代 numpy 数组调用(需要 16.9 毫秒),即使在大多数 print() 函数调用被删除后。

    您的 DAC() 函数中带有 print() 的代码已删除:

    %%timeit
    """
    def cleanData(df):
        # df = pd.read_csv(file)
        del df['Open']
        del df['Low']
        del df['High']
        del df['Volume']
        return np.array(df)
    """    
    # df = cleanData('prices-split-adjusted.csv')
    # df = cleanData(df0)
    df = np.array(df0)
    
    bestStock = [None, None, None, float(-math.inf)]
    
    def DAC(data):
        global bestStock
    
        if len(data) > 1:
            mid = len(data)//2
            left = data[:mid]
            right = data[mid:]
        
            DAC(left)
            DAC(right)
        
            for i in range(len(data)):
                for j in range(i+1,len(data)):
                    if data[i,1] == data[j,1]:
                        profit = data[j,2] - data[i,2]
                        if profit > bestStock[3]:
                            bestStock[0] = data[i,0]
                            bestStock[1] = data[j,0]
                            bestStock[2] = data[i,1]
                            bestStock[3] = profit
                        
                        # print(bestStock)
        # print('\n')
        return bestStock
        
    print(DAC(df))
    
    [Timestamp('2020-03-16 00:00:00'), Timestamp('2020-03-31 00:00:00'), 'NFLX', 76.66000366210938]
    16.9 ms ± 303 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    熊猫编码方式中的新简化代码:

    %%timeit
    df_result = df.groupby('Company').agg(Start_Date=pd.NamedAgg(column='Date', aggfunc="first"), End_Date=pd.NamedAgg(column='Date', aggfunc="last"), bestGain=pd.NamedAgg(column='Close', aggfunc=lambda x: x.max() - x.iloc[0]))
    df_result.loc[df_result['bestGain'].idxmax()]
    
    8.9 ms ± 195 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    【讨论】:

    • 我取出了打印语句,但我的代码仍然无法使用导入的 .csv 文件执行。 Pandas 方法确实有效,但是有什么方法可以在递归函数中执行 Pandas 代码?我的问题需要一个递归函数。
    • @JaredPino 递归函数解决方案已添加为如何正确使用递归调用的演示。处理断货的代码仍有限制。你必须加强它。只是为了演示递归调用。
    【解决方案3】:

    使用递归函数的解决方案:

    你的递归函数的主要问题在于你没有利用递归调用减小尺寸数据的结果。

    要正确使用递归函数作为分而治之的方法,您应该采取 3 个主要步骤:

    1. 将整个数据集分成更小的部分,并通过递归调用处理更小的部分,每个调用都取一个较小的部分
    2. 在每个递归调用中处理端点情况(大多数时候最简单的情况)
    3. 合并所有小块递归调用的结果

    递归调用的美妙之处在于,您可以通过用两个更简单的步骤替换处理来解决复杂的问题:第一步是处理端点情况,在这种情况下,您大部分时间只能处理一个数据项(这通常很容易)。第二步是采取另一个简单的步骤来合并缩减大小的调用的结果。

    您设法迈出了第一步,但没有迈出其他 2 步。特别是,您没有利用较小部分的结果来简化处理。相反,您通过循环遍历二维 numpy 数组中的所有行来处理每次调用中的整个数据集。嵌套循环逻辑就像“冒泡排序”[复杂度 order(n squared) 而不是 order(n)]。因此,您的递归调用只是在浪费时间,没有价值!

    建议修改你的递归函数如下:

    def DAC(data):
        # global bestStock                 # define bestStock as a local variable instead
        bestStock = [None, None, None, float(-math.inf)]    # init bestStock
    
        if len(data) = 1:                  # End-point case: data = 1 row
            bestStock[0] = data[0,0]
            bestStock[1] = data[0,0]
            bestStock[2] = data[0,1]
            bestStock[3] = 0.0
        elif len(data) = 2:                # End-point case: data = 2 rows
            bestStock[0] = data[0,0]
            bestStock[1] = data[1,0]
            bestStock[2] = data[0,1]       # Enhance here to allow stock break 
            bestStock[3] = data[1,2] - data[0,2]
        elif len(data) >= 3:               # Recursive calls and consolidate results
            mid = len(data)//2
            left = data[:mid]
            right = data[mid:]
        
            bestStock_left = DAC(left)
            bestStock_right = DAC(right)
    
            # Now make use of the results of divide-and-conquer and consolidate the results
            bestStock[0] = bestStock_left[0]
            bestStock[1] = bestStock_right[1]
            bestStock[2] = bestStock_left[2]    # Enhance here to allow stock break 
            bestStock[3] = bestStock_left[3] if bestStock_left[3] >= bestStock_right[3] else bestStock_right[3] 
        
        # print(bestStock)
        # print('\n')
        return bestStock
    

    这里我们需要处理 2 种端点情况:1 行和 2 行。原因是对于只有 1 行的情况,我们无法计算增益,只能将增益设置为零。增益可以从 2 行开始计算。如果不分成这两种端点情况,我们最终可能只会一直传播零增益。

    这是一个演示,说明您应该如何编写递归调用以利用它。您仍然需要微调的代码存在限制。您必须进一步增强它以处理断货情况。 2 行和 >= 3 行的代码现在假设目前没有断货。

    【讨论】:

      猜你喜欢
      • 2017-03-19
      • 2018-08-23
      • 1970-01-01
      • 1970-01-01
      • 2017-07-10
      • 2017-07-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多