索引操作可以方便地获取 Pandas 基础数据结构中的指定元素。
1 | import pandas as pd |
索引器
DataFrame 的索引形式是列索引,即通过 [列名] 的形式可以从 DataFrame 中取出相应的列。如果是单个列名,则返回一个 Series 对象;如果是多个列名组成的列表,则返回一个 DataFrame。此外,若要取出单列,且列名中不包含空格,则还可以使用 .列名 的形式取出。
1
2
3
4df = pd.read_csv('./data/learn_pandas.csv', usecols=['School', 'Grade', 'Name',
'Gender', 'Weight', 'Transfer'])
print(df['Name'].head()) # 单列Series对象 == df.Name.head()
print(df[['Gender', 'Name']].head()) # 多列组成的DataFrameSeries 的索引形式是行索引,即通过 [idx] 的形式可以从 Series 中取出相应的行,这里的 idx 可以是字符串也可以是整数(包括未指定索引时从0开始的缺省值)。如果 idx 对应的只有单个值,则返回这个标量值;否则返回 Series 对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# 以字符串为索引的Series
s = pd.Series([1, 2, 3, 4, 5, 6], index=['a', 'b', 'a', 'a', 'a', 'c'])
print(s['a']) # Series([1, 3, 4, 5], index=['a', 'a', 'a', 'a'])
print(s['b']) # 2
print(s[['c', 'b']]) # Series([6, 2], index=['c', 'b'])
# 如果想要取出某两个索引之间的元素,并且这两个索引是在整个索引中唯一出现,则可以使用切片
# 需要注意的是这里的切片会包含两个端点!
print(s['c': 'b': -2]) # Series([6, 4, 2], index=['c', 'a', 'b'])
# 以整数为索引的Series
s = pd.Series(['a', 'b', 'c', 'd', 'e', 'f'], index=[1, 3, 1, 2, 5, 4])
print(s[1]) # Series(['a', 'c'], index=[1, 1])
print(s[[2, 3]]) # Series(['d', 'b'], index=[2, 3])
# 如果使用整数切片,则会取出对应索引位置的值,注意这里的整数切片同 Python 中的切片一样不包含右端点
print(s[1:-1:2]) # Series(['b', 'd'], index=[3, 2])
DataFrame 默认的中括号索引是列索引,还需要支持行索引才可以方便我们按行访问数据,所以 Pandas 为 DataFrame 提供了另外两种行索引器:一种是基于元素的 loc索引器,另一种是基于位置的 iloc索引器。
loc 索引器的一般形式是 loc[*, *] ,其中第一个 * 代表行的选择,第二个 * 代表列的选择,如果省略第二个位置写作 loc[*] ,这个 * 是指行的筛选。其中,* 的位置一共有五类合法对象,分别是:单个元素、元素列表、元素切片、布尔列表以及函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24df_demo = df.set_index('Name') # 将“姓名”设置为索引
print(df_demo.loc['Qiang Sun']) # 改名字存在多人,则返回DataFrame; 如果只有一个,则返回Series
print(df_demo.loc['Qiang Sun', 'School']) # 同时选择行和列
print(df_demo.loc[['Qiang Sun','Quan Zhao'], ['School','Gender']])
# 使用切片: 无论是整数还是字符串类型的索引,切片都包含起点与终点,且不允许有重复值,否则报错
print(df_demo.loc['Gaojuan You':'Gaoqiang Qian', 'School':'Gender'])
# 使用布尔列表: 要求传入的布尔列表长度与DataFrame相同
print(df_demo.loc[df_demo.Weight>70])
condition_1_1 = df_demo.School == 'Fudan University'
condition_1_2 = df_demo.Grade == 'Senior'
condition_1_3 = df_demo.Weight > 70
condition_1 = condition_1_1 & condition_1_2 & condition_1_3
print(df_demo.loc[condition_1]) # 复合逻辑判断组成的布尔列表
# 使用函数: 返回值必须是索引支持的类型(上述几种),函数的输入是Dataframe本身
def condition(x):
condition_2_1 = x.School == 'Peking University'
condition_2_2 = x.Grade == 'Senior'
condition_2_3 = x.Weight > 80
condition_2 = condition_2_1 & (~condition_2_2) & condition_2_3
return condition_2
print(df_demo.loc[condition])iloc 索引器的使用与 loc 完全类似,只不过是针对位置进行筛选,在相应的* 位置处一共也有五类合法对象,分别是:整数、整数列表、整数切片、布尔列表以及函数
1
2
3
4
5
6
7
8
9
10
11print(df_demo.iloc[1, 1]) # 第二行第二列
print(df_demo.iloc[[0, 1], [0, 1]]) # 前两行前两列
# 使用切片: 注意不包含终点!
print(df_demo.iloc[1: 4, 2:4])
# 使用布尔列表: 注意需要出入的是条件序列的 values 属性!
print(df_demo.iloc[(df_demo.Weight>80).values])
# 使用函数: 返回值必须是索引支持的类型(上述几种),函数的输入是Dataframe本身
print(df_demo.iloc[lambda x: slice(1, 4)]) # lambda函数的返回值是切片
在使用索引对表或者序列赋值时需要特别注意,应当在使用一层索引器后直接进行赋值操作,这样做是由于进行多次索引后赋值是赋在临时返回的 copy 副本上的,而没有真正修改元素。
query方法
另一种对 DataFrame 进行查询的方法是 query 方法,它支持直接使用字符串形式的查询表达式进行查询数据,其表达式的执行结果必须返回布尔列表。在 query 表达式中,帮用户注册了所有来自 DataFrame 的列名,所有属于列 Series 的方法都可以被调用,和正常的函数调用并没有区别
1 | print(df.query('((School == "Fudan University")&' |
可以看出,query 表达式支持 python 中的逻辑单词(or, and, is in等);对于含有空格的列名,需要使用反引号括住列名(`col name`)的方式进行引用。
随机抽样
还有一种返回 DataFrame 数据的方法就是随机抽样,如果把 DataFrame 的每一行看作一个样本,而把每一列看作一个特征,再把整个 DataFrame 看作总体,有时候想要对样本或特征进行随机抽样就可以用 sample 函数。
1 | df_sample = pd.DataFrame({'id': list('abcde'), |
sample 函数中的主要参数为 n, axis, frac, replace, weights,前三个分别是指抽样数量、抽样的方向(0 为行、1 为列)和抽样比例(0.3 则为从总体中抽出30% 的样本)。replace 和 weights 分别是指是否放回和每个样本的抽样相对概率,当replace = True 则表示有放回抽样
多级索引
我们基于上面的 DataFrame 表格数据,人工造一个具有多级索引的新表格:
1 | import numpy as np |
与单层索引的表一样,具备元素值、行索引和列索引三个部分。其中,这里的行索引和列索引都 MultiIndex 类型,只不过索引中的一个元素是元组而不是单层索引中的标量。例如,行索引的第四个元素为(”B”, ”Male”) ,列索引的第二个元素为(”Height”, ”Senior”)。
1 | print(df_multi.index.names) |
接下来,我们来看一看多级索引中的 loc 索引器如何使用,我们使用原表重新构建一个具有多级行索引的 DataFrame(列仍然是单级索引)。
1 | df_multi = df.set_index(['School', 'Grade']) |
很显然,多级索引应该用元组为下标进行访问,loc 的方法和前面所讲的完全一样,只需要把下标从单级索引的标量替换成多级索引的元组(在索引前最好对 MultiIndex 进行排序以避免性能警告):
1 | df_multi = df_multi.sort_index() |
布尔列表和函数同样是可以使用的,多级索引元组的元素如果是列表,则可以自动交叉组合成新的索引。此外,iloc 的方法同样可以完全照搬。
前面介绍的方法,即使在索引不重复的时候,也只能对元组整体进行切片,而不能对每层进行切片,也不允许将切片和布尔列表混合使用,引入 IndexSlice 对象就能解决这个问题。Slice 对象一共有两种形式,第一种为 loc[idx[*,*]] 型,第二种为 loc[idx[*,*],idx[*,*]] 型。这里我们就不详细展开了,大家遇到具体问题可以上网查找相应的使用方法。
索引的常用方法
- 交换:由 swaplevel( ) 和 reorder_levels( ) 来实现,前者只能交换两个层,后者可以交换任意层,两者都可以指定交换的轴是哪一个(即行索引或列索引)
- 删除:若想删除某一层的索引,可以使用 droplevel( ) 方法
- 修改:通过 rename_axis( ) 可以对索引层的名字进行修改,常用的修改方式是传入字典的映射;通过 rename( ) 可以对索引的值进行修改,如果是多级索引需要指定修改的层号 level;还有一个 map( ) 函数接收多级索引的元组作为参数
- 设置:使用 set_index( ) 完成索引的设置,这里的主要参数是 append ,表示是否来保留原来的索引,直接把新设定的添加到原索引的内层;reset_index( ) 是 set_index( ) 的逆函数,其主要参数是 drop ,表示是否要把去掉的索引层丢弃,而不是添加到列中
我们构建了一个更为复杂的具有三级索引的 DataFrame,来演示上述方法的使用:
1 | np.random.seed(0) |
索引运算
如果存在两张表,它们拥有相同的索引,我们可以通过对索引进行相应的集合(逻辑)操作,来实现对表格数据(索引)的筛选。
1 | df_set_1 = pd.DataFrame([[0,1],[1,2],[3,4]], index = pd.Index(['a','b','a'], name='id1')) |