Scipy csr_matrix:理解indptr

56

偶尔我需要操作一个 csr_matrix,但我总是忘记参数 indicesindptr 如何共同构建稀疏矩阵。

我正在寻找一个清晰而直观的解释,说明在使用符号 csr_matrix((data, indices, indptr), [shape=(M, N)]) 定义稀疏矩阵时,indptr 如何与 dataindices 参数交互。

scipy 文档 可以看出,data 参数包含所有非零数据,indices 参数包含与该数据相关联的列(因此,在文档中给出的示例中,indices 等于 col)。但是,如何以清晰的术语解释 indptr 参数呢?


查看lil等效部分可能会有所帮助。如@Tanguy所述,连续的切片M.indices[indptr[i]:indptr[i+1]]对应于lil中的rows数组中的列表。 - hpaulj
1
用通俗易懂的语言来解释,“indptr”(索引指针)代表了“data”和“indices”(列索引)进行分区所需的索引。要填充矩阵中的(非零)数据,仅知道列索引显然是不够的。我们需要的剩余信息可能是每个非零值的行索引,但另一种选择是指定分隔非零值(以及同时指定列索引的方式),这正是csr公式化的方法。 - chichi
7个回答

107
也许这个解释可以帮助理解这个概念:
- `data` 是包含稀疏矩阵所有非零元素的数组。 - `indices` 是将 `data` 中的每个元素映射到稀疏矩阵中对应列的数组。 - `indptr` 将 `data` 和 `indices` 的元素映射到稀疏矩阵的行。这是通过以下推理完成的:
- 如果稀疏矩阵有 M 行,则 `indptr` 是一个包含 M+1 个元素的数组。 - 对于第 i 行,`[indptr[i]:indptr[i+1]]` 返回要从 `data` 和 `indices` 中取出的与第 i 行对应的元素的索引。假设 `indptr[i]=k` 且 `indptr[i+1]=l`,则第 i 行对应的数据为 `data[k:l]`,位于列 `indices[k:l]`。这是棘手的部分,我希望以下示例能帮助理解。
注意:我用字母替换了 `data` 数组中的数字,以避免在下面的示例中产生混淆。

enter image description here

注意:{{indptr}}中的值必须递增,因为{{indptr}}中的下一个单元格(下一行)引用了{{data}}和{{indices}}中对应该行的下一个值。

2
谢谢您尝试让它更清晰,但还是不太明白...您能给一个M的例子,然后按照一行中的第二点,解释一下indicesdata中哪些值会出现吗?我尝试了一个例子,但还是没懂。 - SarahData
2
我花了一些时间才理解这个问题(但比自己研究要短得多),不过现在我明白了。让我困惑的部分是数据的连续值。我一直认为这些值代表列。如果它们是随机值,我认为读者会更快地理解。总体而言很好,谢谢。 - spacedustpi
1
@ivankeller:更新您的问题以编辑后的示例/图片:“indptr的最后一个元素不应该是10而不是11吗?”--> 不是,因为在Python中对数据结构进行子集操作时,范围中的最后一个元素不会被选中(例如data[l]不包含在data[k:l]中)。 - Tanguy
2
@spacedustpi:我用字母替换了data中的值,希望这样可以避免进一步的混淆。关于你对空行的问题,@a-nadjar给出了一个例子。如果第i行为空,则indptr的第i个值等于第i+1个值,因此使用范围[indptr[i]:indptr[i+1]]dataindices进行子集化不会返回任何值。 - Tanguy
2
一张图片胜过千言万语。谢谢! - Boris Burkov
显示剩余5条评论

2
在这个例子中:
indptr = np.array([0, 2, 3, 6])
indices = np.array([0, 2, 2, 0, 1, 2])
data = np.array([1, 2, 3, 4, 5, 6])
csr_matrix((data, indices, indptr), shape=(3, 3)).toarray()
array([[1, 0, 2],
      [0, 0, 3],
      [4, 5, 6]])

要读取indptr,请按照以下步骤进行-

  • 忽略indptr[0] = 0
  • indptr[1] = 2表示第一行非零数据元素的数量,直到该行末尾
  • indptr[2] = 3表示从开头开始到第二行末尾的非零数据元素的数量。
  • indptr[3] = 6表示从开头开始到第三行末尾的非零数据元素的数量。

2
当然,indptr内的元素是按升序排列的。但如何解释indptr的行为呢?简单来说,直到indptr内的元素相同或不增加时,您可以跳过稀疏矩阵的行索引。
以下示例说明了上述对indptr元素的解释:
示例1)想象一下这个矩阵:
array([[0, 1, 0],
       [8, 0, 0],
       [0, 0, 0],
       [0, 0, 0],
       [0, 0, 7]])


mat1 = csr_matrix(([1,8,7], [1,0,2], [0,1,2,2,2,3]), shape=(5,3))
mat1.indptr
# array([0, 1, 2, 2, 2, 3], dtype=int32)
mat1.todense()  # to get the corresponding sparse matrix

例子2)将数组转换为CSR矩阵(当稀疏矩阵已经存在时):

Original Answer翻译成"最初的回答"

arr = np.array([[0, 0, 0],
                [8, 0, 0],
                [0, 5, 4],
                [0, 0, 0],
                [0, 0, 7]])


mat2 = csr_matrix(arr))
mat2.indptr
# array([0, 0, 1, 3, 3, 4], dtype=int32)
mat2.indices
# array([0, 1, 2, 2], dtype=int32)
mat.data
# array([8, 5, 4, 7], dtype=int32)

1

由于这是一个稀疏矩阵,这意味着与整个元素($m \times n$)相比,矩阵中的非零元素相对较少。

我们使用:

  • data 存储所有非零元素,从左到右,从上到下
  • indices 存储每个数据对应的列索引
  • indptr[i]:indptr[i+1] 表示在data字段中查找行[i]的所有非零元素的切片

1

这其实很简单。

indptr 是一个列表,逐个显示每列从哪个元素的索引开始。

例如:

rows = np.array([0, 0, 1, 2, 2])
cols = np.array([0, 2, 0, 0, 1])
data = np.array([1, 2, 3, 4, 5])
sparse_matrix = csc_matrix((data, (rows, cols)))
[[1, 0, 2],
 [3, 0, 0],
 [4, 5, 0]]

indptr = sparse_matrix.indptr
[0, 3, 4, 5]

这是秘密:

col_data = sparse_matrix.data  # data, column-by-column
[1, 3, 4, 5, 2]

indptr 是一个索引列表,其中包含 col_data 中每个新列开始的位置。

看看这个例子:

  • 0 列从元素 1 开始,该元素位于 col_data 的索引 0 = indptr[0]
  • 1 列从元素 5 开始,该元素位于 col_data 的索引 3 = indptr[1]
  • 2 列从元素 2 开始,该元素位于 col_data 的索引 4 = indptr[2]
  • 3 列将从 col_data 的索引 5 = indptr[3] 开始,即在其外部

1
indptr = np.array([0, 2, 3, 6])
indices = np.array([0, 2, 2, 0, 1, 2])
data = np.array([1, 2, 3, 4, 5, 6])
csr_matrix((data, indices, indptr), shape=(3, 3)).toarray()
array([[1, 0, 2],
      [0, 0, 3],
      [4, 5, 6]])

在上面的scipy文档示例中:
数据数组包含按行遍历的稀疏矩阵中存在的非零元素。
索引数组为每个非零数据点给出列号。
例如:对于数据中的第一个元素即1,col[0]为其列号;对于数据中的第二个元素即2,col[2]为其列号,直到最后一个数据元素。因此,数据数组和索引数组的大小相同。
indptr数组基本上指示了该行第一个元素的位置。它的大小比行数多1。
例如:indptr的第一个元素为0,表示在data[0]即‘1’处存在row[0]的第一个元素;indptr的第二个元素为2,表示在data[2]即‘3’处存在row[1]的第一个元素;indptr的第三个元素为3,表示在data[3]即‘4’处存在row[2]的第一个元素。
希望你能理解这个意思。

0

将indptr中的值视为在预压缩(稀疏)格式中特定行开始之前已经传递的非零元素数量。这有点难以理解,但下面的示例应该可以澄清。

import numpy as np
from scipy.sparse import csr_matrix

array_for_csr = np.array([[2, 0, 19, 5],
                          [8, 0, 0, 1],
                          [0, 0, 0, 0],
                          [4, 6, 6, 0]])
matrix = csr_matrix(array_for_csr)
print(matrix)
"""
(0, 0)  2
(0, 2)  19
(0, 3)  5
(1, 0)  8
(1, 3)  1
(3, 0)  4
(3, 1)  6
(3, 2)  6
"""
print(matrix.indices)
# [0 2 3 0 3 0 1 2]
print(matrix.indptr)
# [0 3 5 5 8]

示例:

indptr[0] = 0,因为在预压缩矩阵中第1行的开头之前已经传递了0个值(由于我们尚未开始遍历矩阵,因此没有传递任何值)

indptr[1] = 3,因为在预压缩矩阵中第2行的开头之前已经传递了3个值(即值2、19、5)

indptr[2] = 5,因为在预压缩矩阵中第3行的开头之前已经传递了5个值(即值2、19、5、8、1)

indptr[3] = 5,因为在预压缩矩阵中第4行的开头之前已经传递了5个值(由于预压缩矩阵中第4行的所有值都为零)

indptr[4] = 8,因为在预压缩矩阵中第5行的开头之前已经传递了8个值(indptr数组中的最后一个值始终等于预压缩(稀疏)矩阵中非零值的数量)


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