分级索引,groupby和pandas

本文概述

  • 分层索引和熊猫数据框
  • 层次结构索引, groupby对象和Split-Apply-Combine
  • 日常使用的分层索引
在上一篇文章中, 你通过拆分应用合并的原理了解了groupby操作是如何自然产生的。你检出了Netflix用户评级的数据集, 并按电影的发行年份对行进行了分组, 以生成下图:
分级索引,groupby和pandas

文章图片
这是通过按单个列进行分组来实现的。顺便说一句, 我提到你可能希望按几列进行分组, 在这种情况下, 生成的pandas DataFrame最终会具有多索引或层次结构索引。在本文中, 你将学习什么层次索引, 并查看它们在按数据的几个功能分组时如何产生。你可以在我们的” 用熊猫操作数据框” 课程中找到有关所有这些概念和实践的更多信息。
首先, 什么是层次结构索引?
分级索引,groupby和pandas

文章图片
分层索引和熊猫数据框 什么是数据帧的索引?
在介绍层次结构索引之前, 我想让你回顾一下熊猫DataFrame的索引是什么。 DataFrame的索引是一个集合, 由每行的标签组成。让我们来看一个例子。我将首先导入假设的srcmini学生Ellie在srcmini上的活动的综合数据集。这些列是日期, 编程语言以及当天Ellie用该语言完成的练习次数。加载数据:
# Import pandasimport pandas as pd# Load in datadf = pd.read_csv('data/user_ex_python.csv')df

日期 语言 ex_complete
0 2017-01-01 python 6
1 2017-01-02 python 5
2 2017-01-03 python 10
你可以在DataFrame的左侧看到Index, 它由整数组成。这是一个RangeIndex:
# Check out indexdf.index

RangeIndex(start=0, stop=3, step=1)

我们可以使用此索引切出一行df:
# Slice and dice datadf.loc[:1]

日期 语言 ex_complete
0 2017-01-01 python 6
1 2017-01-02 python 5
但是, 该索引的信息不足。如果要标记DataFrame的行, 则尽可能以有意义的方式标记它们。你可以对相关数据集执行此操作吗?考虑这一挑战的一种好方法是, 你希望每行都有一个唯一且有意义的标识符。检查列, 看是否符合这些条件。注意, date列包含唯一的日期, 因此在date列上标记每一行是有意义的。也就是说, 你可以使用.set_index()方法使date列成为DataFrame的索引(n.b. inplace = True表示你实际上是就地更改了DataFrame df):
# Set new indexdf.set_index(pd.DatetimeIndex(df['date']), inplace=True)df

日期 语言 ex_complete
日期
2017-01-01 2017-01-01 python 6
2017-01-02 2017-01-02 python 5
2017-01-03 2017-01-03 python 10
然后给df一个DateTimeIndex:
# Check out new indexdf.index

DatetimeIndex(['2017-01-01', '2017-01-02', '2017-01-03'], dtype='datetime64[ns]', name='date', freq=None)

现在, 你可以使用创建的DateTimeIndex分割行:
# Slice and dice data w/ new indexdf.loc['2017-01-02']

date2017-01-02languagepythonex_complete5Name: 2017-01-02 00:00:00, dtype: object

还请注意, .columns属性返回包含df列名称的索引:
# Check out columnsdf.columns

Index(['date', 'language', 'ex_complete'], dtype='object')

这可能会造成一些混乱, 因为这表示df.columns是Index类型。这并不意味着这些列是DataFrame的索引。 df的索引始终由df.index给出。查看我们的pandas DataFrames教程以获取更多有关索引的信息。现在是时候满足层次结构索引了。
熊猫DataFrame的多索引
如果像srcmini一样, 我们的数据集有多种语言怎么办?看一看:
# Import and check out datadf = pd.read_csv('data/user_ex.csv')df

日期 语言 ex_complete
0 2017-01-01 python 6
1 2017-01-02 python 5
2 2017-01-03 python 10
3 2017-01-01 [R 8
4 2017-01-02 [R 8
5 2017-01-03 [R 8
现在, 每个日期对应于几行, 每种语言对应一行。例如, 对于语言Python和R, 日期’ 2017-01-02’ 分别出现在第1和第4行中。因此, 日期不再唯一地指定行。但是, “ 日期” 和” 语言” 共同可以唯一地指定行。因此, 我们将两者都用作索引:
# Set indexdf.set_index(['date', 'language'], inplace=True)df

ex_complete
日期 语言
2017-01-01 python 6
2017-01-02 python 5
2017-01-03 python 10
2017-01-01 [R 8
2017-01-02 [R 8
2017-01-03 [R 8
现在, 你已经创建了一个多索引或分层索引(对这两个术语都感到满意, 因为你会发现它们可以互换使用), 你可以通过按以下方式检出索引来看到这一点:
# Check out multi-indexdf.index

MultiIndex(levels=[['2017-01-01', '2017-01-02', '2017-01-03'], ['python', 'r']], labels=[[0, 1, 2, 0, 1, 2], [0, 0, 0, 1, 1, 1]], names=['date', 'language'])

上面告诉你, DataFrame df现在具有一个MultiIndex, 它具有两个级别, 第一个级别由日期给出, 第二个级别由语言给出。回想一下, 上面你可以使用索引和.loc访问器来切片DataFrame:df.loc [‘ 2017-01-02’ ]。为了能够使用多索引切片, 你需要首先对索引进行排序:
# Sort indexdf.sort_index(inplace=True)df

ex_complete
日期 语言
2017-01-01 python 6
[R 8
2017-01-02 python 5
[R 8
2017-01-03 python 10
[R 8
现在, 你可以通过将元组传递给.loc访问器, 从而切出2017年1月2日完成的R练习的数量:
# Slice & dice your DataFramedf.loc[('2017-01-02', 'r')]

ex_complete8Name: (2017-01-02, r), dtype: int64

你现在对分层索引(或多索引)有所了解。现在该看看在使用groupby对象时它们如何产生。
层次结构索引, groupby对象和Split-Apply-Combine 在上一篇文章中, 我们探讨了使用netflix数据进行分组的对象以及split-apply-combine的数据分析原理。让我们快速浏览一下另一个数据集, 这些数据集内置在seaborn软件包中。 “ 提示” 包含小费, total_bill, 星期几和一天中的时间等功能。首先加载并浏览数据:
# Import and check out dataimport seaborn as snstips = sns.load_dataset("tips")tips.head()

total_bill 小费 性别 吸烟者 时间 尺寸
0 16.99 1.01 No Sun 晚餐 2
1 10.34 1.66 No Sun 晚餐 3
2 21.01 3.50 No Sun 晚餐 3
3 23.68 3.31 No Sun 晚餐 2
4 24.59 3.61 No Sun 晚餐 4
请注意, 技巧的索引是RangeIndex:
# Check out indextips.index

RangeIndex(start=0, stop=244, step=1)

在深入研究计算之前, 总是可以做一些可视化的EDA, 而Seaborn的pairplot函数可以让你大致了解所有数值变量:
# Import pyplot, figures inline, set style, plot pairplotimport matplotlib.pyplot as plt%matplotlib inlinesns.set()sns.pairplot(tips, hue='day');

分级索引,groupby和pandas

文章图片
如果要查看” 吸烟者” 和” 不吸烟者” 之间的平均小费之间的差异, 可以将原始数据框架按” 吸烟者” 划分(使用groupby), 应用功能” 均值” 并合并为一个新的DataFrame :
# Get mean of smoker/non-smoker groupsdf = tips.groupby('smoker').mean()df

total_bill 小费 尺寸
吸烟者
20.756344 3.008710 2.408602
No 19.188278 2.991854 2.668874
DataFrame df的结果索引是原始” tips” DataFrame的” 吸烟者” 列/特征:
# Check out new indexdf.index

CategoricalIndex(['Yes', 'No'], categories=['Yes', 'No'], ordered=False, name='smoker', dtype='category')

如果需要, 你可以重置索引, 以使” 吸烟者” 成为DataFrame的一列:
# Reset the indexdf.reset_index()

吸烟者 total_bill 小费 尺寸
0 20.756344 3.008710 2.408602
1 No 19.188278 2.991854 2.668874
现在是时候找出分层索引是如何从split-apply-combine和groupby操作中产生的。
多个分组和层次结构索引
上面, 你根据” 吸烟者” 功能对提示数据集进行了分组。有时, 你需要根据两个功能对数据集进行分组。例如, 将小费数据集归类为吸烟者/不吸烟者和晚餐/午餐是很自然的。为此, 将希望分组的列名作为列表传递:
# Group by two columnsdf = tips.groupby(['smoker', 'time']).mean()df

total_bill 小费 尺寸
吸烟者 时间
午餐 17.399130 2.834348 2.217391
晚餐 21.859429 3.066000 2.471429
No 午餐 17.050889 2.673778 2.511111
晚餐 20.095660 3.126887 2.735849
综上所述, 你也许可以看到” 吸烟者” 和” 时间” 都是df的指数。这是事实, 这是有道理的:如果按” 吸烟者” 分组导致索引为原始” 吸烟者” 列, 那么按两列分组将为你提供两个索引。检查索引以确认它是分层的:
# Check out indexdf.index

MultiIndex(levels=[['Yes', 'No'], ['Lunch', 'Dinner']], labels=[[0, 0, 1, 1], [0, 1, 0, 1]], names=['smoker', 'time'])

是的。你现在可以做很多有用的事情, 例如获取每个分组中的计数:
# Group by two featurestips.groupby(['smoker', 'time']).size()

smokertimeYesLunch23Dinner70NoLunch45Dinner106dtype: int64

你还可以交换层次结构索引的级别, 以便在索引中的” 吸烟者” 之前出现” 时间” :
# Swap levels of multi-indexdf.swaplevel()

total_bill 小费 尺寸
时间 吸烟者
午餐 17.399130 2.834348 2.217391
晚餐 21.859429 3.066000 2.471429
午餐 No 17.050889 2.673778 2.511111
晚餐 No 20.095660 3.126887 2.735849
然后, 你可能希望从层次结构索引中删除这些功能之一, 并针对该功能形成不同的列。你可以使用unstack方法:
# Unstack your multi-indexdf.unstack()

total_bill 小费 尺寸
时间 午餐 晚餐 午餐 晚餐 午餐 晚餐
吸烟者
17.399130 21.859429 2.834348 3.066000 2.217391 2.471429
No 17.050889 20.095660 2.673778 3.126887 2.511111 2.735849
你可以使用关键字参数” level” 在索引的外部特征上进行堆叠:
# Unsstack the outer indexdf.unstack(level=0)

total_bill 小费 尺寸
吸烟者 No No No
时间
午餐 17.399130 17.050889 2.834348 2.673778 2.217391 2.511111
晚餐 21.859429 20.095660 3.066000 3.126887 2.471429 2.735849
【分级索引,groupby和pandas】如你所料, 拆栈的结果具有非分层索引:
# Check out indexdf.unstack().index

CategoricalIndex(['Yes', 'No'], categories=['Yes', 'No'], ordered=False, name='smoker', dtype='category')

结果, 你现在可以针对这些分组执行所有类型的数据分析。我鼓励你这样做。
日常使用的分层索引 在这篇文章中, 向你介绍了层次结构索引(或多索引), 并看到了它们是如何希望DataFrame索引唯一且有意义地标记DataFrame的行的自然结果。你还了解了当你需要将数据按多列进行分组时, 它们是如何产生的, 并采用了split-apply-combine的原理。我希望你对工作中的层次结构索引感到乐趣。
该帖子来自Jupyter Notebook;你可以在此存储库中找到它。如果你有任何想法, 回应和/或反省, 请随时通过twitter @ hugobowne与我联系。

    推荐阅读