Pandas - 数据清洗 Pt.3

这篇随笔主要介绍如何利用 Pandas 对数据进行清洗
Pt.1 部分主要介绍利用 Pandas 处理缺失数据
Pt.2 部分详细介绍利用 Pandas 处理重复数据、替换数据和划分数据
Pt.3 部分主要介绍利用 Pandas 与 正则表达式的结合

1
2
import pandas as pd
import numpy as np

随机采样

np.random.permutation( x )

1
2
3
df = pd.DataFrame(np.arange(5 * 4).reshape((5, 4)))
sampler = np.random.permutation(5)
df.iloc[sampler]
0123
416171819
312131415
2891011
00123
14567

obj.sample( n )

1
2
df = pd.DataFrame(np.arange(5 * 4).reshape((5, 4)))
df.sample(n=5)
0123
312131415
2891011
00123
14567
416171819

将 分类变量 转换为 向量变量

pd.get_dummies( series, prefix )

1
2
3
df = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'b'],
'data': range(6)})
df
keydata
0b0
1b1
2a2
3c3
4a4
5b5
1
pd.get_dummies(df['key'])
abc
0010
1010
2100
3001
4100
5010

prefix : 给指标 DataFrame 的列加上一个前缀

1
pd.get_dummies(df['key'], prefix='key')
key_akey_bkey_c
0010
1010
2100
3001
4100
5010
1
pd.get_dummies(df['key'], prefix='key').join(df['data'])
key_akey_bkey_cdata
00100
10101
21002
30013
41004
50105

字符串的处理方法

str.split( sep, maxsplit ) : 根据 sep 拆分字符串, str → list

1
2
val = ' a ,b, guido '
val.split(',')
1
[' a ', 'b', ' guido ']

str.strip( ) : 去除字符串两边空白符(包括换行符)

str.lstrip( ), str.rstrip( ) : 去除字符串 左或右 的空白符(包括换行符)

1
[x.strip() for x in val.split(',')]
1
['a', 'b', 'guido']

sep.join( list ) : 去除字符串首尾空白符(包括换行符)

1
'::'.join( val.split(',') )
1
' a ::b:: guido '

str.index( sep ), str.find( sep ) : 返回 sep 在 str 中第一次出现的位置

区别 : 如果 sep 在 str 中不存在, sep.find 返回 -1 , sep.index 会引发异常

1
val.index(',')
1
3
1
val.find(':')
1
-1

str.rfind( sep ) : 返回 sep 在 str 中最后一次出现的位置

1
val.rfind(',')
1
5

str.count( sep ) : 返回 sep 在 str 中出现的次数

1
val.count(',')
1
2

str.replace( old, new ) : 替换

1
val.replace(',', '::')
1
' a ::b:: guido '

str.endswith( sep ), str.startswith( sep ) : 判断 str 是否以 sep 结尾或开始

1
val.strip().endswith('a')
1
False
1
val.strip().startswith('a')
1
True

str.lower( ), str.upper( ), str.title( ) : 控制大小写

1
val.title(), val.upper(), val.lower()
1
(' A ,B, Guido ', ' A ,B, GUIDO ', ' a ,b, guido ')

正则表达式

1
import re

re.split( pattern, str ) : 根据 sep 拆分字符串( str 中的分隔符 sep 数量不定 )

1
2
text = "foo   bar\t baz \tqux"
re.split('\s+', text) # 描述一个或多个空白符的正则表达式是'\s+'
1
['foo', 'bar', 'baz', 'qux']

re.compile( pattern ) : 根据 pattern 返回一个正则表达式类( regex )的对象

1
2
regex = re.compile('\s+')
regex.split(text)
1
['foo', 'bar', 'baz', 'qux']

regex.split( str ) : 根据 regex 拆分字符串

re.findall( pattern, str ), regex.findall( str ) : 返回字符串中的正则表达式匹配项

1
regex.findall(text)
1
['   ', '\t ', ' \t']
1
2
3
4
5
6
7
8
9
text = """WANG wangbj27@mail2.sysu.edu.cn
Dave dave@google.com
Steve steve@gmail.com
Rob rob@gmail.com
Ryan ryan@yahoo.com"""
pattern = r'[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}'
# \.[A-Z]{2,4} : 必须以 '.[A-Z]'结尾, 并且[A-Z]的字符数为2~4个
regex = re.compile(pattern, flags=re.IGNORECASE)
regex.findall(text)
1
2
3
4
5
['wangbj27@mail2.sysu.edu.cn',
'dave@google.com',
'steve@gmail.com',
'rob@gmail.com',
'ryan@yahoo.com']

re.finditer( pattern, str ), regex.finditer( str ) : 以迭代器的形式返回字符串中的正则表达式匹配项

1
2
for x in regex.finditer(text):
print(x.group())
1
2
3
4
5
wangbj27@mail2.sysu.edu.cn
dave@google.com
steve@gmail.com
rob@gmail.com
ryan@yahoo.com

re.sub( pattern, repl, str ), regex.sub( repl, str ) : 替换字符串中的正则表达式匹配项

1
print(regex.sub(repl='E-mail', string=text))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
WANG E-mail
Dave E-mail
Steve E-mail
Rob E-mail
Ryan E-mail
···
### 正则表达式的分组模式

#### pattern : r' **(** [A-Z0-9._%+-]+ **)** @ **(** [A-Z0-9.-]+ **)** \. **(** [A-Z]{2,4} **)** '

```python
pattern = r'([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})'
regex = re.compile(pattern, flags=re.IGNORECASE)
regex.findall(text)
1
2
3
4
5
[('wangbj27', 'mail2.sysu.edu', 'cn'),
('dave', 'google', 'com'),
('steve', 'gmail', 'com'),
('rob', 'gmail', 'com'),
('ryan', 'yahoo', 'com')]

sub 还能通过 \1\2 之类的特殊符号访问各匹配项中的分组, 符号 \1 对应第一个匹配的组

1
print(regex.sub(r'Username: \1, Domain: \2, Suffix: \3', text))
1
2
3
4
5
WANG Username: wangbj27, Domain: mail2.sysu.edu, Suffix: cn
Dave Username: dave, Domain: google, Suffix: com
Steve Username: steve, Domain: gmail, Suffix: com
Rob Username: rob, Domain: gmail, Suffix: com
Ryan Username: ryan, Domain: yahoo, Suffix: com

pandas 的矢量化字符串方法 obj.str.func( ... )

将字符串的方法应用于 series 的各个单元里去

series.str.contains( pattern ) : 检查各行是否含有字符串 string

1
2
3
4
data = {'Dave': 'dave@google.com', 'Steve': 'steve@gmail.com', 
'Rob': 'rob@gmail.com', 'Wes': np.nan}
data = pd.Series(data)
data
1
2
3
4
5
Dave     dave@google.com
Steve steve@gmail.com
Rob rob@gmail.com
Wes NaN
dtype: object
1
data.str.contains('gmail')
1
2
3
4
5
Dave     False
Steve True
Rob True
Wes NaN
dtype: object

series.str.findall( pattern, flags )

1
2
pattern = r'([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})'
data.str.findall(pattern, flags=re.IGNORECASE)
1
2
3
4
5
Dave     [(dave, google, com)]
Steve [(steve, gmail, com)]
Rob [(rob, gmail, com)]
Wes NaN
dtype: object

series.str.match( pattern, flags )

1
2
pattern = r'([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})'
data.str.match(pattern, flags=re.IGNORECASE)
1
2
3
4
5
Dave     True
Steve True
Rob True
Wes NaN
dtype: object

series.str.get( i ), series.str.slice( start, stop ), series.str[ start : stop ] : 切片

1
data.str.get(0)
1
2
3
4
5
Dave       d
Steve s
Rob r
Wes NaN
dtype: object
1
data.str[:5]
1
2
3
4
5
Dave     dave@
Steve steve
Rob rob@g
Wes NaN
dtype: object

series.str.len( )

1
data.str.len()
1
2
3
4
5
Dave     15.0
Steve 15.0
Rob 13.0
Wes NaN
dtype: float64

series1.str.cat( series2, sep ) : 根据索引实现元素级字符串连接

1
2
name = pd.Series({'Dave':'Dave', 'Steve':'Steve', 'Rob':'Rob', 'Wes':'Wes'})
name.str.cat(data, '----')
1
2
3
4
5
Dave      Dave----dave@google.com
Steve Steve----steve@gmail.com
Rob Rob----rob@gmail.com
Wes NaN
dtype: object

series.str.len( )
series.str.lower( ), series.str.upper( ), series.str.title( )
series.str.strip( ), series.str.lstrip( ), series.str.rstrip( ) : 去除两边/左/右的空格

series.str.endswith( sep ), series.str.startswith( sep )
series.str.find( sep ), series.str.rfind( sep ) : 返回 sep 在字符串中的位置
series.str.count( sep ) : 计数 sep 在字符串中出现的次数
series.str.split( sep ) : 根据分隔符 sep 对字符串进行划分, str→list
series.str.join( sep ) : 利用分隔符 sep 将 list 连接起来, liat→str
series.str.replace( old, new ) : 替换

series1.str.cat( series2, sep ) : 根据索引实现元素级字符串连接

... ...