Руководство по миграции для нового типа данных строк (pandas 3.0)#
Предстоящий выпуск pandas 3.0 представляет новый тип данных строк по умолчанию. Это скорее всего вызовет некоторую работу при обновлении до pandas 3.0, и эта страница предоставляет обзор проблем, с которыми вы можете столкнуться, и даёт рекомендации по их решению.
Этот новый тип данных уже доступен в выпуске pandas 2.3, и вы можете включить его с помощью:
pd.options.future.infer_string = True
Это позволяет протестировать ваш код до финального релиза 3.0.
Фон#
Исторически pandas всегда использовал NumPy object тип данных по умолчанию для хранения текстовых данных. Это имеет два основных недостатка. Во-первых, object dtype не специфичен для строк: любой объект Python может быть сохранён в object-dtype
массив, а не только строки, и видя object как тип данных для колонки со
строками сбивает пользователей с толку. Во-вторых, это не всегда эффективно (как
с точки зрения производительности, так и использования памяти).
Начиная с pandas 1.0 доступен опциональный строковый тип данных, но он ещё не стал типом по умолчанию и использует pd.NA скаляр для представления пропущенных значений.
Pandas 3.0 изменяет тип данных по умолчанию для строк на новый тип строковых данных,
вариант существующего опционального типа строковых данных, но использующий NaN как
индикатор пропущенного значения, для согласованности с другими типами данных по умолчанию.
Для повышения производительности новый строковый тип данных будет использовать pyarrow
пакет по умолчанию, если установлен (в противном случае он использует тип данных object под капотом как запасной вариант).
См. PDEP-14: Выделенный строковый тип данных для pandas 3.0 для получения дополнительной информации и подробностей.
Краткое введение в новый строковый тип данных по умолчанию#
По умолчанию pandas будет определять этот новый строковый тип данных вместо типа object для строковых данных (при создании объектов pandas, например, в конструкторах или функциях ввода-вывода).
Будучи типом данных по умолчанию, означает, что строковый тип данных будет использоваться в методах ввода-вывода или конструкторах, когда тип данных выводится, и входные данные считаются строковыми:
>>> pd.Series(["a", "b", None])
0 a
1 b
2 NaN
dtype: str
Также может быть указано явно с помощью "str" псевдоним:
>>> pd.Series(["a", "b", None], dtype="str")
0 a
1 b
2 NaN
dtype: str
Аналогично, функции, такие как read_csv(), read_parquet(), и другие теперь будут использовать новый тип данных string при чтении строковых данных.
В отличие от текущего типа данных object, новый строковый тип данных будет хранить только строки. Это также означает, что он вызовет ошибку, если вы попытаетесь сохранить нестроковое значение в нем (подробнее см. ниже).
Пропущенные значения с новым строковым типом данных всегда представлены как NaN (np.nan),
и поведение с пропущенными значениями аналогично другим типам данных по умолчанию.
Этот новый строковый тип данных в остальном должен вести себя так же, как существующий
object типы данных, к которым привыкли пользователи. Например, все строковые методы
через str аксессор будет работать так же:
>>> ser = pd.Series(["a", "b", None], dtype="str")
>>> ser.str.upper()
0 A
1 B
2 NaN
dtype: str
Примечание
Новый dtype строк по умолчанию является экземпляром pandas.StringDtype
класс. Тип данных может быть создан как pd.StringDtype(na_value=np.nan),
но для общего использования мы рекомендуем использовать более короткий "str" alias.
Обзор различий в поведении и способы их устранения#
Тип данных больше не является numpy-типом "object"#
При определении или чтении строковых данных тип данных результирующего столбца DataFrame
или Series будет автоматически становиться новым "str" dtype вместо
numpy "object" тип данных, и это может повлиять на ваш код.
Новый строковый тип данных является типом данных pandas ("расширенный тип данных") и больше не является
numpy np.dtype экземпляра. Поэтому передача dtype строкового столбца в
функции numpy больше не будет работать (например, передача в dtype= аргумент
функции numpy, или используя np.issubdtype для проверки типа данных).
Проверка типа данных#
При проверке типа данных код может делать что-то вроде:
>>> ser = pd.Series(["a", "b", "c"])
>>> ser.dtype == "object"
для проверки столбцов со строковыми данными (путем проверки, что dtype равен
"object"). Это больше не будет работать в pandas 3+, так как ser.dtype теперь будет "str" с новым типом строк по умолчанию, и вышеуказанная проверка
вернет False.
Чтобы проверить столбцы со строковыми данными, вы должны вместо этого использовать:
>>> ser.dtype == "str"
Как писать совместимый код
Для кода, который должен работать как на pandas 2.x, так и на 3.x, вы можете использовать
pandas.api.types.is_string_dtype() функция:
>>> pd.api.types.is_string_dtype(ser.dtype)
True
Это вернет True как для типа данных object, так и для строковых типов данных.
Жёстко заданное использование типа данных object#
Если у вас есть код, где тип данных жёстко закодирован в конструкторах, например
>>> pd.Series(["a", "b", "c"], dtype="object")
это продолжит использовать тип object. Вам следует обновить этот код, чтобы гарантировать получение преимуществ нового строкового типа данных.
Как писать совместимый код?
Во-первых, во многих случаях может быть достаточно удалить конкретный тип данных и
позволить pandas сделать вывод. Но если вы хотите быть конкретным, вы можете указать
"str" dtype:
>>> pd.Series(["a", "b", "c"], dtype="str")
Это также совместимо с pandas 2.x, поскольку в pandas < 3
dtype="str" по сути рассматривался как псевдоним для типа данных object.
Внимание
При использовании dtype="str" в конструкторах совместим с pandas 2.x,
указание его как dtype в astype() сталкивается с проблемой также строкового представления пропущенных значений в pandas 2.x. См. раздел
astype(str) с сохранением пропущенных значений для получения дополнительной информации.
Для выбора строковых столбцов с select_dtypes() совместимым с pandas 2.x и 3.x способом, невозможно использовать "str". Хотя это
работает для pandas 3.x, это вызывает ошибку в pandas 2.x.
В качестве альтернативы вы можете выбрать оба object (для pandas 2.x) и
"string" (для pandas 3.x; который также выберет значение по умолчанию str dtype
и не вызывает ошибку на pandas 2.x):
# can use ``include=["str"]`` for pandas >= 3
>>> df.select_dtypes(include=["object", "string"])
Сентинель пропущенных значений теперь всегда NaN#
При использовании типа object поддерживаются несколько возможных маркеров пропущенных значений, включая None и np.nan. С новым типом строк по умолчанию,
маркер отсутствующего значения всегда NaN (np.nan):
# with object dtype, None is preserved as None and seen as missing
>>> ser = pd.Series(["a", "b", None], dtype="object")
>>> ser
0 a
1 b
2 None
dtype: object
>>> print(ser[2])
None
# with the new string dtype, any missing value like None is coerced to NaN
>>> ser = pd.Series(["a", "b", None], dtype="str")
>>> ser
0 a
1 b
2 NaN
dtype: str
>>> print(ser[2])
nan
Обычно это не должно быть проблемой при использовании поведения пропущенных значений в методах pandas (например, ser.isna() даст тот же результат, что и раньше).
Но если вы полагались на точное значение None присутствие, которое может повлиять на ваш код.
Как писать совместимый код?
При проверке на отсутствующее значение, вместо проверки точного значения
None или np.nan, следует использовать pandas.isna() функция. Это наиболее надежный способ проверки пропущенных значений, так как он будет работать независимо от типа данных и конкретного значения-индикатора пропуска:
>>> pd.isna(ser[2])
True
Одно предостережение: эта функция работает как со скалярами, так и с массивами, и в последнем случае она вернет массив булевых значений. При использовании в булевом контексте (например, if pd.isna(..): ..) убедитесь, что передаете только скалярное значение.
Операции "setitem" теперь будут вызывать ошибку для нестроковых данных#
С новым строковым dtype любая попытка установить нестроковое значение в Series или DataFrame вызовет ошибку:
>>> ser = pd.Series(["a", "b", None], dtype="str")
>>> ser[1] = 2.5
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
...
TypeError: Invalid value '2.5' for dtype 'str'. Value should be a string or missing value, got 'float' instead.
Если вы полагались на гибкую природу типа object, способного хранить любой объект Python, но ваши исходные данные были определены как строки, ваш код может быть затронут этим изменением.
Как писать совместимый код?
Вы можете обновить свой код, чтобы гарантировать установку только строковых значений в таких столбцах,
или вы можете явно убедиться, что столбец имеет тип object сначала. Это
можно сделать, указав dtype явно в конструкторе или используя astype() method:
>>> ser = pd.Series(["a", "b", None], dtype="str")
>>> ser = ser.astype("object")
>>> ser[1] = 2.5
Это astype("object") вызов будет избыточным при использовании pandas 2.x, но
этот код будет работать для всех версий.
Некорректный ввод Unicode#
Python позволяет иметь встроенный str объект, представляющий недопустимые данные Unicode. И поскольку object dtype может содержать любой объект Python, вы можете иметь pandas Series с такими недопустимыми данными в Unicode:
>>> ser = pd.Series(["\u2600", "\ud83d"], dtype=object)
>>> ser
0 ☀
1 \ud83d
dtype: object
Однако при использовании строкового dtype с pyarrow под капотом это может
хранить только корректные данные Unicode, в противном случае будет вызвана ошибка:
>>> ser = pd.Series(["\u2600", "\ud83d"])
---------------------------------------------------------------------------
UnicodeEncodeError Traceback (most recent call last)
...
UnicodeEncodeError: 'utf-8' codec can't encode character '\ud83d' in position 0: surrogates not allowed
Если вы хотите сохранить предыдущее поведение, вы можете явно указать
dtype=object чтобы продолжать работать с типом данных object.
Когда у вас есть байтовые данные, которые вы хотите преобразовать в строки с помощью decode(), decode() метод теперь имеет dtype параметр для возможности указания object dtype вместо стандартного string dtype для этого случая использования.
Series.values() теперь возвращает ExtensionArray#
С типом object, используя .values Для Series вернёт базовый массив NumPy.
>>> ser = pd.Series(["a", "b", np.nan], dtype="object")
>>> type(ser.values)
Однако с новым строковым dtype возвращается базовый ExtensionArray.
>>> ser = pd.Series(["a", "b", pd.NA], dtype="str")
>>> ser.values
['a', 'b', nan]
Length: 3, dtype: str
Если вашему коду требуется массив NumPy, вы должны использовать Series.to_numpy().
>>> ser = pd.Series(["a", "b", pd.NA], dtype="str")
>>> ser.to_numpy()
['a' 'b' nan]
В общем случае всегда следует предпочитать Series.to_numpy() чтобы получить массив NumPy или Series.array() чтобы получить ExtensionArray вместо использования Series.values().
Значительные исправления ошибок#
astype(str) сохранение пропущенных значений#
Преобразование пропущенных значений в строки — давний «баг» или недостаток, как обсуждалось в pandas-dev/pandas#25353, но его исправление вносит значительное изменение в поведение.
В pandas < 3 при использовании astype(str) или astype("str"), операция
преобразует каждый элемент в строку, включая пропущенные значения:
# OLD behavior in pandas < 3
>>> ser = pd.Series([1.5, np.nan])
>>> ser
0 1.5
1 NaN
dtype: float64
>>> ser.astype("str")
0 1.5
1 nan
dtype: object
>>> ser.astype("str").to_numpy()
array(['1.5', 'nan'], dtype=object)
Обратите внимание, как NaN (np.nan) был преобразован в строку "nan". Это было
непреднамеренным поведением и противоречило обработке пропущенных значений другими типами данных.
В pandas 3 это поведение исправлено, и теперь astype("str") будет преобразовывать
в новый строковый тип данных, который сохраняет пропущенные значения:
# NEW behavior in pandas 3
>>> pd.options.future.infer_string = True
>>> ser = pd.Series([1.5, np.nan])
>>> ser.astype("str")
0 1.5
1 NaN
dtype: str
>>> ser.astype("str").to_numpy()
array(['1.5', nan], dtype=object)
Если вы хотите сохранить старое поведение преобразования каждого объекта в
строку, вы можете использовать ser.map(str) вместо этого. Если вы хотите выполнить такое преобразование, сохраняя пропущенные значения способом, работающим как с pandas 2.x, так и с 3.x, вы можете использовать ser.map(str, na_action="ignore") (только для pandas 3.x, вы
можете сделать ser.astype("str")).
Если вы хотите преобразовать в тип данных object или string для pandas 2.x и 3.x соответственно, без необходимости преобразовывать каждый отдельный элемент в строку, вам придется использовать условную проверку версии pandas. Например, для преобразования категориальной Series со строковыми категориями в ее плотную некатегориальную версию с типом данных object или string:
>>> import pandas as pd
>>> ser = pd.Series(["a", np.nan], dtype="category")
>>> ser.astype(object if pd.__version__ < "3" else "str")
prod() вызов исключения для строковых данных#
В pandas < 3, вызов prod() метод на Series с
строковыми данными обычно вызывал ошибку, за исключением случаев, когда Series был пустым или
содержал только одну строку (возможно, с пропущенными значениями):
>>> ser = pd.Series(["a", None], dtype=object)
>>> ser.prod()
'a'
Когда Series содержит несколько строк, это вызовет TypeError. Это
поведение остается таким же в pandas 3 при использовании гибкого object dtype.
Но благодаря использованию нового строкового dtype, это обычно последовательно
вызывает ошибку независимо от количества строк:
>>> ser = pd.Series(["a", None], dtype="str")
>>> ser.prod()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
...
TypeError: Cannot perform reduction 'prod' with string dtype