Как уменьшить функцию, которая постоянно увеличивается в python?

#python #pandas #dataframe #function #date

Вопрос:

У меня есть фрейм данных в виде:

 big       fc15       fc16       fc17       fc18       fc19  ...       fc23       fc24       fc25       fc26       fc27       fc28
 28 2018-10-01 2019-02-01 2019-06-04 2019-08-06 2019-10-07  ... 2015-01-01 2015-01-01 2015-01-01 2015-01-01 2015-01-01 2015-01-01
 27 2015-01-01 2019-02-01 2015-01-01 2015-01-01 2015-01-01  ... 2015-01-01 2015-01-01 2015-01-01 2015-01-01 2015-01-01 2015-01-01
 20 2018-10-01 2019-02-01 2019-06-04 2019-08-06 2019-10-07  ... 2015-01-01 2015-01-01 2015-01-01 2015-01-01 2015-01-01 2015-01-01
 21        NaT        NaT        NaT        NaT        NaT  ...        NaT        NaT        NaT        NaT        NaT        NaT
 24 2018-10-01 2019-02-01 2019-06-04 2019-08-06 2019-10-07  ... 2015-01-01 2020-09-03 2015-01-01 2015-01-01 2021-03-03 2021-05-05
 25 2018-10-01 2019-02-01 2019-06-04 2019-08-06 2019-10-07  ... 2015-01-01 2020-09-03 2015-01-01 2021-01-06 2021-03-03 2015-01-01
 26 2018-10-01 2019-02-01 2019-06-04 2019-08-06 2019-10-07  ... 2015-01-01 2015-01-01 2020-11-05 2021-01-06 2015-01-01 2015-01-01
 23 2015-01-01 2015-01-01 2015-01-01 2019-08-06 2019-10-07  ... 2020-07-13 2020-09-03 2020-11-05 2021-01-06 2021-03-03 2015-01-01
 22        NaT        NaT        NaT        NaT        NaT  ...        NaT        NaT 2020-11-05 2021-01-06 2021-03-03 2021-05-05
 27 2018-10-01 2019-02-01 2019-06-04 2015-01-01 2019-10-07  ... 2015-01-01 2015-01-01 2015-01-01 2015-01-01 2015-01-01 2015-01-01
 

Итак, в зависимости от big столбца он должен рассчитать разницу (timedelta) между датой в столбце с одинаковым размером, например, if big == 24 then fc24 - max(fc23, fc22, fc21, fc20, fc19, fc18) и так далее … НО если big > max(big) или максимальное количество столбцов, которое в данном случае равно 28 (из-за fc28 столбца (последнего столбца)), предположим, что 29, оно должно вычислить today_date - max(fc28, fc27, fc26, fc25, fc24) .

Я попробовал свою функцию в качестве:

 def estimated_time(df):
    today_date = datetime.datoday()
    df['time_without_offer'] = np.where(df['big'] == 16, (df['fc16'] - df['fc15']).astype('timedelta64[D]'),
                                            np.where(df['big'] == 17, (df['fc17'] - df[['fc15', 'fc16']].max(axis = 1)).astype('timedelta64[D]'),
                                            np.where(df['big'] == 18, (df['fc18'] - df[['fc15', 'fc16', 'fc17']].max(axis = 1)).astype('timedelta64[D]'),
                                            np.where(df['big'] == 19, (df['fc19'] - df[['fc15', 'fc16', 'fc17', 'fc18']].max(axis = 1)).astype('timedelta64[D]'),
                                            np.where(df['big'] == 20, (df['fc20'] - df[['fc15', 'fc16', 'fc17', 'fc18', 'fc19']].max(axis = 1)).astype('timedelta64[D]'),
                                            np.where(df['big'] == 21, (df['fc21'] - df[['fc15', 'fc16', 'fc17', 'fc18', 'fc19', 'fc20']].max(axis = 1)).astype('timedelta64[D]'),
                                            np.where(df['big'] == 22, (df['fc22'] - df[['fc16', 'fc17', 'fc18', 'fc19', 'fc20', 'fc21']].max(axis = 1)).astype('timedelta64[D]'),
                                            np.where(df['big'] == 23, (df['fc23'] - df[['fc17', 'fc18', 'fc19', 'fc20', 'fc21', 'fc22']].max(axis = 1)).astype('timedelta64[D]'),
                                            np.where(df['big'] == 24, (df['fc24'] - df[['fc18', 'fc19', 'fc20', 'fc21', 'fc22', 'fc23']].max(axis = 1)).astype('timedelta64[D]'),
                                            np.where(df['big'] == 25, (df['fc25'] - df[['fc19', 'fc20', 'fc21', 'fc22', 'fc23', 'fc24']].max(axis = 1)).astype('timedelta64[D]'),                                                                   
                                            np.where(df['big'] == 26, (df['fc26'] - df[['fc20', 'fc21', 'fc22', 'fc23', 'fc24', 'fc25']].max(axis = 1)).astype('timedelta64[D]'),
                                            np.where(df['big'] == 27, (df['fc27'] - df[['fc21', 'fc22', 'fc23', 'fc24', 'fc25', 'fc26']].max(axis = 1)).astype('timedelta64[D]'),
                                            np.where(df['big'] == np.max(df['big'])   1, (pd.to_datetime(today_date) - df[['fc22', 'fc23', 'fc24', 'fc25', 'fc26', 'fc27', 'fc28']].max(axis = 1)).astype('timedelta64[D]'), ...))))))
 

и моя функция, кажется, работает правильно, но проблема в том, что я не хочу обновлять функцию при добавлении нового столбца fcXX .

Знаете ли вы способ, как уменьшить эту функцию? Это должно быть очень полезно.

Ответ №1:

Это черновик решения, потому что я не совсем уверен в паре вопросов:
сколько столбцов вы хотите включить в расчет максимумов[изменяемая сумма с минимальным номером столбца],
каковы максимальные и минимальные значения df.big ,
как обрабатывать max(big) [если до назначения minuent].

Но в целом он масштабируется до произвольного количества столбцов.

О groupby.apply программе : она выполняется только для каждой группы (строк с одинаковым номером big ), а не для каждой строки.

Установка:

 import pandas as pd
import numpy as np
from datetime import datetime as dt

np.random.seed(1234)

def pp_rounded(start, end, n):
    start_u = start.value//10**9
    end_u = end.value//10**9
    output = pd.Series(
        pd.DatetimeIndex(
            (10**9*np.random.randint(start_u, end_u, n, dtype=np.int64)
            ).view('M8[ns]')
        )
    ).dt.floor("1D")
    return output

start = pd.to_datetime('2010-01-01')
end = pd.to_datetime('2020-01-01')
rows_num = 100_000

biglist = np.random.randint(16, 30, rows_num)
df = pd.DataFrame(biglist, columns=["big"])
for i in range(15, 29):
    df[f"fc{i}"] = pp_rounded(start, end, rows_num)

big_max = df.big.max()
col_min = 15 
 

Основные функции:

 def pick_col_names(big_grp):
    global col_min, big_max 
    number_columns_to_include = 6 
    start = min(big_max, big_grp) - 1
    end = max(col_min, start - number_columns_to_include) - 1 
    column_names = [f'fc{j}' for j in range(start, end, -1)]
    return column_names

def calc_time_without_offer(grp, big_max):
    big = grp.big.iloc[0]
    column_names = pick_col_names(big)
    
    minuend = None
    if big >= big_max:
        minuend = pd.to_datetime(dt.today().date())
    else:
        minuend = grp[f"fc{big}"]
        
    grp['time_without_offer'] = minuend - grp[column_names].max(axis=1)
    return grp
 

Использование:

 df = df.groupby("big").apply(calc_time_without_offer, big_max)
df["time_without_offer"]
 

Выходы:

 0       -1175 days
1        -634 days
2       -1233 days
3         108 days
4       -1546 days
           ...    
99995   -1603 days
99996    -457 days
99997    1965 days
99998     882 days
99999   -2390 days
Name: time_without_offer, Length: 100000, dtype: timedelta64[ns]