Pandas中的多级索引(MultiIndex)是指在一个DataFrame或Series中,使用多个索引级别来组织数据。多级索引可用于存储高维数据,如时间序列数据或具有多个分类变量的数据。
在Pandas中,MultiIndex可以通过以下方式创建:
import pandas as pd
index = pd.MultiIndex.from_tuples([('A', 'X'), ('A', 'Y'), ('B', 'X'), ('B', 'Y')])
import pandas as pd
import numpy as np
index1 = np.array(['A', 'A', 'B', 'B'])
index2 = np.array(['X', 'Y', 'X', 'Y'])
index = pd.MultiIndex.from_arrays([index1, index2])
import pandas as pd
index1 = ['A', 'A', 'B', 'B']
index2 = ['X', 'Y', 'X', 'Y']
index = pd.MultiIndex.from_product([index1, index2])
创建MultiIndex后,可以使用MultiIndex.get_level_values()
方法获取每个级别的值,并使用loc()
方法选择特定级别的数据。例如:
import pandas as pd
index = pd.MultiIndex.from_product([['A', 'B'], ['X', 'Y']])
data = pd.DataFrame({'value': [1, 2, 3, 4]}, index=index)
print(data.loc['A'])
这将输出:
value
X 1
Y 2
能使我们快速便捷地处理数据的函数和方法。
对于从未听说过Pandas的人来说,多索引(MultiIndex)最直接的用法是使用第二个索引列作为第一个索引列的补充,以唯一地标识每行。例如,为了消除来自不同州的城市的歧义,州的名字通常附加在城市的名字后面。例如,在美国大约有40个springfield(在关系型数据库中,它被称为复合主键)。
你可以在从CSV解析DataFrame后指定要包含在索引中的列,也可以立即作为read_csv的参数。
您还可以使用append=True将现有级别添加到多重索引,如下图所示:
另一个更典型的用例是表示多维。当你有一组具有特定属性的对象或者随着时间的推移而演变的对象时。例如:
Titanic
数据集
这也被称为“面板数据”,Pandas就是以此命名的。
让我们添加这样一个维度:
现在我们有了一个四维空间,如下所示:
下图说明了这个概念:
为了给对应列的尺寸名称留出空间,Pandas将整个标题向上移动:
关于多重索引需要注意的第一件事是,它并不按照它可能出现的情况对任何内容进行分组。在内部,它只是一个扁平的标签序列,如下所示:
你可以通过对行标签进行排序来获得相同的groupby效果:
你甚至可以通过设置相应的Pandas选项来完全禁用视觉分组 :pd.options.display.multi_sparse=False。
Pandas(以及Python本身)区分数字和字符串,因此在无法自动检测数据类型时,通常最好将数字转换为字符串:
pdi.set_level(df.columns, 0, pdi.get_level(df.columns, 0).astype('int'))
如果你喜欢冒险,可以使用标准工具做同样的事情:
df.columns = df.columns.set_levels(df.columns.levels[0].astype(int), level=0)
但为了正确使用它们,你需要理解什么是levels
和codes
,而pdi允许你使用多索引,就像使用普通的列表或NumPy数组一样。
如果你真的想知道,levels
和codes
是特定级别的常规标签列表被分解成的东西,以加速像pivot、join等操作:
除了从CSV文件读取和从现有列构建外,还有一些方法可以创建多重索引。它们不太常用——主要用于测试和调试。
由于历史原因,使用Panda自己的多索引表示的最直观的方法不起作用。
这里的Levels
和codes
(现在)被认为是不应该暴露给最终用户的实现细节,但我们已经拥有了我们所拥有的。
可能最简单的构建多重索引的方法如下:
这样做的缺点是必须在单独的一行中指定级别的名称。有几种可选的构造函数将名称和标签捆绑在一起。
当关卡形成规则结构时,您可以指定关键元素,并让Pandas自动交织它们,如下所示:
上面列出的所有方法也适用于列。例如:
通过多重索引访问DataFrame的好处是,您可以轻松地使用熟悉的语法一次引用所有级别(可能省略内部级别)。
列——通过普通的方括号
行和单元格——使用.loc[]
现在,如果你想选择俄勒冈州的所有城市,或者只留下包含人口的列,该怎么办?Python语法在这里有两个限制。
在技术方面,这并不难安排。我给DataFrame打了猴补丁,添加了这样的功能,你可以在这里看到:
这种语法唯一的缺点是,当你使用两个索引器时,它返回一个副本,所以你不能写df.mi[:, ’ Oregon ‘]。Co [’ population '] = 10。有许多可选的索引器,其中一些允许这样的赋值,但它们都有自己的特点:
因此,df[:, ‘population’]可以用df.swaplevel(axis=1)[‘population’]实现。
这感觉很hacky,不方便超过两层。
它给人的感觉不够python化,尤其是在选择多个关卡时。这种方法无法同时过滤行和列,因此名称xs(代表“横截面”)背后的原因并不完全清楚。它不能用于设置值。
3.可以为pd创建别名。idx=pd.IndexSlice;df.loc [:, idx[:, ’ population ']]
这更符合python风格,但要访问元素,必须使用别名,这有点麻烦(没有别名的代码太长了)。您可以同时选择行和列。可写的。
作为底线,Pandas有多种使用括号使用多重索引访问DataFrame元素的方法,但没有一种方法足够方便,因此他们不得不采用另一种索引语法:
它方便快捷,但缺乏IDE的支持(没有自动补全,没有语法高亮等),而且它只过滤行,而不是列。这意味着你不能在不转置DataFrame的情况下用它实现df:, ’ population '。Non-writable。
Pandas没有针对列的set_index。向列中添加层次的一种常见方法是将现有的层次从索引中“解栈”:
Pandas的栈与NumPy的栈有很大不同。让我们看看文档中对命名约定的说明:
“该函数的命名类似于重新组织的书籍集合,从水平位置并排(dataframe的列)到垂直堆叠(在dataframe的索引中)。”
“在上面”的部分听起来并不能让我信服,但至少这个解释有助于记住谁把东西朝哪个方向移动。顺便说一下,Series有unstack,但没有stack,因为它已经“堆叠”了。由于是一维的,Series在不同情况下可以作为行向量或列向量,但通常被认为是列向量(例如dataframe列)。
例如:
您还可以通过名称或位置索引指定要堆叠/解堆叠的级别。在这个例子中,df.stack()、df.stack(1)和df.stack(’ year ‘)与df1.unstack()、df1.unstack(2)和df1.unstack(’ year ')产生相同的结果。目的地总是在“最后一层之后”,并且不可配置。如果需要将级别放在其他地方,可以使用df.swaplevel().sort_index()或pdi。swap_level (df = True)
列必须不包含重复的值才能堆叠(在反堆叠时,索引也是如此):
stack和unstack都有一个坏习惯,会不可预测地按字典顺序排序结果索引。这有时可能令人恼火,但这是在有大量缺失值时给出可预测结果的唯一方法。
考虑下面的例子。你希望一周中的天数以何种顺序出现在右边的表中?
你可以推测,如果John的星期一在John的星期五的左边,那么就是’ Mon ’ < ’ Fri ‘,类似地,Silvia的’ Fri ’ < ’ Sun ‘,因此结果应该是’ Mon ’ < ’ Fri ’ < ’ Sun ‘。这是合法的,但是如果剩余的列顺序不同,比如’ Mon ’ < ’ frii ‘和’ Tue ’ < ’ frii ‘,该怎么办?或者’ Mon ’ < ’ friday ‘和’ Wed ’ < ’ Sat ’ ?
好吧,一周没有那么多天,Pandas可以根据先验知识推断出顺序。但是,人类还没有得出一个决定性的结论,那就是星期天应该作为一周的结束还是开始。Pandas应该默认使用哪种顺序?阅读区域设置?那么不那么琐碎的顺序呢,比如美国的州的顺序?
在这种情况下,Pandas所做的只是简单地按字母顺序排序,如下所示:
虽然这是一个合理的默认,但感觉上仍然是错误的。应该有一个解决方案!有一个。它被称为CategoricalIndex。即使缺少一些标签,它也会记住顺序。它最近已经顺利集成到Pandas工具链中。它唯一缺少的是基础设施。它很难建立;它是脆弱的(在某些操作中会退回到对象),但它是完全可用的,并且pdi库有一些帮助程序可以陡峭地提高学习曲线。
例如,要告诉Pandas锁定存储产品的简单索引的顺序(如果你决定将一周中的天数解栈回列,则不可避免地会排序),你需要编写像df这样可怕的代码。index = pd.CategoricalIndex(df. index)df指数。指数排序= True)。它更适合多索引。
pdi库有一个辅助函数locked(以及一个默认为inplace=True的别名lock),通过将某个多索引级别提升到CategoricalIndex来锁定该级别的顺序:
等级名称旁边的勾选标记表示等级被锁定。它可以使用pdi.vis(df)手动可视化,也可以使用pdi.vis_patch()对DataFrame HTML输出进行monkey补丁自动可视化。应用补丁后,在Jupyter单元中简单地写df
将显示锁定顺序的所有级别的复选标记。
Lock和locked在简单的情况下自动工作(如客户端名称),但在更复杂的情况下(如缺少日期的星期几)需要用户提示。
在级别切换到CategoricalIndex之后,它会在sort_index、stack、unstack、pivot、pivot_table等操作中保持原来的顺序。
不过,它很脆弱。即使像df[’ new_col '] = 1这样简单的操作也会破坏它。使用pdi.insert (df。columns, 0, ’ new_col ', 1)用CategoricalIndex正确处理级别。
除了前面提到的方法之外,还有一些其他的方法:
pdi.swap_levels (obj, src=-2, dst=-1)交换两个级别(默认是两个最内层的级别)
pdi.move_level (obj, src, dst)将特定级别src移动到指定位置dst
除了上述参数外,本节中的所有函数还有以下参数:
上面的所有操作都是从传统意义上理解“级别”这个词的(级别的标签数量与数据框中的列数量相同),隐藏了索引的机制。标签和索引。来自最终用户的代码。
在极少数情况下,当移动和交换单独的关卡不够时,您可以使用纯Pandas调用:df一次性重新排序所有关卡。columns = df.columns.reorder_levels([’ M ', ’ L ', ’ K ‘])其中[’ M ', ’ L ', ’ K ']是层的期望顺序。
通常,使用get_level和set_level对标签进行必要的修复就足够了,但如果你想一次对多索引的所有级别应用转换,Pandas有一个(命名不明确)函数rename接受一个dict或一个函数:
至于重命名级别,它们的名称存储在.names字段中。该字段不支持直接赋值(为什么不?):df.index.names[1] = ’ x ’ # TypeError,但可以作为一个整体替换:
当你只需要重命名一个特定的级别时,语法如下:
正如我们在上面看到的,便捷的查询方法只解决了处理行中的多索引的复杂性。尽管有这么多的辅助函数,但当某些Pandas函数返回列中的多索引时,对初学者来说会有一个震惊的效果。因此,pdi库具有以下内容:
join_levels(obj, sep=’_’, name=None) 将所有多索引级别连接到一个索引
split_level(obj, sep=’_’, names=None)将索引拆分回多索引
它们都有可选的axis和inplace参数。
由于多索引由多个级别组成,因此排序比单索引更做作。这仍然可以使用sort_index方法完成,但可以使用以下参数进行进一步微调。
要对列级别进行排序,指定axis=1。
Pandas可以以完全自动化的方式将具有多重索引的DataFrame写入CSV文件:df.to_csv('df.csv ')。但是在读取这样的文件时,Pandas无法自动解析多重索引,需要用户的一些提示。例如,要读取具有三层高列和四层宽索引的DataFrame,你需要指定pd.read_csv(‘df.csv’, header=[0,1,2], index_col=[0,1,2,3])。
这意味着前三行包含有关列的信息,后续每一行的前四个字段包含索引级别(如果列的级别不止一个,你不能再通过名称来引用行级别,只能通过编号)。
手动解读多索引中的层数是不方便的,所以更好的主意是在将DataFrame保存到CSV之前,stack()所有列头层,并在读取后将它们解stack()。
如果你需要“置之不理”的解决方案,可能需要研究二进制格式,例如Python的pickle格式:
直接调用:df.to_pickle(‘df.pkl’), pd.read_pickle(‘df.pkl’)
使用storemagic在Jupyter %store df然后%store -r df(存储在$ HOME/.ipython/profile_default/db/autorestore)
Python的pickle小巧而快速,但只能在Python中访问。如果您需要与其他生态系统互操作,请查看更标准的格式,如Excel格式(在读取MultiIndex时需要与read_csv相同的提示)。代码如下:
!pip install openpyxl
df.to_excel('df3.xlsx')
df.to_pd.read_excel('df3.xlsx', header=[0,1,2], index_col=[0,1,2,3])
或者查看其他选项(参见文档)。
当使用多索引数据框时,与普通数据框适用相同的规则(见上文)。但是处理细胞的一个子集有它自己的一些特性。
用户可以通过外部的多索引级别更新部分列,如下所示:
如果想保持原始数据不变,可以使用df1 = df.assign(population=df.population*10)。
你也可以用density=df.population/df.area轻松获得人口密度。
但不幸的是,你不能用df.assign将结果赋值给原始的dataframe。
一种方法是将列索引的所有不相关级别堆叠到行索引中,执行必要的计算,然后将它们解堆叠回去(使用pdi)。锁以保持列的原始顺序)。
或者,你也可以使用pdi.assign:
pdi.assign是锁定顺序感知的,所以如果你给它一个(多个)锁定级别的dataframe,它不会解锁它们或后续的栈/解栈/等。操作将保持原始的列和行顺序。