具有这样的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: How to Compare Columns of Lists Row-wise in a DataFrame with Pandas (not for loop)?,但我正在研究差异,而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)
这正常工作,但是大约需要一分钟才能完成12万个条目。那么有没有更快的方法来完成此任务?
答案 0 :(得分:15)
不确定性能,但是在缺少更好的解决方案的情况下,这可能适用:
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}
答案 1 :(得分:7)
df['today'].apply(set) - df['yesterday'].apply(set)
答案 2 :(得分: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的代码
%%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)
以获得相同的输出
答案 3 :(得分:1)
这里是将计算部件卸载到矢量化NumPy工具的想法。我们将为每个标头将所有数据收集到单个数组中,对NumPy执行所有必需的匹配,最后切回到所需的行条目。在承担繁重任务的NumPy上,我们将使用基于np.searchsorted
的组ID和每个组中的ID的哈希。我们也利用数字,因为使用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]
答案 4 :(得分:1)
您的解决方案
%timeit additions = df.apply(lambda row: np.setdiff1d(row.today, row.yesterday), axis=1)
每个循环590 µs±13 µs(平均±标准偏差,共运行7次,每个循环1000次)
%timeit removals = df.apply(lambda row: np.setdiff1d(row.yesterday, row.today), axis=1)
每个循环609 µs±28 µs(平均±标准偏差,共运行7次,每个循环1000次)
使用地图功能或numpy向量化以获得更好的性能,在某些情况下地图功能会失败。
使用numpy向量化功能
vector = np.vectorize(lambda x,y:set(x)-set(y))
%timeit additions = vector(df.today,df.yesterday)
每个循环56.6 µs±256 ns(平均±标准偏差,共运行7次,每个循环10000个
%timeit removals = vector(df.yesterday,df.today)
每个循环58.1 µs±2.04 µs(平均±标准偏差,共运行7次,每个10000个循环)
使用地图功能
首先设置要转换的列表
df.today = list(map(set,df.today))
df.yesterday = list(map(set,df.yesterday))
然后使用lambda和map函数
%timeit additions = list(map(lambda x:x[0]-x[1],zip(df.today,df.yesterday)))
每个循环15.3 µs±1.63 µs(平均±标准偏差,共运行7次,每个循环100000次)
%timeit removals = list(map(lambda x:x[1]-x[0],zip(df.today,df.yesterday)))
每个循环15.1 µs±502 ns(平均±标准偏差,共运行7次,每个循环100000次)
因此可以使用map函数或np.vectorize函数