So sánh danh sách trong hai cột một cách hiệu quả


16

Khi có một DataFrame như thế này:

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

Nhưng với khoảng 100 000 mục, tôi đang tìm kiếm các bổ sung và loại bỏ các danh sách đó trong hai cột trên cơ sở hàng khôn ngoan.

Nó có thể so sánh với câu hỏi này: Pandas: Làm thế nào để so sánh các cột của danh sách Row-khôn ngoan trong DataFrame với Pandas (không phải cho vòng lặp)? nhưng tôi đang xem xét sự khác biệt và Pandas.applyphương pháp dường như không nhanh cho nhiều mục như vậy. Đây là mã mà tôi hiện đang sử dụng. Pandas.applyvới numpy's setdiff1dphương pháp:

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)

Điều này hoạt động tốt, tuy nhiên phải mất khoảng một phút cho 120 000 mục. Vì vậy, có một cách nhanh hơn để thực hiện điều này?


Có bao nhiêu mục tối đa (trong một hàng) một trong những cột này có thể chứa?
thushv89

2
Bạn đã thử các phương pháp trong bài đăng mà bạn liên kết chưa? cụ thể là những cái sử dụng giao điểm thiết lập, tất cả những gì bạn sẽ phải làm là sử dụng khác biệt thiết lập thay thế, phải không?
gold_cy

1
@aws_apprentice giải pháp đó thực chất là những gì OP có ở đây.
Quang Hoàng

Một DataFrame Pandas có thể không phải là cấu trúc dữ liệu phù hợp cho việc này. Bạn có thể chia sẻ thêm một chút nền tảng về chương trình và dữ liệu không?
AMC

Câu trả lời:


14

Không chắc chắn về hiệu suất, nhưng khi thiếu một giải pháp tốt hơn, điều này có thể áp dụng:

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) 

Xóa:

  yesterday
0        {}
1        {}
2       {a}

Bổ sung:

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

2
Điều này rất nhanh.
rpanai

2
Điều này thực sự rất nhanh. Nó giảm xuống còn khoảng 2 giây!
MegaCookie

2
Wow, tôi rất ngạc nhiên về hiệu suất cũng do applymap, nhưng rất vui vì nó hiệu quả với bạn!
r.ook

2
Bây giờ, như chúng ta đã biết giải pháp của rook rất nhanh, Ai đó có thể giải thích cho tôi không. Tại sao nó nhanh hơn?
Grijesh Chauhan

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

Cảm ơn! Đây là giải pháp dễ đọc nhất, tuy nhiên giải pháp của r.ook nhanh hơn một chút.
MegaCookie

5

Tôi sẽ đề nghị bạn tính toán additionsremovalstrong cùng áp dụng.

Tạo một ví dụ lớn hơn

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)

Giải pháp của bạn

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

Giải pháp của bạn trong một lần áp dụng

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

Sử dụng set

Trừ khi danh sách của bạn rất lớn, bạn có thể tránh 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 của giải pháp

Nếu bạn hài lòng khi có các bộ thay vì danh sách làm đầu ra, bạn có thể sử dụng mã của @ 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

@ Giải pháp của 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

và cuối cùng bạn có thể thêm .apply(list)để có được đầu ra tương tự của bạn


1
So sánh tuyệt vời mà bạn đã làm!
MegaCookie

1

Đây là một ý tưởng giảm tải phần tính toán cho các công cụ NumPy được vector hóa. Chúng tôi sẽ tập hợp tất cả dữ liệu vào các mảng đơn cho mỗi tiêu đề, thực hiện tất cả các kết quả khớp được yêu cầu trên NumPy và cuối cùng cắt lại các mục hàng yêu cầu. Trên NumPy, phần nâng hạng nặng, chúng tôi sẽ sử dụng băm dựa trên ID nhóm và ID trong mỗi nhóm bằng cách sử dụng np.searchsorted. Chúng tôi cũng đang sử dụng các số vì số này nhanh hơn với NumPy. Việc thực hiện sẽ trông giống như thế này -

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ối ưu hóa thêm có thể ở các bước để tính toán t_masky_mask, nơi np.searchsortedcó thể được sử dụng lại.

Chúng ta cũng có thể sử dụng một phép gán mảng đơn giản thay thế cho isinbước để có được t_masky_mask, như vậy -

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]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.