数据分析学习笔记之数据清洗与准备

259 阅读16分钟

「这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战」。

1.处理缺失值

  • 对于数值型数据,pandas使用浮点值NaN(Not a Number)来表示缺失值,称NaN为容易检测到的标识值;
  • 在pandas中,将缺失值称为NA,意思是not available。在统计学应用中,NA数据可以是不存在的数据或是存在但不可观察的数据;
  • 当清洗数据用于分析时,对缺失数据本身进行分析以确定数据收集问题或数据丢失导致的数据偏差通常很重要;
  • Python内建的None值在对象数组中也被当作NA处理。

NA处理方法

函数名描述
dropna根据每个标签的值是否是确实数据来筛选轴标签,并根据允许丢失的数据量来确定阈值
fillna用某些值填充缺失的数据或使用插值方法(如'ffill'或'bfill')
isnull返回表明哪些值是缺失值的布尔值
notnullisnull的反函数

1.1 过滤缺失值

  • 在Series上使用dropna,它会返回Series中所有的非空数据及其索引值;

  • 当处理DataFrame对象时,dropna默认情况下会删除包含缺失值的行;

    • 传入how = 'all'时,将删除所有值均为NA的行;
    • 如果要用同样的方式删除列,传入参数axis = 1。
  • 过滤DataFrame的行的相关方法往往涉及时间序列数据,假设只想保留包含一定数量的观察值的行,可以用thresh参数来表示:df.dropna(thresh = n,保留至少有n个非NA数的行)。

1.2 补全缺失值

  • 大多数情况下,主要使用fillna方法来补全缺失值,调用fillna时,可以使用一个常数来替代缺失值;
  • 在调用fillna时使用字典,可以为不同列设定不同的填充值;
  • fillna返回的是一个新的对象,但也可以修改已经存在的对象(inplace = True);
  • 用于重建索引的相同的插值方法也可以用于fillna。

fillna函数参数

参数描述
value标量值或字典型对象用于填充缺失值
method插值方法,如果没有其他参数,默认是'ffill'
axis需要填充的轴,默认axis = 0,0代表行,1列表列
inplace修改被调用的对象,而不是生成一个备份
limit用于向前或向后填充时最大的填充范围

2.数据转换

2.1 删除重复值

  • DataFrame的duplicated方法返回的是一个布尔值Series,这个Series反映的是每一行是否存在重复(与之前出现过的行相同)情况;
  • drop_duplicates返回的是DataFrame,内容是duplicated返回数组中为False的部分;
  • 基于某特定列去除重复值:data.drop_duplicates[列名];
  • duplicated和drop_duplicates默认都是保留第一个观测到的值,传入参数keep = 'last'将会返回最后一个(从后往前)。

2.2 使用函数或映射进行数据转换

Series的map方法接收一个函数或一个包含映射关系的字典型对象。

例子:假设有如下肉类数据:

[in]
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]})
[out]
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

现在想要添加一列用于表明每种食物的动物肉类型,先写一个食物和肉类类型的映射:

meat_to_animal = {
    'bacon': 'pig',
    'pulled pork': 'pig',
    'pastrami': 'cow',
    'corned beef': 'cow',
    'honey ham': 'pig',
    'nova lox': 'salmon'
}

将该映射传入Series的map方法中:

lowercased = data['food'].str.lower() # 先将food里面的大写字母变为小写,和映射对应起来
data['animal'] = lowercased.map(meat_to_animal)
[out]
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['animal'] = data['food'].map(lambda x: meat_to_animal[x.lower()])

2.3 替代值

  • 对于数据中的缺失值,如果要使用NA来替代这些值,可以使用replace方法生成新的Series(除非传入了inplace = True);
  • 如果想要一次替代多个值,可以传入一个列表和替代值;
  • 要将不同的值替换为不同的值,可以传入替代值的列表;
  • 参数也可以通过字典传递。

2.4 重命名轴索引

和Series中的值一样,可以通过函数或某种形式的映射对轴标签进行类似的转换,生成新的且带有不同标签的对象,也可以在不生成新的数据结构的情况下修改轴。下面是简单的示例:

data = pd.DataFrame(np.arange(12).reshape((3, 4)), index = ['Ohio', 'Colorado', 'New York'], columns = ['one', 'two', 'three', 'four'])
[out]
data
			one	two	three	four
Ohio		0	1	2		3
Colorado	4	5	6		7
New York	8	9	10		11

与Series类似,轴索引也有一个map方法:

transform = lambda x: x[:4].upper()
data.index.map(transform)
[out]
Index(['OHIO', 'COLO', 'NEW '], dtype='object')

可以赋值给index,修改DataFrame:

data.index = data.index.map(transform)
[out]
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)
    [out]
    		ONE	TWO	THREE	FOUR
        Ohio	0	1	2		3
        Colo	4	5	6		7
        New	8	9	10		11
    
  • rename可以结合字典型对象使用,为轴标签的子集提供新的值:\

    data.rename(index = {'OHIO': 'INDIANA'}, columns = {'three': 'peekaboo'})
    [out]
    		one	two	peekaboo	four
    INDIANA	0	1	2			3
    COLO	4	5	6			7
    NEW		8	9	10			11
    
  • 如果想要修改原有的数据集,传入inplace = True:

    data.rename(index = {'OHIO': 'INDIANA'}, inplace=True)
    data
    [out]
    		one	two	three	four
    INDIANA	0	1	2		3
    COLO	4	5	6		7
    NEW		8	9	10		11
    

2.5 离散化和分箱

连续值经常需要离散化,或者分离成“箱子”进行分析。

  • 可以使用pandas中的cut对数据进行分组:

    ages = [20, 22, 25, 27, 21, 23, 37, 61, 45, 41, 32]
    bins = [18, 25, 35, 60, 100]
    cats = pd.cut(ages, bins)
    [out]
    cats
    [(18, 25], (18, 25], (18, 25], (25, 35], (18, 25], ..., (35, 60], (60, 100], (35, 60], (35, 60], (25, 35]]
    Length: 11
    Categories (4, interval[int64]): [(18, 25] < (25, 35] < (35, 60] < (60, 100]]
    
  • pandas返回的对象是一个特殊的Categorical对象,可以将它当作一个表示箱名的字符串数组,它在内部包含了一个categories(类别)数组,它指定了不同的类别名称以及codes属性中的数据标签:

    cats.codes
    [out]
    array([0, 0, 0, 1, 0, 0, 2, 3, 2, 2, 1], dtype=int8)
    cats.categories
    [out]
    IntervalIndex([(18, 25], (25, 35], (35, 60], (60, 100]],
                  closed='right',
                  dtype='interval[int64]')
    
  • 注意:pd.values_counts(cats)是对pandas.cut的结果中的箱数量的计算:

    pd.value_counts(cats)
    [out]
    (18, 25]     5
    (35, 60]     3
    (25, 35]     2
    (60, 100]    1
    dtype: int64
    
  • 与区间的数学符号一样,小括号表示开,中括号表示闭,可以通过传递right = False 来改变哪一边是封闭的:

    pd.cut(ages, [18, 26, 36, 61, 100], right = False)
    [out]
    [[18, 26), [18, 26), [18, 26), [26, 36), [18, 26), ..., [36, 61), [61, 100), [36, 61), [36, 61), [26, 36)]
    Length: 11
    Categories (4, interval[int64]): [[18, 26) < [26, 36) < [36, 61) < [61, 100)]
    
  • 可以通过向labesl选项传递一个列表或数组来传入自定义的箱名:

    group_names = ['Youth', 'YoungAdult', 'MiddleAged', 'Senior']
    pd.cut(ages, bins, labels=group_names)
    [out]
    ['Youth', 'Youth', 'Youth', 'YoungAdult', 'Youth', ..., 'MiddleAged', 'Senior', 'MiddleAged', 'MiddleAged', 'YoungAdult']
    Length: 11
    Categories (4, object): ['Youth' < 'YoungAdult' < 'MiddleAged' < 'Senior']
    
  • 如果传给cut整数个的箱来代替显式的箱边,pandas将根据数据中的最大值和最小值计算出等长的箱(precision:十进制精度):

    data = np.random.rand(20)
    pd.cut(data, 4, precision = 2)
    [out]
    [(0.24, 0.46], (0.69, 0.91], (0.46, 0.69], (0.0094, 0.24], (0.46, 0.69], ..., (0.0094, 0.24], (0.69, 0.91], (0.69, 0.91], (0.0094, 0.24], (0.0094, 0.24]]
    Length: 20
    Categories (4, interval[float64]): [(0.0094, 0.24] < (0.24, 0.46] < (0.46, 0.69] < (0.69, 0.91]]
    

qcut是一个与分箱密切相关的函数,它基于样本分位数进行分箱。取决于数据的分布,使用cut通常不会使每个箱具有相同数据量的数据点,由于qcut使用样本的分位数,所以可以通过qcut获得等长的箱:

data = np.random.randn(1000)
cats = pd.qcut(data, 4) # 切成四份
[out]
cats
[(0.676, 3.786], (0.0152, 0.676], (-3.283, -0.653], (-3.283, -0.653], (0.676, 3.786], ..., (0.676, 3.786], (-3.283, -0.653], (-3.283, -0.653], (0.0152, 0.676], (0.0152, 0.676]]
Length: 1000
Categories (4, interval[float64]): [(-3.283, -0.653] < (-0.653, 0.0152] < (0.0152, 0.676] < (0.676, 3.786]]
pd.value_counts(cats)
[out]
(-3.283, -0.653]    250
(-0.653, 0.0152]    250
(0.0152, 0.676]     250
(0.676, 3.786]      250
dtype: int64

与cut类似,可以传入自定义的分位数(注意,qcut是没有right这个参数的):

pd.qcut(data, [0, 0.1, 0.5, 0.9, 1.])
[out]
[(1.295, 3.786], (0.0152, 1.295], (-1.304, 0.0152], (-1.304, 0.0152], (1.295, 3.786], ..., (0.0152, 1.295], (-3.283, -1.304], (-3.283, -1.304], (0.0152, 1.295], (0.0152, 1.295]]
Length: 1000
Categories (4, interval[float64]): [(-3.283, -1.304] < (-1.304, 0.0152] < (0.0152, 1.295] < (1.295, 3.786]]

2.6 检测和过滤异常值

考虑一个具有正态分布数据的DataFrame:

data = pd.DataFrame(np.random.randn(1000, 4))
data.describe()
[out]
		0			1			2			3
count	1000.000000	1000.000000	1000.000000	1000.000000
mean	0.004252	-0.068202	0.018967	-0.023168
std		1.041946	0.990577	1.000506	0.977232
min		-3.225913	-3.268806	-2.780122	-2.758672
25%		-0.712939	-0.708957	-0.716310	-0.685308
50%		0.025723	-0.049111	-0.010828	-0.035590
75%		0.694367	0.589943	0.664508	0.628488
max		3.448641	2.741577	3.309812	3.456869

假设要找出一列中绝对值大于三的值:

col = data[2]
col[np.abs(col) > 3]
[out]
87     3.309812
109    3.151158
568    3.157156
Name: 2, dtype: float64

要选出所有值大于3或小于-3的行,可以对布尔值DataFrame使用any方法:

data[(np.abs(data) > 3).any(axis=1)] #axis = 1表示的是以列为轴向
[out]
	0			1			2			3
39	-0.809281	-0.392603	1.801699	3.057056
72	3.057684	1.445641	0.125901	0.091375
87	-0.584990	1.009517	3.309812	0.156830
101	3.110218	0.265354	-0.305812	-0.427352
109	-2.998249	0.335766	3.151158	0.233610
295	-0.266134	0.672752	-0.634659	3.276163
516	-1.152287	-3.268806	-0.855634	0.647719
568	0.138585	0.654785	3.157156	1.968288
664	-3.225913	-1.925428	0.605383	-0.265089
676	-3.054581	-0.462598	0.831141	0.513706
692	3.015440	-0.807693	0.262138	-0.287005
712	3.448641	0.761175	-0.027719	1.074143
781	1.258356	-0.375759	-1.183974	3.456869
805	0.100815	-3.035329	-0.170239	-0.205602
888	3.078300	-0.157611	1.156965	0.496205

值可以根据这些标准来设置,下面代码限制了-3到3之间的数值(语句np.sign(data)根据数据中的值的正负分别生成1和-1):

data[np.abs(data) > 3] = np.sign(data) * 3
data.describe()
[out]
		0			1			2			3
count	1000.000000	1000.000000	1000.000000	1000.000000
mean	0.003822	-0.067898	0.018349	-0.023958
std		1.038955	0.989638	0.998589	0.974634
min		-3.000000	-3.000000	-2.780122	-2.758672
25%		-0.712939	-0.708957	-0.716310	-0.685308
50%		0.025723	-0.049111	-0.010828	-0.035590
75%		0.694367	0.589943	0.664508	0.628488
max		3.000000	2.741577	3.000000	3.000000

2.7 置换和随机抽样

使用numpy.random.permutation对DataFrame中的Series或行进行置换(随机重排序),在调用permutation时根据想要的轴长度可以产生一个表示新顺序的整数数组,整数数组可以用在基于iloc的索引或等价的take函数中。

df = pd.DataFrame(np.arange(5 * 4).reshape((5, 4)))
df
[out]
	0	1	2	3
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
[out]
array([0, 2, 1, 3, 4])

df.take(sampler)
[out]
	0	1	2	3
0	0	1	2	3
2	8	9	10	11
1	4	5	6	7
3	12	13	14	15
4	16	17	18	19

要选出一个不含有替代值的随机子集,可以使用Series和DataFrame的sample方法:

df.sample(2)
[out]
	0	1	2	3
0	0	1	2	3
1	4	5	6	7

要生成一个带有替代值的样本(允许有重复选择),将 replace = True 传入sample方法即可:

choices = pd.Series([5, 7, -1, 6, 4])
draws = choices.sample(n = 10, replace = True)
draws
[out]
4    4
0    5
1    7
4    4
0    5
0    5
1    7
3    6
1    7
2   -1
dtype: int64

2.8 计算指标/虚拟变量

将分类变量转换为“虚拟”或“指标”矩阵是另一种用于统计建模或机器学习的转换操作。如果DataFrame中的一列有k个不同的值,则可以衍生一个k列的值为1或0的矩阵或DataFrame。pandas有一个get_dummies函数用于实现该功能。

df = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'b'], 'data1': range(6)})
pd.get_dummies(df['key'])
[out]
	a	b	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

在某些情况下,可能想在指标DataFrame的列上加入前缀,然后与其他数据合并。在get_dummies方法中有一个前缀参数(prefix)用于实现该功能:

dummies = pd.get_dummies(df['key'], prefix = 'key')
df_with_dummy = df[['data1']].join(dummies)
df_with_dummy
[out]
	data1	key_a	key_b	key_c
0	0		0		1		0
1	1		0		1		0
2	2		1		0		0
3	3		0		0		1
4	4		1		0		0
5	5		0		1		0

注意:df['data1']取的是一个Series数据,而Series数据是没有join方法的,所以这里取'data1'列用的是:df[['data1']]这个形式。

如果DataFrame中的一行属于多个类别,则情况略为复杂。下面以MovieLens的IM数据集为例展开阐述:

mnames = ['movie_id', 'title', 'genres']
movies = pd.read_table('datasets/movielens/movies.dat', sep='::', header = None, names = mnames)
movies[:10]
[out]
	movie_id	title								genres
0	1			Toy Story (1995)					Animation|Children's|Comedy
1	2			Jumanji (1995)						Adventure|Children's|Fantasy
2	3			Grumpier Old Men (1995)				Comedy|Romance
3	4			Waiting to Exhale (1995)			Comedy|Drama
4	5			Father of the Bride Part II (1995)	Comedy
5	6			Heat (1995)							Action|Crime|Thriller
6	7			Sabrina (1995)						Comedy|Romance
7	8			Tom and Huck (1995)					Adventure|Children's
8	9			Sudden Death (1995)					Action
9	10			GoldenEye (1995)					Action|Adventure|Thriller

为每个电影流派添加指标变量需要进行一些数据处理,首先,从数据集提取出所有不同的流派的列表:

all_genres = []
for x in movies.genres:
    all_genres.extend(x.split('|'))
genres = pd.unique(all_genres)
genres
[out]
array(['Animation', "Children's", 'Comedy', 'Adventure', 'Fantasy',
       'Romance', 'Drama', 'Action', 'Crime', 'Thriller', 'Horror',
       'Sci-Fi', 'Documentary', 'War', 'Musical', 'Mystery', 'Film-Noir',
       'Western'], dtype=object)

使用全0的DataFrame是构建指标DataFrame的一种方式:

zero_matrix = np.zeros((len(movies), len(genres)))
dummies = pd.DataFrame(zero_matrix, columns = genres)

现在,遍历每一部电影,将dummies每一行的条目设置为1,这里使用dummies.columns来计算每一个流派的列指标:

gen = movies.genres[0]
gen.split('|')
[out]
['Animation', "Children's", 'Comedy']

dummies.columns.get_indexer(gen.split('|'))
[out]
array([0, 1, 2], dtype=int64)

之后,使用 .loc 根据这些指标来设置值:

for i, gen in enumerate(movies.genres):
    indices = dummies.columns.get_indexer(gen.split('|'))
    dummies.iloc[i, indices] = 1

之后,将结果与movies进行联合:

movies_windic = movies.join(dummies.add_prefix('Genre_'))
movies_windic.iloc[0]
[out]
movie_id                                       1
title                           Toy Story (1995)
genres               Animation|Children's|Comedy
Genre_Animation                              1.0
Genre_Children's                             1.0
Genre_Comedy                                 1.0
Genre_Adventure                              0.0
Genre_Fantasy                                0.0
Genre_Romance                                0.0
Genre_Drama                                  0.0
Genre_Action                                 0.0
Genre_Crime                                  0.0
Genre_Thriller                               0.0
Genre_Horror                                 0.0
Genre_Sci-Fi                                 0.0
Genre_Documentary                            0.0
Genre_War                                    0.0
Genre_Musical                                0.0
Genre_Mystery                                0.0
Genre_Film-Noir                              0.0
Genre_Western                                0.0
Name: 0, dtype: object

:对于更大的数据,使用上述这种多成员构建指标的方法并不是特别快速,更好的方法是写一个直接将数据写为NumPy数组的底层函数,然后将结果封装为DataFrame。

将get_dummies与cut等离散化函数结合使用是统计应用的一个有用的方法:

np.random.seed(12345) # 改变随机数生成器的种子,seed值一定时每次生成的随机数都是一样的
values = np.random.rand(10)
values
[out]
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.get_dummies(pd.cut(values, bins))
[out]
	(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

3.字符串操作

pandas允许将字符串和正则表达式简洁地应用到整个数据数组上,此外还能处理数据缺失带来的困扰。

3.1 字符串对象方法

Python内建字符串方法

方法描述
count返回子字符串在字符串中的非重叠出现次数
endswith如果字符串以后缀结尾则返回True
startswith如果字符串以前缀开始则返回True
join使用字符串作为间隔符,用于粘合其他字符串的序列
index如果在字符串中找到,则返回子字符串中第一个字符的位置,如果找不到则引发valueError
find返回字符串中第一个出现子字符串的位置,类似index,但如果没有找到则返回-1
rfind返回子字符串在字符串中最后一次出现时第一个字符的位置,没有找到则返回-1
replace使用一个字符串替代另一个字符串
strip, rstrip, lstrip修建空白,包括换行符
split使用分隔符将字符串拆分为子字符串的列表
lower将大写字母转换为小写字母
upper将小写字母转换为大写字母
casefold将字符转化为小写,并将任何特定于区域的变量字符组合转换为常见的可比较形式
ljust, rjust左对齐或右对齐,用空格(或其他一些字符)填充字符串的相反侧以返回具有最小宽度的字符串

3.2 正则表达式

单个表达式通常被称为regex,是根据正则表达式语言形成的字符串,Python内建的re模块就是与之相关的库。

  • re模块主要有三个主题:模式匹配、替代、拆分,这三个主题是相关联的,一个正则表达式描述了在文本中需要定位的一种模式,可以用于多个目标;

  • 可以使用re.compile(正则表达式)自行编译,形成一个可复用的正则表达式对象;

  • 如果想得到一个所有匹配正则表达式的模式的列表,可以使用findall方法;

  • 为了在正则表达式中避免转义符\的影响,可以使用原生字符串语法;

  • findall返回的是字符串中所有的匹配项:

    text = """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}'
    regex = re.compile(pattern, flags = re.IGNORECASE) # IGNORECASE 使正则表达式不区分大小写
    regex.findall(text)
    [out]
    ['dave@google.com', 'steve@gmail.com', 'rob@gmail.com', 'ryan@yahoo.com']
    
  • search返回的仅仅是第一个匹配项,匹配对象只能告诉我们模式在字符串中起始和结束的位置:

    m = regex.search(text)
    m
    [out]
    <re.Match object; span=(5, 20), match='dave@google.com'>
    
    text[m.start():m.end()]
    [out]
    
    'dave@google.com'
    
  • match更为严格,它只在字符串的起始位置进行匹配,如果没有匹配到,返回None:

    print(regex.match(text))
    [out]
    None
    
  • sub会返回一个新的字符串,原字符串中的模式会被一个新的字符串替代:

    print(regex.sub('REDACTED', text))
    [out]
    Dave REDACTED
    Steve REDACTED
    Rob REDACTED
    Ryan REDACTED
    
  • 用括号将模式包括起来,修改后的正则表达式产生的匹配对象的groups方法,返回的是模式组件的元组:

    pattern = r'([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})'
    regex = re.compile(pattern, flags = re.IGNORECASE)
    m = regex.match('wesm@bright.net')
    m.groups()
    [out]
    ('wesm', 'bright', 'net')
    
  • 当模式可以分组时,findall返回的是包含元组的列表:

    regex.findall(text)
    [out]
    [('dave', 'google', 'com'),
     ('steve', 'gmail', 'com'),
     ('rob', 'gmail', 'com'),
     ('ryan', 'yahoo', 'com')]
    
  • sub也可以使用特殊符号,如\1和\2,访问每个匹配对象中的分组,符号\1代表的是第一个匹配分组,\2代表的是第二个匹配分组,以此类推:

    print(regex.sub(r'Username: \1, Domain: \2, Suffix: \3', text))
    [out]
    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
    

正则表达式方法

方法描述
findall将字符串中所有的非重叠匹配模式以列表形式返回
finditer与findall类似,但返回的是迭代器
match在字符串起始位置匹配模式,也可以将模式组建匹配到分组中;如果模式匹配上了,返回匹配对象,否则返回None
search扫描字符串的匹配模式,如果扫描到了返回匹配对象,与match方法不同的是,search方法的匹配可以是字符串的任意位置,而不仅仅是字符串的起始位置
split根据模式,将字符串拆分为多个部分
sub, subn用替换表达式替换字符串中所有的匹配(sub)或第n个出现的匹配串(subn);使用\1、\2...来引用替换字符串中的匹配组元素

3.3 pandas中的向量化字符串函数

清理杂乱的数据集用于分析通常需要大量的字符串处理和正则化,包含字符串的列有时会含有缺失数据,使得情况变得复杂:

data = {'Dava': 'dave@google.com', 'Steve': 'steve@gmail.com', 'Rob': 'rob@gmail.com', 'Wes': np.nan}
data = pd.Series(data)
data
[out]
Dava     dave@google.com
Steve    steve@gmail.com
Rob        rob@gmail.com
Wes                  NaN
dtype: object
    
data.isnull()
[out]
Dava     False
Steve    False
Rob      False
Wes       True
dtype: bool

可以使用data.map将字符串和有效的正则表达式方法(以lambda或其他函数的方式传递)应用到每个值上,但是在NA(null)值上会失败。为了解决这个问题,Series有面向数组的方法用于跳过NA值的字符串操作。这些方法通过Series的str属性进行调用:

data.str.contains('gmail')
[out]
Dava     False
Steve     True
Rob       True
Wes        NaN
dtype: object

正则表达式也可以结合任意的re模块选项使用:

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

data.str.findall(pattern, flags = re.IGNORECASE)
[out]
Dava     [(dave, google, com)]
Steve    [(steve, gmail, com)]
Rob        [(rob, gmail, com)]
Wes                        NaN
dtype: object

有多种方法可以进行向量化的元素检索,可以使用str.get或在str属性内部索引:

matches = data.str.match(pattern, flags = re.IGNORECASE)
matches
[out]
Dava     True
Steve    True
Rob      True
Wes       NaN
dtype: object

要访问嵌入式列表的元素,可以将索引传递给这些函数中的任意一个:

data.str.get(1)
[out]
Dava       a
Steve      t
Rob        o
Wes      NaN
dtype: object
    
data.str[0]
[out]
Dava       d
Steve      s
Rob        r
Wes      NaN
dtype: object

也可以使用字符串切片的类似语法进行向量化切片:

data.str[:5]
[out]
Dava     dave@
Steve    steve
Rob      rob@g
Wes        NaN
dtype: object

部分向量化字符串方法列表

方法描述
cat根据可选的分隔符暗元素黏合字符串
contains返回是否含有某个模式/正则表达式的布尔值数组
count模式出现次数的计数
extract使用正则表达式从字符串Series中分组抽取一个或多个字符串;返回的结果是每个分组形成一列的DataFrame
endswith等价于对每个元素使用x.endswith
startswith等价于对每个元素使用x.startswith
findall找出字符串中所有的模式.正则表达式匹配项,以列表返回
get对每个元素进行索引(获得第i个元素)
isalnum等价于内建的str.alnum(检测字符串是否有字母和数字组成)
isalpha等价于内建的str.isalpha
isdecimal等价于内建的str.isdecimal
isdigit等价于内建的str.isdight
islower等价于内建的str.islower
isnumeric等价于内建的str.isnumeric
isupper等价于内建的str.isupper
join根据传递的分隔符,将Series中的字符串联合
len计算每个字符串的长度
lower, upper转换大小写
match使用re.match将正则表达式应用到每个元素上,将匹配分组以列表形式返回
pad将空白加到字符串的左边、右边或两边
center等价于pad(side = 'both')
repeat重复值(s.str.repeat(3)等价于对每个字符串进行 *3)
replace以其他字符串替代模式/正则表达式的匹配项
slice对Series中的字符串进行切片
split以分隔符或正则表达式对字符串进行拆分
strip对字符串两侧的空白进行消除,包括换行符
rstrip消除字符串右边的空白
lstrip消除字符串左边的空白