Python 长期以来一直是一种流行的原始数据操作语言,部分原因是它易于对字符串和文本处理。大多数文本操作都使用 string 对象的内置方法,从而变得简单。对于更复杂的模式匹配和文本操作,可能需要正则表达式。pandas 使我们能够将字符串和正则表达式简洁的应用于整个数据数组,从而进一步帮我们解决了缺失数据的烦恼。
一.Python 内置字符串对象方法
用一组代码示例简单学习下Python内置字符串方法的使用。
import numpy as np
import pandas as pd
val = "a,b, guido"
# 按逗号切片字符串val
# 输出['a', 'b', ' guido']
val.split(",")
# 先按逗号切片val,然后列表用推导式遍历每个切分出来的字符,并使用strip()方法去除前后空格
# 输出['a', 'b', 'guido']
pieces = [x.strip() for x in val.split(",")]
# 将pieces列表的三个值分别赋值给三个变量
first, second, third = pieces
# 用分隔符"::"连接三个字符串
# 输出'a::b::guido'
first + "::" + second + "::" + third
# 调用join方法输出效果和上面的相同
# 输出'a::b::guido'
"::".join(pieces)
# 输出True
"guido" in val
# 输出1
val.index(",")
# 输出-1
val.find(":")
# 输出2
val.count(",")
# 输出'a::b:: guido'
val.replace(",", "::")
# 输出'ab guido'
val.replace(",", "")
二.正则表达式
正则表达式提供了一种灵活的方法来搜索或匹配文本中的字符串模式。单个表达式(通常称为正则表达式)是根据正则表达式语言形成的字符串。Python 内置的 re 模块负责将正则表达式应用于字符串,下面我将在这里给出一些使用它的示例。
re 模块函数分为三类:模式匹配、替换和拆分。当然,这些都是相关的,正则表达式描述了要在文本中定位的模式,然后可以将其用于多种用途。让我们看一个简单的例子:假设我们想拆分一个具有可变数量空白字符(制表符、空格和换行符)的字符串。
描述一个或多个空白字符的正则表达式是 s+ 。本次正则表达式的学习都写在如下代码的注释中了
import re
text = "foo bar\t baz \tqux"
# 输出 ['foo', 'bar', 'baz', 'qux']
re.split(r"\s+", text)
# 上面那行代码当你调用 re.split(r“s+”, text) 时,首先编译正则表达式,
# 然后对传递的文本调用它的 split 方法。
# 我们也可以使用 re.compile 自行编译正则表达式,从而形成一个可重用的正则表达式对象如下:
regex = re.compile(r"\s+")
# 输出 ['foo', 'bar', 'baz', 'qux']
regex.split(text)
# 相反,可以使用 findall 方法 获取与正则表达式匹配的所有模式的列表
# 输出 [' ', '\t ', ' \t']
regex.findall(text)
# match 和 search 与 findall 密切相关。
# findall 返回字符串中的所有匹配项,
# 而 search 仅返回第一个匹配项。更严格地说,仅匹配字符串开头的匹配项。
# 下面的例子,识别大多数电子邮件地址的正则表达式
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 使正则表达式不区分大小写
regex = re.compile(pattern, flags=re.IGNORECASE)
# 输出['dave@google.com', 'steve@gmail.com', 'rob@gmail.com', 'ryan@yahoo.com']
regex.findall(text)
m = regex.search(text)
# 输出<re.Match object; span=(5, 20), match='dave@google.com'>
print(m)
# 输出 'dave@google.com'
text[m.start():m.end()]
# regex.match 返回 None,因为只有当模式出现在字符串的开头时,它才会匹配
# 输出None
print(regex.match(text))
# sub()方法 将返回一个新字符串,其中出现的模式将替换为新字符串,例如
# 输出:
# Dave REDACTED
# Steve REDACTED
# Rob REDACTED
# Ryan REDACTED
print(regex.sub("REDACTED", text))
# 假设我要查找电子邮件地址并同时将每个地址细分为三个组成部分:用户名、域名和域后缀。为此,要要分段的模式部分周围加上括号
# 如下模式
pattern = r"([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})"
regex = re.compile(pattern, flags=re.IGNORECASE)
# 由此修改后的正则表达式生成的 match 对象调用groups()方法返回模式组件的元组
m = regex.match("wesm@bright.net")
# 输出 ('wesm', 'bright', 'net')
m.groups()
# findall 在模式具有组时返回 Tuples 列表:
# 输出:
# [('dave', 'google', 'com'),
# ('steve', 'gmail', 'com'),
# ('rob', 'gmail', 'com'),
# ('ryan', 'yahoo', 'com')]
regex.findall(text)
# sub() 还可以使用\1 和 \2 等特殊符号访问每个匹配项中的组。符号 \1 对应于第一个匹配的组,\2 对应于第二个组,依此类推。
# 例如以下代码输出:
# 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
print(regex.sub(r"Username: \1, Domain: \2, Suffix: \3", text))
Python 中的正则表达式还有很多,下面的图是一些列表。
三.pandas 中的字符串函数
清理杂乱的数据集以进行分析通常需要大量的字符串操作。更复杂的是,包含字符串的列有时会缺少数据,看如下示例:
import re
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)
print(data)
输出:
Dave dave@google.com
Steve steve@gmail.com
Rob rob@gmail.com
Wes NaN
dtype: object
以上data调用isna() data.isna()输出:
Dave False
Steve False
Rob False
Wes True
dtype: bool
我们可以使用 data.map() 将字符串和正则表达式方法(传递 lambda 或其他函数)应用于每个值,但在 NA (null) 值上将失败。为了解决这个问题,Series 为字符串操作提供了面向数组的方法,这些方法可以跳过并传播 NA 值。可以通过 Series 的 str 属性访问;例如,我们可以使用 str.contains 检查每个电子邮件地址是否包含 “Gmail”:
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)
print(data.str.contains("gmail"))
输出:
Dave False
Steve True
Rob True
Wes NaN
dtype: object
请注意,以上代码操作返回的对象类型是 object。pandas 具有扩展类型,这些类型提供了对字符串、整数和布尔数据的特殊处理,简单理解就是得包住输出得类型值了,以上输出有布尔值和NaN值,所有这里的对象类型输出object。
如果我们用astype()方法将data设置为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)
#print(data.str.contains("gmail"))
data_as_string_ext = data.astype('string')
print(data_as_string_ext)
print(data_as_string_ext.str.contains("gmail"))
输出:
Dave dave@google.com
Steve steve@gmail.com
Rob rob@gmail.com
Wes <NA>
dtype: string
Dave False
Steve True
Rob True
Wes <NA>
dtype: boolean
正则表达式也可以与任何 re 选项(如 IGNORECASE)一起使用 :
import re
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)
pattern = r"([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})"
a = data.str.findall(pattern, flags=re.IGNORECASE)
print(a)
输出:
Dave [(dave, google, com)]
Steve [(steve, gmail, com)]
Rob [(rob, gmail, com)]
Wes NaN
dtype: object
有几种方法可以进行矢量化元素检索。使用 str.get 或 str[index]
import re
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)
pattern = r"([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})"
matches = data.str.findall(pattern, flags=re.IGNORECASE).str[0]
print(matches)
print(matches.str.get(1))
#可以使用以下语法对字符串进行切片:
print(data.str[:5])
输出:
Dave (dave, google, com)
Steve (steve, gmail, com)
Rob (rob, gmail, com)
Wes NaN
dtype: object
Dave google
Steve gmail
Rob gmail
Wes NaN
dtype: object
Dave dave@
Steve steve
Rob rob@g
Wes NaN
dtype: object
str.extract 方法将捕获的正则表达式组作为 DataFrame 返回:
import re
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)
pattern = r"([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})"
df = data.str.extract(pattern, flags=re.IGNORECASE)
print(df)
输出:
0 | 1 | 2 | |
---|---|---|---|
Dave | dave | com | |
Steve | steve | gmail | com |
Rob | rob | gmail | com |
Wes | NaN | NaN | NaN |
Series 字符串方法的部分列表如下图: