如何绕过稀疏矩阵的慢 groupby?

How to get around slow groupby for a sparse matrix?

我有一个大矩阵(约 2 亿行)描述了每天发生的操作列表(有约 10000 个可能的操作)。我的最终目标是创建一个共现矩阵,显示在同一天发生了哪些动作。

这是一个示例数据集:

data = {'date':   ['01', '01', '01', '02','02','03'],
        'action': [100, 101, 989855552, 100, 989855552, 777]}
df = pd.DataFrame(data, columns = ['date','action'])

我试图用 pd.get_dummies 创建一个稀疏矩阵,但是解开矩阵并在其上使用 groupby 非常慢,仅 5000 行需要 6 分钟。

# Create a sparse matrix of dummies
dum = pd.get_dummies(df['action'], sparse = True)
df = df.drop(['action'], axis = 1)
df = pd.concat([df, dum], axis = 1)

# Use groupby to get a single row for each date, showing whether each action occurred.
# The groupby command here is the bottleneck.
cols = list(df.columns)
del cols[0]
df = df.groupby('date')[cols].max()

# Create a co-occurrence matrix by using dot-product of sparse matrices
cooc = df.T.dot(df)

我也试过:

  1. 获取非稀疏格式的虚拟对象;
  2. 使用 groupby 进行聚合;
  3. 在矩阵乘法之前进入稀疏格式。

但是我在第 1 步中失败了,因为没有足够的 RAM 来创建这么大的矩阵。

非常感谢您的帮助。

您可以考虑几个相当简单的简化。

其中之一是您可以直接在 GroupBy 对象上调用 max(),您不需要在所有列上使用花哨的索引,因为默认情况下 returns:

df = df.groupby('date').max()

其次,您可以禁用 GroupBy 的排序。正如 Pandas reference for groupby() 所说:

sort : bool, default True

Sort group keys. Get better performance by turning this off. Note this does not influence the order of observations within each group. Groupby preserves the order of rows within each group.

所以也试试看:

df = df.groupby('date', sort=False).max()

第三,您也可以使用简单的 pivot_table() 来产生相同的结果。

df = df.pivot_table(index='date', aggfunc='max')

另一种方法是返回到您的 "actions" DataFrame,将其转换为 MultiIndex 并将其用于一个简单的系列,然后在其上使用 unstack(),这应该会得到相同的结果,而不必使用 get_dummies() 步骤(但不确定这是否会删除您当前依赖的一些稀疏属性。)

actions_df = pd.DataFrame(data, columns = ['date', 'action'])
actions_index = pd.MultiIndex.from_frame(actions_df, names=['date', ''])
actions_series = pd.Series(1, index=actions_index)
df = actions_series.unstack(fill_value=0)

您提供的示例 DataFrame 对于检查它们是否全部等效并产生相同的结果非常有用,但不幸的是,对于对其进行基准测试并不是那么好...我建议您采用更大的数据集(但仍然小于您的真实数据集)数据,例如小 10 倍或小 40-50 倍),然后对操作进行基准测试以检查它们需要多长时间。

如果您使用的是 Jupyter(或另一个 IPython shell),则可以使用 %timeit 命令对表达式进行基准测试。

所以你可以输入:

%timeit df.groupby('date').max()
%timeit df.groupby('date', sort=False).max()
%timeit df.pivot_table(index='date', aggfunc='max')
%timeit actions_series.unstack(fill_value=0)

并比较结果,然后扩大规模并检查整个 运行 是否会在可接受的时间内完成。

我仅使用基于 的稀疏矩阵得出了一个答案。代码速度很快,1000 万行大约需要 10 秒(我之前的代码 5000 行需要 6 分钟,而且不可扩展)。

时间和内存的节省来自于使用稀疏矩阵直到最后一步,此时有必要在导出之前解开(已经很小的)共现矩阵。

## Get unique values for date and action
date_c = CategoricalDtype(sorted(df.date.unique()), ordered=True)
action_c = CategoricalDtype(sorted(df.action.unique()), ordered=True)

## Add an auxiliary variable
df['count'] = 1

## Define a sparse matrix
row = df.date.astype(date_c).cat.codes
col = df.action.astype(action_c).cat.codes
sparse_matrix = csr_matrix((df['count'], (row, col)),
                shape=(date_c.categories.size, action_c.categories.size))

## Compute dot product with sparse matrix
cooc_sparse = sparse_matrix.T.dot(sparse_matrix)

## Unravel co-occurrence matrix into dense shape
cooc = pd.DataFrame(cooc_sparse.todense(), 
       index = action_c.categories, columns = action_c.categories)