Skip to content

数据类型

所有数据处理相关的库类型系统都是重中之重。Pandas 最初的类型系统继承自 numpy,实际上 Series 中的值就是使用 ndarray 来存储的。但是由于 Pandas 中处理的数据要比 numpy 庞杂,因此直接继承 numpy 会有很多问题,其中比较麻烦的就是对缺失值的处理。于是引入了 numpy_nullable 类型,就是在 numpy 的基础上为 Boolean、String 和 Integer 引入了 NA 作为缺失值。尽管则稍微改善了一些性能,但是毕竟还是会有些问题。于是在 Panda 2.0 中引入了 pyarrow 类型系统。他真正的解决了 pandas 中类型系统的一系列问题,最大的问题就是兼容性可能不太好。

数据类型和数据结构

这两个概念非常容易混淆:

  • 数据结构: 指数据元素之间的逻辑或物理关系集合,强调数据如何组织和存储。例如,数组、链表等结构通过特定规则管理元素间的关联
  • 数据类型: 包含数据值的集合及在该集合上定义的操作(如整数类型的加减运算),他是数据结构和操作的封装

他两个是相辅相成的,数据结构是数据类型的底层支撑,而数据类型是更高层次的抽象。他们的区别主要在于关注点的不同:

  • 数据结构: 侧重数据元素的关系(如线性、树形、网状结构)及存储实现(顺序存储、链式存储等),因此更接近于底层数据结构更加重要
  • 数据类型: 关注数据的取值范围(如布尔类型只能为 True/False)和合法操作(如整数可进行算术运算),因此在用户层数据类型更加重要

两个概念是有内在联系的,并且不同的数据类型和数据结构的联系也不太一样:

  • 基本数据类型: 整数、浮点数、布尔值、字符等,他的数据结构更多的是解决如何存储实现(非常底层)
  • 复合数据类型: 将数据结构与操作封装,如队列的入队/出队操作需依赖数组或链表的实现

数据结构是数据的组织骨架,而数据类型是骨架+操作的完整封装。理解两者差异有助于选择合适的数据模型(如选择数组还是哈希表),而联系则体现在实际编程中数据类型的实现离不开底层结构的设计。

在 Pandas 中数据结构和数据类型分装的并没有那么紧密,他在不同的时候使用不同的类型系统:

  • 对于标量: 他使用 Python 的类型系统,例如 str、int、float、datetime 等
  • 对于向量: 他具有自己的数据结构就是 Series,而Series 是一大堆数据结构的统称,对应到实际就是 IntegerArray、DatetimeArray、StringArray 等数据结构,而 Series.dtype 决定了他的数据类型。因此 Series 可以看作是数据结构和数据类型的结合
  • 对于 DataFrame: 这个更特殊,他算是一个容器类数据结构,其中的元素是 Series,他同样有自己的类型这取决于 Series 的类型集合

Pandas 类型系统

Pandas 目前支持三种类型系统,他们的对应关系如下:

pyarrow pandas扩展(numpy_nullable) numpy
pa.bool_() BooleanDtype np.bool_
pa.int8() Int8Dtype np.int8
pa.int16() Int16Dtype np.int16
pa.int32() Int32Dtype np.int32
pa.int64() Int64Dtype np.int64
pa.uint8() UInt8Dtype np.uint8
pa.uint16() UInt16Dtype np.uint16
pa.uint32() UInt32Dtype np.uint32
pa.uint64() UInt64Dtype np.uint64
pa.float32() Float32Dtype np.float32
pa.float64() Float64Dtype np.float64
pa.time32() - -
pa.time64() - -
pa.timestamp() DatetimeTZDtype datetime64[ns](不支持时区)
pa.date32() - -
pa.date64() - -
pa.duration() - np.timedelta64
pa.binary() - -
pa.string() StringDtype np.str_
pa.decimal128() - -
pa.list_() - -
pa.map_() - -
pa.dictionary() CategoricalDtype -

可以看到 pyarrow 的表现力更好,而之前都是在 numpy 类型系统上的扩展。

类型转换

类型转换分为两种情况:

  1. 兼容类型的类型转换,这通常并不损失精度,他又分为两种情况,一种是自动转换,一种是手动转换

    1. Series.astype(): 手动进行转换
    2. Series|Datetime.convert_dtypes(dtype_backend="numpy_nullable"|"pyarrow") 根据所选自动将对应的数据类型(DataFrame 自动构造的是 numpy 所以 convert_dtypes 不需要这种类型)
  2. 不兼容类型转换,这种通常是将字符串转换为其他类型,或者是丢失精度的转换,目前主要有两个方法实现

    1. pd.to_numeric: 转换为数值
    2. pd.to_datetime: 转换为时间类型

astype

Series.astype 用于转换兼容类型,这里比较特殊的是 Pyarrow,他需要使用 pd.ArrowDtype(pa.bool_()) 这样的包装方法来包装,当然也可以使用更加简便的字符串表示法 "bool[pyarrow]"。对于 numpy_nullable 则是 pd.BooleanDtype 或者是取消了 Dtype 后缀且全小写的 boolean

to_numeric

pd.to_numeric()将 data 转换为数值类型:

Python
def to_numeric(
    arg, # 要转换的数据,可以是标量、列表、元组、Series、ndarray
    errors:{"ignore", "raise", "coerce"} = "raise",
    # "raise": 无效的解析将引发异常
    # "coerce": 无效的解析将设置为 NaN
    # "ignore": 无效的解析直接返回
    dtype_backend: {"numpy_nullable", "pyarrow"}="numpy_nullable" # 类型系统
):
    pass

它用于将其他值转换为数值,主要针对的是字符串,注意他是能够对混用字符串或者数值的序列执行转换的:

Python
# 字符串和数值混合
s = pd.Series(['1.0', '2', -3])
pd.to_numeric(s)
0    1.0
1    2.0
2   -3.0
dtype: float64

to_datetime

to_datetime()将 data 转换为时间类型:

Python
def to_datetime(
    arg, # 可以是 int float str datetime 标量,也可以是上面四种类型标量组成的列表、元组、ndarray、Series
    errors:{"ignore", "raise", "coerce"} = "raise",
    # "raise": 无效的解析将引发异常
    # "coerce": 无效的解析将设置为 NaN
    # "ignore": 无效的解析直接返回
    utc:bool=False,
    # 如果为 True 该函数始终返回时区感知的 UTC 时间(无),无时区的会当作本地时间转为 UTC 时间
    # 如果为 False,无时区的保持默认
    format:str=None,
    # 解释时间的 strftime
    # 也可以能是 "ISO8601"
    # 也可以是 "mixed" 混合多种形式,注意他只针对于字符串形式的
    unit:str='ns', # 时间戳(整数、浮点数)的精度
    origin='unix', # 整数、浮点数表示的原点,默认是 unix 即以 1970-1-1 为原点,也可以是 datetime 来自定义原点
):
    pass

大多数情况下它会自动检测来执行转换,其中:

  • 对于整数和浮点数: 会自动被认为是时间戳,此时可以使用 unit 来决定时间戳的符号
  • 对于字符串: 会自动认为是时间格式字符串,其中允许出现 ["2020/1/1", "2020-10-11", "20201111"] 这样的不同形式,当然不要太过依赖这类自动解析,有可能出问题,如果想要确保一定不出问题就通过 format 来强制指定需要解析的字符串类型

可以混合字符串和数值类型,但是此时就无法指定 unixformat 这类参数,也就是说他会根据默认的参数来自动检测匹配:

Python
# 时间戳明显是 s 级别的,但是只能使用默认的 ns
pd.to_datetime(pd.Series([1740925298, "2020/1/1", "2020-10-11", "20201111"]))
0   1970-01-01 00:00:01.740925298
1   2020-01-01 00:00:00.000000000
2   2020-10-11 00:00:00.000000000
3   2020-11-11 00:00:00.000000000
dtype: datetime64[ns]

参考