str
对象是定义在Index
或Series
上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其str
对象。在Python标准库中也有str
模块,为了使用上的便利,有许多函数的用法pandas
照搬了它的设计,例如字母转为大写的操作:
#python的str对象,str模块
var='abcd'
str.upper(var)
#Series的str对象
s=pd.Series(['abcd','efg','hi'])
s.str
s.str.upper()
根据文档API
材料,在pandas
的50个str
对象方法中,有31个是和标准库中的str
模块方法同名且功能一致,这为批量处理序列提供了有力的工具。
对于str
对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过[]
可以取出某个位置的元素:
var[0]
var[-1:0:-2]
s.str[0]
s.str[-1:0:-2]
s.str[2]
应当尽量保证每一个序列中的值都是字符串的情况下才使用str
属性
必要条件是序列中至少有一个可迭代(Iterable)对象,包括但不限于字符串、字典、列表。对于一个可迭代对象,string
类型的str
对象和object
类型的str
对象返回结果可能是不同的
s=pd.Series([{1:'temp_1',2:'temp_2'},['a','b'],0.5,'my_string'])
s.str[1]
[out]"
0 temp_1
1 b
2 NaN #不可迭代对象
3 y
dtype: object
s.astype('string').str[1]
当序列类型为object
时,是对于每一个元素进行[]
索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行[]
索引。而string
类型的str
对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和object
类型一致。
string
类型是Nullable
类型,但object
不是。这意味着string
类型的序列,
如果调用的str
方法返回值为整数Series
和布尔Series
时,其分别对应的dtype
是Int
和boolean
的Nullable
类型
而object
类型则会分别返回int/float
和bool/object
,取决于缺失值的存在与否。同时,字符串的比较操作,也具有相似的特性,string
返回Nullable
类型,但object
不会
s=pd.Series(['a'])
s.str.len()#int
s.astype('string').str.len()#Int
s=='a'
s.astype('string')=='a'#bool
s=pd.Series(['a',np.nan])#boolean
s.str.len()
s.astype('string').str.len()
s=='a'
s.astype('string')=='a'
最后需要注意的是,对于全体元素为数值类型的序列,即使其类型为object
或者category
也不允许直接使用str
属性。如果需要把数字当成string
类型处理,可以使用astype
强制转换为string
类型的Series
:
s=pd.Series([12,345,6789])
s.astype('string').str[1]
# 0 2
# 1 4
# 2 7
# dtype: string
元字符 | 描述 |
---|---|
. | 匹配除换行符以外的任意字符 |
[ ] | 字符类,匹配方括号中包含的任意字符 |
[^ ] | 否定字符类,匹配方括号中不包含的任意字符 |
* | 匹配前面的子表达式零次或多次 |
+ | 匹配前面的子表达式一次或多次 |
? | 匹配前面的子表达式零次或一次 |
{n,m} | 花括号,匹配前面字符至少 n 次,但是不超过 m 次 |
(xyz) | 字符组,按照确切的顺序匹配字符xyz |
| | 分支结构,匹配符号之前的字符或后面的字符 |
\ | 转义符,它可以还原元字符原来的含义 |
^ | 匹配行的开始 |
$ | 匹配行的结束 |
#正则表达式
import re
re.findall(r'.','Apple! This is an Apple!')
re.findall(r'.','abc')
re.findall(r'[ac]','abc')
re.findall(r'[^ac]','abc')
re.findall(r'[ab]{2}','aaaabbbb')
#{n}指匹配n次 aa,ab,bb
re.findall(r'aaa|bbb','aaaabbbb')
re.findall(r'a\\?|a\*','aa?a*a')
re.findall(r'a?.','abaacadaae')
此外,正则表达式中还有一类简写字符集,其等价于一组字符的集合:
简写 | 描述 |
---|---|
\w | 匹配所有字母、数字、下划线: [a-zA-Z0-9_] |
\W | 匹配非字母和数字的字符: [^\w] |
\d | 匹配数字: [0-9] |
\D | 匹配非数字: [^\d] |
\s | 匹配空格符: [\t\n\f\r\p{Z}] |
\S | 匹配非空格符: [^\s] |
\B | 匹配一组非空字符开头或结尾的位置,不代表具体字符 |
re.findall(r'.\s','Apple! This Is an Apple!')
#匹配空格符前面的字符
re.findall(r'\s','Apple! This Is an Apple!')
#匹配空格符
re.findall(r'\w{2}','09 8? 7w c_ 9q p@')
#匹配两次字母数字下划线
#匹配字母|数字|下划线,非字母|数字|下划线,非空字符开头或结尾的位置
re.findall(r'\w\W\B','09 8? 7w c_ 9q p@')
#匹配上海市,匹配区至少两次不超过3次 匹配数字加号
re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)', '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
str.split
能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数n
,是否展开为多个列expand
。
与其类似的函数是str.rsplit
,其区别在于使用n
参数的时候是从右到左限制最大拆分次数。但是当前版本下rsplit
因为bug
而无法使用正则表达式进行分割:
s = pd.Series(['上海市黄浦区方浜中路249号', '上海市宝山区密山路5号'])
s.str.split('[市区路]')
s.str.split('[市区路]', n=2, expand=True)
s.str.rsplit('[市区路]',n=2,expand=True)
关于合并一共有两个函数,分别是str.join
和str.cat
。
str.join
表示用某个连接符把Series
中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值
str.cat
用于合并两个序列,主要参数为连接符sep
、连接形式join
以及缺失值替代符号na_rep
,其中连接形式默认为以索引为键的左连接。
s=pd.Series([['a','b'],[1,'a'],[['a','b'],'c']])
s.str.join('-')
s.str.cat(s2,sep='-')
s2.index=[1,2]
s1.str.cat(s2,sep='-',na_rep='?',join='outer')
str.contains
返回了每个字符串是否包含正则模式的布尔序列
s = pd.Series(['my cat', 'he is fat', 'railway station'])
s.str.contains('\s\wat')
#匹配空格,字母,at
str.startwith和str.endswith返回每个字符串以给定模式为开始和结束的布尔序列
不支持正则表达式
s.str.startswith('my')
s.str.endswith('t')
使用正则表达式检测开始和结束字符串的模式,用str.match()
,也可以在str.contains的正则中使用^
和$
来实现:
#从开头开始匹配
s.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$')
返回索引的匹配函数
s=pd.Series(['This is an apple.That is not an apple.'])
s.str.find('apple')
s.str.rfind('apple')
str.replace
和replace
不是一个函数,在使用字符串替换的时候应当使用前者
s=pd.Series(['a_1_b','c_?'])
s.str.replace('\d|\?','new',regex=True)
当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容)
import numpy as np
import pandas as pd
s=pd.Series(['上海市黄浦区方滨中路249号','上海市宝山区密山路5号','北京市昌平区北农路2号'])
pat='(\w+市)(\w+区)(\w+路)(\w+号)'
city={'上海市':'Shanghai','北京市':'Beijing'}
district={'昌平区':'CP District','黄浦区':'HP District','宝山区':'BS District'}
road={'方滨中路':'Mid Fangbin Road','密山路':'Mishan Road','北农路':'Beinong Road'}
#group(k)代表匹配到的第k个子组
def my_func(m):
str_city=city[m.group(1)]
str_district=district[m.group(2)]
str_road=road[m.group(3)]
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)
str.split
例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract
进行提取
pat='(\w+市)(\w+区)(\w+路)(\w+号)'
s.str.extract(pat)
pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
s.str.extract(pat)
#extractall:
#把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储
s=pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
str.extractall()
str.findall
的功能类似于str.extractall
str.findall:结果存入列表
str.extractall:处理为多级索引,每个行值对应一组匹配,不是把所有匹配组合构成列表
pat_with_name='[A|B](?P\d+)[T|S](?P\d+)'
s.str.extractall(pat_with_name)
s.str.findall(pat)
extractall:处理成多级索引
findall:结果存入列表
upper,lower,title,capitalize,swapcase
将字母大小写转化
s=pd.Series(['lower','CAPITALS','this is a sentence','SwApCaSe'])
s.str.upper()
s.str.lower()
s.str.title()
s.str.capitalize()
s.str.swapcase()
pd.to_numeric()
#对字符格式的数值进行快速转换和筛选
参数:errors:非数值的处理方式
downcast:转换类型
errors:不能转换为数值的有
'raise:直接报错
coerce:设为缺失
ignore:保持原来的字符串'
s=pd.Series(['1','2.2','2e','??','-2.1','0'])
pd.to_numeric(s,errors='ignore')
#忽略错误
pd.to_numeric(s,errors='coerce')
#将错误设置成缺失值
#数据清洗的时候可以利用coerce的设定,快速查看非数值的行
s[pd.to_numeric(s,errors='coerce').isna()]
count
和len
的作用分别是返回出现正则模式的次数和字符串的长度:
#count
#统计型函数
s=pd.Series(['cat rat fat at','get feed sheet heat'])
s.str.count('[r|f]at|ee')
s.str.len()#计算长度
格式型函数主要分为两类,
第一种是除空型,第二种是填充型。
#除空型函数
my_Index=pd.Index([' col1','col2 ',' col3 '])
my_Index.str.strip().str.len()
#444
#去除空格之后统计长度
my_Index.str.rstrip().str.len()
#545
my_Index.str.lstrip().str.len()
#455
第一类函数一共有三种,它们分别是strip, rstrip, lstrip
,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。
#填充型函数
s=pd.Series(['a','b','c'])
s.str.pad(5,'left','*')
#左边填充使得字符串长度为5
#****a
#pad函数可以选定字符串长度,填充的方向,填充的内容
s.str.pad(5,'right','*')
#a****
s.str.pad(5,'both','*')
#**ab*
#*aba*
#三种情况可以分别用rjust,ljust,center来等效完成
#ljust是指右侧填充
s.str.rjust(5,'*')#左侧填充
s.str.ljust(5,'*')#右侧填充
s.str.center(5,'*')#中心填充
#读取excel文件的时候,经常会出现数字前补零的需求
s=pd.Series([7,155,303000]).astype('string')
#在左侧补零
s.str.pad(6,'left','0')
s.str.rjust(6,'0')
s.str.zfill(6)
现有一份房屋信息数据集如下:
df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])
df.head(3)
df=pd.read_excel('../data/house_info.xls',usecols=['floor','year','area','price'])
df.head(3)
#问题1.1将year列改为整数年份存储。
a=df.year.head(3)
pat_year='([0-9]{4})'
print("各列的缺失信息",df.isna().sum()/df.shape[0])
df1=df.fillna(value='0000')
#1. 使用提取函数
year_temp=df1.year.str.extract(pat_year).astype('Int64')
df.year=year_temp
df.head()
#2.使用去除函数
df=pd.read_excel('../data/house_info.xls',usecols=['floor','year','area','price'])
df.head(3)
year=df.year.str.rstrip("年建")
year=pd.to_numeric(year,errors='coerce').astype('Int64')
year.fillna(value='0000')
df.year=year
3.计算房屋每平米的均价avg_price,以**
元/平米的格式存储到表中,其中***
为整数
#计算房屋每平米的均价avg_price,
#以***元/平米的格式存储到表中,其中***为整数。
df1=df.copy()
df1['area']=pd.to_numeric(df1['area'].str[:-1],errors='coerce').astype('float64')
df1['price']=pd.to_numeric(df1['price'].str[:-1],errors='coerce').astype('float64')
df1['avg_price']=((10000*df1['price']/df1['area']).astype('int')).astype('string')+"元/平米"
df1.head()
现有一份权力的游戏剧本数据集如下:
df = pd.read_csv('../data/script.csv')
df.head(3)
计算每一个Episode
的台词条数。
以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
df['word_count']=df['Sentence'].str.count('\s')+1
#句子空格数目+1=句子单词数
actor_word=df.groupby(['Name'])['word_count'].mean().reset_index().sort_values('word_count',ascending=False)
actor_word.head(5)
#构造回答者
#将name列向上移动一位,最后一位用NAN表示
#shift将回答者放到问题上,最后一位没有问题NAN
s=pd.Series(df.Sentence.values,index=df.Name.shift(-1))
# periods = -1(默认为1),将Name列向上移动一位,最后一位用NaN填充
s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()
#数