MEP28:从 Axes.boxplot 中移除复杂性#

状态#

讨论

分支和拉取请求#

以下列出了与此 MEP 相关的任何开放 PR 或分支机构:

  1. 弃用冗余统计 kwargs Axes.boxplothttps ://github.com/phobson/matplotlib/tree/MEP28-initial-deprecations

  2. 弃用冗余样式选项Axes.boxplothttps ://github.com/phobson/matplotlib/tree/MEP28-initial-deprecations

  3. 不推荐将 2D NumPy 数组作为输入传递:无

  4. 将预处理和后处理选项添加到cbook.boxplot_statshttps ://github.com/phobson/matplotlib/tree/boxplot-stat-transforms

  5. cbook.boxplot_stats通过Axes.boxplotkwargs暴露:无

  6. 删除多余的统计 kwargs Axes.boxplot:无

  7. 删除多余的样式选项Axes.boxplot:无

  8. 通过讨论产生的剩余项目:无

摘要#

在过去的几个版本中,该Axes.boxplot方法变得越来越复杂,以支持完全可定制的艺术家样式和统计计算。这导致Axes.boxplot被分成多个部分。绘制箱线图所需的统计数据是在 中计算的 cbook.boxplot_stats,而实际的艺术家是在 中绘制的Axes.bxp。原始方法Axes.boxplot仍然是最公开的 API,用于处理将用户提供的数据传递给cbook.boxplot_stats、将结果提供给Axes.bxp以及预处理箱线图每个方面的样式信息。

该 MEP 将概述一条前进的道路,以回滚增加的复杂性并简化 API,同时保持合理的向后兼容性。

详细说明#

目前,该Axes.boxplot方法接受允许用户为将在图中绘制的每个框指​​定中值和置信区间的参数。提供这些是为了让高级用户可以提供以与 matplotlib 提供的简单方法不同的方式计算的统计数据。然而,处理这个输入需要复杂的逻辑来确保数据结构的形式与需要绘制的内容相匹配。目前,该逻辑包含 9 个单独的 if/else 语句,嵌套最多 5 层,带有一个 for 循环,最多可能引发 2 个错误。这些参数是在创建方法之前添加的,该Axes.bxp方法从包含相关统计数据的字典列表中绘制箱线图。Matplotlib 还提供了一个计算这些统计数据的函数cbook.boxplot_stats. 请注意,高级用户现在可以 a) 编写自己的函数来计算 所需的统计数据 Axes.bxp,或 b) 修改返回的输出cbook.boxplots_stats 以完全自定义绘图艺术家的位置。凭借这种灵活性,仅手动指定中位数及其置信区间的参数保持向后兼容性。

大约在同一时间,这两个角色Axes.boxplot被分为 cbook.boxplot_stats计算和Axes.bxp绘图,两者都 Axes.boxplotAxes.bxp编写为接受单独切换箱线图所有组件的绘图的参数,以及单独配置这些艺术家风格的参数。但是,为了保持向后兼容性,保留了该sym参数(以前用于指定传单的符号)。此参数本身需要相当复杂的逻辑来将sym参数与新flierprops参数以matplotlibrc.

该 MEP 旨在为新手和高级用户等显着简化箱线图的创建。重要的是,这里提出的更改也将适用于 seaborn 等下游包,因为 seaborn 巧妙地允许用户通过 seaborn API 将任意参数字典传递给底层的 matplotlib 函数。

这将通过以下方式实现:

  1. cbook.boxplot_stats将被修改以允许传入计算前和计算后的转换函数(例如,np.log 对于np.exp对数正态分布的数据)

  2. Axes.boxplot将被修改为也接受并天真地将它们传递给cbook.boxplots_stats(Alt:传递 stat 函数及其可选参数的字典)。

  3. 过时的参数 fromAxes.boxplot将被弃用,稍后将被删除。

重要性#

由于晶须的限制是用算术计算的,因此在箱形和晶须图中存在一个隐含的正态性假设。这主要影响哪些数据点被归类为异常值。

如果已知数据不符合正态分布,则允许对数据和用于绘制箱线图的结果进行转换将允许用户选择退出该假设。

下面是一个示例,说明如何Axes.boxplot根据这些类型的转换对对数正态数据的异常值进行不同的分类。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cbook
np.random.seed(0)

fig, ax = plt.subplots(figsize=(4, 6))
ax.set_yscale('log')
data = np.random.lognormal(-1.75, 2.75, size=37)

stats = cbook.boxplot_stats(data, labels=['arithmetic'])
logstats = cbook.boxplot_stats(np.log(data), labels=['log-transformed'])

for lsdict in logstats:
    for key, value in lsdict.items():
        if key != 'label':
            lsdict[key] = np.exp(value)

stats.extend(logstats)
ax.bxp(stats)
fig.show()

源代码png

../../_images/MEP28-1.png

实施#

将变换函数传递给cbook.boxplots_stats#

该 MEP 建议将两个参数(例如,transform_in添加 transform_out到计算 boxplot 函数统计数据的食谱函数中。这些将是可选的仅关键字参数,并且可以在用户省略时轻松设置为无操作。当函数循环传递给它的每个数据子集时, 该函数将应用于数据。在计算统计字典列表后,该函数将应用于字典中的每个值。lambda x: xtransform_inboxplot_statstransform_out

然后可以将这些转换添加到调用签名中 Axes.boxplot,而对该方法的复杂性几乎没有影响。这是因为它们可以直接传递给cbook.boxplot_stats. 或者,Axes.boxplot可以修改为接受可选的统计函数 kwarg 和要直接传递给它的参数字典。

此时在实现中,用户和 seaborn 等外部库将通过该Axes.boxplot方法完全控制。更重要的是,至少,seaborn 不需要更改其 API 以允许用户利用这些新选项。

Axes.boxplotAPI 和其他功能的简化#

简化箱线图方法主要包括弃用然后删除冗余参数。或者,下一步将包括纠正 和 之间的细微术语Axes.boxplot 不一致Axes.bxp

要弃用和删除的参数包括:

  1. usermedians- 由 10 个 SLOC、3if个块、一个for循环处理

  2. conf_intervals- 由 15 个 SLOC、6if个块、一个for循环处理

  3. sym- 由 12 个 SLOC 处理,4if个块

删除该sym选项允许将处理剩余样式参数的所有代码移至Axes.bxp. 这并没有消除任何复杂性,但确实加强了 、 和 之间的单一Axes.bxp责任cbook.boxplot_stats原则Axes.boxplot

此外,notch参数可以重命名shownotches 为与Axes.bxp. 这种清理可以更进一步,whis, bootstrap,autorange可以滚动到传递给新statfxn参数的 kwargs 中。

向后兼容性#

此 MEP 的实施最终将导致向后不兼容的弃用,然后删除关键字参数 usermediansconf_intervalssym。在 GitHub 上的粗略搜索表明usermedians,conf_intervals被少数用户使用,他们似乎都对 matplotlib 有非常深入的了解。一个强大的弃用周期应该为这些用户提供足够的时间来迁移到新的 API。

然而,弃用sym可能对 matplotlib 用户群有更广泛的影响。

时间表#

加速时间线可能如下所示:

  1. v2.0.1 添加转换cbook.boxplots_stats,暴露在Axes.boxplot

  2. v2.1.0 Initial Deprecations ,并使用 2D NumPy 数组作为输入

    1. 使用 2D NumPy 数组作为输入。二维数组的语义通常令人困惑。

    2. usermedians, conf_intervals,sym参数

  3. v2.2.0

    1. 删除usermedians, conf_intervals,sym参数

    2. 弃用notch赞成shownotches与其他参数和Axes.bxp

  4. v2.3.0
    1. 移除notch参数

    2. 将所有风格和艺术家切换逻辑都转移到Axes.bxp这样Axes.boxplot 的地方,只不过是和之间的Axes.bxp经纪人cbook.boxplots_stats

对用户的预期影响#

如上所述,弃用usermedians并且conf_intervals 可能会影响少数用户。那些将受到影响的人几乎可以肯定是能够适应变化的高级用户。

弃用该sym选项可能会引入更多用户,因此应努力收集社区对此的反馈。

对下游图书馆的预期影响#

检查了源代码(截至 2016 年 10 月 17 日的 GitHub 主文件)的 seaborn 和 python-ggplot 以查看这些更改是否会影响它们的使用。seaborn 没有使用本 MEP 中提名删除的参数。使用 matplotlib 的 boxplot 函数的 seaborn API 允许用户任意**kwargs传递到 matplotlib 的 API。因此,安装了现代 matplotlib 的 seaborn 用户将能够充分利用由于此 MEP 添加的任何新功能。

Python-ggplot 已经实现了自己的功能来绘制箱线图。因此,实施本 MEP 不会对其产生任何影响。

替代方案#

主题变化#

这个 MEP 可以分为几个松散耦合的组件:

  1. 允许在计算前和计算后转换函数cbook.boxplot_stats

  2. Axes.boxplot在API中公开该转换

  3. 删除多余的统计选项Axes.boxplot

  4. 将所有样式参数处理从 转移Axes.boxplotAxes.bxp.

使用这种方法,#2 依赖于#1,#4 依赖于#3。

#2 有两种可能的方法。第一个也是最直接的方法是镜像in的新参数transform_intransform_out参数 并直接传递它们。cbook.boxplot_statsAxes.boxplot

第二种方法是将statfxnstatfxn_args 参数添加到Axes.boxplot. 在此实现下,默认值为,但用户statfxn可以cbook.boxplot_stats传递自己的函数。然后transform_intransform_out然后将作为statfxn_args参数的元素传递。

def boxplot_stats(data, ..., transform_in=None, transform_out=None):
    if transform_in is None:
        transform_in = lambda x: x

    if transform_out is None:
        transform_out = lambda x: x

    output = []
    for _d in data:
        d = transform_in(_d)
        stat_dict = do_stats(d)
        for key, value in stat_dict.item():
            if key != 'label':
                stat_dict[key] = transform_out(value)
        output.append(d)
    return output


 class Axes(...):
     def boxplot_option1(data, ..., transform_in=None, transform_out=None):
         stats = cbook.boxplot_stats(data, ...,
                                     transform_in=transform_in,
                                     transform_out=transform_out)
         return self.bxp(stats, ...)

     def boxplot_option2(data, ..., statfxn=None, **statopts):
         if statfxn is None:
             statfxn = boxplot_stats
         stats = statfxn(data, **statopts)
         return self.bxp(stats, ...)

这两种情况都允许用户执行以下操作:

fig, ax1 = plt.subplots()
artists1 = ax1.boxplot_optionX(data, transform_in=np.log,
                               transform_out=np.exp)

但是选项二允许用户编写一个完全自定义的统计函数(例如my_box_stats

这在当前 API 下可用:

fig, ax1 = plt.subplots()
my_stats = my_box_stats(data, bootstrap_method='BCA',
                        whisker_method='dynamic')
ax1.bxp(my_stats)

并且使用选项二会更简洁

fig, ax = plt.subplots()
statopts = dict(transform_in=np.log, transform_out=np.exp)
ax.boxplot(data, ..., **statopts)

用户还可以传递他们自己的函数来计算统计数据:

fig, ax1 = plt.subplots()
ax1.boxplot(data, statfxn=my_box_stats, bootstrap_method='BCA',
            whisker_method='dynamic')

从上面的例子来看,选项二似乎只有边际收益,但在像 seaborn 这样的下游库的上下文中,它的优势更加明显,因为在没有任何 seaborn 补丁的情况下,以下是可能的:

import seaborn
tips = seaborn.load_data('tips')
g = seaborn.factorplot(x="day", y="total_bill", hue="sex", data=tips,
                       kind='box', palette="PRGn", shownotches=True,
                       statfxn=my_box_stats, bootstrap_method='BCA',
                       whisker_method='dynamic')

这种类型的灵活性是将整个 boxplot API 拆分为当前三个函数的意图。然而,在实践中,像 seaborn 这样的下游库支持的 matplotlib 版本可以追溯到分裂之前。因此,只需增加一点灵活性,就 Axes.boxplot可以通过现代 matplotlib 安装向下游库的用户公开所有功能,而无需下游库维护者的干预。

少做事#

另一个明显的替代方案是省略 和 中添加的计算前和计算后转换功能,cbook.boxplot_statsAxes.boxplot简单地删除如上所述的冗余统计和样式参数。

什么都不做#

与生活中的许多事情一样,在这里什么都不做是一种选择。这意味着我们只是提倡用户和下游库利用它们之间的分离cbook.boxplot_statsAxes.bxp并让他们决定如何提供接口。