作者 | Bex T.
编译 | VK
来源 | Towards Datas Science
介绍
我很喜欢 DataCamp 上的 "Seaborn 中间数据可视化"(Intermediate Data Visualization with Seaborn)这个课程. 它教给新手非常棒的图表和方法. 但说到热图, 课程的老师不知怎么地引入了一个全新的 pandas 函数 crosstab. 然后, 很快说:"crosstab 是一个计算交叉表的有用函数..."
我就在那里不理解了. 显然, 我的第一反应是查看函数的文档. 我刚开始觉得我可以处理 Matplotlib 的任何文档, 但是... 我错了..
在我练习之后, 我知道这是别人也会挣扎的事情. 所以, 我在这里写了一整篇文章.
在本文的最后一部分中, 我讨论了为什么有些课程不教你像 crosstab 这样的高级函数. 因为如果不在具体的环境下很难使用这样的函数, 同时又保持示例的初学者级别.
此外, 大多数课程使用小型或玩具数据集. 在更复杂的数据科学环境中, 这些复杂函数的好处更为明显, 并且经常被更有经验的 pandas 用户使用.
在这篇文章中, 我将教你如何使用 crosstab 以及如何在其他类似函数中选择它.
目录
简介
设置
crosstab 基础知识
Pandas crosstab()与 pivot_table()和 groupby()的比较
Pandas crosstab()的进一步定制
Pandas crosstab(), 多个组
你可以在这个 GitHub repo 上下载本文的 notebook:
设置
- # 导入必要的库
- import pandas as pd
- import matplotlib.pyplot as plt
- import seaborn as sns
- import numpy as np
- # 忽略警告
- import warnings
- warnings.filterwarnings('ignore')
- # 启用多单元输出
- from IPython.core.interactiveshell import InteractiveShell
- InteractiveShell.ast_node_interactivity = 'all'
对于示例数据, 我将使用 Seaborn 内置的 diamonds 数据集. 它足够大, 并且有一些可以用 crosstab()的变量:
- diamonds = sns.load_dataset('diamonds')
- diamonds.head()
crosstab()基础知识
与许多计算分组汇总统计信息的函数一样, crosstab()可以处理分类数据. 它可用于将两个或多个变量分组, 并为每组的给定值执行计算. 当然, 使用 groupby()或 pivot_table()可以执行此类操作, 但正如我们稍后将要看到的, crosstab()为你的日常工作流程带来了许多好处.
函数接受两个或多个列表, pandas series 或 dataframe, 默认情况下返回每个组合的频率. 我总是喜欢从一个例子开始, 这样你可以更好地理解定义, 然后我将继续解释语法.
crosstab()总是返回一个数据帧, 下面是一个例子. dataframe 是 diamonds 中两个变量的交叉表: cut 和 color. 交叉表表示取一个变量, 将其组显示为 index, 取另一个变量, 将其组显示为 columns.
pd.crosstab(index=diamonds['cut'], columns=diamonds['color'])
语法相当简单. index 用于对变量进行分组, 并将其显示为 index(行), 对于列也是如此. 如果没有给定聚合函数, 则每个单元格将计算每个组合中的观察数. 例如, 左上角的单元格告诉我们, 有 2834 颗颜色代码为 D 而且是理想切割的钻石,.
接下来, 我们要查看每个组合的平均价格. crosstab()提供 values 参数来引入第三个要聚合的数值变量:
- pd.crosstab(index=diamonds['cut'],
- columns=diamonds['color'],
- values=diamonds['price'],
- aggfunc=np.mean).round(0)
现在, 每个单元格包含了 cut 和 color 组合的平均价格. 为了说明我们要计算平均价格, 我们将 price 列传递给 values. 请注意, 始终必须同时使用 values 和 aggfunc. 否则, 你将得到一个错误. 我还使用 round()将答案四舍五入.
尽管它有点高级, 但是当你将 crosstab()表传递到 seaborn 的热图中时, 你将充分利用 crosstab()表的优点. 让我们在热图中看到上表:
- cross = pd.crosstab(index=diamonds['cut'],
- columns=diamonds['color'],
- values=diamonds['price'],
- aggfunc=np.mean).round(0)
- sns.heatmap(cross, cmap='rocket_r', annot=True, fmt='g');
seaborn 可以自动将 crosstab()表转换为热图. 我将注释设置为 True, 并用颜色条显示热图. seaborn 还为列和索引名添加了样式(fmt='g' 将数字显示为整数而不是科学计数).
热图更容易解释. 你不想让你的最终用户看到一张满是数字的表格. 因此, 我将在需要时将每个 crosstab()结果放入热图中. 为了避免重复, 我创建了一个有用的函数:
- def plot_heatmap(cross_table, fmt='g'):
- fig, ax = plt.subplots(figsize=(8, 5))
- sns.heatmap(cross_table,
- annot=True,
- fmt=fmt,
- cmap='rocket_r',
- linewidths=.5,
- ax=ax)
- plt.show();
Pandas crosstab()与 pivot_table()和 groupby()的比较
在我们继续讨论更有趣的内容之前, 我想我需要澄清计算分组摘要统计的三个函数之间的区别.
我在本文的第一部分介绍了 pivot_table()和 groupby()的区别. 对于 crosstab(), 这三者之间的区别在于语法和结果的形状. 让我们使用这三种方法计算:
- # 使用 groupby()
- >>> diamonds.groupby(['cut', 'color'])['price'].mean().round(0)
- cut color
- Ideal D 2629.0
- E 2598.0
- F 3375.0
- G 3721.0
- H 3889.0
- I 4452.0
- J 4918.0
- Premium D 3631.0
- E 3539.0
- F 4325.0
- G 4501.0
- H 5217.0
- I 5946.0
- J 6295.0
- Very Good D 3470.0
- E 3215.0
- F 3779.0
- G 3873.0
- H 4535.0
- I 5256.0
- J 5104.0
- Good D 3405.0
- E 3424.0
- F 3496.0
- G 4123.0
- H 4276.0
- I 5079.0
- J 4574.0
- Fair D 4291.0
- E 3682.0
- F 3827.0
- G 4239.0
- H 5136.0
- I 4685.0
- J 4976.0
- Name: price, dtype: float64
- # 使用 pivot_table()
- diamonds.pivot_table(values='price',
- index='cut',
- columns='color',
- aggfunc=np.mean).round(0)
- # 使用 crosstab()
- pd.crosstab(index=diamonds['cut'],
- columns=diamonds['color'],
- values=diamonds['price'],
- aggfunc=np.mean).round(0)
以上是 pivot_table 的输出
以上是 crosstab 的输出
我想你已经知道你最喜欢的了. grouppy()返回一个序列, 而另两个返回相同的数据帧. 但是, 可以将 groupby 系列转换为相同的数据帧, 如下所示:
- grouped = diamonds.groupby(['cut', 'color'])['price'].mean().round(0)
- grouped.unstack()
如果你不了解 pivot_table()和 unstack()的语法, 我强烈建议你阅读本文的第一部分.
说到速度, crosstab()比 pivot_table()快, 但都比 groupby()慢得多:
- %%timeit
- diamonds.pivot_table(values='price',
- index='cut',
- columns='color',
- aggfunc=np.mean)
- 11.5 ms ± 483 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
- %%timeit
- pd.crosstab(index=diamonds['cut'],
- columns=diamonds['color'],
- values=diamonds['price'],
- aggfunc=np.mean)
- 10.8 ms ± 344 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
- %%timeit
- diamonds.groupby(['cut', 'color'])['price'].mean().unstack()
- 4.13 ms ± 39.8 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
如你所见, 即使使用 unstack()链接, groupby()也比其他两个快 3 倍. 这说明如果你只想分组和计算摘要统计信息, 那么应该使用相同的 groupby(). 当我链接其他方法 (如 simple round() 时, 速度差甚至更大.
其余的比较主要是关于 pivot_table()和 crosstab(). 如你所见, 这两个函数的结果的形状是相同的. 两者之间的第一个区别是 crosstab()可以处理任何数据类型.
它可以接受任何类似数组的对象, 比如列表, numpy 数组, 数据帧列 (pandas series). 但是, pivot_table() 只对 dataframe 有效. 在一个很有帮助的 Stack Overflow 中, 我发现如果在数据帧上使用 crosstab(), 它会在后台调用 pivot_table().
接下来是参数. 有些参数只存在于一个参数中, 反之亦然. 第一个最流行的是 crosstab()的 normalize.normalize 接受以下选项(来自文档):
如果传递了 all 或 True, 则将规范化所有值.
如果传递 index, 将规范化每一行.
如果传递 columns, 将规范化每个列.
让我们看一个简单的例子:
- cross = pd.crosstab(index=diamonds['cut'],
- columns=diamonds['color'],
- normalize='all')
- plot_heatmap(cross, fmt='.2%')
如果传递 all, 对于每个单元格, pandas 计算总金额的百分比:
- # 证明所有值加起来约等于 1
- >>> pd.crosstab(diamonds['cut'],
- diamonds['color'],
- normalize='all').values.sum()
- 1.0000000000000002
如果传递 index 或 columns, 则按列或按行执行相同的操作:
- cross = pd.crosstab(diamonds['cut'],
- diamonds['color'],
- normalize='index')
- plot_heatmap(cross, fmt='.2%')
以上是按行规范化
- cross = pd.crosstab(diamonds['cut'], diamonds['color'], normalize='columns')
- plot_heatmap(cross, fmt='.2%')
以上是按列规范化
在 crosstab()中, 还可以使用行名和列名直接在函数内更改索引和列名. 之后不必手动执行. 当我们一次按多个变量分组时, 这两个参数非常有用, 你将在后面看到.
参数 fill_value 只存在于 pivot_table()中. 有时, 当你按许多变量分组时, 不可避免地会出现不一致. 在 pivot_table()中, 可以使用 fill_value 将它们更改为自定义值:
- diamonds.pivot_table(index='color',
- columns='cut',
- fill_value=0)
但是, 如果使用 crosstab(), 则可以通过在 dataframe 上链接 fillna()来实现相同的效果:
pd.crosstab(diamonds['cut'], diamonds['color']).fillna(0)
Pandas crosstab()的进一步定制
crosstab()的另外两个有用参数是 margins 和 margins_name(两者都存在于 pivot_table()中). 设置为 True 时, 边界计算每行和每列的和. 我们来看一个例子:
- pd.crosstab(index=diamonds['cut'],
- columns=diamonds['clarity'],
- margins=True)
pandas 自动添加最后一行和最后一列, 默认名称为 All.margins_name 可以控制名字:
- pd.crosstab(index=diamonds['cut'],
- columns=diamonds['clarity'],
- margins=True,
- margins_name='Total Number')
右下角的单元格将始终包含观察的总数, 或者如果 "normalize" 设置为 True, 则为 1:
- pd.crosstab(index=diamonds['cut'],
- columns=diamonds['clarity'],
- margins=True,
- margins_name='Total Percentage',
- normalize=True)
请注意, 如果将 margins 设置为 True, 则热图是无用的.
Pandas crosstab(), 多组
对于 index 和 columns 参数, 可以传递多个变量. 结果将是一个具有多级索引的数据帧. 这次我们插入所有的分类变量:
- pd.crosstab(index=[diamonds['cut'], diamonds['clarity']],
- columns=diamonds['color'])
对于 index, 我传递了 color 和 cut. 如果我把它们传递给列, 结果将是一个包含 40 列的数据帧. 如果你注意的话, 多级索引如预期的那样命名为 cut 和 clear. 对于存在多级索引或列名的情况, crosstab()有方便的参数来更改它们的名称:
- pd.crosstab(index=[diamonds['cut'], diamonds['clarity']],
- columns=diamonds['color'],
- rownames=['Diamond Cut', 'Clarity']).head()
传递相应名称的列表, 以将索引名称更改为行名称. 这个过程对于控制列名的 colnames 是相同的.
有一件事让我很惊讶, 如果你把多个函数传递给 aggfunc,pandas 就会抛出一个错误. 同样, Stack Overflow 上的伙计们认为这是一个 bug, 而且已经有 6 年多没有解决过了.
最后要注意的是, 在 pivot_table()和 crosstab()中, 都有一个 dropna 参数, 如果设置为 True, 则会删除包含所有 nan 的列或行.
来源: http://www.bubuko.com/infodetail-3683447.html