Применение произвольной функции к строкам dataframe накопительным итогом
У меня есть DataFrame с двумя столбцами 'total', 'cashflow'.
timestamp total cashflow
2020-06-11 300.61 -300.61
2020-06-12 304.21 0.00
2020-06-15 307.05 0.00
2020-06-16 313.86 0.00
Есть функция, которую нужно применить к DataFrame[:1], DataFrame[:2], DataFrame[:3] ... DataFrame[:n], где n - количество строк в DataFrame. При этом в каждом вложенном DataFrame перед применением функции необходимо добавить к значению в последней строке столбца 'cashflow' значение столбца 'total' из той же строки и отфильтровать все нулевые значения. В итоге нужно получить список значений функции для каждого из вложенных DataFrame. Я дошел до реализации через цикл, но код работает очень долго.
data_xirr_1 = []
df_xirr_copy = data_cashflow['cashflow'].copy()
df_x0 = df_xirr_copy[:1]
for i in data_cashflow.index[1:]:
df_x0 = df_x0.append(df_xirr_copy[df_xirr_copy.index == i])
df_xirr = df_x0.copy()
df_xirr[-1] += data_cashflow['total'][i]
data_xirr_1 += [xirr_1(df_xirr.loc[np.abs(df_xirr) > 1E-10])]
Сама функция работает сравнительно быстро.
from scipy import optimize as op
def xnpv(rate, cashflows):
chron_order = sorted(cashflows, key=lambda x: x[0])
t0 = chron_order[0][0] # t0 is the date of the first cash flow
return sum(cf / (1 + rate) ** ((t - t0).days / 365.0) for (t, cf) in chron_order)
def xirr_1(cashflow, guess=0.001):
return op.newton(lambda r: xnpv(r, [(x, cashflow.loc[x]) for x in cashflow.index]), guess)
В результате получается список с n-1 значений, где n - количество строк в изначальном DataFrame
[76.10327236009363, 5.918598501439627, 22.306438719895517]
Есть какие-либо решения, которые могут ускорить применение функции накопительно ко всем вложенным dataframe?
Ответы (1 шт):
Первый способ, который я нашел - векторизация. Перевел данные в numpy и так же посчитал в цикле.
На длине массива в 2630 строк, получил выигрыш почти в 8 раз. Но все равно как то долго. Есть ли какие либо еще верхнеуровневые подходы к ускорению?
Pandas в цикле:
time_1 = time.time()
data_xirr_1 = []
df_xirr_copy = data_cashflow['cashflow'].copy()
df_x0 = df_xirr_copy[:1]
for i in data_cashflow.index[1:]:
df_x0 = df_x0.append(df_xirr_copy[df_xirr_copy.index == i])
df_xirr = df_x0.copy()
df_xirr[-1] += data_cashflow['total'][i]
data_xirr_1 += [xirr_1(df_xirr.loc[np.abs(df_xirr) > 1E-10])]
print(f'pandas {len(data_xirr_1)} {time.time() - time_1:.2f} сек.')
Numpy в цикле:
time_1 = time.time()
df_t = data_cashflow.copy()
df_t['date'] = data_cashflow.index
arr_cashflow = df_t[['cashflow', 'date']].to_numpy()
arr_xirr = []
for i in range(1,len(arr_cashflow)):
arr_cf = arr_cashflow[:i+1].copy()
arr_cf[i, 0] += data_cashflow['total'].iloc[i]
arr_xirr += [xirr([(d,x) for x,d in arr_cf[np.abs(arr_cf[:, 0]) > 1E-10]])]
print(f'{len(arr_xirr)} {time.time() - time_1:.2f} сек')
Вывод:
pandas 2630 16.54 сек.
numpy 2630 2.52 сек