如何通过索引将scipy.sparse矩阵分配给NumPy数组?

时间:2014-11-14 16:18:25

标签: python numpy scipy

当我尝试将scipy.sparse矩阵s(任何可用的稀疏类型)分配给NumPy数组a时,如下所示:

a[:] = s

我得到TypeError

  

TypeError:float()参数必须是字符串或数字

有没有办法解决这个问题?

我知道todense()toarray()方法,但我真的想避免不必要的副本,我更喜欢为NumPy数组使用相同的代码SciPy稀疏矩阵。 目前,我并不关心从稀疏矩阵中获取低效率的值。

是否有某种类型的稀疏矩阵包装器可以与NumPy索引赋值配合使用?

如果没有,任何建议我如何自己建立这样的东西?

在这种情况下,是否存在与NumPy合作的不同稀疏数组库?

更新

我在NumPy源代码中搜索过,并且在搜索错误消息字符串时,我想我找到了@TYPE@_setitem()函数float()中索引分配发生的部分。

我仍然没有真正得到它,但在某些时候,a函数似乎被调用(如果import scipy s = scipy.sparse.dok_matrix((5, 1)) def myfloat(self): assert self.shape == (1, 1) return self[0, 0] scipy.sparse.dok.dok_matrix.__float__ = myfloat a[:] = s 是一个浮点数组)。所以我试图修补其中一个SciPy稀疏矩阵类,以允许调用此函数:

float()

可悲的是,这并不起作用,因为在整个稀疏矩阵上调用float()而不是在其中的单个项目上调用。{/ p>

所以我想我的新问题是:如何进一步更改稀疏矩阵类以使NumPy迭代所有项目并在每个项目上调用PySequence_Check()

另一个更新:

我在Github上发现了一个稀疏数组模块(numpy/core/src/multiarray/arraytypes.c.src around line 187),它允许赋值给NumPy数组。遗憾的是,模块的功能非常有限(例如,切片还没有真正起作用),但它可能有助于理解如何实现分配给NumPy数组。 我会进一步调查......

再次更新:

我做了一些挖掘,发现更有趣的源文件可能是https://github.com/FRidh/sparse。 我怀疑函数__array_struct__numpy/core/src/multiarray/ctors.c / docs)在分配期间的某个时间被调用。来自code的简单稀疏数组类通过了测试,但它看起来像SciPy中的稀疏矩阵类(尽管在我看来它们是序列)。

他们会检查__array_interface____array____getitem__,然后它会以某种方式决定它们不是序列。 不检查属性__len__PySequence_Check()(所有稀疏数组类都有!)。

这引出了另一个问题:如何以他们通过__getitem__()的方式操纵稀疏矩阵类(或其对象)?

我认为一旦它们被识别为序列,分配就应该有效,因为__len__()和{{1}}应该足够了。

2 个答案:

答案 0 :(得分:1)

正如我对问题的评论中所提到的,序列界面不适用于稀疏的矩阵,因为当使用单个数字编制索引时,它们不会丢失维度。 无论如何要尝试它,我在纯Python中创建了一个非常有限的快速和脏的稀疏数组类,当用单个数字索引时,它返回一个"行" class(包含原始数据的视图),再次可以使用单个数字编制索引以生成此索引处的实际值。使用我的类的实例s,分配给NumPy数组a完全按照要求工作:

a[:] = s

我预计这种效率会有些低效,但它确实非常非常慢。分配500.000 x 100稀疏阵列需要几分钟! 但是,好消息是在分配期间没有创建完整大小的临时数组。在分配期间,内存使用率保持不变(其中一个CPU最大化)。

所以这基本上是对原始问题的一种解决方案。

为了使赋值更有效并且仍然不使用密集数组数据的临时副本,NumPy必须在内部执行与

类似的操作
s.toarray(out=a)

据我所知,目前没有办法让NumPy这样做。

但是,有一种方法可以通过提供返回NumPy数组的__array__()方法来做一些非常相似的事情。顺便提一下,SciPy稀疏矩阵已经有了这样一种方法,只是名称不同:toarray()。所以我只是重命名了它:

scipy.sparse.dok_matrix.__array__ = scipy.sparse.dok_matrix.toarray
a[:] = s

这就像一个魅力(也与其他稀疏矩阵类一样)并且非常快!

根据我对情况的有限理解,这应该创建一个与a大小相同的临时NumPy数组,它保存s(和许多零)的所有值,然后分配到a。 但奇怪的是,即使我使用占用几乎所有可用内存的非常大的a,分配仍然会很快发生,并且不会使用额外的RAM。

所以我想这是我原来问题的另一个更好的解决方案。

这留下了另一个问题:为什么没有临时数组会有效?

答案 1 :(得分:0)

如何使用nonzero来识别哪些元素不为零?

x = np.ones((3,4))
s = sparse.csr_matrix((3,4))
s[0,0] = 2
s[1,2] = 3
I,J = s.nonzero()
x[:] = 0  # omit if just changing nonzero values
x[I,J] = s.data
x
对于密集和nonzero数组,

csr的功能相同。我还没有尝试过其他格式。


对于csr(和coo)稀疏矩阵,值存储在s.data数组中。在此示例中,它看起来像:

array([ 2.,  4.])

x值位于data缓冲区x.data中。在这种情况下,它是12个连续的花车。

x.ravel()
# array([ 2.,  0.,  0.,  0.,  0.,  0.,  4.,  0.,  0.,  0.,  0.,  0.])

s的这两个值无法在不复制的情况下映射到x的12个值。通常,稀疏数据值不会与其密集等效值中的连续值块匹配。

您担心IJ数组的大小。如果稀疏矩阵采用coo格式,则可以以相同的方式使用其rowcol属性:

sc=s.tocoo()
x[sc.row, sc.col]=sc.data

但是从一种稀疏格式转换为另一种稀疏格式涉及复制数据。并且铜阵列不是可订阅的。


x = np.zeros((3,4))
x[:]=['123','321','0','1']

产生

array([[ 123.,  321.,    0.,    1.],
       [ 123.,  321.,    0.,    1.],
       [ 123.,  321.,    0.,    1.]])

它会将float应用于右侧的每个项目,然后播放'它适合x大小。

[]转换为对-_setitem__的调用。

x.__setitem__((1,2),3)  # same as x[1,2]=3
x.__setitem__((None,2),'3') # sets the last row

似乎在任何可迭代的每个项目上调用float(需要仔细检查这个)。但是如果值是某个其他对象,我们会得到类似于原始对象的错误:

class Foo(): pass
x.__setitem__((1,2), Foo())
# TypeError: float() argument must be a string or a number, not 'Foo'

稀疏coodok格式产生类似错误,而csrlil产生

ValueError: setting an array element with a sequence.

我还没弄清楚这里使用了稀疏矩阵的哪种方法或属性。

看看np.broadcast。我认为这复制了这些分配中使用的迭代类型。

b = np.broadcast(x[:], [1,2,3,4])
list(b)

我们可以通过从带有dtype对象的数组开始来移除浮点转换复杂度,该数组可以包含任何内容:

xa=np.zeros((3,4), dtype=object)
xa[:]=s

但现在s的每个元素中都会出现xa。它没有s超过xa的值。

我猜测当s不是np.array时,numpy在执行作业时首先将其包裹起来,例如:

x[:] = np.array(s)

s是标量或列表时,会生成一个可以广播以适合x的数组。但是当它是一个对象(稀疏数组不是一个numpy数组)时,这个包装只是一个带有dtype = object的0d数组。您需要通过一个函数传递s,该函数将其转换为可以广播的迭代。最明显的是toarray()