个性化阅读
专注于IT技术分析

分级索引,groupby和pandas

本文概述

在上一篇文章中, 你通过拆分应用合并的原理了解了groupby操作是如何自然产生的。你检出了Netflix用户评级的数据集, 并按电影的发行年份对行进行了分组, 以生成下图:

熊猫分组

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

首先, 什么是层次结构索引?

分级索引熊猫

分层索引和熊猫数据框

什么是数据帧的索引?

在介绍层次结构索引之前, 我想让你回顾一下熊猫DataFrame的索引是什么。 DataFrame的索引是一个集合, 由每行的标签组成。让我们来看一个例子。我将首先导入假设的srcmini学生Ellie在srcmini上的活动的综合数据集。这些列是日期, 编程语言以及当天Ellie用该语言完成的练习次数。加载数据:

# Import pandas
import pandas as pd

# Load in data
df = 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 index
df.index
RangeIndex(start=0, stop=3, step=1)

我们可以使用此索引切出一行df:

# Slice and dice data
df.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 index
df.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 index
df.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 index
df.loc['2017-01-02']
date           2017-01-02
language           python
ex_complete             5
Name: 2017-01-02 00:00:00, dtype: object

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

# Check out columns
df.columns
Index(['date', 'language', 'ex_complete'], dtype='object')

这可能会造成一些混乱, 因为这表示df.columns是Index类型。这并不意味着这些列是DataFrame的索引。 df的索引始终由df.index给出。查看我们的pandas DataFrames教程以获取更多有关索引的信息。现在是时候满足层次结构索引了。

熊猫DataFrame的多索引

如果像srcmini一样, 我们的数据集有多种语言怎么办?看一看:

# Import and check out data
df = 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 index
df.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-index
df.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 index
df.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 DataFrame
df.loc[('2017-01-02', 'r')]
ex_complete    8
Name: (2017-01-02, r), dtype: int64

你现在对分层索引(或多索引)有所了解。现在该看看在使用groupby对象时它们如何产生。


层次结构索引, groupby对象和Split-Apply-Combine

在上一篇文章中, 我们探讨了使用netflix数据进行分组的对象以及split-apply-combine的数据分析原理。让我们快速浏览一下另一个数据集, 这些数据集内置在seaborn软件包中。 “提示”包含小费, total_bill, 星期几和一天中的时间等功能。首先加载并浏览数据:

# Import and check out data
import seaborn as sns
tips = 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 index
tips.index
RangeIndex(start=0, stop=244, step=1)

在深入研究计算之前, 总是可以做一些可视化的EDA, 而Seaborn的pairplot函数可以让你大致了解所有数值变量:

# Import pyplot, figures inline, set style, plot pairplot
import matplotlib.pyplot as plt
%matplotlib inline
sns.set()
sns.pairplot(tips, hue='day');
分级索引,groupby和pandas3

如果要查看”吸烟者”和”不吸烟者”之间的平均小费之间的差异, 可以将原始数据框架按”吸烟者”划分(使用groupby), 应用功能”均值”并合并为一个新的DataFrame :

# Get mean of smoker/non-smoker groups
df = 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 index
df.index
CategoricalIndex(['Yes', 'No'], categories=['Yes', 'No'], ordered=False, name='smoker', dtype='category')

如果需要, 你可以重置索引, 以使”吸烟者”成为DataFrame的一列:

# Reset the index
df.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 columns
df = 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 index
df.index
MultiIndex(levels=[['Yes', 'No'], ['Lunch', 'Dinner']], labels=[[0, 0, 1, 1], [0, 1, 0, 1]], names=['smoker', 'time'])

是的。你现在可以做很多有用的事情, 例如获取每个分组中的计数:

# Group by two features
tips.groupby(['smoker', 'time']).size()
smoker  time  
Yes     Lunch      23
        Dinner     70
No      Lunch      45
        Dinner    106
dtype: int64

你还可以交换层次结构索引的级别, 以便在索引中的”吸烟者”之前出现”时间”:

# Swap levels of multi-index
df.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-index
df.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 index
df.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

如你所料, 拆栈的结果具有非分层索引:

# Check out index
df.unstack().index
CategoricalIndex(['Yes', 'No'], categories=['Yes', 'No'], ordered=False, name='smoker', dtype='category')

结果, 你现在可以针对这些分组执行所有类型的数据分析。我鼓励你这样做。

日常使用的分层索引

在这篇文章中, 向你介绍了层次结构索引(或多索引), 并看到了它们是如何希望DataFrame索引唯一且有意义地标记DataFrame的行的自然结果。你还了解了当你需要将数据按多列进行分组时, 它们是如何产生的, 并采用了split-apply-combine的原理。我希望你对工作中的层次结构索引感到乐趣。

该帖子来自Jupyter Notebook;你可以在此存储库中找到它。如果你有任何想法, 回应和/或反省, 请随时通过twitter @ hugobowne与我联系。

赞(0)
未经允许不得转载:srcmini » 分级索引,groupby和pandas

评论 抢沙发

评论前必须登录!