教程地址:joyful-pandas/第2章 索引——datawhalechina
Pandas索引
一、单级索引
1. loc方法、iloc方法、[]操作符
最常用的索引方法可能就是这三类,其中iloc表示位置索引,loc表示标签索引,[]也具有很大的便利性,各有特点
- (1)loc方法
所有在loc中使用的切片全部包含右端点
本质上说,loc中能传入的只有布尔列表和索引子集构成的列表
df.loc[1103] #单行索引
df.loc[[1102,2304]] #多行索引
df.loc[1304:] #行截取索引
df.loc[2402::-1] #行逆序排列索引
# 列索引中必须在列名前面加上“ :,”顶替行的位置
df.loc[:,'Height'] #单列索引
df.loc[:,['Height','Math']] #多列索引
df.loc[:,'Height':'Math'] #列截取索引
df.loc[1102:2401:3,'Height':'Math'] #联合索引
# loc的函数索引,前面的df就是传入参数
df.loc[lambda x:x['Gender'] == 'M'] #函数式索引1
def f(x): #函数式索引2
return [1101,1103]
df.loc[f]
# 布尔索引(下面两个语句意思一样)
df.loc[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]]
- (2)iloc方法
与loc不同,切片右端点不包含
loc中接收的参数只能为整数或整数列表,不能使用布尔索引
df.iloc[3] #单行索引
df.iloc[3:5] #多行索引
df.iloc[:,3] #单列索引
# 表示从第序号为7的列开始从后往前每隔1个取出1个
df.iloc[:,7::-2] #多列索引
df.iloc[3::4,7::-2] #混合索引
df.iloc[lambda x:[3]] #函数式索引
- (3)[]操作符
不要在行索引为浮点时使用[]操作符,因为在Series中的浮点[]并不是进行位置比较,而是值比较,非常特殊
一般来说,[]操作符常用于列选择或布尔选择,尽量避免行的选择
# 1)Series的[]操作
s = pd.Series(df['Math'],index = df.index) #单元素索引,使用原df设定索引标签
print(s[1103])
s[0:4] #多行索引,使用的是绝对位置的整数切片,与元素无关
# 注意使用lambda函数时,使用的不是绝对位置切片,而是元素切片
# (如:s[lambda x: 16::-6]就报错)
s[lambda x: x.index[16::-6]] #函数式索引
s[s>80] #布尔索引,也是对于元素的判断而非位置
# 2)DataFrame的[]操作
df[0:1] #单行索引,只能使用切片形式(如:df[0]会报错)
#利用get_loc()输出索引为1102的行
row = df.index.get_loc(1102)
df[row:row+1]
df[3:5] #多行索引
df['School'] #单列索引
df[['School','Math']] #多列索引
df[lambda x:['Math','Physics']] #函数式索引
df[df['Gender']=='F'] #布尔索引
2. 布尔索引
- (1)布尔符号
布尔符号 | 含义 |
---|---|
& | 和(and) |
| | 或(or) |
~ | 取反(not) |
# 筛选出不满足“Math>75或者Address为street_1”的数据行
df[~((df['Math']>75) | (df['Address']=='street_1'))]
# 下面语句会输出满足“Math>60且列号为3”的的数据项
df.loc[df['Math']>60,[True if i == 3 else False for i in range(8)]]
- (2)isin()方法
# 筛选出Address为street_1或street_4且Physics为A的A+的数据行
df[df['Address'].isin(['street_1','street_4'])&df['Physics'].isin(['A','A+'])]
#上面也可以用字典方式写(all与&的思路是类似的,1代表按照跨列方向判断是否全为True)
df[df[['Address','Physics']].isin({'Address':['street_1','street_4'],'Physics':['A','A+']}).all(1)]
3. 快速标量索引
当只需要取一个元素时,at和iat方法能够提供更快的实现
df.at[1101,'School']
df.iat[0,0]
4. 区间索引
- (1)interval_range方法
interval_range方法可输出特定索引组
print(pd.interval_range(start = 0,end = 5))
# periods参数控制区间个数,freq控制步长
# closed参数可选'left''right''both''neither',默认左开右闭
print(pd.interval_range(start = 0,periods = 8,freq = 5,closed = 'both'))
输出:
IntervalIndex([(0, 1], (1, 2], (2, 3], (3, 4], (4, 5]],
closed='right',
dtype='interval[int64]')
IntervalIndex([(0, 5], (5, 10], (10, 15], (15, 20], (20, 25], (25, 30], (30, 35], (35, 40]],
closed='right',
dtype='interval[int64]')
- (2)利用cut将数值列转为区间为元素的分类变量
# 统计数学成绩的区间情况
math_interval = pd.cut(df['Math'],bins=[0,40,60,80,100])
#注意,如果没有类型转换,此时并不是区间类型,而是category类型
print(math_interval.head(3))
输出:
ID
1101 (0, 40]
1102 (0, 40]
1103 (80, 100]
Name: Math, dtype: category
Categories (4, interval[int64]): [(0, 40] < (40, 60] < (60, 80] < (80, 100]]
- (3)区间索引的选取
# 将'Math','Math_interval'两列取出并将'Math_interval'设置为
df_i = df.join(math_interval,rsuffix='_interval')[['Math',
'Math_interval']].reset_index().set_index('Math_interval')
#包含该值就会被选中
df_ii = df_i.loc[[65,90]]
print(df_ii.head(3),"\n",df_ii.tail(3))
# 如果想要选取某个区间,先要把分类变量转为区间变量,再使用overlap方法
# 只要索引与(70,85]这个区间有交集就会被选中(所以会有Math=87.9的行)
dd = df[df_i.index.astype('interval').overlaps(pd.Interval(70, 85))]
print(dd.head(3))
结果:
ID Math
Math_interval
(60, 80] 1202 63.5
(60, 80] 1205 68.4
(60, 80] 1305 61.7
ID Math
Math_interval
(80, 100] 2101 83.3
(80, 100] 2205 85.4
(80, 100] 2304 95.5
School Class Gender Address Height Weight Math Physics
ID
1103 S_1 C_1 M street_2 186 82 87.2 B+
1104 S_1 C_1 F street_2 167 81 80.4 B-
1105 S_1 C_1 F street_4 159 64 84.8 B+
二、多级索引
1. 创建多级索引
- (1)通过from_tuple或from_arrays
# 直接创建元组
tuples = [('A','a'),('A','b'),('B','a'),('B','b')]
mul_index = pd.MultiIndex.from_tuples(tuples, names = ('Upper', 'Lower'))
tupd = pd.DataFrame({'Score':['perfect','good','fair','bad']},index = mul_index)
print(tupd)
# 或利用zip创建元组
L1 = list('AABB')
L2 = list('abab')
tuples = list(zip(L1,L2))
# 或通过Array创建元组(应用from_tuples()时会自动变为tuples)
arrays = [['A','a'],['A','b'],['B','a'],['B','b']]
# 通过from_product,相当于两两相乘(结果同上)
L1 = ['A','B']
L2 = ['a','b']
mul_index = pd.MultiIndex.from_product([L1,L2],names=('Upper', 'Lower'))
结果:
Score
Upper Lower
A a perfect
b good
B a fair
b bad
- (2)指定df中的列创建(set_index方法)
df_using_mul = df.set_index(['Class','Address'])
2. 多层索引切片
# 当索引不排序时,单个索引会报出性能警告
# 可用df_using_mul.index.is_lexsorted()检查是否排序
df_using_mul.sort_index().loc['C_2','street_5'] #单行切片
df_using_mul.sort_index().loc[('C_2','street_6'):('C_3','street_4')] #多行切片
# 非元组也是合法的,表示选中该层所有元素
df_using_mul.sort_index().loc[('C_2','street_7'):'C_3']
# 由元组构成列表
df_using_mul.sort_index().loc[[('C_2','street_7'),('C_3','street_2')]]
# 选出第一层在‘C_2’和'C_3'中且第二层在'street_4'和'street_7'中的行
df_using_mul.sort_index().loc[(['C_2','C_3'],['street_4','street_7']),:]
3.多层索引中的slice对象
L1,L2 = ['A','B'],['a','b','c']
mul_index1 = pd.MultiIndex.from_product([L1,L2],names=('Upper', 'Lower'))
L3,L4 = ['D','E'],['d','e','f']
mul_index2 = pd.MultiIndex.from_product([L3,L4],names=('Big', 'Small'))
df_s = pd.DataFrame(np.random.rand(6,6),index=mul_index1,columns=mul_index2)
print(df_s)
idx = pd.IndexSlice
# loc[idx[*,*]]型:第一个星号表示行,第二个表示列
# 使用布尔索引时,需要索引对齐
# 如df_s.loc[idx['B':,df_s.iloc[:,0]>0.6]]会因索引没有对齐报错
df1 = df_s.loc[idx[('B','a'):,df_s.iloc[0]>0.6]]
print("df1:",df1.head(3))
# loc[idx[*,*],idx[*,*]]型:前面一个idx表示行索引,后面一个idx为列索引
# 后面的层出现,则前面的层必须出现,不能是df_s.loc[idx['a'],idx['D':]]
df_s.loc[idx['A'],idx['D':]]
# 相应位置还可以使用布尔索引
df_s.loc[idx[:'B',df_s.iloc[:,0]>0.6],:]
# 此形式下布尔索引是可以索引不对齐的,只需要长度一样
df_s.loc[idx[:'B',(df_s.iloc[0]>0.6)[:6]],:]
# idx中层数k1大于df层数k2时,idx前k2个参数若相应位置是元素或者元素切片,则表示相应df层的元素筛选,同时也可以选择用同长度bool序列
# idx后面多出来的参数只能选择同bool序列,这样设计的目的是可以将元素筛选和条件筛选同时运用
df_s.loc[idx[:'B','c':,(df_s.iloc[:,0]>0.6)],:]
输出:
Big D E
Small d e f d e f
Upper Lower
A a 0.756318 0.681447 0.131777 0.419787 0.843431 0.993405
b 0.567127 0.318070 0.462902 0.657311 0.879187 0.615061
c 0.316502 0.716819 0.808445 0.280472 0.347763 0.450904
B a 0.062730 0.025407 0.688346 0.263974 0.147681 0.321922
b 0.654218 0.462553 0.764526 0.727644 0.472959 0.429260
c 0.930351 0.643988 0.377320 0.656949 0.095370 0.605625
df1: Big D E
Small d e e f
Upper Lower
B a 0.062730 0.025407 0.147681 0.321922
b 0.654218 0.462553 0.472959 0.429260
c 0.930351 0.643988 0.095370 0.605625
4. 索引层的交换
- (1)swaplevel方法(两层交换)
df_using_mul = df.set_index(['Class','Address'])
df_using_mul2 = df_using_mul.swaplevel(i=1,j=0,axis=0).sort_index()
print(df_using_mul.head(3),df_using_mul2.head(3))
输出:
School Gender Height Weight Math Physics
Class Address
C_1 street_1 S_1 M 173 63 34.0 A+
street_2 S_1 F 192 73 32.5 B+
street_2 S_1 M 186 82 87.2 B+ School Gender Height Weight Math Physics
Address Class
street_1 C_1 S_1 M 173 63 34.0 A+
C_2 S_2 M 175 74 47.2 B-
C_3 S_1 F 175 57 87.7 A-
- (2)reorder_levels方法(多层交换)
df_muls = df.set_index(['School','Class','Address'])
df_muls2 = df_muls.reorder_levels([2,0,1],axis=0).sort_index()
print(df_muls.head(3),df_muls2.head(3))
输出:
Gender Height Weight Math Physics
School Class Address
S_1 C_1 street_1 M 173 63 34.0 A+
street_2 F 192 73 32.5 B+
street_2 M 186 82 87.2 B+ Gender Height Weight Math Physics
Address School Class
street_1 S_1 C_1 M 173 63 34.0 A+
C_3 F 175 57 87.7 A-
S_2 C_2 M 175 74 47.2 B-
- (3)如果索引有name,可以直接使用name
df_muls.reorder_levels(['Address','School','Class'],axis=0)
三、索引设定
1. index_col参数
index_col是read_csv中的一个参数,而不是某一个方法
pd.read_csv('data/table.csv',index_col=['Address','School'])
2. reindex和reindex_like
reindex是指重新索引,它的重要特性在于索引对齐,很多时候用于重新排序
可以选择缺失值的填充方法:fill_value和method(bfill/ffill/nearest)
其中method参数必须索引单调,其各个参数意义如下:
bfill表示用所在索引1206的后一个有效行填充
ffill为前一个有效行
nearest是指最近的
df.reindex(index=[1101,1203,1206,2402],method='bfill')
df.reindex(columns=['Height','Gender','Average'])
# reindex_like的作用为生成一个横纵索引完全与参数列表一致的DataFrame,数据使用被调用的表
df_temp = pd.DataFrame({'Weight':range(5),
'Height':range(5),
'ID':[1101,1104,1103,1106,1102]}).set_index('ID').sort_index()
df_temp.reindex_like(df[0:5][['Weight','Height']],method='bfill')
3. set_index和reset_index
- (1)set_index方法
将某些列设置为索引,append参数可以将当前索引维持不变
df.set_index('Class',append = True)
# 当使用与表长相同的列作为索引,需要先转化为Series
df.set_index(pd.Series(range(df.shape[0])))
# 可以直接添加多级索引
df.set_index([pd.Series(range(df.shape[0])),pd.Series(np.ones(df.shape[0]))])
- (2)reset_index方法
将索引重置
df.reset_index() #默认状态直接恢复到自然数索引
# 用level参数指定哪一层被reset,用col_level参数指定set到哪一层
df_temp1 = df_temp.reset_index(level=1,col_level=1)
4. rename_axis和rename
- (1)rename_axis
针对多级索引,作用是修改某一层的索引名,而不是索引标签
df_temp.rename_axis(index={'Lower':'LowerLower'},columns={'Big':'BigBig'})
- (2)rename
用于修改列或者行索引标签,而不是索引名
df_temp.rename(index={'A':'T'},columns={'e':'changed_e'})
四、常用索引型函数
1. where函数
where函数将不满足条件的行全部设置为NaN,并用dropna()去掉
df.where(df['Gender']=='M').dropna()
# 第一个参数为布尔条件,第二个参数为填充值
df.where(df['Gender']=='M',np.random.rand(df.shape[0],df.shape[1]))
2. mask函数
mask函数与where功能上相反,其余完全一致,即对条件为True的单元进行填充
df.mask(df['Gender']=='M').dropna()
df.mask(df['Gender']=='M',np.random.rand(df.shape[0],df.shape[1]))
3. query函数
query函数中的布尔表达式中,下面的符号都是合法的:
行列索引名、字符串、and/not/or/&/|/~/not in/in/==/!=、四则运算符
df.query('(Address in ["street_6","street_7"])&
(Weight>(70+10))&(ID in [1303,2304,2402])')
五、重复元素处理
1. duplicated方法
返回是否重复的布尔列表
可选参数keep默认为first,即首次出现设为不重复
若为last,则最后一次设为不重复,若为False,则所有重复项为True
df.duplicated('Class',keep = 'last')
2. drop_duplicates方法
剔除重复项,参数与duplicate函数类似
df.drop_duplicates('Class',keep = 'last')
# 在传入多列时等价于将多列共同视作一个多级索引,比较重复项
df.drop_duplicates(['School','Class'])
六、抽样函数(sample函数)
# n为样本量,axis为抽样维度(默认为0,即抽行)
df.sample(n = 5,axis = 1)
# frac为抽样比
df.sample(frac = 0.05)
# replace为是否放回
df.sample(n = df.shape[0],replace = True)
# weights为样本权重,自动归一化
df.sample(n=3,weights=np.random.rand(df.shape[0]))
# 以某一列为权重,这在抽样理论中很常见
df.sample(n=3,weights=df['Math']) #抽到的概率与Math数值成正比
七、问题与练习
- 问题
-
【问题一】 如何更改列或行的顺序?如何交换奇偶行(列)的顺序?
-
【问题二】 如果要选出DataFrame的某个子集,请给出尽可能多的方法实现。
-
【问题三】 query函数比其他索引方法的速度更慢吗?在什么场合使用什么索引最高效?
-
【问题四】 单级索引能使用Slice对象吗?能的话怎么使用,请给出一个例子。
-
【问题五】 如何快速找出某一列的缺失值所在索引?
-
【问题六】 索引设定中的所有方法分别适用于哪些场合?怎么直接把某个DataFrame的索引换成任意给定同长度的索引?
-
【问题七】 多级索引有什么适用场合?
-
【问题八】 什么时候需要重复元素处理?
- 练习
- 【练习一】 现有一份关于UFO的数据集,请解决下列问题
(a)在所有被观测时间超过60s的时间中,哪个形状最多?
(b)对经纬度进行划分:-180°至180°以30°为一个划分,-90°至90°以18°为一个划分,请问哪个区域中报告的UFO事件数量最多?
df = pd.read_csv('joyful-pandas-master/data/UFO.csv')
# 取出duration (seconds) > 60s的记录,并对其中shape进行计数,输出其中最大值
shapecounts = df[df['duration (seconds)'] > 60]['shape'].value_counts()
print("(a)",shapecounts.index[0])
# 用cut()划分longitude和latitude,并加入df
df['longitudes'] = pd.cut(df['longitude'],bins = [30*i for i in range(-6,7)])
df['latitudes'] = pd.cut(df['latitude'],bins = [18*i for i in range(-5,6)])
# 利用多层索引组合longitude和latitude并统计,输出其中最大值
llcount = df.set_index(['longitudes','latitudes']).index.value_counts()
print("(b)",llcount.head(1))
结果:
(a) light
(b) ((-90, -60], (36, 54])
- 【练习二】 现有一份关于口袋妖怪的数据集,请解决下列问题
(a)双属性的Pokemon占总体比例的多少?
(b)在所有种族值(Total)不小于580的Pokemon中,非神兽(Legendary=False)的比例为多少?
(c)在第一属性为格斗系(Fighting)的Pokemon中,物攻排名前三高的是哪些?
(d)请问六项种族指标(HP、物攻、特攻、物防、特防、速度)极差的均值最大的是哪个属性(只考虑第一属性,且均值是对属性而言)?
(e)哪个属性(只考虑第一属性)的神兽比例最高?该属性神兽的种族值也是最高的吗?
df = pd.read_csv('joyful-pandas-master/data/Pokemon.csv')
# 观察知,单属性的Pokemon'Type 2'一栏为Nan
# 故直接统计这栏有效数据数量,即为双属性的Pokemon的个数,再除以总数
twotypes = df['Type 2'].count()/df.shape[0]
print("(a)",twotypes)
# 因为Legendary一栏下只有True和False,可借助统计函数分析
Legendary = df[df['Total'] >= 580]['Legendary'].value_counts(normalize=True)
print("(b)",Legendary)
# 将第一属性为Fighting的Pokemon,按物攻进行排序,并输出前3个
Fighting = df[df['Type 1']=='Fighting'].sort_values(by='Attack',ascending=False)
print("(c)",Fighting.iloc[:3]['Name'])
# 计算极差
df['range'] = df.iloc[:,5:11].max(axis=1)-df.iloc[:,5:11].min(axis=1)
# 将Type 1和range取出
Pokemon = df[['Type 1','range']].set_index('Type 1')
Type1 = Pokemon.index.unique()
maxtype = ''
maxmean = 0
# 循环计算种族值最大值
for ty in Type1:
newmean = Pokemon.loc[ty,:].mean()[0]
if newmean > maxmean:
maxtype = ty
maxmean = newmean
print(maxtype)
LegendaryType1c = df[df['Legendary'] == True]['Type 1'].value_counts()
print("(e)",LegendaryType1c.index[0])
# 将神兽的type 1和Total两行取出
LegendaryType1 = df[df['Legendary'] == True][['Type 1','Total']]
LegendaryType1.set_index('Type 1')
Type1 = LegendaryType1.index.unique()
maxtype = ''
maxmean = 0
# 循环计算种族值最大值
for ty in Type1:
newmean = LegendaryType1.loc[ty,:].mean()
if newmean > maxmean:
maxtype = ty
maxmean = newmean
print(maxtype)
结果:
(a) 0.5175
(b) 0.424779
(c) LucarioMega Lucario
Conkeldurr
Machamp
(d) 'Steel'
(e)'Psychic'
'Normal'