如何让我的 python 程序运行得更快?

时间:2021-02-07 07:00:55

标签: python-3.x pandas numpy recursion divide-and-conquer

我正在读取 .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))

3 个答案:

答案 0 :(得分:1)

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

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

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

答案 1 :(得分:0)

导致系统性能下降的主要问题是:

  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 数组即使在删除大部分 print() 函数调用之后调用(需要 16.9 毫秒)。

删除了 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)

pandas 编码方式中的新简化代码:

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

答案 2 :(得分:0)

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

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

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

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

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

您设法迈出了第一步,但没有迈出其他两步。特别是,您没有利用通过使用较小片段的结果来简化处理的优势。相反,您可以通过遍历二维 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 个端点情况,我们最终只能一直向上传播零增益。

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

相关问题