import pandas as pd
import numpy as np
from numpy import nan as NA
string_data = pd.Series(['aardvark', 'artichoke', NA, 'avocado'])
string_data
0 aardvark
1 artichoke
2 NaN
3 avocado
dtype: object
string_data.isnull()
0 False 1 False 2 True 3 False dtype: bool 这里有一些用来处理缺失值的函数: ![](http://oydgk2hgw.bkt.clouddn.com/pydata-book/zq0q8.png) ### 1、Filtering Out Missing Data(过滤缺失值) 有一些方法来过滤缺失值。可以使用pandas.isnull和boolean indexing, 配合使用dropna。对于series,只会返回non-null数据和index values:
# 直接过滤缺失值
data = pd.Series([1, NA, 3.5, NA, 7])
data.dropna()
0 1.0
2 3.5
4 7.0
dtype: float64
df = pd.DataFrame(np.random.randn(7, 3))
df
0 | 1 | 2 | |
---|---|---|---|
0 | -0.029785 | 0.110282 | -1.577633 |
1 | -1.489474 | -1.467383 | 0.006396 |
2 | -1.105860 | 0.476798 | -0.400001 |
3 | -1.573019 | 0.147722 | 0.250568 |
4 | -0.848726 | 1.002656 | 0.568422 |
5 | 1.457525 | 0.411282 | -0.257635 |
6 | 2.828644 | -1.237598 | -0.532588 |
df.iloc[:4, 1] = NA
df.iloc[:2, 2] = NA
df
0 | 1 | 2 | |
---|---|---|---|
0 | -0.029785 | NaN | NaN |
1 | -1.489474 | NaN | NaN |
2 | -1.105860 | NaN | -0.400001 |
3 | -1.573019 | NaN | 0.250568 |
4 | -0.848726 | 1.002656 | 0.568422 |
5 | 1.457525 | 0.411282 | -0.257635 |
6 | 2.828644 | -1.237598 | -0.532588 |
# 会删除行或列
df.dropna()
0 | 1 | 2 | |
---|---|---|---|
4 | -0.848726 | 1.002656 | 0.568422 |
5 | 1.457525 | 0.411282 | -0.257635 |
6 | 2.828644 | -1.237598 | -0.532588 |
df.dropna(thresh=2)
0 | 1 | 2 | |
---|---|---|---|
2 | -1.105860 | NaN | -0.400001 |
3 | -1.573019 | NaN | 0.250568 |
4 | -0.848726 | 1.002656 | 0.568422 |
5 | 1.457525 | 0.411282 | -0.257635 |
6 | 2.828644 | -1.237598 | -0.532588 |
在处理缺失值的时候,一些情况下我们会直接用dropna来把缺失值删除,但另一些情况下,我们希望用一些固定的值来代替缺失值,而fillna就是用来做这个的,例如,这里我们用平均值mean来代替缺失值NA
- fillna返回一个新对象,但你可以使用in-place来直接更改原有的数据
# 全部填充0
df.fillna(0)
0 | 1 | 2 | |
---|---|---|---|
0 | -0.029785 | 0.000000 | 0.000000 |
1 | -1.489474 | 0.000000 | 0.000000 |
2 | -1.105860 | 0.000000 | -0.400001 |
3 | -1.573019 | 0.000000 | 0.250568 |
4 | -0.848726 | 1.002656 | 0.568422 |
5 | 1.457525 | 0.411282 | -0.257635 |
6 | 2.828644 | -1.237598 | -0.532588 |
# 给fillna传入一个dict,可以给不同列替换不同的值
df.fillna({1: 0.5, 2: 0})
0 | 1 | 2 | |
---|---|---|---|
0 | -0.029785 | 0.500000 | 0.000000 |
1 | -1.489474 | 0.500000 | 0.000000 |
2 | -1.105860 | 0.500000 | -0.400001 |
3 | -1.573019 | 0.500000 | 0.250568 |
4 | -0.848726 | 1.002656 | 0.568422 |
5 | 1.457525 | 0.411282 | -0.257635 |
6 | 2.828644 | -1.237598 | -0.532588 |
下面是fillna的一些参数:
states = ['Ohio', 'New York', 'Vermont', 'Florida',
'Oregon', 'Nevada', 'California', 'Idaho']
data = pd.Series(np.random.randn(8), index=states)
data[['Vermont', 'Nevada', 'Idaho']] = np.nan
data
Ohio 0.760579
New York 0.729209
Vermont NaN
Florida -2.195910
Oregon -0.658126
Nevada NaN
California -1.851410
Idaho NaN
dtype: float64
group_key = ['East'] * 4 + ['West'] * 4
data.groupby(group_key).mean()
East -0.235374
West -1.254768
dtype: float64
然后我们可以用每个组的平均值来填充NA:
fill_mean = lambda g: g.fillna(g.mean())
data.groupby(group_key).apply(fill_mean)
Ohio 0.760579
New York 0.729209
Vermont -0.235374
Florida -2.195910
Oregon -0.658126
Nevada -1.254768
California -1.851410
Idaho -1.254768
dtype: float64
在另外一些情况下,我们可能希望提前设定好用于不同组的填充值。因为group有一个name属性,我们可以利用这个:
fill_values = {'East': 0.5, 'West': -1}
fill_func = lambda g: g.fillna(fill_values[g.name])
data.groupby(group_key).apply(fill_func)
Ohio 0.760579
New York 0.729209
Vermont 0.500000
Florida -2.195910
Oregon -0.658126
Nevada -1.000000
California -1.851410
Idaho -1.000000
dtype: float64
data = pd.DataFrame({'k1': ['one', 'two'] * 3 + ['two'],
'k2': [1, 1, 2, 3, 3, 4, 4]})
data
k1 | k2 | |
---|---|---|
0 | one | 1 |
1 | two | 1 |
2 | one | 2 |
3 | two | 3 |
4 | one | 3 |
5 | two | 4 |
6 | two | 4 |
# 判断是否有重复值
data.duplicated()
0 False
1 False
2 False
3 False
4 False
5 False
6 True
dtype: bool
# 删除重复值,只考虑k2列
data.drop_duplicates(['k2'])
k1 | k2 | |
---|---|---|
0 | one | 1 |
2 | one | 2 |
3 | two | 3 |
5 | two | 4 |
data = pd.DataFrame({'food': ['bacon', 'pulled pork', 'bacon',
'Pastrami', 'corned beef', 'Bacon',
'pastrami', 'honey ham', 'nova lox'],
'ounces': [4, 3, 12, 6, 7.5, 8, 3, 5, 6]})
data
food | ounces | |
---|---|---|
0 | bacon | 4.0 |
1 | pulled pork | 3.0 |
2 | bacon | 12.0 |
3 | Pastrami | 6.0 |
4 | corned beef | 7.5 |
5 | Bacon | 8.0 |
6 | pastrami | 3.0 |
7 | honey ham | 5.0 |
8 | nova lox | 6.0 |
# 大写字母转为小写字母
lowercased = data['food'].str.lower() # upper() 小写字母转为大写字母
lowercased
0 bacon
1 pulled pork
2 bacon
3 pastrami
4 corned beef
5 bacon
6 pastrami
7 honey ham
8 nova lox
Name: food, dtype: object
meat_to_animal = {
'bacon': 'pig',
'pulled pork': 'pig',
'pastrami': 'cow',
'corned beef': 'cow',
'honey ham': 'pig',
'nova lox': 'salmon'
}
data['animal'] = lowercased.map(meat_to_animal)
data
food | ounces | animal | |
---|---|---|---|
0 | bacon | 4.0 | pig |
1 | pulled pork | 3.0 | pig |
2 | bacon | 12.0 | pig |
3 | Pastrami | 6.0 | cow |
4 | corned beef | 7.5 | cow |
5 | Bacon | 8.0 | pig |
6 | pastrami | 3.0 | cow |
7 | honey ham | 5.0 | pig |
8 | nova lox | 6.0 | salmon |
# 合写
data['food'].map(lambda x: meat_to_animal[x.lower()])
0 pig
1 pig
2 pig
3 cow
4 cow
5 pig
6 cow
7 pig
8 salmon
Name: food, dtype: object
### 3、Replacing Values(替换值) 其实fillna是一个特殊换的替换操作。map可以用于修改一个object里的部分值,但是replace能提供一个更简单和更灵活的方法做到这点 - 用NA来替代的话,用replace,会产生一个新series(除非使用inplace=True)
data = pd.Series([1., -999., 2., -999., -1000., 3.])
data
0 1.0
1 -999.0
2 2.0
3 -999.0
4 -1000.0
5 3.0
dtype: float64
# 一次替换多个值
data.replace([-999,-1000], np.nan) # 用NaN替换成-999, -1000
0 1.0
1 NaN
2 2.0
3 NaN
4 NaN
5 3.0
dtype: float64
# 分开替换 方法一 用list
data.replace([-999,-1000], [-99,-100]) # -999,-1000分别替换为-99,-100
0 1.0
1 -99.0
2 2.0
3 -99.0
4 -100.0
5 3.0
dtype: float64
# 分开替换 方法二 用dict
data.replace({-999: np.nan, -1000: 0}) # -999,-1000分别替换为NaN,0
0 1.0
1 NaN
2 2.0
3 NaN
4 0.0
5 3.0
dtype: float64
注意:data.replace方法和data.str.replace方法是不同的,后者会对string进行element-wise替换。
像是series里的value一样,axis label也能类似地是函数或映射来转换,产生一个新的object。当然也可以设置in-place不产生新的数据:
data = pd.DataFrame(np.arange(12).reshape((3, 4)),
index=['Ohio', 'Colorado', 'New York'],
columns=['one', 'two', 'three', 'four'])
data
one | two | three | four | |
---|---|---|---|---|
Ohio | 0 | 1 | 2 | 3 |
Colorado | 4 | 5 | 6 | 7 |
New York | 8 | 9 | 10 | 11 |
data.index
Index(['Ohio', 'Colorado', 'New York'], dtype='object')
# 先小写转大写再截取前4个字母
transform = lambda x: x[:4].upper()
# 不会修改原数据,会新建object
data.index.map(transform)
Index(['OHIO', 'COLO', 'NEW '], dtype='object')
# 直接赋值,会修改原数据
data.index = data.index.map(transform)
data
one | two | three | four | |
---|---|---|---|---|
OHIO | 0 | 1 | 2 | 3 |
COLO | 4 | 5 | 6 | 7 |
NEW | 8 | 9 | 10 | 11 |
如果你想要创建一个转换后的版本,而且不用修改原始的数据,可以用rename
data.rename(index=str.title, columns=str.upper)
ONE | TWO | THREE | FOUR | |
---|---|---|---|---|
Ohio | 0 | 1 | 2 | 3 |
Colo | 4 | 5 | 6 | 7 |
New | 8 | 9 | 10 | 11 |
# rename能用于dict一样的oject
data.rename(index={'OHIO': 'INDIANA'},
columns={'three': 'pekaboo'},
inplace=True) # inplace=True 会修改原数据
data
one | two | pekaboo | four | |
---|---|---|---|---|
INDIANA | 0 | 1 | 2 | 3 |
COLO | 4 | 5 | 6 | 7 |
NEW | 8 | 9 | 10 | 11 |
连续型数据经常被离散化或分散成bins(分箱)来分析。假设你有一组数据,你想把人分到不同的年龄组里,可以用pandas里的cut。
ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]
# 分到四个bin里,19~25, 26~35, 36~60, >60
bins = [18, 25, 35, 60, 100]
cats = pd.cut(ages, bins)
cats
[(18, 25], (18, 25], (18, 25], (25, 35], (18, 25], ..., (25, 35], (60, 100], (35, 60], (35, 60], (25, 35]]
Length: 12
Categories (4, interval[int64]): [(18, 25] < (25, 35] < (35, 60] < (60, 100]]
返回的是一个特殊的Categorical object。我们看到的结果描述了pandas.cut如何得到bins。可以看作是一个string数组用来表示bin的名字,它内部包含了一个categories数组,用来记录不同类别的名字,并伴有表示ages的label(可以通过codes属性查看)
# 判断各区间里 ages 个数
cats.codes
array([0, 0, 0, 1, 0, 0, 2, 1, 3, 2, 2, 1], dtype=int8)
# 查看区间 右关闭,默认right=True
cats.categories # 注意区间。括号表示不包含,方括号表示包含。你可以自己设定哪一边关闭(right=False)
IntervalIndex([(18, 25], (25, 35], (35, 60], (60, 100]]
closed='right',
dtype='interval[int64]')
# 查看各区间的个数
pd.value_counts(cats) # 这里pd.value_counts(cats)是pandas.cut后bin的数量
(18, 25] 5
(35, 60] 3
(25, 35] 3
(60, 100] 1
dtype: int64
这里pd.value_counts(cats)是pandas.cut后bin的数量。
这里我们注意一下区间。括号表示不包含,方括号表示包含。你可以自己设定哪一边关闭(right=False)
# 查看区间 左关闭 right=False
cuts2 = pd.cut(ages, [18, 26, 36, 61, 100], right=False)
cuts2.categories
IntervalIndex([[18, 26), [26, 36), [36, 61), [61, 100)]
closed='left',
dtype='interval[int64]')
# 用一个list或数组给labels选项来设定bin的名字
group_names = ['Youth', 'YoungAdult', 'MiddleAged', 'Senior']
pd.cut(ages, bins, labels=group_names)
[Youth, Youth, Youth, YoungAdult, Youth, ..., YoungAdult, Senior, MiddleAged, MiddleAged, YoungAdult]
Length: 12
Categories (4, object): [Youth < YoungAdult < MiddleAged < Senior]
如果你只是给一个bins的数量来cut,而不是自己设定每个bind的范围,cut会根据最大值和最小值来计算等长的bins。比如下面我们想要做一个均匀分布的四个bins
data = np.random.rand(20)
pd.cut(data, 4, precision=2) # precision=2 表示精确到小数点后两位
[(0.51, 0.72], (0.092, 0.3], (0.092, 0.3], (0.092, 0.3], (0.3, 0.51], ..., (0.092, 0.3], (0.3, 0.51], (0.092, 0.3], (0.51, 0.72], (0.3, 0.51]]
Length: 20
Categories (4, interval[float64]): [(0.092, 0.3] < (0.3, 0.51] < (0.51, 0.72] < (0.72, 0.93]]
一个近似的函数,qcut,会按照数据的分位数来分箱。取决于数据的分布,用cut通常不能保证每一个bin有一个相同数量的数据点。而qcut是按百分比来切的,所以可以得到等数量的bins
# 平均分为四部分
data = np.random.randn(1000)
cats = pd.qcut(data, 4) # 分四段
cats
[(-0.657, 0.0235], (0.0235, 0.685], (0.0235, 0.685], (-0.657, 0.0235], (-3.002, -0.657], ..., (0.0235, 0.685], (-0.657, 0.0235], (0.0235, 0.685], (0.685, 3.274], (-0.657, 0.0235]]
Length: 1000
Categories (4, interval[float64]): [(-3.002, -0.657] < (-0.657, 0.0235] < (0.0235, 0.685] < (0.685, 3.274]]
# 按百分比来切
pd.value_counts(cats)
(0.685, 3.274] 250
(0.0235, 0.685] 250
(-0.657, 0.0235] 250
(-3.002, -0.657] 250
dtype: int64
类似的,在cut中我们可以自己指定百分比:
cats2 = pd.cut(data, [0, 0.1, 0.5, 0.9, 1.]) # 累进的百分比
cats2
[NaN, (0.1, 0.5], (0.5, 0.9], NaN, NaN, ..., (0.1, 0.5], NaN, (0.1, 0.5], NaN, NaN]
Length: 1000
Categories (4, interval[float64]): [(0.0, 0.1] < (0.1, 0.5] < (0.5, 0.9] < (0.9, 1.0]]
pd.value_counts(cats2)
(0.1, 0.5] 163
(0.5, 0.9] 124
(0.0, 0.1] 37
(0.9, 1.0] 25
dtype: int64
过滤或转换异常值是数组操作的一个重头戏。
data = pd.DataFrame(np.random.randn(1000, 4)) # 生成正态数据
data.describe()
0 | 1 | 2 | 3 | |
---|---|---|---|---|
count | 1000.000000 | 1000.000000 | 1000.000000 | 1000.000000 |
mean | 0.005916 | 0.009086 | -0.083772 | -0.013680 |
std | 0.982372 | 0.989198 | 0.994994 | 1.012198 |
min | -3.317565 | -2.773347 | -3.306008 | -3.563968 |
25% | -0.695527 | -0.693453 | -0.734059 | -0.689416 |
50% | 0.015663 | 0.015709 | -0.055282 | -0.023872 |
75% | 0.716060 | 0.674484 | 0.582683 | 0.670211 |
max | 2.779288 | 3.222485 | 3.267874 | 3.328084 |
# 查看前5行
data.head()
0 | 1 | 2 | 3 | |
---|---|---|---|---|
0 | 0.112181 | 0.066283 | 2.693501 | -1.098930 |
1 | 1.266445 | 0.661368 | 0.365074 | 0.151563 |
2 | -0.895071 | 0.683068 | 0.100348 | -0.804245 |
3 | 0.423192 | -1.269358 | 0.309651 | -0.204646 |
4 | 0.596470 | 1.592684 | -0.032159 | 0.230689 |
# 查看列名为2的数据,并返回后5行
col = data[2]
col.tail()
995 -0.765241
996 1.399000
997 0.833521
998 0.465798
999 0.433316
Name: 2, dtype: float64
# 返回col中绝对值大于3的数
col[np.abs(col)> 3]
33 3.267874
111 -3.296620
561 -3.226878
666 3.230451
816 -3.306008
Name: 2, dtype: float64
# 所有绝对值大于3的行
# data[(np.abs(data) > 3).any(axis=1)] # any中axis=1表示column
data[(np.abs(data) > 3).any(1)] # 同上
0 | 1 | 2 | 3 | |
---|---|---|---|---|
33 | -0.294413 | 1.009243 | 3.267874 | 0.107169 |
99 | 1.957645 | 1.777069 | 1.643548 | 3.328084 |
111 | -0.678862 | -1.248394 | -3.296620 | -1.639195 |
147 | 0.332718 | 3.222485 | -1.043792 | 0.435667 |
409 | 0.031337 | 1.574926 | 1.419990 | 3.323908 |
515 | -1.660116 | 0.899630 | -0.061961 | 3.030211 |
561 | 0.926034 | 1.035953 | -3.226878 | 1.315150 |
656 | 0.637542 | -1.059537 | -1.769527 | -3.165496 |
666 | -1.339547 | 1.036912 | 3.230451 | 0.422901 |
676 | 0.104951 | 2.117886 | 0.404746 | 3.296446 |
684 | -1.398323 | -0.342887 | 0.580598 | -3.020550 |
816 | 0.248030 | -1.278687 | -3.306008 | 1.844911 |
860 | -3.317565 | 2.252828 | 0.865087 | 0.037816 |
865 | 1.146137 | 0.212220 | 1.081564 | -3.563968 |
# 把绝对值大于3的数字直接变成-3或3
data[np.abs(data)>3] = np.sign(data) * 3 # np.sign(data)会根据值的正负号来得到1或-1
# 查看33行
data[33:34]
0 | 1 | 2 | 3 | |
---|---|---|---|---|
33 | -0.294413 | 1.009243 | 3.0 | 0.107169 |
排列(随机排序)一个series或DataFrame中的row,用numpy.random.permutation函数很容易就能做到。调用permutation的时候设定好你想要进行排列的axis,会产生一个整数数组表示新的顺序
df = pd.DataFrame(np.arange(5 * 4).reshape((5, 4)), columns=list('ABCD'))
df
A | B | C | D | |
---|---|---|---|---|
0 | 0 | 1 | 2 | 3 |
1 | 4 | 5 | 6 | 7 |
2 | 8 | 9 | 10 | 11 |
3 | 12 | 13 | 14 | 15 |
4 | 16 | 17 | 18 | 19 |
# 随机排序
sampler = np.random.permutation(5)
sampler
array([0, 2, 4, 1, 3])
# 利用take()应用行索引
df.take(sampler) # 默认axis=0, 如果axis=1将应用于列索引
A | B | C | D | |
---|---|---|---|---|
0 | 0 | 1 | 2 | 3 |
2 | 8 | 9 | 10 | 11 |
4 | 16 | 17 | 18 | 19 |
1 | 4 | 5 | 6 | 7 |
3 | 12 | 13 | 14 | 15 |
# 随机抽取3列数据
df.sample(3, axis='columns') # 默认axis=0 表示行
D | B | A | |
---|---|---|---|
0 | 3 | 1 | 0 |
1 | 7 | 5 | 4 |
2 | 11 | 9 | 8 |
3 | 15 | 13 | 12 |
4 | 19 | 17 | 16 |
Dummy Variables:虚拟变量,又称虚设变量、名义变量或哑变量,用以反映质的属性的一个人工变量,是量化了的自变量,通常取值为0或1。
另一种在统计模型上的转换或机器学习应用是把一个categorical variable(类别变量)变为一个dummy or indicator matrix(虚拟或指示器矩阵)。如果DataFrame中的一列有k个不同的值,我们可以用一个矩阵或DataFrame用k列来表示,1或0。pandas有一个get_dummies函数实现这个工作
df = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'b'],
'data1': range(6)})
df
data1 | key | |
---|---|---|
0 | 0 | b |
1 | 1 | b |
2 | 2 | a |
3 | 3 | c |
4 | 4 | a |
5 | 5 | b |
# 通过get_dummies进行one-hot编码,产生虚拟变量表
pd.get_dummies(df['key'], prefix='key') # prefix表示前缀
key_a | key_b | key_c | |
---|---|---|---|
0 | 0 | 1 | 0 |
1 | 0 | 1 | 0 |
2 | 1 | 0 | 0 |
3 | 0 | 0 | 1 |
4 | 1 | 0 | 0 |
5 | 0 | 1 | 0 |
np.random.seed(12345) # 伪随机数
values = np.random.rand(10)
values
array([0.92961609, 0.31637555, 0.18391881, 0.20456028, 0.56772503,
0.5955447 , 0.96451452, 0.6531771 , 0.74890664, 0.65356987])
# 离散化和装箱
bins = [0, 0.2, 0.4, 0.6, 0.8, 1.]
pd.cut(values, bins)
[(0.8, 1.0], (0.2, 0.4], (0.0, 0.2], (0.2, 0.4], (0.4, 0.6], (0.4, 0.6], (0.8, 1.0], (0.6, 0.8], (0.6, 0.8], (0.6, 0.8]]
Categories (5, interval[float64]): [(0.0, 0.2] < (0.2, 0.4] < (0.4, 0.6] < (0.6, 0.8] < (0.8, 1.0]]
# 结合使用
pd.get_dummies(pd.cut(values, bins), prefix='区间')
区间_(0.0, 0.2] | 区间_(0.2, 0.4] | 区间_(0.4, 0.6] | 区间_(0.6, 0.8] | 区间_(0.8, 1.0] | |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 1 |
1 | 0 | 1 | 0 | 0 | 0 |
2 | 1 | 0 | 0 | 0 | 0 |
3 | 0 | 1 | 0 | 0 | 0 |
4 | 0 | 0 | 1 | 0 | 0 |
5 | 0 | 0 | 1 | 0 | 0 |
6 | 0 | 0 | 0 | 0 | 1 |
7 | 0 | 0 | 0 | 1 | 0 |
8 | 0 | 0 | 0 | 1 | 0 |
9 | 0 | 0 | 0 | 1 | 0 |
python很多内建方法很适合处理string。而且对于更复杂的模式,可以配合使用正则表达式。而pandas则混合了两种方式。
大部分string处理,使用内建的一些方法就足够了。比如,可以用split来分割用逗号区分的字符串:
# 分离及去除空格(包括换行符)
val = 'a ,b, guido'
pieces = [x.strip() for x in val.split(',')]
pieces
['a', 'b', 'guido']
print('判断guido是否在这个字符串中:', 'guido' in val)
print('从第三个位置开始查找逗号索引值:', val.index(',', 3,)) # strs.index(str, beg=0, end=len(string))
print('查看字母"i"索引值:', val.find('i') ) # str.find(str, beg=0, end=len(string))
print('统计逗号个数:', val.count(','))
判断guido是否在这个字符串中: True
从第三个位置开始查找逗号索引值: 4
查看字母"i"索引值: 8
统计逗号个数: 2
注意index和find的区别。如果要找的string不存在的话,index会报错。而find会返回-1
# 合并
first, second, third = pieces
' & '.join(pieces)
'a & b & guido'
# 替换
val.replace(',', '::')
'a ::b:: guido'
一些内建的string方法:
正则表达式能让我们寻找更复杂的pattern。通常称一个表达式为regex,由正则表达语言来代表一个字符串模式。可以使用python内建的re模块来使用。
关于正则表达式,有很多教学资源,可以自己找几篇来学一些,这里不会介绍太多。
re模块有以下三个类别:patther matching(模式匹配), substitution(替换), splitting(分割)。通常这三种都是相关的,一个regex用来描述一种pattern,这样会有很多种用法。这里举个例子,假设我们想要根据空格(tabs,spaces,newlines)来分割一个字符串。
import re
# 用于描述一个或多个空格的regex是\s+
text = "foo bar\t baz \tqux"
re.split('\s+', text)
['foo', 'bar', 'baz', 'qux']
# 如果应用多次,这样分开能节省CPU的资源
regex = re.compile('\s+')
regex.split(text)
['foo', 'bar', 'baz', 'qux']
text = """Dave [email protected]
Steve [email protected]
Rob [email protected]
Ryan [email protected] """
pattern = r'[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}'
regex = re.compile(pattern, flags=re.IGNORECASE) # re.IGNORECASE表示区分大小写
# findall() 返回所有匹配的结果
regex.findall(text)
['[email protected]', '[email protected]', '[email protected]', '[email protected]']
# search() 只返回第一次查询到的结果
regex.search(text)
<_sre.SRE_Match object; span=(5, 20), match='[email protected]'>
# regex.match返回None,因为它只会在pattern存在于stirng开头的情况下才会返回匹配结果
print(regex.match(text))
None
# sub返回一个新的string,把pattern出现的地方替换为我们指定的string
print(regex.sub('REDACTED', text))
Dave REDACTED
Steve REDACTED
Rob REDACTED
Ryan REDACTED
# 去空格和标点符号
def clean_string(strings):
result = []
for value in strings:
value = value.strip() # 删除首尾空格符及换行符
value = re.sub('[!#?]', '', value) # 利用正则表达式删除
value = value.title()
result.append(value)
return result
states = [' Alabama ', ' Georgia!', 'Georgia ', 'georgia',
'FlOrIda', 'south carolina##', 'West virginia?']
clean_string(states)
['Alabama',
'Georgia',
'Georgia',
'Georgia',
'Florida',
'South Carolina',
'West Virginia']
这里给一些正则表达式的方法:
一些复杂的数据清理中,string会有缺失值:
data = {'Dave': '[email protected]', 'Steve': '[email protected]',
'Rob': '[email protected]', 'Wes': np.nan}
data = pd.Series(data)
data
Dave [email protected]
Rob [email protected]
Steve [email protected]
Wes NaN
dtype: object
# 是否有空值
data.isnull()
Dave False
Rob False
Steve False
Wes True
dtype: bool
# 是否包含字符串gmail
data.str.contains('gmail')
Dave False
Rob True
Steve True
Wes NaN
dtype: object
pattern = '([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\\.([A-Z]{2,4})'
data.str.findall(pattern, flags=re.IGNORECASE)
Dave [(dave, google, com)]
Rob [(rob, gmail, com)]
Steve [(steve, gmail, com)]
Wes NaN
dtype: object
有很多方法用于向量化。比如str.get或index索引到str属性
data.str.get(1)
Dave a
Rob o
Steve t
Wes NaN
dtype: object
data.str[:5]
Dave dave@
Rob rob@g
Steve steve
Wes NaN
dtype: object
这里有一些字符串向量化的方法: