在pandas中高效地创建稀疏透视表?

40

我正在将一个具有两列(A和B)的记录列表转换为矩阵表示。我一直在使用 pandas 中的 pivot 函数,但结果变得相当大。Pandas 是否支持将数据透视为稀疏格式?我知道我可以将其旋转然后将其转换为某种稀疏表示,但这不像我想要的那样优雅。我的最终目标是将其用作预测模型的输入。

或者,除了 pandas 之外,是否存在某种稀疏透视功能?

编辑:这里是一个非稀疏透视的示例

import pandas as pd
frame=pd.DataFrame()
frame['person']=['me','you','him','you','him','me']
frame['thing']=['a','a','b','c','d','d']
frame['count']=[1,1,1,1,1,1]

frame

  person thing  count
0     me     a      1
1    you     a      1
2    him     b      1
3    you     c      1
4    him     d      1
5     me     d      1

frame.pivot('person','thing')

        count            
thing       a   b   c   d
person                   
him       NaN   1 NaN   1
me          1 NaN NaN   1
you         1 NaN   1 NaN

这会创建一个矩阵,其中包含所有可能的人和物品组合,但它不是稀疏的。

http://docs.scipy.org/doc/scipy/reference/sparse.html

稀疏矩阵需要更少的空间,因为它们可以表示NaN或0之类的东西。如果我有一个非常大的数据集,这个枢轴函数可以生成一个应该是稀疏的矩阵,因为它有大量的NaN或0。我希望通过生成一些稀疏的东西来节省大量空间/内存,而不是创建一个密集矩阵,然后将其转换为稀疏。


2
你能提供一些样例输入、输出和代码吗? - Skorpeo
稀疏是什么意思? - AZhao
@AZhao 这是一个数学术语 https://en.m.wikipedia.org/wiki/Sparse_matrix - Alex Taylor
刚刚添加了一个示例和解释。谢谢! - neelshiv
数据透视表只是查看原始数据的方式,这些数据已经很稀疏了(除了将“人”和“物”转换为整数)。 - Alexander
5个回答

35

这是一种基于人和物的数据和索引创建稀疏scipy矩阵的方法。person_uthing_u是代表您要创建的数据透视表的行和列的唯一条目的列表。注意:这假设您的计数列已经具有所需的值。

from scipy.sparse import csr_matrix

person_u = list(sort(frame.person.unique()))
thing_u = list(sort(frame.thing.unique()))

data = frame['count'].tolist()
row = frame.person.astype('category', categories=person_u).cat.codes
col = frame.thing.astype('category', categories=thing_u).cat.codes
sparse_matrix = csr_matrix((data, (row, col)), shape=(len(person_u), len(thing_u)))

>>> sparse_matrix 
<3x4 sparse matrix of type '<type 'numpy.int64'>'
    with 6 stored elements in Compressed Sparse Row format>

>>> sparse_matrix.todense()

matrix([[0, 1, 0, 1],
        [1, 0, 0, 1],
        [1, 0, 1, 0]])

根据您最初的问题,scipy的稀疏矩阵应该足够满足您的需求,但如果您想要一个稀疏数据框,可以按照以下步骤操作:

dfs=pd.SparseDataFrame([ pd.SparseSeries(sparse_matrix[i].toarray().ravel(), fill_value=0) 
                              for i in np.arange(sparse_matrix.shape[0]) ], index=person_u, columns=thing_u, default_fill_value=0)

>>> dfs
     a  b  c  d
him  0  1  0  1
me   1  0  0  1
you  1  0  1  0

>>> type(dfs)
pandas.sparse.frame.SparseDataFrame

1
谢谢!我真的希望避免创建一个密集矩阵,然后使用to_sparse(),因为这样做仍然需要在某个时候或另一个时刻需要密集矩阵所需的内存量。我觉得还有其他Pandas函数可以输出稀疏数据,但也许我错了或者我必须去其他地方找找。 - neelshiv
非常有趣。如果没有现成的解决方案,我的计划就是尝试类似的东西,但我需要先学习一些关于scipy稀疏矩阵的知识。现在我可以从你的代码中学习了。谢谢! - neelshiv
1
为什么要对列表进行排序,例如person_u = list(sort(frame.person.unique()))?似乎最终的矩阵(sparse_matrix)与数据框不相符。 - kitchenprinzessin
pandas.DataFrame.astype 不再接受 categories= 参数!您的答案需要根据新版本进行更新。 - Farid Alijani

31

@khammel之前发布的答案很有用,但是由于pandas和Python的更改,它现在不再起作用。以下代码应该能够产生相同的输出:

from scipy.sparse import csr_matrix
from pandas.api.types import CategoricalDtype

person_c = CategoricalDtype(sorted(frame.person.unique()), ordered=True)
thing_c = CategoricalDtype(sorted(frame.thing.unique()), ordered=True)

row = frame.person.astype(person_c).cat.codes
col = frame.thing.astype(thing_c).cat.codes
sparse_matrix = csr_matrix((frame["count"], (row, col)), \
                           shape=(person_c.categories.size, thing_c.categories.size))

>>> sparse_matrix
<3x4 sparse matrix of type '<class 'numpy.int64'>'
     with 6 stored elements in Compressed Sparse Row format>

>>> sparse_matrix.todense()
matrix([[0, 1, 0, 1],
        [1, 0, 0, 1],
        [1, 0, 1, 0]], dtype=int64)


dfs = pd.SparseDataFrame(sparse_matrix, \
                         index=person_c.categories, \
                         columns=thing_c.categories, \
                         default_fill_value=0)
>>> dfs
        a   b   c   d
 him    0   1   0   1
  me    1   0   0   1
 you    1   0   1   0

主要更改如下:

  • .astype() 不再接受 "categorical",您需要创建一个 CategoricalDtype 对象。
  • sort() 不再起作用

其他更改较为表面:

  • 使用类别大小而不是唯一 Series 对象的长度,只是因为我不想不必要地创建另一个对象
  • csr_matrix 的数据输入(frame["count"])不需要是列表对象
  • pandas SparseDataFrame 现在直接接受 scipy.sparse 对象

4
自 Pandas 1.0.1 版本起,您需要将 pd.SparseDataFrame() 替换为 pd.DataFrame.sparse.from_spmatrix()。请参阅:https://pandas.pydata.org/pandas-docs/stable/user_guide/sparse.html#migrating - BumbleBee
这个很棒!非常感谢。 - igorkf

4

我曾经遇到过类似的问题,然后发现了这篇文章。唯一的区别是我的DataFrame有两列来定义输出矩阵的“行维度”(i)。我认为这可能是一个有趣的泛化方法,我使用了grouper

# function
import pandas as pd

from scipy.sparse import csr_matrix

def df_to_sm(data, vars_i, vars_j):
    grpr_i = data.groupby(vars_i).grouper

    idx_i = grpr_i.group_info[0]

    grpr_j = data.groupby(vars_j).grouper

    idx_j = grpr_j.group_info[0]

    data_sm = csr_matrix((data['val'].values, (idx_i, idx_j)),
                         shape=(grpr_i.ngroups, grpr_j.ngroups))

    return data_sm, grpr_i, grpr_j


# example
data = pd.DataFrame({'var_i_1' : ['a1', 'a1', 'a1', 'a2', 'a2', 'a3'],
                     'var_i_2' : ['b2', 'b1', 'b1', 'b1', 'b1', 'b4'],
                     'var_j_1' : ['c2', 'c3', 'c2', 'c1', 'c2', 'c3'],
                     'val' : [1, 2, 3, 4, 5, 6]})

data_sm, _, _ = df_to_sm(data, ['var_i_1', 'var_i_2'], ['var_j_1'])

data_sm.todense()

很好!目前我没有使用稀疏轴,但我一定会去看看的。感谢你的贡献! - neelshiv

0
我不知道什么时候发生的变化,但在Pandas v2.1.0中,如果你的值列是pd.SparseDtype,那么pivot()和pivot_table()将生成稀疏列。

0

这里有一个答案,更新了@Alnilam的答案方法,使用最新的pandas库,该库不再包含该答案中的所有函数。

from scipy.sparse import csr_matrix
from pandas.api.types import CategoricalDtype

rcLabel, vLabel = ('person', 'thing'), 'count'
rcCat = [CategoricalDtype(sorted(frame[col].unique()), ordered=True) for col in rcLabel]
rc = [frame[column].astype(aType).cat.codes for column, aType in zip(rcLabel, rcCat)]
mat = csr_matrix((frame[vLabel], rc), shape=tuple(cat.categories.size for cat in rcCat))
dfPivot = ( pd.DataFrame.sparse.from_spmatrix(
    mat, index=rcCat[0].categories, columns=rcCat[1].categories) )

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