कुशलता से दो कॉलम पंक्ति-वार सूची की तुलना करना


16

जब पंडों का डेटाफ़्रेम इस तरह से हो:

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.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
क्या आपने उस पोस्ट में उन तरीकों को आजमाया है जिनसे आप जुड़े थे? विशेष रूप से जो सेट चौराहे का उपयोग करते हैं, आपको बस इतना करना होगा कि सेट अंतर का उपयोग करें, नहीं?
सोना_सिंह

1
@aw_apprentice वह समाधान अनिवार्य रूप से ओपी के पास है।
क्वांग होआंग

एक पांडस डेटाफ़्रेम इसके लिए सही डेटा संरचना नहीं हो सकता है। क्या आप कार्यक्रम और डेटा पर थोड़ी अधिक पृष्ठभूमि साझा कर सकते हैं?
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
यह बहुत तेज है।
रपनई r

2
यह वास्तव में बहुत तेज है। यह लगभग 2 सेकंड के लिए नीचे आ गया!
मेगाक्युकी

2
वाह, मैं प्रदर्शन के कारण आश्चर्यचकित हूं applymap, लेकिन खुशी है कि यह आपके लिए काम कर रहा है!
r.ook

2
अब, जैसा कि हम जानते हैं कि बदमाश का समाधान तेज है, क्या कोई मुझे समझा सकता है। यह तेज क्यों था?
बृजेश चौहान

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

@ और के के समाधान

%%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
आप की तुलना में अच्छा है!
मेगाक्युकी

1

यहां वेक्टर किए गए न्यूमपी टूल के लिए गणना वाले भाग को हटाने के विचार के साथ एक है। हम प्रत्येक हेडर के लिए सभी सरणियों के लिए सभी डेटा इकट्ठा करेंगे, सभी आवश्यक NumPy पर मिलान करें और अंत में आवश्यक पंक्ति प्रविष्टियों पर वापस स्लाइस करें। NumPy पर जो भारी उठाने वाला हिस्सा है, हम प्रत्येक समूह के भीतर समूह ID और ID के आधार पर हैशिंग का उपयोग करेंगे 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]
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.