【学习笔记】《深入浅出Pandas》第11章:Pandas文本处理

11.1 数据类型

object和StringDtype是Pandas的两个文本类型。

11.1.1 文本数据类型

默认情况下,文本数据会被推断为object类型;如果想要使用string类型,需要专门指定:

# 默认情况
# 原数据
df = pd.DataFrame({
    'A': ['a1', 'a1', 'a2', 'a2'],
    'B': ['b1', 'b2', None, 'b2'],
    'C': [1, 2, 3, 4],
    'D': [5, 6, None, 8],
    'E': [5, None, 7, 8]
})
df.dtypes
"""
A     object
B     object
C      int64
D    float64
E    float64
dtype: object
"""
# 指定数据类型
pd.Series(['a', 'b', 'c'], dtype="string") 
pd.Series(['a', 'b', 'c'], dtype=pd.StringDtype()) # 效果同上
"""
0    a
1    b
2    c
dtype: string
"""

11.1.2 类型转换

df.convert_dtype()能够智能地将数据类型转换为最新支持的合适类型,包括string类型。

# 类型转换,支持string类型
df.convert_dtypes().dtypes
# object -> string 
"""
A    string
B    string
C     Int64
D     Int64
E     Int64
dtype: object
"""

astype()也能用于类型转换。

s = pd.Series(['a', 'b', 'c'])
s.astype("object") # 转化为object
s.astype("string") # 转化为string

11.1.3 类型异同

StringDtype在操作上与object有所不同。对于StringDtype,返回数值输出的字符串访问器(.str操作)方法将始终返回可为null的整数dtype,而不是int或float dtype,具体取决于NA值的存在,返回布尔输出的方法将返回可为null的布尔数据类型。

# 数值为Int64
pd.Series(["a", None, "b"]).str.count("a") 
"""
0    1.0
1    NaN
2    0.0
dtype: float64
"""

pd.Series(["a", None, "b"], dtype="string").str.count("a")
"""
0       1
1    <NA>
2       0
dtype: Int64
"""
# 逻辑判断为boolean
pd.Series(["a", None, "b"]).str.isdigit()
"""
0    False
1     None
2    False
dtype: object
"""

pd.Series(["a", None, "b"], dtype="string").str.isdigit()
"""
0    False
1     <NA>
2    False
dtype: boolean
"""

另外,Series.str.decode()在StringDtype上不可用,因为StringArray只保存字符串,而不是字节。
最后,在比较操作中,基于StringArray的arrays.StringArray和Series将返回一个BooleanDtype对象。
对于其他方法,string和object的操作都相同。

11.2 字符的操作

11.2.1 .str访问器

.str< method >访问器可以对内容进行字符操作。

# 原数据
s = pd.Series(['A', 'Boy', 'C', np.nan], dtype="string")
# 转为小写
s.str.lower()

"""
0       a
1     boy
2       c
3    <NA>
dtype: string
"""

对于非字符类型, 可以先转换再使用:

# 转为object
df.Q1.astype(str).str

# 转为StringDtype
df.team.astype("string").str
df.Q1.astype(str).astype("string").str

大多数操作也适用于df.index、df.columns索引类型:

# 对索引进行操作
df.index.str.lower()
# 对表头、列名进行操作
df.columns.str.lower()

如果对数据连续进行字符操作,则每个操作都要使用.str方法:

# 移除字符串头尾空格&小写&替换下划线
df.columns.str.strip().str.lower().str.replace(' ', '_')

11.2.2 文本格式

以下是一些对文本的格式操作:

s.str.lower() # 转为小写
s.str.upper() # 转为大写
s.str.title() # 标题格式,每个单词大写
s.str.capitalize() # 首字母大写
s.str.swapcase() # 大小写互换
s.str.casefold() # 转为小写,支持其他语言

11.2.3 文本对齐

# 居中对齐,宽度为10,用'-'填充
s.str.center(10, fillchar='-')
# 左对齐
s.str.ljust(10, fillchar='-')
# 右对齐
s.str.rjust(10, fillchar='-')
# 指定宽度,填充内容对齐方式,填充内容
# 参数side可取值为left、right或both,默认为left
s.str.pad(width=10, side='left', fillchar='-')
# 填充对齐
s.str.zfill(3) # 生成字符,不足3位的在前面加0

11.2.4 计数和编码

s.str.count('a') # 字符串中指定字母的数量
s.str.len() # 字符串长度
s.str.encode('utf-8') # 编码
s.str.decode('utf-8') # 解码

# 字符串的Unicode普通格式
# form{'NFC', 'NFKC', 'NFD', 'NFKD'}
s.str.normalize('NFC')

11.2.5 格式判定

s.str.isalpha # 是否为字母
s.str.isnumeric # 是否为数字0~9
s.str.isalnum # 是否由字母或数字组成
s.str.isdigit # 是否为数字
s.str.isdecimal # 是否为小数
s.str.isspace # 是否为空格
s.str.islower # 是否小写
s.str.isupper # 是否大写
s.str.istitle # 是否标题格式

11.3 文本高级处理

11.3.1 文本分隔

文本分隔后会生成一个列表,可以对列表进行切片操作,容易找到想要的内容。分隔后还可以将分隔内容展开,形成单独的行。分隔对空值不起作用。

# 构造数据
s = pd.Series(['天_地_人', np.nan], dtype="string")
"""
0    天_地_人
1     <NA>
dtype: string
"""
# 用下划线分隔
s.str.split('-')
"""
0    [,,]
1         <NA>
dtype: object
"""

分隔后可以使用get或[ ]来取出相应内容,[ ]更加灵活,不仅可以取出单个内容,还可以取出由多个内容组成的片段。

# 取出每行第二个
s.str.split('_').str[1]
# get只能取出一个值
s.str.split('_').str.get(1)
"""
01    <NA>
dtype: object
"""
# []可以使用切片操作
s.str.split('_').str[1:3]
s.str.split('_').str[:-2]
# 如果不指定分隔符,按空格进行分隔
s.str.split()
# 限制分隔的次数,从左开始,剩余的不分隔
s.str.split('_', n=2)

11.3.2 字符分隔展开

在用.str.split()将数据分隔为列表后,可以传入参数expand=True,让列表共同索引位上的值在同一列,形成一个DataFrame。还可以通过参数n指定分隔索引位来控制形成几列。

# 分隔展开后形成DataFrame
s.str.split('_', expand=True)
"""
	0		1		2
0	天		地		人
1	<NA>	<NA>	<NA>
"""
# 使用n作为切片右值,指定展开列数
s.str.split('_', expand=True, n=1)
"""
	0		1
0	天_地	人
1	<NA>	<NA>
"""

rsplit和split一样,只不过它从右边开始分隔。如果没有n参数,二者输出一致。

# 从右分隔为两部分后展开为DataFrame
s.str.rsplit('_', expand=True, n=1)
"""
	0		1
0	天_地	人
1	<NA>	<NA>
"""

对于比较复杂的规则,分隔符处可以传入正则表达式:

s = pd.Series(["你和我及他"])
# 用正则表达式代表分隔位
s.str.split(r"\和|及", expand=True)
"""
	0	1	2
0	你	我	他
"""

11.3.3 文本切片选择

.str.slice()可以切除指定内容,但是s.str[ ]更推荐。

s = pd.Series(["sun", "moon", "star"])

# 以下为切除第一个字符
s.str.slice(1)
s.str.slice(start=1)

其他用法示例:

s.str.slice() # 不做任何事
s.str.slice(start=-1) # s.str[-1] 切除最后一个之前的所有字符
s.str.slice(stop=2) # s.str[:2] 切除第二位以后的所有字符
s.str.slice(step=2) # s.str[::2] 切除步长为2的所有字符
s.str.slice(start=0, stop=5, step=3) # s.str[0:5:3] 
# 切除0之前、5之后(此时不起任何作用),步长为3的字符(key)
"""
0     s
1    mn
2    sr
dtype: object
"""

11.3.4 文本划分

.str.partition()可以将文本按分隔符号划分为三个部分,形成一个全新的DataFrame或者相关数据类型。

# 构造数据
s = pd.Series(['How are you', 'What are you doing'])
# 划分为三列
s.str.partition()
"""
	0		1			2
0	How				  are you
1	What		are you doing
"""

其他操作方法如下:

# 从右开始划分
s.str.rpartition()
"""
		  	   0	1	2
0		 How are		you
1	What are you		doing
"""
# 指定字符
s.str.partition("are")
"""
	0		1		2
0	How		are		you
1	What	are		you doing
"""
# 划分为一个元组序列
s.str.partition("you", expand=False)
"""
0           (How are , you, )
1    (What are , you,  doing)
dtype: object
"""
# 对索引进行划分
idx = pd.Index(['A 123', 'B 345'])
idx.str.partition()
"""
MultiIndex([('A', ' ', '123'),
            ('B', ' ', '345')],
           )
"""

11.3.5 文本替换

.str.replace()方法可以实现替换功能。该方法有两个基本参数,第一个是旧内容,第二个是新内容。替换字符默认是支持正则表达式的,如果被替换内容是一个正则表达式,可以使regex=True关闭对正则表达式的支持。在被替换字符位还可以传入一个定义好的函数或者直接使用lambda。

另外,df.replace()和s.replace()也可以完成替换工作。

11.3.6 指定替换

str.slice_replace()可实现保留选定内容,替换剩余内容:

s = pd.Series(['ax', 'bxy', 'cxyz'])
# 保留第一个字符,其他的替换为或者追加T
s.str.slice_replace(1, repl='T') # 第一个字符之后的都替换
"""
0    aT
1    bT
2    cT
dtype: object
"""
# 第二个及第二个之前的全部替换
s.str.slice_replace(stop=2, repl='T')
"""
0      T
1     Ty
2    Tyz
dtype: object
"""
# 指定区间的内容被替换
s.str.slice_replace(start=1, stop=3, repl='T') # start=1和stop=3的并集
"""
0     aT
1     bT
2    cTz
dtype: object
"""

11.3.7 重复替换

.str.repeat(repeats)方法可以重复原有文本内容:

# 将整体重复两次
pd.Series(['a', 'b', 'c']).repeat(repeats=2)
"""
0    a
0    a
1    b
1    b
2    c
2    c
dtype: object
"""
# 将每一行的内容重复两次
pd.Series(['a', 'b', 'c']).str.repeat(repeats=2)
"""
0    aa
1    bb
2    cc
dtype: object
"""
# 指定每行重复几次
pd.Series(['a', 'b', 'c']).str.repeat(repeats=[1, 2, 3])
"""
0      a
1     bb
2    ccc
dtype: object
"""

11.3.8 文本连接

s.str.cat()能够将序列连接成一个文本或者将两个文本序列连接在一起。

# 文本序列
s = pd.Series(['x', 'y', 'z'], dtype="string")
# 默认无符号连接
s.str.cat() # 'xyz'
# 用逗号连接
s.str.cat(sep=',') # 'x,y,z'

如果序列中有空值, 会默认忽略空值,也可以用参数na_rep指定空值的占位符号:

# 包含空值的文本序列
t = pd.Series(['h', 'i', np.nan, 'k'], dtype="string")
# 用逗号连接
t.str.cat(sep=',') # 'h,i,k'
# 用连接符
t.str.cat(sep=',', na_rep='-') # 'h,i,-,k'

当然也可以使用pd.concat()来连接两个序列:

# 连接
pd.concat([s, t], axis=1) # 连接成两列

# 两次连接
s.str.cat(pd.concat([s, t], axis=1), na_rep='-')
"""
0    xxh
1    yyi
2    zz-
dtype: string
"""

连接的对齐方式:

h = pd.Series(['b', 'd', 'a'], index=[1, 0, 2], dtype="string")

# 以左边的索引为准
s.str.cat(h)
s.str.cat(t, join='left')
"""
0      axd
1     bxyb
2    cxyza
dtype: object
"""
# 以右边的索引为准
s.str.cat(h, join='right')
"""
1     bxyb
0      axd
2    cxyza
dtype: object
"""
# 其他
s.str.cat(h, join='outer', na_rep='-')
s.str.cat(h, join='inner', na_rep='-')

11.3.9 文本查询

.str.findall()可以查询文本中包括的内容:

# 字符序列
s = pd.Series(['One', 'Two', 'Three'])
# 查询字符
s.str.findall('T')
"""
0     []
1    [T]
2    [T]
dtype: object
"""
# 严格区分大小写,不会查出内容
s.str.findall('ONE')

# 忽略大小写
import re
s.str.findall('ONE', flags=re.IGNORECASE)
# 以o结尾
s.str.findall('o$')
# 包含多个的会形成一个列表
s.str.findall('e')
"""
0       [e]
1        []
2    [e, e]
dtype: object
"""

.str.find()返回匹配结果的位置(从0开始),-1为不匹配:

s.str.find('One')
"""
0    0
1   -1
2   -1
dtype: int64
"""

此外,还有.str.rfind(), 它从右开始匹配。

11.3.10 文本包含

.str.contains()会判断字符是否有包含关系,返回布尔序列,经常用于数据筛选。它默认支持正则表达式,如果不需要可以关掉。na参数指定空值的处理方式。

# 原数据
s = pd.Series(['One', 'Two', 'Three', np.NaN])
# 是否包含检测
s.str.contains('o', regex=False)
"""
0    False
1     True
2    False
3      NaN
dtype: object
"""

用于数据查询:

# 名字包含A字母
df.loc[df.name.str.contains('A')]
# 包含字母AC
df.loc[df.name.str.contains('A|C')]
# 忽略大小写
import re
df.loc[df.name.str.contains('A|C', flags=re.IGNORECASE)]
# 包含数字
df.loc[df.name.str.contains('\d')]

此外,.str.startswith()和.str.endswith()还可以指定是开头还是结尾包含:

# 原数据
s = pd.Series(['One', 'Two', 'Three', np.NaN])
s.str.startswith('O')
s.str.startswith('O', na=False) # 对空值的处理

.str.match()确定每个字符串是否与正则表达式匹配:

pd.Series(['1', '2', '3a', '3b', '03c'], dtype="string").str.match(r'[0-9][a-z]')
"""
0    False
1    False
2     True
3     True
4    False
dtype: boolean
"""

11.3.11 文本提取

.str.extract()可以利用正则表达式提取文本数据,形成单独的列。expand=True时,返回一个DataFrame;如果为假,则仅当只有一列时才会返回一个Series/Index。

# 下面的正则表达式将文本分为两部分,第一部分匹配a和b两个字母,第二部分匹配数字
# c3由于无法匹配,得到两列空值
(pd.Series(['a1', 'b2', 'c3'], dtype="string").str.extract(r'([ab])(\d)', expand=True))
"""
	0		1
0	a		1
1	b		2
2	<NA>	<NA>
"""

匹配全部,会将一个文本中所有符合规则的内容匹配出来,最终形成一个多层索引数据。

11.3.12 提取虚拟变量

.str.get_dummies()可以从字符串列中提取虚拟变量,例如用’/'分隔:

s = pd.Series(['a/b', 'b/c', np.nan, 'c'], dtype="string")
s.str.get_dummies(sep='/')
"""
	a	b	c
0	1	1	0
1	0	1	1
2	0	0	0
3	0	0	1
"""

也可以对索引进行这种操作:

s = pd.Index(['a/b', 'b/c', np.nan, 'c'], dtype="string")
idx.str.get_dummies(sep='/')
"""
MultiIndex([(1, 0),
            (0, 1)],
           names=['A 123', 'B 345'])
"""

11.4 本章小结

文本数据虽然不能参与算术运算,但文本数据具有数据维度高、数据量大且语义复杂的特点,在数据分析中需要得到重视。本章介绍了Pandas操作的文本数据类型的方法,及str访问器,大大提高了文本数据的处理效率。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值