Apakah ada cara di Pandas untuk menggunakan nilai baris sebelumnya di dataframe.apply saat nilai sebelumnya juga dihitung di apply?

94

Saya memiliki dataframe berikut:

 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

Memerlukan:

 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 Cberasal untuk 2015-01-31dengan mengambil valuedari D.

Kemudian saya perlu menggunakan valuedari Cuntuk 2015-01-31dan kalikan dengan valuedari Apada 2015-02-01dan menambahkan B.

Saya telah mencoba applydan shiftmenggunakan if elseoleh ini memberikan kesalahan kunci.

ctrl-alt-delete
sumber
Mengapa baris terakhir Anda dalam kerangka data berbeda untuk kolom Adan B?
Anton Protopopov
@Anda minta maaf sudah benar sekarang.
ctrl-alt-delete
Berapakah nilai baris berikutnya dalam kolom Adan kolom D?
jezrael
7
Ini pertanyaan yang bagus. Saya memiliki kebutuhan serupa untuk solusi vektorisasi. Alangkah baiknya jika pandas menyediakan versi di apply()mana fungsi pengguna dapat mengakses satu atau beberapa nilai dari baris sebelumnya sebagai bagian dari penghitungannya atau setidaknya mengembalikan nilai yang kemudian diteruskan 'ke dirinya sendiri' pada iterasi berikutnya. Bukankah ini memungkinkan beberapa peningkatan efisiensi dibandingkan dengan loop for?
Tagihan
@Bill, Anda mungkin tertarik dengan jawaban yang baru saja saya tambahkan, numbasering kali merupakan pilihan yang baik di sini.
jpp

Jawaban:

64

Pertama, buat nilai turunan:

df.loc[0, 'C'] = df.loc[0, 'D']

Kemudian lakukan iterasi melalui baris yang tersisa dan isi nilai yang dihitung:

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
Stefan
sumber
41
apakah ada fungsi di panda untuk melakukan ini tanpa loop?
ctrl-alt-delete
1
Sifat berulang dari penghitungan di mana input bergantung pada hasil langkah sebelumnya memperumit vektorisasi. Anda mungkin bisa menggunakan applyfungsi yang melakukan kalkulasi yang sama seperti loop, tetapi di balik layar ini juga akan menjadi loop. pandas.pydata.org/pandas-docs/version/0.17.1/generated/…
Stefan
Jika saya menggunakan loop ini dan menghitung pada dataframe gabungan dan menemukan Nan itu berfungsi tetapi hanya untuk baris dengan Nan. Tidak ada kesalahan yang dilempar, Jika saya mencoba fillNa saya mendapatkan AttributeError: objek 'numpy.float64' tidak memiliki atribut 'fillna' Apakah ada cara untuk melewati baris dengan Nan atau menyetel nilai ke nol?
ctrl-alt-delete
Apakah yang Anda maksud adalah nilai yang hilang di kolom selain C?
Stefan
Ya, solusi Anda baik-baik saja. Saya hanya memastikan saya mengisi Nans di dataframe sebelum loop.
ctrl-alt-delete
41

Diberikan kolom angka:

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

Anda dapat mereferensikan baris sebelumnya dengan 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
kztd
sumber
9
Ini tidak akan membantu dalam situasi ini karena nilai dari baris sebelumnya tidak diketahui di awal. Itu harus dihitung setiap iterasi dan kemudian digunakan di iterasi berikutnya.
Tagihan
6
Saya masih bersyukur atas jawaban ini karena saya menemukan ini, mencari kasus di mana saya tahu nilainya dari baris sebelumnya. Jadi terima kasih @kztd
Kevin Pauli
28

numba

Untuk penghitungan rekursif yang tidak dapat vektor, numbayang menggunakan kompilasi JIT dan bekerja dengan objek tingkat yang lebih rendah, sering kali menghasilkan peningkatan kinerja yang besar. Anda hanya perlu menentukan forloop biasa dan menggunakan dekorator @njitatau (untuk versi yang lebih lama) @jit(nopython=True):

Untuk kerangka data ukuran yang wajar, ini memberikan peningkatan kinerja ~ 30x dibandingkan dengan forloop biasa :

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
sumber
1
Itu mengagumkan! Saya telah mempercepat fungsi saya, yang menghitung nilai dari nilai sebelumnya. Terima kasih!
Artem Malikov
20

Menerapkan fungsi rekursif pada array numpy akan lebih cepat daripada jawaban saat ini.

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

Keluaran

      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

sumber
3
Jawaban ini bekerja dengan sempurna untuk saya dengan perhitungan serupa. Saya mencoba menggunakan kombinasi cumsum dan shift tetapi solusi ini bekerja jauh lebih baik. Terima kasih.
Simon
Ini bekerja juga sempurna untuk saya, terima kasih. Saya berjuang dengan banyak bentuk iterrow, itertuple, apply, dan sebagainya dan ini tampaknya mudah dipahami dan kinerjanya.
chaim
9

Meskipun sudah lama sejak pertanyaan ini diajukan, saya akan memposting jawaban saya dengan harapan dapat membantu seseorang.

Penafian: Saya tahu solusi ini tidak standar , tetapi menurut saya solusi ini berfungsi dengan baik.

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

Jadi pada dasarnya kami menggunakan a applyfrom pandas dan bantuan variabel global yang melacak nilai yang dihitung sebelumnya.


Perbandingan waktu dengan forloop:

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 s ± 114 ms per loop (rata-rata ± std. Dev. Dari 7 run, masing-masing 1 loop)

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 s ± 64,4 ms per loop (rata-rata ± std. Dev. Dari 7 run, masing-masing 1 loop)

Jadi rata-rata 0,57 kali lebih cepat.

iipr
sumber
0

Secara umum, kunci untuk menghindari pengulangan eksplisit adalah menggabungkan (menggabungkan) 2 instance dataframe pada rowindex-1 == rowindex.

Kemudian Anda akan memiliki kerangka data besar yang berisi baris r dan r-1, dari mana Anda dapat melakukan fungsi df.apply ().

Namun, overhead pembuatan kumpulan data besar dapat mengimbangi manfaat pemrosesan paralel ...

HTH Martin

Martin Alley
sumber