第7章 文本数据

第7章 文本数据

import pandas as pd
import numpy as np

一、string类型的性质

1. string与object的区别

string类型和object不同之处有三:
① 字符存取方法(string accessor methods,如str.count)会返回相应数据的Nullable类型,而object会随缺失值的存在而改变返回类型
② 某些Series方法不能在string上使用,例如: Series.str.decode(),因为存储的是字符串而不是字节
③ string类型在缺失值存储或运算时,类型会广播为pd.NA,而不是浮点型np.nan
其余全部内容在当前版本下完全一致,但迎合Pandas的发展模式,我们仍然全部用string来操作字符串

2. 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')
0     1
1    1.
dtype: string
pd.Series([1,2]).astype('str').astype('string')
0    1
1    2
dtype: string
pd.Series([True,False]).astype('str').astype('string')
0     True
1    False
dtype: string

二、拆分与拼接

1. str.split方法

(a)分割符与str的位置元素选取
s = pd.Series(['a_b_c', 'c_d_e', np.nan, 'f_g_h'], dtype="string")
s
0    a_b_c
1    c_d_e
2     <NA>
3    f_g_h
dtype: string
根据某一个元素分割,默认为空格
s.str.split('_')
0    [a, b, c]
1    [c, d, e]
2         <NA>
3    [f, g, h]
dtype: object
这里需要注意split后的类型是object,因为现在Series中的元素已经不是string,而包含了list,且string类型只能含有字符串
对于str方法可以进行元素的选择,如果该单元格元素是列表,那么str[i]表示取出第i个元素,如果是单个元素,则先把元素转为列表在取出
s.str.split('_').str[1]
0       b
1       d
2    <NA>
3       g
dtype: object
pd.Series(['a_b_c', ['a','b','c']], dtype="object").str[1]
#第一个元素先转为['a','_','b','_','c']
0    _
1    b
dtype: object
(b)其他参数
expand参数控制了是否将列拆开,n参数代表最多分割多少次
s.str.split('_',expand=True)
012
0abc
1cde
2<NA><NA><NA>
3fgh
s.str.split('_',n=1)
0    [a, b_c]
1    [c, d_e]
2        <NA>
3    [f, g_h]
dtype: object
s.str.split('_',expand=True,n=1)
01
0ab_c
1cd_e
2<NA><NA>
3fg_h

2. str.cat方法

(a)不同对象的拼接模式
cat方法对于不同对象的作用结果并不相同,其中的对象包括:单列、双列、多列
① 对于单个Series而言,就是指所有的元素进行字符合并为一个字符串
s = pd.Series(['ab',None,'d'],dtype='string')
s
0      ab
1    <NA>
2       d
dtype: string
s.str.cat()
'abd'
其中可选sep分隔符参数,和缺失值替代字符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')
s2
0      24
1    <NA>
2    <NA>
dtype: string
s.str.cat(s2)
0    ab24
1    <NA>
2    <NA>
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         <NA>
2        dd0dd
dtype: string
(b)cat中的索引对齐
当前版本中,如果两边合并的索引不相同且未指定join参数,默认为左连接,设置join=‘left’
s2 = pd.Series(list('abc'),index=[1,2,3],dtype='string')
s2
1    a
2    b
3    c
dtype: string
s.str.cat(s2,na_rep='*')
0    ab*
1     *a
2     db
dtype: string

三、替换

广义上的替换,就是指str.replace函数的应用,fillna是针对缺失值的替换,上一章已经提及
提到替换,就不可避免地接触到正则表达式,这里默认读者已掌握常见正则表达式知识点,若对其还不了解的,可以通过这份资料来熟悉

1. str.replace的常见用法

s = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca','', np.nan, 'CABA', 'dog', 'cat'],dtype="string")
s
0       A
1       B
2       C
3    Aaba
4    Baca
5        
6    <NA>
7    CABA
8     dog
9     cat
dtype: string
第一个值写r开头的正则表达式,后一个写替换的字符串
s.str.replace(r'^[AB]','***')
0       ***
1       ***
2         C
3    ***aba
4    ***aca
5          
6      <NA>
7      CABA
8       dog
9       cat
dtype: string

2. 子组与函数替换

通过正整数调用子组(0返回字符本身,从1开始才是子组)
s.str.replace(r'([ABC])(\w+)',lambda x:x.group(2)[1:]+'*')
0       A
1       B
2       C
3     ba*
4     ca*
5        
6    <NA>
7     BA*
8     dog
9     cat
dtype: string
利用?P<…>表达式可以对子组命名调用
s.str.replace(r'(?P<one>[ABC])(?P<two>\w+)',lambda x:x.group('two')[1:]+'*')
0       A
1       B
2       C
3     ba*
4     ca*
5        
6    <NA>
7     BA*
8     dog
9     cat
dtype: string

3. 关于str.replace的注意事项

首先,要明确str.replace和replace并不是一个东西:
str.replace针对的是object类型或string类型,默认是以正则表达式为操作,目前暂时不支持DataFrame上使用
replace针对的是任意类型的序列或数据框,如果要以正则表达式替换,需要设置regex=True,该方法通过字典可支持多列替换
但现在由于string类型的初步引入,用法上出现了一些问题,这些issue有望在以后的版本中修复
(a)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) #报错
此时,可以先转为object类型再转换回来,曲线救国:
pd.Series(['A','B'],dtype='string').astype('O').replace(r'[A]',pd.NA,regex=True).astype('string')
0    <NA>
1       B
dtype: string
至于为什么不用replace函数的regex替换(但string类型replace的非正则替换是可以的),原因在下面一条
(b)对于string类型Series,在使用replace函数时不能使用正则表达式替换
该bug现在还未修复
pd.Series(['A','B'],dtype='string').replace(r'[A]','C',regex=True)
0    A
1    B
dtype: string
pd.Series(['A','B'],dtype='O').replace(r'[A]','C',regex=True)
0    C
1    B
dtype: object
(c)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    <NA>
dtype: string
综上,概况的说,除非需要赋值元素为缺失值(转为object再转回来),否则请使用str.replace方法

四、子串匹配与提取

1. str.extract方法

(a)常见用法
pd.Series(['10-87', '10-88', '10-89'],dtype="string").str.extract(r'([\d]{2})-([\d]{2})')
01
01087
11088
21089
使用子组名作为列名
pd.Series(['10-87', '10-88', '-89'],dtype="string").str.extract(r'(?P<name_1>[\d]{2})-(?P<name_2>[\d]{2})')
name_1name_2
01087
11088
2<NA><NA>
利用?正则标记选择部分提取
pd.Series(['10-87', '10-88', '-89'],dtype="string").str.extract(r'(?P<name_1>[\d]{2})?-(?P<name_2>[\d]{2})')
name_1name_2
01087
11088
2<NA>89
pd.Series(['10-87', '10-88', '10-'],dtype="string").str.extract(r'(?P<name_1>[\d]{2})-(?P<name_2>[\d]{2})?')
name_1name_2
01087
11088
210<NA>
(b)expand参数(默认为True)
对于一个子组的Series,如果expand设置为False,则返回Series,若大于一个子组,则expand参数无效,全部返回DataFrame
对于一个子组的Index,如果expand设置为False,则返回提取后的Index,若大于一个子组且expand为False,报错
s = pd.Series(["a1", "b2", "c3"], ["A11", "B22", "C33"], dtype="string")
s.index
Index(['A11', 'B22', 'C33'], dtype='object')
s.str.extract(r'([\w])')
0
A11a
B22b
C33c
s.str.extract(r'([\w])',expand=False)
A11    a
B22    b
C33    c
dtype: string
s.index.str.extract(r'([\w])')
0
0A
1B
2C
s.index.str.extract(r'([\w])',expand=False)
Index(['A', 'B', 'C'], dtype='object')
s.index.str.extract(r'([\w])([\d])')
01
0A1
1B2
2C3
#s.index.str.extract(r'([\w])([\d])',expand=False) #报错

2. str.extractall方法

与extract只匹配第一个符合条件的表达式不同,extractall会找出所有符合条件的字符串,并建立多级索引(即使只找到一个)
s = pd.Series(["a1a2", "b1", "c1"], index=["A", "B", "C"],dtype="string")
two_groups = '(?P<letter>[a-z])(?P<digit>[0-9])'
s.str.extract(two_groups, expand=True)
letterdigit
Aa1
Bb1
Cc1
s.str.extractall(two_groups)
letterdigit
match
A0a1
1a2
B0b1
C0c1
s['A']='a1'
s.str.extractall(two_groups)
letterdigit
match
A0a1
B0b1
C0c1
如果想查看第i层匹配,可使用xs方法
s = pd.Series(["a1a2", "b1b2", "c1c2"], index=["A", "B", "C"],dtype="string")
s.str.extractall(two_groups).xs(1,level='match')
letterdigit
Aa2
Bb2
Cc2

3. str.contains和str.match

前者的作用为检测是否包含某种正则模式
pd.Series(['1', None, '3a', '3b', '03c'], dtype="string").str.contains(r'[0-9][a-z]')
0    False
1     <NA>
2     True
3     True
4     True
dtype: boolean
可选参数为na
pd.Series(['1', None, '3a', '3b', '03c'], dtype="string").str.contains('a', na=False)
0    False
1    False
2     True
3    False
4    False
dtype: boolean
str.match与其区别在于,match依赖于python的re.match,检测内容为是否从头开始包含该正则模式
pd.Series(['1', None, '3a_', '3b', '03c'], dtype="string").str.match(r'[0-9][a-z]',na=False)
0    False
1    False
2     True
3     True
4    False
dtype: boolean
pd.Series(['1', None, '_3a', '3b', '03c'], dtype="string").str.match(r'[0-9][a-z]',na=False)
0    False
1    False
2    False
3     True
4    False
dtype: boolean

五、常用字符串方法

1. 过滤型方法

(a)str.strip
常用于过滤空格
pd.Series(list('abc'),index=[' space1  ','space2  ','  space3'],dtype="string").index.str.strip()
Index(['space1', 'space2', 'space3'], dtype='object')
(b)str.lower和str.upper
pd.Series('A',dtype="string").str.lower()
0    a
dtype: string
pd.Series('a',dtype="string").str.upper()
0    A
dtype: string
(c)str.swapcase和str.capitalize
分别表示交换字母大小写和大写首字母
pd.Series('abCD',dtype="string").str.swapcase()
0    ABcd
dtype: string
pd.Series('abCD',dtype="string").str.capitalize()
0    Abcd
dtype: string

2. isnumeric方法

检查每一位是否都是数字,请问如何判断是否是数值?(问题二)
pd.Series(['1.2','1','-0.3','a',np.nan],dtype="string").str.isnumeric()
0    False
1     True
2    False
3    False
4     <NA>
dtype: boolean

六、问题与练习

1. 问题

【问题一】 str对象方法和df/Series对象方法有什么区别?

string类型和object不同之处有三:
① 字符存取方法(string accessor methods,如str.count)会返回相应数据的Nullable类型,而object会随缺失值的存在而改变返回类型
② 某些Series方法不能在string上使用,例如: Series.str.decode(),因为存储的是字符串而不是字节
③ string类型在缺失值存储或运算时,类型会广播为pd.NA,而不是浮点型np.nan
其余全部内容在当前版本下完全一致,但迎合Pandas的发展模式,我们仍然全部用string来操作字符串

【问题二】 给出一列string类型,如何判断单元格是否是数值型数据?

s.str.isnumeric()

【问题三】 rsplit方法的作用是什么?它在什么场合下适用?

按照一定规则拆分某列字符串,split后的类型是object

【问题四】 在本章的第二到第四节分别介绍了字符串类型的5类操作,请思考它们各自应用于什么场景?

2.字符串拆分和拼接
- str.split:对字符串进行拆分
- str.cat:不同对象进行拼接
3.str.replace:默认以正则表达式进行替换
4.子串匹配与提取
- str.extract方法:匹配和提取字符串
- str.extractall方法:找出所有符合条件的字符串,并建立多级索引(即使只找到一个)
- str.contains:检测是否包含某种正则模式
- str.match:依赖于python的re.match,检测内容为是否从头开始包含该正则模式

2. 练习

【练习一】 现有一份关于字符串的数据集,请解决以下问题:
data = pd.read_csv('data/String_data_one.csv',index_col='人员编号')
data.head()
姓名国籍性别出生年出生月出生日
人员编号
1aesfd21942810
2fasefa51985104
3aeagd419461015
4aef41999513
5eaf12010624
(a)现对字符串编码存储人员信息(在编号后添加ID列),使用如下格式:“×××(名字):×国人,性别×,生于×年×月×日”
ID = data['姓名']+':'+data['国籍'].apply(str)+'国人,性别'+data['性别'].apply(str)+',生于'+data['出生年'].apply(str)+'年'+data['出生月'].apply(str)+'月'+data['出生日'].apply(str)+'日'
data.insert(0,'ID',ID)
data.head()
ID姓名国籍性别出生年出生月出生日
人员编号
1aesfd:2国人,性别男,生于1942年8月10日aesfd21942810
2fasefa:5国人,性别女,生于1985年10月4日fasefa51985104
3aeagd:4国人,性别女,生于1946年10月15日aeagd419461015
4aef:4国人,性别男,生于1999年5月13日aef41999513
5eaf:1国人,性别女,生于2010年6月24日eaf12010624
(b)将(a)中的人员生日信息部分修改为用中文表示(如一九七四年十月二十三日),其余返回格式不变。
import re
num_dict={"0":u"零","1":u"一","2":u"二","3":u"三","4":u"四","5":u"五","6":u"六","7":u"七","8":u"八","9":u"九"}
data.loc[:,'ID'] = data['ID'].str.replace(r'(生于)(\d*)(年)',lambda x:x.group(1)+''.join(map(lambda x: num_dict[x],list(x.group(2))))+x.group(3))
data.loc[:,'ID'] = data['ID'].str.replace(r'(年)(\d*)(\d)(月)',lambda x:x.group(1)+\
                                          ('十' if len(x.group(2))>0 else'')+(''if x.group(3)==str(0)else num_dict[x.group(3)])+x.group(4))
data.loc[:,'ID'] = data['ID'].str.replace(r'(月)(\d*)(\d)(日)',lambda x:x.group(1)+\
                                          (num_dict[x.group(2)] if len(x.group(2))>0 and int(x.group(2))>1 else'')+('十' if len(x.group(2))>0 else'')+(''if x.group(3)==str(0)else num_dict[x.group(3)])+x.group(4))
data.head()
ID姓名国籍性别出生年出生月出生日
人员编号
1aesfd:2国人,性别男,生于一九四二年八月十日aesfd21942810
2fasefa:5国人,性别女,生于一九八五年十月四日fasefa51985104
3aeagd:4国人,性别女,生于一九四六年十月十五日aeagd419461015
4aef:4国人,性别男,生于一九九九年五月十三日aef41999513
5eaf:1国人,性别女,生于二零一零年六月二十四日eaf12010624
data.index
Int64Index([   1,    2,    3,    4,    5,    6,    7,    8,    9,   10,
            ...
            1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000],
           dtype='int64', name='人员编号', length=2000)
(c)将(b)中的ID列结果拆分为原列表相应的5列,并使用equals检验是否一致。
name = data['ID'].str.split(':',expand=True)
country = name.iloc[:,-1].str.split('国人,性别',expand=True)
gender = country.iloc[:,-1].str.split(',生于',expand=True)
year = gender.iloc[:,-1].str.split('年',expand=True)
month = year.iloc[:,-1].str.split('月',expand=True)
day = month.iloc[:,-1].str.split('日',expand=True)
pd.DataFrame({'姓名':name.iloc[:,0],'国籍':country.iloc[:,0],'性别':gender.iloc[:,0],'出生年':year.iloc[:,0],'出生月':month.iloc[:,0],'出生日':day.iloc[:,0],})
姓名国籍性别出生年出生月出生日
人员编号
1aesfd2一九四二
2fasefa5一九八五
3aeagd4一九四六十五
4aef4一九九九十三
5eaf1二零一零二十四
6faefdf1一九四二二十一
7daf4一九五七十一
8faefdf3一九九八
9fs3一九五七十四
10faefdffa3一九八八十五
11arege4一九三三十二
12ase3一九四一十八
13fasf3一九八七
14gfa5一九六七
15asf3一九九零二十
16eter4二零零一十三
17esf4一九八七二十八
18srgdsrg5一九九三十二
19aefase4一九三五
20szf1一九三六
21eae2一九三四二十八
22dsrgdrg4一九六八
23ggase4一九九四
24hgdrg1一九九一二十七
25fef3一九四五十一
26zsgdrhdth4一九四一十六
27gfe4一九七七二十六
28esfa4一九九八
29efaeg3一九三四十八
30fasef1一九八零二十四
.....................
1971fse2一九八六
1972asef5一九九三十二十七
1973aesfaes2二零零九十四
1974fesf4一九五二十五
1975se3一九八五十一
1976asef4一九五二
1977fas2一九三七十四
1978zgzd5一九七七十六
1979aesfa4二零一零十八
1980awe1一九六九十九
1981gzg3一九五八二十三
1982fsf3一九六九十一十六
1983sg2二零一五二十七
1984gfdg4一九八七
1985fg2一九八零
1986rgd4二零一五十二十五
1987zes5二零一一十二二十六
1988hxr1一九三五二十七
1989dxrg3一九四八十二二十
1990xdr3一九八三
1991gzseg2一九七零二十三
1992efs3二零零九
1993gx1一九九三二十四
1994rdxg2一九九七
1995hx1一九七七
1996sdf5一九八四十七
1997hx1一九四三十六
1998drg5二零一八
1999zfgzdrg5二零零五
2000fsdf3一九六二二十三

2000 rows × 6 columns

【练习二】 现有一份半虚拟的数据集,第一列包含了新型冠状病毒的一些新闻标题,请解决以下问题:
(a)选出所有关于北京市和上海市新闻标题的所在行。
(b)求col2的均值。
(c)求col3的均值。
data1 = pd.read_csv('data/String_data_two.csv')
data1.head()
col1col2col3
0鄂尔多斯市第2例确诊患者治愈出院19363.6923
1云南新增2例,累计124例-67-152.281
2武汉协和医院14名感染医护出院-86325.6221
3山东新增9例,累计307例-74-204.9313
4上海开学日期延至3月-954.05
data2 = data1[data1['col1'].map(lambda x:'北京'in x or '上海' in x)]
data2.head()
col1col2col3
4上海开学日期延至3月-954.05
5北京新增25例确诊病例,累计确诊253例-4-289.1719
6上海新增10例,累计243例2-73.7105
36上海新增14例累计233例-55-83
40上海新增14例累计233例-88-99
data2['col2'].map(eval).mean()
-20.152173913043477
data2.iloc[:,-1].map(eval).mean()
-6.998510869565218
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值