新字符串数据类型(pandas 3.0)迁移指南#
即将发布的 pandas 3.0 版本引入了一个新的默认字符串数据类型。这在升级到 pandas 3.0 时很可能会带来一些工作,本页面概述了您可能遇到的问题,并提供了如何解决它们的指导。
此新 dtype 已在 pandas 2.3 版本中提供,您可以通过以下方式启用它:
pd.options.future.infer_string = True
这使您可以在最终的 3.0 版本发布之前测试您的代码。
背景#
历史上,pandas 一直使用 NumPy 的 object dtype 作为存储文本数据的默认方式。这有两个主要缺点。首先,object dtype 不仅限于字符串:任何 Python 对象都可以存储在 object-dtype 数组中,而不仅仅是字符串,并且对于用户来说,将带有字符串的列的 dtype 显示为 object 会造成混淆。其次,这并不总是非常高效(无论是在性能还是内存使用方面)。
自 pandas 1.0 起,已提供可选的字符串数据类型,但尚未将其设为默认值,并且使用 pd.NA 标量来表示缺失值。
Pandas 3.0 将字符串的默认 dtype 更改为新的字符串数据类型,这是现有可选字符串数据类型的一个变体,但使用 NaN 作为缺失值指示符,以与其他默认数据类型保持一致。
为了提高性能,新的字符串数据类型默认将使用 pyarrow 包(如果已安装;否则,它将使用 object dtype 作为后备)。
有关更多背景和详细信息,请参阅 PDEP-14: Dedicated string data type for pandas 3.0 。
新默认字符串 dtype 简介#
默认情况下,pandas 将推断此新的字符串 dtype 而不是 object dtype 来处理字符串数据(在创建 pandas 对象时,例如在构造函数或 IO 函数中)。
作为默认 dtype,这意味着在推断 dtype 且输入被推断为字符串数据时,字符串 dtype 将用于 IO 方法或构造函数中:
>>> 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() 等函数在读取字符串数据时将使用新的字符串 dtype。
与当前的 object dtype 不同,新的字符串 dtype 将仅存储字符串。这也意味着如果您尝试向其中存储非字符串值,它将引发错误(更多详细信息请参见下文)。
新字符串 dtype 的缺失值始终表示为 NaN``(``np.nan),并且缺失值行为与其他默认 dtype 类似。
否则,此新的字符串 dtype 应与用户已习惯的现有 object dtype 行为相同。例如,通过 str 访问器的所有特定于字符串的方法都将正常工作:
>>> ser = pd.Series(["a", "b", None], dtype="str")
>>> ser.str.upper()
0 A
1 B
2 NaN
dtype: str
备注
新的默认字符串 dtype 是 pandas.StringDtype 类的实例。该 dtype 可以通过 pd.StringDtype(na_value=np.nan) 来构造,但对于一般用法,我们建议使用更短的 "str" 别名。
行为差异概述及如何解决#
dtype 不再是 numpy 的 “object” dtype#
检查 dtype
在检查 dtype 时,代码可能当前这样做:
通过检查 dtype 是否为 "object" 来查找具有字符串数据的列。在 pandas 3+ 中,这不再有效,因为使用新的默认字符串 dtype 时,ser.dtype 将为 "str",而上述检查将返回 False。#
要检查具有字符串数据的列,您应该改为使用:
>>> ser = pd.Series(["a", "b", "c"])
>>> ser.dtype == "object"
如何编写兼容的代码
对于可以在 pandas 2.x 和 3.x 上运行的代码,您可以使用 pandas.api.types.is_string_dtype() 函数:
>>> ser.dtype == "str"
这将返回 object dtype 和 string dtypes 的 True。
For code that should work on both pandas 2.x and 3.x, you can use the
pandas.api.types.is_string_dtype() function:
>>> pd.api.types.is_string_dtype(ser.dtype)
True
如果您有像这样的代码,其中 dtype 在构造函数中被硬编码:
这将继续使用 object dtype。您需要更新此代码以确保获得新字符串 dtype 的优势。#
如何编写兼容的代码?
>>> pd.Series(["a", "b", "c"], dtype="object")
首先,在许多情况下,删除特定数据类型并让 pandas 进行推断就足够了。但如果您想具体指定,可以指定 "str" dtype:
实际上,这与 pandas 2.x 兼容,因为在 pandas < 3 中,dtype="str" essentially 被视为 object dtype 的别名。
虽然在构造函数中使用 dtype="str" 与 pandas 2.x 兼容,但将其指定为 astype() 的 dtype 会在 pandas 2.x 中引起对缺失值进行字符串化的同一个问题。有关更多详细信息,请参阅 astype(str) 保留缺失值 部分。
>>> pd.Series(["a", "b", "c"], dtype="str")
为了以兼容 pandas 2.x 和 3.x 的方式使用 select_dtypes() 选择字符串列,无法使用 "str"。虽然这适用于 pandas 3.x,但在 pandas 2.x 中会导致错误。作为替代方案,可以选择 object``(用于 pandas 2.x)和 ``"string"``(用于 pandas 3.x;这将也选择默认的 ``str dtype 并且不会在 pandas 2.x 中引发错误):
注意
While using dtype="str" in constructors is compatible with pandas 2.x,
specifying it as the dtype in astype() runs into the issue
of also stringifying missing values in pandas 2.x. See the section
astype(str) 保留缺失值 for more details.
For selecting string columns with select_dtypes() in a pandas
2.x and 3.x compatible way, it is not possible to use "str". While this
works for pandas 3.x, it raises an error in pandas 2.x.
As an alternative, you can select both object (for pandas 2.x) and
"string" (for pandas 3.x; which will also select the default str dtype
and does not error on pandas 2.x):
# can use ``include=["str"]`` for pandas >= 3
>>> df.select_dtypes(include=["object", "string"])
通常,在依赖 pandas 方法中的缺失值行为时(例如,ser.isna() 的结果与以前相同),这不会有问题。但是,如果您依赖于 None 的确切值存在,这可能会影响您的代码。#
在检查缺失值时,而不是检查 None 或 np.nan 的确切值,您应该使用 pandas.isna() 函数。这是检查缺失值的最稳健的方法,因为它无论 dtype 和确切的缺失值标记如何都能正常工作:
# 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
有一个注意事项:此函数同时处理标量和类数组对象,在后一种情况下,它将返回一个布尔数组。在布尔上下文中(例如,if pd.isna(..): ..)使用它时,请确保只向它传递一个标量。
实际上,这与 pandas 2.x 兼容,因为在 pandas < 3 中,dtype="str" essentially 被视为 object dtype 的别名。
When checking for a missing value, instead of checking for the exact value of
None or np.nan, you should use the pandas.isna() function. This is
the most robust way to check for missing values, as it will work regardless of
the dtype and the exact missing value sentinel:
>>> pd.isna(ser[2])
True
使用新的字符串 dtype,任何尝试在 Series 或 DataFrame 中设置非字符串值的操作都将引发错误:
如果您依赖于 object dtype 能够容纳任何 Python 对象的灵活特性,但您的初始数据被推断为字符串,那么您的代码可能会受到此更改的影响。#
您可以更新代码以确保仅在此类列中设置字符串值,或者可以通过在构造函数中显式指定 dtype,或使用 astype() 方法来显式确保列具有 object dtype。
>>> 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.
当使用 pandas 2.x 时,这个 astype("object") 调用是多余的,但这段代码将适用于所有版本。
实际上,这与 pandas 2.x 兼容,因为在 pandas < 3 中,dtype="str" essentially 被视为 object dtype 的别名。
You can update your code to ensure you only set string values in such columns,
or otherwise you can explicitly ensure the column has object dtype first. This
can be done by specifying the dtype explicitly in the constructor, or by using
the astype() method:
>>> ser = pd.Series(["a", "b", None], dtype="str")
>>> ser = ser.astype("object")
>>> ser[1] = 2.5
Python 允许有一个表示无效 unicode 数据的内置 str 对象。由于 object dtype 可以容纳任何 Python 对象,因此您可以拥有一个包含此类无效 unicode 数据的 pandas Series:
但是,当使用底层使用 pyarrow 的字符串 dtype 时,它只能存储有效的 unicode 数据,否则会引发错误:#
如果您想保留之前的行为,您可以显式指定 dtype=object 以继续使用 object dtype。
>>> ser = pd.Series(["\u2600", "\ud83d"], dtype=object)
>>> ser
0 ☀
1 \ud83d
dtype: object
However, when using the string dtype using pyarrow under the hood, this can
only store valid unicode data, and otherwise it will raise an error:
>>> 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
If you want to keep the previous behaviour, you can explicitly specify
dtype=object to keep working with object dtype.
当您需要使用 decode() 将字节数据转换为字符串时,decode() 方法现在提供了一个 dtype 参数,以便在这种情况下能够指定 object 类型而不是默认的 string 类型。
Series.values() 现在返回一个 ExtensionArray#
对于 object 类型,对 Series 使用 .values 将返回底层的 NumPy 数组。
>>> ser = pd.Series(["a", "b", np.nan], dtype="object")
>>> type(ser.values)
<class 'numpy.ndarray'>
但是,对于新的 string 类型,则会返回底层的 ExtensionArray。
>>> ser = pd.Series(["a", "b", pd.NA], dtype="str")
>>> ser.values
<ArrowStringArray>
['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() 。
值得注意的 bug 修复#
astype(str) 保留缺失值#
根据 pandas-dev/pandas#25353 的讨论,缺失值的字符串化是一个长期存在的“bug”或不当行为,但修复它会带来重大的行为改变。
在 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") 将强制转换为新的 string 类型,该类型会保留缺失值:
# 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"))。
如果您希望将 pandas 2.x 和 3.x 转换为 object 或 string 类型,而无需单独字符串化每个元素,您将不得不使用 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 中,在具有字符串数据的 Series 上调用 prod() 方法通常会引发错误,除非 Series 为空或仅包含单个字符串(可能包含缺失值):
>>> ser = pd.Series(["a", None], dtype=object)
>>> ser.prod()
'a'
当 Series 包含多个字符串时,它会引发 TypeError。在使用灵活的 object 类型时,此行为在 pandas 3 中保持不变。但由于使用了新的 string 类型,这通常会一致地引发错误,而与字符串的数量无关:
>>> ser = pd.Series(["a", None], dtype="str")
>>> ser.prod()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
...
TypeError: Cannot perform reduction 'prod' with string dtype