首页 游戏 软件 资讯 排行榜 专题
首页
业界动态
数据类型详解 Categorical Date Time 的区别与应用

数据类型详解 Categorical Date Time 的区别与应用

热心网友
70
转载
2026-05-18

在数据处理领域,类型系统常被视为一个“能用即可”的次要角色。许多开发者认为,只要程序能输出结果,选择何种数据类型似乎无关紧要。然而,事实真的如此吗?恰恰相反,正确理解并高效运用类型系统,往往是区分普通脚本与高性能、高健壮性应用程序的核心所在。它能带来的优势是立竿见影的:内存占用可能降低数十倍,计算性能提升数倍,代码逻辑也因此变得更加清晰可维护,潜在的缺陷自然大幅减少。

本文将深入探讨Polars这一高性能数据处理库的类型系统,揭示它如何成为您数据分析工作中的强大武器。

1. Polars支持哪些数据类型?

Polars提供了一套丰富且精细的数据类型体系,足以应对各类复杂的数据分析场景。您可以通过一个简单的命令来查看其支持的所有数据类型:

import polars as pl
# 查看所有数据类型
print(pl.datatypes)

从基础的整数、浮点数,到字符串、日期时间,再到更高级的分类、列表和结构体类型,Polars一应俱全。深入理解这些类型是高效使用该库的第一步。

2. 创建不同类型的列

在创建DataFrame时,指定列的数据类型非常简单直观。

整数和浮点数:Polars区分了不同精度的数值类型,例如Int8、Int16、Int32、Int64以及Float32、Float64。在处理海量数据时,选择合适的精度对内存优化至关重要。

df = pl.DataFrame({
    "int32_col": [1, 2, 3],
    "int64_col": [1, 2, 3],
    "float32_col": [1.5, 2.5, 3.5],
    "float64_col": [1.5, 2.5, 3.5],
})
# 查看数据类型
print(df.dtypes)
# [Int32, Int64, Float32, Float64]

字符串:字符串默认是Utf8类型。对于包含大量重复值的列,可以考虑使用Categorical(分类)类型,这能显著提升性能,下文将详细说明。

df = pl.DataFrame({
    "name": ["张三", "李四", "王五"],  # 默认 Utf8
    "code": pl.Series("code", ["A", "B", "C"], dtype=pl.Categorical)  # 分类
})

日期与时间:Polars对Python原生的日期时间类型提供了良好的支持,创建时通常能自动识别。

from datetime import date, datetime, time
df = pl.DataFrame({
    "pu re_date": [date(2024, 1, 1), date(2024, 1, 2)],
    "pu re_time": [time(10, 30), time(14, 45)],
    "pu re_datetime": [datetime(2024, 1, 1, 10, 30), datetime(2024, 1, 2, 14, 45)]
})
print(df)

输出结果清晰地展示了不同的日期时间类型:

┌───────────┬──────────┬─────────────────────┐
│ pu re_date ┆ pu re_time ┆ pu re_datetime       │
│ date      ┆ time      ┆ datetime[μs]       │
╞═══════════╪═══════════╪═════════════════════╡
│ 2024-01-01┆ 10:30:00  ┆ 2024-01-01 10:30:00│
│ 2024-01-02┆ 14:45:00  ┆ 2024-01-02 14:45:00│
└───────────┴───────────┴─────────────────────┘

3. 类型转换:cast方法

在数据清洗过程中,类型转换是常规操作。Polars使用cast()方法来完成这一任务。

df = pl.DataFrame({
    "num_str": ["1", "2", "3"],
    "price": [10.5, 20.3, 30.1]
})
# 字符串转整数
result = df.with_columns(
    pl.col("num_str").cast(pl.Int32).alias("num_int")
)
print(result)

转换后,新列的数据类型就变成了Int32:

┌─────────┬──────┬──────────┐
│ num_str ┆ price ┆ num_int  │
│ str     ┆ f64   ┆ i32      │
╞═════════╪══════╪══════════╡
│ 1       ┆ 10.5  ┆ 1        │
│ 2       ┆ 20.3  ┆ 2        │
│ 3       ┆ 30.1  ┆ 3        │
└─────────┴───────┴──────────┘

以下是一些常见的数据类型转换场景:

# 字符串 → 整数
pl.col("num_str").cast(pl.Int32)
# 整数 → 浮点数
pl.col("num_int").cast(pl.Float64)
# 浮点数 → 整数(直接截断)
pl.col("price").cast(pl.Int32)  # 10.5 → 10
# 四舍五入后转整数
pl.col("price").round(0).cast(pl.Int32)  # 10.5 → 11
# 日期 → 字符串
pl.col("date").cast(pl.Utf8)
# 字符串 → 日期
pl.col("date_str").str.to_date()
# 字符串 → 日期时间
pl.col("datetime_str").str.to_datetime()

4. Categorical:节省内存的利器

什么是Categorical类型?

简而言之,当某一列存在大量重复的字符串值时,使用Categorical(分类)类型可以带来惊人的内存节省和性能提升。其内部机制是使用整数编码来代表不同的类别,而非存储完整的原始字符串。

来看一个直观的对比示例:

# 生成100万行数据,城市只有‘北京’、‘上海’、‘深圳’3个类别
cities = ["北京", "上海", "深圳"] * 1000000

# 使用Utf8类型存储
df_utf8 = pl.DataFrame({"城市": cities})
print(f"Utf8内存占用: {df_utf8.get_column('城市').estimated_size() / 1024 / 1024:.2f} MB")

# 使用Categorical类型存储
df_cat = pl.DataFrame({"城市": pl.Series("城市", cities, dtype=pl.Categorical)})
print(f"Cat内存占用: {df_cat.get_column('城市').estimated_size() / 1024 / 1024:.2f} MB")

输出结果的对比非常悬殊:

Utf8内存占用: 57.00 MB
Cat内存占用: 0.50 MB

内存节省超过了99%!这对于处理大规模数据集具有重大意义。

创建Categorical列

主要有两种方式:

# 方法1:创建Series时直接指定
df = pl.DataFrame({
    "城市": pl.Series(["北京", "上海", "深圳"], dtype=pl.Categorical)
})

# 方法2:对现有列进行转换
df = pl.DataFrame({"城市": ["北京", "上海", "深圳"]})
df = df.with_columns(
    pl.col("城市").cast(pl.Categorical)
)

何时使用Categorical?

记住一个核心原则:低基数,高重复。典型场景包括性别、省份、产品类别、状态码等枚举值。对于像“用户ID”、“订单号”这种几乎每个值都唯一的高基数列,使用Categorical反而会增加额外的映射开销,得不偿失。

5. 日期时间处理:dt模块

Polars为日期时间列提供了强大的.dt访问器,让时间序列数据处理得心应手。

字符串转日期

df = pl.DataFrame({
    "date_str": ["2024-01-15", "2024-02-20", "2024-03-25"]
})
result = df.with_columns(
    pl.col("date_str").str.to_date().alias("date")
)
print(result)

提取年月日时分秒等组件

从日期时间中提取特定信息是常见需求:

df = pl.DataFrame({
    "dt": [datetime(2024, 1, 15, 10, 30, 45)]
})
result = df.with_columns(
    pl.col("dt").dt.year().alias("年"),
    pl.col("dt").dt.month().alias("月"),
    pl.col("dt").dt.day().alias("日"),
    pl.col("dt").dt.hour().alias("时"),
    pl.col("dt").dt.minute().alias("分"),
    pl.col("dt").dt.second().alias("秒"),
    pl.col("dt").dt.weekday().alias("星期几"),  # 1=周一, 7=周日
    pl.col("dt").dt.day_of_year().alias("一年第几天"),
)
print(result)

日期时间格式化

将日期时间转换为特定格式的字符串:

# 日期 → 字符串
result = df.with_columns(
    pl.col("dt").dt.strftime("%Y年%m月%d日").alias("中文格式"),
    pl.col("dt").dt.strftime("%Y-%m-%d").alias("ISO格式"),
    pl.col("dt").dt.strftime("%H:%M:%S").alias("时间格式"),
)
print(result)

日期计算与操作

进行日期的加减、截断等操作:

from datetime import timedelta
df = pl.DataFrame({
    "date": [date(2024, 1, 1), date(2024, 1, 15), date(2024, 2, 1)]
})
result = df.with_columns(
    # 加7天
    (pl.col("date") + timedelta(days=7)).alias("加7天"),
    # 减3天
    (pl.col("date") - timedelta(days=3)).alias("减3天"),
    # 截断到月初
    pl.col("date").dt.truncate("1mo").alias("月初"),
    # 获取月末日期
    pl.col("date").dt.month_end().alias("月末"),
    # 获取所属季度
    pl.col("date").dt.quarter().alias("季度"),
)
print(result)

6. 字符串处理:str模块

字符串操作通过.str访问器进行,功能全面且强大。

常用字符串操作

df = pl.DataFrame({
    "name": ["Zhang San", "LI SI", "Wang Wu"],
    "email": ["zhang@qq.com", "li@163.com", "wang@gmail.com"]
})
result = df.with_columns(
    # 转换为大写
    pl.col("name").str.to_uppercase().alias("大写"),
    # 转换为小写
    pl.col("name").str.to_lowercase().alias("小写"),
    # 首字母大写(标题格式)
    pl.col("name").str.to_titlecase().alias("首大写"),
    # 计算字符串长度
    pl.col("name").str.lengths().alias("长度"),
    # 提取@符号前的邮箱前缀
    pl.col("email").str.strip_prefix("@").alias("邮箱前缀"),
)
print(result)

字符串包含与替换

result = df.with_columns(
    # 判断是否包含特定子串
    pl.col("email").str.contains("qq").alias("是QQ邮箱"),
    # 判断是否以某字符串开头
    pl.col("email").str.starts_with("zhang").alias("是zhang开头"),
    # 替换子串
    pl.col("email").str.replace("gmail", "outlook").alias("替换后"),
    # 移除字符串前后的空白字符
    pl.col("name").str.strip().alias("去空格"),
)
print(result)

使用正则表达式提取子串

df = pl.DataFrame({
    “text”: [“订单号: A12345”, “订单号: B67890”, “订单号: C11111”]
})
result = df.with_columns(
    # 使用正则表达式提取数字部分
    pl.col(“text”).str.extract(r”(\d+)”, 0).alias(“订单号”),
)
print(result)

7. Null值处理

现实世界的数据中,Null(空值)无处不在。Polars提供了灵活多样的处理方式。

检测Null值

df = pl.DataFrame({
    "name": ["张三", None, "王五"],
    "age": [25, 30, None],
    "salary": [8000, None, 12000]
})
# 检查是否为Null
result = df.select(
    pl.col("name").is_null().alias("name是Null"),
    pl.col("age").is_not_null().alias("age非Null"),
)
print(result)

填充Null值

# 用固定值填充
result = df.with_columns(
    pl.col("age").fill_null(0).alias("age填0"),
    pl.col("salary").fill_null(pl.col("salary").mean()).alias("salary填均值"),
)
# 用前向或后向值填充
result = df.with_columns(
    pl.col("name").fill_null(strategy="forward").alias("用前值填充"),
)

删除包含Null值的行

# 删除任何列包含Null的行
result = df.drop_nulls()
# 仅删除指定列包含Null的行
result = df.drop_nulls(subset=["salary"])

8. List和Struct类型

List类型:存储值序列

List类型允许在一列中存储数组或列表,非常适合存储如多次考试成绩、用户浏览历史、标签列表等序列化数据。

df = pl.DataFrame({
    "name": ["张三", "李四"],
    "scores": [[90, 85, 92], [78, 88, 95]]
})
result = df.with_columns(
    # 计算List长度
    pl.col("scores").list.lengths().alias("考试次数"),
    # 求List中的最大值
    pl.col("scores").list.max().alias("最高分"),
    # 求List中的平均值
    pl.col("scores").list.mean().alias("平均分"),
)
print(result)

Struct类型:组合字段

Struct类型可以将多个相关的字段组合成一个列,类似于字典、JSON对象或命名元组,便于管理复杂嵌套数据。

df = pl.DataFrame({
    "name": ["张三", "李四"],
    "info": [
        {"age": 25, "city": "北京"},
        {"age": 30, "city": "上海"}
    ]
})
result = df.with_columns(
    pl.col("info").struct.field("age").alias("年龄"),
    pl.col("info").struct.field("city").alias("城市"),
)
print(result)

9. 实战:完整数据清洗流程

将上述知识点串联起来,完成一个完整的数据清洗流程示例:

raw_df = pl.DataFrame({
    "order_id": ["A-001", "B-002", "C-003", None],
    "customer": ["Zhang", "LI", "Wang", "Zhao"],
    "amount": ["100", "200", "abc", "400"],
    "date": ["2024-01-01", "2024-01-02", "2024-01-03", "2024-01-04"],
    "category": ["电子产品", "电子产品", "服装", "服装"]
})

# 完整清洗流程
clean_df = (
    raw_df
    # 1. 删除order_id为Null的行
    .drop_nulls()
    # 2. 类型转换与清洗
    .with_columns(
        # 尝试转换金额,无效值会变为Null
        pl.col("amount").cast(pl.Int32, strict=False).alias("金额"),
        pl.col("date").str.to_date().alias("日期"),
    )
    # 3. 删除转换后金额为Null的行(如‘abc’)
    .drop_nulls(subset=["金额"])
    # 4. 分类列使用Categorical节省内存
    .with_columns(
        pl.col("category").cast(pl.Categorical)
    )
    # 5. 新增计算列(例如计算含税金额)
    .with_columns(
        (pl.col("金额") * 1.1).alias("含税金额"),
    )
    # 6. 选择并重排最终需要的列
    .select(["order_id", "customer", "金额", "含税金额", "日期", "category"])
)
print(clean_df)
print(clean_df.dtypes)

10. 避坑指南

在实际使用中,有几个常见的“坑”需要注意避开。

坑1:字符串转日期时格式不匹配

# ❌ 错误:默认格式可能不匹配,导致解析失败或错误
pl.col("date_str").str.to_date()  # 未指定格式
# ✅ 正确:明确指定日期字符串的格式
pl.col("date_str").str.to_date("%Y/%m/%d")

坑2:误将高基数列转换为Categorical

# ❌ 错误:像‘姓名’这种几乎每个值都不同的高基数列,转换会降低性能
pl.col("姓名").cast(pl.Categorical)  # 内存开销反而更大
# ✅ 正确:仅对低基数、高重复的列使用Categorical
pl.col("城市").cast(pl.Categorical)

坑3:浮点数直接转换为整数导致精度丢失

# ❌ 错误:price=10.9会直接截断为10,丢失小数部分
pl.col("price").cast(pl.Int32)
# ✅ 正确:应先进行四舍五入,再转换
pl.col("price").round(0).cast(pl.Int32)

11. 总结

本文系统性地梳理了Polars库强大的类型系统。从基础数据类型的创建与转换,到能极大优化内存的Categorical类型,再到功能强大的日期时间(.dt)和字符串(.str)处理模块,最后涵盖了Null值处理以及List、Struct等复杂类型的应用。熟练掌握这些知识,意味着您不仅能编写出可运行的代码,更能构建出高效、清晰、内存友好的高质量数据处理程序。数据处理的效率与优雅,往往就体现在对这些数据类型的精妙运用之中。

来源:https://www.51cto.com/article/843417.html
免责声明: 游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

最新APP

宝宝过生日
宝宝过生日
应用辅助 04-07
台球世界
台球世界
体育竞技 04-07
解绳子
解绳子
休闲益智 04-07
骑兵冲突
骑兵冲突
棋牌策略 04-07
三国真龙传
三国真龙传
角色扮演 04-07

热门推荐

斯柯达晶锐Fabia Motorsport特别版车型正式发布
业界动态
斯柯达晶锐Fabia Motorsport特别版车型正式发布

为庆祝品牌投身赛车运动整整125年,斯柯达正式推出了晶锐Fabia Motorsport Edition特别版。这款车基于Fabia 130打造,设计灵感直接来源于征战赛场的Fabia RS Rally2拉力赛车,整体风格充满了对赛事历史的致敬意味。不过,得先说明白,它的升级重点主要落在了外观和底盘

热心网友
05.18
灰度以太坊质押ETF持仓超10万枚ETH 价值2.37亿美元
web3.0
灰度以太坊质押ETF持仓超10万枚ETH 价值2.37亿美元

Grayscale 通过其以太坊质押 ETF 质押了 102,400 个 ETH,价值 2 37 亿美元 先来看一组数据:资产管理巨头 Grayscale 最近通过其以太坊质押 ETF,一口气质押了超过10万个 ETH,价值约2 37亿美元。这个动作本身不小,但更有意思的是市场的后续反应——或者说,

热心网友
05.18
劳斯莱斯库里南防弹版发布 Inkas打造隐形防护座驾
业界动态
劳斯莱斯库里南防弹版发布 Inkas打造隐形防护座驾

劳斯莱斯库里南自问世以来,始终是超豪华全尺寸SUV领域的标杆。对于追求极致安全又不愿牺牲低调气质的高净值人士而言,如何实现“隐形”的顶级防护,一直是核心诉求。如今,加拿大专业防弹车制造商Inkas,以一款近乎“零痕迹”改装的库里南,给出了完美解决方案——一座移动的“隐形堡垒”。 区别于常见的外露装甲

热心网友
05.18
GTA5与荒野大镖客2高清复刻版或将登陆Switch平台
游戏资讯
GTA5与荒野大镖客2高清复刻版或将登陆Switch平台

新加坡维塔士工作室正考虑将《侠盗猎车手V》与《荒野大镖客:救赎2》移植至任天堂Switch平台。该团队拥有丰富的移植经验,曾成功负责多款游戏的跨平台适配。这两款作品全球销量巨大,若能登陆Switch,其便携特性可能成为新的市场增长点。

热心网友
05.18
大众ID. Polo GTI全球首发亮相 高尔夫GTI刷新纽北赛道纪录
业界动态
大众ID. Polo GTI全球首发亮相 高尔夫GTI刷新纽北赛道纪录

当高尔夫GTI迎来五十周年里程碑,传奇的纽博格林北环赛道成为其致敬历史与展望未来的最佳舞台。这里不仅铭刻了燃油性能图腾的巅峰时刻,也正式开启了电动GTI的新纪元。近日,大众汽车正式宣布,高尔夫GTI 50周年版在纽北创下全新纪录,荣膺最快前驱量产车称号;与此同时,品牌首款纯电动GTI车型——ID

热心网友
05.18