许多人选择使用通用编程语言或unix文本处理工具(如sed或awk)对数据格式进行专门处理。
幸运的是,pandas和python标准库提供了一组高级的、灵活的、高效的核心函数和算法,将数据规整化正确的形式。
1. 合并数据集:
pandas对象中的数据可以通过一些内置的方式进行合并:
(1)pandas.merge可根据一个名多个键将不同DataFrame中的行连接起来。sql关系型数据库也有merge法,它是用merge实现的就是数据库的连接操作(inner内连接交集,outer外连接,左连接,右连接=>并集)。
(2)pandas.concat可以沿着一条轴将多个对象堆叠到一起。
(3)实例方法combine_first可以将重复数据编接在一起,用一个对象中的值填充另一个对象中的缺失值。
下面分别讲解这些方法:
数据库风格的DataFrame合并:
数据集的合并(merge)或连接(join)运算是通过一个或多个键将行连接起来的。这些运算是关系型数据库的核心。
pandas的merge函数是对数据应用这些算法的主要切入点。
(1)merge的用法
import pandas as pd
import numpy as np
from pandas import Series,DataFrame
df1=DataFrame({'key':['b','b','a','c','a','a','b'],
'data1':range(7)})
df2=DataFrame({'key':['a','b','d'],
'data2':range(3)})
print df1
#输出结果如下:
# data1 key
# 0 0 b
# 1 1 b
# 2 2 a
# 3 3 c
# 4 4 a
# 5 5 a
# 6 6 b
# print df2
# #输出结果如下:
# data2 key
# 0 0 a
# 1 1 b
# 2 2 d
(1)这是一种多对一的合并:df1中的数据有多个被标记为a和b行,而df2中key列的每个值则仅对应一行。
这些对象调用merge即可得到
print pd.merge(df1,df2)
#输出结果如下:
# data1 key data2
# 0 0 b 1
# 1 1 b 1
# 2 6 b 1
# 3 2 a 0
# 4 4 a 0
# 5 5 a 0
# #注意:上面的例子并没有指明要用哪个列进行连接。如果没有指定,merge就会将重叠列的列名当做键。
(2)下面指定列:用能数on=
# print pd.merge(df1,df2,on='key') #输出结果与上面一样
(3)如果两个对象的列名不同,也可以分别进行指定:left_on,right_on
df3=DataFrame({'lkey':['b','b','a','c','a','a','b'],
'data1':range(7)})
df4=DataFrame({'rkey':['a','b','d'],
'data2':range(3)})
print pd.merge(df3,df4,left_on='lkey',right_on='rkey')
#输出结果如下:
# data1 lkey data2 rkey
# 0 0 b 1 b
# 1 1 b 1 b
# 2 6 b 1 b
# 3 2 a 0 a
# 4 4 a 0 a
# 5 5 a 0 a
#
#注意:结果里面c和d以及与之相关的数据消失了。默认情况下,merge做的是"inner"连接:结果中的键是交集。
# 而其它外连接left,right,outer求取的是键的并集,组合了左连接和右连接的效果。
(4) outer外连接:并集,两个数据Key都要
# print pd.merge(df1,df2,how='outer') #外连接,两边的key都要传入
# #输出结果如下:并集,df1,df2所有的值都放入
# # data1 key data2
# # 0 0.0 b 1.0
# # 1 1.0 b 1.0
# # 2 6.0 b 1.0
# # 3 2.0 a 0.0
# # 4 4.0 a 0.0
# # 5 5.0 a 0.0
# # 6 3.0 c NaN
# # 7 NaN d 2.0
(5) left左连接:并集,左连接是以左边的为主
# print pd.merge(df1,df2,on='key',how='left') #左连接
# #输出结果如下: 左连接是以左边的为主,即df1为主,顾只有df1的key
# # data1 key data2
# # 0 0 b 1.0
# # 1 1 b 1.0
# # 2 2 a 0.0
# # 3 3 c NaN
# # 4 4 a 0.0
# # 5 5 a 0.0
# # 6 6 b 1.0
(6)right右连接:并集,右连接是以右边的为主
# print pd.merge(df1,df2,how='right') #右连接
# #输出结果如下:右连接,以df2的为主,顾只有df2的key,其它的值是全部写入
# # data1 key data2
# # 0 0.0 b 1
# # 1 1.0 b 1
# # 2 6.0 b 1
# # 3 2.0 a 0
# # 4 4.0 a 0
# # 5 5.0 a 0
# # 6 NaN d 2
(7)inner内连接:交集,默认就是内连接
# print pd.merge(df1,df2,how='inner') #即merge默认情况下是inner,inner是交集
# #输出结果如下:取df1,df2都有key
# # data1 key data2
# # 0 0 b 1
# # 1 1 b 1
# # 2 6 b 1
# # 3 2 a 0
# # 4 4 a 0
# # 5 5 a 0
(8)若多个键要进行合并,传入一个由列名组成的列表即可
left=DataFrame({'key1':['foo','foo','bar'],
'key2':['one','two','one'],
'lval':[1,2,3]})
right=DataFrame({'key1':['foo','foo','bar','bar'],
'key2':['one','one','one','two'],
'rval':[4,5,6,7]})
print left
#输出结果如下:
# key1 key2 lval
# 0 foo one 1
# 1 foo two 2
# 2 bar one 3
print right
#输出结果如下:
# key1 key2 rval
# 0 foo one 4
# 1 foo one 5
# 2 bar one 6
# 3 bar two 7
print pd.merge(left,right,on=['key1','key2'],how='outer')
#输出结果如下:看key1,key2两个键对应的lval的值。外连接,即所有的key都要。
#right中的key组合[foo,one],[foo,one],[foo,two]...用right的key看left有没有。
# key1 key2 lval rval
# 0 foo one 1.0 4.0
# 1 foo one 1.0 5.0
# 2 foo two 2.0 NaN
# 3 bar one 3.0 6.0
# 4 bar two NaN 7.0
(9)合并运算需要考虑对重复列名的处理,merge有一个实用的suffixes选项,用于指定附加到左右两个DataFrame对象的重叠列名上的字符串。
print pd.merge(left,right,on='key1') #只用单个key,它就有单个key1分别和left,right的key2的组合
#输出结果如下:
# key1 key2_x lval key2_y rval
# 0 foo one 1 one 4
# 1 foo one 1 one 5
# 2 foo two 2 one 4
# 3 foo two 2 one 5
# 4 bar one 3 one 6
# 5 bar one 3 two 7
print pd.merge(left,right,on='key1',suffixes=('_left','_right'))
#输出结果如下:
# key1 key2_left lval key2_right rval
# 0 foo one 1 one 4
# 1 foo one 1 one 5
# 2 foo two 2 one 4
# 3 foo two 2 one 5
# 4 bar one 3 one 6
# 5 bar one 3 two 7
merger函数的参数
参数 说明
left 参与合并的左侧DataFrame
right 参与合并的右侧DataFrame
how “inner”、“outer”、“left”、“right”其中之一。默认为"inner"(交集),其它为并集
on 用于连接的列名。必须存在于左右两个DataFrame对象中。如果未指定,且其它连接键也未指定,由以
left和right列名的交集作为连接键
left_on 左侧DataFrame中用作连接键的列
right_on 右侧DataFrame中用作连接键的列
left_index 将左侧的行索引用作其连接键
righgt_index 将右侧的行索引用作其连接键
sort 根据连接键对合并后的数据进行排序,默认为True.有时处理大数据集时,禁用该选项可获得更好的性能
suffixes 字符串值元组,用于追加到重叠列名的末尾,默认为('_x','_y').
copy 设置为False,可以在某些特殊情况下避免将数据复制到结果数据结构中。默认总是复制。
索引上DataFrame的合并:
DataFrame中的连接键位于其索引中。在这种情况下,可以传入left_index=True或right_index=True(或两个都传)
以说明索引应该被用作连接键。
(1)merge的用法:索引上合并用left_index=True、right_index=True
import pandas as pd
import numpy as np
from pandas import Series,DataFrame
(1)DataFrame中的连接键位于其索引中:left_index=True或right_index=True(或两个都传)来说明索引应该被用作连接键。
left1=DataFrame({'key':['a','b','a','a','b','c'],
'value':range(6)})
right1=DataFrame({'group_val':[3.5,7]},index=['a','b'])
print left1
#输出结果如下:
# key value
# 0 a 0
# 1 b 1
# 2 a 2
# 3 a 3
# 4 b 4
# 5 c 5
print right1
#输出结果如下:
# group_val
# a 3.5
# b 7.0
print pd.merge(left1,right1,left_on='key',right_index=True)
#输出结果如下:
# key value group_val
# 0 a 0 3.5
# 2 a 2 3.5
# 3 a 3 3.5
# 1 b 1 7.0
# 4 b 4 7.0
(2)由于默认的merge方法是求取连接键的交集,因此可以通过外连接的方式得到它们的并集。how='outer'
print pd.merge(left1,right1,left_on='key',right_index=True,how='outer')
#输出结果如下:
# key value group_val
# 0 a 0 3.5
# 2 a 2 3.5
# 3 a 3 3.5
# 1 b 1 7.0
# 4 b 4 7.0
# 5 c 5 NaN
(3)层次化索引,多层行/列索引
lefth=DataFrame({'key1':['Ohio','Ohio','Ohio','Nevada','Nevada'],
'key2':[2000,2001,2002,2001,2002],
'data':np.arange(5.)})
righth=DataFrame(np.arange(12).reshape(6,2),
index=[['Nevada','Nevada','Ohio','Ohio','Ohio','Ohio'],
[2001,2000,2000,2000,2001,2002]],
columns=['event1','event2'])
print lefth
#输出结果如下:
# data key1 key2
# 0 0.0 Ohio 2000
# 1 1.0 Ohio 2001
# 2 2.0 Ohio 2002
# 3 3.0 Nevada 2001
# 4 4.0 Nevada 2002
print righth
#输出结果如下:
# event1 event2
# Nevada 2001 0 1
# 2000 2 3
# Ohio 2000 4 5
# 2000 6 7
# 2001 8 9
# 2002 10 11
#必须以列表的形式指明用作合并键的多个列(注意对重复索引值的处理)
#left_on左侧DataFrame作为连接键,即lefth,而索引是right_index,即右侧数据为行索引即righth的行索引为行索引
print pd.merge(lefth,righth,left_on=['key1','key2'],right_index=True)
#没有how参数,默认是inner连接,即交集,取lefth的['key1','key2']的数据与righth的行有交集,即相同的取出
#输出结果如下:
# data key1 key2 event1 event2
# 0 0.0 Ohio 2000 4 5
# 0 0.0 Ohio 2000 6 7
# 1 1.0 Ohio 2001 8 9
# 2 2.0 Ohio 2002 10 11
# 3 3.0 Nevada 2001 0 1
#(4)外连接:how='outer' 并集
print pd.merge(lefth,righth,left_on=['key1','key2'],right_index=True,how='outer')
#输出结果如下:
# data key1 key2 event1 event2
# 0 0.0 Ohio 2000 4.0 5.0
# 0 0.0 Ohio 2000 6.0 7.0
# 1 1.0 Ohio 2001 8.0 9.0
# 2 2.0 Ohio 2002 10.0 11.0
# 3 3.0 Nevada 2001 0.0 1.0
# 4 4.0 Nevada 2002 NaN NaN
# 4 NaN Nevada 2000 2.0 3.0
#(5)用merge同时使用合并双方的索引
left2=DataFrame([[1.,2.],[3.,4.],[5.,6.]],index=['a','c','e'],
columns=['Ohio','Nevada'])
right2=DataFrame([[7.,8.],[9.,10.],[11.,12.],[13,14]],
index=['b','c','d','e'],columns=['Missouri','Alabama'])
print left2
#输出结果如下:
# Ohio Nevada
# a 1.0 2.0
# c 3.0 4.0
# e 5.0 6.0
print right2
#输出结果如下:
# Missouri Alabama
# b 7.0 8.0
# c 9.0 10.0
# d 11.0 12.0
# e 13.0 14.0
print pd.merge(left2,right2,how='outer',left_index=True,right_index=True)
#同时使用了left_index,right_index同时合并双方索引,且outer外连接交集
#输出结果如下:
# Ohio Nevada Missouri Alabama
# a 1.0 2.0 NaN NaN
# b NaN NaN 7.0 8.0
# c 3.0 4.0 9.0 10.0
# d NaN NaN 11.0 12.0
# e 5.0 6.0 13.0 14.0
(2)join的用法:索引上合并用left_index=True、right_index=True
DataFrame的一个实例方法Join:更为方便地实现按索引合并。它还可以用于合并多个带有相同或相似索引的DataFrame对象,而不管它们之间有没有重叠的列。
#Join:在连接键上是做左连接
# (1)DataFrame的一个实例方法Join:更为方便地实现按索引合并。不管它们之间有没有重叠的列。
#上面的例子可以编写如下:
print left2.join(right2,how='outer') #类似python的字符串连接Join
#输出结果如下:
# Ohio Nevada Missouri Alabama
# a 1.0 2.0 NaN NaN
# b NaN NaN 7.0 8.0
# c 3.0 4.0 9.0 10.0
# d NaN NaN 11.0 12.0
# e 5.0 6.0 13.0 14.0
# (2)它还支持DataFrame的索引跟调用者DataFrame的某个列之间的连接(即第一个DataFrame的行索引与第二个DataFrame的列连接)
print left1.join(right1,on='key')
#输出结果如下:
# key value group_val
# 0 a 0 3.5
# 1 b 1 7.0
# 2 a 2 3.5
# 3 a 3 3.5
# 4 b 4 7.0
# 5 c 5 NaN
#(3)可以向join传入一组DataFrame
another=DataFrame([[7.,8.],[9.,10.],[11.,12.],[16.,17.]],
index=['a','c','e','f'],columns=['New York','Oregon'])
print another
#输出结果如下:
# New York Oregon
# a 7.0 8.0
# c 9.0 10.0
# e 11.0 12.0
# f 16.0 17.0
print left2.join([right2,another]) #left2左分别连接right2,another
# left2内容如下:
# # Ohio Nevada
# # a 1.0 2.0
# # c 3.0 4.0
# # e 5.0 6.0
# right2内容如下:
# # Missouri Alabama
# # b 7.0 8.0
# # c 9.0 10.0
# # d 11.0 12.0
# # e 13.0 14.0
#left2.join([right2,another])输出结果如下:
# Ohio Nevada Missouri Alabama New York Oregon
# a 1.0 2.0 NaN NaN 7.0 8.0
# c 3.0 4.0 9.0 10.0 9.0 10.0
# e 5.0 6.0 13.0 14.0 11.0 12.0
另一种数据合并运算也被称作连接、绑定或堆叠。numpy有一个用于合并原始Numpy数组的concatenation函数。concatenate是Numpy的方法,
而concate是pandas的方法。
concatenate连接:
arr=np.arange(12).reshape((3,4))
print arr
#输出的结果如下:
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print np.concatenate([arr,arr],axis=1) #axis=1是列
#输出的结果如下:
# [[ 0 1 2 3 0 1 2 3]
# [ 4 5 6 7 4 5 6 7]
# [ 8 9 10 11 8 9 10 11]]
concate连接:
# 如果各对象其他轴上的索引不同,那些轴应该是做并集还是交集?
# 结果对象中的分组需要各不相同吗?
# 用于连接的轴axis重要吗?
concate用于解决上面的三个问题,它与concatenate没多大区别,只是多了一些参数
(1)三个没有重叠索引的Series,对这些对象调用concat可以将值和索引粘合在一起
s1=Series([0,1],index=['a','b'])
s2=Series([2,3,4],index=['c','d','e'])
s3=Series([5,6],index=['f','g'])
print s1
# a 0
# b 1
print s2
# c 2
# d 3
# e 4
print s3
# f 5
# g 6
print pd.concat([s1,s2,s3])
#输出结果如下:
# a 0
# b 1
# c 2
# d 3
# e 4
# f 5
# g 6
(2)默认情况下,concat是在axix=0行上作用的,最终产生一个新的Series.如果传入axis=1,则结果就会变成一个DataFrame(axis=1是列)
#series行向合并,本来series只有行索引,故产生一个新的Series.如果列合并,有几个数组,列不一定相同,所以会产生一个DataFrame
print pd.concat([s1,s2,s3],axis=1)
#输出结果如下:
# 0 1 2
# a 0.0 NaN NaN
# b 1.0 NaN NaN
# c NaN 2.0 NaN
# d NaN 3.0 NaN
# e NaN 4.0 NaN
# f NaN NaN 5.0
# g NaN NaN 6.0
(3)这种情况下,另外一条轴上没有重叠,从索引的有序并集上就可以看出。。传入join='inner'即可得到它们的交集
s4=pd.concat([s1*5,s3])
print pd.concat([s1,s4],axis=1)
#输出结果如下:
# 0 1
# a 0.0 0
# b 1.0 5
# f NaN 5
# g NaN 6
print pd.concat([s1,s4],axis=1,join='inner')
#输出结果如下:
# 0 1
# a 0 0
# b 1 5
(4)可以通过join_axes指字要在其他轴上使用的索引
print pd.concat([s1,s4],axis=1,join_axes=[['a','c','b','e']])
#输出结果如下:
# 0 1
# a 0.0 0.0
# c NaN NaN
# b 1.0 5.0
# e NaN NaN
(5)不过有个问题,参与连接的片段在结果中区分不开。假设你想要在连接轴上创建一个层次化索引。使用keys参数即可达到目的
result=pd.concat([s1,s1,s3],keys=['one','two','three'])
print result
#输出结果如下:
# one a 0
# b 1
# two a 0
# b 1
# three f 5
# g 6
# dtype: int64
print result.unstack()
#输出结果如下:
# a b f g
# one 0.0 1.0 NaN NaN
# two 0.0 1.0 NaN NaN
# three NaN NaN 5.0 6.0
(6)如果沿着axis=1对Series进行合并,则keys就会成为DataFrame的列头
print pd.concat([s1,s2,s3],axis=1,keys=['one','two','three'])
#输出结果如下:
# one two three
# a 0.0 NaN NaN
# b 1.0 NaN NaN
# c NaN 2.0 NaN
# d NaN 3.0 NaN
# e NaN 4.0 NaN
# f NaN NaN 5.0
# g NaN NaN 6.0
(7)同样的逻辑对DataFrame对象也是一样:
df1=DataFrame(np.arange(6).reshape(3,2),index=['a','b','c'],
columns=['one','two'])
df2=DataFrame(5+np.arange(4).reshape(2,2),index=['a','c'],
columns=['three','four'])
print pd.concat([df1,df2],axis=1,keys=['level1','level2'])
#输出结果如下:
# level1 level2
# one two three four
# a 0 1 5.0 6.0
# b 2 3 NaN NaN
# c 4 5 7.0 8.0
(8)如果传入的不是列表而是一个字典,则字黄的键就会被做Keys选项的值。
print pd.concat({'level1':df1,'level2':df2},axis=1)
# level1 level2
# one two three four
# a 0 1 5.0 6.0
# b 2 3 NaN NaN
# c 4 5 7.0 8.0
(9)此外还有两个用于管理层次化索引创建方式的参数:
a:key,names
print pd.concat([df1,df2],axis=1,keys=['level1','level2'],
names=['upper','lower'])
#输出的结果如下:
# upper level1 level2
# lower one two three four
# a 0 1 5.0 6.0
# b 2 3 NaN NaN
# c 4 5 7.0 8.0
b:ignore_index=True
df1=DataFrame(np.random.randn(3,4),columns=['a','b','c','d'])
df2=DataFrame(np.random.randn(2,3),columns=['b','d','a'])
print df1
print df2
#在这种情况下,传入ignore_index=True即可:
print pd.concat([df1,df2],ignore_index=True)
#输出结果如下:
# a b c d
# 0 -0.540071 -0.225394 -0.851317 -1.638987
# 1 0.163700 1.375732 0.255257 1.290061
# 2 0.787074 0.137692 0.929145 -0.793582
# 3 -0.502263 0.717226 NaN -1.224583
# 4 -0.023067 -0.556030 NaN -0.819273
concat函数的参数
参数 说明
objs 参与连接的pandas对象的列表或字典。唯一必需的参数
axis 指明连接的轴向,默认为0
join “inner”、“outer”其中之一,默认为"outer".指明其他轴向上的索引是按交集(inner)
还是并集(outer)进行合并
join_axes 指明用于其他n-1条轴的索引,不执行并集/交集运算
keys 与连接对象有关的值,用于形成连接轴向上的层次化索引。可以是任意值的列表或数组、元组数组、
数组列表(如果将levels设置成多级数组的话)
levels 指定用作层次化索引各级别上的索引,如果设置了keys的话
names 用于创建分层级别的名称,如果设置了keys和levels的话
verify_integrity 检查结果对象新轴上的重复情况,如果发现则引发异常。默认(False)允许重复
ignore_index 不保留连接轴上的索引,产生一组新索引
合并重叠数据:numpy.where, pd.combine_first
还有一种数据组合问题不能用简单的合并(merge)或连接(concatenation)运算来处理。
比如,可能有索引全部或部分重叠的两个数据集。我们使用Numpy的where函数,它用于表达一种矢量化的if-else:
(1)Numpy的where
import pandas as pd
import numpy as np
from pandas import Series,DataFrame
(1)Numpy的where
a=Series([np.nan,2.5,np.nan,3.5,4.5,np.nan],
index=['f','e','d','c','b','a'])
b=Series(np.arange(len(a),dtype=np.float64),
index=['f','e','d','c','b','a'])
b[-1]=np.nan
print a
#输出结果如下:
# f NaN
# e 2.5
# d NaN
# c 3.5
# b 4.5
# a NaN
print b
#输出结果如下:
# f 0.0
# e 1.0
# d 2.0
# c 3.0
# b 4.0
# a NaN
print np.where(pd.isnull(a),b,a) #a如果是Nan,则用b的数据代替,否则还是a的值
#输出结果如下:
# [ 0. 2.5 2. 3.5 4.5 nan]
(2)Series有一个combine_first方法,可以实现上面一样的功能,而且会时行数据对齐
print b[:-2].combine_first(a[2:]) #b[:-2]的值若有nan则用a[2:]的值代替。但是b[:-2]是不包括右边的倒数第二个b的,
# 则b的值用第一个4.5
#输出结果如下:
# a NaN
# b 4.5
# c 3.0
# d 2.0
# e 1.0
# f 0.0
#Series的combine_first方法实例:DataFrame也有combine_first方法
s1=pd.Series([1,np.nan])
s2=pd.Series([3,4])
print s1
# 0 1.0
# 1 NaN
print s2
# 0 3
# 1 4
print s1.combine_first(s2) #s1里有nan就换成s2的值
# 0 1.0
# 1 4.0
(3)DataFrame的combine_first也是同上,因此可以将其看做:用参数对象中的数据为调用者对象的缺失数据"打补丁"
df1=DataFrame({'a':[1.,np.nan,5.,np.nan],
'b':[np.nan,2.,np.nan,6.],
'c':range(2,18,4)})
df2=DataFrame({'a':[5.,4.,np.nan,3.,7.],
'b':[np.nan,3.,4.,6.,8.]})
print df1
#输出结果如下:
# a b c
# 0 1.0 NaN 2
# 1 NaN 2.0 6
# 2 5.0 NaN 10
# 3 NaN 6.0 14
print df2
#输出结果如下:
# a b
# 0 5.0 NaN
# 1 4.0 3.0
# 2 NaN 4.0
# 3 3.0 6.0
# 4 7.0 8.0
print df1.combine_first(df2) #用df2里的数据为df1中的数据为nan的数据打补丁
#输出结果如下:
# a b c
# 0 1.0 NaN 2.0
# 1 4.0 2.0 6.0
# 2 5.0 4.0 10.0
# 3 3.0 6.0 14.0
# 4 7.0 8.0 NaN
2. 重塑和轴向旋转:
pandas对象中有许多用于重新排列表格型数据的基础运算。这些函数也称作重塑或轴向旋转运算。
(1)stack:将数据的列"旋转"为行。(将DataFrame转换成Series,而Series无stack方法)
(2)unstack:与stack相反的操作,将数据的行“旋转”为列。(将Series转换成DataFrame,DataFrame还是DataFrame)
重塑层次化索引:
层次化索引为DataFrame数据的重排任务提供了一个具有良好一致性的方式。主要功能有以下二个方法:
层次化索引为DataFrame数据的
stack:将数据的列"旋转"为行,将DataFrame转换成Series,Series无stack()方法
unstack:将数据的行"旋转"为列,将Series转换成DataFrame了,DataFrame还是DataFrame
(1)将DataFrame的列旋转为行:DataFrame.stack()
data=DataFrame(np.arange(6).reshape((2,3)),
index=pd.Index(['Ohio','Colorado'],name='state'),
columns=pd.Index(['one','two','three'],name='number'))
print data
#输出结果如下:
# number one two three
# state
# Ohio 0 1 2
# Colorado 3 4 5
result=data.stack()
print result
# #输出结果如下:
# # state number
# # Ohio one 0
# # two 1
# # three 2
# # Colorado one 3
# # two 4
# # three 5
(2)对于一个层次化索引的Series,可以用unstack将其重排为一个DataFrame.
print result.unstack()
#输出结果如下:
# number one two three
# state
# Ohio 0 1 2
# Colorado 3 4 5
默认情况下,unstack操作的是最内层(stack也是如此)。传入分层级别的编号或名称即可对其他级别进行unstack操作
print result.unstack(0)
#输出结果如下:
# state Ohio Colorado
# number
# one 0 3
# two 1 4
# three 2 5
print result.unstack('state')
#输出结果如下:
# state Ohio Colorado
# number
# one 0 3
# two 1 4
# three 2 5
(3)如果不是所有级别值都能在各分组中找到的话,则unstack操作可能会引入缺失数据:
s1=Series([0,1,2,3],index=['a','b','c','d'])
s2=Series([4,5,6],index=['c','d','e'])
data2=pd.concat([s1,s2],keys=['one','two'])
print data2
#输出结果如下:
# one a 0
# b 1
# c 2
# d 3
# two c 4
# d 5
# e 6
# dtype: int64
print data2.unstack()
#输出结果如下:
# a b c d e
# one 0.0 1.0 2.0 3.0 NaN
# two NaN NaN 4.0 5.0 6.0
(4)stack默认会滤除缺失数据,因此该运算是可逆的:
# print data2.stack() #报错'Series' object has no attribute 'stack'
#可以对data2.unstack()后的DataFrame进行stack()
print data2.unstack().stack(dropna=False) #DataFrame转换成Series了,且列变成行了
#输出结果如下:
# one a 0.0
# b 1.0
# c 2.0
# d 3.0
# e NaN
# two a NaN
# b NaN
# c 4.0
# d 5.0
# e 6.0
print data2.unstack().unstack() #data2.unstack()的行变成列
#输出结果如下:
# a one 0.0
# two NaN
# b one 1.0
# two NaN
# c one 2.0
# two 4.0
# d one 3.0
# two 5.0
# e one NaN
# two 6.0
(5)对DataFrame进行unstack操作时,作为旋转轴的级别将会成为结果中的最低级别
df=DataFrame({'left':result,'right':result+5},
columns=pd.Index(['left','right'],name='side'))
print df
#result的内容如下:
# # state number
# # Ohio one 0
# # two 1
# # three 2
# # Colorado one 3
# # two 4
# # three 5
#df的结果如下:
# side left right
# state number
# Ohio one 0 5
# two 1 6
# three 2 7
# Colorado one 3 8
# two 4 9
# three 5 10
print df.unstack('state') #将state的行旋转为列
#输出结果如下:
# side left right
# state Ohio Colorado Ohio Colorado
# number
# one 0 3 5 8
# two 1 4 6 9
# three 2 5 7 10
print df.unstack('state').stack('side') #将上面的再stack将列为side的旋转为行(即left,right)
#输出结果如下:
# state Colorado Ohio
# number side
# one left 3 0
# right 8 5
# two left 4 1
# right 9 6
# three left 5 2
# right 10 7
将"长格式"转换为"宽格式":
时间序列数据通常是以所谓的"长格式"(long)或"堆叠格式"(stacked)存储在数据库和CSV中的。
关系型数据库通常是主键形式提供了关系的完整性,而且提供了更为简单的查询支持。但对于长格式的数据操作起来就不那么轻松了。
而DataFrame可以把主键中不同的Item值分别形成一列,date列中的时间值则用作索引,可以用DataFrame的pivot方法完全可以实现这个转换。
import pandas as pd
import numpy as np
from pandas import Series,DataFrame
ldata=DataFrame({'date':['1959-03-31 00:00:00','1959-03-31 00:00:00','1959-03-31 00:00:00',
'1959-06-30 00:00:00','1959-06-30 00:00:00','1959-06-30 00:00:00',
'1959-09-30 00:00:00','1959-09-30 00:00:00','1959-09-30 00:00:00','1959-12-31 00:00:00'],
'item':['realgdp','infl','unemp','realgdp','infl','unemp','realgdp','infl','unemp','realgdp'],
'value':['2710.349','0.000','5.800','2778.801','2.340','5.100','2775.488','2.740','5.300','2785.204']
})
print ldata
#输出结果如下:
# date item value
# 0 1959-03-31 00:00:00 realgdp 2710.349
# 1 1959-03-31 00:00:00 infl 0.000
# 2 1959-03-31 00:00:00 unemp 5.800
# 3 1959-06-30 00:00:00 realgdp 2778.801
# 4 1959-06-30 00:00:00 infl 2.340
# 5 1959-06-30 00:00:00 unemp 5.100
# 6 1959-09-30 00:00:00 realgdp 2775.488
# 7 1959-09-30 00:00:00 infl 2.740
# 8 1959-09-30 00:00:00 unemp 5.300
# 9 1959-12-31 00:00:00 realgdp 2785.204
(1)使用DataFrame的pivot的方法
pivoted=ldata.pivot('date','item','value')
print pivoted.head()
#输出结果如下:
# item infl realgdp unemp
# date
# 1959-03-31 00:00:00 0.000 2710.349 5.800
# 1959-06-30 00:00:00 2.340 2778.801 5.100
# 1959-09-30 00:00:00 2.740 2775.488 5.300
# 1959-12-31 00:00:00 None 2785.204 None
(2)前两个参数值分别用作行和列索引的列名,最后一个参数值则是用于填充DataFrame的数据列的列名。
#假设有两个需要参与重塑的数据列:
ldata['value2']=np.random.randn(len(ldata))
print ldata[:10]
#输出结果如下:
# date item value value2
# 0 1959-03-31 00:00:00 realgdp 2710.349 0.935557
# 1 1959-03-31 00:00:00 infl 0.000 1.609324
# 2 1959-03-31 00:00:00 unemp 5.800 -0.753734
# 3 1959-06-30 00:00:00 realgdp 2778.801 -0.668879
# 4 1959-06-30 00:00:00 infl 2.340 -0.256601
# 5 1959-06-30 00:00:00 unemp 5.100 0.537770
# 6 1959-09-30 00:00:00 realgdp 2775.488 1.073817
# 7 1959-09-30 00:00:00 infl 2.740 -0.027340
# 8 1959-09-30 00:00:00 unemp 5.300 -0.531161
# 9 1959-12-31 00:00:00 realgdp 2785.204 0.706080
(3)如果忽略掉最后一个参数,得到的DataFrame就会带有层次化的列
pivoted=ldata.pivot('date','item')
print pivoted[:5]
#输出结果如下:
# value value2
# item infl realgdp unemp infl realgdp unemp
# date
# 1959-03-31 00:00:00 0.000 2710.349 5.800 -0.505690 -1.090732 -2.123859
# 1959-06-30 00:00:00 2.340 2778.801 5.100 -0.940028 -2.204997 -1.195357
# 1959-09-30 00:00:00 2.740 2775.488 5.300 -0.636424 0.510898 1.105585
# 1959-12-31 00:00:00 None 2785.204 None NaN -0.203154 NaN
(4)注意,pivot其实只是一个快捷方式而己:用set_index创建层次化索引,再用unstack重塑。
unstacked=ldata.set_index(['date','item']).unstack('item')
print unstacked[:7]
#输出结果如下:
# value value2
# item infl realgdp unemp infl realgdp unemp
# date
# 1959-03-31 00:00:00 0.000 2710.349 5.800 -0.380805 -2.499867 -1.398593
# 1959-06-30 00:00:00 2.340 2778.801 5.100 -0.168914 0.435372 -1.454938
# 1959-09-30 00:00:00 2.740 2775.488 5.300 -0.297850 -1.693196 1.615807
# 1959-12-31 00:00:00 None 2785.204 None NaN 1.551105 NaN
import pandas as pd
import numpy as np
from pandas import Series,DataFrame
df = pd.DataFrame({'foo': ['one', 'one', 'one', 'two', 'two',
'two'],
'bar': ['A', 'B', 'C', 'A', 'B', 'C'],
'baz': [1, 2, 3, 4, 5, 6],
'zoo': ['x', 'y', 'z', 'q', 'w', 't']})
print df
#输出结果如下:
# bar baz foo zoo
# 0 A 1 one x
# 1 B 2 one y
# 2 C 3 one z
# 3 A 4 two q
# 4 B 5 two w
# 5 C 6 two t
print df.pivot(index='foo',columns='bar')['baz'] #行索引foo,列bar,值baz
#输出结果如下:
# bar A B C
# foo
# one 1 2 3
# two 4 5 6
print df.pivot(index='foo',columns='bar',values='baz') #与上面的一致
# print df.pivot(index='foo',columns='bar',values=['baz', 'zoo'])
#若是两行的索引一样的话会报错,因为值就不知道对应哪个了
#如下例中bar中的两个索一样
df=pd.DataFrame({"foo":['one','one','two','two'],
"bar":['A','A','B','C'],
"baz":[1,2,3,4]})
print df
print df.pivot(index='foo',columns='bar',values='baz') #报错,foo,bar为[one,A]不知道对应哪个值了,两个一样的索引
3. 数据转换:
前面讲的都是数据重排,另一类重要操作则是过滤、清理以及其它的转换工作。
(1)drop_duplicates:删除重复数据
(2)map:函数或映射进行数据转换(map,lambda),map用于修改对象的子集
(3)fillna: 用于填充缺失数据,看做值替换的一种特殊情况。或者用pandas的replace方法替换缺失值(与python的str.replace方法一样)
(4)rename:复制DataFrame并对其行索引和列标签进行修改。若要就地修改某个数据集,传入inplace=True即可
(5)cut:将数据划分阶段(即元面)例不同的年龄阶段,它返回的Categorical对象。而qcut是根据样本分位数对数据进行划分(可以等分)
(6)abs:过滤绝对值,np.abs(data)>3取绝对值>3的数。
(7)np.sign:过滤的区间为-1到1的数组,表示原始值的符号。
(8)take:选取子集,从DataFrame或Series中选取部分子集
(9)get_dummies:DataFrame的某一列中有多个不同的值,可将该列的值作为列名,其值全为1和0.例:第一行只有a的值,则a为1,其它为0
Series只有panda.str.get_dummies.
删除重复数据:
import pandas as pd
import numpy as np
from pandas import Series,DataFrame
#前面讲的都是数据重排,另一类重要操作则是过滤、清理以及其它的转换工作。
#删除重复数据
#DataFrame中常常会出现重复行,举例如下:
data=DataFrame({'k1':['one']*3+['two']*4,
'k2':[1,1,2,3,3,4,4]})
print data
#输出结果如下:
# k1 k2
# 0 one 1
# 1 one 1
# 2 one 2
# 3 two 3
# 4 two 3
# 5 two 4
# 6 two 4
(1)DataFrame的duplicated方法返回一个布尔型Series,表示各行是否是重复行:
print data.duplicated()
#输出结果如下:
# 0 False
# 1 True
# 2 False
# 3 False
# 4 True
# 5 False
# 6 True
# dtype: bool
(2)还有一个与此相关的drop_duplicates方法,它用于返回一个移除了重复行的DataFrame
print data.drop_duplicates()
#输出结果如下:
# k1 k2
# 0 one 1
# 2 one 2
# 3 two 3
# 5 two 4
(3)这两个方法默认会判断全部列,你可以指定部分列进行重复项判断。假设你还有一列值,且只希望根据k1列过滤重复项。
data['v1']=range(7)
print data.drop_duplicates(['k1']) #过滤k1列的重复项
#输出结果如下:
# k1 k2 v1
# 0 one 1 0
# 3 two 3 3
(4)duplicated和drop_duplicates默认保留的是第一个出现的值组合。传入last则保留最后一个
#它的参数值有:first是保留第一个过滤,False则删除全是重复(即k1,k2,v1)
print data.drop_duplicates(['k1','k2'],'last') #即以k2值过滤
#输出结果如下:
# k1 k2 v1
# 1 one 1 1
# 2 one 2 2
# 4 two 3 4
# 6 two 4 6
利用函数或映射进行数据转换:
map用于修改对象的子集:
import pandas as pd
import numpy as np
from pandas import Series,DataFrame
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]})
print 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
(1)假如你想要添加一列表示该肉类食物来源的动物类型,我们可以先编写一个肉类到动物的映射:映射用map
meat_to_animal={'bacon':'pig','pulled pork':'pig','pastrami':'cow',
'corned beef':'cow','honey ham':'pig','nova lox':'salmon'}
#Series的map方法可以接受一个函数或含有映射关系的字典型对象,但是这里有个小问题,即有些肉类的首字母大写了,而另一些则没有。
#因此我们还需要将各个值转换为小写:
data['animal']=data['food'].map(str.lower).map(meat_to_animal)
print 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
(2)也可以传入一个能够完成全部这些工作的函数:lambda
print '++++++++++++++++++++++++++++++++'
print 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
data['animal']=data['food'].map(lambda x:meat_to_animal[x.lower()])
print 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
#map是实现元素级转换以及其它数据工作的便捷方式
替换值:replace
import pandas as pd
import numpy as np
from pandas import Series,DataFrame
data=Series([1.,-999.,2.,-999.,-1000.,3.])
print data
#输出结果如下:
# 0 1.0
# 1 -999.0
# 2 2.0
# 3 -999.0
# 4 -1000.0
# 5 3.0
(1)-999这个值可能是一个表示缺失数据的标记值,要将其替换为Pandas能够理解的NA值、我们可以利用replace来产生一个新的Series.
print data.replace(-999,np.nan)
#输出结果如下:
# 0 1.0
# 1 NaN
# 2 2.0
# 3 NaN
# 4 -1000.0
# 5 3.0
(2)如果希望一次性替换多个值,可以传入一个由待替换值组成的列表以及一个替换值。
print data.replace([-999,-1000],np.nan) #将-999,-1000的都替换成NaN
#输出结果如下:
# 0 1.0
# 1 NaN
# 2 2.0
# 3 NaN
# 4 NaN
# 5 3.0
(3)如果希望对不同的值进行不同的替换,则传入一个由替换关系组成的列表即可:
print data.replace([-999,-1000],[np.nan,0]) #将-999替换成NaN,-1000替换成0
#输出的结果如下:
# 0 1.0
# 1 NaN
# 2 2.0
# 3 NaN
# 4 0.0
# 5 3.0
(4)传入的参数也可以是字典
print data.replace({-999:np.nan,-1000:0})
#输出的结果如下:
# 0 1.0
# 1 NaN
# 2 2.0
# 3 NaN
# 4 0.0
# 5 3.0
重命名轴索引:
跟Series中的值一样,轴标签也可以通过函数或映射进行转换,从而得到一个新对象。
轴还可以被就地修改,而无需新建一个数据结构。
import pandas as pd
import numpy as np
from pandas import Series,DataFrame
data=DataFrame(np.arange(12).reshape((3,4)),
index=['Ohio','Colorado','New York'],
columns=['one','two','three','four'])
(1)和轴标签一样,轴标签也有一map方法:
print data.index.map(str.upper) #data的index用map转成全是大写
#输出结果如下:
# Index([u'OHIO', u'COLORADO', u'NEW YORK'], dtype='object')
#可以将其直接赋给index,这样就达到了就地修改了。
data.index=data.index.map(str.upper)
print data
#输出结果如下:
# one two three four
# OHIO 0 1 2 3
# COLORADO 4 5 6 7
# NEW YORK 8 9 10 11
(2)如果想要创建数据集的转换版(而不是修改原始数据),比较实用的方法是rename:
print data.rename(index=str.title,columns=str.upper)
#输出结果如下:
# ONE TWO THREE FOUR
# Ohio 0 1 2 3
# Colorado 4 5 6 7
# New York 8 9 10 11
(3)rename可以结合字典对象实现对部分轴标签的更新
print data.rename(index={'OHIO':'INDIANA'},
columns={'three':'peekaboo'})
#输出结果如下:
# one two peekaboo four
# Ohio 0 1 2 3
# Colorado 4 5 6 7
# New York 8 9 10 11
(4)rename帮我们实现了:复制DataFrame并对其索引和列标签进行赋值。如果希望就地修改某个数据集,传入inplace=True即可
_=data.rename(index={'OHIO':'INDIANA'},inplace=True)
print data
离散化和面元划分:cut,pcut
为了便于分析,连续数据常常被离散化或拆分为“面元”(bin).假设有一组人员数据,而你希望将它们划分为不同年龄组:
import pandas as pd
import numpy as np
from pandas import Series,DataFrame
ages=[20,22,25,27,21,23,37,31,61,45,41,32]
(1)接下来将这些数据划分为"18-25"、"26到35"、"35到60"以及"60以上"几个面元,要实现该功能,需要使用pandas的cut函数.
cut返回的是一个特殊的Categorical对象。可以看做一组表示面元名称的字符串。
bins=[18,25,35,60,100]
cats=pd.cut(ages,bins)
print 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]]
(2)实际上,它含有一个表示不同分类名称的levels数组以及一个为年龄数据进行标号的labels属性,现在用codes替换了labels
# print cats.labels
#输出结果如下:
# [0 0 0 1 0 0 2 1 3 2 2 1]
print cats.codes
#输出结果如下:
# [0 0 0 1 0 0 2 1 3 2 2 1]
print pd.value_counts(cats) #符合各区间的个数
#输出结果如下:
# (18, 25] 5
# (35, 60] 3
# (25, 35] 3
# (60, 100] 1
(3)跟"区间"的数学符号一样,圆括号表示开端,而方括号则表示闭端。哪边是闭端可以通过right-False进行修改:
print pd.cut(ages,[18,26,36,61,100],right=False)
#输出结果如下:
# [[18, 26), [18, 26), [18, 26), [26, 36), [18, 26), ..., [26, 36), [61, 100), [36, 61), [36, 61), [26, 36)]
# Length: 12
# Categories (4, interval[int64]): [[18, 26) < [26, 36) < [36, 61) < [61, 100)]
(4)你也可以设置自己的面元名称,将labels选项设置为一个列表或数组即可:
group_names=['Youth','YoungAdult','MiddleAged','Senior']
print 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]
(5)如果向cut传入的是面元的数量而不是确切的面元边界,则它会根据数据的最小值和最大值计算等长面元。
#下面这个例子,我们将一些均匀分布的数据分成四组:
data=np.random.rand(20)
print pd.cut(data,4,precision=2)
#输出结果如下:
# [(0.046, 0.28], (0.046, 0.28], (0.28, 0.52], (0.28, 0.52], (0.76, 0.99], ..., (0.76, 0.99], (0.76, 0.99], (0.76, 0.99], (0.76, 0.99], (0.28, 0.52]]
# Length: 20
# Categories (4, interval[float64]): [(0.046, 0.28] < (0.28, 0.52] < (0.52, 0.76] < (0.76, 0.99]]
(6)qcut是一个非常类似于cut函数,它可以根据样本分位数对数据进行面元划分
#cut可能无法使各个面元中含有相同数量的数据点。而qcut使用的是样本分位数,因此可以得到大小基本相等的面元。
data=np.random.randn(1000) #randn正态分布
cats=pd.qcut(data,4) #按4分位进行切割
print cats
#输出结果如下:
# [(-0.658, 0.0366], (-4.299, -0.658], (-0.658, 0.0366], (-4.299, -0.658], (0.0366, 0.686], ..., (0.686, 3.005], (0.686, 3.005], (0.686, 3.005], (-4.299, -0.658], (0.686, 3.005]]
# Length: 1000
# Categories (4, interval[float64]): [(-4.299, -0.658] < (-0.658, 0.0366] < (0.0366, 0.686] <
# (0.686, 3.005]]
(7)和cut一样,也可以设置自定义的分位数(0到1之间的数值,包含端点):
print pd.qcut(data,[0,0.1,0.5,0.9,1.])
注意:本章稍后在讲解聚合和分组运算时会再次用到cut和qcut,因为这两个离散化函数对分量和分组分析非常重要。
检测和过滤异常值:abs(col)>3,np.sign(data)*3
异常值的过滤或变换运算在很大程度上其实就是数组运算。来看一个含有正态分布数据的DataFrame:
import pandas as pd
import numpy as np
from pandas import Series,DataFrame
np.random.seed(12345)
data=DataFrame(np.random.randn(1000,4))
print data.describe()
#输出结果如下:
# 0 1 2 3
# count 1000.000000 1000.000000 1000.000000 1000.000000
# mean -0.067684 0.067924 0.025598 -0.002298
# std 0.998035 0.992106 1.006835 0.996794
# min -3.428254 -3.548824 -3.184377 -3.745356
# 25% -0.774890 -0.591841 -0.641675 -0.644144
# 50% -0.116401 0.101143 0.002073 -0.013611
# 75% 0.616366 0.780282 0.680391 0.654328
# max 3.366626 2.653656 3.260383 3.927528
(1)假如想找出某列中绝对值大小超过3的值:abs(col)>3
col=data[3] #取data[3]列
print col[np.abs(col)>3]
#输出结果如下:
# 97 3.927528
# 305 -3.399312
# 400 -3.745356
# Name: 3, dtype: float64
(2)要选出全部含有"超过3或-3的值"的行,可以利用布尔型DataFrame以及any方法:
print data[(np.abs(data)>3).any(1)]
(3)根据这些条件,可轻松地对值进行设置。下面的代码可以将值限制在区间-3到3以内:
np.sign这个函数返回的是一个由-1到1组成的数组,表示原始值的符号
data[np.abs(data)>3]=np.sign(data)*3
print data.describe()
#输出结果如下:
# 0 1 2 3
# count 1000.000000 1000.000000 1000.000000 1000.000000
# mean -0.067623 0.068473 0.025153 -0.002081
# std 0.995485 0.990253 1.003977 0.989736
# min -3.000000 -3.000000 -3.000000 -3.000000
# 25% -0.774890 -0.591841 -0.641675 -0.644144
# 50% -0.116401 0.101143 0.002073 -0.013611
# 75% 0.616366 0.780282 0.680391 0.654328
# max 3.000000 2.653656 3.000000 3.000000
排列和随机采样:permutation(5)需要排列的长度为5,take选取子集
利用numpy.random.permutation函数可以轻松实现对Series或DataFrame的列的排列工作(permuting).通过需要排列的轴的长度
调用permutation,可产生一个表示新顺序的整数数组:
import numpy as np
from pandas import DataFrame,Series
(1)take:选取子集,df.take[0,3]是选取第0行和第3行,而不是选取行索引为0和3的行
df=DataFrame(np.arange(5*4).reshape(5,4))
sampler=np.random.permutation(5) #排列的轴的长度调用permutation,可产生一个整数数组
print sampler
#输出结果如下:
# [1 2 3 0 4]
print df
#输出结果如下:
# 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
print df.take(sampler) #取df的第1,2,3,0,4行。而不是取索行号为1,2,3,0,4的行。
#输出结果如下:
# 0 1 2 3
# 1 4 5 6 7
# 2 8 9 10 11
# 3 12 13 14 15
# 0 0 1 2 3
# 4 16 17 18 19
(2)如果不想用替换的方式选取随机子集,则可以使用permutation:从permutation返回的数组中切下前k个元素,其中k为期望的子集大小。
直接切下选出permutation的值
#虽然有很多高效的算法可以实现非替换式采样,但是手边有的工具为什么不用呢
print '+++++++++++++++++++++++++'
print np.random.permutation(len(df))
#输出结果:[2 1 3 0 4]
print df.take(np.random.permutation(len(df))[:3]) #取出[:3]下标从0-3不包括3的行,即的是[2,1,3]行的数据
#输出结果如下:
# 0 1 2 3
# 1 4 5 6 7
# 3 12 13 14 15
# 0 0 1 2 3
(3)要通过替换的方式产生样本,最快的方式是通过np.random.randint得到一组随机整数:
bag=np.array([5,7,-1,6,4])
sampler=np.random.randint(0,len(bag),size=10)
print sampler
#输出结果如下:
# [2 2 4 3 0 4 4 3 0 4]
draw=bag.take(sampler)
print draw
#输出结果如下:按上面sampler的值取第几个,如sample为2,则取第二个值
# [-1 -1 4 6 5 4 4 6 5 4]
计算指标/哑变量:get_dummies
用于统计建模或机器学习的转换方式是:将分类变量(categorical variable)转换为"哑变量矩阵"(dummy matrix)或
"指标矩阵"(indicator matrix).如果DataFrame的某一列中含有K个不同的值,则可以派生出一个k列矩阵或DataFrame
(其值全为1和0)。pandas有一个get_dummies函数可以实现该功能.
import numpy as np
import pandas as pd
from pandas import DataFrame,Series
(1) get_dummies某一列中有多个不同的值,可将该列的值作为列名,其值全为1和0。例:第一行只有a的值,则a为1,其它为0
df=DataFrame({'key':['b','b','a','c','a','b'],
'data1':range(6)})
print df
#输出结果如下:
# data1 key
# 0 0 b
# 1 1 b
# 2 2 a
# 3 3 c
# 4 4 a
# 5 5 b
print pd.get_dummies(df['key'])
#输出结果如下:
# 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
(2)若想给DataFrame的列加上一个前缀,以便能够跟其他数据进行合并。可以用prefix='key'
dummies=pd.get_dummies(df['key'],prefix='key') #prefix是将列分成好几个子列的表示如:key_a,key_b,key_c...
print dummies
#输出结果如下:
# 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_with_dummy=df[['data1']].join(dummies) #data1列和dummies合并
print df_with_dummy
#输出结果如下:
# 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
(3)如果DataFrame中某行同属于多个分类,则事情就会有点复杂,具体的操作如下
#movies.dat的内容如下:movies的数据就是同一行的数据是多个分类
# 1::Toy Story(1995)::Animation|Children's|Comedy
# 2::Jumanji(1995)::Adventure|Children's|Fantasy
# 3::Grumpier Old Men(1995)::Comedy|Romance
# 4::Waiting to Exhale(1995)::Comedy|Drama
# 5::Father of the Bride Part II(1995)::Comedy
# 6::Heat(1995)::Action|Crime|Thriller
mnames=['movie_id','title','genres']
#下面的engine不是read_table的参数,它是为了消除pycharm产生的warning
movies=pd.read_table('movies.dat',sep='::',header=None,names=mnames,engine='python')
print '++++++++++++++++++'
print len(movies) #输出结果为:6
print movies[:3]
#输出结果如下:
# 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
(a)要为每个genre添加指标变量就需要做一些数据的规整。首先,我们从数据集中抽取出不同的genre值(巧用set.union),set.union集合的并集
genre_iter=(set(x.split('|')) for x in movies.genres) #取出movies.genres的每个数据,以'|'分开后放入set集合
print genre_iter
#输出结果:<generator object <genexpr> at 0x103e7c0f0> 是个集合对象
genres=sorted(set.union(*genre_iter)) #接收的参数是一个元组,并且排序
print genres
#输出结果如下:
# ['Action', 'Adventure', 'Animation', "Children's", 'Comedy', 'Crime', 'Drama', 'Fantasy', 'Romance', 'Thriller']
(b)现在,我们从一个全零DataFrame开始构建指标DataFrame:
dummies=DataFrame(np.zeros((len(movies),len(genres))),columns=genres)#构建movies的行数,genres的个数为列的全为0的数据
# print dummies #6行10列的全为0的数据
(c)接下来,迭代每一部电影并将dummies各行的项设置为1:
for i,gen in enumerate(movies.genres): #enumerate会自动加上索引,故要两个变量接收,i是索引,gen是值
dummies.ix[i,gen.split('|')]=1 #ix取行,ix[i,gen.split('|')]若i=0,则表示取第一行,gen的'|'分开的值都赋1;
# 即假如这一行有该值,若有Action则该值为1.
print dummies
#输出结果如下:
# Action Adventure Animation Children's Comedy Crime Drama Fantasy \
# 0 0.0 0.0 1.0 1.0 1.0 0.0 0.0 0.0
# 1 0.0 1.0 0.0 1.0 0.0 0.0 0.0 1.0
# 2 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0
# 3 0.0 0.0 0.0 0.0 1.0 0.0 1.0 0.0
# 4 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0
# 5 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0
(d)genres列处理好了,再将其与moivies合并起来
movies_windic=movies.join(dummies.add_prefix('Genre')) #
print movies_windic
# print movies_windic.ix[0]#取第一行的数据
#输出结果如下:
# movie_id 1
# title Toy Story(1995)
# genres Animation|Children's|Comedy
# GenreAction 0
# GenreAdventure 0
# GenreAnimation 1
# GenreChildren's 1
# GenreComedy 1
# GenreCrime 0
# GenreDrama 0
# GenreFantasy 0
# GenreRomance 0
# GenreThriller 0
下面简单介绍一下get_dummies函数
import pandas as pd
import numpy as np
from pandas import DataFrame,Series
#get_dummies 某列的有多个值(a,b,c),没有值的用0表示
s=pd.Series(list('abca'))
print s
#输出结果如下:
# 0 a
# 1 b
# 2 c
# 3 a
print pd.get_dummies(s) #转换categoriacl variable为dummy/indicator 变量
#输出结果如下:
# a b c
# 0 1 0 0
# 1 0 1 0
# 2 0 0 1
# 3 1 0 0
s1=['a','b',np.nan]
print pd.get_dummies(s1)
#输出结果如下:
# a b
# 0 1 0
# 1 0 1
# 2 0 0
print pd.get_dummies(s1,dummy_na=True) #包含NaN值
#输出结果如下:
# a b NaN
# 0 1 0 0
# 1 0 1 0
# 2 0 0 1
df=pd.DataFrame({'A':['a','b','a'],'B':['b','a','c'],
'C':[1,2,3]})
print pd.get_dummies(df,prefix=['col1','col2'])
4. 字符串操作:
对于更为复杂的模式匹配和文本操作,则可能需要用到正由表达式。pandas对些进行了加强,它使你能够对数组数据应用字符串表达式和正则表达式,
而且能处理烦人的缺失数据。
python内置的字符串方法
(1)split: 拆分,用于以一种格式(,)之类的可以拆分成数据的数据.它常与strip一起使用,用于去除空格
(2)+/join:连接字符串,join用的多
(3)in/index/find/count:python子串的定位,in返回True/False;index与find区别是,若没找到由报异常,find返回-1,找到都返回1.
count用返回子串出现的次数(非重叠),若没有出现则为0.
(4)replace:用于指字模式替换另一模式
(5)startswith/endswith:如果字符串以某个前缀/后缀开头/结尾,则返回True.
(6)rfind:如果在子符串找到子串,则返回第一个发现的子串的第一个字符所在的位置 。如没找到则返回-1.
(7)strip,rstrip,lstrip:去除空白符
(8)lower,upper:转换为小写/大写
(9)ljust,rjust:用空格(或其它字符)填充字符串的空白侧以返回符合最低宽度的字符串
字符串对象方法:
对于大部分字符串处理应用而言,内置的字符串方法已经够满足要求了。例如,以逗号分隔的字符串可以用split拆分成数段。
内置python字符串的方法举例:
val='a,b, guido'
print val.split(',')
(1)split常结合strip(用于去除空白)一起使用:
pieces=[x.strip() for x in val.split(',')]
print pieces
#输出结果如下:
['a', 'b', 'guido']
(2)python:'+'连接各字符串(用的少) 或者 join连接字符串(join常用)
first,second,third=pieces #将pieces的值分别赋给一个个变量
print first+"::"+second+"::"+third
#输出结果如下:
# a::b::guido
#可用join达到一样的效果
print "::".join(pieces)
(3)python子串的定位:in方法,index,find查找.index未找到会抛异常,而find未找到返回-1。
# count函数用于返回子串出现的次数,若为0未找到
print 'guido' in val #输出:True
print val.index(',') #输出:1
print val.find(':') #输出:-1
print val.count(',') #输出:2
(4)replace用于指定模式替换为另一个模式
print val.replace(',','::')
#输出结果如下:
# a::b:: guido
print val.replace(',','')
#输出结果如下:
# ab guido
正则表达式:
import re
#正则表达式。python内置的re模块负责对字符串应用正则表达式
text='foo bar\t baz \tqux'
print re.split('\s+',text)
#输出结果如下:
# ['foo', 'bar', 'baz', 'qux']
(1)调用re.split('\s+',text)时,正则表达式会先被编译,然后再在text上调用其split方法。
你可以用re.compile自己编译regex以得到一个可重用的regex对象
regex=re.compile('\s+')
print regex.split(text)
#输出结果如下:
# ['foo', 'bar', 'baz', 'qux']
#如果只希望得到匹配regex的所有模式,则可以使用findall方法:
print regex.findall(text)
#输出结果如下:
# [' ', '\t ', ' \t']
#如果打算对许多字符串应用同一条正则表达式,强列建议通过re.compile创建regex对象,这样可以节省大量的CPU时间。
(2)match和search跟findall功能类似。findall返回的是字符串所有的匹配项,而search则只返回第一个匹配项。match更加严格,
#它只匹配字符串的首部。
#如果想避免正则表达式中不需要的转义(\),则可以使用原始字符串字面r'
text="""
Dava dava@google.com
Steven@gmail.com
Rob rob@gmail.com
Ryan ryan@yahoo.com
"""
pattern=r'[A-Z0-9._%+-]@[A-Z0-9.-]+\.[A-Z]{2,4}'
#re.IGNORECASE的作用是使用正则表达式对大小写不敏感
regex=re.compile(pattern,flags=re.IGNORECASE)
#对text使用findall将得到一组电子邮件地址:
print regex.findall(text)
#输出结果如下:
# ['a@google.com', 'n@gmail.com', 'b@gmail.com', 'n@yahoo.com']
#search返回的是文本中第一个电子邮件地址。
m=regex.search(text)
print m
#输出结果如下:
# <_sre.SRE_Match object at 0x1078c9d30>
print text[m.start():m.end()]
#regex.match则将返回None,因为它只匹配出现在字符串开头的模式:
print regex.match(text) #返回None
(3)另外还有一个sub方法,它会将匹配到模式替换为指定字符串,并返回所得到的新符串:
print regex.sub('REDACTED',text)
#输出结果如下:
# Dava davREDACTED
# SteveREDACTED
# Rob roREDACTED
# Ryan ryaREDACTED
(4)假设你不仅想找要找出电子邮件地址,还想将各个地址分成3个部分:用户名、域名以及域后缀
要想实现此功能,只需将待分段的模式的各部分用圆括号包起来即可:
pattern=r'([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})'
regex=re.compile(pattern,flags=re.IGNORECASE)
#由这种正则表达式所产生的匹配项对象,可以通过其groups方法返回一个由模式各段组成的元组。
m=regex.match('wesm@bright.net')
print m.groups()
#输出结果如下:
# ('wesm', 'bright', 'net')
#对于带有分组功能的模式,findall会返回一个元组列表:
print regex.findall(text)
#输出结果如下:
# [('dava', 'google', 'com'), ('Steven', 'gmail', 'com'), ('rob', 'gmail', 'com'), ('ryan', 'yahoo', 'com')]
#sub还能通过诸如\1、\2之类的特殊符号访问各匹配项中的分组:
print regex.sub(r'Username:\1,Domain:\2,Suffix:\3',text)
#输出结果如下:
# Dava Username:dava,Domain:google,Suffix:com
# Username:Steven,Domain:gmail,Suffix:com
# Rob Username:rob,Domain:gmail,Suffix:com
# Ryan Username:ryan,Domain:yahoo,Suffix:com
#对上面那个电子邮件正则表达式做一点小变动:为各个匹配分组加上一个名称:
regex=re.compile(r"""
(?P<username>[A-Z0-9._%+-]+)
@
(?P<domain>[A-Z0-9.-]+)
\.
(?P<suffix>[A-Z]{2,4})
""",flags=re.IGNORECASE|re.VERBOSE)
m=regex.match('wes@bright.net')
print m.groupdict()
#输出结果如下:
# {'username': 'wes', 'domain': 'bright', 'suffix': 'net'}
正则表达式方法
正则 说明
findall、finditer 返回字符串中所有的非重叠匹配模式。findall返回的是由所有模式组成的列表,
而finditer则通过一个迭代器逐个返回
match 从字符串起始位置匹配模式,还可以对模式各部分进行分组。如果匹配到模式,则
返回一个匹配对象,否则返回None
search 扫描整个字符串以匹配模式。如果找到则返回一个匹配项对象。跟match不同,其
匹配可以位于字符串的任意位置,而不仅仅是起始处
split 根据找到的模式将字符串拆分为数段
sub、subn 将字符串所有的sub或前n个subn模式替换为指定表达式。在替换字符串中可以通过\1、\2等
符号表示各分组项
注意:正则表达式细节还是看正则表达式的专题。
pandas中矢量化的字符串函数:
清理待分析的散乱数据时,常常需要做一些字符串规整化工作。更为复杂的情况是,含有字符串的列有时还含有缺失数据:
import pandas as pd
import numpy as np
from pandas import DataFrame,Series
import re
data={'Dava':'dava@google.com','Steve':'steve@gmail.com','Rob':'rob@gmail.com','Wes':np.nan}
data=Series(data)
print data
#输出结果如下:
# Dava dava@google.com
# Rob rob@gmail.com
# Steve steve@gmail.com
# Wes NaN
print data.isnull()
(1)通过data.map,所有字符串和正则表达式方法都能被应用于(传入lambda表达式或其他函数)各个值,但是如果存在NA就会报错。
#为了解决这个问题,Series有一些能够跳过NA值的字符串操作方法。通过Series的str属性即可访问这些方法。例如,我们可以
#通过str.contains检查各个电子邮件地址是否含有"gmail".
print data.str.contains('gmail')
#输出结果如下:
# Dava False
# Rob True
# Steve True
# Wes NaN
(2)这里也可以使用正则表达式,还可以加上任意re选项(如IGNORECASE)
pattern='([A-Z0-9._%+-]@([A-Z0-9.-]+)\\.([A-Z]{2,4}))'
print data.str.findall(pattern,flags=re.IGNORECASE)
#输出结果如下:
# Dava [(a@google.com, google, com)]
# Rob [(b@gmail.com, gmail, com)]
# Steve [(e@gmail.com, gmail, com)]
# Wes NaN
(3)有两个方法可以实现矢量化的元素获取操作:要么使用str.get,要么在str属性上使用索引。
matches=data.str.match(pattern,flags=re.IGNORECASE)
print matches
print matches.str.get(1)
print matches.str[0]
矢量化的字符串方法
方法 说明
cat 实现元素级的字符串连接操作,可指定分隔符
contains 返回表示各字符串是否含有指定模式的布尔型数组
count 模式的出现次数
endswith、startwith 相当于对各个元素执行x.endswith(pattern)或x.startswith(pattern)
findall 计算各字符串的模式列表
get 获取各元素的第i个字符
join 根据指定的分隔符将Series中各元素的字符串连接起来
len 计算各字符串的长度
lower、upper 转换大小写。相当于对各个元素执行x.lower()或x.upper()
match 根据指定的正则表达式对各个元素执行re.match
pad 在字符串的左边、右边或左右两边添加空白符
center 相当于pad(side='both')
repeat 重复值。s.str.repeat(3)相当于执行x*x*x即x的3次方
replace 用指定字符串替换找到的模式
slice 对Series中的种个字符串进行子串截取
split 根据分隔符或正则表达式对字符串进行拆分
strip、rstrip、lstrip 去除空白符,所括换行符。相当于执行x.strip(),x.rstrip(),x.lstrip()
5. 示例:usda食品数据库
import json
from pandas import DataFrame,Series
import pandas as pd
db=json.load(open('foods-2011-10-03.json'))
print len(db) #6636
print db[0].keys()
#输出结果:
#[u'portions', u'description', u'tags', u'nutrients', u'group', u'id', u'manufacturer']
print db[0]['nutrients'][0]
#输出结果如下:
# {u'units': u'g', u'group': u'Composition', u'description': u'Protein', u'value': 25.18}
nutrients=DataFrame(db[0]['nutrients'])
print nutrients[:7]
#输出结果如下:
# description group units value
# 0 Protein Composition g 25.18
# 1 Total lipid (fat) Composition g 29.20
# 2 Carbohydrate, by difference Composition g 3.06
# 3 Ash Other g 3.28
# 4 Energy Energy kcal 376.00
# 5 Water Composition g 39.28
# 6 Energy Energy kJ 1573.00
#将字典列表转换为DataFrame时,可以只抽取其中的一部分字段。这里,我们将取出食物的名称、分类、编号以及制造商等信息。
info_keys=['description','group','id','manufacturer']
info=DataFrame(db,columns=info_keys)
print info[:5]
#通过value_counts,可以查看食物类别的分布情况:
print pd.value_counts(info.group)[:10]
#输出结果如下:
# Vegetables and Vegetable Products 812
# Beef Products 618
# Baked Products 496
# Breakfast Cereals 403
# Legumes and Legume Products 365
# Fast Foods 365
# Lamb, Veal, and Game Products 345
# Sweets 341
# Fruits and Fruit Juices 328
# Pork Products 328
# Name: group, dtype: int64
#为了对全部营养数据做一些分析,最简单的办法是将所有食物的营养成分整合到一个大表中。我们分几步骤来
#实现该目的。首先,将各食物的营养成分列表转换为一个DataFrame,并添加一个表示编号的列,然后将该DataFrmae添
#加到一个列表中,最后通过concat将这些东西连接起来就可以了。
nutrients=[]
for rec in db:
fnuts=DataFrame(rec['nutrients'])
fnuts['id']=rec['id']
nutrients.append(fnuts)
nutrients=pd.concat(nutrients,ignore_index=True)
print nutrients
#输出结果如下:
# Vegetables and Vegetable Products 812
# Beef Products 618
# Baked Products 496
# Breakfast Cereals 403
# Legumes and Legume Products 365
# Fast Foods 365
# Lamb, Veal, and Game Products 345
# Sweets 341
# Fruits and Fruit Juices 328
# Pork Products 328
# Name: group, dtype: int64
print nutrients.duplicated().sum()
#输出结果如下:14179
#由于两个DataFrmae对象中都有"group"和"description",所以为了明确到底谁是谁,我们需要对它们进行重命名。
col_mapping={'description':'food',
'group':'fgroup'}
info=info.rename(columns=col_mapping,copy=False)
print info
col_mapping={'description':'nutrient',
'group':'nutgroup'}
nutrients=nutrients.rename(columns=col_mapping,copy=False)
print nutrients
#做完这些事情之后,就可以将info跟nutrients合并起来:
ndata=pd.merge(nutrients,info,on='id',how='outer')
print ndata