Вы очень сильно взаимодействуете с пандами в своем коде, что кажется плохой идеей с точки зрения производительности.Чтобы сделать его таким же простым в использовании, как и сейчас, pandas должен вести большой бухгалтерский учет, что приводит к снижению производительности.
Мы выполняем все расчеты по порядку, а затем, собрав все строительные блоки, в конце строим фрейм данных.Таким образом, код преобразуется в:
def get_vals(rates, spending_rate):
n = len(rates)
vals_after_spending = np.zeros((n+1, ))
vals_before_spending = np.zeros((n+1, ))
vals_after_spending[0] = 1.0
for i in range(n):
vals_before_spending[i+1] = vals_after_spending[i] * (1 + rates[i])
spending = np.mean(np.array([vals_after_spending[i], vals_before_spending[i+1]])) * spending_rate
vals_after_spending[i+1] = vals_before_spending[i+1] - spending
return vals_before_spending[1:], vals_after_spending[1:]
rates = np.array(port_rets["port_ret"].tolist())
vals_before_spending, vals_after_spending = get_vals(rates, spending_rate)
port_rets = pd.DataFrame({'port_ret': rates, "val_before_spending": vals_before_spending, "val_after_spending": vals_after_spending})
Мы можем еще больше улучшить с помощью JIT-компиляции кода, поскольку петли Python работают медленно.Ниже я использую numba:
import numba as nb
@nb.njit(cache=True) # as easy as putting this decorator
def get_vals(rates, spending_rate):
n = len(rates)
vals_after_spending = np.zeros((n+1, ))
vals_before_spending = np.zeros((n+1, ))
# ... code remains same, we are just compiling the function
Если мы рассмотрим случайный список показателей, подобный этому:
port_rets = pd.DataFrame({'port_ret': np.random.uniform(low=-1.0, high=1.0, size=(100000,))})
Мы получим сравнение производительности:
Вашкод: 15.758 с
get_vals: 1.407 с
JITed get_vals: 0,093 с (при втором запуске со скидкой на время компиляции)