第二章 索引
import numpy as np
import pandas as pd
一、索引器
1. 表的列索引
列索引是最常见的索引形式,一般通过[]来实现。通过[列名]可以从DataFrame中取出相应的列,返回值为Series,例如从表中取出姓名一列:
# 读取数据,usecols 取所需列
df = pd.read_csv('data/learn_pandas.csv', usecols = ['School', 'Grade', 'Name', 'Gender', 'Weight', 'Transfer'])
df['Name'].head()
0 Gaopeng Yang
1 Changqiang You
2 Mei Sun
3 Xiaojuan Sun
4 Gaojuan You
Name: Name, dtype: object
如果列名不包含空格,可以直接.列名取,等价于[列名],仅针对于取出单列:
df.Name.head()
0 Gaopeng Yang
1 Changqiang You
2 Mei Sun
3 Xiaojuan Sun
4 Gaojuan You
Name: Name, dtype: object
取多列,就用[列名组成的列表],中间用逗号隔开,返回的是DataFrame,例如从表中取出性别和姓名两列:
df[['Gender', 'Name']].head()
Gender | Name | |
---|---|---|
0 | Female | Gaopeng Yang |
1 | Male | Changqiang You |
2 | Male | Mei Sun |
3 | Female | Xiaojuan Sun |
4 | Male | Gaojuan You |
2. 序列的行索引
【a】以字符串为索引的Series
如果取出单个索引的对应元素,则可以使用[item]
若Series只有单个值对应,则返回这个标量值
如果有多个值对应,则返回一个Series:
s = pd.Series([1, 2, 3, 4, 5, 6], index=['a', 'b', 'a', 'a', 'a', 'c'])
s['a']
a 1
a 3
a 4
a 5
dtype: int64
s['b']
2
如果取出多个索引的对应元素,则可以使用[items的列表]:
s[['c','b']]
c 6
b 2
dtype: int64
如果想要取出某两个索引之间的元素,并且这两个索引是在整个索引中唯一出现,则可以使用切片,同时需要注意这里的切片会包含两个端点:
s['b':'c':2] # 元素索引b→c,步长为2
b 2
a 4
c 6
dtype: int64
【b】以整数为索引的Series
数据读入,如果不特别指定所对应的列作为索引,会生成从0开始的整数索引作为默认索引。
和字符串一样,如果使用[int]或[int_list],则可以取出对应索引元素的值:
s = pd.Series(['a', 'b', 'c', 'd', 'e', 'f'], index=[1, 3, 1, 2, 5, 4])
s[1]
1 a
1 c
dtype: object
s[[2,3]]
2 d
3 b
dtype: object
如果使用整数切片,则会取出对应索引位置的值
这里的整数切片同Python中的切片一样是开区间,不包含右端点:
s[1:-1:1] # 位置索引1→末尾,步长为1,不包右端点
3 b
1 c
2 d
5 e
dtype: object
【WARNING】关于索引类型的说明
不要把纯浮点以及任何混合类型(字符串、整数、浮点类型等的混合)作为索引,操作时报错或者返回非预期的结果
3. loc索引器
备注:.loc 可以把对应的数据信息全都显示出来
对于表而言,有两种索引器:
①基于元素的loc索引器:loc[*, *]
,第一个*
代表行的选择,第二个*
代表列的选择,如果直接locloc[*]
,这个*
是指行的筛选
*
的位置一共有五类合法对象,分别是:单个元素、元素列表、元素切片、布尔列表以及函数
②基于位置的iloc索引器
为了演示相应操作,先利用set_index方法把Name列设为索引
df_demo = df.set_index('Name')
df_demo.head()
School | Grade | Gender | Weight | Transfer | |
---|---|---|---|---|---|
Name | |||||
Gaopeng Yang | Shanghai Jiao Tong University | Freshman | Female | 46.0 | N |
Changqiang You | Peking University | Freshman | Male | 70.0 | N |
Mei Sun | Shanghai Jiao Tong University | Senior | Male | 89.0 | N |
Xiaojuan Sun | Fudan University | Sophomore | Female | 41.0 | N |
Gaojuan You | Fudan University | Sophomore | Male | 74.0 | N |
【a】*
为单个元素
直接取出相应的行或列,如果该元素在索引中重复则结果为DataFrame,否则为Series:
df_demo.loc['Qiang Sun'] # 多个人叫此名字,重复则结果为DataFrame
School | Grade | Gender | Weight | Transfer | |
---|---|---|---|---|---|
Name | |||||
Qiang Sun | Tsinghua University | Junior | Female | 53.0 | N |
Qiang Sun | Tsinghua University | Sophomore | Female | 40.0 | N |
Qiang Sun | Shanghai Jiao Tong University | Junior | Female | NaN | N |
df_demo.loc['Quan Zhao'] # 名字唯一,为Series
School Shanghai Jiao Tong University
Grade Junior
Gender Female
Weight 53
Transfer N
Name: Quan Zhao, dtype: object
也可以同时选择行和列:
df_demo.loc['Qiang Sun','School']
# 姓名为索引,又重复,返回Series
Name
Qiang Sun Tsinghua University
Qiang Sun Tsinghua University
Qiang Sun Shanghai Jiao Tong University
Name: School, dtype: object
df_demo.loc['Quan Zhao','School']
# 返回单个元素
'Shanghai Jiao Tong University'
【b】*
为元素列表
取出列表中所有元素值对应的行或列:
df_demo.loc[['Qiang Sun','Quan Zhao'],['School','Gender']]
School | Gender | |
---|---|---|
Name | ||
Qiang Sun | Tsinghua University | Female |
Qiang Sun | Tsinghua University | Female |
Qiang Sun | Shanghai Jiao Tong University | Female |
Quan Zhao | Shanghai Jiao Tong University | Female |
【c】*
为切片
之前的Series使用字符串索引时提到,如果是唯一值的起点和终点字符,那么就可以使用切片,并且包含两个端点,如果不唯一则报错:
df_demo.loc['Gaojuan You':'Gaoqiang Qian','School':'Gender']
School | Grade | Gender | |
---|---|---|---|
Name | |||
Gaojuan You | Fudan University | Sophomore | Male |
Xiaoli Qian | Tsinghua University | Freshman | Female |
Qiang Chu | Shanghai Jiao Tong University | Freshman | Female |
Gaoqiang Qian | Tsinghua University | Junior | Female |
需要注意的是,如果DataFrame使用整数索引,其使用整数切片的时候和上面字符串索引的要求一致,都是元素切片,包含端点且起点、终点不允许有重复值
df_loc_slice_demo = df_demo.copy()
df_loc_slice_demo.index = range(df_demo.shape[0],0,-1)
# range(start, stop,[step]),df_demo的行数,到0,逐步减1
df_loc_slice_demo.loc[5:3]
School | Grade | Gender | Weight | Transfer | |
---|---|---|---|---|---|
5 | Fudan University | Junior | Female | 46.0 | N |
4 | Tsinghua University | Senior | Female | 50.0 | N |
3 | Shanghai Jiao Tong University | Senior | Female | 45.0 | N |
# 没有返回,说明不是整数位置切片
df_loc_slice_demo.loc[3:5]
School | Grade | Gender | Weight | Transfer |
---|
【d】*
为布尔列表
在实际的数据处理中,根据条件来筛选行是极其常见的,此处传入loc的布尔列表与DataFrame长度相同,且列表为True的位置所对应的行会被选中,False则会被剔除。
# 例如,选出体重超过70kg的学生:
df_demo.loc[df_demo.Weight>70].head()
School | Grade | Gender | Weight | Transfer | |
---|---|---|---|---|---|
Name | |||||
Mei Sun | Shanghai Jiao Tong University | Senior | Male | 89.0 | N |
Gaojuan You | Fudan University | Sophomore | Male | 74.0 | N |
Xiaopeng Zhou | Shanghai Jiao Tong University | Freshman | Male | 74.0 | N |
Xiaofeng Sun | Tsinghua University | Senior | Male | 71.0 | N |
Qiang Zheng | Shanghai Jiao Tong University | Senior | Male | 87.0 | N |
前面所提到的传入元素列表,也可以通过isin方法返回的布尔列表等价写出
# 例如,选出所有大一和大四的同学信息:
df_demo.loc[df_demo['Grade'].isin(['Freshman','Senior'])].head()
# 此处df_demo['Grade']在名字没有空格的情况下可以直接df_demo.Grade
School | Grade | Gender | Weight | Transfer | |
---|---|---|---|---|---|
Name | |||||
Gaopeng Yang | Shanghai Jiao Tong University | Freshman | Female | 46.0 | N |
Changqiang You | Peking University | Freshman | Male | 70.0 | N |
Mei Sun | Shanghai Jiao Tong University | Senior | Male | 89.0 | N |
Xiaoli Qian | Tsinghua University | Freshman | Female | 51.0 | N |
Qiang Chu | Shanghai Jiao Tong University | Freshman | Female | 52.0 | N |
对于复合条件而言,可以用|(或), &(且), ~(取反)的组合来实现
# 例如,选出复旦大学中体重超过70kg的大四学生,或者北大男生中体重超过80kg的非大四的学生:
condition1_1 = df_demo['School'] == 'Fudan University'
condition1_2 = df_demo['Grade'] =='Senior'
condition1_3 = df_demo['Weight'] >70
condition1 = condition1_1&condition1_2&condition1_3
condition2_1 = df_demo['School'] == 'Peking University'
condition2_2 = df_demo['Grade'] =='Senior'
condition2_3 = df_demo['Weight'] >80
condition2 = condition2_1&(~condition2_2)&condition2_3
df_demo.loc[condition1 | condition2]
# 打印出详细的信息,用.loc[]
School | Grade | Gender | Weight | Transfer | |
---|---|---|---|---|---|
Name | |||||
Qiang Han | Peking University | Freshman | Male | 87.0 | N |
Chengpeng Zhou | Fudan University | Senior | Male | 81.0 | N |
Changpeng Zhao | Peking University | Freshman | Male | 83.0 | N |
Chengpeng Qian | Fudan University | Senior | Male | 73.0 | Y |
【练一练】
select_dtypes是一个实用函数,它能够从表中选出相应类型的列,若要选出所有数值型的列,只需使用.select_dtypes(‘number’),请利用布尔列表选择的方法结合DataFrame的dtypes属性在learn_pandas数据集上实现这个功能。
# 首先先查看数据类型
df_demo.dtypes
School object
Grade object
Gender object
Weight float64
Transfer object
dtype: object
# 用select_dtype选择
df_demo.select_dtypes('float64').head()
Weight | |
---|---|
Name | |
Gaopeng Yang | 46.0 |
Changqiang You | 70.0 |
Mei Sun | 89.0 |
Xiaojuan Sun | 41.0 |
Gaojuan You | 74.0 |
# 用布尔类型筛选
df_demo.loc[df_demo.Weight.dtype =='float64']
# 这个会报错,但是直接df_demo.Weight.dtype =='float64'会返回true
.select_dtypesh知识点扩充
df_demo.select_dtypes(include=['float64']).head() # 筛选出loat64的列
Weight | |
---|---|
Name | |
Gaopeng Yang | 46.0 |
Changqiang You | 70.0 |
Mei Sun | 89.0 |
Xiaojuan Sun | 41.0 |
Gaojuan You | 74.0 |
df_demo.select_dtypes(exclude=['object']).head()
# 不包含object的列
Weight | |
---|---|
Name | |
Gaopeng Yang | 46.0 |
Changqiang You | 70.0 |
Mei Sun | 89.0 |
Xiaojuan Sun | 41.0 |
Gaojuan You | 74.0 |
df_demo.select_dtypes(include=['float64'], exclude=None).head()
# include包含需要获取的列类型,exclude包含不需要获取的数据类型
【e】*
为函数
这里的函数,必须以前面的四种合法形式之一为返回值,并且函数的输入值为DataFrame本身。假设仍然是上述复合条件筛选的例子,可以把逻辑写入一个函数中再返回,需要注意的是函数的形式参数x本质上即为df_demo:
def condition(x):
condition_1_1 = x.School == 'Fudan University'
condition_1_2 = x.Grade == 'Senior'
condition_1_3 = x.Weight > 70
condition_1 = condition_1_1 & condition_1_2 & condition_1_3
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
result = condition_1 | condition_2
return result
df_demo.loc[condition]
School | Grade | Gender | Weight | Transfer | |
---|---|---|---|---|---|
Name | |||||
Qiang Han | Peking University | Freshman | Male | 87.0 | N |
Chengpeng Zhou | Fudan University | Senior | Male | 81.0 | N |
Changpeng Zhao | Peking University | Freshman | Male | 83.0 | N |
Chengpeng Qian | Fudan University | Senior | Male | 73.0 | Y |
此外,还支持使用lambda表达式,其返回值也同样必须是先前提到的四种形式之一:
df_demo.loc[lambda x:'Quan Zhao',lambda x:'Gender']
'Female'
由于函数无法返回如start: end: step的切片形式,故返回切片时要用slice对象进行包装:
df_demo.loc[lambda x:slice('Gaojuan You','Gaoqiang Qian')]
School | Grade | Gender | Weight | Transfer | |
---|---|---|---|---|---|
Name | |||||
Gaojuan You | Fudan University | Sophomore | Male | 74.0 | N |
Xiaoli Qian | Tsinghua University | Freshman | Female | 51.0 | N |
Qiang Chu | Shanghai Jiao Tong University | Freshman | Female | 52.0 | N |
Gaoqiang Qian | Tsinghua University | Junior | Female | 50.0 | N |
Series也可以使用loc索引,其遵循的原则与DataFrame中用于行筛选的loc[*]完全一致.
【WARNING】不要使用链式赋值
在对表或者序列赋值时,应当在使用一层索引器后直接进行赋值操作,这样做是由于进行多次索引后赋值是赋在临时返回的copy副本上的,而没有真正修改元素从而报出SettingWithCopyWarning警告。例如,下面给出的例子:
df_chain = pd.DataFrame([[0,0],[1,0],[-1,0]],columns=list('AB'))
df_chain
A | B | |
---|---|---|
0 | 0 | 0 |
1 | 1 | 0 |
2 | -1 | 0 |
import warnings
with warnings.catch_warnings():
warnings.filterwarnings('error')
try:
df_chain[df_chain.A!=0].B = 1
# 使用方括号列索引后,再使用点的列索引
except Warning as w:
Warning_Msg = w
print(Warning_Msg)
df_chain
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
A | B | |
---|---|---|
0 | 0 | 0 |
1 | 1 | 0 |
2 | -1 | 0 |
df_chain.loc[df_chain.A!=0,'B'] = 1
df_chain
A | B | |
---|---|---|
0 | 0 | 0 |
1 | 1 | 1 |
2 | -1 | 1 |
4. iloc索引器
iloc的使用与loc完全类似,只不过是针对位置进行筛选
在相应的*
位置处一共也有五类合法对象,分别是:
整数、整数列表、整数切片、布尔列表以及函数
函数的返回值必须是前面的四类合法对象中的一个,其输入同样也为DataFrame本身。
# 第二行第二列
df_demo.iloc[1,1]
'Freshman'
# 前两行前两列
df_demo.iloc[[0,1],[0,1]]
School | Grade | |
---|---|---|
Name | ||
Gaopeng Yang | Shanghai Jiao Tong University | Freshman |
Changqiang You | Peking University | Freshman |
# 切片不包含结束端点-右端点
df_demo.iloc[1:4,2:4]
# 传入切片为返回值的函数,函数无法返回如start: end: step的切片形式,故返回切片时要用slice对象进行包装
df_demo.iloc[lambda x:slice(1,4)]
School | Grade | Gender | Weight | Transfer | |
---|---|---|---|---|---|
Name | |||||
Changqiang You | Peking University | Freshman | Male | 70.0 | N |
Mei Sun | Shanghai Jiao Tong University | Senior | Male | 89.0 | N |
Xiaojuan Sun | Fudan University | Sophomore | Female | 41.0 | N |
使用布尔列表,不能传入Series而必须传入序列的values,否则会报错。
因此,在使用布尔筛选的时候还是应当优先考虑loc的方式。
# 例如,选出体重超过80kg的学生:
df_demo.iloc[(df_demo['Weight']>80).values].head()
School | Grade | Gender | Weight | Transfer | |
---|---|---|---|---|---|
Name | |||||
Mei Sun | Shanghai Jiao Tong University | Senior | Male | 89.0 | N |
Qiang Zheng | Shanghai Jiao Tong University | Senior | Male | 87.0 | N |
Qiang Han | Peking University | Freshman | Male | 87.0 | N |
Chengpeng Zhou | Fudan University | Senior | Male | 81.0 | N |
Feng Han | Shanghai Jiao Tong University | Sophomore | Male | 82.0 | N |
对Series而言同样也可以通过iloc返回相应位置的值或子序列:
df_demo['School'].iloc[1]
'Peking University'
df_demo['School'].iloc[1:5:2]
# 从位置1开始到5,步长为2,不包含右端点
Name
Changqiang You Peking University
Xiaojuan Sun Fudan University
Name: School, dtype: object
5. query方法–减少代码行数
在pandas中,支持把字符串形式的查询表达式传入query方法来查询数据,其表达式的执行结果必须返回布尔列表。
在进行复杂索引时,由于这种检索方式无需像普通方法一样重复使用DataFrame的名字来引用列名,一般而言会使代码长度在不降低可读性的前提下有所减少。
# 例如,将loc一节中的复合条件查询例子可以如下改写:
df.query('((School == "Fudan University")&'
'(Grade == "Senior")&'
'(Weight > 70))|'
'((School == "Peking University")&'
'(Grade != "Senior")&'
'(Weight > 80))')
School | Grade | Name | Gender | Weight | Transfer | |
---|---|---|---|---|---|---|
38 | Peking University | Freshman | Qiang Han | Male | 87.0 | N |
66 | Fudan University | Senior | Chengpeng Zhou | Male | 81.0 | N |
99 | Peking University | Freshman | Changpeng Zhao | Male | 83.0 | N |
131 | Fudan University | Senior | Chengpeng Qian | Male | 73.0 | Y |
在query表达式中,帮用户注册了所有来自DataFrame的列名,所有属于该Series的方法都可以被调用,和正常的函数调用并没有区别
# 例如,查询体重超过均值的学生:
df.query('Weight>Weight.mean()').head()
School | Grade | Name | Gender | Weight | Transfer | |
---|---|---|---|---|---|---|
1 | Peking University | Freshman | Changqiang You | Male | 70.0 | N |
2 | Shanghai Jiao Tong University | Senior | Mei Sun | Male | 89.0 | N |
4 | Fudan University | Sophomore | Gaojuan You | Male | 74.0 | N |
10 | Shanghai Jiao Tong University | Freshman | Xiaopeng Zhou | Male | 74.0 | N |
14 | Tsinghua University | Senior | Xiaomei Zhou | Female | 57.0 | N |
【NOTE】query中引用带空格的列名
对于含有空格的列名,需要使用`col name`
的方式进行引用
同时,在query中还注册了若干英语的字面用法,帮助提高可读性。
例如:or, and, or, is in, not in。
# 例如,筛选出男生中不是大一大二的学生:
df.query('(Grade not in ["Freshman","Sophomore"]) and (Gender == "Male")').head()
School | Grade | Name | Gender | Weight | Transfer | |
---|---|---|---|---|---|---|
2 | Shanghai Jiao Tong University | Senior | Mei Sun | Male | 89.0 | N |
16 | Tsinghua University | Junior | Xiaoqiang Qin | Male | 68.0 | N |
17 | Tsinghua University | Junior | Peng Wang | Male | 65.0 | N |
18 | Tsinghua University | Senior | Xiaofeng Sun | Male | 71.0 | N |
21 | Shanghai Jiao Tong University | Senior | Xiaopeng Shen | Male | 62.0 | NaN |
此外,在字符串中出现与列表的比较时,==和!=分别表示元素出现在列表和没有出现在列表。
等价于is in和not in
# 例如,查询所有大三和大四的学生:
df.query('Grade ==["Junior","Senior"]').head()
School | Grade | Name | Gender | Weight | Transfer | |
---|---|---|---|---|---|---|
2 | Shanghai Jiao Tong University | Senior | Mei Sun | Male | 89.0 | N |
7 | Tsinghua University | Junior | Gaoqiang Qian | Female | 50.0 | N |
9 | Peking University | Junior | Juan Xu | Female | NaN | N |
11 | Tsinghua University | Junior | Xiaoquan Lv | Female | 43.0 | N |
12 | Shanghai Jiao Tong University | Senior | Peng You | Female | 48.0 | NaN |
对于query中的字符串,如果要引用外部变量,只需在变量名前加@符号。
# 例如,取出体重位于70kg到80kg之间的学生:
low,high= 70,80
df.query('(Weight>=@low)&''(Weight<=@high)').head()
# df.query('Weight.between(@low, @high)').head()
# 👇
# 报错:'Series' objects are mutable, thus they cannot be hashed
School | Grade | Name | Gender | Weight | Transfer | |
---|---|---|---|---|---|---|
1 | Peking University | Freshman | Changqiang You | Male | 70.0 | N |
4 | Fudan University | Sophomore | Gaojuan You | Male | 74.0 | N |
10 | Shanghai Jiao Tong University | Freshman | Xiaopeng Zhou | Male | 74.0 | N |
18 | Tsinghua University | Senior | Xiaofeng Sun | Male | 71.0 | N |
35 | Peking University | Freshman | Gaoli Zhao | Male | 78.0 | N |
# 例如,查询所有大三和大四的学生:
level = ["Junior","Senior"]
df.query('Grade == @level').head()
School | Grade | Name | Gender | Weight | Transfer | |
---|---|---|---|---|---|---|
2 | Shanghai Jiao Tong University | Senior | Mei Sun | Male | 89.0 | N |
7 | Tsinghua University | Junior | Gaoqiang Qian | Female | 50.0 | N |
9 | Peking University | Junior | Juan Xu | Female | NaN | N |
11 | Tsinghua University | Junior | Xiaoquan Lv | Female | 43.0 | N |
12 | Shanghai Jiao Tong University | Senior | Peng You | Female | 48.0 | NaN |
6. 随机抽样
如果把DataFrame的每一行看作一个样本,或把每一列看作一个特征,再把整个DataFrame看作总体,想要对样本或特征进行随机抽样就可以用sample函数。
有时在拿到大型数据集后,想要对统计特征进行计算来了解数据的大致分布,但是这很费时间。
同时,由于许多统计特征在等概率不放回的简单随机抽样条件下,是总体统计特征的无偏估计,比如样本均值和总体均值,那么就可以先从整张表中抽出一部分来做近似估计。
sample函数中的主要参数:
n→抽样数量
axis→抽样的方向(0为行、1为列)
frac→抽样比例(0.3则为从总体中抽出30%的样本)
replace→是否放回,当replace = True则表示有放回抽样
weights→每个样本的抽样相对概率
df_sample = pd.DataFrame({'id': list('abcde'), 'value': [1, 2, 3, 4, 90]})
df_sample
id | value | |
---|---|---|
0 | a | 1 |
1 | b | 2 |
2 | c | 3 |
3 | d | 4 |
4 | e | 90 |
# 例如,对df_sample以value值的相对大小为抽样概率进行有放回抽样,抽样数量为3。
df_sample.sample(3,replace=True,weights=df_sample.value)
id | value | |
---|---|---|
4 | e | 90 |
4 | e | 90 |
4 | e | 90 |
二、多级索引
1. 多级索引及其表的结构
为了更加清晰地说明具有多级索引的DataFrame结构,下面新构造一张表
np.random.seed(0)
multi_index = pd.MultiIndex.from_product([list('ABCD'), df.Gender.unique()], names=('School', 'Gender'))
multi_column = pd.MultiIndex.from_product([['Height', 'Weight'], df.Grade.unique()], names=('Indicator', 'Grade'))
df_multi = pd.DataFrame(np.c_[(np.random.randn(8,4)*5 + 163).tolist(), (np.random.randn(8,4)*5 + 65).tolist()],
index = multi_index, columns = multi_column).round(1)
df_multi
Indicator | Height | Weight | |||||||
---|---|---|---|---|---|---|---|---|---|
Grade | Freshman | Senior | Sophomore | Junior | Freshman | Senior | Sophomore | Junior | |
School | Gender | ||||||||
A | Female | 171.8 | 165.0 | 167.9 | 174.2 | 60.6 | 55.1 | 63.3 | 65.8 |
Male | 172.3 | 158.1 | 167.8 | 162.2 | 71.2 | 71.0 | 63.1 | 63.5 | |
B | Female | 162.5 | 165.1 | 163.7 | 170.3 | 59.8 | 57.9 | 56.5 | 74.8 |
Male | 166.8 | 163.6 | 165.2 | 164.7 | 62.5 | 62.8 | 58.7 | 68.9 | |
C | Female | 170.5 | 162.0 | 164.6 | 158.7 | 56.9 | 63.9 | 60.5 | 66.9 |
Male | 150.2 | 166.3 | 167.3 | 159.3 | 62.4 | 59.1 | 64.9 | 67.1 | |
D | Female | 174.3 | 155.7 | 163.2 | 162.1 | 65.3 | 66.5 | 61.8 | 63.2 |
Male | 170.7 | 170.3 | 163.8 | 164.9 | 61.6 | 63.2 | 60.9 | 56.4 |
下图通过颜色区分,标记了DataFrame的结构。与单层索引的表一样,具备元素值、行索引和列索引三个部分。其中,这里的行索引和列索引都是MultiIndex类型,只不过索引中的一个元素是元组而不是单层索引中的标量。例如,行索引的第四个元素为(“B”, “Male”),列索引的第二个元素为(“Height”, “Senior”),这里需要注意,外层连续出现相同的值时,第一次之后出现的会被隐藏显示,使结果的可读性增强。
与单层索引类似,MultiIndex也具有名字属性,图中的School和Gender分别对应了表的第一层和第二层行索引的名字,Indicator和Grade分别对应了第一层和第二层列索引的名字。
索引的名字和值属性分别可以通过names和values获得:
df_multi.index.names # 获取行索引名
FrozenList(['School', 'Gender'])
df_multi.index.values#获取行索引的值
array([('A', 'Female'), ('A', 'Male'), ('B', 'Female'), ('B', 'Male'),
('C', 'Female'), ('C', 'Male'), ('D', 'Female'), ('D', 'Male')],
dtype=object)
df_multi.columns.names#获取列索引名
FrozenList(['Indicator', 'Grade'])
df_multi.columns.values#获取列索引的值
array([('Height', 'Freshman'), ('Height', 'Senior'),
('Height', 'Sophomore'), ('Height', 'Junior'),
('Weight', 'Freshman'), ('Weight', 'Senior'),
('Weight', 'Sophomore'), ('Weight', 'Junior')], dtype=object)
如果想要得到某一层的索引,则需要通过get_level_values获得:
df_multi.index.get_level_values(0)
#第一层行索引的值
Index(['A', 'A', 'B', 'B', 'C', 'C', 'D', 'D'], dtype='object', name='School')
但对于索引而言,无论是单层还是多层,用户都无法通过index_obj[0] = item的方式来修改元素,也不能通过index_name[0] = new_name的方式来修改名字,关于如何修改这些属性的话题将在第三节被讨论。
2. 多级索引中的loc索引器
熟悉了结构后,现在回到原表,将学校和年级设为索引,此时的行为多级索引,列为单级索引,由于默认状态的列索引不含名字,因此对应于刚刚图中Indicator和Grade的索引名位置是空缺的。
df_multi = df.set_index(['School','Grade'])
df_multi.head()
Name | Gender | Weight | Transfer | ||
---|---|---|---|---|---|
School | Grade | ||||
Shanghai Jiao Tong University | Freshman | Gaopeng Yang | Female | 46.0 | N |
Peking University | Freshman | Changqiang You | Male | 70.0 | N |
Shanghai Jiao Tong University | Senior | Mei Sun | Male | 89.0 | N |
Fudan University | Sophomore | Xiaojuan Sun | Female | 41.0 | N |
Sophomore | Gaojuan You | Male | 74.0 | N |
由于多级索引中的单个元素以元组为单位,因此在之前介绍的loc和iloc方法完全可以照搬,只需把标量的位置替换成对应的元组,不过在索引前最好对MultiIndex进行排序以避免性能警告:
df_multi = df_multi.sort_index()
# 对MultiIndex进行排序以避免性能警告
df_multi.loc[('Fudan University', 'Junior')].head()
Name | Gender | Weight | Transfer | ||
---|---|---|---|---|---|
School | Grade | ||||
Fudan University | Junior | Yanli You | Female | 48.0 | N |
Junior | Chunqiang Chu | Male | 72.0 | N | |
Junior | Changfeng Lv | Male | 76.0 | N | |
Junior | Yanjuan Lv | Female | 49.0 | NaN | |
Junior | Gaoqiang Zhou | Female | 43.0 | N |
df_multi.loc[[('Fudan University', 'Senior'),('Shanghai Jiao Tong University', 'Freshman')]].head()
Name | Gender | Weight | Transfer | ||
---|---|---|---|---|---|
School | Grade | ||||
Fudan University | Senior | Chengpeng Zheng | Female | 38.0 | N |
Senior | Feng Zhou | Female | 47.0 | N | |
Senior | Gaomei Lv | Female | 34.0 | N | |
Senior | Chunli Lv | Female | 56.0 | N | |
Senior | Chengpeng Zhou | Male | 81.0 | N |
# 布尔列表也是可用的
df_multi.loc[df_multi.Weight>70].head()
Name | Gender | Weight | Transfer | ||
---|---|---|---|---|---|
School | Grade | ||||
Fudan University | Freshman | Feng Wang | Male | 74.0 | N |
Junior | Chunqiang Chu | Male | 72.0 | N | |
Junior | Changfeng Lv | Male | 76.0 | N | |
Senior | Chengpeng Zhou | Male | 81.0 | N | |
Senior | Chengpeng Qian | Male | 73.0 | Y |
# 函数
df_multi.loc[lambda x:('Fudan University','Junior')].head()
Name | Gender | Weight | Transfer | ||
---|---|---|---|---|---|
School | Grade | ||||
Fudan University | Junior | Yanli You | Female | 48.0 | N |
Junior | Chunqiang Chu | Male | 72.0 | N | |
Junior | Changfeng Lv | Male | 76.0 | N | |
Junior | Yanjuan Lv | Female | 49.0 | NaN | |
Junior | Gaoqiang Zhou | Female | 43.0 | N |
【练一练】
与单层索引类似,若存在重复元素,则不能使用切片,请去除重复索引后给出一个元素切片的例子。
# 去除重复索引
df_multi_slice_demo = df_multi[~df_multi.index.duplicated()]
df_multi_slice_demo
Name | Gender | Weight | Transfer | ||
---|---|---|---|---|---|
School | Grade | ||||
Fudan University | Freshman | Changqiang Yang | Female | 49.0 | N |
Junior | Yanli You | Female | 48.0 | N | |
Senior | Chengpeng Zheng | Female | 38.0 | N | |
Sophomore | Xiaojuan Sun | Female | 41.0 | N | |
Peking University | Freshman | Changqiang You | Male | 70.0 | N |
Junior | Juan Xu | Female | NaN | N | |
Senior | Changli Lv | Female | 41.0 | N | |
Sophomore | Changmei Xu | Female | 43.0 | N | |
Shanghai Jiao Tong University | Freshman | Gaopeng Yang | Female | 46.0 | N |
Junior | Feng Zheng | Female | 51.0 | N | |
Senior | Mei Sun | Male | 89.0 | N | |
Sophomore | Yanfeng Qian | Female | 48.0 | N | |
Tsinghua University | Freshman | Xiaoli Qian | Female | 51.0 | N |
Junior | Gaoqiang Qian | Female | 50.0 | N | |
Senior | Xiaomei Zhou | Female | 57.0 | N | |
Sophomore | Li Wang | Male | 79.0 | N |
# 唯一值的起点和终点字使用切片,含两个端点,不唯一则报错
df_multi_slice_demo.loc['Fudan University':'Shanghai Jiao Tong University']
Name | Gender | Weight | Transfer | ||
---|---|---|---|---|---|
School | Grade | ||||
Fudan University | Freshman | Changqiang Yang | Female | 49.0 | N |
Junior | Yanli You | Female | 48.0 | N | |
Senior | Chengpeng Zheng | Female | 38.0 | N | |
Sophomore | Xiaojuan Sun | Female | 41.0 | N | |
Peking University | Freshman | Changqiang You | Male | 70.0 | N |
Junior | Juan Xu | Female | NaN | N | |
Senior | Changli Lv | Female | 41.0 | N | |
Sophomore | Changmei Xu | Female | 43.0 | N | |
Shanghai Jiao Tong University | Freshman | Gaopeng Yang | Female | 46.0 | N |
Junior | Feng Zheng | Female | 51.0 | N | |
Senior | Mei Sun | Male | 89.0 | N | |
Sophomore | Yanfeng Qian | Female | 48.0 | N |
# 或者列也筛选一下
df_multi_slice_demo.loc['Fudan University':'Shanghai Jiao Tong University','Name':'Gender']
Name | Gender | ||
---|---|---|---|
School | Grade | ||
Fudan University | Freshman | Changqiang Yang | Female |
Junior | Yanli You | Female | |
Senior | Chengpeng Zheng | Female | |
Sophomore | Xiaojuan Sun | Female | |
Peking University | Freshman | Changqiang You | Male |
Junior | Juan Xu | Female | |
Senior | Changli Lv | Female | |
Sophomore | Changmei Xu | Female | |
Shanghai Jiao Tong University | Freshman | Gaopeng Yang | Female |
Junior | Feng Zheng | Female | |
Senior | Mei Sun | Male | |
Sophomore | Yanfeng Qian | Female |
此外,在多级索引中的元组有一种特殊的用法,可以对多层的元素进行交叉组合后索引,但同时需要指定loc的列,全选则用:表示。
其中,每一层需要选中的元素用列表存放,传入loc的形式为[(level_0_list, level_1_list), cols]。
# 例如,想要得到所有北大和复旦的大二大三学生,可以如下写出:
res=df_multi.loc[(['Peking University', 'Fudan University'],['Sophomore', 'Junior']),:]
res.head()
# 全选则用:表示,指定loc的列如下:
# res=df_multi.loc[(['Peking University', 'Fudan University'],['Sophomore', 'Junior']),('Name','Weight')]
Name | Gender | Weight | Transfer | ||
---|---|---|---|---|---|
School | Grade | ||||
Peking University | Sophomore | Changmei Xu | Female | 43.0 | N |
Sophomore | Xiaopeng Qin | Male | NaN | N | |
Sophomore | Mei Xu | Female | 39.0 | N | |
Sophomore | Xiaoli Zhou | Female | 55.0 | N | |
Sophomore | Peng Han | Female | 34.0 | NaN |
res.shape
(33, 4)
下面的语句和上面类似,前者是小括号包括中括号,后者是中括号包裹小括号,但仍然传入的是元素(这里为元组)的列表,它们的意义是不同的
# 选出北大的大三学生和复旦的大二学生:
res=df_multi.loc[[('Peking University', 'Junior'), ('Fudan University', 'Sophomore')]]
res.head()
Name | Gender | Weight | Transfer | ||
---|---|---|---|---|---|
School | Grade | ||||
Peking University | Junior | Juan Xu | Female | NaN | N |
Junior | Changjuan You | Female | 47.0 | N | |
Junior | Gaoli Xu | Female | 48.0 | N | |
Junior | Gaoquan Zhou | Male | 70.0 | N | |
Junior | Qiang You | Female | 56.0 | N |
res.shape
(16, 4)
3. IndexSlice对象
前面介绍的方法,即使在索引不重复的时候,也只能对元组整体进行切片,而不能对每层进行切片,也不允许将切片和布尔列表混合使用,引入IndexSlice
对象就能解决这个问题。
Slice对象一共有两种形式:
第一种为loc[idx[*,*]]
型
第二种为loc[idx[*,*],idx[*,*]]
型,下面将进行介绍。
为了方便演示,下面构造一个索引不重复的`的DataFrame:
np.random.seed(0)
L1,L2 = ['A','B','C'],['a','b','c']
mul_index1 = pd.MultiIndex.from_product([L1,L2],names=('Upper', 'Lower'))
L3,L4 = ['D','E','F'],['d','e','f']
mul_index2 = pd.MultiIndex.from_product([L3,L4],names=('Big', 'Small'))
df_ex = pd.DataFrame(np.random.randint(-9,10,(9,9)), index=mul_index1, columns=mul_index2)
df_ex
Big | D | E | F | |||||||
---|---|---|---|---|---|---|---|---|---|---|
Small | d | e | f | d | e | f | d | e | f | |
Upper | Lower | |||||||||
A | a | 3 | 6 | -9 | -6 | -6 | -2 | 0 | 9 | -5 |
b | -3 | 3 | -8 | -3 | -2 | 5 | 8 | -4 | 4 | |
c | -1 | 0 | 7 | -4 | 6 | 6 | -9 | 9 | -6 | |
B | a | 8 | 5 | -2 | -9 | -8 | 0 | -9 | 1 | -6 |
b | 2 | 9 | -7 | -9 | -9 | -5 | -4 | -3 | -1 | |
c | 8 | 6 | -5 | 0 | 1 | -8 | -8 | -2 | 0 | |
C | a | -6 | -3 | 2 | 5 | 9 | -9 | 5 | -6 | 3 |
b | 1 | 2 | -5 | -3 | -5 | 6 | -6 | 3 | -5 | |
c | -1 | 5 | 6 | -6 | 6 | 4 | 7 | 8 | -4 |
为了使用silce对象,先要进行定义:
idx = pd.IndexSlice
【a】loc[idx[*,*]]
型
这种情况并不能进行多层分别切片,前一个*
表示行的选择,后一个*
表示列的选择,与单纯的loc
是类似的:
# 选取行索引为C,类索引为D中的f及其后面所有
df_ex.loc[idx['C':,('D','f'):]]
Big | D | E | F | |||||
---|---|---|---|---|---|---|---|---|
Small | f | d | e | f | d | e | f | |
Upper | Lower | |||||||
C | a | 2 | 5 | 9 | -9 | 5 | -6 | 3 |
b | -5 | -3 | -5 | 6 | -6 | 3 | -5 | |
c | 6 | -6 | 6 | 4 | 7 | 8 | -4 |
另外,也支持布尔序列的索引:
# 列和大于0的数,再取对应的行索引到A
df_ex.loc[idx[:'A',lambda x:x.sum()>0]]
Big | D | F | ||
---|---|---|---|---|
Small | d | e | e | |
Upper | Lower | |||
A | a | 3 | 6 | 9 |
b | -3 | 3 | -4 | |
c | -1 | 0 | 9 |
【b】loc[idx[*,*],idx[*,*]]
型
这种情况能够分层进行切片,前一个idx
指代的是行索引,后一个是列索引。
df_ex.loc[idx[:'A','b':],idx['E':,'e':]]
Big | E | F | |||
---|---|---|---|---|---|
Small | e | f | e | f | |
Upper | Lower | ||||
A | b | -2 | 5 | -4 | 4 |
c | 6 | 6 | 9 | -6 |
4. 多级索引的构造
前面提到了多级索引表的结构和切片,那么除了使用set_index
之外,如何自己构造多级索引呢?
常用的三种方法:
from_tuples, from_arrays, from_product
它们都是pd.MultiIndex
对象下的函数。
my_tuple = [('a','cat'),('a','dog'),('b','cat'),('b','dog')]
my_tuple
[('a', 'cat'), ('a', 'dog'), ('b', 'cat'), ('b', 'dog')]
from_tuples
指根据传入由元组组成的列表进行构造:
pd.MultiIndex.from_tuples(my_tuple, names=['First','Second'])
MultiIndex([('a', 'cat'),
('a', 'dog'),
('b', 'cat'),
('b', 'dog')],
names=['First', 'Second'])
from_arrays指根据传入列表中,对应层的列表进行构造:
my_array = [list('aabb'), ['cat', 'dog']*2]
pd.MultiIndex.from_arrays(my_array, names=['First','Second'])
MultiIndex([('a', 'cat'),
('a', 'dog'),
('b', 'cat'),
('b', 'dog')],
names=['First', 'Second'])
from_product指根据给定多个列表的笛卡尔积进行构造:
my_list1 = ['a','b']
my_list2 = ['cat','dog']
pd.MultiIndex.from_product([my_list1, my_list2], names=['First','Second'])
MultiIndex([('a', 'cat'),
('a', 'dog'),
('b', 'cat'),
('b', 'dog')],
names=['First', 'Second'])
三、索引的常用方法
1. 索引层的交换和删除
为了方便理解交换的过程,这里构造一个三级索引的例子:
np.random.seed(0)
L1,L2,L3 = ['A','B'],['a','b'],['alpha','beta']
mul_index1 = pd.MultiIndex.from_product([L1,L2,L3], names=('Upper', 'Lower','Extra'))
L4,L5,L6 = ['C','D'],['c','d'],['cat','dog']
mul_index2 = pd.MultiIndex.from_product([L4,L5,L6], names=('Big', 'Small', 'Other'))
df_ex = pd.DataFrame(np.random.randint(-9,10,(8,8)), index=mul_index1, columns=mul_index2)
df_ex
Big | C | D | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
Small | c | d | c | d | ||||||
Other | cat | dog | cat | dog | cat | dog | cat | dog | ||
Upper | Lower | Extra | ||||||||
A | a | alpha | 3 | 6 | -9 | -6 | -6 | -2 | 0 | 9 |
beta | -5 | -3 | 3 | -8 | -3 | -2 | 5 | 8 | ||
b | alpha | -4 | 4 | -1 | 0 | 7 | -4 | 6 | 6 | |
beta | -9 | 9 | -6 | 8 | 5 | -2 | -9 | -8 | ||
B | a | alpha | 0 | -9 | 1 | -6 | 2 | 9 | -7 | -9 |
beta | -9 | -5 | -4 | -3 | -1 | 8 | 6 | -5 | ||
b | alpha | 0 | 1 | -8 | -8 | -2 | 0 | -6 | -3 | |
beta | 2 | 5 | 9 | -9 | 5 | -6 | 3 | 1 |
索引层的交换由swaplevel和reorder_levels完成
swaplevel→前者只能交换两个层
reorder_levels→可以交换任意层
两者都可以指定交换的是轴是哪一个,即行索引或列索引:
# 列索引的第一层和第三层交换
df_ex.swaplevel(0,2,axis=1).head()
Other | cat | dog | cat | dog | cat | dog | cat | dog | ||
---|---|---|---|---|---|---|---|---|---|---|
Small | c | c | d | d | c | c | d | d | ||
Big | C | C | C | C | D | D | D | D | ||
Upper | Lower | Extra | ||||||||
A | a | alpha | 3 | 6 | -9 | -6 | -6 | -2 | 0 | 9 |
beta | -5 | -3 | 3 | -8 | -3 | -2 | 5 | 8 | ||
b | alpha | -4 | 4 | -1 | 0 | 7 | -4 | 6 | 6 | |
beta | -9 | 9 | -6 | 8 | 5 | -2 | -9 | -8 | ||
B | a | alpha | 0 | -9 | 1 | -6 | 2 | 9 | -7 | -9 |
# 列表数字指代原来索引中的层
# 原来第三层索引现在到第一层,第一层索引到现在第二层位置,第二层索引到现在第三层位置
df_ex.reorder_levels([2,0,1],axis=0).head()
Big | C | D | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
Small | c | d | c | d | ||||||
Other | cat | dog | cat | dog | cat | dog | cat | dog | ||
Extra | Upper | Lower | ||||||||
alpha | A | a | 3 | 6 | -9 | -6 | -6 | -2 | 0 | 9 |
beta | A | a | -5 | -3 | 3 | -8 | -3 | -2 | 5 | 8 |
alpha | A | b | -4 | 4 | -1 | 0 | 7 | -4 | 6 | 6 |
beta | A | b | -9 | 9 | -6 | 8 | 5 | -2 | -9 | -8 |
alpha | B | a | 0 | -9 | 1 | -6 | 2 | 9 | -7 | -9 |
【NOTE】轴之间的索引交换
这里只涉及行或列索引内部的交换,不同方向索引之间的交换将在第五章中被讨论。
若想要删除某一层的索引,可以使用droplevel
方法:
# 删除列索引第二层 small
df_ex.droplevel(1,axis=1)
Big | C | D | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
Other | cat | dog | cat | dog | cat | dog | cat | dog | ||
Upper | Lower | Extra | ||||||||
A | a | alpha | 3 | 6 | -9 | -6 | -6 | -2 | 0 | 9 |
beta | -5 | -3 | 3 | -8 | -3 | -2 | 5 | 8 | ||
b | alpha | -4 | 4 | -1 | 0 | 7 | -4 | 6 | 6 | |
beta | -9 | 9 | -6 | 8 | 5 | -2 | -9 | -8 | ||
B | a | alpha | 0 | -9 | 1 | -6 | 2 | 9 | -7 | -9 |
beta | -9 | -5 | -4 | -3 | -1 | 8 | 6 | -5 | ||
b | alpha | 0 | 1 | -8 | -8 | -2 | 0 | -6 | -3 | |
beta | 2 | 5 | 9 | -9 | 5 | -6 | 3 | 1 |
# 删除行索引第一、第二层
df_ex.droplevel([0,1],axis=0)
Big | C | D | ||||||
---|---|---|---|---|---|---|---|---|
Small | c | d | c | d | ||||
Other | cat | dog | cat | dog | cat | dog | cat | dog |
Extra | ||||||||
alpha | 3 | 6 | -9 | -6 | -6 | -2 | 0 | 9 |
beta | -5 | -3 | 3 | -8 | -3 | -2 | 5 | 8 |
alpha | -4 | 4 | -1 | 0 | 7 | -4 | 6 | 6 |
beta | -9 | 9 | -6 | 8 | 5 | -2 | -9 | -8 |
alpha | 0 | -9 | 1 | -6 | 2 | 9 | -7 | -9 |
beta | -9 | -5 | -4 | -3 | -1 | 8 | 6 | -5 |
alpha | 0 | 1 | -8 | -8 | -2 | 0 | -6 | -3 |
beta | 2 | 5 | 9 | -9 | 5 | -6 | 3 | 1 |
2. 索引属性的修改
通过rename_axis
可以对索引层的名字进行修改,常用的修改方式是传入字典的映射:
df_ex.rename_axis(index={'Upper':'Changed_row'},columns={'Other':'Changed_col'}).head()
Big | C | D | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
Small | c | d | c | d | ||||||
Changed_col | cat | dog | cat | dog | cat | dog | cat | dog | ||
Changed_row | Lower | Extra | ||||||||
A | a | alpha | 3 | 6 | -9 | -6 | -6 | -2 | 0 | 9 |
beta | -5 | -3 | 3 | -8 | -3 | -2 | 5 | 8 | ||
b | alpha | -4 | 4 | -1 | 0 | 7 | -4 | 6 | 6 | |
beta | -9 | 9 | -6 | 8 | 5 | -2 | -9 | -8 | ||
B | a | alpha | 0 | -9 | 1 | -6 | 2 | 9 | -7 | -9 |
通过rename可以对索引的值进行修改,如果是多级索引需要指定修改的层号level:
# 将列上的第三层(Other)的cat列名更改为not_cat
df_ex.rename(columns={'cat':'not_cat'},level=2).head()
Big | C | D | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
Small | c | d | c | d | ||||||
Other | not_cat | dog | not_cat | dog | not_cat | dog | not_cat | dog | ||
Upper | Lower | Extra | ||||||||
A | a | alpha | 3 | 6 | -9 | -6 | -6 | -2 | 0 | 9 |
beta | -5 | -3 | 3 | -8 | -3 | -2 | 5 | 8 | ||
b | alpha | -4 | 4 | -1 | 0 | 7 | -4 | 6 | 6 | |
beta | -9 | 9 | -6 | 8 | 5 | -2 | -9 | -8 | ||
B | a | alpha | 0 | -9 | 1 | -6 | 2 | 9 | -7 | -9 |
传入参数也可以是函数,其输入值就是索引元素
# 将行上的第三层(Extra )的名字都改成大写
df_ex.rename(index=lambda x:str.upper(x),level=2).head()
Big | C | D | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
Small | c | d | c | d | ||||||
Other | cat | dog | cat | dog | cat | dog | cat | dog | ||
Upper | Lower | Extra | ||||||||
A | a | ALPHA | 3 | 6 | -9 | -6 | -6 | -2 | 0 | 9 |
BETA | -5 | -3 | 3 | -8 | -3 | -2 | 5 | 8 | ||
b | ALPHA | -4 | 4 | -1 | 0 | 7 | -4 | 6 | 6 | |
BETA | -9 | 9 | -6 | 8 | 5 | -2 | -9 | -8 | ||
B | a | ALPHA | 0 | -9 | 1 | -6 | 2 | 9 | -7 | -9 |
【练一练】
尝试在rename_axis
中使用函数完成与例子中一样的功能。
df_ex.rename_axis(index=lambda x:str.upper(x),columns=lambda x:str.upper(x))
BIG | C | D | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
SMALL | c | d | c | d | ||||||
OTHER | cat | dog | cat | dog | cat | dog | cat | dog | ||
UPPER | LOWER | EXTRA | ||||||||
A | a | alpha | 3 | 6 | -9 | -6 | -6 | -2 | 0 | 9 |
beta | -5 | -3 | 3 | -8 | -3 | -2 | 5 | 8 | ||
b | alpha | -4 | 4 | -1 | 0 | 7 | -4 | 6 | 6 | |
beta | -9 | 9 | -6 | 8 | 5 | -2 | -9 | -8 | ||
B | a | alpha | 0 | -9 | 1 | -6 | 2 | 9 | -7 | -9 |
beta | -9 | -5 | -4 | -3 | -1 | 8 | 6 | -5 | ||
b | alpha | 0 | 1 | -8 | -8 | -2 | 0 | -6 | -3 | |
beta | 2 | 5 | 9 | -9 | 5 | -6 | 3 | 1 |
对于整个索引的元素替换,可以利用迭代器实现
new_values = iter(list('abcdefgh'))
df_ex.rename(index=lambda x:next(new_values),level=2)
Big | C | D | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
Small | c | d | c | d | ||||||
Other | cat | dog | cat | dog | cat | dog | cat | dog | ||
Upper | Lower | Extra | ||||||||
A | a | a | 3 | 6 | -9 | -6 | -6 | -2 | 0 | 9 |
b | -5 | -3 | 3 | -8 | -3 | -2 | 5 | 8 | ||
b | c | -4 | 4 | -1 | 0 | 7 | -4 | 6 | 6 | |
d | -9 | 9 | -6 | 8 | 5 | -2 | -9 | -8 | ||
B | a | e | 0 | -9 | 1 | -6 | 2 | 9 | -7 | -9 |
f | -9 | -5 | -4 | -3 | -1 | 8 | 6 | -5 | ||
b | g | 0 | 1 | -8 | -8 | -2 | 0 | -6 | -3 | |
h | 2 | 5 | 9 | -9 | 5 | -6 | 3 | 1 |
若想要对某个位置的元素进行修改,在单层索引时容易实现,即先取出索引的values属性,再给对得到的列表进行修改,最后再对index对象重新赋值。
但是如果是多级索引的话就有些麻烦,一个解决的方案是先把某一层索引临时转为表的元素,然后再进行修改,最后重新设定为索引,下面一节将介绍这些操作。
另外一个需要介绍的函数是map,它是定义在Index上的方法,与前面rename方法中层的函数式用法是类似的,只不过它传入的不是层的标量值,而是直接传入索引的元组,这为用户进行跨层的修改提供了遍历。
例如,可以等价地写出上面的字符串转大写的操作:
# df_ex.rename_axis(index=lambda x:str.upper(x),columns=lambda x:str.upper(x))
df_temp = df_ex.copy()
new_index =df_temp.index.map(lambda x:(x[0],x[1],str.upper(x[2])))
df_temp.index = new_index
df_temp.head()
Big | C | D | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
Small | c | d | c | d | ||||||
Other | cat | dog | cat | dog | cat | dog | cat | dog | ||
Upper | Lower | Extra | ||||||||
A | a | ALPHA | 3 | 6 | -9 | -6 | -6 | -2 | 0 | 9 |
BETA | -5 | -3 | 3 | -8 | -3 | -2 | 5 | 8 | ||
b | ALPHA | -4 | 4 | -1 | 0 | 7 | -4 | 6 | 6 | |
BETA | -9 | 9 | -6 | 8 | 5 | -2 | -9 | -8 | ||
B | a | ALPHA | 0 | -9 | 1 | -6 | 2 | 9 | -7 | -9 |
关于map的另一个使用方法是对多级索引的压缩,后续操作也会需要
df_temp = df_ex.copy()
new_index = df_temp.index.map(lambda x:(x[0]+'-'+x[1]+'-'+x[2]))
# 将三个层的索引用“-”连接起来
df_temp.index=new_index
df_temp.head()# 单层索引
Big | C | D | ||||||
---|---|---|---|---|---|---|---|---|
Small | c | d | c | d | ||||
Other | cat | dog | cat | dog | cat | dog | cat | dog |
A-a-alpha | 3 | 6 | -9 | -6 | -6 | -2 | 0 | 9 |
A-a-beta | -5 | -3 | 3 | -8 | -3 | -2 | 5 | 8 |
A-b-alpha | -4 | 4 | -1 | 0 | 7 | -4 | 6 | 6 |
A-b-beta | -9 | 9 | -6 | 8 | 5 | -2 | -9 | -8 |
B-a-alpha | 0 | -9 | 1 | -6 | 2 | 9 | -7 | -9 |
同时,也可以反向地展开:
new_idx = df_temp.index.map(lambda x:tuple(x.split('-')))
# 用split将其元组分割
df_temp.index=new_idx
df_temp.head()
# 三层索引
Big | C | D | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
Small | c | d | c | d | ||||||
Other | cat | dog | cat | dog | cat | dog | cat | dog | ||
A | a | alpha | 3 | 6 | -9 | -6 | -6 | -2 | 0 | 9 |
beta | -5 | -3 | 3 | -8 | -3 | -2 | 5 | 8 | ||
b | alpha | -4 | 4 | -1 | 0 | 7 | -4 | 6 | 6 | |
beta | -9 | 9 | -6 | 8 | 5 | -2 | -9 | -8 | ||
B | a | alpha | 0 | -9 | 1 | -6 | 2 | 9 | -7 | -9 |
3. 索引的设置与重置
为了说明本节的函数,下面构造一个新表:
df_new = pd.DataFrame({'A':list('aacd'), 'B':list('PQRT'), 'C':[1,2,3,4]})
df_new
A | B | C | |
---|---|---|---|
0 | a | P | 1 |
1 | a | Q | 2 |
2 | c | R | 3 |
3 | d | T | 4 |
索引的设置可以使用set_index完成,这里的主要参数是append,表示是否来保留原来的索引,直接把新设定的添加到原索引的内层:
df_new.set_index('A')
B | C | |
---|---|---|
A | ||
a | P | 1 |
a | Q | 2 |
c | R | 3 |
d | T | 4 |
df_new.set_index('A',append=True) # 保留原来的索引
B | C | ||
---|---|---|---|
A | |||
0 | a | P | 1 |
1 | a | Q | 2 |
2 | c | R | 3 |
3 | d | T | 4 |
可以同时指定多个列作为索引:
df_new.set_index(['A','B'])
C | ||
---|---|---|
A | B | |
a | P | 1 |
Q | 2 | |
c | R | 3 |
d | T | 4 |
如果想要添加索引的列没有出现其中,可直接在参数中传入相应的Series:
my_index = pd.Series(list('WXYZ'),name='D')
df_new = df_new.set_index(['A',my_index])
df_new
# 相当于新增了一列索引,可根据实际情况进行调整
B | C | ||
---|---|---|---|
A | D | ||
a | W | P | 1 |
X | Q | 2 | |
c | Y | R | 3 |
d | Z | T | 4 |
reset_index是set_index的逆函数,其主要参数是drop,表示是否要把去掉的索引层丢弃,而不是添加到列中
df_new.reset_index(['D'])
# 默认是不丢弃,添加到列中
D | B | C | |
---|---|---|---|
A | |||
a | W | P | 1 |
a | X | Q | 2 |
c | Y | R | 3 |
d | Z | T | 4 |
df_new.reset_index(['D'], drop=True)
# 丢弃D
B | C | |
---|---|---|
A | ||
a | P | 1 |
a | Q | 2 |
c | R | 3 |
d | T | 4 |
如果重置了所有的索引,那么pandas会直接重新生成一个默认索引:
df_new.reset_index()
# 重置索引
A | D | B | C | |
---|---|---|---|---|
0 | a | W | P | 1 |
1 | a | X | Q | 2 |
2 | c | Y | R | 3 |
3 | d | Z | T | 4 |
4. 索引的变形
在某些场合下,需要对索引做一些扩充或者剔除,更具体地要求是给定一个新的索引,把原表中相应的索引对应元素填充到新索引构成的表中。
# 例如,下面的表中给出了员工信息,需要重新制作一张新的表
# 要求增加一名员工的同时去掉身高列并增加性别列
df_reindex = pd.DataFrame({"Weight":[60,70,80], "Height":[176,180,179]}, index=['1001','1003','1002'])
df_reindex
Weight | Height | |
---|---|---|
1001 | 60 | 176 |
1003 | 70 | 180 |
1002 | 80 | 179 |
df_reindex.reindex(index=['1001','1003','1002','1004'],columns=['Weight','Gender'])
Weight | Gender | |
---|---|---|
1001 | 60.0 | NaN |
1003 | 70.0 | NaN |
1002 | 80.0 | NaN |
1004 | NaN | NaN |
这种需求常出现在时间序列索引的时间点填充以及ID编号的扩充。
另外,需要注意的是原来表中的数据和新表中会根据索引自动对齐,例如原先的1002号位置在1003号之后,而新表中相反,那么reindex中会根据元素对齐,与位置无关。
还有一个与reindex功能类似的函数是reindex_like,其功能是仿照传入的表的索引来进行被调用表索引的变形。
# 例如,现在以及存在一张表具备了目标索引的条件,那么上述功能可以如下等价地写出
df_existed = pd.DataFrame(index=['1001','1002','1003','1004'],columns=['Weight','Gender'])
df_reindex.reindex_like(df_existed)
Weight | Gender | |
---|---|---|
1001 | 60.0 | NaN |
1002 | 80.0 | NaN |
1003 | 70.0 | NaN |
1004 | NaN | NaN |
四、索引运算
1. 集合的运算法则
经常会有一种利用集合运算来取出符合条件行的需求,例如有两张表A和B,它们的索引都是员工编号,现在需要筛选出两表索引交集的所有员工信息,此时通过Index上的运算操作就很容易实现。
S
A
.
i
n
t
e
r
s
e
c
t
i
o
n
(
S
B
)
=
S
A
∩
S
B
⇔
{
x
∣
x
∈
S
A
a
n
d
x
∈
S
B
}
\rm S_A.intersection(S_B) = \rm S_A \cap S_B \Leftrightarrow \rm \{x|x\in S_A\, and\, x\in S_B\}
SA.intersection(SB)=SA∩SB⇔{x∣x∈SAandx∈SB}
S
A
.
u
n
i
o
n
(
S
B
)
=
S
A
∪
S
B
⇔
{
x
∣
x
∈
S
A
o
r
x
∈
S
B
}
\rm S_A.union(S_B) = \rm S_A \cup S_B \Leftrightarrow \rm \{x|x\in S_A\, or\, x\in S_B\}
SA.union(SB)=SA∪SB⇔{x∣x∈SAorx∈SB}
S
A
.
d
i
f
f
e
r
e
n
c
e
(
S
B
)
=
S
A
−
S
B
⇔
{
x
∣
x
∈
S
A
a
n
d
x
∉
S
B
}
\rm S_A.difference(S_B) = \rm S_A - S_B \Leftrightarrow \rm \{x|x\in S_A\, and\, x\notin S_B\}
SA.difference(SB)=SA−SB⇔{x∣x∈SAandx∈/SB}
S
A
.
s
y
m
m
e
t
r
i
c
_
d
i
f
f
e
r
e
n
c
e
(
S
B
)
=
S
A
△
S
B
⇔
{
x
∣
x
∈
S
A
∪
S
B
−
S
A
∩
S
B
}
\rm S_A.symmetric\_difference(S_B) = \rm S_A\triangle S_B\Leftrightarrow \rm \{x|x\in S_A\cup S_B - S_A\cap S_B\}
SA.symmetric_difference(SB)=SA△SB⇔{x∣x∈SA∪SB−SA∩SB}
2. 一般的索引运算
由于集合的元素是互异的,但是索引中可能有相同的元素,先用unique去重后再进行运算。
# 构造两张最为简单的示例表进行演示:
df_set_1 = pd.DataFrame([[0,1],[1,2],[3,4]], index = pd.Index(['a','b','a'],name='id1'))
df_set_2 = pd.DataFrame([[4,5],[2,6],[7,1]],index = pd.Index(['b','b','c'],name='id2'))
id1,id2 = df_set_1.index.unique(), df_set_2.index.unique()
id1.intersection(id2)# 两表的交集
# 等价于
# id1 & id2
Index(['b'], dtype='object')
id1.union(id2)# 并集
# 等价于
# id1 | id2
Index(['a', 'b', 'c'], dtype='object')
id1.difference(id2)#差集
# 等价于
# (id1 ^ id2) & id1
Index(['a'], dtype='object')
# 对等差分:在A或B中的元素的集合,但不在它们的交点上
id1.symmetric_difference(id2)
# 等价于
# id1 ^ id2 # ^符号即对称差
Index(['a', 'c'], dtype='object')
# 对等差分示例:symmetric_difference()
A = {'a', 'b', 'c', 'd'}
B = {'c', 'd', 'e' }
C = {}
print(A.symmetric_difference(B))
print(B.symmetric_difference(A))
print(A.symmetric_difference(C))
print(B.symmetric_difference(C))
{'e', 'a', 'b'}
{'e', 'b', 'a'}
{'b', 'd', 'c', 'a'}
{'d', 'e', 'c'}
若两张表需要做集合运算的列并没有被设置索引:
①:先转成索引,运算后再恢复
②:利用isin函数
# 例如,在重置索引的第一张表中选出id列交集的所在行:
df_set_in_col_1 = df_set_1.reset_index()
df_set_in_col_2 = df_set_2.reset_index()
df_set_in_col_1
id1 | 0 | 1 | |
---|---|---|---|
0 | a | 0 | 1 |
1 | b | 1 | 2 |
2 | a | 3 | 4 |
df_set_in_col_2
id2 | 0 | 1 | |
---|---|---|---|
0 | b | 4 | 5 |
1 | b | 2 | 6 |
2 | c | 7 | 1 |
df_set_in_col_1[df_set_in_col_1.id1.isin(df_set_in_col_2.id2)]
id1 | 0 | 1 | |
---|---|---|---|
1 | b | 1 | 2 |