如何矢量化 pandas 操作以提高速度?
How to vectorize pandas operation to improve speed?
这是一个SKU亲和性问题。我有一个这样的数据框。每个 ctn_id 有多个 sku_codes.
dfr = pd.DataFrame(columns=['ctn_id','sku_code'])
dfr['ctn_id'] = np.random.randint(low=1,high=21,size=200)
dfr['sku_code'] = np.random.choice(['a','b','c','d'],size=200)
dfr.drop_duplicates(['ctn_id','sku_code'], inplace=True)
我要填充以下数据框。
dfx = pd.DataFrame(columns=['sku_code','a','b','c','d'])
dfx['sku_code'] = ['a','b','c','d']
dfx = dfx.fillna(0)
dfx.set_index('sku_code',inplace=True)
使用下面的逻辑
for idx in dfr['ctn_id'].unique():
x = list(dfr[dfr['ctn_id'] == idx]['sku_code'].unique())
for skui in dfx.index:
if skui in x:
for skuj in x:
dfx.loc[skui, skuj] = dfx.loc[skui, skuj] + 1
我有250万个ctn_id和400个sk_codes,总共有十亿次赋值操作。使用 pandas 或任何其他包是否有更好的方法来做到这一点?
已更新以处理来自随机输入的重复项
此答案假设没有重复行(具有相同 ctn_id 和 sku_code 的行)。不过,您可以轻松地针对该用例扩展此答案。
是的,您可以旋转数据框,使 ctn_id 成为行,sku_code 成为列。为此,您可以添加一个全为 1 的虚拟列,然后使用
dfr['Dummy'] = 1
piv = dfr.drop_duplicates().pivot('ctn_id', 'sku_code', 'Dummy').fillna(0.0)
现在你基本上有了一个稀疏矩阵,只要有 ctn_id/sku_code 关系就为 1,否则为 0。从这里你可以只使用矩阵代数。
mat = piv.values
counts = mat.T.dot(mat)
变量 counts
有你要找的东西(它将是对称的,值将是 sku_code 在 ctn_id 中一起出现的次数,这是我相信你正在寻找的。
好吧,我试试看。
不确定这是否足够快,但我想说它已经比链接的 for 循环快多了。
它使用 hacky 方式来执行 "vectorized" 设置差异。
s = df.groupby(['sku_code']).ctn_id.agg(set)
pd.DataFrame(map(lambda s: list(map(len,s)), np.array(s) & np.array(s).reshape([-1,1])))
0 1 2 3
0 18 17 18 16
1 17 19 19 17
2 18 19 20 17
3 16 17 17 17
使用您提供的示例,性能提升了约 100 倍。
# your method
79.4 ms ± 3.3 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
# my try
668 µs ± 30.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
对于具有 integers,
的 ctn_id
,我们可以使用基于 数组赋值 的方法来获取 2D
网格上的所有映射,并且然后使用矩阵乘法得到 binned-summations,类似于 -
Ie = dfr.ctn_id.values
J = dfr.sku_code.values
I = pd.factorize(Ie,sort=False)[0]
col2IDs,col2L = pd.factorize(J,sort=True) #use sort=False if order is irrelevant
a = np.zeros((I.max()+1,col2IDs.max()+1),dtype=int)
a[I,col2IDs] = 1
df_out = pd.DataFrame(a.T.dot(a), columns=col2L, index=col2L)
选择 #1
为了获得更好的性能,我们可以使用 float
值进行矩阵乘法。为此,使用 float
dtype 得到 a
。因此,设置 a
,像这样 -
a = np.zeros((I.max()+1,col2IDs.max()+1),dtype=float)
选择#2
或者用布尔数组存储1s
然后转换dtype:
a = np.zeros((I.max()+1,col2IDs.max()+1),dtype=bool)
a[I,col2IDs] = 1
a = a.astype(float)
这是一个SKU亲和性问题。我有一个这样的数据框。每个 ctn_id 有多个 sku_codes.
dfr = pd.DataFrame(columns=['ctn_id','sku_code'])
dfr['ctn_id'] = np.random.randint(low=1,high=21,size=200)
dfr['sku_code'] = np.random.choice(['a','b','c','d'],size=200)
dfr.drop_duplicates(['ctn_id','sku_code'], inplace=True)
我要填充以下数据框。
dfx = pd.DataFrame(columns=['sku_code','a','b','c','d'])
dfx['sku_code'] = ['a','b','c','d']
dfx = dfx.fillna(0)
dfx.set_index('sku_code',inplace=True)
使用下面的逻辑
for idx in dfr['ctn_id'].unique():
x = list(dfr[dfr['ctn_id'] == idx]['sku_code'].unique())
for skui in dfx.index:
if skui in x:
for skuj in x:
dfx.loc[skui, skuj] = dfx.loc[skui, skuj] + 1
我有250万个ctn_id和400个sk_codes,总共有十亿次赋值操作。使用 pandas 或任何其他包是否有更好的方法来做到这一点?
已更新以处理来自随机输入的重复项
此答案假设没有重复行(具有相同 ctn_id 和 sku_code 的行)。不过,您可以轻松地针对该用例扩展此答案。
是的,您可以旋转数据框,使 ctn_id 成为行,sku_code 成为列。为此,您可以添加一个全为 1 的虚拟列,然后使用
dfr['Dummy'] = 1
piv = dfr.drop_duplicates().pivot('ctn_id', 'sku_code', 'Dummy').fillna(0.0)
现在你基本上有了一个稀疏矩阵,只要有 ctn_id/sku_code 关系就为 1,否则为 0。从这里你可以只使用矩阵代数。
mat = piv.values
counts = mat.T.dot(mat)
变量 counts
有你要找的东西(它将是对称的,值将是 sku_code 在 ctn_id 中一起出现的次数,这是我相信你正在寻找的。
好吧,我试试看。
不确定这是否足够快,但我想说它已经比链接的 for 循环快多了。
它使用 hacky 方式来执行 "vectorized" 设置差异。
s = df.groupby(['sku_code']).ctn_id.agg(set)
pd.DataFrame(map(lambda s: list(map(len,s)), np.array(s) & np.array(s).reshape([-1,1])))
0 1 2 3
0 18 17 18 16
1 17 19 19 17
2 18 19 20 17
3 16 17 17 17
使用您提供的示例,性能提升了约 100 倍。
# your method
79.4 ms ± 3.3 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
# my try
668 µs ± 30.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
对于具有 integers,
的 ctn_id
,我们可以使用基于 数组赋值 的方法来获取 2D
网格上的所有映射,并且然后使用矩阵乘法得到 binned-summations,类似于
Ie = dfr.ctn_id.values
J = dfr.sku_code.values
I = pd.factorize(Ie,sort=False)[0]
col2IDs,col2L = pd.factorize(J,sort=True) #use sort=False if order is irrelevant
a = np.zeros((I.max()+1,col2IDs.max()+1),dtype=int)
a[I,col2IDs] = 1
df_out = pd.DataFrame(a.T.dot(a), columns=col2L, index=col2L)
选择 #1
为了获得更好的性能,我们可以使用 float
值进行矩阵乘法。为此,使用 float
dtype 得到 a
。因此,设置 a
,像这样 -
a = np.zeros((I.max()+1,col2IDs.max()+1),dtype=float)
选择#2
或者用布尔数组存储1s
然后转换dtype:
a = np.zeros((I.max()+1,col2IDs.max()+1),dtype=bool)
a[I,col2IDs] = 1
a = a.astype(float)