文中内容仅限技术学习与代码实践参考,市场存在不确定性,技术分析需谨慎验证,不构成任何投资建议。
使用字符串和字节数组
尽管 NumPy 主要是一个数值库,但使用 NumPy 的字符串或字节数组往往也很方便。最常见的两种用例是:
从数据文件加载或内存映射数据,其中数据的一个或多个字段是字符串或字节字符串,且字段的最大长度是已知的。这通常用于名称或标签字段。
使用 NumPy 索引和广播处理未知长度的 Python 字符串数组,这些字符串可能并非每个值都有数据定义。
对于第一种用例,NumPy 提供了固定宽度的 numpy.void
、numpy.str_
和 numpy.bytes_
数据类型。对于第二种用例,NumPy 提供了 numpy.dtypes.StringDType
。下面我们将描述如何处理固定宽度和可变宽度的字符串数组,如何在这两种表示之间进行转换,并为在 NumPy 中高效处理字符串数据提供一些建议。
固定宽度数据类型
在 NumPy 2.0 之前,固定宽度的 numpy.str_
、numpy.bytes_
和 numpy.void
数据类型是 NumPy 中处理字符串和字节字符串的唯一类型。因此,它们被用作字符串和字节字符串的默认数据类型:
>>> np.array(["hello", "world"])
array(['hello', 'world'], dtype='<U5')
这里检测到的数据类型是 '<U5'
,即小端 Unicode 字符串数据,最大长度为 5 个 Unicode 编码点。
对于字节字符串同样适用:
>>> np.array([b"hello", b"world"])
array([b'hello', b'world'], dtype='|S5')
由于这是一种单字节编码,字节序为 ' | '
(不适用),检测到的数据类型是最大长度为 5 个字符的字节字符串。
您也可以使用 numpy.void
来表示字节字符串:
>>> np.array([b"hello", b"world"]).astype(np.void)
array([b'\x68\x65\x6C\x6C\x6F', b'\x77\x6F\x72\x6C\x64'], dtype='|V5')
当处理不能很好地表示为字节字符串的字节流时,而是更好地被视为 8 位整数的集合时,这最有用。
可变宽度字符串
新增于版本 2.0。
注意
numpy.dtypes.StringDType
是 NumPy 的一个新功能,它是通过 NumPy 对灵活的用户定义数据类型的新支持实现的,其在生产工作流程中的测试程度不如旧的 NumPy 数据类型广泛。
通常,现实世界中的字符串数据长度是不可预测的。在这种情况下,使用固定宽度的字符串会很不方便,因为要在创建数组之前就知道要存储的最长字符串的长度,才能在不截断数据的情况下存储所有数据。
为了支持这种情况,NumPy 提供了 numpy.dtypes.StringDType
,它可以在 NumPy 数组中以 UTF-8 编码的形式存储可变宽度的字符串数据:
>>> from numpy.dtypes import StringDType
>>> data = ["this is a longer string", "short string"]
>>> arr = np.array(data, dtype=StringDType())
>>> arr
array(['this is a longer string', 'short string'], dtype=StringDType())
请注意,与固定宽度字符串不同,StringDType
不以数组元素的最大长度为参数,任意长度的长字符串或短字符串都可以存储在同一个数组中,而无需为短字符串预留填充字节的存储空间。
还需要注意的是,与固定宽度字符串和大多数其他 NumPy 数据类型不同,StringDType
不在“主”ndarray
数据缓冲区中存储字符串数据。相反,数组缓冲区用于存储有关字符串数据在内存中存储位置的元数据。这种差异意味着期望数组缓冲区包含字符串数据的代码将无法正常工作,需要更新以支持 StringDType
。
缺失数据支持
通常字符串数据集并不完整,需要一个特殊的标签来表示某个值缺失。默认情况下,StringDType
除了使用空字符串填充空数组外,并没有特别支持缺失值:
>>> np.empty(3, dtype=StringDType())
array(['', '', ''], dtype=StringDType())
可选地,您可以通过在初始化器中传递 na_object
作为关键字参数来创建一个支持缺失值的 StringDType
实例:
>>> dt = StringDType(na_object=None)
>>> arr = np.array(["this array has", None, "as an entry"], dtype=dt)
>>> arr
array(['this array has', None, 'as an entry'],
dtype=StringDType(na_object=None))
>>> arr[1] is None
True
na_object
可以是任意的 Python 对象。常见的选择有 numpy.nan
、float('nan')
、None
、专门用于表示缺失数据的对象(如 pandas.NA
),或者一个(希望是)独特的字符串(如 "__placeholder__"
)。
NumPy 对 NaN 类似的哨兵值和字符串哨兵值有特殊处理。
NaN 类似的缺失数据哨兵值
NaN 类似的哨兵值会在算术运算中返回自身作为结果。这包括 Python 的 nan
浮点数和 Pandas 的缺失数据哨兵 pd.NA
。在字符串操作中,NaN 类似的哨兵值继承了这些行为。这意味着,例如,与任何其他字符串进行加法运算的结果是哨兵值:
>>> dt = StringDType(na_object=np.nan)
>>> arr = np.array(["hello", np.nan, "world"], dtype=dt)
>>> arr + arr
array(['hellohello', nan, 'worldworld'], dtype=StringDType(na_object=nan))
按照浮点数组中 nan
的行为,NaN 类似的哨兵值在排序时会排在数组的末尾:
>>> np.sort(arr)
array(['hello', 'world', nan], dtype=StringDType(na_object=nan))
字符串缺失数据哨兵值
字符串缺失数据值是 str
的实例或子类型。如果将这样的数组传递给字符串操作或进行类型转换,“缺失”的条目将被视为具有由字符串哨兵值给出的值。比较操作同样直接使用哨兵值来处理缺失条目。
其他哨兵值
其他对象(如 None
)也支持作为缺失数据的哨兵值。如果在使用此类哨兵值的数组中存在任何缺失数据,那么字符串操作将引发错误:
>>> dt = StringDType(na_object=None)
>>> arr = np.array(["this array has", None, "as an entry"])
>>> np.sort(arr)
Traceback (most recent call last):
...
TypeError: '<' not supported between instances of 'NoneType' and 'str'
强制转换非字符串
默认情况下,非字符串数据会被强制转换为字符串:
>>> np.array([1, object(), 3.4], dtype=StringDType())
array(['1', '<object object at 0x7faa2497dde0>', '3.4'], dtype=StringDType())
如果这种行为不被期望,可以通过在初始化器中设置 coerce=False
来创建一个数据类型的实例,从而禁用字符串强制转换:
>>> np.array([1, object(), 3.4], dtype=StringDType(coerce=False))
Traceback (most recent call last):
...
ValueError: StringDType only allows string data when string coercion is disabled.
这允许在 NumPy 用于创建数组的数据遍历过程中进行严格的数据验证。设置 coerce=True
可以恢复默认行为,允许强制转换为字符串。
在固定宽度字符串之间进行类型转换
StringDType
支持在 numpy.str_
、numpy.bytes_
和 numpy.void
之间进行往返类型转换。当需要在 ndarray 中进行内存映射字符串,或者需要固定宽度字符串来读取和写入具有已知最大字符串长度的列式数据格式时,将字符串转换为固定宽度字符串最有用。
在所有情况下,将字符串转换为固定宽度字符串都需要指定最大允许的字符串长度:
>>> arr = np.array(["hello", "world"], dtype=StringDType())
>>> arr.astype(np.str_)
Traceback (most recent call last):
...
TypeError: Casting from StringDType to a fixed-width dtype with an
unspecified size is not currently supported, specify an explicit
size for the output dtype instead.
The above exception was the direct cause of the following
exception:
TypeError: cannot cast dtype StringDType() to <class 'numpy.dtypes.StrDType'>.
>>> arr.astype("U5")
array(['hello', 'world'], dtype='<U5')
numpy.bytes_
类型转换对于已知仅包含 ASCII 字符的字符串数据最有用,因为超出此范围的字符无法在 UTF-8 编码中用单字节表示,会被拒绝。
任何有效的 Unicode 字符串都可以转换为 numpy.str_
,但由于 numpy.str_
使用 32 位 UCS4 编码来表示所有字符,这通常会为可以由更节省内存的编码很好地表示的实际文本数据浪费内存。
此外,任何有效的 Unicode 字符串都可以转换为 numpy.void
,直接在输出数组中存储 UTF-8 字节:
>>> arr = np.array(["hello", "world"], dtype=StringDType())
>>> arr.astype("V5")
array([b'\x68\x65\x6C\x6C\x6F', b'\x77\x6F\x72\x6C\x64'], dtype='|V5')
需要注意的是,必须确保输出数组有足够的空间来存储字符串的 UTF-8 字节,因为 UTF-8 字节流的大小并不一定与字符串中的字符数量相同。
风险提示与免责声明
本文内容基于公开信息研究整理,不构成任何形式的投资建议。历史表现不应作为未来收益保证,市场存在不可预见的波动风险。投资者需结合自身财务状况及风险承受能力独立决策,并自行承担交易结果。作者及发布方不对任何依据本文操作导致的损失承担法律责任。市场有风险,投资须谨慎。