ch07 数据清理与准备

本文介绍了数据清理与准备的关键步骤,包括处理缺失值(如基本方法、过滤和补全)、数据转换(如删除重复值、使用函数映射、替换值、重命名轴索引、离散化和分箱)以及字符串操作(如对象方法、正则表达式和向量化函数)。内容详细阐述了各种方法的使用和实例,旨在帮助读者掌握数据预处理技巧。
摘要由CSDN通过智能技术生成

一、处理缺失值

1.1 基本方法

缺失数据在pandas中呈现的⽅式有些不完美,但对于⼤多数⽤户可以保证功能正常。对于数值数据,pandas使⽤浮点值 NaN(Not a Number)表示缺失数据。我们称NaN为容易检测到的标识值。

string_data = pd.Series(['aardvark', 'artichoke', np.nan, 'avocado'])
string_data
string_data.isnull()

在pandas中,我们采⽤了R语⾔中的惯⽤法,即将缺失值表示为 NA,它表示不可⽤not available。当进⾏数据清洗以进⾏分析时,最好直接对 缺失数据进⾏分析,以判断数据采集的问题或缺失数据可能导致的偏差。
Python内置的None值在对象数组中也可以作为NA:

string_data[0] = None
string_data.isnull()

~NA处理方法
在这里插入图片描述

1.2 过滤缺失值

过滤掉缺失数据的办法有很多种。你可以通过pandas.isnull或布尔索引的⼿⼯⽅法,但dropna可能会更实⽤⼀些。

from numpy import nan as NA

# Series的dropna()
data = pd.Series([1, NA, 3.5, NA, 7])
data.dropna()
	#等价于:
data[data.notnull()]

# DataFrame的dropna()
data = pd.DataFrame([[1., 6.5, 3.], [1., NA, NA],
					 [NA, NA, NA], [NA, 6.5, 3.]])
  #丢弃任何含有缺失值的行:
data.dropna()
  #丢弃全为NA的那些行:
data.dropna(how='all')
  #丢弃列
data[4] = NA
data.dropna(axis=1, how='all')

# 留下一部分观测数据:thresh参数
	#实际上就是保留非缺失值的个数大于给定参数的行,其余的行丢弃掉
df = pd.DataFrame(np.random.randn(7, 3))
df.iloc[:4, 1] = NA
df.iloc[:2, 2] = NA

df.dropna(thresh=2) #即保留非缺失值个数大于2的行

# 判断哪些行/列存在缺失值
	#哪些行
data.isnull().any(axis=1)
	#哪些列
data.isnull().T.any(axis=1)

1.3 补全缺失值

为了补全缺失值,fillna⽅法是最主要的函数。通过⼀个常数调⽤fillna就会将缺失值替换为那个常数值:

# 填充缺失值
df.fillna(0)

# 不同列填充不同值
df.fillna({1: 0.5, 2: 0})

# fillna默认会返回新对象,但也可以对现有对象进⾏就地修改
_ = df.fillna(0, inplace=True)

# 插值方法
	#对reindexing有效的那些插值⽅法也可⽤于fillna
df = pd.DataFrame(np.random.randn(6, 3))
df.iloc[2:, 1] = NA
df.iloc[4:, 2] = NA
df

df.fillna(method='ffill')
df.fillna(method='ffill', limit=2)

# 其他填充
data = pd.Series([1., NA, 3.5, NA, 7])
data.fillna(data.mean())

fillna函数的参数
在这里插入图片描述

二、数据转换

2.1 删除重复值

data = pd.DataFrame({'k1': ['one', 'two'] * 3 + ['two'],
                     'k2': [1, 1, 2, 3, 3, 4, 4]})
data

[Out]:
0 one 1 
1 two 1 
2 one 2 
3 two 3 
4 one 3 
5 two 4 
6 two 4

DataFrame的duplicated⽅法返回⼀个布尔型Series,表示各⾏是否是重复⾏(前⾯出现过的⾏):

data.duplicated()

# 还有⼀个与此相关的drop_duplicates⽅法,它会返回⼀个DataFrame,重复的数组会被丢弃
data.drop_duplicates()

这两个⽅法默认会判断全部列,你也可以指定部分列进⾏重复项判断。假设我们还有⼀列值,且只希望根据k1列过滤重复项:

data['v1'] = range(7)
data.drop_duplicates(['k1'])

duplicated和drop_duplicates默认保留的是第⼀个出现的值组合。传⼊keep='last’则保留最后⼀个:

data.drop_duplicates(['k1', 'k2'], keep='last')

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

对于许多数据集,你可能希望根据数组、Series或DataFrame列中的值来实现转换⼯作。我们来看看下⾯这组有关⾁类的数据:

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

[Out]:
	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⽅法可以接受⼀个函数或含有映射关系的字典型对象,但是这⾥有⼀个⼩问题,即有些⾁类的⾸字⺟⼤写了,⽽另⼀些则没有。因此,我们还需要使⽤Series的str.lower⽅法,将各个值转换为⼩写:

lowercased = data['food'].str.lower()
data['animal'] = lowercased.map(meat_to_animal)
data

# 我们也可以传⼊⼀个能够完成全部这些⼯作的函数:
data['food'].map(lambda x: meat_to_animal[x.lower()])

使⽤map是⼀种实现元素级转换以及其他数据清理⼯作的便捷⽅式。

2.3 替代值

利⽤fillna⽅法填充缺失数据可以看做值替换的⼀种特殊情况。前⾯已经看到,map可⽤于修改对象的数据⼦集,⽽replace则提供了⼀种实现该功能的更简单、更灵活的⽅式。

data = pd.Series([1., -999., 2., -999., -1000., 3.])
data

# replace函数
data.replace(-999, np.nan)

# 一次性替换多个值
data.replace([-999, -1000], np.nan)

# 让每个值有不同的替换值
data.replace([-999, -1000], [np.nan, 0])
data.replace({-999: np.nan, -1000: 0})

注意:data.replace⽅法与data.str.replace不同,后者做的是字符串的元素级替换。我们会在后⾯学习Series的字符串⽅法。

2.4 重命名轴索引

跟Series中的值⼀样,轴标签也可以通过函数或映射进⾏转换,从⽽得到⼀个新的不同标签的对象。轴还可以被就地修改,⽽⽆需新建⼀个数据结构。

data = pd.DataFrame(np.arange(12).reshape((3, 4)),
                    index=['Ohio', 'Colorado', 'New York'],
                    columns=['one', 'two', 'three', 'four'])
data

[Out]:
			one		two		three	four
Ohio    	0		1		2		3
Colorado	4		5		6		7
New York	8		9		10		11

# 轴索引的map方法
transform = lambda x: x[:4].upper()
data.index.map(transform)
	#赋值给index,这样就可以对DataFrame进⾏就地修改
data.index = data.index.map(transform)

# 如果想要创建数据集的转换版(⽽不是修改原始数据),可以使用rename函数:
data.rename(index=str.title, columns=str.upper)

# rename可以结合字典型对象实现对部分轴标签的更新:
data.rename(index={'OHIO': 'INDIANA'},
            columns={'three': 'peekaboo'})

# rename函数默认赋值数据集,如果希望就地修改,可以传入inplace=True
data.rename(index={'OHIO': 'INDIANA'}, inplace=True)

2.5 离散化和分箱

连续值经常需要离散化,或者分离成“箱子”进行分析。假设有一组人的年龄数据:

ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]

# cut函数进行分箱
bins = [18, 25, 35, 60, 100]
cats = pd.cut(ages, bins)
cats

pandas返回的是⼀个特殊的Categorical对象。结果展示了 pandas.cut划分的箱。你可以将其看做⼀组表示箱名称的字符串。它的内部含有⼀个categories(类别)数组,它制定了不同类别名称以及codes属性中的ages(年龄)数据标签:

cats.codes
cats.categories
pd.value_counts(cats)

跟“区间”的数学符号⼀样,圆括号表示开端,⽽⽅括号则表示闭端(包括)。哪边是闭端可以通过right=False进⾏修改:

pd.cut(ages, [18, 26, 36, 61, 100], right=False)

你可以通过传递⼀个列表或数组到labels,设置自定义的箱名:

group_names = ['Youth', 'YoungAdult', 'MiddleAged', 'Senior']
pd.cut(ages, bins, labels=group_names)

如果你传给cut整数个的箱来代替显式的箱边,pandas将根据数据中的最小值和最大值计算出等长等箱。考虑一些均匀分布的数据被切成四份的情况:

data = np.random.rand(20)
pd.cut(data, 4, precision=2)
#precision=2表示保留十进制精度限制在两位

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

data = np.random.randn(1000)  # Normally distributed
cats = pd.qcut(data, 4)  # Cut into quartiles
cats
pd.value_counts(cats)

# 传入自定义的分位数:
pd.qcut(data, [0, 0.1, 0.5, 0.9, 1.])

2.6 检测和过滤异常值

过滤或变换异常值(outlier)在很⼤程度上就是运⽤数组运算。来看⼀个含有正态分布数据的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.056269	0.043063	0.021109   -0.007811
std		0.993739	0.995920	1.008986	0.995057
min	   -3.428254   -3.645860   -3.184377   -3.745356
25%	   -0.747963   -0.599807   -0.642943   -0.697084
50%	   -0.091364	0.047101   -0.016127   -0.031732
75%     0.620197	0.738466	0.695298	0.692355
max	    3.366626	2.653656	3.525865	2.735527

# 找出某列中绝对值大小超过3的值
col = data[2]
col[np.abs(col) > 3]

# 要选出全部含有“超过3或-3的值”的⾏,你可以在布尔型DataFrame中使⽤any⽅法:
data[(np.abs(data) > 3).any(1)]

# 根据这些条件,就可以对值进⾏设置。下⾯的代码可以将值限制在区间-3到3以内:
data[np.abs(data) > 3] = np.sign(data) * 3
data.describe()

# 根据数据的值是正还是负,np.sign(data)可以⽣成1和-1:
np.sign(data).head()

2.7 置换和随机抽样

利⽤numpy.random.permutation函数可以轻松实现对Series或DataFrame的列的排列⼯作(permuting,随机重排序)。通过需要排列的轴的⻓度调⽤permutation,可产⽣⼀个表示新顺序的整数数组:

df = pd.DataFrame(np.arange(5 * 4).reshape((5, 4)))
sampler = np.random.permutation(5)
sampler

# 整数数组可以用在基于iloc的索引或等价的take函数中:
df
df.take(sampler)

# 要选出一个不含有替代值的随机子集,可以使用Series和DataFrame的sample方法:
df.sample(n=3)

# 要通过替换的⽅式产⽣样本(允许重复选择),可以传递replace=True到sample:
choices = pd.Series([5, 7, -1, 6, 4])
draws = choices.sample(n=10, replace=True)
draws

2.8 计算指标/虚拟变量

将分类变量转换为“虚拟”或“指标”是另一种用于统计建模或机器学习的转换操作。如果DataFrame的某⼀列中含有k个不同的值,则可以派⽣出⼀个k列矩阵或DataFrame(其值全为1和0)。pandas有⼀个get_dummies函数可以实现该功能(其实⾃⼰动⼿做⼀个也不难)。使⽤之前的⼀个DataFrame例⼦:

df = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'b'],
                   'data1': range(6)})
pd.get_dummies(df['key'])

# 给DataFrame的列加前缀:prefix参数
dummies = pd.get_dummies(df['key'], prefix='key')
df_with_dummy = df[['data1']].join(dummies)
df_with_dummy

如果DataFrame中的一行属于多个类别,则情况略微复杂:

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		 	Comedy
		Part II (1995)
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

要为每个genre添加指标变量就需要做⼀些数据处理。

# 首先,从数据集中提取出所有不同的流派的列表:
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.
# 为了实现该功能,我们使用dummie.columns来计算每一个流派的列指标:
gen = movies.genres[0]
gen.split('|')
dummies.columns.get_indexer(gen.split('|'))

# 之后,使用.loc根据这些指标来设置值
for i, gen in enumerate(movies.genres):
    indices = dummies.columns.get_indexer(gen.split('|'))
    dummies.iloc[i, indices] = 1
# 上述循环与以下循环是等价的:
for i, gen in enumerate(movies.genres):
    gen_list = gen.split('|')
    for j in gen_list:
        dummies[j].iloc[i] = 1

# 然后,和以前⼀样,再将其与movies合并起来:
movies_windic = movies.join(dummies.add_prefix('Genre_'))
movies_windic.iloc[0]

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

np.random.seed(12345)
values = np.random.rand(10)
values
bins = [0, 0.2, 0.4, 0.6, 0.8, 1]
pd.get_dummies(pd.cut(values, bins))

三、字符串操作

3.1 字符串对象方法

对于许多字符串处理和脚本应⽤,内置的字符串⽅法已经能够满⾜要求了。例如,以逗号分隔的字符串可以⽤split拆分成数段:

val = 'a,b,  guido'
val.split(',')

split常常与strip⼀起使⽤,以去除空⽩符(包括换⾏符):

pieces = [x.strip() for x in val.split(',')]
pieces

利⽤加法,可以将这些⼦字符串以双冒号分隔符的形式连接起来:

first, second, third = pieces
first + '::' + second + '::' + third

#但这种⽅式并不是很实⽤。更常用的方式是利用join方法传入一个列表或元组
'::'.join(pieces)

检测⼦串的最佳⽅式是利⽤Python的in关键字,还可以使⽤index和find:

'guido' in val
val.index(',')
val.find(':')
#注意find和index的区别:如果找不到字符串,index将会引发⼀个异常(⽽不是返回-1)

与此相关,count可以返回指定⼦串的出现次数:

val.count(',')

replace⽤于将指定模式替换为另⼀个模式。通过传⼊空字符串,它也常常⽤于删除模式:

val.replace(',', '::')
val.replace(',', '')

3.2 正则表达式

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值