Pandas: สร้างคอลัมน์ใหม่สองคอลัมน์ในดาต้าเฟรมด้วยค่าที่คำนวณจากคอลัมน์ที่มีอยู่แล้ว


100

ฉันกำลังทำงานกับไลบรารีแพนด้าและต้องการเพิ่มคอลัมน์ใหม่สองคอลัมน์ในดาต้าเฟรมdfมี n คอลัมน์ (n> 0)
คอลัมน์ใหม่เหล่านี้เป็นผลมาจากการประยุกต์ใช้ฟังก์ชันกับคอลัมน์ใดคอลัมน์หนึ่งในดาต้าเฟรม

ฟังก์ชั่นที่จะใช้มีดังนี้:

def calculate(x):
    ...operate...
    return z, y

วิธีหนึ่งในการสร้างคอลัมน์ใหม่สำหรับฟังก์ชันที่ส่งคืนเฉพาะค่าคือ:

df['new_col']) = df['column_A'].map(a_function)

ดังนั้นสิ่งที่ฉันต้องการและพยายามอย่างไม่ประสบความสำเร็จ (*) ก็คือ:

(df['new_col_zetas'], df['new_col_ys']) = df['column_A'].map(calculate)

วิธีที่ดีที่สุดในการทำสิ่งนี้ให้สำเร็จคืออะไร? ฉันสแกนเอกสารโดยไม่มีเงื่อนงำ

** df['column_A'].map(calculate)ส่งคืนชุดหมีแพนด้าแต่ละรายการประกอบด้วยทูเพิล z, y และการพยายามกำหนดสิ่งนี้ให้กับคอลัมน์ดาต้าเฟรมสองคอลัมน์จะทำให้เกิด ValueError *

คำตอบ:


119

ฉันจะใช้zip:

In [1]: from pandas import *

In [2]: def calculate(x):
   ...:     return x*2, x*3
   ...: 

In [3]: df = DataFrame({'a': [1,2,3], 'b': [2,3,4]})

In [4]: df
Out[4]: 
   a  b
0  1  2
1  2  3
2  3  4

In [5]: df["A1"], df["A2"] = zip(*df["a"].map(calculate))

In [6]: df
Out[6]: 
   a  b  A1  A2
0  1  2   2   3
1  2  3   4   6
2  3  4   6   9

ขอบคุณเยี่ยมมากมันใช้งานได้ ฉันไม่พบสิ่งนี้ในเอกสารสำหรับ 0.8.1 ... ฉันคิดว่าฉันควรจะคิดว่าซีรีส์เป็นรายการสิ่งที่ดึงดูดใจเสมอ ...
joaquin

มีประสิทธิภาพที่แตกต่างกันในการทำสิ่งนี้แทนหรือไม่ zip (* map (คำนวณ, df ["a"])) แทน zip (* df ["a"]. map (คำนวณ)) ซึ่งให้ (ตามด้านบน) [(2, 4, 6), ( 3, 6, 9)]?
ekta

1
ฉันได้รับคำเตือนต่อไปนี้เมื่อทำการสร้างคอลัมน์ใหม่เช่นนี้: "SettingWithCopyWarning: มีการพยายามตั้งค่าบนสำเนาของชิ้นส่วนจาก DataFrame ลองใช้. loc [row_indexer, col_indexer] = value แทน" ฉันควรจะกังวลไหม pandas v.0.15
taras

47

คำตอบด้านบนมีข้อบกพร่องในความคิดของฉัน หวังว่าไม่มีใครมวลการนำเข้าทั้งหมดของหมีแพนด้าเข้าไปใน namespace from pandas import *ของพวกเขาด้วย นอกจากนี้mapควรสงวนวิธีการไว้สำหรับช่วงเวลาดังกล่าวเมื่อส่งผ่านพจนานุกรมหรือซีรีส์ สามารถใช้ฟังก์ชันได้ แต่นี่คือสิ่งที่applyใช้สำหรับ

ดังนั้นถ้าคุณต้องใช้แนวทางข้างต้นฉันจะเขียนแบบนี้

df["A1"], df["A2"] = zip(*df["a"].apply(calculate))

ไม่มีเหตุผลที่จะใช้ zip ที่นี่ คุณสามารถทำได้ง่ายๆ:

df["A1"], df["A2"] = calculate(df['a'])

วิธีที่สองนี้เร็วกว่ามากใน DataFrames ขนาดใหญ่

df = pd.DataFrame({'a': [1,2,3] * 100000, 'b': [2,3,4] * 100000})

DataFrame สร้างด้วย 300,000 แถว

%timeit df["A1"], df["A2"] = calculate(df['a'])
2.65 ms ± 92.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit df["A1"], df["A2"] = zip(*df["a"].apply(calculate))
159 ms ± 5.24 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

เร็วกว่าซิป 60 เท่า


โดยทั่วไปควรหลีกเลี่ยงการใช้ Apply

โดยทั่วไปการสมัครจะไม่เร็วไปกว่าการทำซ้ำในรายการ Python มาทดสอบประสิทธิภาพของ for-loop เพื่อทำสิ่งเดียวกันกับด้านบน

%%timeit
A1, A2 = [], []
for val in df['a']:
    A1.append(val**2)
    A2.append(val**3)

df['A1'] = A1
df['A2'] = A2

298 ms ± 7.14 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

นี่จึงช้าเป็นสองเท่าซึ่งไม่ใช่การถดถอยของประสิทธิภาพที่แย่มาก แต่ถ้าเราไซ ธ อนข้างต้นเราจะได้ประสิทธิภาพที่ดีขึ้นมาก สมมติว่าคุณกำลังใช้ ipython:

%load_ext cython

%%cython
cpdef power(vals):
    A1, A2 = [], []
    cdef double val
    for val in vals:
        A1.append(val**2)
        A2.append(val**3)

    return A1, A2

%timeit df['A1'], df['A2'] = power(df['a'])
72.7 ms ± 2.16 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

มอบหมายโดยตรงโดยไม่ต้องใช้

คุณจะได้รับการปรับปรุงความเร็วให้ดียิ่งขึ้นหากคุณใช้การดำเนินการแบบเวกเตอร์โดยตรง

%timeit df['A1'], df['A2'] = df['a'] ** 2, df['a'] ** 3
5.13 ms ± 320 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

สิ่งนี้ใช้ประโยชน์จากการดำเนินการเวกเตอร์ที่รวดเร็วมากของ NumPy แทนการวนซ้ำของเรา ตอนนี้เรามีสปีดอัพ 30 เท่าจากต้นฉบับ


การทดสอบความเร็วที่ง่ายที่สุดด้วย apply

ตัวอย่างข้างต้นควรแสดงให้เห็นอย่างชัดเจนว่าapplyสามารถทำได้ช้าเพียงใด แต่เพื่อให้ชัดเจนเป็นพิเศษลองดูตัวอย่างพื้นฐานที่สุด ลองยกชุดตัวเลข 10 ล้านตัวเลขที่มีและไม่มีใช้

s = pd.Series(np.random.rand(10000000))

%timeit s.apply(calc)
3.3 s ± 57.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

โดยไม่ต้องสมัครจะเร็วขึ้น 50 เท่า

%timeit s ** 2
66 ms ± 2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

1
นี่คือคำตอบที่ยอดเยี่ยมจริงๆ ฉันอยากถาม: คุณคิดอย่างไรapplymapกับกรณีนี้เมื่อคุณต้องใช้ฟังก์ชันเฉพาะกับแต่ละองค์ประกอบของดาต้าเฟรม
David

3
แม้ว่าจะมีคำแนะนำที่ดีในคำตอบนี้ แต่ฉันเชื่อว่าคำแนะนำหลักในการใช้func(series)แทนseries.apply(func)จะใช้ได้เฉพาะเมื่อ func ถูกกำหนดทั้งหมดโดยใช้การดำเนินการที่ทำงานคล้ายกันทั้งในแต่ละค่าและในซีรี่ส์ นั่นเป็นกรณีตัวอย่างในคำตอบแรก แต่ไม่ใช่ในกรณีของคำถามของ OP ซึ่งถามโดยทั่วไปเกี่ยวกับการใช้ฟังก์ชันกับคอลัมน์ 1/2
Graham Lea

1
ตัวอย่างเช่นถ้าเป็น DF: DataFrame({'a': ['Aaron', 'Bert', 'Christopher'], 'b': ['Bold', 'Courageous', 'Distrusted']})และcalcเป็น: def calc(x): return x[0], len(x)แล้วtdf.a.apply(calc))และcalc(tdf.a)กลับสิ่งที่แตกต่างกันมาก
Graham Lea
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.