循环的替代方法?矢量化,cython?

时间:2019-12-11 12:37:51

标签: python-3.x pandas dataframe vectorization cython

我有一个熊猫数据框,如下所示:

       Total    Yr_to_Use   First_Year_Del    Del_rate 2019 2020 2021 2022 2023 etc 
ref1    100       2020         5                 10    0    0    0    0   0
ref2    20        2028         2                 5     0    0    0    0   0 
ref3    30        2021         7                 16    0    0    0    0   0
ref4    40        2025         9                 18    0    0    0    0   0
ref5    10        2022         4                 30    0    0    0    0   0

“总计”列显示需要交付多少产品。 “ First_yr_Del”告诉您第一年将交付多少。此后,交货费率将恢复为“ Del_rate”(统一费率),在所有产品交付之前,每年都可以采用统一费率。 “使用年”列告诉您从第一年开始交付。

示例: Ref1有100个要交付。它将于2020年开始交付,并将在第一年交付5台,此后每年交付10台,直到全部100台交付。

有什么想法可以解决这个问题吗?

我认为我可能会使用类似下面的内容来依次引用要使用的列,但我什至不确定这是否有帮助,因为这取决于解决方案(在正确的版本中,base_date.year是定义为表格的第一列-2019):

start_index_for_slice = df.columns.get_loc(base_date.year)
end_index_for_slice = start_index_for_slice+no_yrs_to_project
df.columns[start_index_for_slice:end_index_for_slice]

我是python的新手,不确定我是否能超越自己...

我认为解决该问题的方法是使用for循环或使用迭代的东西,但是其他帖子似乎说这是个坏主意,我应该使用矢量化,cython或lambda。到目前为止,在这3个中,我只处理了一个非常简单的lambda。其他解决方案对我来说还是个谜,因为该解决方案似乎建议一个接一个地执行直到完成。

任何人和所有帮助表示赞赏!

谢谢

编辑:下面的示例预期输出(我编辑了一些日期,以便更好地了解逻辑):

       Total    Yr_to_Use   First_Year_Del Del_rate 2019 2020 2021 2022 2023etc 
ref1    100       2020         5              10    0    5    10    10   10
ref2    20        2021         2              5     0    0    2     5    5 
ref3    30        2021         7              16    0    0    7     16   7
ref4    40        2019         9              18    9    18   13    0    0
ref5    10        2020         4              30    0    4    6     0    0

2 个答案:

答案 0 :(得分:1)

这是另一个选项,它分离了费率/年矩阵的计算,并稍后将其附加到输入df上。仍然会在脚本本身中循环(而不是“外部化”到某些numpy / pandas函数中)。我猜想5k行应该没问题。

import pandas as pd
import numpy as np

# create the inital df without years/rates
df = pd.DataFrame({'Total': [100, 20, 30, 40, 10], 
                   'Yr_to_Use': [2020, 2021, 2021, 2019, 2020], 
                   'First_Year_Del': [5, 2, 7, 9, 4],
                   'Del_rate': [10, 5, 16, 18, 30]})

# get number of rates + remainder
n, r = np.divmod((df['Total']-df['First_Year_Del']), df['Del_rate'])

# get the year of the last rate considering all candidates
max_year = np.max(n + r.astype(np.bool) + df['Yr_to_Use'])

# get the offsets for the start of delivery, year zero is 2019
offset = df['Yr_to_Use'] - 2019

# get a year index
yrs = np.arange(2019, max_year+1)

# prepare a matrix to hold the rates for all years
out = np.zeros((df['Total'].shape[0], yrs.shape[0]))
# this could probably be optimized by getting rid of the for loop:
for i in range(df['Total'].shape[0]):
    rates = np.concatenate([[df['First_Year_Del'][i]], n[i]*[df['Del_rate'][i]], [r[i]]])
    out[i, offset[i]:offset[i]+rates.shape[0]] = rates

# add the years/rates matrix to the original df    
df = pd.concat([df, pd.DataFrame(out, columns=yrs.astype(str))], axis=1, sort=False)

答案 1 :(得分:0)

您可以使用两个用户定义的函数和apply方法来完成此操作

import pandas as pd
import numpy as np

df = pd.DataFrame(data={'id': ['ref1','ref2','ref3','ref4','ref5'], 
                        'Total': [100, 20, 30, 40, 10],
                        'Yr_to_Use': [2020, 2028, 2021, 2025, 2022],
                        'First_Year_Del': [5,2,7,9,4],
                        'Del_rate':[10,5,16,18,30]})

def f(r):
    ''' 
    Computes values per year and respective year
    '''

    n = (r['Total'] - r['First_Year_Del'])//r['Del_rate']
    leftover = (r['Total'] - r['First_Year_Del'])%r['Del_rate']
    r['values'] = [r['First_Year_Del']] + [r['Del_rate'] for _ in range(n)] + [leftover]
    r['years'] = np.arange(r['Yr_to_Use'], r['Yr_to_Use'] + len(r['values']))

    return r

df = df.apply(f, axis=1)


def get_year_range(r):
    '''
    Computes min and max year for each row
    '''

    r['y_min'] = min(r['years'])
    r['y_max'] = max(r['years'])
    return r 

df = df.apply(get_year_range, axis=1)

y_min = df['y_min'].min()
y_max = df['y_max'].max()

#Initialize each year value to zero
for year in range(y_min, y_max+1):
    df[year] = 0


def expand(r):
    '''
    Update value for each year
    '''
    for v, y in zip(r['values'], r['years']):
        r[y] = v 
    return r

# Apply and drop temporary columns
df = df.apply(expand, axis=1).drop(['values', 'years', 'y_min', 'y_max'], axis=1)