在上篇文章学机器学习,不会数据处理怎么行?—— 一、NumPy详解中,介绍了NumPy的一些基本内容,以及使用方法,在这篇文章中,将接着介绍另一模块——Pandas。(本文所用代码在这里)
Pandas数据结构介绍
大家应该都听过表结构,但是,如果让你自己来实现这么一个结构,并且能对其进行数据处理,能实现吗?我相信,大部分人都能做出来,但是不一定能做的很好。而Python中的一个模块pandas给我们提供了一个很好的数据结构,它包括了序列Series和数据框DataFrame。pandas是基于NumPy数组构建的,特别是基于数组的函数和不使用for循环的数据处理,让以Numpy为中心的应用变得更加简单。
Series
创建方式
Series是一种类似于一维数组的对象,它由一组数据(各种NumPy数据类型)以及一组与之相关的数据标签(即索引)组成,其创建主要有三种方式
1)通过一维数组创建序列
import numpy as np import pandas as pd arr1 = np.arange(10) s1 = pd.Series(arr1)
通过type函数可查看arr1与s1的类型分别为,numpy.ndarray 和 pandas.core.series.Series。
3)通过字典的方式创建
dic1 = {'a': 1,'b': 2,'c': 3,'d': 4,'e': 50} s2 = pd.Series(dic1)
通过type函数可查看dic1与s2的类型分别为,dict 和 pandas.core.series.Series。
3)通过DataFrame中的某一行或者某一列创建序列
这一步将在下面讲DataFrame的时候再补充
Series索引
我们上面在创建Series序列时,可以输出查看我们创建的Series长什么样,发现有两列,第二列的数据跟我们输入的是一样的,那第一列是什么呢?那就是索引。Series的字符串表现形式为:索引在左边,值在右边。如果我们没有为数据指定索引,就会自动创建一个0到N-1(N为数据的长度)的整数型索引。
我们可以通过 Series 的 values 和 index 属性获取其数组表示形式和索引对象。
obj = pd.Series([6, 9, -8, 1]) obj.values obj.index
我们也能修改其索引
obj.index = ['a','b','c','d']
讲了那么多,知道这是索引了,但它有什么用呢?
1.我们可以通过索引值或索引标签进行数据的获取
obj['a'] obj[3] obj[[0,2,3]] obj[0:2] obj['a':'c']
注:若通过索引标签获取数据的话,末端标签对应的值也将返回!!!
2.自动化对齐
如果有两个序列,需要对这两个序列进行算术运算,这时索引的存在就体现的它的价值了—自动化对齐.
obj1 = pd.Series(np.array([10,15,20,30,55,80]),index = ['a','b','c','d','e','f']) obj2 = pd.Series(np.array([12,11,13,15,14,16]),index = ['a','c','g','b','d','f']) obj1+obj2 obj1*obj2
我们会发现,计算后的序列号中,g和e对应的值为NaN,这是因为obj1中没有g索引,obj2中没有e索引,所以数据的运算会产生两个缺失值NaN。
DataFrame
DataFrame是一个表格型的数据结构,它含有一组有序的列,每列可以是不同的值类型(数值、字符串、布尔值等)。DataFrame既有行索引也有列索引,它可以被看做由Series组成的字典。
创建方式
1)通过二维数组创建
arr2 = np.array(np.arange(12).reshape(4,3))
df1 = pd.DataFrame(arr2)
2)通过字典的方式创建
该方式可通过字典列表和嵌套字典实现,如果将嵌套字典传给DataFrame,pandas就会被解释为:外层字典的键作为列,内层键则作为行索引
#字典列表 dic2 = {'a': [1, 2, 3, 4],'b': [5, 6, 7, 8],'c': [9, 10, 11, 12],'d': [13, 14, 15, 16]} df2 = pd.DataFrame(dic2) #嵌套字典 dic3 = {'one': {'a': 1,'b': 2,'c': 3,'d':4}, 'two': {'a': 5,'b': 6,'c': 7,'d': 8}, 'three':{'a': 9,'b': 10,'c': 11,'d':12}} df3 = pd.DataFrame(dic3)
3)通过数据框的方式创建
df4 = df3[['one','three']] s3 = df4['one']
DataFrame索引
DataFrame的索引与Series的索引大体上是一样的,不过DataFrame有两个索引,分别是列索引和行索引,感觉看起来就跟excel差不多了。具体的实现方式可通过下面的部分来了解。
利用pandas进行数据处理
pandas可以通过布尔索引有针对的选取原数据的子集、指定行、指定列等,同时我们可以通过 pd.read_csv()来导入数据集,因为暂时找不到数据集,就从别人的代码里复制一些过来了
data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'], 'year': [2000, 2001, 2002, 2001, 2002, 2003], 'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]} frame = pd.DataFrame(data)
选择数据
简单筛选
我们可以使用以下函数来查询数据的前几行或后几行(默认5行)
frame.head()
frame.tail()
也可以使用行列索引来简单地查询指定的行和列
frame['state'] frame[0:3]
loc
当然,上面的方式只是比较简单的操作,复杂些的,我们可以通过 loc 来选取数据(: 表示所有行)
frame.loc[:,['state','year']]
iloc
另外,我们也可以使用iloc,通过位置选择在不同情况下所需要的数据,可进行选取某个数据、连续或者跨行选择等操作
frame.iloc[0:2,0:2]
ix
除此之外还有一种操作方式,使用 混合选择 ix ,我这里的表可能无法很好的体现,如果将行索引改为字符就可以看出来了
frame.ix[3:5,['year']]
发现输入这行代码运行之后发出一条警告
DeprecationWarning: .ix is deprecated. Please use .loc for label based indexing or .iloc for positional indexing
emmmmm。。想想以后主要还是用 loc 和 iloc 好了。
通过判断的筛选
除了上面讲的之外,我们还可以通过判断指令进行选择,即通过约束某项条件然后选择出当前所有数据。
frame[frame['year']>2001]
数据修改
讲完了数据选择,既然选择出了数据,我们可能会想去修改数据,那么,怎么修改呢?
根据位置
我们可以利用索引或者标签确定需要修改的位置
frame.iloc[0,2]=1.6 frame.loc[1,'pop']=2
根据条件
如果我们想将数据中'pop'栏小于1.7的'year'改为1999,那么我们可以进行如下操作
frame.year[frame['pop']<1.7]=1999
按行或按列设置
我们还可以对行列进行批处理
frame['pop']=2.6
添加和删除数据
上面讲的都是数据的修改,再来讲讲数据的添加和删除
#数据添加 #方法一 frame['A'] = np.nan #方法二 frame['B'] = pd.Series([1, 2, 3, 4, 5, 6]) #数据删除 frame.drop('B',axis = 1)
注:
- 对于方法二,长度必须对齐,如果行索不为默认则需指定索引
dates = pd.date_range('20130101', periods=6) df = pd.DataFrame(np.arange(24).reshape((6,4)),index=dates, columns=['A','B','C','D']) df['E'] = pd.Series([1, 2, 3, 4, 5, 6],index=pd.date_range('20130101',periods=6))
- 对于数据删除,axis取值 0表示删除行索引 1表示删除列索引 默认为0
统计分析
pandas模块为我们提供了非常多的描述性统计分析的指标函数,如总和、均值、最小值、最大值等,跟对应的NumPy数组方法相比,它们都是基于没有缺失数据的假设而构建的,下图中列举了大部分函数
这里有几点需要注意
- descirbe函数只能针对序列或者数据框,一维数组是没有这个方法的。
- 使用sum时,NaN值会自动被排除,除非整个切片都是NaN,通过skipna选项可以禁用该功能。
- 调用sum时,返回的时含有列的和的Series,需要传入axis的值才会进行行运算
当我们要对一列数据进行分析时,我们可以将要分析的内容封装成一个函数,方便调用
def stats(x): return pd.Series([x.count(),x.min(),x.idxmin(), x.quantile(.25),x.median(), x.quantile(.75),x.mean(), x.max(),x.idxmax(), x.mad(),x.var(), x.std(),x.skew(),x.kurt()], index = ['Count','Min','Whicn_Min', 'Q1','Median','Q3','Mean', 'Max','Which_Max','Mad', 'Var','Std','Skew','Kurt'])
有时或许会需要对一个数据框进行分析,那该如何操作?是一列一列不断地取出来然后调用函数吗?这个方法也不是不可行,但面对众多数据时该怎么办?一个一个调用怕不是要弄到明年,在这里我们可以使用apply函数
d1 = pd.Series(2*np.random.normal(size = 100)+3) d2 = np.random.f(2,4,size = 100) d3 = np.random.randint(1,100,size = 100) df = pd.DataFrame(np.array([d1,d2,d3]).T,columns=['x1','x2','x3']) df.head() df.apply(stats)
就这样很简单的创建了数值型数据的统计性描述。但如果是离散型数据呢?就不能用这个统计口径了,我们需要统计离散变量的观测数、唯一值个数、众数水平及个数。你只需要使用describe方法就可以实现这样的统计了。
缺失值处理
现实生活中的数据是非常杂乱的,其中缺失值也是非常常见的,对于缺失值的存在可能会影响到后期的数据分析或挖掘工作,那么我们该如何处理这些缺失值呢?常用的有三大类方法,即删除法、填补法和插值法。
删除法:当数据中的某个变量大部分值都是缺失值,可以考虑删除改变量;当缺失值是随机分布的,且缺失的数量并不是很多是,也可以删除这些缺失的观测。
替补法:对于连续型变量,如果变量的分布近似或就是正态分布的话,可以用均值替代那些缺失值;如果变量是有偏的,可以使用中位数来代替那些缺失值;对于离散型变量,我们一般用众数去替换那些存在缺失的观测。
插补法:插补法是基于蒙特卡洛模拟法,结合线性模型、广义线性模型、决策树等方法计算出来的预测值替换缺失值。
删除法
这里仅讲述删除法和替补法,先来看删除法,先建立一个6X4的矩阵数据并且把两个位置置为空
dates = pd.date_range('20130101', periods=6) df = pd.DataFrame(np.arange(24).reshape((6,4)),index=dates, columns=['A','B','C','D']) df.iloc[0,1] = np.nan df.iloc[1,2] = np.nan
我们可以结合sum函数和isnull函数来检测数据中含有多少缺失值
for i in df.columns: print(sum(pd.isnull(df[i])))
也可以通过any来判断是否存在缺失值
np.any(df.isnull()) == True
删除直接dropna就行了
df.dropna( axis=0, # 0: 对行进行操作; 1: 对列进行操作 默认为0 how='any' # 'any': 只要存在 NaN 就 drop 掉; 'all': 必须全部是 NaN 才 drop 默认为'any' )
填补法
简单粗暴的办法就是把所有的NaN用0代替
df.fillna(value=0)
稍微好一点的方法,使用常量填充不同的列
df.fillna({'B': 3,'C': 4})
还算好的方法,采用前项填充或后向填充
df.fillna(method = 'ffill') #用前一个观测值填充 df.fillna(method = 'bfill') #用后一个观测值填充
较好的方法,用均值或中位数填充各自的列
df.fillna(df.median())
df.fillna(df.mean())
很显然,在使用填充法时,相对于常数填充或前项、后项填充,使用各列的众数、均值或中位数填充要更加合理一点,这也是工作中常用的一个快捷手段。
注:使用fillna,dropna时,需要添加参数 inplace = True,如df.fillna(df.median(),inplace = True),以确认修改,否则实际的数据并不会有改动。
数据合并
concat
pandas 处理多组数据的时候往往会要用到数据的合并处理,使用 concat 是一种基本的合并方式.而且concat中有很多参数可以调整,合并成你想要的数据形式.
先创建数据集
df1 = pd.DataFrame(np.ones((3,4))*0, columns=['a','b','c','d']) df2 = pd.DataFrame(np.ones((3,4))*1, columns=['a','b','c','d']) df3 = pd.DataFrame(np.ones((3,4))*2, columns=['a','b','c','d'])
concat合并
pd.concat([df1, df2, df3], axis=0) #纵向合并 pd.concat([df1, df2, df3], axis=1) #横向合并
这里观察下纵向合并的输出,会发现index的值为0,1,2,0,1,2,0,1,2,大部分情况下,这肯定不是我们想要的,我们可以通过一下方法来重置index
pd.concat([df1, df2, df3], axis=0, ignore_index=True)
pandas中的数据结构是支持类似SQL的部分操作方式的,如添加新行新列、多表连接、聚合什么的。pandas有个join参数,用来定义连接方式的,默认为outer,此方式是依照column来做纵向合并有相同的column上下合并在一起,其他的独自成列,没有的值用NaN填充
df1 = pd.DataFrame(np.ones((3,4))*0, columns=['a','b','c','d'], index=[1,2,3]) df2 = pd.DataFrame(np.ones((3,4))*1, columns=['b','c','d','e'], index=[2,3,4]) res = pd.concat([df1, df2], axis=0, join='outer')#纵向"外"合并df1与df2
参数:
- axis 表示行或列,0为行,1为列
- join 'inner':内连接,会产生NaN的列丢弃 ‘outer’:外连接,上面介绍过了
另外,还有join_axes也可以决定连接方式
pd.concat([df1, df2], axis=1, join_axes=[df1.index])
pd.concat([df1, df2], axis=1, join_axes=[df2.index])
运行上面两行代码,就会发现有所不同,第一行是以df1的index为依据,将df2中具有相同索引的行对应拼接上去,第二行同样的道理。
此外,还有append操作,该操作类似与axis=0,join='outer'时的操作,不过要注意的是append只有纵向合并。
merge
pandas中的合并操作除了concat之外,还有merge操作,主要用于两组有key column的数据,统一索引的数据. 通常也被用在Database的处理当中.
看个简单的,依据一组Key合并
left = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'], 'A': ['A0', 'A1', 'A2', 'A3'], 'B': ['B0', 'B1', 'B2', 'B3']}) right = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'], 'C': ['C0', 'C1', 'C2', 'C3'], 'D': ['D0', 'D1', 'D2', 'D3']}) pd.merge(left, right, on='key')
稍微难一点就是依据两组Key合并,合并时有四种方法 how=['left','right','outer','inner'],默认为how='inner'
left = pd.DataFrame({'key1': ['K0', 'K0', 'K1', 'K2'], 'key2': ['K0', 'K1', 'K0', 'K1'], 'A': ['A0', 'A1', 'A2', 'A3'], 'B': ['B0', 'B1', 'B2', 'B3']}) right = pd.DataFrame({'key1': ['K0', 'K1', 'K1', 'K2'], 'key2': ['K0', 'K0', 'K0', 'K0'], 'C': ['C0', 'C1', 'C2', 'C3'], 'D': ['D0', 'D1', 'D2', 'D3']}) res = pd.merge(left, right, on=['key1', 'key2'], how='inner') print(res) res = pd.merge(left, right, on=['key1', 'key2'], how='outer') print(res) res = pd.merge(left, right, on=['key1', 'key2'], how='left') print(res) res = pd.merge(left, right, on=['key1', 'key2'], how='right') print(res)
都试一试然后输出就知道是如何操作的了。
上面讲的是以列索引为标准进行合并,当然,我们还可以以index为标准进行合并
left = pd.DataFrame({'A': ['A0', 'A1', 'A2'], 'B': ['B0', 'B1', 'B2']}, index=['K0', 'K1', 'K2']) right = pd.DataFrame({'C': ['C0', 'C2', 'C3'], 'D': ['D0', 'D2', 'D3']}, index=['K0', 'K2', 'K3']) res = pd.merge(left, right, left_index=True, right_index=True, how='outer') print(res) res = pd.merge(left, right, left_index=True, right_index=True, how='inner') print(res)
聚合和分组
pandas提供了一个灵活高效的groupby功能,它使你能以一种自然的方式对数据集进行切片、切块、摘要等操作。根据一个或多个键(可以是函数、数组或DataFrame列名)拆分pandas对象。计算分组摘要统计,如计数、平均值、标准差,或用户自定义函数。
根据列索引分组
df = pd.DataFrame({'key1':['a', 'a', 'b', 'b', 'a'], 'key2':['one', 'two', 'one', 'two', 'one'], 'data1':np.random.randn(5), 'data2':np.random.randn(5)}) df['data1'].groupby(df['key1']).mean()
这里我们按照key1将数据分组,然后计算分组后的data1的平均值。
我们也可以一次传入多个数组
df['data1'].groupby([df['key1'], df['key2']]).mean()
此外,还可以将列名用作分组
df.groupby('key1').mean() df.groupby(['key1', 'key2']).mean()
注:这里在执行df.groupby('key1').mean()时,结果中没有key2列。这是因为df['key2']不是数值数据,所以被从结果中排除了。默认情况下,所有数值列都会被聚合,虽然有时可能会被过滤为一个子集。
分组后可进行迭代
for name, group in df.groupby('key1'): print(name) print(group)
对于多重键的情况
for (k1, k2), group in df.groupby(['key1', 'key2']): print (k1, k2) print (group)
上面进行的group操作返回的类型都是Series,如果我们想返回DataFrame类型的结果,那么我们可以进行如下操作
df[['data2']].groupby([df['key1']])
根据行类型进行分组
groupby默认是在axis=0上进行分组的,通过设置也可以在其他任何轴上进行分组。拿上面例子中的df来说,我们可以根据行类型(dtype)对列进行分组
dict(list(df.groupby(df.dtypes,axis = 1)))
这方面还有蛮多内容,不过都还没接触过,只能暂时讲到这里了,如果要了解更多,可以访问这篇博客 ,我在后面也会逐渐完善这些方面的内容。
结尾
讲了这么多,相信你对pandas肯定有了一定的了解了,但由于个人接触不多,就暂时介绍到这里,pandas还有很多内容如数据透视表、多层索引的使用、数据清洗等,将在后面学到时再做补充