У меня есть следующий фреймворк:
Index_Date A B C D
===============================
2015-01-31 10 10 Nan 10
2015-02-01 2 3 Nan 22
2015-02-02 10 60 Nan 280
2015-02-03 10 100 Nan 250
Требовать:
Index_Date A B C D
===============================
2015-01-31 10 10 10 10
2015-02-01 2 3 23 22
2015-02-02 10 60 290 280
2015-02-03 10 100 3000 250
Column C
выводится 2015-01-31
путем взятия value
из D
.
Тогда мне нужно использовать value
из C
для 2015-01-31
и умножить на value
из A
на 2015-02-01
и добавить B
.
Я прибегла к apply
и shift
используя if else
при этом дает ключевую ошибку.
A
иB
?A
и столбцеD
?apply()
которой пользовательская функция может получить доступ к одному или нескольким значениям из предыдущей строки как часть своего расчета или, по крайней мере, вернуть значение, которое затем передается «себе» на следующей итерации. Разве это не позволило бы повысить эффективность по сравнению с циклом for?numba
часто здесь хороший вариант.Ответы:
Сначала создайте производное значение:
df.loc[0, 'C'] = df.loc[0, 'D']
Затем выполните итерацию по оставшимся строкам и заполните рассчитанные значения:
for i in range(1, len(df)): df.loc[i, 'C'] = df.loc[i-1, 'C'] * df.loc[i, 'A'] + df.loc[i, 'B'] Index_Date A B C D 0 2015-01-31 10 10 10 10 1 2015-02-01 2 3 23 22 2 2015-02-02 10 60 290 280
источник
apply
функцию, которая выполняет те же вычисления, что и цикл, но за кулисами это также будет цикл. pandas.pydata.org/pandas-docs/version/0.17.1/generated/…C
?Учитывая столбец чисел:
lst = [] cols = ['A'] for a in range(100, 105): lst.append([a]) df = pd.DataFrame(lst, columns=cols, index=range(5)) df A 0 100 1 101 2 102 3 103 4 104
Вы можете ссылаться на предыдущую строку с помощью shift:
df['Change'] = df.A - df.A.shift(1) df A Change 0 100 NaN 1 101 1.0 2 102 1.0 3 103 1.0 4 104 1.0
источник
numba
Для рекурсивных вычислений, не подлежащих векторизации,
numba
которые используют JIT-компиляцию и работают с объектами более низкого уровня, часто приводят к значительному повышению производительности. Вам нужно только определить обычныйfor
цикл и использовать декоратор@njit
или (для более старых версий)@jit(nopython=True)
:Для фрейма данных разумного размера это дает примерно 30-кратное повышение производительности по сравнению с обычным
for
циклом:from numba import jit @jit(nopython=True) def calculator_nb(a, b, d): res = np.empty(d.shape) res[0] = d[0] for i in range(1, res.shape[0]): res[i] = res[i-1] * a[i] + b[i] return res df['C'] = calculator_nb(*df[list('ABD')].values.T) n = 10**5 df = pd.concat([df]*n, ignore_index=True) # benchmarking on Python 3.6.0, Pandas 0.19.2, NumPy 1.11.3, Numba 0.30.1 # calculator() is same as calculator_nb() but without @jit decorator %timeit calculator_nb(*df[list('ABD')].values.T) # 14.1 ms per loop %timeit calculator(*df[list('ABD')].values.T) # 444 ms per loop
источник
Применение рекурсивной функции к массивам numpy будет быстрее, чем текущий ответ.
df = pd.DataFrame(np.repeat(np.arange(2, 6),3).reshape(4,3), columns=['A', 'B', 'D']) new = [df.D.values[0]] for i in range(1, len(df.index)): new.append(new[i-1]*df.A.values[i]+df.B.values[i]) df['C'] = new
Выход
A B D C 0 1 1 1 1 1 2 2 2 4 2 3 3 3 15 3 4 4 4 64 4 5 5 5 325
источник
Хотя этот вопрос был задан давно, я опубликую свой ответ в надежде, что он кому-то поможет.
Отказ от ответственности: я знаю, что это нестандартное решение , но я думаю, что оно работает хорошо.
import pandas as pd import numpy as np data = np.array([[10, 2, 10, 10], [10, 3, 60, 100], [np.nan] * 4, [10, 22, 280, 250]]).T idx = pd.date_range('20150131', end='20150203') df = pd.DataFrame(data=data, columns=list('ABCD'), index=idx) df A B C D ================================= 2015-01-31 10 10 NaN 10 2015-02-01 2 3 NaN 22 2015-02-02 10 60 NaN 280 2015-02-03 10 100 NaN 250 def calculate(mul, add): global value value = value * mul + add return value value = df.loc['2015-01-31', 'D'] df.loc['2015-01-31', 'C'] = value df.loc['2015-02-01':, 'C'] = df.loc['2015-02-01':].apply(lambda row: calculate(*row[['A', 'B']]), axis=1) df A B C D ================================= 2015-01-31 10 10 10 10 2015-02-01 2 3 23 22 2015-02-02 10 60 290 280 2015-02-03 10 100 3000 250
Итак, в основном мы используем
apply
from pandas и помощь глобальной переменной, которая отслеживает предыдущее вычисленное значение.Сравнение времени с
for
циклом:data = np.random.random(size=(1000, 4)) idx = pd.date_range('20150131', end='20171026') df = pd.DataFrame(data=data, columns=list('ABCD'), index=idx) df.C = np.nan df.loc['2015-01-31', 'C'] = df.loc['2015-01-31', 'D'] %%timeit for i in df.loc['2015-02-01':].index.date: df.loc[i, 'C'] = df.loc[(i - pd.DateOffset(days=1)).date(), 'C'] * df.loc[i, 'A'] + df.loc[i, 'B']
3,2 с ± 114 мс на цикл (среднее ± стандартное отклонение из 7 прогонов, по 1 циклу)
data = np.random.random(size=(1000, 4)) idx = pd.date_range('20150131', end='20171026') df = pd.DataFrame(data=data, columns=list('ABCD'), index=idx) df.C = np.nan def calculate(mul, add): global value value = value * mul + add return value value = df.loc['2015-01-31', 'D'] df.loc['2015-01-31', 'C'] = value %%timeit df.loc['2015-02-01':, 'C'] = df.loc['2015-02-01':].apply(lambda row: calculate(*row[['A', 'B']]), axis=1)
1,82 с ± 64,4 мс на цикл (среднее ± стандартное отклонение из 7 прогонов, по 1 циклу в каждом)
То есть в среднем в 0,57 раза быстрее.
источник
В общем, ключом к тому, чтобы избежать явного цикла, было бы объединить (объединить) 2 экземпляра фрейма данных в rowindex-1 == rowindex.
Тогда у вас будет большой фрейм данных, содержащий строки r и r-1, откуда вы можете выполнить функцию df.apply ().
Однако накладные расходы на создание большого набора данных могут свести на нет преимущества параллельной обработки ...
HTH Martin
источник