Pandas 第八章 文本数据
一、str对象
1. str对象的设计意图
(1)核心定义
str是定义在Index 或 Series上的属性,专门用于批量处理序列中每个元素的文本内容,内置了大量文本处理方法,是 Pandas 处理文本数据的核心入口。
(2)设计逻辑
- 对标 Python 内置
str模块:Pandas 的str对象大量复用了 Python 原生字符串方法的命名与功能,降低学习成本。- 原生 Python:
str.upper('abcd')→ 输出'ABCD' - Pandas 中:
pd.Series(['abcd','efg','hi']).str.upper()→ 批量对每个元素执行大写转换,输出 Series
- 原生 Python:
- 方法兼容性:Pandas 的 50 个
str对象方法中,有 31 个与 Python 标准库str方法同名且功能完全一致,是批量处理文本序列的核心工具。
2. [] 索引器
str对象支持和 Python 原生字符串完全一致的[]索引与切片语法,实现对每个元素的字符级操作,同时自动处理越界问题。
(1)基础索引
- 原生字符串:
var = 'abcd'→var[0]→'a' - Pandas
str对象:s = pd.Series(['abcd','efg','hi'])→s.str[0]- 输出:
0 a\n1 e\n2 h\ndtype: object - 特性:越界索引自动返回缺失值
NaN,例如s.str[2]中,长度为 2 的'hi'会返回NaN
- 输出:
(2)切片操作
- 原生字符串:
var[-1:0:-2]→ 从末尾到开头,步长 - 2,输出'db' - Pandas
str对象:s.str[-1:0:-2]→ 批量对每个元素执行相同切片- 输出:
0 db\n1 g\n2 i\ndtype: object
- 输出:
3.string 类型
string类型是专门为字符串设计的存储类型,和数值型、category类型并列,实现更规范的文本数据管理。
(1)object vs string 差异
| 对比维度 | object 类型 | string 类型 |
|---|---|---|
| 索引逻辑 | 直接对元素本身做[]索引(列表取第 2 个元素、字典取 key 对应值) |
先把整个元素转为字面字符串,再对字符串做字符级索引 |
| 空值支持 | 非 Nullable 类型,缺失值用np.nan,数值结果会转为float64 |
Nullable 类型,缺失值用<NA>,数值结果保持Int64(整数型) |
| 布尔结果 | 缺失值会转为False,类型为bool |
缺失值保持<NA>,类型为boolean(Nullable 布尔型) |
| 适用场景 | 混合类型序列 | 纯文本序列(推荐) |
(2)典型示例
(a)索引逻辑差异
# 混合类型object序列
s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
# object类型str索引:按元素本身结构取
s.str[1]
# 输出:0 temp_1\n1 b\n2 NaN\n3 y\ndtype: object
# 转为string类型后:先转字符串再索引
s.astype('string').str[1]
# 输出:0 1\n1 '\n2 .\n3 y\ndtype: string
- 差异原因:
object直接对元素(字典 / 列表 / 数字)做索引;string先把元素转为字符串(如字典{1: 'temp_1'}转为"{1: 'temp_1', 2: 'temp_2'}"),再取第 1 个字符。
(b)空值处理差异
# 带缺失值的序列
s = pd.Series(['a', np.nan])
# object类型:len返回float64,缺失值为NaN
s.str.len()
# 输出:0 1.0\n1 NaN\ndtype: float64
# string类型:len返回Int64(Nullable整数),缺失值为<NA>
s.astype('string').str.len()
# 输出:0 1\n1 <NA>\ndtype: Int64
# 比较操作差异
s == 'a'
# 输出:0 True\n1 False\ndtype: bool
s.astype('string') == 'a'
# 输出:0 True\n1 <NA>\ndtype: boolean
注:
数值序列不能直接用 str 属性全数值的
object/category序列,禁止直接调用.str,必须先通过astype('string')转为字符串类型,再执行文本操作:s = pd.Series([12, 345, 6789]) # 报错:AttributeError # s.str[1] # 正确写法:先转string类型 s.astype('string').str[1] # 输出:0 2\n1 4\n2 7\ndtype: stringstr 属性的使用前提序列中至少有一个可迭代对象(字符串、列表、字典等),否则无法调用
.str属性。string 类型的推荐场景处理纯文本数据时,优先使用
string类型,避免object类型的混合类型隐患,同时获得更规范的 Nullable 空值处理。
二.正则表达式基础
1. 一般字符的匹配
正则表达式(Regex)是一种按指定模式匹配字符串内容的工具,核心逻辑是「从左到右匹配」,常用于文本提取、替换、校验等场景。
- 演示工具:Python
re模块的findall函数,作用是匹配所有符合模式、不重叠的子串,返回列表。 - 基础示例:匹配字符串中的
Appleimport re re.findall(r'Apple', 'Apple! This Is an Apple!') # 输出:['Apple', 'Apple'] - 注意:正则表达式前加
r表示原生字符串,避免转义符冲突,是 Python 中的最佳实践。
2.元字符基础
元字符是正则中具有特殊含义的符号,是构建匹配规则的基础,完整对照表如下:
|
元字符 |
描述 |
示例 |
运行结果/说明 |
|---|---|---|---|
|
|
匹配除换行符以外的任意字符 |
|
|
|
|
字符类,匹配方括号中包含的任意字符 |
|
|
|
|
否定字符类,匹配方括号中不包含的任意字符 |
|
|
|
|
匹配前面的子表达式零次或多次(贪婪匹配) |
|
|
|
|
匹配前面的子表达式一次或多次(贪婪匹配) |
|
|
|
|
匹配前面的子表达式零次或一次;或用于开启非贪婪匹配 |
|
|
|
|
花括号,匹配前面字符至少 n 次,不超过 m 次 |
|
|
|
|
字符组(分组),按照确切的顺序匹配字符 xyz |
|
|
|
|
分支结构,匹配符号左边或右边的子表达式(多选一) |
|
|
|
\ |
转义符,还原元字符原本的字面含义(如匹配 |
|
|
|
|
匹配行的开始 |
|
|
|
|
匹配行的结束 |
|
|
- 贪婪与非贪婪:
*、+、{n,m}默认是贪婪匹配(尽可能长),加?变为非贪婪(尽可能短)。- 字符类的特殊规则:
[]内的元字符会失去特殊含义(如.仅表示点号),-可表示范围(如[a-z]匹配小写字母)。- 转义符的使用:匹配
.、*、?等元字符本身时,必须加\转义(如r'\.'匹配点号)。
impore re
print(re.findall(r'.', 'abc')) # ['.'] 任意字符
print(re.findall(r'[ac]', 'abc')) # ['a','c'] 字符类
print(re.findall(r'[^ac]', 'abc')) # ['b'] 否定字符类
print(re.findall(r'[ab]{2}', 'aaaabbbb')) # ['aa','aa','bb','bb'] 次数匹配
print(re.findall(r'aaa|bbb', 'aaaabbbb')) # ['aaa','bbb'] 分支匹配
3.简写字符集(常用快捷语法)
简写字符集是常用字符类的缩写,大幅简化正则写法,完整对照表如下:
| 简写 | 描述 | 等价写法 |
|---|---|---|
\w |
匹配所有字母、数字、下划线 | [a-zA-Z0-9_] |
\W |
匹配非字母、数字、下划线的字符 | [^\w] |
\d |
匹配数字 | [0-9] |
\D |
匹配非数字 | [^\d] |
\s |
匹配空白字符(空格、制表符、换行等) | [\t\n\f\r\p{Z}] |
\S |
匹配非空白字符 | [^\s] |
\B |
匹配非单词边界(非空字符开头 / 结尾的位置,不匹配具体字符) | - |
import re
# 1. 匹配包含is的子串(\s匹配空白,.匹配任意字符)
re.findall(r'\sis', 'Apple! This Is an Apple!')
# 输出:['is', 'Is']
# 2. 匹配2个连续的\w(字母/数字/下划线)
re.findall(r'\w{2}', '09 8? 7w c_ 9q p@')
# 输出:['09', '7w', 'c_', '9q']
# 3. 匹配\w+\W\B(单词+非单词字符+非单词边界)
re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@')
# 输出:['8?', 'p@']
# 4. 匹配空白+任意字符
re.findall(r'\s.', 'Constant dropping wears the stone.')
# 输出:[' d', ' w', ' t', ' s', ' t']
1. 匹配逻辑优先级
- 字符组
()> 数量词*/+/?/{}> 分支|> 锚点^/$- 分支
|是短路匹配:从左到右匹配,一旦匹配成功就不再尝试右侧规则。2. 常见使用误区
- 忘记加
r原生字符串:导致\n、\t等被 Python 解析为转义字符,而非正则语法。- 混淆贪婪 / 非贪婪匹配:
r'a.*b会匹配最长的a...b,r'a.*?b匹配最短的。- 字符类中误用元字符:
[.*]仅匹配.或*,而非任意字符多次。
三、文本处理的五类操作
1. 拆分
(1)核心方法 str.split
-
功能:按正则表达式拆分字符串序列,支持控制拆分次数、展开为多列。
-
核心参数:
pat:第一个参数,正则表达式分隔符n:最大拆分次数(从左到右),默认 - 1(全部分拆)expand:是否展开为 DataFrame 多列,默认False(返回列表 Series)
-
示例演示:
import pandas as pd s = pd.Series(['上海市黄浦区方浜中路249号', '上海市宝山区密山路5号']) # 1. 全部分拆,返回列表Series print(s.str.split('[市区路]')) # 输出: # 0 [上海, 黄浦, 方浜中, 249号] # 1 [上海, 宝山, 密山, 5号] # dtype: object # 2. 限制拆分2次,展开为DataFrame print(s.str.split('[市区路]', n=2, expand=True)) # 输出: # 0 1 2 # 0 上海 黄浦 方浜中路249号 # 1 上海 宝山 密山路5号
(2)相似方法 str.rsplit
- 区别:
n参数从右到左限制拆分次数,和split方向相反。 - 注意:当前版本
rsplit存在 bug,不支持正则表达式分割,仅支持普通字符串分隔符。# 正则分隔符在rsplit中失效,直接返回原字符串 s.str.rsplit('[市区路]', n=2, expand=True)
2.合并操作
(1)str.join:序列内元素合并
- 功能:用指定连接符,将 Series 中每个元素的列表 / 可迭代对象拼接为字符串。
- 规则:列表中出现非字符串元素(如数字),直接返回
NaN。s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']]) print(s.str.join('-')) # 输出: # 0 a-b # 1 NaN # 2 NaN # dtype: object
(2)str.cat:多序列横向合并
-
功能:合并两个 Series,按索引对齐拼接字符串。
-
核心参数:
sep:连接符join:连接方式(left/right/outer/inner,默认left左连接)na_rep:缺失值替代符号
-
示例演示:
s1 = pd.Series(['a','b']) s2 = pd.Series(['cat','dog']) # 1. 默认左连接,索引对齐 print(s1.str.cat(s2, sep='-')) # 输出: # 0 a-cat # 1 b-dog # dtype: object # 2. 外连接,用?填充缺失值 s2.index = [1, 2] print(s1.str.cat(s2, sep='-', na_rep='?', join='outer')) # 输出: # 0 a-? # 1 b-cat # 2 ?-dog # dtype: object
3.匹配操作
(1)布尔型匹配(返回 True/False Series)
| 方法 | 功能 | 是否支持正则 | 核心示例 |
|---|---|---|---|
str.contains |
判断字符串是否包含指定正则模式 | 支持 | s.str.contains('\swat'):匹配包含空格 + at 的字符串 |
str.startswith |
判断字符串是否以指定前缀开头 | 不支持 | s.str.startswith('my'):匹配以 my 开头的字符串 |
str.endswith |
判断字符串是否以指定后缀结尾 | 不支持 | s.str.endswith('t'):匹配以 t 结尾的字符串 |
str.match |
判断字符串起始处是否匹配正则 | 支持 | `s.str.match('mh')`:匹配以 m/h 开头的字符串 |
s = pd.Series(['my cat', 'he is fat', 'railway station'])
print(s.str.contains('\swat')) # 匹配空格+at
# 输出:0 True, 1 True, 2 False
print(s.str.startswith('my')) # 匹配my开头
# 输出:0 True, 1 False, 2 False
print(s.str.endswith('t')) # 匹配t结尾
# 输出:0 True, 1 True, 2 False
print(s.str.match('m|h')) # 匹配m/h开头
# 输出:0 True, 1 True, 2 False
-
等价写法:用
str.contains+^/$实现startswith/endswith的正则版本# 等价于str.match('m|h') s.str.contains('^[m|h]') # 等价于反转后匹配结尾(s.str[::-1].str.match('ta[f|g]|n')) s.str.contains('[f|g]at|n$')
(2)索引型匹配(返回位置索引)
| 方法 | 功能 | 是否支持正则 | 示例 |
|---|---|---|---|
str.find |
返回从左到右第一次匹配子串的索引,未找到返回 - 1 | 仅支持普通字符串 | s.str.find('apple') → 11 |
str.rfind |
返回从右到左第一次匹配子串的索引,未找到返回 - 1 | 仅支持普通字符串 | s.str.rfind('apple') → 33 |
s = pd.Series(['This is an apple. That is not an apple.'])
print(s.str.find('apple')) # 左起第一个apple的索引:11
print(s.str.rfind('apple')) # 右起第一个apple的索引:33
4.替换
(1)正则替换
-
关键区分:
str.replace(文本专用)和DataFrame.replace(值替换)是完全不同的函数,文本替换必须用前者。 -
核心参数:
pat:正则表达式 / 待替换字符串repl:替换的目标内容(支持字符串或自定义函数)regex:是否启用正则,默认True(Pandas 1.2+)
-
基础示例:
import pandas as pd s = pd.Series(['a_1_b','c_?']) # 替换数字\d或问号?为'new' s.str.replace('\d|\?', 'new', regex=True) ### 0 a_new_b 1 c_new dtype: object
(2)子组 + 自定义函数替换
当需要对不同部分做差异化替换时,用 ** 正则子组(圆括号)** 提取内容,再传入自定义函数处理。
group(k):代表匹配到的第k个子组(圆括号包裹的内容)
示例:中文地址转英文
s = pd.Series([
'上海市黄浦区方浜中路249号',
'上海市宝山区密山路5号',
'北京市昌平区北农路2号'
])
# 正则子组:(市名)(区名)(路名)(编号)
pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
# 映射字典
city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
district = {
'昌平区': 'CP District',
'黄浦区': 'HP District',
'宝山区': 'BS District'
}
road = {
'方浜中路': 'Mid Fangbin Road',
'密山路': 'Mishan Road',
'北农路': 'Beinong Road'
}
# 自定义替换函数
def my_func(m):
str_city = city[m.group(1)]
str_district = district[m.group(2)]
str_road = road[m.group(3)]
# 编号去掉末尾的"号",加No.
str_no = 'No. ' + m.group(4)[:-1]
return ' '.join([str_city, str_district, str_road, str_no])
# 执行替换
s.str.replace(pat, my_func, regex=True)
输出:
0 Shanghai HP District Mid Fangbin Road No. 249
1 Shanghai BS District Mishan Road No. 5
2 Beijing CP District Beinong Road No. 2
dtype: object
(3)命名子组
用(?P<名称>模式)给子组命名,让代码更易读、可维护性更强:
# 命名子组正则
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
def my_func(m):
# 用子组名替代数字索引
str_city = city[m.group('市名')]
str_district = district[m.group('区名')]
str_road = road[m.group('路名')]
str_no = 'No. ' + m.group('编号')[:-1]
return ' '.join([str_city, str_district, str_road, str_no])
s.str.replace(pat, my_func, regex=True)
输出和数字子组完全一致,代码可读性大幅提升。
5.提取
提取是带捕获的匹配,可以从文本中精准提取目标字段,是文本特征工程的核心工具。
(1)str.extract:单次匹配
-
功能:每个字符串只匹配第一个符合模式的内容,返回 DataFrame,每列对应一个子组。
-
示例:提取地址中的市、区、路、编号
pat = '(\w+市)(\w+区)(\w+路)(\d+号)' s.str.extract(pat)输出:
0 1 2 3 0 上海市 黄浦区 方浜中路 249 号 1 上海市 宝山区 密山路 5 号 2 北京市 昌平区 北农路 2 号 -
命名子组版本:直接给 DataFrame 列命名
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)' s.str.extract(pat)输出:
市名 区名 路名 编号 0 上海市 黄浦区 方浜中路 249 号 1 上海市 宝山区 密山路 5 号 2 北京市 昌平区 北农路 2 号
(2)str.extractall:全量匹配
-
功能:匹配字符串中所有符合模式的内容,返回多级索引 DataFrame(第一层是原 Series 索引,第二层是匹配序号)。
-
示例:提取字符串中的多组数字
s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B']) # 正则:匹配A/B开头+数字+T/S+数字 pat = '[A|B](\d+)[T|S](\d+)' s.str.extractall(pat)输出:
0 1 my_A 0 135 15 1 26 5 my_B 0 674 2 1 25 6 -
命名子组版本:给列命名
pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)' s.str.extractall(pat_with_name)输出:
name1 name2 my_A 0 135 15 1 26 5 my_B 0 674 2 1 25 6
(3)str.findall:列表式全量匹配
- 功能:和
str.extractall类似,但返回列表 Series,每个元素是该字符串的所有匹配结果(元组形式)。 - 示例:
输出:s.str.findall(pat)my_A [(135, 15), (26, 5)] my_B [(674, 2), (25, 6)] dtype: object
str.replacevsreplace:绝对不要混淆!DataFrame.replace是值替换,不支持正则子组和函数替换,文本处理必须用str.replace。- 正则转义:如果要匹配
.、*、?等元字符,必须加\转义,或设置regex=False。- 子组数量:
str.extract的列数等于正则中子组的数量,无捕获组会报错。- 多级索引处理:
str.extractall返回的多级索引,可用.reset_index()展平为普通 DataFrame
四.常用字符串函数
1.字母型函数
核心函数对照表
| 函数 | 功能 | 示例输入 | 示例输出 |
|---|---|---|---|
str.upper() |
全部转为大写 | ['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'] |
['LOWER', 'CAPITALS', 'THIS IS A SENTENCE', 'SWAPCASE'] |
str.lower() |
全部转为小写 | 同上 | ['lower', 'capitals', 'this is a sentence', 'swapcase'] |
str.title() |
每个单词首字母大写,其余小写 | 同上 | ['Lower', 'Capitals', 'This Is A Sentence', 'Swapcase'] |
str.capitalize() |
仅第一个字符大写,其余小写 | 同上 | ['Lower', 'Capitals', 'This is a sentence', 'Swapcase'] |
str.swapcase() |
大小写反转(大写转小写,小写转大写) | 同上 | ['LOWER', 'capitals', 'THIS IS A SENTENCE', 'sWaPcAsE'] |
关键区别
title():按单词拆分,每个单词首字母大写(适合标题、名称)capitalize():仅整个字符串的第一个字符大写(适合句子开头)swapcase():完全反转大小写,无其他格式修改
2.数值型函数
pd.to_numeric() 非str对象方法,但文本处理中高频使用,用于将字符串格式的数值转为真正的数值类型,同时处理非法值。
核心参数
errors:非数值的处理模式,3 个核心选项:raise:遇到无法转换的值直接报错(默认)coerce:无法转换的值设为NaN(缺失值)ignore:保留原字符串,不转换
downcast:数值类型向下转换(如float64转int64,可选)
import pandas as pd
s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
# 1. errors='ignore':保留无法转换的原字符串
print(pd.to_numeric(s, errors='ignore'))
# 输出:
# 0 1
# 1 2.2
# 2 2e
# 3 ??
# 4 -2.1
# 5 0
# dtype: object
# 2. errors='coerce':无法转换的设为NaN
print(pd.to_numeric(s, errors='coerce'))
# 输出:
# 0 1.0
# 1 2.2
# 2 NaN
# 3 NaN
# 4 -2.1
# 5 0.0
# dtype: float64
# 3. 数据清洗:快速筛选出非数值的行
print(s[pd.to_numeric(s, errors='coerce').isna()])
# 输出:
# 2 2e
# 3 ??
# dtype: object
3.统计型函数
(1) 计数 str.count()
- 功能:统计字符串中正则模式出现的次数
- 示例:
s = pd.Series(['cat rat fat at', 'get feed sheet heat']) s.str.count('[r|f]at|ee') # 输出: # 0 2 # 1 2 # dtype: int64
(2)长度str.len()
- 功能:返回每个字符串的字符长度(含空格、符号)
- 示例:
s.str.len() # 输出: # 0 14 # 1 19 # dtype: int64
4.格式型函数
(1)除空型:去除空格
| 函数 | 功能 | 示例输入 | 处理后长度 |
|---|---|---|---|
str.strip() |
去除两侧空格 | [' col1', 'col2 ', ' col3 '] |
[4, 4, 4] |
str.rstrip() |
去除右侧空格 | 同上 | [5, 4, 5] |
str.lstrip() |
去除左侧空格 | 同上 | [4, 5, 5] |
- 适用场景:数据清洗(如列名、用户输入的首尾空格)、格式标准化
(2) 填充型:字符串补全
(a)通用填充:str.pad()
- 功能:自定义填充方向、长度、填充字符
- 参数:
width:目标长度side:填充方向(left/right/both)fillchar:填充字符(默认空格)
- 示例:
s = pd.Series(['a','b','c']) # 左侧填充*,长度5 s.str.pad(5, 'left', '*') # 输出: ['****a', '****b', '****c'] # 右侧填充*,长度5 s.str.pad(5, 'right', '*') # 输出: ['a****', 'b****', 'c****'] # 两侧填充*,长度5 s.str.pad(5, 'both', '*') # 输出: ['**a**', '**b**', '**c**']
(b)等价快捷函数
| 函数 | 等价pad写法 |
功能 |
|---|---|---|
str.rjust(width, fillchar) |
pad(width, 'left', fillchar) |
右侧对齐(左侧填充) |
str.ljust(width, fillchar) |
pad(width, 'right', fillchar) |
左侧对齐(右侧填充) |
str.center(width, fillchar) |
pad(width, 'both', fillchar) |
居中对齐(两侧填充) |
注意:
ljust是右侧填充,不是左侧填充,不要被名称误导!
(c)专用补 0:str.zfill()
- 功能:专门用于左侧补 0,固定长度,完美解决证券代码、编号等前导 0 丢失问题
- 示例:
s = pd.Series([7, 155, 303000]).astype('string') # 3种等价写法,都输出6位字符串,左侧补0 s.str.pad(6, 'left', '0') s.str.rjust(6, '0') s.str.zfill(6) # 输出: # 0 000007 # 1 000155 # 2 303000 # dtype: string
ljust/rjust方向误区:ljust是左对齐(右侧填充),rjust是右对齐(左侧填充),不要按字面意思搞反。pd.to_numeric不是str方法:必须直接调用pd.to_numeric(series),不能写series.str.to_numeric()。zfill仅补 0:如果需要填充其他字符,必须用pad/rjust/ljust。str.count支持正则:可以统计复杂模式的出现次数,不只是单个字符。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)