Listeleri iki sütunda verimli bir şekilde karşılaştırma


16

Böyle bir Panda DataFrame'iniz olduğunda:

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

Ancak yaklaşık 100 000 girişle, bu listelerin eklerini ve kaldırılmasını iki sütunda satır bazında bulmak istiyorum.

Şu soru ile karşılaştırılabilir: Pandalar: Bir DataFrame'deki Listelerin Sütunlarını Pandalarla (döngü için değil) nasıl karşılaştırırım? ancak farklılıklara bakıyorum ve Pandas.applyyöntem bu kadar çok giriş için o kadar hızlı görünmüyor. Şu anda kullandığım kod bu. Pandas.applyile numpy's setdiff1dyöntem:

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)

Bu iyi çalışır, ancak 120.000 giriş için yaklaşık bir dakika sürer. Bunu başarmanın daha hızlı bir yolu var mı?


Bu sütunlardan birinde en fazla kaç öğe (tek bir satırda) tutulabilir?
thushv89

2
bu yazıdaki bağlantı kurduğunuz yöntemleri denediniz mi? özellikle set kavşağı kullananlarda, tek yapmanız gereken set farkı kullanmaktır, değil mi?
gold_cy

1
@aws_apprentice bu çözüm aslında OP'nin burada sahip olduğu şeydir.
Quang Hoang

Bir Pandas DataFrame bunun için doğru veri yapısı olmayabilir. Program ve veriler hakkında biraz daha fazla bilgi paylaşabilir misiniz?
AMC

Yanıtlar:


14

Performans konusunda emin değilim, ancak daha iyi bir çözüm olmadığında bu geçerli olabilir:

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) 

Kaldırma:

  yesterday
0        {}
1        {}
2       {a}

İlaveler:

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

2
Bu çok hızlı.
rpanai

2
Bu gerçekten çok hızlı. Yaklaşık 2 saniyeye indi!
MegaCookie

2
Vay canına, performansından dolayı da şaşırdım applymap, ama senin için çalıştığına sevindim!
r.ook

2
Şimdi, rook'un çözümünün hızlı olduğunu bildiğimiz gibi, biri bana açıklayabilir mi? Neden daha hızlıydı?
Grijesh Chauhan

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

Teşekkürler! Bu en okunabilir çözüm olduğunu düşünüyorum, ancak r.ook'un çözümü biraz daha hızlı.
MegaCookie

5

Hesaplamanızı additionsve removalsaynısını uygulamanızı öneririm .

Daha büyük bir örnek oluşturun

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)

Çözümünüz

%%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

Tek bir başvuruda çözümünüz

%%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

kullanma set

Listeleriniz çok büyük olmadıkça, 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'un çözümü

Çıktı olarak liste yerine set almaktan memnunsanız @ r.ook'un kodunu kullanabilirsiniz

%%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.'nın çözümü

%%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

ve sonunda .apply(list)aynı çıktıyı elde etmek için ekleyebilirsiniz


1
Yaptığınız harika karşılaştırma!
MegaCookie

1

İşte hesaplama parçasını vectorized NumPy araçlarına boşaltma fikri. Tüm verileri her başlık için tek diziler halinde toplayacağız, NumPy'de gerekli tüm eşleşmeleri gerçekleştireceğiz ve son olarak gerekli satır girişlerine geri dilimleyeceğiz. Ağır kaldırma kısmını yapan NumPy'de, kullanarak her gruptaki grup kimlikleri ve kimlikleri temel alan karma kullanacağız np.searchsorted. NumPy ile daha hızlı olduğu için sayıları da kullanıyoruz. Uygulama şöyle görünecektir -

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:])])

Hesaplama adımlarında t_maskve y_masknerede np.searchsortedtekrar kullanılabileceği konusunda daha fazla optimizasyon mümkündür .

Ayrıca, alma isinadımına alternatif olarak basit bir dizi ataması da kullanabiliriz t_maskve bunun y_maskgibi -

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]
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.