数据分析索引总结(上)Pandas单级索引

Datawhale干货 

作者:闫钟峰, Datawhale优秀学习者

寄语:本文对单级索引中的 loc、 iloc、[]三种方法进行了详细的阐述。同时,对布尔索引,快速标量索引方式、区间索引方式做了详细介绍。

读取csv数据的时候, 使用参数index_col指定表中的列作为索引

import numpy as np

import pandas as pd

df = pd.read_csv('data/table.csv',index_col='ID')

df.head()

效果等同于读取数据后, 使用set_index方法指定某一列为索引,但index_col的方式更简洁。

df1 = pd.read_csv('data/table.csv')

df2=df1.set_index(['ID'])

df2.head()

最常用的索引方法有三类:

  • loc表示标签索引

  • iloc表示位置索引

  • []也具有很大的便利性。

loc方法

注意:所有在loc中使用的切片全部包含右端点!

① 单行索引

df.loc[1103]

虽然这里的1103是整数, 但loc索引方式用的是索引标签, 而不是默认整数索引(注意默认整数索引和标签索引这二者有时候是一样的)

② 多行索引

多行索引时,需传入一个list,而不是多个索引

df.loc[[1102,2304]]

多行索引时, 传入的必须是一个list, 而不是两个或多个索引, 否则会报错

#以下索引报错

# TypeError: cannot do label indexing on <class 'pandas.core.indexes.base.Index'> with these indexers [2304] of <class 'int'>

df.loc[1102,2304]

list的切片方法可以沿用

df.loc[1304:].head()

和list的情形一样, 2402::-1表示从索引标签=2402的元素开始,以步长=1返回list的元素, 负号表示方向是从后向前。注意由于用的是loc, 所以这里的2402是标签索引, 这和list所用默认整数索引不一样。

df.loc[2402::-1].head()

③ 单列索引

使用loc方法获取列, 比直接使用列标签获取列更复杂

df.loc[:,'Height'].head()

等价的更简单的获取列的方法,loc和iloc的长处在于, 可以同时对列和行进行切片

df['Height'].head()

更简洁的使用列名标签索引的方式

df.Height.head()

④ 多列索引

df.loc[:,['Height','Math']].head()

# 等价于df[['Height','Math']].head()

注意传入的应该是list

df[['Height','Math']].head()

使用列名标签做切片---这是list里所没有的

df.loc[:,'Height':'Math'].head()

还可以使用iloc的方式进行切片, 这时候传入的应该是默认整数索引, 从0开始, 并且切片的结尾是不包含的

df.iloc[:,4:7].head()

切片方法对列名不成立,若使用报错如下:

# 以下语句报错

# TypeError: cannot do slice indexing on <class 'pandas.core.indexes.numeric.Int64Index'> with these indexers [Height] of <class 'str'>

df['Height':'Math'].head()

⑤ 联合索引

1102=起点,2401=终点,3=步长

df.loc[1102:2401:3,'Height':'Math']#.head()

⑥ 函数式索引

loc中使用的函数,传入参数就是前面的df;本质上这是一个布尔索引: lambda函数分别根据每行的Gender值列返回一个布尔值, 然后用这个布尔值序列来筛选df的行,布尔值为真则返回,否则筛选掉。

df.loc[lambda x:x['Gender']=='M'].head()

看起来和上述略有不同。但实际上, 使用loc等方法筛选行或者列的时候, 都是根据待筛选的行或者列对给定的筛选条件是否为真来决定是否返回该行或该列的。

def f(x):

return [1101,1103]

df.loc[f]

⑦ 布尔索引

df.loc[df['Address'].isin(['street_7','street_4'])].head()

# 类似的sql语句为 select * from df where Address in ('street_7','street_4')

布尔值的Series

df['Address'].isin(['street_7','street_4'])

df.loc[[True if i[-1]=='4' or i[-1]=='7' else False for i in df['Address'].values]].head()

# 实现相同筛选的sql代码类似于 select * from df where substr(Address,-1,1) in ('4','7')


小节:本质上说,loc中能传入的只有布尔列表和索引子集构成的列表,只要把握这个原则就很容易理解上面那些操作。

iloc方法

① 单行索引
df.iloc[3]

② 多行索引

注意结尾是不包含的---和list的切片保持一致

df.iloc[3:5]
③ 单列索引
df.iloc[:,3].head()

④ 多列索引

逗号前的 : 对行筛选, 表示返回所有行。逗号后的 7::-2 表示从第8列开始,向前每隔一列取一列(步长为2, 2前的负号表示向前迭代)

df.iloc[:,7::-2].head()

⑤ 混合索引

从第四行开始向后以步长为4选择行, 从第八列开始向前以步长为2选择列。得到原始df的若干行和若干列的交叉位置组成的一个子df, 类似于子矩阵。

df.iloc[3::4,7::-2]#.head()

⑥ 函数式索引

注意: 由于是iloc,返回值必须是由默认整数索引作为元素构成的类list的数据结构。

df.iloc[lambda x:[3]].head()

如果返回值不存在, 则会报错。

# IndexError: positional indexers are out-of-bounds

df.iloc[lambda x:[30000]].head()
df.iloc[lambda x:range(3)]

其他比如 list(range(3)) , tuple(range(3))等等类list的数据结构都可以, 但set(range(3))不行。

df.iloc[lambda x:np.arange(3)] 

小节:由上所述,iloc中接收的参数只能为整数或整数列表,不能使用布尔索引。

[]操作符

如果不想陷入困境,请不要在行索引为浮点时使用[]操作符,因为在Series中的浮点[]并不是进行位置比较,而是值比较,非常特殊。

Series的[]操作

① 单元素索引

如果没有指明loc还是iloc, 默认传入的是索引标签

s = pd.Series(df['Math'],index=df.index)

s[1101]

使用loc方法

s.loc[1101]

s.head()

如果传入默认整数索引, 会出错---特别是索引标签也恰好包含了这个你传入的默认整数索引的时候,不会报错,但会返回和你想象中不一样的元素,需要特别注意

s[1]

使用iloc,后边接默认整数索引

s.iloc[1]
② 多行索引

② 多行索引

使用的是绝对位置的整数切片,与元素无关,这里容易混淆。

s[0:4]

③ 函数式索引

注意使用lambda函数时,直接切片(如:s[lambda x: 16::-6])就报错,此时使用的不是绝对位置切片,而是元素切片,非常易错。

s[lambda x: x.index[16::-6]]

下面语句报错:

s[lambda x: 16::-6]

#TypeError: cannot do slice indexing on <class 'pandas.core.indexes.numeric.Int64Index'> with these indexers [<function <lambda> at 0x00000000083FFCA8>] of <class 'function'>

是因为方括号不能直接接整数索引切片吗? 并不是, 直接传递整数切片是可以的。

s[16::-6]

def f(x):

return x[16::-6]

f(s)

改一下lambda函数定义,返回了一个预料之外的Series。

s[lambda x: x[16::-6]]

再改一下,改为取索引。

s[lambda x: x[16::-6].index]

这样就对了---这是因为, 这里的lambda函数返回的是索引, 因此能够根据索引正确地返回s的一段切片。 因为lambda函数返回值是索引, 索引通过方括号传递给s,就可以取回s的相应索引位置的元素。

s[16::-6].index

作为对比, 最普通的形式其实是这样的---这里的16是默认整数索引。

s[16::-6]

使用apply方法怎么实现?

s.apply??

④ 布尔索引

s[s>80]

# 类似的sql语句是 select * from s where s.value>80

复杂的布尔索引--可能是支持一阶谓词逻辑的

s[(s>80)&(s<95)] # 没有括号的时候会报错--s[(s>80&s<95)]

DataFrame的[]操作

① 单行索引

这里非常容易写成df['label'],会报错。同Series使用了绝对位置切片。

df[1:2]

如果使用了标签索引, 程序仍会认为传入的是默认整数索引,就会得到意料之外的结果

df[1102:]

这个没有报错, 是因为并没有像上边直接去找整数索引等于1102的,而是用一个空的索引集合去取df的子集。 如果想要获得某一行,更好的办法是用如下的 get_loc 方法:

row = df.index.get_loc(1102) # df.index.get_loc 将标签索引转换为默认整数索引

df[row:row+1]

df.index.get_loc??

使用loc方法得到的是个Series

df.loc[1102,:]

为了得到的一行仍然是个df, 需要传入区间--注意传入的1102是标签索引,因此尾端是不包含的。

df.loc[1102:1102,:]
② 多行索引

② 多行索引

用切片,如果是选取指定的某几行,推荐使用loc,否则很可能报错。尽管这种写法可以正确执行,但不推荐。

df[3:5]

推荐使用iloc或者loc来明确地说明用的是默认整数索引(iloc, 尾端不包含)还是标签索引(loc,尾端包含)。

df.iloc[3:5]
③ 单列索引

③ 单列索引

使用列名标签来返回单列,之所以选择列的语法如此简单, 是因为df本质上是将多个Series作为列拼接起来的。

df['School'].head()

将列传递给df构造器

pd.DataFrame(df['School']).head()

使用to_frame方法将Series转为df

df['School'].to_frame().head()

使用iloc方法返回同一列--注意由于是iloc,所以这里传入了列的默认整数索引。

df.iloc[:,0].head()

使用loc时, 需使用标签索引---如果df只有默认整数索引, 这时候默认整数索引同时也是标签索引。

df.loc[:,'School'].head()

只有默认整数索引,因此默认整数索引也是标签索引的一个例子

df1=pd.DataFrame(np.random.randint(1,10,12).reshape(3,4))

对上述df1, 使用iloc, 自然会返回第一列

df1.iloc[:,0]

对上述df1, 使用loc时, 也会正确地返回第一列。

df1.loc[:,0]

但是传入的是切片的时候,会默认使用的是默认整数索引, 因此尾端是不包含的。

df1[0:2]

除非显式地使用loc,才会包含尾端

df1.loc[0:2]

单列索引的另一个更加简单的选择方式

df.School.head()

如果列名标签中有空格, 这没法用这种方式

df.columns=['School Name', 'Class', 'Gender', 'Address', 'Height', 'Weight', 'Math', 'Physics']

如果用这种形式,会报错

df.School Name

上式报错SyntaxError: invalid syntax,这时只有用

df['School Name'].head()

复原columns

df.columns=['School', 'Class', 'Gender', 'Address', 'Height', 'Weight', 'Math', 'Physics']

④ 多列索引

索引多列时,传入的必须是一个list,而不是多个列名标签--方括号应该有两层。

df[['School','Math']].head()

使用loc方式

df.loc[:,['School','Math']].head()

使用iloc方式--传入的列名是默认整数索引,从0开始

df.iloc[:,[0,6]].head()

⑤函数式索引

这里的lambda是一个常数值的函数

df[lambda x:['Math','Physics']].head()

一个选取列名长度大于5的函数,其实这仍然是个常值函数

df[lambda x: [x for x in df.columns if len(x)>5]].head()

改进版--选择列名长度大于5的列

df[lambda x: [a for a in x.columns if len(a)>5]].head()

df1 的列名是默认整数索引, 需先转化为string类型---当然, 由于一位数的长度都小于5, 所以会是个空df。

df1[lambda x: [a for a in x.columns if len(str(a))>5]].head()

lambda函数有时候不是必要的, 用列表推导式同样可以实现上述筛选, 而且更容易理解。关键是要给df后的[]传入一个list。

df[[ x for x in df.columns if len(x)>5]].head()


⑥ 布尔索引

df[df['Gender']=='F'].head()

# 等价的sql语句 select * from df where Gender='F'

df[df.Gender=='F'].head()
小节:一般来说,[]操作符常用于列选择或布尔选择,尽量避免行的选择

布尔索引

1. 布尔符号:'&','|','~':分别代表和and,或or,取反not

df[(df['Gender']=='F')&(df['Address']=='street_2')].head()

# 等价的sql语句 select * from df where Gender='F' and Address='street_2'

df[(df['Math']>85)|(df['Address']=='street_7')].head()

# select * from df where df.Math>85 or df.Address='street_7'

df[~((df['Math']>75)|(df['Address']=='street_1'))].head()

# 等价的sql语句 select * from df where not (math>75 or Address='street_1')

#df[~((df['Math']>75)|(df['Address']=='street_1'))].head()

df[~(df['Math']>75)][~(df['Address']=='street_1')].head()

loc和[]中相应位置都能使用布尔列表选择:

如果不加values就会索引对齐发生错误,Pandas中的索引对齐是一个重要特征,很多时候非常使用。但是若不加以留意,就会埋下隐患。这个筛选不能直接对应到相应的sql ,因为sql没有对列名做筛选的机制。

df.loc[df['Math']>60,(df[:8]['Address']=='street_6').values].head()

在对列的筛选中, 如果不加values属性,得到的是一个布尔值的Series。

df[:8]['Address']=='street_6'

使用了values属性,就得到一个list,就可以用来选择列了。

(df[:8]['Address']=='street_6').values

不加values 会报错,因为传入的不是list。

df.loc[df['Math']>60,(df[:8]['Address']=='street_6')].head()

# IndexingError: Unalignable boolean Series provided as indexer (index of the boolean Series and of the indexed object do not match).

2. isin方法

df[df['Address'].isin(['street_1','street_4'])&df['Physics'].isin(['A','A+'])]

# select * from df where Address in ('street_1','street_4') and Physic in ('A','A+')

上面也可以用字典方式写:all与&的思路是类似的,其中的1代表按照跨列方向判断是否全为True。熟悉sql的话,显然上一个筛选方法更容易理解。

df[df[['Address','Physics']].isin({'Address':['street_1','street_4'],'Physics':['A','A+']}).all(1)

快速标量索引

当只需要取一个单元格里的元素时,at和iat方法能够提供更快的实现:

display(df.at[1101,'School'])

display(df.loc[1101,'School'])

display(df.iat[0,0])

display(df.iloc[0,0])

#可尝试去掉注释对比时间

%timeit df.at[1101,'School']

%timeit df.loc[1101,'School']

%timeit df.iat[0,0]

%timeit df.iloc[0,0]

#当数据集更大的时候,差别更明显

df.at[1101,:]

# at方法只能选择单元格?

# TypeError: unhashable type: 'slice'

df.at[1101,df.columns[:3]]

# TypeError: unhashable type: 'Index'

df.at??

# Access a single value for a row/column label pair.

区间索引

此处介绍并不是说只能在单级索引中使用区间索引,只是作为一种特殊类型的索引方式,在此处先行介绍。

1. 利用interval_range方法

closed参数可选'left''right''both''neither',默认左开右闭#只传入start和end,默认 freq=1。

pd.interval_range(start=0,end=5)

periods参数控制区间个数,freq控制步长。

pd.interval_range(start=0,periods=8,freq=5)
pd.interval_range??

2. 利用cut将数值列转为区间为元素的分类变量,

例如统计数学成绩的区间情况:使用pd.cut函数进行分割后, 如果没有类型转换,此时并不是区间类型,而是category类型。

math_interval = pd.cut(df['Math'],bins=[0,40,60,80,100])

默认是左开右闭区间,可以使用right参数指定是左闭右开还是左开右闭。在选择bins的时候,bins的范围尽量将数据取值区间完全包括在内,避免因区间开闭导致取值被舍去。

math_interval.head()
math_interval.values

3. 区间索引的选取

将数学成绩转化为分数所在区间, 效果类似于降采样。

df_i = df.join(math_interval,rsuffix='_interval')[['Math','Math_interval']].reset_index().set_index('Math_interval')

df_i.head()

它是个Categories类型。

df_i.index

包含该值就会被选中

df_i.loc[65].head()

传入包含两个值的list,则list中每个元素所在的区间都会被选中。

df_i.loc[[65,90]]

df_i.loc[[65,30]]

如果想要选取某个区间,先要把分类变量转为区间变量,再使用overlap方法:

报错--这是由于cut得到的看起来像是区间的数据类型, 实际上是个categories。 cut得到的区间实际上是个catagory 类型的数据,并不能直接用来判断和给定区间是否重合,必须使用astype转换为区间类型的数据。

df_i.loc[pd.Interval(70,75)].head()

df_i[df_i.index.astype('interval').overlaps(pd.Interval(70, 85))].head()

overlaps 用于判断一个由区间构成的类list的表里的元素是否与给定的区间有重合,有重合则返回True---本质上还是传递一个布尔值list给df_i。返回所有的行索引(转换为区间后)与给定区间有重叠的行。

cut得到的区间实际上是个catagory 类型的数据,并不能直接用来判断和给定区间是否重合,必须使用astype转换为区间类型的数据。

df_i.index

overlaps 用于判断一个由区间构成的类list的表里的元素是否与给定的区间有重合,有重合则返回True。

tmp=df_i.index.astype('interval')

tmp.overlaps??

不能同时传入两个区间。

df_i[df_i.index.astype('interval').overlaps(pd.Interval(70, 85),pd.Interval(20, 35))].head()

可以看到,最终传给df_i后的方括号的,还是一个布尔值索引。

tmp.overlaps(pd.Interval(20, 35))
pd.Interval(70, 85)

pd.Interval??

# left, right, close 三个参数

方便学习 后台回复 索引 下载 完整PDF(上中下)

“为沉迷学习 点赞

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章