将Pandas稀疏DataFrame转换为稀疏矩阵,而不在内存中生成密集矩阵

15
有没有一种方法可以将 pandas.SparseDataFrame 转换为 scipy.sparse.csr_matrix,而不生成内存中的密集矩阵?
scipy.sparse.csr_matrix(df.values)

这不起作用,因为它生成的是一个密集矩阵,该矩阵被转换为csr_matrix

提前感谢!


这个倒着运行?https://dev59.com/-WMm5IYBdhLWcg3wKsgj - JohnE
6个回答

15

Pandas 0.20.0+:

从pandas版本0.20.0开始(发布于2017年5月5日),可以使用一行代码来实现这个功能:

from scipy import sparse


def sparse_df_to_csr(df):
    return sparse.csr_matrix(df.to_coo())

这里使用了新的to_coo()方法

早期版本:

在Victor May的答案基础上,这里提供了一个稍微更快的实现方式,但仅适用于整个SparseDataFrame都是稀疏的情况,而且所有的BlockIndex都是这种情况(注:如果是通过get_dummies创建的,那么就满足这个条件)。

编辑说明:我修改了它,使得它可以处理非零填充值。CSR没有本地的非零填充值,所以你需要在外部记录它。

import numpy as np
import pandas as pd
from scipy import sparse

def sparse_BlockIndex_df_to_csr(df):
    columns = df.columns
    zipped_data = zip(*[(df[col].sp_values - df[col].fill_value,
                         df[col].sp_index.to_int_index().indices)
                        for col in columns])
    data, rows = map(list, zipped_data)
    cols = [np.ones_like(a)*i for (i,a) in enumerate(data)]
    data_f = np.concatenate(data)
    rows_f = np.concatenate(rows)
    cols_f = np.concatenate(cols)
    arr = sparse.coo_matrix((data_f, (rows_f, cols_f)),
                            df.shape, dtype=np.float64)
    return arr.tocsr()

使用 series.to_coo() 将每一列转换,然后使用 sparse.bmat() 将它们连接成一个矩阵,这个方案怎么样? - hpaulj
@hpaulj,听起来像是一个独特的答案 - 你应该写下来! - T.C. Proctor
深入挖掘后,我发现Multiindex映射与我所想的简单列向量非常不同。它更像是sklearn人们喜欢的特征矩阵。 - hpaulj
1
看起来现在这个可行。数据集=稀疏.csr_matrix(df.to_coo()) - Simd

4

从Pandas版本0.25开始,SparseSeriesSparseDataFrame已经被弃用。现在,DataFrames支持稀疏数据类型的列。通过sparse访问器可以使用稀疏方法,因此转换一行代码现在看起来像这样:

sparse_matrix = scipy.sparse.csr_matrix(df.sparse.to_coo())

一个后续问题是:如何高效地将具有大量值的分类列转换为“Sparse Dtypes”?pd.get_dummies(df, sparse = True)需要很长时间。 - learner

3

@Marigold的答案可以解决问题,但由于访问每列中的所有元素(包括零),速度较慢。在此基础上,我编写了以下快速且不太规范的代码,使用密度约为1%的1000x1000矩阵时,运行速度约为原先的50倍。我的代码还适当处理了密集列。

def sparse_df_to_array(df):
    num_rows = df.shape[0]   

    data = []
    row = []
    col = []

    for i, col_name in enumerate(df.columns):
        if isinstance(df[col_name], pd.SparseSeries):
            column_index = df[col_name].sp_index
            if isinstance(column_index, BlockIndex):
                column_index = column_index.to_int_index()

            ix = column_index.indices
            data.append(df[col_name].sp_values)
            row.append(ix)
            col.append(len(df[col_name].sp_values) * [i])
        else:
            data.append(df[col_name].values)
            row.append(np.array(range(0, num_rows)))
            col.append(np.array(num_rows * [i]))

    data_f = np.concatenate(data)
    row_f = np.concatenate(row)
    col_f = np.concatenate(col)

    arr = coo_matrix((data_f, (row_f, col_f)), df.shape, dtype=np.float64)
    return arr.tocsr()

1
Pandas文档提到了一个实验性的转换到Scipy稀疏矩阵的方法,即SparseSeries.to_coo。

http://pandas-docs.github.io/pandas-docs-travis/sparse.html#interaction-with-scipy-sparse

================

编辑 - 这是来自多重索引的特殊函数,而不是数据框。请参阅其他答案以了解数据框的情况。请注意日期的差异。

============

从0.20.0版本开始,有一个和一个多索引。由于稀疏矩阵本质上是2d的,因此要求多索引对于(实际上)1d数据系列是有意义的。而数据帧可以表示表格或2d数组。
当我第一次回答这个问题时,这个稀疏数据框架/系列特性是实验性的(2015年6月)。

这仅适用于使用MultiIndexSparseSeries,而不适用于DataFrame。 - T.C. Proctor
正如 @eleanora 所提到的,现在这个方法是可行的(截至2017年5月5日发布的版本0.20.0)。sparse.csr_matrix(df.to_coo()) 是一行代码就能解决问题的方法。也许你应该编辑你的回答来明确这一点? - T.C. Proctor
也许我们应该将该主题作为过时的内容关闭? - hpaulj
关闭一个完全有效的问题,因为答案已经过时,这是常见的做法吗?我认为这不应该成为一种做法,而且总体上似乎是个坏主意。 - T.C. Proctor
这会避免我因为我的答案不再有效而被踩。虽然这并不是很重要。 :) - hpaulj
如果你认为应该删除你的答案,因为你担心会被踩,那么你可以这样做。就我个人而言,我永远不会因为需要更新就踩一个答案。我真的怀疑你会因此而受到踩的惩罚。 - T.C. Proctor

0

这是一种按列填充稀疏矩阵的解决方案(假设你至少可以将一列放入内存)。

import pandas as pd
import numpy as np
from scipy.sparse import lil_matrix

def sparse_df_to_array(df):
    """ Convert sparse dataframe to sparse array csr_matrix used by
    scikit learn. """
    arr = lil_matrix(df.shape, dtype=np.float32)
    for i, col in enumerate(df.columns):
        ix = df[col] != 0
        arr[np.where(ix), i] = df.ix[ix, col]

    return arr.tocsr()

-1
编辑:实际上,这种方法在某个阶段具有密集表示,因此它并不能解决问题。

您应该能够以以下方式在 pandas [1] 中使用实验性的 .to_coo() 方法:

df, idx_rows, idx_cols = df.stack().to_sparse().to_coo()
df = df.tocsr()

这个方法不是使用 DataFrame(行/列),而是使用带有行和列的 MultiIndexSeries(这就是为什么需要 .stack() 方法)。这个带有 MultiIndexSeries 需要是一个 SparseSeries,即使你的输入是一个 SparseDataFrame.stack() 返回的也是一个常规的 Series。因此,在调用 .to_coo() 之前,需要使用 .to_sparse() 方法。

.stack() 返回的 Series,即使它不是一个 SparseSeries,也只包含非空元素,因此它不应该比稀疏版本占用更多的内存(至少在类型为 np.float 时使用 np.nan)。

  1. http://pandas.pydata.org/pandas-docs/stable/sparse.html#interaction-with-scipy-sparse

这个方法似乎很遗憾地使用了大量的内存。 - Simd
你说得对 @eleanora,我不确定之前是怎么测试的,但是看起来内部这个方法有一个密集的数组内部表示,所以它对于这个问题是无意义的。对于错误的答案感到抱歉。 - Marc Garcia
看起来现在可以了。dataset = sparse.csr_matrix(df.to_coo()) - Simd

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接