第7章 数据规整化:清理、转换、合并、重塑(5)
数据转换
前部分都是数据的重排,对数据的处理还包括过滤,清理等。
移除重复数据
- duplicated()
- drop_duplicates()
data = DataFrame({'k1':['one']*3+['two']*4,'k2':[1,1,2,3,3,4,4]})
Out[126]:
k1 k2
0 one 1
1 one 1
2 one 2
3 two 3
4 two 3
5 two 4
6 two 4
###返回布尔型Series,表示各行是否重复
data.duplicated()
Out[127]:
0 False
1 True
2 False
3 False
4 True
5 False
6 True
dtype: bool
####返回移除重复行的DataFrame
data.drop_duplicates() ###注意索引的保留
Out[128]:
k1 k2
0 one 1
2 one 2
3 two 3
5 two 4
###duplicated/drop_duplicates默认判断全部列,可以指定部分列进行重复项判断
data['v1'] = range(7)
Out[130]:
k1 k2 v1
0 one 1 0
1 one 1 1
2 one 2 2
3 two 3 3
4 two 3 4
5 two 4 5
6 two 4 6
data.drop_duplicates(['k1']) ###仅判断k1列中的重复元素
Out[133]:
k1 k2 v1
0 one 1 0
3 two 3 3
####此处书上错误
####duplicated/drop_duplicates默认保留第一个出现的值的组合,keep参数可以控制保留项
####keep三个参数,'first' 'last' False,默认为'first',为False是删除所有重复项,first/last分别是删除重复项但保留第一个/最后一个出现的重复项
data.drop_duplicates(['k1','k2'],keep = 'last')
Out[135]:
k1 k2 v1
1 one 1 1
2 one 2 2
4 two 3 4
6 two 4 6
利用函数或映射进行数据转换
data = 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[137]:
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
####或者引入lamda。 data['animal'] = data['food'].map(lamda x: meat_to_animal[x.lower()])
data['animal'] = data['food'].map(str.lower).map(meat_to_animal)
替换值
- fillna填充缺失数据
- map修改对象的数据子集
- replace
data = Series([1,-999,2,-999,-1000,3]) ##-999代表数据缺失的标记值。要将其替换为NAN
data.replace(-999,np.nan)###用np.nan替换-999
data.replace([-999,-1000],np.nan)####一次替换多个值,-999和-1000都替换为NAN
data.replace([-999,-1000],[np.nan,0])####一一对应替换 -999→NAN,-1000→0
data.replace({-999:np.nan,-1000:0}) ####传入字典参数
重命名轴索引
与Series一样,轴标签中的元素也可以通过函数或者映射进行转换,从而得到新对象。
data = DataFrame(np.arange(12).reshape(3,4),index=['Ohio','Colorado','New York'],columns=['one','two','three','four'])
data.index = data.index.map(str.upper) ###index中字符全替换为大写再赋值回去
date_new = data.rename(index = str.title,columns=str.upper)##创建转化后的新的数据集,保留原始数据
####str.title将字符串转化为标题版本,即首字母大写,其余小写
###rename结合字典可以对部分轴标签更新
data_new2 = data.rename(index={'OHIO':'IDIANA'},columns={'three':'peekaboo'})
Out[154]:
one two peekaboo four
IDIANA 0 1 2 3
COLORADO 4 5 6 7
NEW YORK 8 9 10 11
####rename就地修改,直接修改源数据,不返回新数据
_ = data.rename(index={'OHIO':'INDIANA'},inplace = True)
In [158]:data
Out[158]:
one two three four
INDIANA 0 1 2 3
COLORADO 4 5 6 7
NEW YORK 8 9 10 11
离散化和区间划分
连续数据的拆分
ages = [20,22,25,27,21,23,37,31,61,45,41,32]
bins = [18,25,35,60,100] ##设定划分区间18-25,26-35,36-60,61-100
cats = pd.cut(ages,bins) ##返回一个Categorical对象,默认区间为左开右闭,通过right参数来调整,right = False为左闭右开,默认right = True
In [164]:cats
Out[164]:
[(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对象的描述与书上有不同
####Categorical对象由两个部分组成,一个是划分的区间,还有一个是原数据划分后的结果,其中有两个属性,Categorical.categories(返回划分的区间,并表明闭区间在左侧还是右侧)和Categorical.codes(表明原数据中每个数据所处的区间,用划分区间的下标索引表示,从0开始)
In [169]:cats.categories
Out[169]:
IntervalIndex([(18, 25], (25, 35], (35, 60], (60, 100]]
closed='right',
dtype='interval[int64]')
In [170]:cats.codes
Out[170]: array([0, 0, 0, 1, 0, 0, 2, 1, 3, 2, 2, 1], dtype=int8)
####给每个区间计数
In [172]:pd.value_counts(cats)
Out[172]:
(18, 25] 5
(35, 60] 3
(25, 35] 3
(60, 100] 1
dtype: int64
####可以给每个区间设定一个名称,通过labels参数传入每个区间的名字
group_name = ['Youth','YoungAdult','MiddleAged','Senior']
pd.cut(ages,bins,labels = group_name)
Out[175]:
[Youth, Youth, Youth, YoungAdult, Youth, ..., YoungAdult, Senior, MiddleAged, MiddleAged, YoungAdult]
Length: 12
Categories (4, object): [Youth < YoungAdult < MiddleAged < Senior]
###如果传入的是区间数量而不是确切边界,则会根据数据最小值和最大值,计算等长区间
data = np.random.randn(20)
pd.cut(data,4,precision = 2)###设置区间边界精度
Out[182]:
[(0.99, 1.94], (-1.87, -0.91], (-0.91, 0.037], (0.037, 0.99], (0.99, 1.94], ..., (0.99, 1.94], (0.99, 1.94], (-1.87, -0.91], (-0.91, 0.037], (-1.87, -0.91]]
Length: 20
Categories (4, interval[float64]): [(-1.87, -0.91] < (-0.91, 0.037] < (0.037, 0.99] < (0.99, 1.94]]
######qcut与cut类似,但是是根据样本分位数对数据进行区间划分,cut可能无法使区间内含有相同数量的数据,但是qcut可以获得数量基本相同的区间
data = np.random.randn(1000)
cats = pd.qcut(data,4) ####按四分位数进行划分
Out[184]:
[(0.606, 2.823], (-0.683, -0.0468], (-0.683, -0.0468], (-0.0468, 0.606], (0.606, 2.823], ..., (-0.683, -0.0468], (-3.382, -0.683], (0.606, 2.823], (-3.382, -0.683], (0.606, 2.823]]
Length: 1000
Categories (4, interval[float64]): [(-3.382, -0.683] < (-0.683, -0.0468] < (-0.0468, 0.606] < (0.606, 2.823]]
In [186]:pd.value_counts(cats)
Out[186]:
(0.606, 2.823] 250
(-0.0468, 0.606] 250
(-0.683, -0.0468] 250
(-3.382, -0.683] 250
dtype: int64
###可自定义分位数,0-1之间的数值,包含端点
new_cats = pd.qcut(data,[0,0.1,0.5,0.9,1])
In [188]:pd.value_counts(new_cats)
Out[188]:
(-0.0468, 1.206] 400
(-1.329, -0.0468] 400
(1.206, 2.823] 100
(-3.382, -1.329] 100
dtype: int64
检测过滤异常值
异常值即孤立点、离群点
data = DataFrame(np.random.randn(1000,4)) ###生成4列,每列都是正态分布数的DataFrame对象
In [190]:data.describe() ####data属性
Out[190]:
0 1 2 3
count 1000.000000 1000.000000 1000.000000 1000.000000
mean 0.036955 -0.030808 0.004385 0.017893
std 1.005653 0.979606 1.003002 0.999522
min -3.400193 -3.871381 -3.626467 -3.561846
25% -0.634620 -0.667826 -0.718142 -0.681812
50% 0.038195 -0.050365 -0.040234 0.024882
75% 0.768188 0.602451 0.684530 0.682487
max 2.859131 3.395861 3.192252 3.020070
col = data[3]
In [192]:col[np.abs(col)>3] ###找出3列中绝对值大于3的值
Out[192]:
29 3.020070
279 -3.055511
500 -3.561846
711 -3.541687
Name: 3, dtype: float64
###找出全部含有绝对值大于3的值的行
data[(np.abs(data)>3).any(axis = 1)] ###设定any方法中的axis = 1表示每行中含有绝对值大于3的值
Out[197]:
0 1 2 3
17 -0.748195 -3.159036 -0.404608 0.904498
29 -0.164734 -1.256519 -0.724999 3.020070
109 0.452139 -1.042520 3.143075 1.048354
279 -1.958829 0.087400 -0.392875 -3.055511
392 0.045182 2.131359 -3.445043 0.416538
500 -2.099903 -1.963188 -0.240716 -3.561846
556 -0.600726 0.470719 -3.626467 -0.585577
576 -0.460733 0.562222 3.192252 -1.666503
682 -0.716962 3.395861 0.047119 0.173432
711 -0.473312 -0.193344 1.278363 -3.541687
751 -3.110940 0.388629 -0.221459 0.790747
769 0.416308 -3.653692 1.500504 -0.837095
840 -3.400193 1.916887 -1.275942 0.483493
887 0.159239 1.655584 -3.086544 -0.213076
959 0.227707 3.008310 0.691871 0.501017
967 0.304056 -3.871381 -1.409443 0.430060
####限制data中所有值在[-3,3]
data[np.abs(data)>3]=np.sign(data)*3 ###返回符号 ±,sign(-3)=-1,sign(3)=1
In [203]:data.describe()
Out[203]:
0 1 2 3
count 1000.000000 1000.000000 1000.000000 1000.000000
mean 0.037466 -0.029528 0.005207 0.019032
std 1.004020 0.972518 0.998189 0.995644
min -3.000000 -3.000000 -3.000000 -3.000000
25% -0.634620 -0.667826 -0.718142 -0.681812
50% 0.038195 -0.050365 -0.040234 0.024882
75% 0.768188 0.602451 0.684530 0.682487
max 2.859131 3.000000 3.000000 3.000000
排列和随机采样
- numpy.random.permutation
df = DataFrame(np.arange(20).reshape(5,4))
sampler = np.random.permutation(5)####返回一个随机排列的数列,或者范围内的随机排列的数列,如果传入参数是个多维ndarray,则沿着第一个索引随机排列,即二维数组,沿着行的索引随机排列
###shuffle有相同效果,但是只接受array_like,不能接受int,且不返回打乱后的数组,是inplace修改
In [207]:sampler
Out[207]: array([3, 1, 2, 0, 4])
df.take(sampler)####沿着轴返回给定顺寻索引的dataframe数据,默认axis=0
Out[208]:
0 1 2 3
3 12 13 14 15
1 4 5 6 7
2 8 9 10 11
0 0 1 2 3
4 16 17 18 19
df.take(np.random.permutation(len(df))[:3])###切取permutation生成的随机排列array的前三个,再按照索引顺序切取df数据
Out[209]:
0 1 2 3
1 4 5 6 7
3 12 13 14 15
4 16 17 18 19
bag = np.array([5,7,-1,6,4])
sampler = np.random.randint(low = 0,high = len(bag),size = 10)###randint(最小值,最大值,个数)
Out[212]: array([2, 2, 0, 4, 2, 2, 1, 4, 1, 3])
draws = bag.take(sampler)
Out[214]: array([-1, -1, 5, 4, -1, -1, 7, 4, 7, 6])
计算指标/哑变量
- get_dummies
df = DataFrame({'key':['b','b','a','c','a','b'],'data1':range(6)})
Out[220]:
data1 key
0 0 b
1 1 b
2 2 a
3 3 c
4 4 a
5 5 b
pd.get_dummies(df['key'])####哑矩阵将传入参数不同种类展开为不同列,例子中abc展开,矩阵中只有0,1两个值,0代表非,1代表是
Out[221]:
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
####给哑矩阵每个列加一个前缀名,参数prefix控制
dummies = pd.get_dummies(df['key'],prefix = 'key')
Out[223]:
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
#####与df['data1']合并为一个DataFrame
df_with_dummy = df[['data1']].join(dummies)
Out[225]:
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
####dummies和cut结合可以清晰展示每个数据的区间
values = np.random.rand(10)
Out[227]:
array([ 0.39412391, 0.07717691, 0.5872264 , 0.25836265, 0.2311182 ,
0.93356344, 0.16665829, 0.96166322, 0.13581445, 0.80504075])
bins = [0,0.2,0.4,0.6,0.8,1]
pd.get_dummies(pd.cut(values,bins))
Out[229]:
(0.0, 0.2] (0.2, 0.4] (0.4, 0.6] (0.6, 0.8] (0.8, 1.0]
0 0 1 0 0 0
1 1 0 0 0 0
2 0 0 1 0 0
3 0 1 0 0 0
4 0 1 0 0 0
5 0 0 0 0 1
6 1 0 0 0 0
7 0 0 0 0 1
8 1 0 0 0 0
9 0 0 0 0 1
字符串操作
val = 'a,b, guido'
val.split(',')###逗号分隔字符串
Out[232]: ['a', 'b', ' guido']
pieces = [x.strip() for x in val.split(',')]###strip()剪去空白符,包括换号符等
Out[234]: ['a', 'b', 'guido']
'::'.join(pieces)###用"::"连接pieces中的字符串
Out[235]: 'a::b::guido'
'guido' in val ###判断是否包含子串
Out[236]: True
val.index(',')####返回第一个出现','符号的位置
Out[237]: 1
val.find(':')###在val中找':'
Out[238]: -1
val.count(',')##返回','出现的次数
Out[239]: 2
val.replace(',','::')###用'::'替换',',传入空字符串可以执行删除操作
Out[240]: 'a::b:: guido'
Python内置字符串方法
方法 | 说明 |
---|---|
count | 返回子串在字符串中出现的次数 |
endswith、startswith | 如果字符串以某个后缀结尾(以某个前缀开头),则返回True |
join | 字符串连接 |
index | 如果在字符串中找到子串,则返回子串第一个字符所在的位置,如果没找到引发异常 |
find | 如果在字符串中找到子串,则返回子串第一个字符所在的位置,如果没找到返回-1 |
rfind | 如果在字符串中找到子串,则返回最后一个发现的子串第一个字符所在的位置,如果没找到返回-1 |
replace | 替换 |
strip、rstrip、lstrip | 去除空白符 |
split | 用制定分隔符拆分字符串 |
lower、upper | 字母大小写转换 |
ljust、rjust | 用空格(或其他字符)填充字符串空白侧以返回符合最低宽度的字符串 |