- 4.1 The NumPy ndarray 多维数组对象
- 4.2 Universal Functions 通用函数
- 4.3 Array-Oriented Programming with Arrays 数组导向编程
- 5.1 Introduction to pandas Data Structures pandas 的数据结构
- 5.2 Essential Functionality 主要功能
- 5.3 Summarizing and Computing Descriptive Statistics 汇总和描述性统计
- 7.1 Handling Missing Data 处理缺失数据
- 7.2 Data Transformation 数据变换
- 7.3 String Manipulation 字符串处理
- 11.1 Date and Time Data Types and Tools 日期和时间数据类型及其工具
- 11.2 Time Series Basics 时间序列基础
- 11.3 Date Ranges, Frequencies, and Shifting 日期范围,频度,和位移
- 12.1 Categorical Data 类别数据
- 14.1 USA.gov Data from Bitly USA.gov 数据集
- 14.2 MovieLens 1M Dataset MovieLens 1M 数据集
- 14.3 US Baby Names 1880–2010 1880年至2010年美国婴儿姓名
7.3 String Manipulation 字符串处理
python 很多内建方法很适合处理 string。而且对于更复杂的模式,可以配合使用正则表达式。而 pandas 则混合了两种方式。
1 String Object Methods(字符串对象方法)
大部分 string 处理,使用内建的一些方法就足够了。比如,可以用 split 来分割用逗号区分的字符串:
val = 'a,b, guido'
val.split(',')
['a', 'b', ' guido']
split 经常和 strip 一起搭配使用来去除空格(包括换行符):
pieces = [x.strip() for x in val.split(',')] pieces
['a', 'b', 'guido']
可以使用+号把::和字符串连起来:
first, second, third = pieces
first + '::' + second + '::' + third
'a::b::guido'
但这种方法并不 python,更快的方法是直接用 join 方法:
'::'.join(pieces)
'a::b::guido'
其他一些方法适合锁定子字符串位置相关的。用 in 关键字是检测 substring 最好的方法,当然,index 和 find 也能完成任务:
'guido' in val
True
val.index(',')
1
val.find(':')
-1
注意 index 和 find 的区别。如果要找的 string 不存在的话,index 会报错。而 find 会返回-1:
val.index(':')
count 会返回一个 substring 出现的次数:
val.count(',')
2
replace 会取代一种出现方式(pattern)。也通常用于删除 pattern,传入一个空字符串即可:
val.replace(',', '::')
'a::b:: guido'
val.replace(',', '')
'ab guido'
2 Regular Expressions(正则表达式)
正则表达式能让我们寻找更复杂的 pattern。通常称一个表达式为 regex,由正则表达语言来代表一个字符串模式。可以使用 python 内建的 re 模块来使用。
关于正则表达式,有很多教学资源,可以自己找几篇来学一些,这里不会介绍太多。
re 模块有以下三个类别:patther matching(模式匹配), substitution(替换), splitting(分割)。通常这三种都是相关的,一个 regex 用来描述一种 pattern,这样会有很多种用法。这里举个例子,假设我们想要根据空格(tabs,spaces,newlines)来分割一个字符串。用于描述一个或多个空格的 regex 是 \s+
:
import re
text = "foo bar\t baz \tqux"
re.split('\s+', text)
['foo', 'bar', 'baz', 'qux']
当调用 re.split('\s+', text)
的时候,正则表达式第一次被 compile 编译,并且 split 方法会被调用搜索 text。我们可以自己编译 regex,用 re.compile,可以生成一个可以多次使用的 regex object:
regex = re.compile('\s+')
regex.split(text)
['foo', 'bar', 'baz', 'qux']
如果想要得到符合 regex 的所有结果,以一个 list 结果返回,可以使用 findall 方法:
regex.findall(text)
[' ', '\t ', ' \t']
为了防止\在正则表达式中的逃逸,推荐使用 raw string literal,比如
r'C:\x'
,而不是使用'C:\\x
使用 re.compile 创建一个 regex object 是被强烈推荐的,如果你打算把一个表达式用于很多 string 上的话,这样可以节省 CPU 的资源。
match 和 search,与 findall 关系紧密。不过 findall 会返回所有匹配的结果,而 search 只会返回第一次匹配的结果。更严格地说,match 只匹配 string 开始的部分。这里举个例子说明,我们想要找到所有的邮件地址:
text = """Dave dave@google.com Steve steve@gmail.com Rob rob@gmail.com Ryan ryan@yahoo.com """ pattern = r'[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}'
# re.IGNORECASE makes the regex case-insensitive regex = re.compile(pattern, flags=re.IGNORECASE)
使用 findall 找到一组邮件地址:
regex.findall(text)
['dave@google.com', 'steve@gmail.com', 'rob@gmail.com', 'ryan@yahoo.com']
search 返回 text 中的第一个匹配结果。match object 能告诉我们找到的结果在 text 中开始和结束的位置:
m = regex.search(text)
m
<_sre.SRE_Match object; span=(5, 20), match='dave@google.com'>
text[m.start():m.end()]
'dave@google.com'
regex.match 返回 None,因为它只会在 pattern 存在于 stirng 开头的情况下才会返回匹配结果:
print(regex.match(text))
None
而 sub 返回一个新的 string,把 pattern 出现的地方替换为我们指定的 string:
print(regex.sub('REDACTED', text))
Dave REDACTED Steve REDACTED Rob REDACTED Ryan REDACTED
假设你想要找到邮件地址,同时,想要把邮件地址分为三个部分,username, domain name, and domain suffix.(用户名,域名,域名后缀)。需要给每一个 pattern 加一个括号:
pattern = r'([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})'
regex = re.compile(pattern, flags=re.IGNORECASE)
match object 会返回一个 tuple,包含多个 pattern 组份,通过 groups 方法:
m = regex.match('wesm@bright.net')
m.groups()
('wesm', 'bright', 'net')
findall 会返回 a list of tuples:
regex.findall(text)
[('dave', 'google', 'com'), ('steve', 'gmail', 'com'), ('rob', 'gmail', 'com'), ('ryan', 'yahoo', 'com')]
sub 也能访问 groups 的结果,不过要使用特殊符号 \1, \2。\1 表示第一个匹配的 group,\2 表示第二个匹配的 group,以此类推:
print(regex.sub(r'Username: \1, Domain: \2, Suffix: \3', text))
Dave Username: dave, Domain: google, Suffix: com Steve Username: steve, Domain: gmail, Suffix: com Rob Username: rob, Domain: gmail, Suffix: com Ryan Username: ryan, Domain: yahoo, Suffix: com
3 Vectorized String Functions in pandas(pandas 中的字符串向量化函数)
一些复杂的数据清理中,string 会有缺失值:
import numpy as np import pandas as pd
data = {'Dave': 'dave@google.com', 'Steve': 'steve@gmail.com', 'Rob': 'rob@gmail.com', 'Wes': np.nan}
data = pd.Series(data) data
Dave dave@google.com Rob rob@gmail.com Steve steve@gmail.com Wes NaN dtype: object
data.isnull()
Dave False Rob False Steve False Wes True dtype: bool
可以把一些字符串方法和正则表达式(用 lambda 或其他函数)用于每一个 value 上,通过 data.map,但是这样会得到 NA(null)值。为了解决这个问题,series 有一些数组导向的方法可以用于字符串操作,来跳过 NA 值。这些方法可以通过 series 的 str 属性;比如,我们想检查每个电子邮箱地址是否有'gmail' with str.contains:
data.str
<pandas.core.strings.StringMethods at 0x111f305c0>
data.str.contains('gmail')
Dave False Rob True Steve True Wes NaN dtype: object
正则表达式也可以用,配合任意的 re 选项,比如 IGNORECASE:
pattern
'([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\\.([A-Z]{2,4})'
data.str.findall(pattern, flags=re.IGNORECASE)
Dave [(dave, google, com)] Rob [(rob, gmail, com)] Steve [(steve, gmail, com)] Wes NaN dtype: object
有很多方法用于向量化。比如 str.get 或 index 索引到 str 属性:
matches = data.str.match(pattern, flags=re.IGNORECASE) matches
Dave (dave, google, com) Rob (rob, gmail, com) Steve (steve, gmail, com) Wes NaN dtype: object
为了访问嵌套 list 里的元素,我们可以传入一个 index 给函数:
matches.str.get(1)
Dave google Rob gmail Steve gmail Wes NaN dtype: object
matches.str.get(0)
Dave dave Rob rob Steve steve Wes NaN dtype: object
也可以使用这个语法进行切片:
data.str[:5]
Dave dave@ Rob rob@g Steve steve Wes NaN dtype: object
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论