Чи є у 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допомогою цього видає ключову помилку.


Чому ваші останні рядки в кадрах даних відрізняються для стовпців Aі B?
Антон Протопопов

@Anton вибачається за правильність.
ctrl-alt-delete

Яке значення має наступний рядок у стовпці Aта стовпці D?
jezrael

7
Це гарне запитання. Мені схожа потреба у векторизованому рішенні. Було б непогано, якщо б панди надали версію, 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

Ви можете посилатися на попередній рядок зі зміщенням:

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

1
Це чудово! Я пришвидшив свою функцію, яка відлічує значення від попередніх значень. Дякую!
Артем Маліков

21

Застосування рекурсивної функції до масивів 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, але це рішення працює набагато краще. Дякую.
Саймон

Це також ідеально для мене працює, дякую. Я боровся з багатьма формами iterrow, itertuples, 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

Отже, в основному ми використовуємо a applyвід 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 Мартін

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.