跳到正文
本地阅读 · 字符串、日期与分类数据

字符串、日期与分类数据

计算机基础 · pandas

前置知识:缺失、重复与类型清洗

本章目录

本章学习目标

  • 使用字符串和日期访问器
  • 避免时区和日期边界误差
  • 理解类别编码及未观测类别

文本是数据而非随意字符

字符串列可以包含缺失,应使用 string 类型配合 str 访问器。str.strip 清理两端空白,str.lower 统一大小写,str.contains 按模式筛选,str.extract 提取结构信息。清洗渠道 ' Email ' 为 'email' 可能合理,但把产品型号强制小写或把账户前导零删掉可能改变标识。先明确字段的业务规则,再应用清洗。

字符串方法通常传播缺失;contains 返回的可空布尔结果,作为筛选条件时需要明确 na=False 或单独处理缺失。模式可能是正则表达式,查找字面的点号或括号时使用 regex=False 或正确转义。不要给所有文本列执行 astype(str) 后把缺失变成文字 'nan',否则后续 isna 不再识别真正缺失。

日期、时间和时间范围

时间戳表达某个时刻,业务日期表达某个时区里的日历日。to_datetime 解析以后,dt.year、dt.month、dt.day 等访问器提取组成信息,to_period('M') 可以表达月份分组。只比较月份数字会把不同年份的一月合并,应使用年月或完整日期范围。

筛选一天的数据通常使用左闭右开的时间区间:大于等于当天零点且小于下一天零点。使用当天二十三点五十九分五十九秒作为上界会漏掉更精细的时间。日期排序依赖日期 dtype,非规范字符串的字典序不一定等于时间顺序。先解析,再按明确时间单位计算差值,不能直接把整数时间戳单位猜成秒或毫秒。

小数据:时区会改变日期

UTC 的一月一日十八点,在上海是下一天凌晨两点。先以 utc=True 解析 UTC 时刻,再 tz_convert 到上海,最后提取 date,才能获得当地业务日期一月二日。若先截取原字符串前十位,就会把同一事件归到错误的日子。tz_localize 给无时区时间声明原来所属时区,tz_convert 才把已有时区的时刻换成另一个显示时区;两者不能互换。

夏令时地区有重复或不存在的本地时间,解析策略需要明确。教学 CSV 日期统一不带时区,示例另外给出 UTC 到上海的边界测试。做 SQL 对照时还要核对数据库会话时区与字段类型,不能以为 DATE(timestamp) 在所有环境中都得到同一个日期。

分类类型和统计口径

category 适合重复很多、取值集合有限的列,内部保存类别词典与代码,可以降低内存并限制取值。国家、渠道等列可以采用它,用户编号通常不适合简单转成分类来掩盖唯一值数量。ordered=True 声明类别顺序,使等级比较和排序符合业务含义;普通类别词典顺序不自动意味着数学大小。

groupby 时显式指定 observed=True,只输出实际出现的类别组合;若需要完整类别列表,应明确 observed=False 并解释未出现组合。pandas 版本对默认值可能有变化,教学代码主动声明避免统计结果随升级改变。新类别写入前需要扩充类别词典,否则赋值可能失败。

把转换接入分析

脚本先解析注册日期,规范渠道,再添加年月字段并将国家转成分类,输出示例帮助核对。转换后的表仍要保留 user_id 和原始粒度;任何字符串拆分导致一条记录变多行,都必须重新说明粒度。练习检查清洗后类别分布以及清洗前后的缺失变化。

最常见的反例是时间截取后才换时区、按 month 单独分组混合年份、把未知国家当成固定国家,以及正则点号匹配任何字符。修正时尽量保持原列或保留副本,列出具体失败值和理由。面试解释不是只说调用了 dt 或 str,而是说明输入含义、边界时刻和统计粒度如何保持一致。

版本与复现约定

本课程 pandas 示例以 2.3.3 为验证基线,页面运行记录给出实际版本。pandas 3.x 默认使用 Copy-on-Write:从表中取出的对象发生修改时,不据此假定原表同步变化。跨版本都应使用一次完整的 loc 赋值表达式;需要独立工作表时显式 copy。阅读网上的旧示例时,先核对版本、输入类型和索引,再判断结果能否复现。

完整示例与实际输出

from pathlib import Path
import pandas as pd
import numpy as np
from _shared import load
ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "outputs"
OUT.mkdir(exist_ok=True)
users=load("users").copy()
users["signup_date"]=pd.to_datetime(users["signup_date"],errors="raise")
users["channel"]=users["channel"].astype("string").str.strip().str.lower()
users["country"]=users["country"].astype("category")
users["signup_month"]=users["signup_date"].dt.to_period("M").astype("string")
assert users["signup_date"].notna().all()
assert str(users["country"].dtype)=="category"
print(users[["user_id","signup_month","channel","country"]].head().to_string(index=False))
raw=pd.Series(["  Email ",None,"PAID SEARCH"],dtype="string")
clean=raw.str.strip().str.lower()
assert clean.iloc[0]=="email" and pd.isna(clean.iloc[1])
print("字符串:",clean.tolist())
utc=pd.to_datetime(pd.Series(["2026-01-01T18:00:00Z"]),utc=True)
local=utc.dt.tz_convert("Asia/Shanghai")
assert local.dt.date.astype(str).iloc[0]=="2026-01-02"
print("上海日期:",local.dt.date.astype(str).iloc[0])

实际运行通过CPU Python execution.

user_id signup_month  channel country
       1      2026-01      ads      US
       2      2026-01 referral      SG
       3      2026-01  organic      CN
       4      2026-01      ads      US
       5      2026-01 referral      SG
字符串: ['email', <NA>, 'paid search']
上海日期: 2026-01-02

五道自测:先回答,再核对

01 · astype(str) 对缺失有什么风险?

查看提示

文字与缺失标记不同。

查看参考答案

可能生成 'nan' 等文本,isna 不再当作缺失;优先 string dtype。

02 · 按 month 分组有什么陷阱?

查看提示

跨年份。

查看参考答案

把不同年份同月混在一起,使用年月 period。

03 · tz_localize 与 tz_convert 的差别?

查看提示

是否已经有时区。

查看参考答案

前者给无时区时间声明时区,后者转换已有时区时刻的表示。

04 · 一天的时间范围怎样写?

查看提示

下一天作为上界。

查看参考答案

使用 [当日零点,次日零点),避免漏掉亚秒数据。

05 · observed=True 对类别分组做什么?

查看提示

实际出现的组合。

查看参考答案

仅输出实际观察到的类别组合,避免默认变化和未出现组合。

两项动手任务

任务 1 · 规范用户渠道并按注册年月展示。

提示

保留缺失,解析后使用 to_period。

完整参考解答与检查方式
from pathlib import Path
import pandas as pd
import numpy as np
from _shared import load
ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "outputs"
OUT.mkdir(exist_ok=True)
users=load("users").copy()
users["signup_date"]=pd.to_datetime(users["signup_date"],errors="raise")
users["channel"]=users["channel"].astype("string").str.strip().str.lower()
users["country"]=users["country"].astype("category")
users["signup_month"]=users["signup_date"].dt.to_period("M").astype("string")
assert users["signup_date"].notna().all()
assert str(users["country"].dtype)=="category"
print(users[["user_id","signup_month","channel","country"]].head().to_string(index=False))
raw=pd.Series(["  Email ",None,"PAID SEARCH"],dtype="string")
clean=raw.str.strip().str.lower()
assert clean.iloc[0]=="email" and pd.isna(clean.iloc[1])
print("字符串:",clean.tolist())
utc=pd.to_datetime(pd.Series(["2026-01-01T18:00:00Z"]),utc=True)
local=utc.dt.tz_convert("Asia/Shanghai")
assert local.dt.date.astype(str).iloc[0]=="2026-01-02"
print("上海日期:",local.dt.date.astype(str).iloc[0])

任务 2 · 检查包含字面点号的渠道,缺失不应被选中。

提示

contains 的 regex 和 na 参数。

完整参考解答与检查方式
from pathlib import Path
import pandas as pd
import numpy as np
from _shared import load
ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "outputs"
OUT.mkdir(exist_ok=True)
s=pd.Series(["a.b","acb",None],dtype="string")
selected=s[s.str.contains(".",regex=False,na=False)]
assert selected.tolist()==["a.b"]
print(selected.tolist())

官方资料与掌握状态

完成两项任务后,按独立完成程度记录。状态会尽量保存在当前浏览器,可导出备份。

动手任务记录

掌握状态与复习

关掉资料独立完成后记 A;提示后完成记 B;需要重学记 C。

记录尽量保存在当前浏览器,建议定期导出备份。