task3 索引
索引器
简单的直接索引
例子
loc索引器
是基于元素的索引器,loc
索引器的一般形式是loc[*, *]
,其中第一个*
代表行的选择,第二个*
代表列的选择,如果省略第二个位置写作loc[*]
,这个*
是指行的筛选。
其中,*
的位置一共有五类合法对象,分别是:单个元素、元素列表、元素切片、布尔列表以及函数。
示例
df_demo = df.set_index('Name')
df_demo.head()
# 单个元素
df_demo.loc['Qiang Sun'] # 多个人叫此名字
df_demo.loc['Qiang Sun', 'School'] # 返回Series
df_demo.loc['Quan Zhao', 'School'] # 返回单个元素
# 元素列表
df_demo.loc[['Qiang Sun','Quan Zhao'], ['School','Gender']]
# 切片
df_demo.loc['Gaojuan You':'Gaoqiang Qian', 'School':'Gender']
df_loc_slice_demo = df_demo.copy()
df_loc_slice_demo.index = range(df_demo.shape[0],0,-1)
df_loc_slice_demo.loc[5:3]
df_loc_slice_demo.loc[3:5] # 没有返回,说明不是整数位置切片
# 布尔列表
df_demo.loc[df_demo.Weight>70].head()
df_demo.loc[df_demo.Grade.isin(['Freshman', 'Senior'])].head()
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
condition_2_1 = df_demo.School == 'Peking University'
condition_2_2 = df_demo.Grade == 'Senior'
condition_2_3 = df_demo.Weight > 80
condition_2 = condition_2_1 & (~condition_2_2) & condition_2_3
df_demo.loc[condition_1 | condition_2]
# 函数
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]
df_demo.loc[lambda x:'Quan Zhao', lambda x:'Gender']
⭐由于函数无法返回如start: end: step
的切片形式,故返回切片时要用slice
对象进行包装:
df_demo.loc[lambda x: slice('Gaojuan You', 'Gaoqiang Qian')]
练一练
select_dtypes
是一个实用函数,它能够从表中选出相应类型的列,若要选出所有数值型的列,只需使用.select_dtypes('number')
,请利用布尔列表选择的方法结合DataFrame
的dtypes
属性在learn_pandas
数据集上实现这个功能。
learn_pandas.loc[:,learn_pandas.dtypes.isin(["object", 'int64'])].head()
【warnings】不要使用链式赋值
在对表或者序列赋值时,应当在使用一层索引器后直接进行赋值操作,这样做是由于进行多次索引后赋值是赋在临时返回的copy
副本上的,而没有真正修改元素从而报出SettingWithCopyWarning
警告。
iloc索引器
iloc
的使用与loc
完全类似,只不过是针对位置进行筛选,在相应的*
位置处一共也有五类合法对象,分别是:整数、整数列表、整数切片、布尔列表以及函数,函数的返回值必须是前面的四类合法对象中的一个,其输入同样也为DataFrame
本身。
df_demo.iloc[1, 1] # 第二行第二列
df_demo.iloc[[0, 1], [0, 1]] # 前两行前两列
df_demo.iloc[1: 4, 2:4] # 切片不包含结束端点
df_demo.iloc[lambda x: slice(1, 4)] # 传入切片为返回值的函数
df_demo.iloc[(df_demo.Weight>80).values].head() #在使用布尔列表的时候要特别注意,不能传入Series而必须传入序列的values
query方法
在pandas
中,支持把字符串形式的查询表达式传入query
方法来查询数据,其表达式的执行结果必须返回布尔列表。在进行复杂索引时,由于这种检索方式无需像普通方法一样重复使用DataFrame
的名字来引用列名,一般而言会使代码长度在不降低可读性的前提下有所减少。

在query
表达式中,帮用户注册了所有来自DataFrame
的列名,所有属于该Series
的方法都可以被调用,和正常的函数调用并没有区别。⭐对于含有空格的列名,需要使用'col name'
的方式进行引用。
同时,在query
中还注册了若干英语的字面用法,帮助提高可读性,例如:or, and, or, in, not in
此外,在字符串中出现与列表的比较时,==
和!=
分别表示元素出现在列表和没有出现在列表,等价于is in
和not in
对于query
中的字符串,如果要引用外部变量,只需在变量名前加@
符号
# 注册了所有来自DataFrame的列名
df.query('((School == "Fudan University")&'
' (Grade == "Senior")&'
' (Weight > 70))|'
'((School == "Peking University")&'
' (Grade != "Senior")&'
' (Weight > 80))')
# 所有属于该`Series`的方法都可以被调用,和正常的函数调用并没有区别
df.query('Weight > Weight.mean()').head()
# `or, and, or, in, not in`
df.query('(Grade not in ["Freshman", "Sophomore"]) and (Gender == "Male")').head()
# 在字符串中出现与列表的比较时,`==`和`!=`分别表示元素出现在列表和没有出现在列表,等价于`is in`和`not in`
df.query('Grade == ["Junior", "Senior"]').head()
#对于query中的字符串,如果要引用外部变量,只需在变量名前加@符号
low, high =70, 80
df.query('@low <= Weight < @high').head()
随机抽样sample方法
df.sample(n, axis, frac, replace, weights)
n
:抽样数量、axis
::抽样的方向(0为行、1为列)frac
:抽样比例(0.3则为从总体中抽出30%的样本)replace
:True有放回,False无放回weights
:每个样本的抽样相对概率
df_sample = pd.DataFrame({
'id': list('abcde'), 'value': [1, 2, 3, 4, 90]})
df_sample.sample(3, replace = True, weights = df_sample.value)
多级索引
多级索引结构如上图所示。
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.index.names
# 列名层的名字
df_multi.columns.names
# 索引的值,多级索引返回以tuple为元素的list
df_multi.index.values
# 列名,同上返回tuple为元素的list
df_multi.columns.values
# 提取指定层索引的值
df_multi.index.get_level_values(0)
多级索引的loc方法
仍然是df.loc[行,列]
,但多级索引中的单个元素以元组为单位,将单层索引中的标量换成元组即可。
⭐建议:索引前对MultiIndex
进行排序以避免性能警告
df_multi = df_multi.sort_index()
df_multi.loc[('Fudan University', 'Junior')].head()
df_multi.loc[[('Fudan University', 'Senior'), ('Shanghai Jiao Tong University', 'Freshman')]].head()
df_multi.loc[df_multi.Weight > 70].head() # 布尔列表也是可用的
练一练
与单层索引类似,若存在重复元素,则不能使用切片,请去除重复索引后给出一个元素切片的例子。
df_new = df_multi.loc[~df_multi.index.duplicated()]
df_new.loc['Fudan University':'Peking University':1]
交叉索引
在多级索引中的元组有一种特殊的用法,可以对多层的元素进行交叉组合后索引,但同时需要指定loc
的列,全选则用:
表示。其中,每一层需要选中的元素用列表存放,传入loc
的形式为[(level_0_list, level_1_list), cols]
。
res = df_multi.loc[(['Peking University', 'Fudan University'], ['Sophomore', 'Junior']), :]
# 与元组的列表不同
res = df_multi.loc[[('Peking University', 'Junior'), ('Fudan University', 'Sophomore')]]
IndexSlice对象
前面介绍的方法,即使在索引不重复的时候,也只能对元组整体进行切片,而不能对每层进行切片,也不允许将切片和布尔列表混合使用,引入IndexSlice
对象就能解决这个问题。Slice
对象一共有两种形式,第一种为loc[idx[*,*]]
型,第二种为loc[idx[*,*],idx[*,*]]
型。
-
loc[idx[*,*]]
型这种情况并不能进行多层分别切片,前一个
*
表示行的选择,后一个*
表示列的选择,与单纯的loc
是类似的df_ex.loc[idx['C':, ('D', 'f'):]] df_ex.loc[idx[:'A', lambda x:x.sum()>0]] # 列和大于0
-
loc[idx[*,*],idx[*,*]]
型这种情况能够分层进行切片,前一个
idx
指代的是行索引,后一个是列索引。df_ex.loc[idx[:'A', 'b':], idx['E':, 'e':]]
多级索引的构造
除了使用set_index
之外,常用的有from_tuples, from_arrays, from_product
三种方法,它们都是pd.MultiIndex
对象下的函数。
from_tuples
指根据传入由元组组成的列表进行构造from_arrays
指根据传入列表中,对应层的列表进行构造from_product
指根据给定多个列表的笛卡尔积进行构造
my_tuple = [('a','cat'),('a','dog'),('b','cat'),('b','dog')]
pd.MultiIndex.from_tuples(my_tuple, names=['First','Second'])
my_array = [list('aabb'), ['cat', 'dog']*2]
pd.MultiIndex.from_arrays(my_array, names=['First','Second'])
my_list1 = ['a','b']
my_list2 = ['cat','dog']
pd.MultiIndex.from_product([my_list1, my_list2], names=['First','Second'])
索引的常用方法
索引层的交换
swaplevel
:只能交换两个层,可以指定交换的是轴是哪一个,即行索引或列索引reorder_levels
:可以交换任意层,可以指定交换的是轴是哪一个,即行索引或列索引
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
df_ex.swaplevel(0,2,axis=1).head() # 列索引的第一层和第三层交换
df_ex.reorder_levels([2,0,1],axis=0).head() # 列表数字指代原来索引中的层
索引层的删除
droplevel
方法
df_ex.droplevel(1,axis=1)
df_ex.droplevel([0,1],axis=0)
索引属性的修改
- 索引层名字的修改:
rename_axis
- 索引值的修改:
rename
,可指定修改的层level
df_ex.rename_axis(index={
'Upper':'Changed_row'}, columns={
'Other':'Changed_Col'}).head()
df_ex.rename(columns={
'cat':'not_cat'}, level=2).head()
df_ex.rename(index=lambda x:str.upper(x), level=2).head()
# 整个索引的元素替换,使用迭代器
new_values = iter(list('abcdefgh'))
df_ex.rename(index=lambda x:next(new_values), level=2)
- map方法来完成指定修改
# 指定修改
df_temp = df_ex.copy()
new_idx = df_temp.index.map(lambda x: (x[0], x[1], str.upper(x[2])))
df_temp.index = new_idx
df_temp.head()
# 压缩索引
df_temp = df_ex.copy()
new_idx = df_temp.index.map(lambda x: (x[0]+'-'+x[1]+'-'+x[2]))
df_temp.index = new_idx
df_temp.head() # 单层索引
索引的设置与重置
df.set_index([item], append)
, append表示是否来保留原来的索引,直接把新设定的添加到原索引的内层df.reset_index([item], drop)
, drop表示是否要把去掉的索引层丢弃,而不是添加到列中
df_new = pd.DataFrame({
'A':list('aacd'), 'B':list('PQRT'), 'C':[1,2,3,4]})
df_new.set_index('A')
df_new.set_index('A', append=True)
df_new.reset_index(['D'])
df_new.reset_index(['D'], drop=True)
索引的变形
df.reindex
df.reindex_like
df_reindex = pd.DataFrame({
"Weight":[60,70,80], "Height":[176,180,179]}, index=['1001','1003','1002'])
df_reindex.reindex(index=['1001','1002','1003','1004'], columns=['Weight','Gender'])
df_existed = pd.DataFrame(index=['1001','1002','1003','1004'], columns=['Weight','Gender'])
df_reindex.reindex_like(df_existed)
索引的运算
索引的集合运算
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}
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
id1.union(id2)
id1 | id2
id1.difference(id2)
(id1 ^ id2) & id1
id1.symmetric_difference(id2)
id1 ^ id2 # ^符号即对称差
可以用来进行一些类数据库的数据选择操作
练习
Ex1:公司员工数据集
现有一份公司员工数据集,
- 分别只使用
query
和loc
选出年龄不超过四十岁且工作部门为Dairy
或Bakery
的男性。 - 选出员工
ID
号 为奇数所在行的第1、第3和倒数第2列。 - 按照以下步骤进行索引操作:
- 把后三列设为索引后交换内外两层
- 恢复中间一层
- 修改外层索引名为
Gender
- 用下划线合并两层行索引
- 把行索引拆分为原状态
- 修改索引名为原表名称
- 恢复默认索引并将列保持为原表的相对位置
## Ex1
# 解法一(每个条件要加括号)
df.loc[(df.age<=40) & (df.department.isin(['Dairy','Bakery'])) & (df.gender=='M')].head()
# 解法二(只能用双引号)
df.query('age<=40 and department in ["Dairy", "Bakery"] and gender=="M"').head()
## Ex2
df[df.EmployeeID%2!=0].iloc[:,[0,2,-2]]
## Ex3
column_names = df.columns.values
a = list(column_names[-3:])
df.set_index(a,inplace=True)
df.reset_index(column_names[-2],inplace=True)
df.rename_axis(index = {
column_names[-1]:'Gender'},inplace = True)
new_idx= df.index.map(lambda x:x[0]+'_'+x[1])
df.index = new_idx
new_idx = df.index.map(lambda x:tuple(x.split('_')))
df.index = new_idx
df.rename_axis([column_names[-3],column_names[-1]],inplace = True)
df = df.reset_index()
df = df[column_names]