RSS
菜单
全部文章快讯开发科技深度热点

第 9 章 文本数据处理

内容摘要

现实数据里字符串无处不在:地址、姓名、商品描述、日志……本篇文章系统讲解 pandas 的文本处理武器库:**str 访问器、大小写与去空格、拆分与拼接、查找替换、正则表达式、提取结构化信息**。

现实数据里字符串无处不在:地址、姓名、商品描述、日志……本篇文章系统讲解 pandas 的文本处理武器库:str 访问器、大小写与去空格、拆分与拼接、查找替换、正则表达式、提取结构化信息。

目录

1. str 访问器是什么

2. 大小写与空白处理

3. 包含与匹配判断

4. 拆分与拼接

5. 查找、替换与删除

6. 正则表达式提取

7. 类型转换与长度统计

8. 处理 object 与 string 类型的差异

9. 文本清洗实战案例

10. 常见坑与注意事项

11. 本章小结与练习


1. str 访问器是什么

pandas 通过 `.str` 访问器把 Python 字符串方法"向量化":对 Series 的每个元素应用同一个字符串操作。

import pandas as pd

s = pd.Series(["  Hello  ", "WORLD", "pandas", "Data Science"])
print(s.str.lower())
# 0        hello
# 1        world
# 2       pandas
# 3    data science
注意:.str 只对字符串类型的 Series有效(object 或 string 类型)。对数值 Series 调用会报错。
# 报错示例
# pd.Series([1, 2, 3]).str.lower()
# AttributeError: Can only use .str accessor with string values!

2. 大小写与空白处理

s = pd.Series(["  Apple  ", "banana", "CHERRY", None])

print(s.str.lower())       # 全部小写
print(s.str.upper())       # 全部大写
print(s.str.title())       # 每个单词首字母大写
print(s.str.capitalize())  # 首字母大写其余小写
print(s.str.strip())       # 去除两端空白(strip/lstrip/rstrip)
print(s.str.swapcase())    # 大小写互换
print(s.str.casefold())    # 更强的小写(处理特殊字符)
strip() 系列是清洗数据的头号工具:" 北京 " → "北京"。
# 缺失值在 str 方法中保持 NaN,不报错
print(s.str.strip())
# 0    Apple
# 1    banana
# 2    CHERRY
# 3      NaN

3. 包含与匹配判断

返回布尔 Series,可用于筛选。

s = pd.Series(["apple pie", "banana split", "cherry cake", "apple juice"])

# 是否包含子串
print(s.str.contains("apple"))
# 0     True
# 1    False
# 2    False
# 3     True

# 是否以某开头 / 结尾
print(s.str.startswith("a"))
print(s.str.endswith("cake"))

# 是否匹配正则
print(s.str.contains("^(apple|cherry)"))
# 0     True
# 1    False
# 2     True
# 3     True

# 精确匹配
print(s.str.match("apple"))     # 从开头匹配
print(s.str.fullmatch("apple")) # 完全一致

# 缺失值默认返回 False?不,默认返回 NaN(na 参数控制)
print(s.str.contains("apple", na=False))

| 方法 | 含义 |

|------|------|

| contains(pat, case=, regex=) | 是否包含(默认支持正则) |

| startswith(pat) | 是否以 pat 开头 |

| endswith(pat) | 是否以 pat 结尾 |

| match(pat) | 开头是否匹配正则 |

| fullmatch(pat) | 是否完全匹配 |

| isalnum/isalpha/isnumeric | 字符类型判断 |

# 筛选应用:找出含"apple"的行
df = pd.DataFrame({"food": ["apple pie", "banana", "cherry"], "price": [5, 3, 4]})
print(df[df["food"].str.contains("apple")])
contains 的 case=False 可忽略大小写;regex=False 则把模式当普通字符串。

4. 拆分与拼接

4.1 拆分:str.split

s = pd.Series(["张三-北京", "李四-上海", "王五-广州"])

# 拆成列表
print(s.str.split("-"))
# 0    [张三, 北京]
# 1    [李四, 上海]
# 2    [王五, 广州]

# 直接展开成多列
print(s.str.split("-", expand=True))
#     0    1
# 0  张三   北京
# 1  李四   上海
# 2  王五   广州

# 指定列名
df_split = s.str.split("-", expand=True)
df_split.columns = ["姓名", "城市"]

# 只取第一部分
print(s.str.split("-").str[0])

# 限制拆分数
print("a-b-c".split("-"))            # ['a', 'b', 'c']
print(pd.Series(["a-b-c"]).str.split("-", n=1, expand=True))
# 0     a    b-c

4.2 按位置切分:str.slice / str[]

s = pd.Series(["abcdef", "uvwxyz"])

print(s.str[0])            # 第一个字符
print(s.str[1:4])          # 切片:bcd / vwx
print(s.str.slice(2, 5))   # 等价写法
print(s.str.slice(0, 4, 2))  # 带步长:ac / uw

4.3 拼接:str.cat

s1 = pd.Series(["张", "李", "王"])
s2 = pd.Series(["三", "四", "五"])

# 元素级拼接
print(s1.str.cat(s2))
# 0    张三
# 1    李四
# 2    王五

# 加分隔符
print(s1.str.cat(s2, sep="-"))
# 0    张-三

# 整列拼成一个字符串
print(s1.str.cat(sep="、"))
# 张、李、王

# 与缺失值:na_rep 处理
s3 = pd.Series(["a", None, "c"])
print(s1.str.cat(s3, na_rep="?"))
# 0    a?  <- 不对,这里是逐元素:0->张+a?  需要注意
拼接多列更常用的写法:df["姓名"] = df["姓"] + df["名"] 或 df["姓名"] = df["姓"].astype(str) + "-" + df["名"]。
df = pd.DataFrame({"姓": ["张", "李"], "名": ["三", "四"]})
df["全名"] = df["姓"] + df["名"]          # 直接 +
df["带分隔"] = df["姓"] + "-" + df["名"]

4.4 重复与填充

s = pd.Series(["ab", "cd"])
print(s.str.repeat(2))        # abab cdcd
print(s.str.pad(width=5, side="left", fillchar="0"))  # 左侧补 0 到 5 位
print(s.str.zfill(5))         # 数字补零:000ab

5. 查找、替换与删除

5.1 查找

s = pd.Series(["apple123", "banana456", "cherry"])

# 子串位置
print(s.str.find("a"))          # 第一次出现位置,找不到 -1
print(s.str.rfind("a"))         # 最后一次出现位置

# 提取匹配内容
print(s.str.extract(r"(\d+)"))  # 提取数字(正则,见下节)

5.2 替换:str.replace

s = pd.Series(["北京-朝阳", "上海-浦东", "广州-天河"])

# 普通替换
print(s.str.replace("-", "市"))
# 0    北京市朝阳
# 1    上海市浦东
# 2    广州市天河

# 正则替换(默认支持正则)
print(s.str.replace(r"-\w+", ""))   # 删除 -后面内容
# 0    北京
# 1    上海
# 2    广州

# 忽略大小写
print(pd.Series(["Apple", "apple"]).str.replace("apple", "fruit", case=False))

# 正则替换函数
s2 = pd.Series(["a1b2", "c3d4"])
print(s2.str.replace(r"(\d)", lambda m: f"[{m.group(1)}]"))
# 0    a[1]b[2]
# 1    c[3]d[4]

5.3 删除字符:translate / removeprefix

s = pd.Series(["ab12cd", "xy78zw"])

# 删除数字
print(s.str.replace(r"\d", "", regex=True))
# 0    abcd
# 1    xyzw

# 删除指定字符集
print(s.str.translate(str.maketrans("", "", "0123456789")))

# 去掉前缀 / 后缀
print(pd.Series(["cat_1", "cat_2"]).str.removeprefix("cat_"))
print(pd.Series(["a.txt", "b.txt"]).str.removesuffix(".txt"))

6. 正则表达式提取

正则(regex)是文本处理的终极武器。pandas 的 .str.extract 和 .str.extractall 能直接从字符串中提取结构化字段。

6.1 提取单个字段

s = pd.Series(["订单A100-500元", "订单B200-800元", "无效记录"])

# 提取金额
print(s.str.extract(r"(\d+)元"))
#      0
# 0  500
# 1  800
# 2  NaN

# 提取多个字段(每个分组一列)
df_ext = s.str.extract(r"订单([A-Z])(\d+)-(\d+)元")
df_ext.columns = ["类别", "编号", "金额"]
print(df_ext)
#   类别   编号   金额
# 0    A   100   500
# 1    B   200   800
# 2  NaN   NaN   NaN

6.2 提取所有匹配(extractall)

s = pd.Series(["a=1;b=2", "c=3"])

print(s.str.extractall(r"(\w)=(\d)"))
#             0  1
#   match
# 0 0        a  1
#   1        b  2
# 1 0        c  3

6.3 常用正则速查

| 模式 | 含义 | 示例 |

|------|------|------|

| \d | 数字 | \d+ 连续数字 |

| \w | 字母数字下划线 | \w+ 单词 |

| \s | 空白 | \s+ 连续空白 |

| . | 任意字符 | .* 任意内容 |

| ^ / $ | 开头 / 结尾 | ^北京 |

| [abc] | 字符集合 | [0-9] 数字 |

| (a\|b) | 或 | (苹果\|香蕉) |

| {n,m} | 重复次数 | \d{4} 四位数字 |

6.4 正则筛选

s = pd.Series(["13800138000", "021-12345678", "10086"])

# 手机号(1 开头 11 位)
print(s.str.contains(r"^1\d{10}$"))
# 0     True
# 1    False
# 2    False

# 带区号电话
print(s[s.str.contains(r"^\d{3}-\d{8}$")])

7. 类型转换与长度统计

s = pd.Series(["apple", "banana", "cherry"])

print(s.str.len())               # 每元素长度:5 6 6
print(s.str.upper())             # 已在前面学过

# 字符串与数值互转
s_num = pd.Series(["1", "2", "3"])
print(pd.to_numeric(s_num))      # 转数值(见第 8 章)
print(s_num.str.isdigit())       # 是否全是数字

8. 处理 object 与 string 类型的差异

# object 类型
s_obj = pd.Series(["a", "b", None])

# string 类型(推荐)
s_str = s_obj.astype("string")

# 差异 1:缺失值表示
print(s_obj.isna())    # True(内部是 np.nan)
print(s_str.isna())    # True(内部是 pd.NA)

# 差异 2:拼接行为
print(s_obj + "-x")    # 会拼出 'nan-x'(坑!)
print(s_str + "-x")    # 缺失保持 <NA>

# 差异 3:contains 的 na 处理
print(s_obj.str.contains("a", na=False))   # 需要 na=False 参数
print(s_str.str.contains("a"))             # 直接得到 False?实际是 <NA>
建议:新代码优先用 astype("string"),避免 object 拼接 NaN 的经典坑。

9. 文本清洗实战案例

import pandas as pd

# 模拟脏数据
raw = pd.DataFrame({
    "姓名": [" 张三 ", "李四", " 王五 ", "赵六"],
    "电话": ["138-0013-8000", "13911112222", "021-5566-7788", "137-5555-9999"],
    "邮箱": ["Zhang@Example.com", "li.si@qq.com", "wangwu@163.com", "INVALID"],
    "备注": ["VIP客户", "普通客户", "VIP客户,重点维护", "普通"],
})

def clean_text(df):
    df = df.copy()
    df["姓名"] = df["姓名"].str.strip()                       # 1. 去空白
    df["电话"] = df["电话"].str.replace("-", "", regex=False)  # 2. 去分隔符
    df["邮箱"] = df["邮箱"].str.lower()                        # 3. 统一小写
    df["是否VIP"] = df["备注"].str.contains("VIP", na=False)   # 4. 提取标记
    df["电话有效"] = df["电话"].str.fullmatch(r"1\d{10}")       # 5. 校验格式
    return df

print(clean_text(raw))

输出:

   姓名          电话              邮箱         备注  是否VIP  电话有效
0  张三  13800138000  zhang@example.com  VIP客户   True    True
1  李四  13911112222     li.si@qq.com  普通客户  False    True
2  王五  02155667788   wangwu@163.com  VIP客户,重点维护  True   False
3  赵六  13755559999          invalid  普通      False    True

10. 常见坑与注意事项

| 坑 | 现象 | 解决办法 |

|----|------|----------|

| 对数值列用 .str | AttributeError | 先 astype(str) 或 astype("string") |

| 正则未转义 | . 匹配任意字符 | 需要字面量时用 \. 或 regex=False |

| replace 默认当正则 | "-" 没问题但 "." 出意外 | 明确 regex=False 做纯文本替换 |

| 拼接时 NaN | "nan-x" 出现 | 用 string 类型或 fillna("") |

| extract 找不到 | 返回 NaN 不是报错 | 先 str.contains 校验再提取 |

| 中文正则 | 某些写法不生效 | 确保 \w 覆盖中文或改用 [\\u4e00-\\u9fa5] |


11. 本章小结与练习

小结

  • .str 访问器把字符串操作向量化,缺失值自动传递;
  • 清洗三件套:strip()、lower()、replace();
  • 拆分 split(expand=True) 展开多列,+/cat 拼接;
  • 正则:contains(筛选)、extract(提取字段)、replace(批量替换);
  • 推荐使用 string 类型替代 object。

练习题

1. 对 [" Apple ", " banana ", "cherry"] 去空白并统一小写。

2. 用 split(expand=True) 把 "2025-01-01 08:30:00" 拆成日期和时间两列。

3. 从 "订单号: A100, 金额: 250.5元" 中提取订单号和金额。

4. 将一列电话中的 - 和空格全部删除。

5. 用正则筛选出所有合法的邮箱地址。


下一篇预告:第 10 章 数据变换:map / apply / applymap —— 掌握 pandas 逐元素、逐行、逐列变换的三大神器。
— 全文完 —回到顶部 ↑
下载推广海报

文章推广海报

《第 9 章 文本数据处理》完整推广海报
DISCUSSION

文章回复

0 条公开回复
未登录回复需要审核后公开
还没有回复,欢迎参与讨论。