来源:https://datawhalechina.github.io/joyful-pandas/build/html/%E7%9B%AE%E5%BD%95/ch8.html
一、str对象
定义在 Index
或 Series
上的属性,基本方法和python里的str类似。
s = pd.Series(['abcd', 'efg', 'hi'])
s.str.upper()
#使用索引
s.str[-1: 0: -2]
'''
0 db
1 g
2 i
'''
string类型(序列类型)
和object类型序列的区别:
- 对于一个可迭代对象,
string
类型的str
对象和object
类型的str
对象返回结果可能是不同的。
s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
s.str[1]
'''
0 temp_1
1 b
2 NaN
3 y
dtype: object
'''
s.astype('string').str[1]
'''
0 1
1 '
2 .
3 y
dtype: string
'''
-
string
类型是Nullable
类型,但object
不是。所以对于nullable的操作和np.nan还是不太一样。
s = pd.Series(['a', np.nan]) # 带有缺失值
操作 | 结果 |
---|---|
s.str.len() | 0 1.0 1 NaN |
s.astype('string').str.len() | 0 1 1 |
s == 'a' | 0 True 1 False |
s.astype('string') == 'a' | 0 True 1 |
二、正则表达式基础
1. 一般字符的匹配
import re
re.findall('Apple', 'Apple! This Is an Apple!')
2. 元字符基础
元字符 | 描述 |
---|---|
. | 匹配除换行符以外的任意字符 |
[ ] | 字符类,匹配方括号中包含的任意字符。 |
[^ ] | 否定字符类,匹配方括号中不包含的任意字符 |
* | 匹配前面的子表达式零次或多次 |
+ | 匹配前面的子表达式一次或多次 |
? | 匹配前面的子表达式零次或一次 |
{n,m} | 花括号,匹配前面字符至少 n 次,但是不超过 m 次 |
(xyz) | 字符组,按照确切的顺序匹配字符xyz。 |
| | 分支结构,匹配符号之前的字符或后面的字符 |
\ | 转义符,它可以还原元字符原来的含义 |
^ | 匹配行的开始 |
$ | 匹配行的结束 |
In [30]: re.findall('.', 'abc') #匹配1个除换行符以外的任意字符
Out[30]: ['a', 'b', 'c']
In [31]: re.findall('[ac]', 'abc')#匹配1个方括号中包含的任意字符
Out[31]: ['a', 'c']
In [32]: re.findall('[^ac]', 'abc')#匹配方括号中不包含的任意一个字符
Out[32]: ['b']
In [33]: re.findall('[ab]{2}', 'aaaabbbb') # {n}指匹配n次
Out[33]: ['aa', 'aa', 'bb', 'bb']
In [34]: re.findall('aaa|bbb', 'aaaabbbb')#匹配符号之前的字符或后面的字符
Out[34]: ['aaa', 'bbb']
In [35]: re.findall('a\\?|a\*', 'aa?a*a')
Out[35]: ['a?', 'a*']
In [36]: re.findall('a?.', 'abaacadaae') #匹配前面的子表达式零次或一次
Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
3. 简写字符集
等价于一组字符的集合
简写 | 描述 |
---|---|
\w | 匹配所有字母、数字、下划线: [a-zA-Z0-9_] |
\W | 匹配非字母和数字的字符: [^\w] |
\d | 匹配数字: [0-9] |
\D | 匹配非数字: [^\d] |
\s | 匹配空格符: [\t\n\f\r\p{Z}] |
\S | 匹配非空格符: [^\s] |
\B | 匹配一组非空字符开头或结尾的位置,不代表具体字符 |
三、文本处理的五类操作
操作 | 函数 | 例子 | 备注 |
---|---|---|---|
拆分 | str.split | s.str.split('[市区路]', n=2, expand=True) | 第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand |
str.rsplit | s.str.rsplit('[市区路]', n=2, expand=True) | 和split的区别在于使用 n 参数的时候是从右到左限制最大拆分次数 |
|
合并 | str.join | s.str.join('-') | 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了字符串元素则返回缺失值 |
str.cat | s1.str.cat(s2, sep='-', na_rep='?', join='outer') | 合并两个序列,主要参数为连接符 sep 、连接形式 join 以及缺失值替代符号 na_rep ,其中连接形式默认为以索引为键的左连接 |
|
匹配 | str.contains | s.str.contains('\s\wat') | 返回了每个字符串是否包含正则模式的布尔序列 |
str.startswith | s.str.startswith('my') | 不支持正则表达式 | |
str.endswith | s.str.endswith('t') | 不支持正则表达式 | |
str.match | s.str.match('m|h') 等于s.str.contains('^[m|h]') |
返回了每个字符串起始处是否符合给定正则模式的布尔序列(startswith) | |
s.str[::-1].str.match('ta[f|g]|n') 等于s.str.contains('[f|g]at|n$') |
反转后匹配,达到endswith的效果 | ||
str.find | s.str.find('apple') | 不支持正则匹配,只能用于字符子串的匹配,返回索引 | |
str.rfind | s.str.rfind('apple') | ||
str.findall | s.str.findall(pat) | ||
替换 | str.replace | s.str.replace('\d|?', 'new', regex=True) | |
提取 | str.extract | pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)' s.str.extract(pat) |
只匹配一次;通过子组的命名,可以直接对新生成 DataFrame 的列命名 |
str.extractall | s.str.extractall(pat_with_name) | 所有符合条件的模式全部匹配出来 |
命名子组:pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
?P
四、常用字符串函数
1. 字母型函数upper, lower, title, capitalize, swapcase
upper, lower, title, capitalize, swapcase
2. 数值型函数pd.to_numeric
pd.to_numeric
参数:
errors:
- raise:直接报错
- coerce:设为缺失
- ignore:保持原来的字符串
3. 统计型函数count,len
count:返回出现正则模式的次数
len :返回字符串的长度
s.str.count('[r|f]at|ee')
s.str.len()
4. 格式型函数
- 除空型
- strip:去除两侧空格
- rstrip:去除右侧空格
- lstrip:去除左侧空格
- 填充型
- pad:可以选定字符串长度、填充的方向和填充内容
- rjust, ljust, center
- zfill: 在前面补零
s.str.pad(5,'left','*') #****a
s.str.pad(5,'right','*') #a****
s.str.pad(5,'both','*') #**a**
s.str.rjust(5, '*') #****a
s.str.ljust(5, '*') #a****
s.str.center(5, '*')#**a**
s.str.zfill(6) #00000a
五、练习
Ex1:房屋信息数据集
现有一份房屋信息数据集如下:
In [114]: df = pd.read_excel('data/house_info.xls', usecols=[
.....: 'floor','year','area','price'])
.....:
In [115]: df.head(3)
Out[115]:
floor year area price
0 高层(共6层) 1986年建 58.23㎡ 155万
1 中层(共20层) 2020年建 88㎡ 155万
2 低层(共28层) 2010年建 89.33㎡ 365万
- 将
year
列改为整数年份存储。
注意:pd.to_numeric里的downcast的‘integer’ 最多只有int8,不能转换float,会自动转成float64
df.year = pd.to_numeric(df.year.str[:4]).astype('Int64')
- 将
floor
列替换为Level, Highest
两列,其中的元素分别为string
类型的层类别(高层、中层、低层)与整数类型的最高层数。
new_cols = df.floor.str.extract('(?P.+)(共(?P\d+)层)')
df = pd.concat([df, new_cols], axis=1).drop(columns='floor')
df.head(3)
'''
year area price Level Highest
0 1986 58.23㎡ 155万 高层 6
1 2020 88㎡ 155万 中层 20
2 2010 89.33㎡ 365万 低层 28
'''
- 计算房屋每平米的均价
avg_price
,以***元/平米
的格式存储到表中,其中***
为整数。
float转换为int列:().astype(int)
df.area = df.area.str[:-1]
df.price = df.price.str[:-1]
df['avg_price'] = (pd.to_numeric(df.price)/pd.to_numeric(df.area)*10000).astype(int).astype('string')+"元/平米"
df.head(3)
'''
year area price Level Highest avg_price
0 1986 58.23 155 高层 6 26618元/平米
1 2020 88 155 中层 20 17613元/平米
2 2010 89.33 365 低层 28 40859元/平米
'''
Ex2:《权力的游戏》剧本数据集
现有一份权力的游戏剧本数据集如下:
In [116]: df = pd.read_csv('data/script.csv')
In [117]: df.head(3)
Out[117]:
Release Date Season Episode Episode Title Name Sentence
0 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce What do you expect? They're savages. One lot s...
1 2011-04-17 Season 1 Episode 1 Winter is Coming will I've never seen wildlings do a thing like this...
2 2011-04-17 Season 1 Episode 1 Winter is Coming waymar royce How close did you get?
- 计算每一个
Episode
的台词条数。
df.columns = df.columns.str.strip()
df.groupby(['Season','Episode']).size()
- 以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
df['Words Number'] = df['Sentence'].str.count(' ')+1
df.groupby('Name')['Words Number'].mean().sort_values(ascending=False).head()
答案:df.set_index('Name').Sentence.str.split()之后还要重新使用str.len(),不能直接使用len()
df.set_index('Name').Sentence.str.split().str.len().groupby('Name').mean().sort_values(ascending=False).head()
- 若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有nn个问号,则认为回答者回答了nn个问题,请求出回答最多问题的前五个人。
df['question'] = df.Sentence.str.count('\?')
df['answer'] = df['question'].shift(1)
df.groupby('Name')['answer'].sum().sort_values(ascending=False).head()
答案:
s = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()