【学习笔记】《深入浅出Pandas》第8章:Pandas多层索引

多层索引(Hierarchical indexing)又叫多层索引,它为一些非常复杂的数据分析和操作(特别是处理高维数据)提供了方法。从本质上讲,它使你可以在Series(一维)和DataFrame(二维)等较低维度的数据结构中存储和处理更高维度的数据。

8.1 概述

本节介绍多层数据的一些基本概念和使用场景,以及如何创建多层索引数据。理解了多层数据的基本概念和使用场景,我们就能更好地应用它的特性来解决实际数据分析中的问题。

8.1.1 什么是多层索引

在这里插入图片描述
多层数据可以只有在行上为多层,可以只在列上为多层,也可以在两个方向上都为多层,理论上层数是没有限制的。除了原生数据为多层外,在做数据分组聚合等操作时也会产生多层数据。本质上,这其实是一个以低维形式展示的多维数据。

8.1.2 通过分组产生多层索引

多个分组条件会产生多层索引:

# eg:按团队分组,个图案段中平均成绩及格的人数
df.groupby(['team', df.mean(1)>=60]).count()

"""
			name	Q1	Q2	Q3	Q4
team						
A	False	14		14	14	14	14
	True	3		3	3	3	3
B	False	14		14	14	14	14
	True	8		8	8	8	8
C	False	17		17	17	17	17
	True	5		5	5	5	5
D	False	10		10	10	10	10
	True	9		9	9	9	9
E	False	15		15	15	15	15
	True	5		5	5	5	5
"""

另外,如果对一列应用多个聚合计算方法,也会产生多层索引。比如计算某列的最大值和最小值,在行上就会产生两层索引。

8.1.3 由序列创建多层索引

MultiIndex对象是Pandas标准Index的子类,由它来表示多层索引业务。可以将MultiIndex视为一个元组对序列,其中每个元组对都是唯一的。可以通过以下方式生成一个索引对象。
序列数据:

# 定义一个序列
arrays = [[1, 1, 2, 2], ['A', 'B', 'A', 'B']]
# 生成多层索引
index = pd.MultiIndex.from_arrays(arrays, names=('class', 'team'))

"""
MultiIndex([(1, 'A'),
            (1, 'B'),
            (2, 'A'),
            (2, 'B')],
           names=['class', 'team'])
"""

可以用这个多层索引对象生成DataFrame:

pd.DataFrame([{'Q1':60, 'Q2':70}], index=index)
"""
				Q1	Q2
class	team		
1		A		60	70
		B		60	70
2		A		60	70
		B		60	70
"""

8.1.4 由元组创建多层索引

可以使用pd.MultiIndex.from_tuples()将由元组组成的序列转换为多层索引。

# 定义一个两层的序列
arrays = [[1, 1, 2, 2], ['A', 'B', 'A', 'B']]
# 转换为元组
tuples = list(zip(*arrays)) # [(1, 'A'), (1, 'B'), (2, 'A'), (2, 'B')]
# 生成多层索引
index = pd.MultiIndex.from_tuples(tuples, names=['class', 'team'])
# 使用多层索引对象
pd.Series(np.random.randn(4), index=index)
"""
class  team
1      A      -0.526717
       B      -1.408692
2      A      -1.003524
       B      -0.499982
dtype: float64
"""

8.1.5 可迭代对象的笛卡尔积

使用上述方法的时候需要将所有层的所有值都写出来,而pd.MultiIndex.from_product()可以做笛卡尔积计算,将所有情况排列组合出来。

_class = [1, 2]
team = ['A', 'B']
# 生成多层索引对象
index = pd.MultiIndex.from_product([_class, team], names=['class', 'team'])
# Series应用多层索引对象
pd.Series(np.random.randn(4), index=index)
"""
	class  		team
1      A       0.923623
       B       0.525440
2      A       0.832210
       B      -2.446039
dtype: float64
"""

8.1.6 将DataFrame转为多层索引对象

pd.MultiIndex.from_frame()可以将DataFrame的数据转化为多层索引对象。

df_i = pd.DataFrame([['1', 'A'], ['1', 'B'], ['2', 'A'], ['2', 'B']], columns=['class', 'team'])
# 将DataFrame中的数据转化为多层索引对象
index = pd.MultiIndex.from_frame(df_i)
# 应用多层对象
pd.Series(np.random.randn(4), index=index)
"""
	class  		team
1      A      -0.367022
       B      -0.226560
2      A      -0.367938
       B      -0.118559
dtype: float64
"""

8.2 多层索引操作

8.2.1 生成数据

在介绍多层索引的构建时,我们将多层索引对象应用到了DataFrame和Series上,使其成为一个多层索引的DataFrame或Series。以下是一个典型的多层索引数据的生成过程:

# 索引
index_arrays = [[1, 1, 2, 2], ['男', '女', '男', '女']] 
# 列名
columns_arrays = [['2019', '2019', '2020', '2020'], ['上半年', '下半年', '上半年', '下半年']]
# 索引转换为多层
index = pd.MultiIndex.from_arrays(index_arrays, names=('班级', '性别'))
# 列名转换为多层
columns = pd.MultiIndex.from_arrays(columns_arrays, names=('年份', '学期'))

# 应用到DataFrame中
df = pd.DataFrame([(88, 99, 88, 99), (77, 88, 97, 98),
                   (67, 89, 54, 78), (34, 67, 89, 54)],
                   columns=columns, index=index)

在这里插入图片描述

8.2.2 索引信息

多层索引也可以查看行、列及行与列的名称。

df.index # 索引,是一个MultiIndex
"""
MultiIndex([(1, '男'),
            (1, '女'),
            (2, '男'),
            (2, '女')],
           names=['班级', '性别'])
"""
df.columns # 列索引,也是一个MultiIndex
"""
MultiIndex([('2019', '上半年'),
            ('2019', '下半年'),
            ('2020', '上半年'),
            ('2020', '下半年')],
           names=['年份', '学期'])
"""
# 查看行索引名称
df.index.names
# FrozenList(['班级', '性别'])

8.2.3 查看层级

多层索引由于层级较多,在数据分析时需要查看它共有多少个层级。

df.index.nlevels # 行层级数 2
df.index.levels # 行的层级 FrozenList([[1, 2], ['女', '男']])

8.2.4 索引内容

可以取指定层级的索引内容,也可以按索引名取索引内容:

# 获取索引第2层的内容
df.index.get_level_values(1)
# Index(['男', '女', '男', '女'], dtype='object', name='性别')

# 获取列索引第二层内容
df.columns.get_level_values(1)
# Index(['上半年', '下半年', '上半年', '下半年'], dtype='object', name='学期')

# 按索引名称取索引内容
df.index.get_level_values('班级')
# Int64Index([1, 1, 2, 2], dtype='int64', name='班级') 

8.2.5 排序

多层索引可以根据需要实现较为复杂的排序操作:

# 使用索引名可进行排序,可以指定具体的列
df.sort_values(by=['性别', ('2020', '下半年')]) # 先按照性别,然后按照2020-下半年,升序排列

在这里插入图片描述

df.index.reorder_levels([1, 0]) # 等级顺序,互换
# 班级:性别 -> 性别:班级
"""
MultiIndex([('男', 1),
            ('女', 1),
            ('男', 2),
            ('女', 2)],
           names=['性别', '班级'])
"""
df.index.reindex(df.index[::-1]) # 顺序颠倒
"""
(MultiIndex([(2, '女'),
             (2, '男'),
             (1, '女'),
             (1, '男')],
            names=['班级', '性别']),
 array([3, 2, 1, 0], dtype=int64))
"""
df.index.sortlevel(level=0, ascending=True) # 按指定级别排序
# 在请求的级别对MultiIndex进行排序。结果将遵守该级别上关联因子的原始排序
# 首先level=0:班级,按照1->2的顺序排列,其次,考虑班级的关联因子:性别,按照female->male
"""
(MultiIndex([(1, '女'),
             (1, '男'),
             (2, '女'),
             (2, '男')],
            names=['班级', '性别']),
 array([1, 0, 3, 2], dtype=int64))
"""
idx.set_codes([1, 1, 0, 0], level='foo') # 设置顺序

8.2.6 其他操作

# 生成一个笛卡尔积的元组对序列
df.index.to_numpy()
# array([(1, '男'), (1, '女'), (2, '男'), (2, '女')], dtype=object)

# 返回没有使用的层级
df.index.remove_unused_levels() 
"""
MultiIndex([(1, '男'),
            (1, '女'),
            (2, '男'),
            (2, '女')],
           names=['班级', '性别'])
"""
df.swaplevel(0, 1) # 交换索引
df.index.droplevel(0) # 删除指定等级并返回
df.index.get_locs((2, '女')) # 返回索引位置 
idx.set_levels(['a', 'b'], level='bar') # 设置新的索引内容
idx.set_levels([['a', 'b', 'c'], [1, 2, 3, 4]], level=[0, 1]) 

idx.to_flat_index() # 转为元组对序列

8.3 数据查询

8.3.1 查询行

查询第一层级的某个索引下的内容,可以直接使用df.loc[]传入它的索引值

df.loc[1] # 查看一班的数据

在这里插入图片描述
此外,切片(如df.loc[1:2]查询1班和2班的数据)也适用。
如果需要同时根据一二级索引查询,可以将需要查询的索引条件组成一个元组:

df.loc[(1, '男')] # 1班男生

在这里插入图片描述

8.3.2 查询列

查询列时,可以直接用切片选择需要查询的列,使用元组指定相关的层级数据:

df['2020'] # 整个一级索引下
df[('2020', '上半年')] # 指定二级索引
df['2020']['上半年'] # 同上

8.3.3 行列查询

行列查询和单层索引一样,指定层内容也用元组表示。slice(None)可以在元组中占位,表示本层所有内容:

df.loc[(1, '男'), '2020'] # 只显示20201班男生

"""
学期
上半年    88
下半年    99
Name: (1,), dtype: int64
"""
df.loc[:, (slice(None), '下半年')] # 只看下半年

在这里插入图片描述

df.loc[(slice(None), '女'), :] # 只看女生
df.loc[(1, slice(None)), :] # 只看1

8.3.4 条件查询

按照一定条件查询数据,和单层索引的数据查询一样,不过在选择列上要按多层的规则。

# 2020年上半年大于80的数据
df[df[('2020', '上半年')] > 80]

在这里插入图片描述

8.3.5 用pd.IndexSlice索引数据

pd.IndexSlice可以创建一个切片对象,轻松执行复杂的索引切片操作:

idx = pd.IndexSlice
idx[0] # 0
idx[:] # slice(None, None, None)
idx[0, 'x'] # (0, 'x')
idx[0:3] # slice(0, 3, None)
idx[0.1:1.5] # slice(0.1, 1.5, None)
idx[0:5, 'x':'y '] # (slice(0, 5, None), slice('x', 'y', None))

应用在查询中:

idx = pd.IndexSlice
df.loc[idx[:, ['男']], :] # 只显示男生
df.loc[:, idx[:, ['上半年']]] # 只显示上半年

8.3.6 df.xs()

使用df.xs()方法采用索引内容作为参数来选择多层索引数据中特定级别的数据:

df.xs((1, '男')) # 1班男生
df.xs('2020', axis=1) # 2020年 表头搭配axis
df.xs('男', level=1) # 所有男生 索引搭配level

8.4 本章小结

多层数据是基于业务需要产生的,它能够让数据的逻辑和归属更加清晰和明确,对于数据的展示和分析操作有重要意义。
在Pandas中,很多函数和方法都支持level参数,可以对指定层级的数据进行操作。
但是,要尽量避免让数据多层级化,以免数据处理复杂,可以先将数据筛选完成,再创建索引,保持一层索引。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值