มีวิธีใน Pandas ที่จะใช้ค่าแถวก่อนหน้าใน dataframe.apply เมื่อคำนวณค่าก่อนหน้าในการนำไปใช้ด้วยหรือไม่


94

ฉันมี dataframe ต่อไปนี้:

 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โดยการใช้ของvalueD

แล้วฉันจำเป็นต้องใช้valueของCสำหรับ2015-01-31และคูณด้วยvalueของAในและเพิ่ม2015-02-01B

ฉันได้ลองapplyและshiftโดยใช้สิ่งif elseนี้ทำให้เกิดข้อผิดพลาดที่สำคัญ


เหตุใดแถวสุดท้ายของคุณในดาต้าเฟรมจึงแตกต่างกันสำหรับคอลัมน์Aและ B?
Anton Protopopov

@ แอนตันขอโทษตอนนี้ถูกต้องแล้ว
ctrl-alt-delete

ค่าของแถวถัดไปในคอลัมน์Aและคอลัมน์Dคืออะไร?
jezrael

7
นี่เป็นคำถามที่ดี ฉันมีความต้องการโซลูชันแบบเวกเตอร์เหมือนกัน คงจะดีไม่น้อยหากแพนด้าจัดเตรียมเวอร์ชันapply()ที่ฟังก์ชันของผู้ใช้สามารถเข้าถึงค่าตั้งแต่หนึ่งค่าขึ้นไปจากแถวก่อนหน้าโดยเป็นส่วนหนึ่งของการคำนวณหรืออย่างน้อยก็ส่งคืนค่าที่ส่งผ่าน 'ให้กับตัวมันเอง' ในการทำซ้ำครั้งถัดไป สิ่งนี้จะไม่ทำให้ประสิทธิภาพเพิ่มขึ้นเมื่อเทียบกับ for loop หรือไม่?
บิล

@ บิลคุณอาจสนใจคำตอบนี้ที่ฉันเพิ่งเพิ่มไป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/…
Stefan

ถ้าฉันใช้ลูปนี้และคำนวณบนดาต้าเฟรมที่ผสานและพบว่าน่านใช้ได้ แต่กับแถวที่มีน่านเท่านั้น ไม่มีข้อผิดพลาดเกิดขึ้นหากฉันลอง FillNa ฉันได้รับ AttributeError: วัตถุ 'numpy.float64' ไม่มีแอตทริบิวต์ 'fillna' มีวิธีใดบ้างที่จะข้ามแถวด้วย Nan หรือตั้งค่าเป็นศูนย์
ctrl-alt-delete

คุณหมายถึงไม่มีค่าในคอลัมน์อื่นที่ไม่ใช่Cหรือ
Stefan

ใช่วิธีแก้ปัญหาของคุณเรียบร้อยดี ฉันแค่แน่ใจว่าฉันเติม 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
Kevin Pauli

28

numba

สำหรับการคำนวณแบบวนซ้ำซึ่งไม่สามารถใช้เวกเตอร์numbaได้ซึ่งใช้การคอมไพล์ JIT และทำงานกับออบเจ็กต์ระดับล่างมักจะได้รับการปรับปรุงประสิทธิภาพอย่างมาก คุณต้องกำหนดเฉพาะforลูปปกติและใช้มัณฑนากร@njitหรือ (สำหรับเวอร์ชันเก่า) @jit(nopython=True):

สำหรับดาต้าเฟรมขนาดที่เหมาะสมสิ่งนี้จะช่วยปรับปรุงประสิทธิภาพ ~ 30x เมื่อเทียบกับ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
มันเยียมมาก! ฉันได้เร่งฟังก์ชันของฉันซึ่งจะนับค่าจากค่าก่อนหน้า ขอบคุณ!
Artem Malikov

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 แต่วิธีนี้ใช้ได้ผลดีกว่ามาก ขอบคุณ.
Simon

การทำงานนี้สมบูรณ์แบบสำหรับฉันด้วยขอบคุณ ฉันกำลังดิ้นรนกับ iterrows หลายรูปแบบ itertuples ใช้และอื่น ๆ และดูเหมือนว่าจะเข้าใจง่ายและมีประสิทธิภาพ
chaim

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จากแพนด้าและความช่วยเหลือของตัวแปรส่วนกลางที่ติดตามค่าที่คำนวณก่อนหน้านี้


การเปรียบเทียบเวลากับการ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 s ± 114 ms ต่อลูป (ค่าเฉลี่ย± std. dev. ของการรัน 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 มิลลิวินาทีต่อลูป (ค่าเฉลี่ย± std. dev ของ 7 รัน, 1 ลูปแต่ละครั้ง)

เร็วขึ้น 0.57 เท่าโดยเฉลี่ย


0

โดยทั่วไปกุญแจสำคัญในการหลีกเลี่ยงการวนซ้ำอย่างชัดเจนคือการเข้าร่วม (ผสาน) 2 อินสแตนซ์ของดาต้าเฟรมบน rowindex-1 == rowindex

จากนั้นคุณจะมี dataframe ขนาดใหญ่ที่มีแถวของ r และ r-1 ซึ่งคุณสามารถทำฟังก์ชัน df.apply () ได้

อย่างไรก็ตามค่าใช้จ่ายในการสร้างชุดข้อมูลขนาดใหญ่อาจหักล้างประโยชน์ของการประมวลผลแบบขนาน ...

HTH มาร์ติน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.