【问题标题】:How to vectorize pandas operation to improve speed?如何矢量化 pandas 操作以提高速度?
【发布时间】:2019-09-14 05:51:18
【问题描述】:

这是一个 SKU 相似性问题。我有一个这样的数据框。每个 ctn_id 有多个 sku_code。

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_ids 和 400 个 sk_codes,总共有 10 亿次赋值操作。有没有更好的方法可以使用 pandas 或任何其他包来做到这一点?

【问题讨论】:

  • 你的机器有多少内存?
  • 16GB。不过,dfr 数据帧大约为 600MB。
  • 你能提供一个示例输出吗?
  • 你可以运行最后一个sn-p的代码来获取输出。

标签: python pandas parallel-processing bigdata vectorization


【解决方案1】:

已更新以处理来自随机输入的重复项

此答案假定没有重复的行(具有相同 ctn_id 和 sku_code 的行)。不过,您可以轻松地为该用例扩展此答案。

是的,您可以旋转数据框,以便 ctn_ids 是行,sku_codes 是列。为此,您可以添加一个全为 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 中一起出现的次数,我相信这就是您要查找的内容。

【讨论】:

  • 查看关于假设没有重复的注释。随机数可能会重复。我假设目标输入不是随机的......
  • 在真正的问题中,不会有重复。无论如何,我在问题中编辑了我的代码以删除重复项
  • 我会花一些时间来理解,但它确实有效!。不确定实际数据集需要多长时间。
  • @Vinay 最后一行只是将矩阵乘以其转置。在这种情况下,由于矩阵是 1 和 0,因此它会计算 2 个 sku_code 在同一 ctn_id 中出现的次数。
  • 知道了。但它破坏了我的记忆。我会尝试@user3483203 的答案,他现在删除了(幸运的是我保存了它),但声称将解决内存问题。
【解决方案2】:

好吧,我会试一试的。

不确定这是否会足够快,但我想说它已经比您的链式 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)

【讨论】:

  • 尝试了您的解决方案。但是收到错误data argument can't be an iterator
  • @vinay 只是用列表包装地图
【解决方案3】:

对于具有integers,ctn_id,我们可以使用基于array-assignment 的方法来获取2D 网格上的所有映射,然后使用矩阵乘法来获得binned -summations,类似于@scomes's post 中所示 -

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)

【讨论】:

  • 它有效!我会接受在真实数据上提供更好性能的答案。
  • 我收到MemoryError: 的真实数据。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-20
  • 2012-10-11
  • 2016-10-07
  • 1970-01-01
  • 2018-05-15
相关资源
最近更新 更多