Python 数据处理(三十五)—— 文本数据处理

时间:2021-6-9 作者:qvyue

1 文本数据类型

pandas 中,存储文本主要有两种方式

  1. object 类型
  2. StringDtype 扩展类型

但一般建议使用 StringDtype 类型存储文本数据。都是由于各种原因,现在字符串数据的默认存储类型还是 object

In [1]: pd.Series(["a", "b", "c"])
Out[1]: 
0    a
1    b
2    c
dtype: object

要存储为 string 类型,需要显式的设置 dtype 参数

In [2]: pd.Series(["a", "b", "c"], dtype="string")
Out[2]: 
0    a
1    b
2    c
dtype: string

In [3]: pd.Series(["a", "b", "c"], dtype=pd.StringDtype())
Out[3]: 
0    a
1    b
2    c
dtype: string

或者在创建 SeriesDataFrame 之后,使用 astype 转换类型

In [4]: s = pd.Series(["a", "b", "c"])

In [5]: s
Out[5]: 
0    a
1    b
2    c
dtype: object

In [6]: s.astype("string")
Out[6]: 
0    a
1    b
2    c
dtype: string

也可以使用 StringDtype/"string" 转换其他非字符串类型的数据

In [7]: s = pd.Series(["a", 2, np.nan], dtype="string")

In [8]: s
Out[8]: 
0       a
1       2
2    
dtype: string

In [9]: type(s[1])
Out[9]: str

转换现有数据的类型

In [10]: s1 = pd.Series([1, 2, np.nan], dtype="Int64")

In [11]: s1
Out[11]: 
0       1
1       2
2    
dtype: Int64

In [12]: s2 = s1.astype("string")

In [13]: s2
Out[13]: 
0       1
1       2
2    
dtype: string

In [14]: type(s2[0])
Out[14]: str
1.1 行为差异

StringDtype 类型对象与 object 类型之间存在一些差异

  1. 对于 StringDtype,对于返回数值型输出字符串方法将始终返回非空的 integer 类型。而不是 intfloat 类型。对于布尔型输出方法,返回可空的布尔类型
In [15]: s = pd.Series(["a", None, "b"], dtype="string")

In [16]: s
Out[16]: 
0       a
1    
2       b
dtype: string

In [17]: s.str.count("a")
Out[17]: 
0       1
1    
2       0
dtype: Int64

In [18]: s.dropna().str.count("a")
Out[18]: 
0    1
2    0
dtype: Int64

两个结果的输出都是 Int64 类型。将其与 object 类型比较

In [19]: s2 = pd.Series(["a", None, "b"], dtype="object")

In [20]: s2.str.count("a")
Out[20]: 
0    1.0
1    NaN
2    0.0
dtype: float64

In [21]: s2.dropna().str.count("a")
Out[21]: 
0    1
2    0
dtype: int64

当存在 NA 值时,输出为 float64。类似地,对于返回布尔值的方法

In [22]: s.str.isdigit()
Out[22]: 
0    False
1     
2    False
dtype: boolean

In [23]: s.str.match("a")
Out[23]: 
0     True
1     
2    False
dtype: boolean
  1. 一些字符串方法,如 Series.str.decode()StringArray 上是不可用的。因为 StringArray 只保存字符串,而不是字节

  2. 在比较操作中,arrays.StringArrayStringArray 支持的 Series 将返回一个具有 BooleanDtype 的对象,而不是一个 bool 对象。StringArray 中的缺失值会在比较操作中传播,而不是像 numpy.nan 那样总是比较不等

2 字符串方法

SeriesIndex 有一套字符串处理方法,可以方便地对数组的每个元素进行操作,最重要的是,这些方法会自动忽略缺失值。

这些方法可以通过 str 属性访问,通常具有与内置字符串方法相匹配的名称

In [24]: s = pd.Series(
   ....:     ["A", "B", "C", "Aaba", "Baca", np.nan, "CABA", "dog", "cat"], dtype="string"
   ....: )
   ....: 

In [25]: s.str.lower()
Out[25]: 
0       a
1       b
2       c
3    aaba
4    baca
5    
6    caba
7     dog
8     cat
dtype: string

In [26]: s.str.upper()
Out[26]: 
0       A
1       B
2       C
3    AABA
4    BACA
5    
6    CABA
7     DOG
8     CAT
dtype: string

In [27]: s.str.len()
Out[27]: 
0       1
1       1
2       1
3       4
4       4
5    
6       4
7       3
8       3
dtype: Int64
In [28]: idx = pd.Index([" jack", "jill ", " jesse ", "frank"])

In [29]: idx.str.strip()
Out[29]: Index(['jack', 'jill', 'jesse', 'frank'], dtype='object')

In [30]: idx.str.lstrip()
Out[30]: Index(['jack', 'jill ', 'jesse ', 'frank'], dtype='object')

In [31]: idx.str.rstrip()
Out[31]: Index([' jack', 'jill', ' jesse', 'frank'], dtype='object')

Index 上的字符串方法对于清理或转换 DataFrame 的列特别有用。

例如,您可能有带有前导或后置空格的列

In [32]: df = pd.DataFrame(
   ....:     np.random.randn(3, 2), columns=[" Column A ", " Column B "], index=range(3)
   ....: )
   ....: 

In [33]: df
Out[33]: 
    Column A    Column B 
0    0.469112   -0.282863
1   -1.509059   -1.135632
2    1.212112   -0.173215

因为 df.columns 是一个 Index 对象,所以我们可以使用 .str 访问器

In [34]: df.columns.str.strip()
Out[34]: Index(['Column A', 'Column B'], dtype='object')

In [35]: df.columns.str.lower()
Out[35]: Index([' column a ', ' column b '], dtype='object')

我们可以根据需要对列名进行处理,然后重新设置列名。

例如,我们删除列名的前后空格,并将其改为小写字母,同时用 _ 替换剩余的空格

In [36]: df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_")

In [37]: df
Out[37]: 
   column_a  column_b
0  0.469112 -0.282863
1 -1.509059 -1.135632
2  1.212112 -0.173215

3 切割和替换字符串

split 方法会返回一个值为 listSeries

In [38]: s2 = pd.Series(["a_b_c", "c_d_e", np.nan, "f_g_h"], dtype="string")

In [39]: s2.str.split("_")
Out[39]: 
0    [a, b, c]
1    [c, d, e]
2         
3    [f, g, h]
dtype: object

可以使用 get[] 访问拆分后的列表中的元素

In [40]: s2.str.split("_").str.get(1)
Out[40]: 
0       b
1       d
2    
3       g
dtype: object

In [41]: s2.str.split("_").str[1]
Out[41]: 
0       b
1       d
2    
3       g
dtype: object

更简单的方法是设置 expand 参数,返回一个 DataFrame

In [42]: s2.str.split("_", expand=True)
Out[42]: 
      0     1     2
0     a     b     c
1     c     d     e
2  
3     f     g     h

当原来的 Series 包含 StringDtype 类型的数据时,输出列也将全部为 StringDtype

当然,也可以设置切割次数

In [43]: s2.str.split("_", expand=True, n=1)
Out[43]: 
      0     1
0     a   b_c
1     c   d_e
2  
3     f   g_h

它还有个对应的 rsplit 方法,从右边起始对字符串进行拆分

In [44]: s2.str.rsplit("_", expand=True, n=1)
Out[44]: 
      0     1
0   a_b     c
1   c_d     e
2  
3   f_g     h

replace 参数支持使用正则表达式,前两个参数是 pat(匹配模式) 和 repl(替换字符串)

In [45]: s3 = pd.Series(
   ....:     ["A", "B", "C", "Aaba", "Baca", "", np.nan, "CABA", "dog", "cat"],
   ....:     dtype="string",
   ....: )
   ....: 

In [46]: s3
Out[46]: 
0       A
1       B
2       C
3    Aaba
4    Baca
5        
6    
7    CABA
8     dog
9     cat
dtype: string

In [47]: s3.str.replace("^.a|dog", "XX-XX ", case=False, regex=True)
Out[47]: 
0           A
1           B
2           C
3    XX-XX ba
4    XX-XX ca
5            
6        
7    XX-XX BA
8      XX-XX 
9     XX-XX t
dtype: string

如果只是想要替换字符串字面值,可以将 regex 参数设置为 False,而不需要对每个特殊字符进行转义。此时 patrepl 参数必须是字符串

In [48]: dollars = pd.Series(["12", "-$10", "$10,000"], dtype="string")

# These lines are equivalent
In [49]: dollars.str.replace(r"-$", "-", regex=True)
Out[49]: 
0         12
1        -10
2    $10,000
dtype: string

In [50]: dollars.str.replace("-$", "-", regex=False)
Out[50]: 
0         12
1        -10
2    $10,000
dtype: string

此外,replace 方法还接受一个可调用的替换函数,会使用 re.sub() 方法在每个匹配的模式上调用该函数

该函数需要传入一个正则对象作为位置参数,并返回一个字符串。例如

# 反转每个小写单词的顺序
In [51]: pat = r"[a-z]+"

In [52]: def repl(m):
   ....:     return m.group(0)[::-1]
   ....: 

In [53]: pd.Series(["foo 123", "bar baz", np.nan], dtype="string").str.replace(
   ....:     pat, repl, regex=True
   ....: )
   ....: 
Out[53]: 
0    oof 123
1    rab zab
2       
dtype: string

# 使用 regex 捕获分组
In [54]: pat = r"(?Pw+) (?Pw+) (?Pw+)"

In [55]: def repl(m):
   ....:     return m.group("two").swapcase()
   ....: 

In [56]: pd.Series(["Foo Bar Baz", np.nan], dtype="string").str.replace(
   ....:     pat, repl, regex=True
   ....: )
   ....: 
Out[56]: 
0     bAR
1    
dtype: string

replace 方法的 pat 参数还接受 re.compile() 编译的正则表达式对象。所有的 flags 需要在编译正则对象时设置

In [57]: import re

In [58]: regex_pat = re.compile(r"^.a|dog", flags=re.IGNORECASE)

In [59]: s3.str.replace(regex_pat, "XX-XX ", regex=True)
Out[59]: 
0           A
1           B
2           C
3    XX-XX ba
4    XX-XX ca
5            
6        
7    XX-XX BA
8      XX-XX 
9     XX-XX t
dtype: string

如果在 replace 中设置 flags 参数,则会抛出异常

In [60]: s3.str.replace(regex_pat, 'XX-XX ', flags=re.IGNORECASE)
---------------------------------------------------------------------------
ValueError: case and flags cannot be set when pat is a compiled regex

4 连接

有几种方法可以将一个 SeriesIndex 与自己或其他的 SeriesIndex 相连接,所有这些方法都是基于 cat() 方法

4.1 将单个 Series 对象连接成字符串

可以连接一个 SeriesIndex 的内容

In [61]: s = pd.Series(["a", "b", "c", "d"], dtype="string")

In [62]: s.str.cat(sep=",")
Out[62]: 'a,b,c,d'

如果未指定 sep 参数,则默认为空字符串

In [63]: s.str.cat()
Out[63]: 'abcd'

默认会跳过缺失值,也可以使用 na_rep 指定缺失值的表示方式

In [64]: t = pd.Series(["a", "b", np.nan, "d"], dtype="string")

In [65]: t.str.cat(sep=",")
Out[65]: 'a,b,d'

In [66]: t.str.cat(sep=",", na_rep="-")
Out[66]: 'a,b,-,d'
4.2 连接 Series 与一个类似列表的对象

cat() 的第一个参数 others 可以是类似列表的对象,但是其长度需要和调用对象一致

In [67]: s.str.cat(["A", "B", "C", "D"])
Out[67]: 
0    aA
1    bB
2    cC
3    dD
dtype: string

只要两个对象中存在缺失值,对应的结果中也是缺失值,除非指定了 na_rep

In [68]: s.str.cat(t)
Out[68]: 
0      aa
1      bb
2    
3      dd
dtype: string

In [69]: s.str.cat(t, na_rep="-")
Out[69]: 
0    aa
1    bb
2    c-
3    dd
dtype: string
4.3 连接 Series 与多个类似列表的对象

others 参数也可以是二维的,但是得保证其行数必须与调用的对象一致

In [70]: d = pd.concat([t, s], axis=1)

In [71]: s
Out[71]: 
0    a
1    b
2    c
3    d
dtype: string

In [72]: d
Out[72]: 
      0  1
0     a  a
1     b  b
2    c
3     d  d

In [73]: s.str.cat(d, na_rep="-")
Out[73]: 
0    aaa
1    bbb
2    c-c
3    ddd
dtype: string
4.4 连接一个 Series 和一个带索引的对象

对于 SeriesDataFrame 的连接,可以通过设置 join 参数指定对齐方式

In [74]: u = pd.Series(["b", "d", "a", "c"], index=[1, 3, 0, 2], dtype="string")

In [75]: s
Out[75]: 
0    a
1    b
2    c
3    d
dtype: string

In [76]: u
Out[76]: 
1    b
3    d
0    a
2    c
dtype: string

In [77]: s.str.cat(u)
Out[77]: 
0    aa
1    bb
2    cc
3    dd
dtype: string

In [78]: s.str.cat(u, join="left")
Out[78]: 
0    aa
1    bb
2    cc
3    dd
dtype: string

通常 join 可选范围为: 'left', 'outer', 'inner', 'right'。此时,不再要求两个对象长度一致

In [79]: v = pd.Series(["z", "a", "b", "d", "e"], index=[-1, 0, 1, 3, 4], dtype="string")

In [80]: s
Out[80]: 
0    a
1    b
2    c
3    d
dtype: string

In [81]: v
Out[81]: 
-1    z
 0    a
 1    b
 3    d
 4    e
dtype: string

In [82]: s.str.cat(v, join="left", na_rep="-")
Out[82]: 
0    aa
1    bb
2    c-
3    dd
dtype: string

In [83]: s.str.cat(v, join="outer", na_rep="-")
Out[83]: 
-1    -z
 0    aa
 1    bb
 2    c-
 3    dd
 4    -e
dtype: string

others 参数是 DataFrame 时,也可以使用

In [84]: f = d.loc[[3, 2, 1, 0], :]

In [85]: s
Out[85]: 
0    a
1    b
2    c
3    d
dtype: string

In [86]: f
Out[86]: 
      0  1
3     d  d
2    c
1     b  b
0     a  a

In [87]: s.str.cat(f, join="left", na_rep="-")
Out[87]: 
0    aaa
1    bbb
2    c-c
3    ddd
dtype: string
4.5 连接 Series 和多个对象

可以将一些类似数组的对象(如 SeriesIndex 等)放在一个类似列表的容器中,然后传递给 cat

In [88]: s
Out[88]: 
0    a
1    b
2    c
3    d
dtype: string

In [89]: u
Out[89]: 
1    b
3    d
0    a
2    c
dtype: string

In [90]: s.str.cat([u, u.to_numpy()], join="left")
Out[90]: 
0    aab
1    bbd
2    cca
3    ddc
dtype: string

对于没有索引的对象,其长度必须与调用 cat 的对象相同。但是 SeriesIndex 可以是任意的,除非设置了 json=None

In [91]: v
Out[91]: 
-1    z
 0    a
 1    b
 3    d
 4    e
dtype: string

In [92]: s.str.cat([v, u, u.to_numpy()], join="outer", na_rep="-")
Out[92]: 
-1    -z--
 0    aaab
 1    bbbd
 2    c-ca
 3    dddc
 4    -e--
dtype: string

如果在 others 参数上包含不同索引的对象,且设置了 join='right',则最后的结果将会是这些索引的并集

In [93]: u.loc[[3]]
Out[93]: 
3    d
dtype: string

In [94]: v.loc[[-1, 0]]
Out[94]: 
-1    z
 0    a
dtype: string

In [95]: s.str.cat([u.loc[[3]], v.loc[[-1, 0]]], join="right", na_rep="-")
Out[95]: 
-1    --z
 0    a-a
 3    dd-
dtype: string
声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。