关于使用稀疏矩阵使用groupby
的SO有几个问题。但是输出似乎是列表,dictionaries,dataframes和其他对象。
我正在研究NLP问题,并希望在处理过程中将所有数据保存在稀疏的scipy矩阵中以防止内存错误。
以下是上下文:
我已经对一些文档进行了矢量化(sample data here):
import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer
df = pd.read_csv('groupbysparsematrix.csv')
docs = df['Text'].tolist()
vectorizer = CountVectorizer()
train_X = vectorizer.fit_transform(docs)
print("Dimensions of training set: {0}".format(train_X.shape))
print type(train_X)
Dimensions of training set: (8, 180)
<class 'scipy.sparse.csr.csr_matrix'>
在原始数据框架中,我使用日期格式的日期来创建我想要总结的组:
from scipy import sparse, hstack
df['Date'] = pd.to_datetime(df['Date'])
groups = df['Date'].apply(lambda x: x.strftime('%j'))
groups_X = sparse.csr_matrix(groups.astype(float)).T
train_X_all = sparse.hstack((train_X, groups_X))
print("Dimensions of concatenated set: {0}".format(train_X_all.shape))
Dimensions of concatenated set: (8, 181)
现在我想应用groupby
(或类似功能)来查找每天每个令牌的总和。我希望输出是另一个稀疏的scipy矩阵。
输出矩阵为3 x 181,看起来像这样:
1, 1, 1, ..., 2, 1, 3
2, 1, 3, ..., 1, 1, 4
0, 0, 0, ..., 1, 2, 5
第1列到第180列代表代币,第181列代表一年中的某一天。
答案 0 :(得分:4)
计算csr
稀疏矩阵的选定列(或行)总和的最佳方法是使用另一个稀疏矩阵的矩阵乘积,该矩阵具有要求求和的1。实际上csr
总和(对于整行或列)按矩阵产品工作,索引行(或列)也用产品(https://stackoverflow.com/a/39500986/901925)
所以我将日期数组分组,并使用该信息构建求和'掩码'。
为了便于讨论,请考虑这个密集阵列:
In [117]: A
Out[117]:
array([[0, 2, 7, 5, 0, 7, 0, 8, 0, 7],
[0, 0, 3, 0, 0, 1, 2, 6, 0, 0],
[0, 0, 0, 0, 2, 0, 5, 0, 0, 0],
[4, 0, 6, 0, 0, 5, 0, 0, 1, 4],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 7, 0, 8, 1, 0, 9, 0, 2, 4],
[9, 0, 8, 4, 0, 0, 0, 0, 9, 7],
[0, 0, 0, 1, 2, 0, 2, 0, 4, 7],
[3, 0, 1, 0, 0, 0, 0, 0, 0, 2],
[0, 0, 1, 8, 5, 0, 0, 0, 8, 0]])
制作稀疏副本:
In [118]: M=sparse.csr_matrix(A)
根据最后一列生成一些组; collections.defaultdict
是执行此操作的便捷工具:
In [119]: grps=defaultdict(list)
In [120]: for i,v in enumerate(A[:,-1]):
...: grps[v].append(i)
In [121]: grps
Out[121]: defaultdict(list, {0: [1, 2, 4, 9], 2: [8], 4: [3, 5], 7: [0, 6, 7]})
我可以迭代这些组,收集M
行,对这些行求和并产生:
In [122]: {k:M[v,:].sum(axis=0) for k, v in grps.items()}
Out[122]:
{0: matrix([[0, 0, 4, 8, 7, 2, 7, 6, 8, 0]], dtype=int32),
2: matrix([[3, 0, 1, 0, 0, 0, 0, 0, 0, 2]], dtype=int32),
4: matrix([[4, 7, 6, 8, 1, 5, 9, 0, 3, 8]], dtype=int32),
7: matrix([[ 9, 2, 15, 10, 2, 7, 2, 8, 13, 21]], dtype=int32)}
在最后一列中,值包括2 * 4和3 * 7
所以有2个任务 - 收集组,无论是使用此默认值,还是itertools.groupby
(在这种情况下需要排序)或pandas
groupby。其次是行和求和的集合。这种字典迭代在概念上很简单。
屏蔽矩阵可能像这样工作:
In [141]: mask=np.zeros((10,10),int)
In [142]: for i,v in enumerate(A[:,-1]): # same sort of iteration
...: mask[v,i]=1
...:
In [143]: Mask=sparse.csr_matrix(mask)
...
In [145]: Mask.A
Out[145]:
array([[0, 1, 1, 0, 1, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
....
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=int32)
In [146]: (Mask*M).A
Out[146]:
array([[ 0, 0, 4, 8, 7, 2, 7, 6, 8, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 3, 0, 1, 0, 0, 0, 0, 0, 0, 2],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 4, 7, 6, 8, 1, 5, 9, 0, 3, 8],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 9, 2, 15, 10, 2, 7, 2, 8, 13, 21],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=int32)
此Mask*M
具有与字典行相同的值,但具有额外的0。我可以用lil
格式隔离非零值:
In [147]: (Mask*M).tolil().data
Out[147]:
array([[4, 8, 7, 2, 7, 6, 8], [], [3, 1, 2], [],
[4, 7, 6, 8, 1, 5, 9, 3, 8], [], [],
[9, 2, 15, 10, 2, 7, 2, 8, 13, 21], [], []], dtype=object)
我可以使用Mask
稀疏输入样式直接构造coo
矩阵:
Mask = sparse.csr_matrix((np.ones(A.shape[0],int),
(A[:,-1], np.arange(A.shape[0]))), shape=(A.shape))
应该更快并避免内存错误(无循环或大密集阵列)。
答案 1 :(得分:2)
这是使用标签二值化器和矩阵乘法的技巧。
from sklearn.preprocessing import LabelBinarizer
lb = LabelBinarizer(sparse_output=True)
grouped = lb.fit_transform(groups).T.dot(train_X)
grouped
是大小为3 x 180的输出稀疏矩阵。您可以在lb.classes_
中找到其稀疏矩阵的列表。