Pandas数据分析 ——Task02:索引

教程地址:joyful-pandas/第2章 索引——datawhalechina

Pandas索引

  • 一、单级索引
    • 1. loc方法、iloc方法、[]操作符
    • 2. 布尔索引
    • 3. 快速标量索引
    • 4. 区间索引
  • 二、多级索引
    • 1. 创建多级索引
  • 2. 多层索引切片
  • 4. 索引层的交换
  • 三、索引设定
    • 1. index_col参数
    • 2. reindex和reindex_like
    • 3. set_index和reset_index
    • 4. rename_axis和rename
  • 四、常用索引型函数
    • 1. where函数
    • 2. mask函数
    • 3. query函数
  • 五、重复元素处理
    • 1. duplicated方法
  • 2. drop_duplicates方法
  • 六、抽样函数(sample函数)
  • 七、问题与练习

一、单级索引

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数值成正比

七、问题与练习

  1. 问题
  • 【问题一】 如何更改列或行的顺序?如何交换奇偶行(列)的顺序?

  • 【问题二】 如果要选出DataFrame的某个子集,请给出尽可能多的方法实现。

  • 【问题三】 query函数比其他索引方法的速度更慢吗?在什么场合使用什么索引最高效?

  • 【问题四】 单级索引能使用Slice对象吗?能的话怎么使用,请给出一个例子。

  • 【问题五】 如何快速找出某一列的缺失值所在索引?

  • 【问题六】 索引设定中的所有方法分别适用于哪些场合?怎么直接把某个DataFrame的索引换成任意给定同长度的索引?

  • 【问题七】 多级索引有什么适用场合?

  • 【问题八】 什么时候需要重复元素处理?

  1. 练习
  • 【练习一】 现有一份关于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'

你可能感兴趣的:(pandas,python,数据分析,pandas)