Есть ли способ в Pandas использовать предыдущее значение строки в dataframe.apply, когда предыдущее значение также вычисляется в приложении?

94

У меня есть следующий фреймворк:

 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при этом дает ключевую ошибку.

ctrl-alt-delete
источник
Почему ваши последние строки в фреймах данных отличаются для столбцов Aи B?
Антон Протопопов
@Anton извиняется, теперь все правильно.
ctrl-alt-delete
Какое значение имеет следующая строка в столбце Aи столбце D?
jezrael
7
Это хороший вопрос. У меня аналогичная потребность в векторизованном решении. Было бы неплохо, если бы pandas предоставил версию, в apply()которой пользовательская функция может получить доступ к одному или нескольким значениям из предыдущей строки как часть своего расчета или, по крайней мере, вернуть значение, которое затем передается «себе» на следующей итерации. Разве это не позволило бы повысить эффективность по сравнению с циклом for?
Билл
@Bill, Вам может быть интересен этот ответ, который я только что добавил, numbaчасто здесь хороший вариант.
jpp

Ответы:

64

Сначала создайте производное значение:

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
Стефан
источник
41
есть ли в пандах функция для этого без цикла?
ctrl-alt-delete
1
Итерационный характер расчета, когда входные данные зависят от результатов предыдущих шагов, усложняет векторизацию. Возможно, вы могли бы использовать applyфункцию, которая выполняет те же вычисления, что и цикл, но за кулисами это также будет цикл. pandas.pydata.org/pandas-docs/version/0.17.1/generated/…
Стефан
Если я использую этот цикл и вычисляю на объединенном фрейме данных, и он находит Nan, он работает, но только для строки с Nan. Никаких ошибок не возникает. Если я попробую fillNa, я получаю AttributeError: объект 'numpy.float64' не имеет атрибута 'fillna'. Есть ли способ пропустить строку с Nan или установить нулевые значения?
ctrl-alt-delete
Вы имеете в виду отсутствующие значения в столбцах, кроме C?
Стефан
Да, ваше решение в порядке. Я просто проверяю, что заполняю Nans в кадре данных перед циклом.
ctrl-alt-delete
41

Учитывая столбец чисел:

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
кзтд
источник
9
В данной ситуации это не поможет, потому что значение из предыдущей строки вначале неизвестно. Его необходимо вычислять на каждой итерации, а затем использовать в следующей итерации.
Билл
6
Я до сих пор благодарен за этот ответ, потому что наткнулся на него в поисках случая, когда мне известно значение из предыдущей строки. Так что спасибо @kztd
Кевин Паули
28

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
jpp
источник
1
Это чудесно! Я ускорил свою функцию, которая считает значения из предыдущих значений. Благодарность!
Артем Маликов
20

Применение рекурсивной функции к массивам 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

источник
3
Этот ответ отлично работает для меня с аналогичным расчетом. Я пробовал использовать комбинацию cumsum и shift, но это решение работает намного лучше. Спасибо.
Саймон
Это тоже идеально подходит для меня, спасибо. Я боролся со многими формами iterrows, iterrows, apply и т. Д., И это кажется простым для понимания и производительности.
Хаим
9

Хотя этот вопрос был задан давно, я опубликую свой ответ в надежде, что он кому-то поможет.

Отказ от ответственности: я знаю, что это нестандартное решение , но я думаю, что оно работает хорошо.

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

Итак, в основном мы используем applyfrom 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 раза быстрее.

иипр
источник
0

В общем, ключом к тому, чтобы избежать явного цикла, было бы объединить (объединить) 2 экземпляра фрейма данных в rowindex-1 == rowindex.

Тогда у вас будет большой фрейм данных, содержащий строки r и r-1, откуда вы можете выполнить функцию df.apply ().

Однако накладные расходы на создание большого набора данных могут свести на нет преимущества параллельной обработки ...

HTH Martin

Мартин Элли
источник