12

Pandas的crosstab函数

 3 years ago
source link: https://panchuang.net/2020/10/19/pandas%e7%9a%84crosstab%e5%87%bd%e6%95%b0/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Pandas的crosstab函数

磐石 • 2020年10月19日 pm9:51 • 机器学习

作者|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:https://github.com/BexTuychiev/medium_stories/tree/master/hardest_of_pandas2

# 导入必要的库
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有效。在一个很有帮助的StackOverflow中,我发现如果在数据帧上使用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就会抛出一个错误。同样,StackOverflow上的伙计们认为这是一个bug,而且已经有6年多没有解决过了。

最后要注意的是,在pivot_table()和crosstab()中,都有一个dropna参数,如果设置为True,则会删除包含所有nan的列或行。

原文链接:https://towardsdatascience.com/meet-the-hardest-functions-of-pandas-part-ii-f8029a2b0c9b

欢迎关注磐创AI博客站:
http://panchuang.net/

sklearn机器学习中文官方文档:
http://sklearn123.com/

欢迎关注磐创博客资源汇总站:
http://docs.panchuang.net/

原创文章,作者:磐石,如若转载,请注明出处:https://panchuang.net/2020/10/19/pandas%e7%9a%84crosstab%e5%87%bd%e6%95%b0/


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK