目录
一、引言
终于放假了,又可以抽出时间来写点博客,这一学期本来以为有数字图像处理的课程,可以在图像处理方面学点理论知识,没想到只是个入门科普性质的课程,虽然有点干货稍微把零散的知识点串了起来,但落实到具体的理论基础也只是一笔带过。阴差阳错的帮老师做了些数据处理和筛选的工作,算是Pandas的重度使用患者了,本篇博客就来记录一下Pandas的基本使用、 常用函数、和某些偏门的方法吧。
二、基本使用方法
i.pandas数据类型
pandas里面常用的数据类型有Serise和DataFrame,二者在使用的时候还是有着大的差异,例如对前者可以使用argmax()的方法找到最大值的索引,而后者则没有这样的用法。自我感觉,DataFrame的用法更加的灵活,因为Pandas中有着一个十分灵活的函数apply(),它可以将DataFrame的所有行或者列作为Series输入一个给定函数,由此DataFrame就可以使用Series所特有的函数了,例如之前介绍的寻找最大值索引作用到DataFrame上就可以表示为df.apply(lambda x:x.argmax())
(后续会进一步介绍apply()的用法).而很多DataFrame中的函数如merge()、concat()则无法移植到Series上,所以后续的介绍基本都是以DataFrame为默认数据类型,如用到Series会特意标注。
ii.DataFrame的创建
DataFrame的创建分为从文件导入和直接创立两种方式。
a)从文件导入
范例如下:
df=pd.read_csv('test.csv',sep='\t',header='infer')
arg1为要导入的数据文件,虽然函数名为read_csv,实际上可以导入的数据格式并不只局限于.csv文件,txt文件也是可以导入的;
可选参数sep限定了文件的分隔符,默认分隔符为空格;
可选参数header限定了文件是否包含列名,如果包含列名的话,header=‘infer’,导入时将第一行作为DataFrame的列名;如果不含列名,header=None,导入时不考虑列名,列名默认按0、1、2…给出;
b)直接创立
范例如下:
df=pd.DataFrame({'a':[1,2,3,4],'b':[5,6,7,8],'c':[9,10,11,12]})
也可使用:
df=pd.DataFrame([[1,5,9],[2,6,10],[3,7,11],[4,8,12]],columns=['a','b','c'])
结果如下:
a b c
0 1 5 9
1 2 6 10
2 3 7 11
3 4 8 12
iii.DataFrame的属性
DataFrame的常用属性包括了列名(columns)、行名(index)、大小(size)、形状(shape),使用df.columns
类似形式即可访问,接下来重点介绍行名和列名的修改。
设使用的DataFrame为:
a b c
0 1 5 9
1 2 6 10
2 3 7 11
3 4 8 12
行名:[0,1,2,3]
列名:[‘a’,‘b’,‘c’]
需要着重强调的是无论是列名还是行名都要着重注意数字的出现,索引‘1’(字符型)与1(整型)是完全不同的。
a)列修改
对某些特定的列名进行修改时可以使用如下所示方法:
df.rename({'a':'d','b':'e'},axis=1)
其中key即为要修改的列名,value为修改后的列名,axis=1表示为对列进行操作。结果如下所示
d e c
0 1 5 9
1 2 6 10
2 3 7 11
3 4 8 12
如果列名的操作是针对于所有列,且所有列名修改都遵循某一规律,比如想在所有列后面加后缀‘_1’,就可以使用rename的改名函数用法,如下:
df.rename(lambda x:x+'_1',axis=1)
这里使用的函数是简单的匿名函数,如果是需要进行判断后的改名,也可以自定义函数后传入函数名。结果如下所示:
a_1 b_1 c_1
0 1 5 9
1 2 6 10
2 3 7 11
3 4 8 12
需要注意的是rename()函数不是立即生效函数,这就是为什么第二次rename的结果第一列列名是‘a_1’而不是‘d_1’,如果想作用于当前DataFrame,可以使用df=df.rename()或者df.rename(inplace=True)。
如果列名的操作是针对所有列,但无同一规律,修改方法为:
df.columns=['d','e','f']
d e f
0 1 5 9
1 2 6 10
2 3 7 11
3 4 8 12
如果是需要对列的顺序进行修改的话可以用:
df=df[['b','a','c']]
结果如下:
b a c
0 5 1 9
1 6 2 10
2 7 3 11
3 8 4 12
b)行修改
行名修改的方式与列名修改的方式类似,不再赘述,只给出参考代码和结果:
df.rename({1:'d',2:'2'})
a b c
0 1 5 9
d 2 6 10
2 3 7 11
3 4 8 12
df.rename(lambda x:x*2)
a b c
0 1 5 9
2 2 6 10
4 3 7 11
6 4 8 12
df=df.loc[[3,2,0,1]]
a b c
3 4 8 12
2 3 7 11
0 1 5 9
1 2 6 10
df.index=['d','e','f','g']
a b c
d 1 5 9
e 2 6 10
f 3 7 11
g 4 8 12
需要注意两点:一是使用rename()函数的时候,axis默认为0即对行进行操作;二是如果对行顺序进行改变,不能直接用
[]
的方式进行访问,因为这种方式是对列进行的操作,而应该使用.loc[]
,这种方式默认对行进行操作。
如果需要自行设置行索引的话,可以使用set_index()函数,使用方式如下:
df.set_index('a',drop=False)
其中arg1为新行索引的列名,drop为选择是否要舍弃作为索引的列,默认为True即将要作为索引的列从DataFrame中移除。结果如下:
a b c
a
1 1 5 9
2 2 6 10
3 3 7 11
4 4 8 12
行索引还可进行重设,这一性质常用于当对行进行了drop()操作后保证了行索引的连续性,便于定位。假定drop后的DataFrame如下:
a b c
0 1 5 9
1 2 6 10
3 4 8 12
重设行索引方式如下:
df.reset_index(drop=True)
其中drop设定是否在重设索引后,默认为False,即保留当前的索引列将其作为DataFrame中的一列,列名为’index’。结果如下:
a b c
0 1 5 9
1 2 6 10
2 4 8 12
iv.DataFrame的访问方式
DataFrame的常用访问方式有三种:[]
、.loc[]
、.iloc[]
,其具体使用方式见下:
[]
方式是直接选取列的操作,其使用范例为:
df['a']
0 1
1 2
2 3
3 4
Name: a, dtype: int64
为了说明.iloc[]
和.ilo[]
的区别,接下来使用的DataFrame为:
a b c
d 1 5 9
e 2 6 10
f 3 7 11
g 4 8 12
.loc[]
方式即可选取行、也可以选取某一个值,使用范例为:
df.loc['d']
a 1
b 5
c 9
Name: d, dtype: int64
选取行时给的参数为行名,返回值是一个Series
df.loc['d','a']
1
选取某一个值时,参数为行名、列名,需要注意这里用的词是行名,如果行名不是行的顺序的话,就应该使用行名。这里也可使用[[1,2],'a']
来访问多个数据。
.iloc[]
也既可选取行和某些特定值,使用范例为:
df.iloc[0]
a 1
b 5
c 9
Name: d, dtype: int64
选某一行时,参数为行顺序
df.iloc[0,0]
1
选取某一个值时,参数为行顺序、列顺序而非之前的行名、列名。
三、常用函数
i.merge()
merge()函数通常是用于求得两个DataFrame的交集或者并集,具体来说就是以两个DataFrame中各自一列(或者行索引)作为键值在列的方向上进行拼接,其具体参数如下:
pd.merge( left, right, how: str = 'inner', on=None, left_on=None, right_on=None, left_index: bool = False, right_index: bool = False, sort: bool = False, suffixes=('_x', '_y'), copy: bool = True, indicator: bool = False, validate=None, )
常用参数介绍:
left
:进行合并的左DataFrame;
right
:进行合并的右DataFrame;
on
:用于合并的键值,要求在左右DataFrame中都能找到该列,默认为None,即自动选取共同列作为合并的键值(可以用多列作为键值);
how
:进行合并的方式,默认为’inner’方式,可选方式还有:‘outer’,‘left’,‘right’。
'inner’方式意味着取交集,即只取两个DataFame中键值列中相同的部分所在行作为合并后的新行;
‘outer’方式意味着取并集,即取两个DataFrame中键值列中所有值所在行作为合并后的新行,对于某个DataFrame不包含该键值行,则以Nan填充;
‘left’方式意味着按左DataFrame进行合并,即保留左DataFrame中所有行,舍弃右DataFrame中与之键值不同的行,对于右DataFrame中不包含的左键值行,以Nan填充;
‘right’方式与’left’方式类似,不再赘述;
假定左DataFrame(df1):
a b c
0 1 5 9
1 2 6 10
2 3 7 11
3 4 8 12
右DataFrame(df2):
a b e
0 1 6 9
1 3 7 11
若直接执行df1.merge(df2)
:则采用on=None、how=‘inner’的方式,首先寻找共同列‘a’、’b‘,再取两df中[a,b]的交集:3,7,保留两df中该行,结果为:
a b c e
0 3 7 11 11
若执行df1.merge(df2,on='a')
:则只寻找共同列’a’,取两df中[a]的交集:1、3,保留两df中对应行,结果为:
a b_x c b_y e
0 1 5 9 6 9
1 3 7 11 7 11
若执行df1.merge(df2,on='a',how='outer')
:则寻找共同列‘a’,取两df中[a]的并集:1、2、3、4,结果为:
a b_x c b_y e
0 1 5 9 6.0 9.0
1 2 6 10 NaN NaN
2 3 7 11 7.0 11.0
3 4 8 12 NaN NaN
若执行df1.merge(df2,on='a',how='right')
:则保留df2的所有行,df1中多余行舍去,结果为:
a b_x c b_y e
0 1 5 9 6 9
1 3 7 11 7 11
其余参数介绍:
left_on
、right_on
:当左右DataFrame中不存在公共列的时候,使用left_on、right_on、分别指定两DataFrame中用于合并的键值列;
left_index
、right_index
:当需要以两DataFrame中的行索引作为键值列进行合并时,设定left_index、right_index为True即可实现;
suffixes
:当左右DataFrame中存在不止一列公共列,而并未取其所有所有作为合并键值,结果中势必会出现两列名相同的列,suffixes为共同列设定后缀以区别,默认为’_x’、‘_y’。
merge()函数有个很好的特性,就是合并后所得到的DataFrame其行顺序是和左DataFrame的顺序一致的,也就是说如果我们已知所计算样本的某些性质、总体的另一些性质,只需将样本作为左DF和总体进行merge,就可以得到样本的所有性质,同时样本的顺序不会发生改变,如果后续有操作涉及到对应项的加减乘除,这一顺序不变性将十分有用。
ii.concat()
concat()函数也是用于对DataFrame进行拼接的,相较于merge(),它可用于多个DataFrame的合并拼接,且不只局限于列方向上的拼接,还可作用于行,但自我感觉灵活性方面较merge()略差,所以一般将其用于行列名完全相同的DF拼接。其具体参数如下:
pd.concat( objs: Union[Iterable[~FrameOrSeries], Mapping[Union[Hashable, NoneType], ~FrameOrSeries]], axis=0, join='outer', ignore_index: bool = False, keys=None, levels=None, names=None, verify_integrity: bool = False, sort: bool = False, copy: bool = True, )
常用参数:
obj
:用于拼接的DF;
axis
:选择在哪一方向上拼接,默认为axis=0,即在行方向上进行拼接(增加行),axis=1时为在列方向上拼接;
ignore_index
:选择是否忽略当前拼接方向上已有的索引,按默认0、1、2重新赋予索引,常用于在行方向上拼接时避免索引重复;
假定用于拼接的两DF(df1,df2)如下:
a b c
0 1 5 9
1 2 6 10
2 3 7 11
3 4 8 12、
a b c
0 13 15 17
1 14 16 18
则执行pd.concat([df1,df2],ignore_index=True)
结果为:
a b c
0 1 5 9
1 2 6 10
2 3 7 11
3 4 8 12
4 13 15 17
5 14 16 18
执行pd.concat([df1,df2],axis=1)
结果为:
a b c a b c
0 1 5 9 13.0 15.0 17.0
1 2 6 10 14.0 16.0 18.0
2 3 7 11 NaN NaN NaN
3 4 8 12 NaN NaN NaN
iii.apply()
apply()函数用于将DF中的每一行(列)作为某一函数的输入,并收集所有的返回值合并为一个Series或者DataFrame输出。其使用方式为:
df1.apply(func, axis=0, raw=False, result_type=None, args=(), **kwds)
常用参数:
func
: 要使用的函数名,可以使用匿名函数;
axis
:传入的轴,默认为0,即将每一行作为单独的Series传入函数;
result_type
:设定apply返回值的数据类型,可以为Series或者DataFrame,默认值为None,可选值:‘reduce’、‘broadcast’和
‘expand’。需要注意的是经过实验和查阅文档发现,该参数的调整只有当axis=1时才有效,若axis=0,则result为单个值时返回Series,多个值时返回DataFrame(即使result为数组的形式)。
‘expand’:当函数func的返回值是类似于数组的形式时,将Seriies传入func时会将所得result中的每一个值作为单独一列,每个Series的函数返回值拼接在一起形成DataFrame返回;
’reduce’:可能的情况下将函数func的返回值作为一个整体,将每个Series的返回值数组作为一个值,所有的返回值拼在一起构成Series;
‘broadcast’:返回值最终会经broadcast变成和输入DataFrame一样的shape,广播机制较为复杂,不推荐使用;
None:当func的返回值为数组形式时,最终返回Seies;当func的返回值为Series时,最终返回DataFrame;
另经实验发现,当func返回值为Series时,即使设置为‘reduce’,最终返回值仍是DataFrame,而非Series。
args=(),**kwds
:当func具有多个输入值时,通过设置args或者kwds来给出,具体见之后的范例;
假定func为:
def test(df):
return [df.max(),df.argmax()]
使用的DataFrame(df1)为:
a b c
0 1 5 9
1 2 6 10
2 3 7 11
3 4 8 12
若执行df1.apply(test,axis=1)
:默认result_type为None,结果为:
0 [9, 2]
1 [10, 2]
2 [11, 2]
3 [12, 2]
dtype: object
若执行df1.apply(test,axis=1,result_type='expand')
:则输出为DataFrame,结果为:
0 1
0 9 2
1 10 2
2 11 2
3 12 2
若执行df1.apply(test,axis=1,result_type='reduce')
:则此时结果和默认值一致;
将func变更为:
def test(df,adding):
return pd.Series([df.max(),df.argmax()])+adding
若执行df1.apply(test,args=(2,),axis=1)
,则结果为:
0 1
0 11 4
1 12 4
2 13 4
3 14 4
若执行df1.apply(test,axis=1,**{'adding':2})
,则结果为:
0 1
0 11 4
1 12 4
2 13 4
3 14 4
需要注意的是,如果以args=()的方式传入多个参数,括号中的逗号不能省略。
iv.groupby()
goupby()函数用于对DataFrame进行分类,通常是用于对总体中不同种类的样本进行聚合,之后按类求取特征或者进行后续处理(常用agg(),apply(),describe())。其函数范例如下:
df1.groupby( by=None, axis=0, level=None, as_index: bool = True, sort: bool = True, group_keys: bool = True, squeeze: bool = <object object at 0x000002696E5D2CC0>, observed: bool = False, dropna: bool = True, )
常用参数:
by
:此参数用于指定如何分类,理论上来说是可以用不同方式,例如函数、dict、Series等等,但实际上并不常用,通常都是用列名或者列名list来进行分类;
as_index
、group_keys
:都是用于设置apply()应用于group之后所得DataFrame的索引,as_index、group_keys为True时返回DataFrame将使用键值同时作为索引(最终为多级索引),group_keys为False时返回DataFrame将使用原索引(单级索引),as_index为False时返回DataFrame将使用新的0、1、2等等同时作为索引(最终为多级索引),详见后续范例;
假定使用DataFrame为:
name v1 v2
0 a 1 10
1 a 2 11
2 b 3 12
3 a 4 13
4 b 5 14
5 c 6 15
6 c 7 16
7 a 8 17
8 a 9 18
9 b 0 19
执行df.groupby('name').apply(lambda x:print(x))
结果为:
name v1 v2
0 a 1 10
1 a 2 11
3 a 4 13
7 a 8 17
8 a 9 18
name v1 v2
2 b 3 12
4 b 5 14
9 b 0 19
name v1 v2
5 c 6 15
6 c 7 16
执行df.groupby('name').apply(lambda x:x.iloc[[0,1]])
:此时as_index、group_keys均为默认值True;
结果为:
name v1 v2
name
a 0 a 1 10
1 a 2 11
b 2 b 3 12
4 b 5 14
c 5 c 6 15
6 c 7 16
执行df.groupby('name',as_index=False).apply(lambda x:x.iloc[[0,1]])
结果为:
name v1 v2
0 0 a 1 10
1 a 2 11
1 2 b 3 12
4 b 5 14
2 5 c 6 15
6 c 7 16
执行df.groupby('name',group_keys=False).apply(lambda x:x.iloc[[0,1]])
结果为:
name v1 v2
0 a 1 10
1 a 2 11
2 b 3 12
4 b 5 14
5 c 6 15
6 c 7 16
四、零散知识
i.缺失值的处理
当我们从文件中读取数据时,很重要的一步是判断数据是否有缺失值、重复值,以及该如何处理这些缺失值、重复值,Pandas提供了一系列函数来干这件事情。
假定使用的DataFrame(df)为:
a b c
0 1 6 111.0
1 2 7 12.0
2 3 8 NaN
3 1 9 13.0
4 1 10 14.0
执行df.duplicated('a',keep='last')
获取存在重复值的行,arg1为用于判断重复值的列,如果不填的话则默认为所有列的组合(此处为[‘a’,‘b’,‘c’]);ag2用于设定如何标记重复值。
默认值’first’:除第一个重复值所在行之外所有存在重复值行标记为True,其余行为False;
可选值‘last’:除最后一个重复值所在行标记为True,其余行为False;
可选值‘False’:所有重复值均标记为True
结果为:
0 True
1 False
2 False
3 True
4 False
dtype: bool
执行df.drop_duplicates('a',keep='last')
以删除存在重复值的行,参数含义和前述一致,另有inplace(就地执行)、ignore_index(结果重编索引)可选;
结果为:
a b c
1 2 7 12.0
2 3 8 NaN
4 1 10 14.0
执行df.isna()
或df.isnull()
以获取存在Nan值的布尔型DataFrame;
isna()和isnull()的用途完全一样,因为pandas的数据类型是基于numpy的,而numpy中并未对na和null进行区分,统一用nan表示,所以这两个函数没有区别,单纯是模仿R语言。
可通过df.isna().sum(0).sum()
获取nan值的数目
结果为:
a b c
0 False False False
1 False False False
2 False False True
3 False False False
4 False False False
执行df.dropna(1,how='any')
以删除存在Nan值的行或列,arg1为轴,arg2为删除依据
默认值‘any’:只要存在Nan就删除
可选值’all’:所有值都为Nan才删除
如果想自行设定存Nan阈值,可使用thresh参数:这一行去除nan值,剩余值数目大于thresh值,则保留该行;
ii.DataFrame特征值获取
常用DataFrame特征值有最小值,最大值,平均值等等,每一个都有其专用的求取函数,如果需要获取这所有的特征值则没有必要一个个求取,可以使用describe()函数获得这一系列的特征值。
假定使用DataFrame(df):
a b c
0 1 6 111.0
1 2 7 12.0
2 3 8 NaN
3 1 9 13.0
4 1 10 14.0
执行df.describe()
,默认只使用数字类型的数值;
结果为:
a b c
count 5.000000 5.000000 4.000000
mean 1.600000 8.000000 37.500000
std 0.894427 1.581139 49.006802
min 1.000000 6.000000 12.000000
25% 1.000000 7.000000 12.750000
50% 1.000000 8.000000 13.500000
75% 2.000000 9.000000 38.250000
max 3.000000 10.000000 111.000000
iii.DataFrmae类型获取及修改
DataFrame类型通过dtypes获取,重点不是dtype 。之后可以通过astypes来修改数据类型,通常来说会搭配numpy里的各种数据来使用,因为pandas允许选取的数据类型较为有限。这种修改一般是为了在大数据处理时,减少对内存的占用。
假定使用DataFrame(df):
a b c
0 1 6 111.0
1 2 7 12.0
2 3 8 4.0
3 1 9 13.0
4 1 10 14.0
执行df.dtypes
;
结果为:
a int64
b int64
c float64
dtype: object
执行df['c']=df['c'].astype('int32')
;
结果为:
a b c
0 1 6 111
1 2 7 12
2 3 8 4
3 1 9 13
4 1 10 14
iv. 删除特定行列
如果想要删除符合某些条件值所在行或者列,可以采用如下方法。
假定DataFrame(df)为:
a b c
0 1 6 111
1 2 7 12
2 3 8 4
3 1 9 13
4 1 10 14
执行df.drop(df[df['a']==1].index)
;
结果为:
a b c
1 2 7 12
2 3 8 4
v.大文件的读取
普通文件的读取使用read_csv()即可达成目标,但是当文件过大时数据的读取速度会大大减慢,对电脑内存的要求也比较高。给出以下几种方法来读取大文件。
a)读取部分行
执行df=pd.read_csv(agefile,sep='\t',header=None,nrows=10)
,只读取文件中的前10行,通过nrows设定读取的行数;
b)读取特定列
执行df=pd.read_csv(agefile,sep='\t',header=None,names=['a','b','c'],usecols=['a'])
或者df=pd.read_csv(agefile,sep='\t',header=None,usecols=[0])
以获取数据中的第一列。
此处的usecols可以为实际的列名或者是位置顺序,如果为列名的话要求从文件本身可以推断出列名(自我理解是包含header)或者通过names显式给出;如果为位置顺序的话需要是列表的形式,即无法用切片[0:1]这一形式表达,且最终读取列的顺序与给出的位置顺序无关,即[1,0]的顺序实际仍为[0,1]。
c)分块读取
执行:
df=pd.read_csv(agefile,sep='\t',header=None,iterator=True)
temp=[]
while(True):
try:
temp.append(df.get_chunk(500))
except:
break
temp=pd.concat(temp)
设定文件为可迭代格式,循环从中读取一定数目的行加入list中,最后将所有DataFrame完成读取;
参考文献:
pandas数据合并之一文弄懂pd.merge()
python数据拼接: pd.concat
pandas中groupby函数中参数ax_index和group_keys的区别
pandas中多重索引multiIndex的使用
关于参数thresh的理解(pd.dropna(thresh=n))
使用Python Pandas处理亿级数据
Pandas分组运算(groupby)修炼
勘误:
i.DataFrame最大值寻址
在比较DataFrame和Series中,曾提到过DataFrame中无类似于argmax()的函数能找到最大值的索引,后发现通过df.idxmax()
即可获取每一列最大值所在位置,df.idxmin()
同理。