การเปรียบเทียบรายการในสองคอลัมน์แถวอย่างชาญฉลาด


16

เมื่อมี Panda DataFrame เช่นนี้:

import pandas as pd
import numpy as np
df = pd.DataFrame({'today': [['a', 'b', 'c'], ['a', 'b'], ['b']], 
                   'yesterday': [['a', 'b'], ['a'], ['a']]})
                 today        yesterday
0      ['a', 'b', 'c']       ['a', 'b']
1           ['a', 'b']            ['a']
2                ['b']            ['a']                          
... etc

แต่มีประมาณ 100,000 รายการฉันกำลังมองหาการเพิ่มและการลบของรายการเหล่านั้นในสองคอลัมน์ในแถวที่ชาญฉลาด

มันเปรียบได้กับคำถามนี้: Pandas: วิธีเปรียบเทียบคอลัมน์ของรายการ Row-wise ใน DataFrame กับ Pandas (ไม่ใช่สำหรับลูป) แต่ฉันกำลังมองหาความแตกต่างและPandas.applyวิธีการดูเหมือนจะไม่เร็วขนาดนั้นสำหรับหลาย ๆ รายการ นี่คือรหัสที่ฉันกำลังใช้อยู่ Pandas.applyด้วยnumpy's setdiff1dวิธีการ:

additions = df.apply(lambda row: np.setdiff1d(row.today, row.yesterday), axis=1)
removals  = df.apply(lambda row: np.setdiff1d(row.yesterday, row.today), axis=1)

ใช้งานได้ดี แต่ใช้เวลาประมาณ 120,000 รายการในหนึ่งนาที ดังนั้นจึงมีวิธีที่เร็วกว่าในการบรรลุเป้าหมายนี้หรือไม่?


หนึ่งรายการมีจำนวนมากที่สุด (ในแถวเดียว) หนึ่งในคอลัมน์เหล่านี้อาจเก็บไว้ได้อย่างไร
thushv89

2
คุณได้ลองวิธีการในโพสต์ที่คุณเชื่อมโยง? สิ่งที่คุณต้องทำคือใช้ความแตกต่างของชุดแทนใช่ไหม?
gold_cy

1
@aws_apprentice โซลูชันนั้นเป็นสิ่งที่ OP มีอยู่ที่นี่
Quang Hoang

Pandas DataFrame อาจไม่ใช่โครงสร้างข้อมูลที่เหมาะสมสำหรับสิ่งนี้ คุณสามารถแบ่งปันพื้นหลังอีกเล็กน้อยในโปรแกรมและข้อมูลได้หรือไม่
AMC

คำตอบ:


14

ไม่แน่ใจเกี่ยวกับประสิทธิภาพ แต่ขาดวิธีการแก้ปัญหาที่ดีกว่านี้อาจนำไปใช้:

temp = df[['today', 'yesterday']].applymap(set)
removals = temp.diff(periods=1, axis=1).dropna(axis=1)
additions = temp.diff(periods=-1, axis=1).dropna(axis=1) 

การลบ:

  yesterday
0        {}
1        {}
2       {a}

เพิ่ม:

  today
0   {c}
1   {b}
2   {b}

2
มันเร็วมาก
rpanai

2
นี่มันเร็วมากจริงๆ มันลงมาประมาณ 2 วินาที!
MegaCookie

2
ว้าวฉันประหลาดใจกับการแสดงด้วยเช่นกันapplymapแต่ดีใจที่มันได้ผลสำหรับคุณ!
r.ook

2
ทีนี้อย่างที่เรารู้ว่าวิธีแก้ปัญหาของโกงนั้นรวดเร็วใคร ๆ ก็สามารถอธิบายให้ฉันฟังได้ ทำไมมันเร็วขึ้น?
Grijesh Chauhan

7
df['today'].apply(set) - df['yesterday'].apply(set)

ขอบคุณ! นี่คือฉันคิดว่าโซลูชันที่อ่านได้มากที่สุด แต่โซลูชันของ r.ook นั้นเร็วกว่าเล็กน้อย
MegaCookie

5

ฉันจะแนะนำให้คุณคำนวณadditionsและremovalsนำไปใช้ภายในเดียวกัน

สร้างตัวอย่างที่ใหญ่กว่า

import pandas as pd
import numpy as np
df = pd.DataFrame({'today': [['a', 'b', 'c'], ['a', 'b'], ['b']], 
                   'yesterday': [['a', 'b'], ['a'], ['a']]})
df = pd.concat([df for i in range(10_000)], ignore_index=True)

ทางออกของคุณ

%%time
additions = df.apply(lambda row: np.setdiff1d(row.today, row.yesterday), axis=1)
removals  = df.apply(lambda row: np.setdiff1d(row.yesterday, row.today), axis=1)
CPU times: user 10.9 s, sys: 29.8 ms, total: 11 s
Wall time: 11 s

วิธีแก้ปัญหาของคุณในครั้งเดียวใช้

%%time
df["out"] = df.apply(lambda row: [np.setdiff1d(row.today, row.yesterday),
                                  np.setdiff1d(row.yesterday, row.today)], axis=1)
df[['additions','removals']] = pd.DataFrame(df['out'].values.tolist(), columns=['additions','removals'])
df = df.drop("out", axis=1)

CPU times: user 4.97 s, sys: 16 ms, total: 4.99 s
Wall time: 4.99 s

การใช้ set

หากคุณไม่มีรายชื่อใหญ่มากคุณก็สามารถหลีกเลี่ยงได้ numpy

def fun(x):
    a = list(set(x["today"]).difference(set(x["yesterday"])))
    b = list((set(x["yesterday"])).difference(set(x["today"])))
    return [a,b]

%%time
df["out"] = df.apply(fun, axis=1)
df[['additions','removals']] = pd.DataFrame(df['out'].values.tolist(), columns=['additions','removals'])
df = df.drop("out", axis=1)

CPU times: user 1.56 s, sys: 0 ns, total: 1.56 s
Wall time: 1.56 s

@ แก้ปัญหาของ r.ook

หากคุณมีความสุขที่ได้ตั้งค่าแทนที่จะเป็นรายการคุณสามารถใช้รหัสของ @ r.ook

%%time
temp = df[['today', 'yesterday']].applymap(set)
removals = temp.diff(periods=1, axis=1).dropna(axis=1)
additions = temp.diff(periods=-1, axis=1).dropna(axis=1) 
CPU times: user 93.1 ms, sys: 12 ms, total: 105 ms
Wall time: 104 ms

@ วิธีแก้ปัญหาของ Andreas K.

%%time
df['additions'] = (df['today'].apply(set) - df['yesterday'].apply(set))
df['removals'] = (df['yesterday'].apply(set) - df['today'].apply(set))

CPU times: user 161 ms, sys: 28.1 ms, total: 189 ms
Wall time: 187 ms

และในที่สุดคุณสามารถเพิ่ม.apply(list)เพื่อรับเอาต์พุตเดียวกัน


1
เปรียบเทียบที่ยอดเยี่ยมที่คุณทำ!
MegaCookie

1

นี่คือส่วนหนึ่งของแนวคิดในการลดปริมาณการประมวลผลส่วนหนึ่งไปยังเครื่องมือ NumPy แบบเวกเตอร์ เราจะรวบรวมข้อมูลทั้งหมดไปยังอาร์เรย์เดียวสำหรับแต่ละส่วนหัวดำเนินการจับคู่ที่จำเป็นทั้งหมดใน NumPy และในที่สุดก็กลับไปที่รายการแถวที่ต้องการ ใน NumPy np.searchsortedที่ไม่เป็นส่วนหนึ่งยกของหนักที่เราจะใช้คร่ำเครียดอยู่บนพื้นฐานของรหัสกลุ่มและรหัสในแต่ละกลุ่มโดยใช้ นอกจากนี้เรายังใช้ตัวเลขเป็นตัวเลขที่เร็วขึ้นสำหรับ NumPy การใช้งานจะมีลักษณะเช่นนี้ -

t = df['today']
y = df['yesterday']
tc = np.concatenate(t)
yc = np.concatenate(y)

tci,tcu = pd.factorize(tc)

tl = np.array(list(map(len,t)))
ty = np.array(list(map(len,y)))

grp_t = np.repeat(np.arange(len(tl)),tl)
grp_y = np.repeat(np.arange(len(ty)),ty)

sidx = tcu.argsort()
idx = sidx[np.searchsorted(tcu,yc,sorter=sidx)]

s = max(tci.max(), idx.max())+1
tID = grp_t*s+tci
yID = grp_y*s+idx

t_mask = np.isin(tID, yID, invert=True)
y_mask = np.isin(yID, tID, invert=True)

t_se = np.r_[0,np.bincount(grp_t,t_mask).astype(int).cumsum()]
y_se = np.r_[0,np.bincount(grp_y,y_mask).astype(int).cumsum()]

Y = yc[y_mask].tolist()
T = tc[t_mask].tolist()

A = pd.Series([T[i:j] for (i,j) in zip(t_se[:-1],t_se[1:])])
R = pd.Series([Y[i:j] for (i,j) in zip(y_se[:-1],y_se[1:])])

การเพิ่มประสิทธิภาพต่อไปเป็นไปได้ที่ขั้นตอนในการคำนวณt_maskและการy_maskที่np.searchsortedสามารถนำมาใช้อีกครั้ง

นอกจากนี้เรายังสามารถใช้การกำหนดอาร์เรย์อย่างง่ายเป็นทางเลือกแทนisinขั้นตอนในการรับt_maskและy_maskเช่น -

M = max(tID.max(), yID.max())+1
mask = np.empty(M, dtype=bool)

mask[tID] = True
mask[yID] = False
t_mask = mask[tID]

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