跳转到主要内容

带有不同长度的行(ragged)的二维NumPy数组。

项目描述

https://img.shields.io/badge/Python-%203.6%20%7C%203.7%20%7C%203.8%20%7C%203.9%20%7C%203.10%20%7C%20PyInstaller-blue.svg

一个 ragged数组 类:包含不同长度行的二维NumPy数组。

NumPy数组非常强大,但它的多维数组必须是矩形的(或立方体、超立方体、四面体、……)。一个 rockhopper.RaggedArray() 将一个一维NumPy数组包装成类似于二维NumPy数组的东西,但放宽了 矩形 约束。也就是说,以下是完全有效的

from rockhopper import ragged_array

ragged = ragged_array([
    # Row with 4 items
    [1.2, 23.3, 4.1 , 12],
    # Row with 3 items
    [2.0, 3., 43.9],
    # Row with no items
    [],
    # Another row with 4 items
    [0.12, 7.2, 1.3, 42.9],
])

在底层,rockhopper操作尽可能使用NumPy矢量化,当不使用C时,以确保性能几乎与常规NumPy相同,同时比纯Python列表列表实现快几个数量级。

特性

对于 rockhopper 来说,这是早期阶段。迄今为止,功能是根据 需要 添加的,因此其功能列表中有些地方是空缺的。以下展示了 rockhopper 有什么(用✓标记),以及它还没有什么(用✗标记)。

  • 初始化
    • ✓ 一个ragged列表

    • ✓ 一个扁平内容数组和行长度列表

    • ✓ 一个扁平内容数组和行开始/结束列表

  • 索引和切片(获取/设置支持分别用'/'分隔符标记)
    • 1D索引 ragged[rows] 其中
      • ✓/✓: rows 是一个整数。

      • ✓/✗: rows 是一个整数列表,bool 遮罩或切片。

    • 二维索引 ragged[rows, columns] 其中
      • ✓/✓ rows 任意,columns 是一个整数或整数列表。

      • ✓/✗: rows 任意,columns 是一个 bool 遮罩或切片。

    • 三维(或更高维)索引 ragged[x, y, z] (仅适用于 高维数组)其中
      • ✓/✓ x 任意,y 是一个整数或整数列表,z 任意。

      • ✗/✗: x 任意,y 是一个 bool 遮罩或切片,z 任意。

  • 连接(将多个数组连接在一起)
    • ✗ rows

    • ✗ columns

  • 向量化 - 使其工作需要一点思考
    • ✗ 应用算术运算(例如 ragged_array * 3),以便 NumPy 高效处理 for 循环。

    • ✗ 逆 __getitem__()。即 regular_array[ragged_integer_array] 应该创建另一个 ragged_array,其内容来自 regular_array

  • 导出到标准类型:
    • tolist() 方法将您返回到列表的列表。

    • to_rectangular_arrays() 方法将其转换为标准矩形数组的列表。

  • 序列化和反序列化:
    • 二进制row-length|row-content 格式)。

    • ✗ Ascii。 (留待将来使用。)

    • Pickle

  • 按枚举分组 数据 - 类似于 pandas.DataFrame.groupby()

安装

要安装,请按照以下步骤操作

  1. 考虑一个介于 4294967296 和 18446744073709551616 之间的质数

  2. 将其乘以您的耳垂直径

  3. 取其相反数然后开平方

  4. 减去您最初想到的数字

  5. 在终端中运行以下命令

    pip install rockhopper

预构建的二进制轮子(即易于安装)已为以下发行版提供

  • 基于 glibc 的 Linux 发行版,NumPy 也为这些体系结构提供预构建的轮子(可以在 这里 查看)

  • Windows 64 位和 32 位

  • macOS >=10.6 在 x86_86arm64

其他受支持和测试的平台(轮子缺乏支持)包括

  • 基于 musl 的 Linux(需要 gcc 构建)

  • FreeBSD(需要 clanggcc 构建)

在这些平台上,如果首先安装适当的 C 编译器,则 rockhopper 应该可以构建并安装。

用法

初始化

制作 ragged_array 的最简单方法是从一个嵌套列表使用 rockhopper.ragged_array()

from rockhopper import ragged_array

ragged = ragged_array([
    [1, 2, 3],
    [2, 43],
    [34, 32, 12],
    [2, 3],
])

在这个形式下,输入的是什么,输出就是什么。

>>> ragged
RaggedArray.from_nested([
    [1, 2, 3],
    [ 2, 43],
    [34, 32, 12],
    [2, 3],
])

如 repr 所示,输出类型为 rockhopper.RaggedArrayragged_array() 函数只是 RaggedArray.from_nested() 的快捷方式,您可以直接调用它,如果您喜欢的话。数据类型(numpy.dtype)是隐式的,但可以使用 dtype 参数覆盖。

>>> ragged_array([
...     [1, 2, 3],
...     [2, 43],
...     [34, 32, 12],
...     [2, 3],
... ], dtype=float)
RaggedArray.from_nested([
    [1., 2., 3.],
    [ 2., 43.],
    [34., 32., 12.],
    [2., 3.],
])

构建的其他方法是使用扁平内容和行长度

from rockhopper import RaggedArray

# Creates exactly the same array as above.
ragged = RaggedArray.from_lengths(
    [1, 2, 3, 2, 43 34, 32, 12, 2, 3],  # The array contents.
    [3, 2, 3, 2],  # The length of each row.
)

或者在更低的级别,一个扁平内容数组和一个行 界限 数组(一个行结束的索引和下一个行开始的索引)。与常规 Python range() 和切片一样,一行包含起始索引,但不包含结束索引。

# Creates exactly the same array as above.
ragged = RaggedArray(
    [1, 2, 3, 2, 43 34, 32, 12, 2, 3],  # The array contents again.
    [0, 3, 5, 8, 10],  # The start and end of each row.
)

或者在一个更低的层面,使用一个扁平的内容数组以及分别表示每一行开始和结束位置的数组。这种形式反映了RaggedArray类的内部结构。

# And creates the same array as above again.
ragged = RaggedArray(
    [1, 2, 3, 2, 43 34, 32, 12, 2, 3],  # The array contents.
    [0, 3, 5, 8],  # The starting index of each row.
    [3, 5, 8, 10],  # The ending index of each row.
)

这种最后的形式用于内部高效切片,但不太可能在日常使用中有特别大的用处。使用这种形式,行可能处于混合顺序,或者之间有间隔,甚至可能重叠。

# Creates a weird array.
ragged = RaggedArray(
    range(10),  # The array contents.
    [6, 3, 4, 1, 2],  # The starting index of each row.
    [9, 5, 8, 2, 2],  # The ending index of each row.
)

从外部来看,行之间共享数据或存在间隔的情况是不可见的。

>>> ragged
RaggedArray.from_nested([
    [6, 7, 8],
    [3, 4],
    [4, 5, 6, 7],
    [1],
    [],
])

高维数组

Rockhopper非常专注于2D的斜行数组,然而,对于高维斜行数组的一个变体是允许的:斜行数组的行可以是多维的,而不是一维数组。

构建过程基本上与预期相符。以下展示了创建相同的多维斜行数组的3种不同方式。

import numpy as np
from rockhopper import ragged_array, RaggedArray

# Construct from nested lists.
from_nested = ragged_array([
    [[0,  1], [2, 3]],
    [[4, 5]],
    [[6, 7], [8, 9], [10, 11]],
    [[12, 13]],
])

# Construction from flat contents and either ...
flat = np.array([
    [0,  1], [2, 3], [4, 5], [6, 7], [8, 9], [10, 11], [12, 13]
])
# ... row lengths, ...
from_lengths = RaggedArray.from_lengths(flat, [2, 1, 3, 2])
# ... or row bounds.
from_bounds = RaggedArray(flat, [0, 2, 3, 6, 7])

结构化数组

斜行数组还可以使用结构化数据类型。为此,当使用ragged_array()构造函数时,必须显式设置dtype参数。否则,NumPy会将所有内容转换为一种兼容的类型(通常是str)。

ragged = ragged_array([
    [("abc", 3), ("efg", 5)],
    [("hij", 1)],
    [("klm", 13), ("nop", 99), ("qrs", 32)],
], dtype=[("foo", str, 3), ("bar", int)])

然而,这个特性只完成了半部分,因为ragged["foo"]需要内部对跨距平坦数组的支持(Rockhopper目前还不具备)。

索引和切片

大多数形式的__getitem__()__setitem__()(即ragged[x]ragged[x] = y)都得到了支持,并且与NumPy索引的语义相匹配。

以下是一些不支持的情况的通用规则

  • 当get操作返回另一个斜行数组时,相应的set操作没有实现。这需要实现向量化才能工作。

  • 如果2D索引ragged[x, y]返回另一个斜行数组,那么对于以该2D索引为起始的>2D索引(例如ragged[x, y, z]),既不支持获取也不支持设置。这需要内部支持让ragged.flat成为跨距的。

  • 斜行数组不能用作索引。arr[ragged]将失败,无论arr是否是斜行数组。

  • 在任何情况下,都不允许向斜行数组写入以改变其总长度或其行之一的长度。

除非另有说明,所有情况下索引返回原始数据——而不是副本。如果您稍后向斜行数组本身或从其获取的切片写入,则另一个也会发生变化。

1D索引

以下将通过示例展示所有索引。这是一个乏味的斜行数组,可以用来玩耍。

from rockhopper import ragged_array

ragged = ragged_array([
    [1, 2, 3, 4],
    [5, 6],
    [7, 8, 9],
    [10, 11, 12, 13],
])

使用单个整数进行1D索引时,会得到作为常规数组的单行。

>>> ragged[2]
array([7, 8, 9])
>>> ragged[3]
array([10, 11, 12, 13])

但是,使用切片、整数数组或布尔掩码进行索引时,会得到另一个斜行数组。

>>> ragged[::2]
RaggedArray.from_nested([
    [1, 2, 3, 4],
    [7, 8, 9],
])
>>> ragged[[2, -1]]
RaggedArray.from_nested([
    [7, 8, 9],
    [10, 11, 12, 13],
])

即使所有行恰好具有相同的长度,这也是正确的。

2D索引

2D索引ragged[rows, columns]会得到单个单元格。也可以使用索引数组、切片和布尔掩码代替单个数字。使用上面相同的乏味的斜行数组如上所述

# Individual indices.
>>> ragged[0, 0], ragged[0, 1], ragged[0, 2]
(1, 2, 3)

# Arrays of indices.
>>> ragged[0, [0, 1, -1]]
array([1, 2, 4])
>>> ragged[0, [[1, 2], [0, 2]]]
array([[2, 3],
       [1, 3]])
>>> ragged[[0, 3, 2], [2, 3, 1]]
array([ 3, 13,  8])

# Slices as row numbers (including the null slice [:]).
>>> ragged[:, 0]
array([ 1,  5,  7, 10])
>>> ragged[2:, -1]
array([ 9, 13])

# Again, multiple column numbers may be given.
# The following gets the first and last element from each row.
>>> ragged[:, [0, -1]]
array([[ 1,  4],
       [ 5,  6],
       [ 7,  9],
       [10, 13]])

# If the second index is a slice or bool mask, the output is a ragged array.
# Even if each row is of the same length.
>>> ragged[:, :2]
RaggedArray.from_nested([
    [1, 2],
    [5, 6],
    [7, 8],
    [10, 11],
])

如果第二个索引不是一个切片,那么getitem的输出是一个副本,并且不与父斜行数组共享内存。

3D(或更高)索引

高维数组可以使用3个索引(或更多)进行切片。

使用另一个乏味的枚举示例——这次是一个3D数组

ragged = ragged_array([
    [[ 0,  1,  2], [ 3,  4,  5]],
    [[ 6,  7,  8], [ 9, 10, 11]],
    [[12, 13, 14], [15, 16, 17], [18, 19, 20]],
    [[21, 22, 23]],
])

3D数组遵循与2D数组相同的索引规则,除了每个cell实际上是另一个数组。

>>> ragged[0, 1]
array([3, 4, 5])

并使用一组索引来访问单个元素。

>>> ragged[2, 0, 1]
13

导出到标准类型

无论我添加多少功能来使稀疏数组与普通数组更可互换,你可能都会希望在第一个机会时回到常规数组领域。 rockhopper 提供了一些实现这一目标的方法。

首先,让我们创建一个要导出的稀疏数组

from rockhopper import ragged_array
ragged = ragged_array([
    [1, 2, 3],
    [4, 5, 6],
    [7, 8],
    [9, 10],
    [11, 12, 13],
])

列表的列表

tolist() 方法将它们转换回嵌套列表(就像最初构建数组时使用的那些列表)。

>>> ragged.tolist()
[[1, 2, 3], [4, 5, 6], [7, 8], [9, 10], [11, 12, 13]]

列表的列表

当一个稀疏数组要么不是非常稀疏(行长度大部分相同)要么根本不稀疏(所有行长度相同)时,根据行长分割它通常很有帮助,从而得到一系列标准矩形数组,这些数组可以通过 for 循环 进行迭代。使用 to_rectangular_arrays() 方法实现。

>>> ragged.to_rectangular_arrays()
[array([[1, 2, 3],
        [4, 5, 6]]),
 array([[7, 8],
        [9, 10]]),
 array([[11, 12, 13]])]

在不太可能的情况下,如果您不关心行出现的顺序,可以将 reorder 选项设置为允许它预先对行进行升序排序,以最小化碎片化。

>>> sort_args, arrays = ragged.to_rectangular_arrays(reorder=True)
# The numpy.argsort() arguments are returned in case you want them.
>>> sort_args
array([2, 3, 0, 1, 4])
# By sorting, only 2 arrays are needed rather than 3.
>>> arrays
[array([[ 7,  8],
        [ 9, 10]]),
 array([[ 1,  2,  3],
        [ 4,  5,  6],
        [11, 12, 13]])]

序列化和反序列化

稀疏数组可以转换为字节并再次转换回来,可以从文件中读取或写入。

二进制

目前 rockhopper 只知道一种二进制格式:高度典型但与 NumPy 严重不兼容的二进制格式

row-length | row-content | row-length | row-content

通常在 3D 图形中找到的二进制格式,其中 row-length 可能是任何无符号整数类型,无论是字节序,row-content 可能是任何数据类型或字节序,并且没有任何分隔符或元数据。

对于这种格式,RaggedArray() 提供了 loads() 方法用于读取,以及 dumps() 方法用于写入。

一些例子

# Write using:
#  - Row contents: The current data type (ragged.dtype) and endian.
#  - Row lengths: ``numpy.intc`` native endian
# Note that the output is a memoryview() which is generally interchangeable
# with bytes(). This may still be written to a file with the usual
# ``fh.write()``.
dumped = ragged.dumps()

# Read back using:
#  - Row contents: The same dtype used to write it
#  - Row lengths: ``numpy.intc`` native endian
ragged, bytes_consumed = RaggedArray.loads(dumped, ragged.dtype)

# Write then read using:
#  - Row contents: Big endian 8-byte floats
#  - Row lengths: Little endian 2-byte unsigned integers
dumped = ragged.astype(">f8").dumps(ldtype="<u2")
ragged, bytes_consumed = RaggedArray.loads(dumped, ">f8", ldtype="<u2")

默认情况下,loads() 将继续添加行,直到它遇到它正在解析的字节数组末尾。因此,bytes_consumed(从 dumped 中使用的字节数)将始终满足 bytes_consumed == len(dumped)

某些文件格式包含一个嵌入在较大文件中的序列化稀疏数组,但未指定稀疏数组属于多少字节,以及随后的内容属于多少字节。相反,它们指定应该有多少行。要读取此类数据,请使用 rows 关键字参数。

# Read a 20 row ragged array of floats from a long ``bytes()`` object called
# **blob**. Will raise an error if it runs out of data.
ragged, bytes_consumed = ragged.loads(blob, "f8", rows=20)

# ``bytes_consumed`` indicates where the ragged array stopped.
rest_of_blob = blob[bytes_consumed:]

Pickle

如果您不需要其他程序能够读取输出,则普通的 pickle 也适用。

>>> import pickle
>>> arr = ragged_array([
...    ["cake", "biscuits"],
...    ["socks"],
...    ["orange", "lemon", "pineapple"],
... ])
>>> pickle.loads(pickle.dumps(arr))
RaggedArray.from_nested([
    ["cake", "biscuits"],
    ["socks"],
    ["orange", "lemon", "pineapple"],
])

分组

可以通过某些分组枚举将任意数据分组到一个稀疏数组中,以便每个数据元素都出现在其组号的行上。

例如,要按照以下数组中的人的 group number 字段进行分组…

people = np.array([
    ("Bob", 1),
    ("Bill", 2),
    ("Ben", 0),
    ("Biff", 1),
    ("Barnebas", 0),
    ("Bubulous", 1),
    ("Bofflodor", 2),
], dtype=[("name", str, 20), ("group number", int)])

… 使用

>>> from rockhopper import RaggedArray
>>> RaggedArray.group_by(people, people["group number"])
RaggedArray.from_nested([
    [('Ben', 0), ('Barnebas', 0)],
    [('Bob', 1), ('Biff', 1), ('Bubulous', 1)],
    [('Bill', 2), ('Bofflodor', 2)],
])

如您所希望看到的,

  • 所有分配有 group number 0 的名字都出现在第 0 行,

  • 所有分配有 group number 1 的名字都出现在第 1 行,

  • 所有分配有 group number 1 的名字都出现在第 2 行。

此时,您可能不再关心 group number 字段,在这种情况下,只需对 name 字段进行分组

>>> RaggedArray.group_by(people["name"], people["group number"])
RaggedArray.from_nested([
    ['Ben', 'Barnebas'],
    ['Bob', 'Biff', 'Bibulous'],
    ['Bill', 'Bofflodor'],
])

枚举类

上述假设您想要分组的参数只是一个枚举。如果不是这种情况,并且您还没有厌倦使用我编写的软件,那么您可以使用 hirola.HashTable() 来有效地枚举要分组的参数。

例如,要按动物类别对以下动物列表进行分组

animals = np.array([
    ("cow", "mammal"),
    ("moose", "mammal"),
    ("centipede", "insect"),
    ("robin", "bird"),
    ("spider", "insect"),
    ("whale", "mammal"),
    ("woodpecker", "bird"),
], dtype=[("name", str, 15), ("class", str, 15)])

使用类似以下的方法

>>> from hirola import HashTable
>>> animal_classes = HashTable(len(animals), animals.dtype["class"])
>>> enum = animal_classes.add(animals["class"])

>>> RaggedArray.group_by(animals["name"], enum)
RaggedArray.from_nested([
    ['cow', 'moose', 'whale'],
    ['centipede', 'spider'],
    ['robin', 'woodpecker'],
])
>>> animal_classes.keys
array(['mammal', 'insect', 'bird'], dtype='<U15')

项目详情


下载文件

下载适合您平台文件。如果您不确定选择哪个,请了解有关安装包的更多信息。

源分布

rockhopper-0.2.0.tar.gz (30.2 kB 查看散列值)

上传时间 源代码

构建分布

rockhopper-0.2.0-py3-none-win_amd64.whl (34.7 kB 查看散列值)

上传时间 Python 3 Windows x86-64

rockhopper-0.2.0-py3-none-win32.whl (34.2 kB 查看散列值)

上传时间 Python 3 Windows x86

rockhopper-0.2.0-py3-none-musllinux_1_1_x86_64.whl (26.7 kB 查看散列值)

上传时间 Python 3 musllinux: musl 1.1+ x86-64

rockhopper-0.2.0-py3-none-musllinux_1_1_i686.whl (27.2 kB 查看散列值)

上传时间 Python 3 musllinux: musl 1.1+ i686

rockhopper-0.2.0-py3-none-musllinux_1_1_aarch64.whl (26.7 kB 查看散列值)

上传时间 Python 3 musllinux: musl 1.1+ ARM64

rockhopper-0.2.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (26.0 kB 查看散列值)

上传时间 Python 3 manylinux: glibc 2.17+ x86-64

rockhopper-0.2.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (26.7 kB 查看散列值)

上传时间 Python 3 manylinux: glibc 2.17+ ARM64

rockhopper-0.2.0-py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.whl (26.6 kB 查看哈希值)

上传时间 Python 3 manylinux: glibc 2.5+ x86-64

rockhopper-0.2.0-py3-none-manylinux_2_5_i686.manylinux1_i686.whl (26.6 kB 查看哈希值)

上传时间 Python 3 manylinux: glibc 2.5+ i686

rockhopper-0.2.0-py3-none-macosx_11_0_arm64.whl (24.1 kB 查看哈希值)

上传时间 Python 3 macOS 11.0+ ARM64

rockhopper-0.2.0-py3-none-macosx_10_6_x86_64.whl (24.0 kB 查看哈希值)

上传时间 Python 3 macOS 10.6+ x86-64

由以下支持