Pandas: 字符串操作
最后更新:2026-08-26
真实数据里的文本从来不干净——多余空格、大小写混乱、特殊字符、格式不统一。Pandas 的 .str 访问器让字符串操作也享受向量化:一行代码清洗整列文本,比 Python 循环快 100 倍。本节覆盖最常用的字符串方法,并构建链式清洗流水线。
⚠️ 注意: 以下代码需在本地 Python 环境中运行。
1. 你将学到
- ❶ .str 访问器
- ❷ 常用方法(strip/lower/upper/title/replace)
- ❸ 正则表达式
- ❹ split / extract
- ❺ contains / startswith / match
2. Carol 的用户输入清洗
(1) 痛点:用户输入五花八门
Carol 收集的用户数据格式混乱:
PYTHON
import pandas as pd
df = pd.DataFrame({
'name': [' alice ', 'BOB', 'Charlie!', ' carol ', 'DaVid123'],
'email': ['alice@EXAMPLE.com', 'bob@company.ORG', 'charlie@uni.edu',
'CAROL@example.COM', 'david@test.net'],
'phone': ['555-0100', '(555) 0200', '555.0300', '+1-555-0400', '5550500']
})
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
(2) 解法:.str 链式清洗
▶ 示例:链式文本清洗(难度⭐)
PYTHON
import pandas as pd
df = pd.DataFrame({
'name': [' alice ', 'BOB', 'Charlie!', ' carol ', 'DaVid123'],
'email': ['alice@EXAMPLE.com', 'bob@company.ORG', 'charlie@uni.edu',
'CAROL@example.COM', 'david@test.net']
})
# Chain .str methods: strip → replace → title
df['name_clean'] = (df['name']
.str.strip()
.str.replace(r'[^a-zA-Z ]', '', regex=True)
.str.title()
)
print(df[['name', 'name_clean']])
# name name_clean
# 0 alice Alice
# 1 BOB Bob
# 2 Charlie! Charlie
# 3 carol Carol
# 4 DaVid123 David
# Lowercase email domain
df['email_clean'] = df['email'].str.lower()
print(df['email_clean'])
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
3. .str 访问器
(1) .str vs Python str
| 特性 | Python str | Pandas .str |
|---|---|---|
| 操作对象 | 单个字符串 | 整列(向量化) |
| NaN 处理 | 报错 | 自动跳过(返回 NaN) |
| 链式 | 不支持 | ✅ 连续调用 |
| 性能 | 循环→慢 | 向量化→快 |
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:.str 基础方法(难度⭐)
PYTHON
import pandas as pd
s = pd.Series(['Hello World', 'pandas', 'DATA SCIENCE', None, 'python'])
# Case conversion
print(s.str.lower()) # hello world, pandas, data science, NaN, python
print(s.str.upper()) # HELLO WORLD, PANDAS, DATA SCIENCE, NaN, PYTHON
print(s.str.title()) # Hello World, Pandas, Data Science, NaN, Python
print(s.str.capitalize()) # Hello world, Pandas, Data science, NaN, Python
# Whitespace
s2 = pd.Series([' hello ', '\tworld\n', ' pandas '])
print(s2.str.strip()) # hello, world, pandas
print(s2.str.lstrip()) # hello , world\n, pandas
print(s2.str.rstrip()) # hello, \tworld, pandas
# Length
print(s.str.len()) # 11, 6, 12, NaN, 6
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
4. replace / contains / match
(1) replace 替换
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:replace 字符串替换(难度⭐⭐)
PYTHON
import pandas as pd
s = pd.Series(['price: $100', 'price: $200', 'cost: €50'])
# Simple replacement
print(s.str.replace('$', 'USD'))
# price: USD100, price: USD200, cost: €50
# Regex replacement
print(s.str.replace(r'[\$€]', '', regex=True))
# price: 100, price: 200, cost: 50
# Replace multiple patterns
phones = pd.Series(['555-0100', '(555) 0200', '555.0300'])
clean = phones.str.replace(r'[^0-9]', '', regex=True)
print(clean) # 5550100, 5550200, 5550300
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
(2) contains / startswith / endswith
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:条件筛选字符串(难度⭐⭐)
PYTHON
import pandas as pd
df = pd.DataFrame({
'name': ['Alice Johnson', 'Bob Smith', 'Charlie Brown', 'Carol White'],
'email': ['alice@example.com', 'bob@company.org', 'charlie@uni.edu', 'carol@test.net']
})
# Contains substring
has_com = df[df['email'].str.contains('.com')]
print(has_com['name']) # Alice Johnson
# Startswith / endswith
starts_with_a = df[df['name'].str.startswith('A')]
print(starts_with_a)
edu_email = df[df['email'].str.endswith('.edu')]
print(edu_email['name']) # Charlie Brown
# Regex contains — find org or edu domains
org_or_edu = df[df['email'].str.contains(r'\.(org|edu)$', regex=True)]
print(org_or_edu[['name', 'email']])
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
(3) contains vs match 区别
| 方法 | 匹配位置 | 正则 |
|---|---|---|
| contains | 任意位置 | ✅ |
| match | 从开头 | ✅ |
| startswith | 从开头 | ❌(纯字符串) |
| endswith | 从末尾 | ❌(纯字符串) |
5. split / extract
(1) split 分割
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:split 分割字符串(难度⭐⭐)
PYTHON
import pandas as pd
df = pd.DataFrame({
'name': ['Alice Johnson', 'Bob Smith', 'Charlie Brown'],
'email': ['alice@example.com', 'bob@company.org', 'charlie@uni.edu']
})
# Split into list
print(df['name'].str.split(' '))
# 0 [Alice, Johnson]
# 1 [Bob, Smith]
# 2 [Charlie, Brown]
# Split into separate columns
name_split = df['name'].str.split(' ', expand=True)
print(name_split)
# 0 1
# 0 Alice Johnson
# 1 Bob Smith
# 2 Charlie Brown
# Split and keep only first part
df['first_name'] = df['name'].str.split(' ').str[0]
print(df['first_name'])
# Split email to get domain
df['domain'] = df['email'].str.split('@').str[1]
print(df['domain'])
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
(2) extract 正则提取
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:extract 提取模式(难度⭐⭐⭐)
PYTHON
import pandas as pd
df = pd.DataFrame({
'contact': [
'Alice <alice@example.com>',
'Bob <bob@company.org>',
'Charlie (555-0100)',
'Carol <carol@test.net>'
]
})
# Extract email with regex group
df['email'] = df['contact'].str.extract(r'<(.+?)>')
print(df[['contact', 'email']])
# contact email
# 0 Alice <alice@example.com> alice@example.com
# 1 Bob <bob@company.org> bob@company.org
# 2 Charlie (555-0100) NaN
# 3 Carol <carol@test.net> carol@test.net
# Extract multiple groups
df2 = pd.DataFrame({
'email': ['alice@example.com', 'bob@company.org']
})
parts = df2['email'].str.extract(r'(.+?)@(.+)')
print(parts)
# 0 1
# 0 alice example.com
# 1 bob company.org
# Extract all matches
colors = pd.Series(['red,blue,green', 'yellow,pink'])
print(colors.str.extractall(r'(\w+)'))
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
6. 其他常用方法
(1) 速查表
| 方法 | 功能 | 示例 |
|---|---|---|
| len() | 字符串长度 | s.str.len() |
| count() | 子串出现次数 | s.str.count('a') |
| find() | 子串位置 | s.str.find('bc') |
| isdigit() | 是否全数字 | s.str.isdigit() |
| isalpha() | 是否全字母 | s.str.isalpha() |
| join() | 用分隔符连接 | s.str.join(',') |
| pad() | 填充到固定宽度 | s.str.pad(10, fillchar='0') |
| repeat() | 重复字符串 | s.str.repeat(3) |
| slice() | 切片 | s.str.slice(0, 5) |
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:pad/slice/zfill(难度⭐)
PYTHON
import pandas as pd
ids = pd.Series(['1', '23', '456', '7890'])
# Pad with zeros to 4 digits
print(ids.str.zfill(4))
# 0 0001
# 1 0023
# 2 0456
# 3 7890
# Slice first 2 characters
codes = pd.Series(['US-001', 'UK-002', 'JP-003'])
print(codes.str.slice(0, 2)) # US, UK, JP
print(codes.str[:2]) # same, Python slice syntax
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
7. 完整示例:用户数据文本清洗流水线
(5) ▶ 文本清洗流程
graph TB
A[原始文本] --> B[strip 去空格]
B --> C[lower / upper 统一大小写]
C --> D[replace 去特殊字符]
D --> E[split / extract 提取信息]
E --> F[title 格式化]
F --> G[干净文本]
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
▶ 示例
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
:文本清洗全流程(难度⭐⭐⭐)
PYTHON
import pandas as pd
# ============================================
# Comprehensive example: Text cleaning
# pipeline for user data
# ============================================
# 1. Raw messy user data
df = pd.DataFrame({
'raw_name': [' ALICE johnson ', 'bob SMITH', ' CHARLIE! brown ', 'CAROL white123'],
'raw_email': ['ALICE@Example.COM', 'bob@company.ORG', 'charlie@uni.EDU', 'CAROL@TEST.NET'],
'raw_phone': ['555-0100', '(555) 0200', '555.0300 ext 42', '+1-555-0400'],
'raw_zip': ['0123', '12345', ' 90210 ', '10001-2345']
})
# 2. Name cleaning: strip → remove special chars → title case
df['name'] = (df['raw_name']
.str.strip()
.str.replace(r'[^a-zA-Z\s]', '', regex=True)
.str.replace(r'\s+', ' ', regex=True) # collapse multiple spaces
.str.title()
)
# 3. Email cleaning: lowercase
df['email'] = df['raw_email'].str.lower()
# 4. Phone cleaning: keep only digits
df['phone_digits'] = df['raw_phone'].str.replace(r'[^0-9]', '', regex=True)
df['phone_formatted'] = df['phone_digits'].str.slice(0, 3) + '-' + df['phone_digits'].str.slice(3, 7)
# 5. Zip code: extract 5-digit code
df['zip5'] = df['raw_zip'].str.strip().str.extract(r'(\d{5})')
# 6. Email domain
df['domain'] = df['email'].str.split('@').str[1]
# 7. Show cleaned results
print("=== Cleaned Data ===")
print(df[['name', 'email', 'phone_formatted', 'zip5', 'domain']])
TEXT
📖 仅展示
> **输出:** 在本地 Python 环境(pandas 2.x)运行。Piston 服务器未预装 pandas,请在本机安装(`pip install pandas`)后实操对照。实际数值会因 pandas 版本略有差异。
❓ 常见问题
Q .str 和 Python str 区别?
A Python str 方法操作单个字符串,.str 访问器操作整列(向量化)。.str 自动跳过 NaN(返回 NaN),Python str 遇 NaN 报错。.str 支持链式调用(
s.str.strip().str.lower()),Python str 不支持。性能上 .str 比循环快 10-100 倍。Q 对 NaN 怎么处理?
A .str 方法自动跳过 NaN——输入 NaN,输出 NaN。不需要手动检测缺失值。这也是 .str 比用 apply + Python str 更方便的原因之一。contains/match 对 NaN 返回 False(不是 NaN),筛选时注意。
Q extract 返回什么?
A extract 返回 DataFrame——每个正则组一列。单组返回 1 列 DataFrame,多组返回多列。extractall 返回所有匹配(MultiIndex)。注意:无匹配的行返回 NaN。需要 Series 则用
.str.extract(...)[0]。Q contains 和 match 区别?
A contains 在字符串任意位置匹配(如 'abc' contains 'b' → True)。match 只从字符串开头匹配(如 'abc' match 'b' → False)。startswith/endswith 是纯字符串匹配(不支持正则)。需要正则用 contains/match,纯文本用 startswith/endswith。
Q StringDtype 有什么好处?
A 默认字符串列是 object 类型(Python 对象),每个值是独立 str 对象。StringDtype(
df['col'].astype('string'))是 Pandas 专用字符串类型,用 pd.NA 表示缺失,内存更高效,.str 方法更一致。Pandas 2.x 推荐用 StringDtype 替代 object。Q str.replace 和 df.replace 区别?
A str.replace 只操作字符串列,支持正则(regex=True),逐元素替换。df.replace 操作整个 DataFrame,可替换任意类型的值,也支持正则。字符串替换优先用 str.replace(语义更清晰),跨列替换用 df.replace。
Q 如何提取电话号码中的数字?
A 用
s.str.replace(r'[^0-9]', '', regex=True) 删除所有非数字字符。或用 extract:s.str.extract(r'(\d{3})[-.)\s]*(\d{3})[-.)\s]*(\d{4})') 提取区号+前缀+后缀。正则表达式是文本提取的核心能力。📖 小节
- .str 访问器提供向量化字符串操作,自动处理 NaN
- strip/lstrip/rstrip 去空格,lower/upper/title/size 转换大小写
- replace 替换(支持正则),contains 条件筛选(支持正则)
- split 分割字符串(expand=True 分列),extract 正则提取
- match 从开头匹配,contains 任意位置匹配,startswith/endswith 纯文本
- 链式调用构建清洗流水线:strip→replace→lower/title
- StringDtype 替代 object,更高效的字符串存储
📝 作业
- 基础题(难度⭐):创建含空格和大小写混乱的姓名 Series,用 .str.strip().str.title() 清洗,再统计清洗后以 'A' 开头的姓名数。
- 进阶题(难度⭐⭐):创建含电话号码的 Series(格式不一),用 replace+正则提取纯数字,再用 zfill 格式化为 10 位。
- 挑战题(难度⭐⭐⭐):模拟用户注册数据(name/email/phone/zip 全有格式问题),构建 5 步链式清洗流水线,每步一种 .str 方法,最后输出干净表格。