두 열의 목록을 행별로 효율적으로 비교


16

다음과 같이 Pandas 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

그러나 약 10 만 개의 항목으로 두 열에서 행 목록으로 해당 목록의 추가 및 제거를 찾고 있습니다.

이 질문과 비교할 수 있습니다 : Pandas : Pandas와 함께 DataFrame에서 목록 열을 행 단위로 비교하는 방법 (루프가 아닌)? 그러나 나는 차이점을보고 있으며 Pandas.apply방법은 그렇게 많은 항목에 대해 그렇게 빠르지 않은 것 같습니다. 이것은 현재 사용중인 코드입니다. Pandas.applynumpy'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)

이것은 잘 작동하지만 12,000 항목에 약 1 분이 걸립니다. 그래서 이것을 달성하는 더 빠른 방법이 있습니까?


이 열 중 하나에 최대 한 줄에 몇 개의 항목이있을 수 있습니까?
따라서 hv89

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에서는을 사용하여 각 그룹 내 그룹 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_masky_mask에서 np.searchsorted다시 사용할 수있는 위치 에서 추가 최적화가 가능 합니다.

또한 간단한 배열 할당을 사용하여 다음 과 같이 isin얻는 단계 대신 사용할 수 있습니다.t_masky_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.