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

状态#

讨论

分支和 Pull requests#

以下列出了与此 MEP 相关的任何未完成的 PR 或分支:

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

  2. 弃用 Axes.boxplot 中冗余的样式选项:phobson/matplotlib

  3. 弃用将 2D NumPy 数组作为输入传递:None

  4. cbook.boxplot_stats 添加预处理和后处理选项:phobson/matplotlib

  5. 通过 Axes.boxplot kwargs 暴露 cbook.boxplot_stats :None

  6. 移除 Axes.boxplot 中冗余的统计 kwargs:None

  7. 移除 Axes.boxplot 中冗余的样式选项:None

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

摘要#

在过去的几个版本中, 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 参数与由 matplotlibrc 指定的默认样式的较新 flierprops 参数.

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

这将通过以下方式实现:

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

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

  3. Axes.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()

(Source code, png)

实施#

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

此MEP建议将两个参数(例如, transform_intransform_out )添加到计算箱线图函数统计数据的cookbook函数中.这些将是可选的仅关键字参数,并且在用户省略时可以轻松设置为 lambda x: x 作为空操作.当 boxplot_stats 函数循环遍历传递给它的每个数据子集时, transform_in 函数将应用于数据.在计算出统计字典列表后, transform_out 函数将应用于字典中的每个值.

这些转换可以添加到 Axes.boxplot 的调用签名中,而对其方法的复杂性影响很小.这是因为它们可以直接传递给 cbook.boxplot_stats .或者,可以修改 Axes.boxplot 以接受一个统计函数的可选关键字参数,以及一个直接传递给它的参数字典.

在实现的这个阶段,用户和像 seaborn 这样的外部库将通过 Axes.boxplot 方法拥有完全的控制权.更重要的是,至少 seaborn 不需要更改其 API,就可以允许用户利用这些新选项.

简化 Axes.boxplot API 和其他函数#

简化箱线图方法主要包括弃用和删除冗余参数.可选地,下一步将包括纠正 Axes.boxplotAxes.bxp 之间的一些术语不一致.

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

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

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

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

删除 sym 选项允许将处理剩余样式参数的所有代码移动到 Axes.bxp .这不会消除任何复杂性,但会加强 Axes.bxp , cbook.boxplot_statsAxes.boxplot 之间的单一职责原则.

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

向后兼容性#

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

然而,弃用 sym 可能会对 matplotlib 用户群产生更广泛的影响.

时间表#

加速的时间表可能如下所示:

  1. v2.0.1 将转换添加到 cbook.boxplots_stats ,在 Axes.boxplot 中公开

  2. v2.1.0 初始弃用,并使用 2D NumPy 数组作为输入

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

    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.bxpcbook.boxplots_stats 之间的代理

预期对用户的影响#

如上所述,弃用 usermediansconf_intervals 可能会影响很少的用户.那些将受到影响的人几乎肯定是高级用户,他们将能够适应这种变化.

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

预期对下游库的影响#

检查了 seaborn 和 python-ggplot 的源代码(截至 2016-10-17 的 GitHub master),以查看这些更改是否会影响它们的使用.此 MEP 中提名的要删除的参数均未被 seaborn 使用.使用 matplotlib 箱线图函数的 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.boxplot 转移到 Axes.bxp .

通过这种方法,#2 依赖于 #1,而 #4 依赖于 #3.

对于 #2,有两种可能的方法.第一种也是最直接的方法是在 Axes.boxplot 中镜像 cbook.boxplot_stats 的新 transform_intransform_out 参数,并直接传递它们.

第二种方法是将 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 ),其中包含花哨的 BCA 置信区间,并且须线根据数据的某些属性以不同的方式设置.

这在当前的 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')

这种类型的灵活性是当前三个函数中拆分整体箱线图 API 背后的意图.然而,在实践中,诸如 seaborn 之类的下游库支持早于拆分的 matplotlib 版本.因此,仅向 Axes.boxplot 添加更多的灵活性就可以将所有功能公开给具有现代 matplotlib 安装的下游库用户,而无需下游库维护人员的干预.

少做#

另一种显而易见的替代方法是省略在 cbook.boxplot_statsAxes.boxplot 中添加的预计算和后计算转换功能,而只是按照上述描述删除冗余的统计和样式参数.

什么都不做#

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