Pandas基础(七):文本数据的类型、拆分、拼接、替换、匹配、提取等方法
DataWhale第十四期组队学习:Joyful-Pandas
Pandas文本数据的内容包括
在Pandas中,表示文本数据一般有object和string两种,在第六章缺失数据中提到过,string为Nullable类型,统一多种缺失值的相关操作。
string和object的不同之处主要有三点
迎合Pandas的发展模式,建议全部用string来操作字符串
若是将其他类型直接转换为string类型会出错
#pd.Series([1,'1.']).astype('string') #报错
#pd.Series([1,2]).astype('string') #报错
#pd.Series([True,False]).astype('string') #报错
正确的方法如下所示,先转为str型object,再转为string类型
pd.Series([1,'1.']).astype('str').astype('string')
pd.Series([1,2]).astype('str').astype('string')
pd.Series([True,False]).astype('str').astype('string')
# 按照指定符号进行分割
# 分割完之后类型会变为object
# 元素由a_b_c变为[a,b,c],即变为列表,缺失值不变,为
s = pd.Series(['a_b_c', 'c_d_e', np.nan, 'f_g_h'], dtype="string")
s.str.split('_')
'''
0 [a, b, c]
1 [c, d, e]
2
3 [f, g, h]
dtype: object
'''
# 由于新的Series是列表,所以可以进行元素选择
s.str.split('_').str[1]
'''
0 b
1 d
2
3 g
dtype: object
'''
# 特殊情况,非单一元素
#第一个元素先转为['a','_','b','_','c']
pd.Series(['a_b_c', ['a','b','c']], dtype="object").str[1]
'''
0 _
1 b
dtype: object
'''
# str.split的相关参数
# expand参数控制了是否将列拆开
s.str.split('_',expand=True) # 拆开后组成的是Dataframe
'''
0 1 2
0 a b c
1 c d e
2
3 f g h
'''
# n参数代表最多分割多少次
s.str.split('_',n=1)
'''
0 [a, b_c]
1 [c, d_e]
2
3 [f, g_h]
dtype: object
'''
# 也可以组合起来用
s.str.split('_',expand=True,n=1)
单列拼接
s = pd.Series(['ab',None,'d'],dtype='string')
s.str.cat() # abd
# 可选参数
# 可以选择分隔符参数,和缺失值替代字符na_rep参数
s.str.cat(sep=',') # ab,d
s.str.cat(sep=',',na_rep='*') # ab,*,d
双列拼接
对于两个Series合并而言,是对应索引的元素进行合并
s2 = pd.Series(['24',None,None],dtype='string')
s.str.cat(s2)
'''
0 ab24
1
2
dtype: string
'''
# 可选参数
# 需要注意的是两个缺失值会被同时替换
s.str.cat(s2,sep=',',na_rep='*')
'''
0 ab,24
1 *,*
2 d,*
dtype: string
'''
多列拼接
多列拼接可以分为表的拼接和多Series的拼接
# 表的拼接
s.str.cat(pd.DataFrame({
0:['1','3','5'],
1:['5','b',None]},
dtype='string'),
na_rep='*')
'''
0 ab15
1 *3b
2 d5*
dtype: string
'''
# 多个Series拼接
s.str.cat([s+'0',s*2])
'''
0 abab0abab
1
2 dd0dd
dtype: string
'''
索引对齐
s2 = pd.Series(list('abc'),index=[1,2,3],dtype='string')
s.str.cat(s2,na_rep='*')
'''
0 ab*
1 *a
2 db
dtype: string
'''
s = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca','',
np.nan, 'CABA', 'dog', 'cat'],dtype="string")
s.str.replace(r'^[AB]','***') # 用***替换A或B开头的字符串中A或B的内容
通过正整数调用子组(0返回字符本身,1代表第一个括号内子组,2同理)
s.str.replace(r'([ABC])(\w+)',lambda x:x.group(2)[0:]+'*')
# 也可以利用?P<…>表达式给子组命名调用
s.str.replace(r'(?P[ABC])(?P\w+)' ,
lambda x:x.group('two')[1:]+'*')
首先,要明确str.replace和replace并不是一个东西
str.replace针对的是object类型或string类型,默认是以正则表达式为操作,目前暂时不支持DataFrame上使用
replace针对的是任意类型的序列或数据框,如果要以正则表达式替换,需要设置regex=True,该方法通过字典可支持多列替换
但现在由于string类型的初步引入,用法上出现了一些问题,这些issue有望在以后的版本中修复
str.replace赋值参数不得为pd.NA
# pd.Series(['A','B'],dtype='string').str.replace(r'[A]',pd.NA) #报错
# pd.Series(['A','B'],dtype='O').str.replace(r'[A]',pd.NA) #报错
# 这时候就需要先转个obiect再转回来
pd.Series(['A','B'],dtype='string').astype('O').replace(r'[A]',pd.NA,regex=True).astype('string')
对于string类型Series,在使用replace函数时不能使用正则表达式替换(bug待修复)
string类型序列如果存在缺失值,不能使用replace替换
#pd.Series(['A',np.nan],dtype='string').replace('A','B') #报错
pd.Series(['A',np.nan],dtype='string').str.replace('A','B')
'''
0 B
1
dtype: string
'''
pd.Series(['10-87', '10-88', '-89'],dtype="string").str.extract(r'(?P[\d]{2})?-(?P[\d]{2})' )pd.Series(['10-87', '10-88', '10-89'],dtype="string").str.extract(r'([\d]{2})-([\d]{2})')
'''
0 1
0 10 87
1 10 88
2 10 89
'''
# 也可以设定组的名字
pd.Series(['10-87', '10-88', '-89'],dtype="string").str.extract(r'(?P[\d]{2})-(?P[\d]{2})' )
'''
name_1 name_2
0 10 87
1 10 88
2
'''
# 利用?正则标记选择部分提取
pd.Series(['10-87', '10-88', '-89'],dtype="string").str.extract(r'(?P[\d]{2})?-(?P[\d]{2})' )
expand参数(默认为True)
对于一个子组的Series,如果expand设置为False,则返回Series,若大于一个子组,则expand参数无效,全部返回DataFrame
s = pd.Series(["a1", "b2", "c3"], ["A11", "B22", "C33"], dtype="string")
s.str.extract(r'([\w])') # 返回DataFrame
s.str.extract(r'([\w])',expand=False) # 返回Series
s.index.str.extract(r'([\w])([\d])') # 返回DataFrame
# s.index.str.extract(r'([\w])([\d])',expand=False) #报错
与extract只匹配第一个符合条件的表达式不同,extractall会找出所有符合条件的字符串,并建立多级索引(即使只找到一个)
s = pd.Series(["a1a2", "b1", "c1"], index=["A", "B", "C"],dtype="string")
two_groups = '(?P[a-z])(?P[0-9])'
s.str.extractall(two_groups)
letter | digit | ||
---|---|---|---|
match | |||
A | 0 | a | 1 |
1 | a | 2 | |
B | 0 | b | 1 |
C | 0 | c | 1 |
如果想查看第i层匹配,可使用xs方法
s = pd.Series(["a1a2", "b1b2", "c1c2"],
index=["A", "B", "C"],dtype="string")
s.str.extractall(two_groups).xs(1,level='match')
'''
letter digit
A a 2
B b 2
C c 2
'''
# str.contains用于检测是否包含某种正则模式
pd.Series(['1', None, '3a', '3b', '03c'],
dtype="string").str.contains(r'[0-9][a-z]')
# 可选参数为na,把None从判定为False
pd.Series(['1', None, '3a', '3b', '03c'],
dtype="string").str.contains('a', na=False)
# str.match与其区别在于,match依赖于python的re.match
# 检测内容为是否从头开始包含该正则模式
# 3、4为True
pd.Series(['1', None, '3a_', '3b', '03c'],
dtype="string").str.match(r'[0-9][a-z]',na=False)
# 4为True
pd.Series(['1', None, '_3a', '3b', '03c'],
dtype="string").str.match(r'[0-9][a-z]',na=False)
# str.strip 去除两侧空格
pd.Series(list('abc'),index=[' space1 ','space2 ',' space3'],
dtype="string").index.str.strip()
# str.;ower和str.upper大小写控制
pd.Series('A',dtype="string").str.lower()
pd.Series('a',dtype="string").str.upper()
# str.swapcase和str.capitalize
# 交换字母大小写
pd.Series('abCD',dtype="string").str.swapcase()
# 大写首字母
pd.Series('abCD',dtype="string").str.capitalize()
这里的数字应该指的是纯数字
# 检查每一位是否都是数字
pd.Series(['1.2','1','-0.3','a',np.nan],dtype="string").str.isnumeric()
'''
0 False
1 True
2 False
3 False
4
dtype: boolean
'''
我的理解是,就像是一些语言中需要单独设置string类一样,可能是为了在做一些字符串操作的时候可以更加方便,比如查找字符串中是否包含特定字符串,或者判断字符串长度是否为多少小于多少等等。
使用内置的str对象方法可能在做一些字符处理的时候效率更高也更有针对性,而对象方法的对象应该是Series和DataFrame,而不是每一列中的string
我想的是利用正则表达式来进行判断,但是自己觉得不是合理解答,健壮性不强
str.contains(r'([+-]?)(\d+)(\.)?(\d+)?')
同样是对字符串进行分割,但是返回的是一个列表,且从字符串末尾进行分割
可以删除什么数据的特定尾缀?比如保留邮箱名字,那就以@为分隔符,取列表0索引
拆分方法:应该比较常用,用于提取数据中有用的部分,或者是按照一定的格式输入的数据进行有效提取
拼接方法:输出的时候用得到?把相关信息拼接在一起构成一句话然后做成DataFrame表
替换方法:那应该就是需要替换数据的时候使用了
匹配方法:应该是查看数据中是否有满足所需正则模式的数据吧
提取方法:从教程中来看不知道是不是比较适合一些数字类的数据,用于分割这类数据,不太了解
pd.read_csv('data/String_data_one.csv',index_col='人员编号').head()
姓名 | 国籍 | 性别 | 出生年 | 出生月 | 出生日 | |
---|---|---|---|---|---|---|
人员编号 | ||||||
1 | aesfd | 2 | 男 | 1942 | 8 | 10 |
2 | fasefa | 5 | 女 | 1985 | 10 | 4 |
3 | aeagd | 4 | 女 | 1946 | 10 | 15 |
4 | aef | 4 | 男 | 1999 | 5 | 13 |
5 | eaf | 1 | 女 | 2010 | 6 | 24 |
(a)现对字符串编码存储人员信息(在编号后添加ID列),使用如下格式:“×××(名字):×国人,性别×,生于×年×月×日”
先改个数据类型,再就可以正常拼接在一起了
q1 = pd.read_csv('data/String_data_one.csv',index_col='人员编号').astype('str').astype('string')
(q1['姓名'] + ':' + q1['国籍'] + '国人,性别' +
q1['性别'] + ',生于' + q1['出生年'] + '年'
+ q1['出生月'] + '月' + q1['出生日']+'日').to_frame().rename(columns={
0:'ID'}).head()
(b)将(a)中的人员生日信息部分修改为用中文表示(如一九七四年十月二十三日),其余返回格式不变
L_year = list('零一二三四五六七八九')
L_one = [s.strip() for s in list(' 二三四五六七八九')]
L_two = [s.strip() for s in list(' 一二三四五六七八九')]
q1_new = (q1['姓名']+':'+q1['国籍']+'国人,性别'+q1['性别']+',生于'
+q1['出生年'].str.replace(r'\d',lambda x:L_year[int(x.group(0))])+'年'
+q1['出生月'].apply(lambda x:x if len(x)==2 else '0'+x)\
.str.replace(r'(?P[\d])(?P\d?)' ,lambda x:L_one[int(x.group('one'))]
+bool(int(x.group('one')))*'十'+L_two[int(x.group('two'))])+'月'
+q1['出生日'].apply(lambda x:x if len(x)==2 else '0'+x)\
.str.replace(r'(?P[\d])(?P\d?)' ,lambda x:L_one[int(x.group('one'))]
+bool(int(x.group('one')))*'十'+L_two[int(x.group('two'))])+'日')\
.to_frame().rename(columns={
0:'ID'})
q1_new.head()
(c)将(b)中的ID列结果拆分为原列表相应的5列,并使用equals检验是否一致
dic_year = {
i[0]:i[1] for i in zip(list('零一二三四五六七八九'),list('0123456789'))}
dic_two = {
i[0]:i[1] for i in zip(list('十一二三四五六七八九'),list('0123456789'))}
dic_one = {
'十':'1','二十':'2','三十':'3',None:''}
q1_res = q1_new['ID'].str.extract(r'(?P<姓名>[a-zA-Z]+):(?P<国籍>[\d])国人,性别(?P<性别>[\w]),生于(?P<出生年>[\w]{4})年(?P<出生月>[\w]+)月(?P<出生日>[\w]+)日')
q1_res['出生年'] = q1_res['出生年'].str.replace(r'(\w)+',lambda x:''.join([dic_year[x.group(0)[i]] for i in range(4)]))
q1_res['出生月'] = q1_res['出生月'].str.replace(r'(?P\w?十)?(?P[\w])' ,lambda x:dic_one[x.group('one')]+dic_two[x.group('two')]).str.replace(r'0','10')
q1_res['出生日'] = q1_res['出生日'].str.replace(r'(?P\w?十)?(?P[\w])' ,lambda x:dic_one[x.group('one')]+dic_two[x.group('two')]).str.replace(r'^0','10')
q1_res.head()
pd.read_csv('data/String_data_two.csv').head()
col1 | col2 | col3 | |
---|---|---|---|
0 | 鄂尔多斯市第2例确诊患者治愈出院 | 19 | 363.6923 |
1 | 云南新增2例,累计124例 | -67 | -152.281 |
2 | 武汉协和医院14名感染医护出院 | -86 | 325.6221 |
3 | 山东新增9例,累计307例 | -74 | -204.9313 |
4 | 上海开学日期延至3月 | -95 | 4.05 |
(a)选出所有关于北京市和上海市新闻标题的所在行
q2[q2['col1'].str.contains(r'[北京]{2}|[上海]{2}')].head()
(b)求col2的均值
q2.loc[[309,396,485],'col2'] = [0,9,7]
q2['col2'].astype('int').mean()
(c)求col3的均值
q2.loc[[28,122,332],'col3 '] = [355.3567, 9056.2253, 3534.6554]
q2['col3 '].astype('float').mean()