Работа с массивами строк и байтов#

Хотя 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. По этой причине они используются как dtype по умолчанию для строк и байтовых строк соответственно:

>>> np.array(["hello", "world"])
array(['hello', 'world'], dtype='

Здесь обнаруженный тип данных ', или строковые данные в кодировке little-endian 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, который хранит данные строк переменной ширины в кодировке UTF-8 в массиве NumPy:

>>> 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())

При желании вы можете создать экземпляр StringDType с поддержкой пропущенных значений путем передачи na_object как аргумент ключевого слова для инициализатора:

>>> 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

The na_object может быть любым произвольным объектом Python. Обычные варианты — numpy.nan, float('nan'), None, объект, специально предназначенный для представления пропущенных данных, таких как pandas.NA, или (предположительно) уникальную строку, такую как "__placeholder__".

NumPy имеет специальную обработку для NaN-подобных и строковых маркеров.

NaN-подобные маркеры отсутствующих данных#

NaN-подобный сентинель возвращает себя в результате арифметических операций. Это включает python nan float и маркер отсутствующих данных 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 или подтип 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', '', '3.4'], dtype=StringDType())


Если такое поведение нежелательно, можно создать экземпляр DType, отключающий приведение строк, установив 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 .
>>> arr.astype("U5")
array(['hello', 'world'], dtype='

The 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 в байтах не обязательно совпадает с количеством символов в строке.