pandas区分str和object类型

Mei*_*ham 19 python numpy pandas

Numpy似乎区分strobject类型.比如我可以做::

>>> import pandas as pd
>>> import numpy as np
>>> np.dtype(str)
dtype('S')
>>> np.dtype(object)
dtype('O')
Run Code Online (Sandbox Code Playgroud)

其中D型("S")和D型("O")对应于strobject分别.

但是熊猫似乎就缺少了区分,并要挟strobject.::

>>> df = pd.DataFrame({'a': np.arange(5)})
>>> df.a.dtype
dtype('int64')
>>> df.a.astype(str).dtype
dtype('O')
>>> df.a.astype(object).dtype
dtype('O')
Run Code Online (Sandbox Code Playgroud)

强制类型dtype('S')也无济于事.::

>>> df.a.astype(np.dtype(str)).dtype
dtype('O')
>>> df.a.astype(np.dtype('S')).dtype
dtype('O')
Run Code Online (Sandbox Code Playgroud)

这种行为有什么解释吗?

Joe*_*ton 27

Numpy的字符串dtypes不是python字符串.

因此,pandas故意使用本机python字符串,这需要一个对象dtype.

首先,让我演示一下numpy的字符串不同的含义:

In [1]: import numpy as np
In [2]: x = np.array(['Testing', 'a', 'string'], dtype='|S7')
In [3]: y = np.array(['Testing', 'a', 'string'], dtype=object)
Run Code Online (Sandbox Code Playgroud)

现在,'x'是一个numpy字符串dtype(固定宽度,类似c的字符串),y是一个原生python字符串数组.

如果我们试图超过7个字符,我们会看到立即的差异.字符串dtype版本将被截断:

In [4]: x[1] = 'a really really really long'
In [5]: x
Out[5]:
array(['Testing', 'a reall', 'string'],
      dtype='|S7')
Run Code Online (Sandbox Code Playgroud)

对象dtype版本可以是任意长度:

In [6]: y[1] = 'a really really really long'

In [7]: y
Out[7]: array(['Testing', 'a really really really long', 'string'], dtype=object)
Run Code Online (Sandbox Code Playgroud)

接下来,|Sdtype字符串不能正确保存unicode,尽管还有一个unicode固定长度字符串dtype.我暂时会跳过一个例子.

最后,numpy的字符串实际上是可变的,而Python字符串则不是.例如:

In [8]: z = x.view(np.uint8)
In [9]: z += 1
In [10]: x
Out[10]:
array(['Uftujoh', 'b!sfbmm', 'tusjoh\x01'],
      dtype='|S7')
Run Code Online (Sandbox Code Playgroud)

出于所有这些原因,pandas选择不允许类似C的固定长度字符串作为数据类型.正如您所注意到的那样,尝试将python字符串强制转换为固定的numpy字符串将无法正常工作pandas.相反,它总是使用本机python字符串,它对大多数用户来说更直观.

  • 实际上,pandas确实允许类似numpy的固定长度字节串,尽管它们很少使用,例如`pd.Series(['a','b','c'],dtype ='S1')` (7认同)

cot*_*ail 8

如果您来到这里想了解pandas'string'和dtypes之间的差异,请阅读此处。object从 pandas 1.5.3 开始,这两种 dtypes 之间有两个主要区别。

1. 空值处理

objectdtype 不仅可以存储字符串,还可以存储混合数据类型,因此如果要将值转换为字符串,astype(str)就是规定的方法。然而,这会将所有值转换为字符串,甚至 NaN 也变成文字'nan'字符串。string是可以为 null 的数据类型,因此转换为'string'会将 NaN 保留为 null 值。

x = pd.Series(['a', float('nan'), 1], dtype=object)
x.astype(str).tolist()          # ['a', 'nan', '1']
x.astype('string').tolist()     # ['a', <NA>, '1']
Run Code Online (Sandbox Code Playgroud)

这样做的结果是,对objectdtype 列执行的字符串操作(例如计数字符、比较)会返回numpy.int等,而对dtypenumpy.bool执行的相同操作会返回 nullable或dtypes。特别是,对于对dtype执行的比较,NaN 比较返回 False(因为 NaN 不等于任何值) ,而对于对 dtype 执行的比较,则返回False。'string'pd.Int64pd.Booleanobjectpd.NApd.NA'string'

x = pd.Series(['a', float('nan'), 'b'], dtype=object)
x == 'a'

0     True
1    False
2    False
dtype: bool
    
    
y = pd.Series(['a', float('nan'), 'b'], dtype='string')
y == 'a'

0     True
1     <NA>
2    False
dtype: boolean
Run Code Online (Sandbox Code Playgroud)

因此,对于'string'dtype,null 处理更加灵活,因为您可以根据fillna()需要调用 etc. 来处理 null 值。1

2. stringdtype更清晰

如果 pandas 列是objectdtype,则其中的值可以替换为任何值。例如,其中的字符串可以替换为整数,这样就可以了(例如x下面)。如果您希望其中的每个值都是字符串,那么之后可能会产生不良后果。stringdtype 不存在这个问题,因为一个字符串只能被另一个字符串替换(例如y下面)。

x = pd.Series(['a', 'b'], dtype=str)
y = pd.Series(['a', 'b'], dtype='string')
x[1] = 3                        # OK
y[1] = 3                        # ValueError
y[1] = '3'                      # OK
Run Code Online (Sandbox Code Playgroud)

这样做的优点是您可以select_dtypes()仅选择字符串列。换句话说,对于objectdtypes,无法识别字符串列,但对于'string'dtypes,可以。

df = pd.DataFrame({'A': ['a', 'b', 'c'], 'B': [[1], [2,3], [4,5]]}).astype({'A': 'string'})
df.select_dtypes('string')      # only selects the string column


    A
0   a
1   b
2   c



df = pd.DataFrame({'A': ['a', 'b', 'c'], 'B': [[1], [2,3], [4,5]]})
df.select_dtypes('object')      # selects the mixed dtype column as well


    A   B
0   a   [1]
1   b   [2, 3]
2   c   [4, 5]
Run Code Online (Sandbox Code Playgroud)

3. 内存效率

字符串 Dtype'string'有存储选项(python 和 pyarrow),如果字符串很短,pyarrow 非常高效。看下面的例子:

lst = np.random.default_rng().integers(1000000, size=1000).astype(str).tolist()

x = pd.Series(lst, dtype=object)
y = pd.Series(lst, dtype='string[pyarrow]')
x.memory_usage(deep=True)       # 63041
y.memory_usage(deep=True)       # 10041
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,如果字符串很短(上例中最多 6 个字符),pyarrow 消耗的内存会减少 6 倍以上。但是,如以下示例所示,如果字符串很长,则几乎没有任何区别。

z = x * 1000
w = (y.astype(str) * 1000).astype('string[pyarrow]')
z.memory_usage(deep=True)       # 5970128
w.memory_usage(deep=True)       # 5917128
Run Code Online (Sandbox Code Playgroud)

1例如str.contains,类似的直觉已经存在于。str.match

x = pd.Series(['a', float('nan'), 'b'], dtype=object)
x.str.match('a', na=np.nan)

0     True
1      NaN
2    False
dtype: object
Run Code Online (Sandbox Code Playgroud)