Работа с массивами строк и байтов#
Хотя 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', '
Если такое поведение нежелательно, можно создать экземпляр 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 в байтах не обязательно совпадает с количеством символов в строке.