MEP28: Remover Complexidade de Axes.boxplot #

Estado #

Discussão

Filiais e solicitações pull #

A seguir, listamos quaisquer PRs ou filiais abertas relacionadas a este MEP:

  1. Descontinuar kwargs estatísticos redundantes em Axes.boxplot: https://github.com/phobson/matplotlib/tree/MEP28-initial-deprecations

  2. Descontinuar as opções de estilo redundante em Axes.boxplot: https://github.com/phobson/matplotlib/tree/MEP28-initial-deprecations

  3. Reprovar a passagem de arrays 2D NumPy como entrada: Nenhum

  4. Adicione opções de pré e pós-processamento a cbook.boxplot_stats: https://github.com/phobson/matplotlib/tree/boxplot-stat-transforms

  5. Expondo cbook.boxplot_statsatravés de Axes.boxplotkwargs: Nenhum

  6. Remova kwargs estatísticos redundantes em Axes.boxplot: Nenhum

  7. Remova as opções de estilo redundante em Axes.boxplot: Nenhum

  8. Itens restantes que surgem através da discussão: Nenhum

Resumo #

Nos últimos lançamentos, o Axes.boxplotmétodo cresceu em complexidade para suportar o estilo de artista totalmente personalizável e a computação estatística. Isso levou a Axes.boxplotser dividido em várias partes. As estatísticas necessárias para desenhar um boxplot são computadas em cbook.boxplot_stats, enquanto os artistas reais são desenhados em Axes.bxp. O método original Axes.boxplotpermanece como a API mais pública que lida com a passagem dos dados fornecidos pelo usuário para cbook.boxplot_stats, alimentando os resultados para Axes.bxp, e pré-processando informações de estilo para cada faceta dos gráficos boxplot.

Este MEP delineará um caminho a seguir para reverter a complexidade adicionada e simplificar a API, mantendo compatibilidade razoável com versões anteriores.

Descrição detalhada #

Atualmente, o Axes.boxplotmétodo aceita parâmetros que permitem ao usuário especificar medianas e intervalos de confiança para cada caixa que será desenhada no gráfico. Eles foram fornecidos para que usuários avançados pudessem fornecer estatísticas computadas de uma maneira diferente do método simples fornecido pelo matplotlib. No entanto, lidar com essa entrada requer uma lógica complexa para garantir que as formas da estrutura de dados correspondam ao que precisa ser desenhado. No momento, essa lógica contém 9 instruções if/else separadas aninhadas em até 5 níveis de profundidade com um loop for e pode gerar até 2 erros. Esses parâmetros foram adicionados antes da criação do Axes.bxpmétodo, que desenha boxplots de uma lista de dicionários contendo as estatísticas relevantes. Matplotlib também fornece uma função que calcula essas estatísticas por meio decbook.boxplot_stats. Observe que os usuários avançados agora podem a) escrever sua própria função para calcular as estatísticas exigidas por Axes.bxp, ou b) modificar a saída retornada por cbook.boxplots_stats para personalizar totalmente a posição dos artistas das plotagens. Com essa flexibilidade, os parâmetros para especificar manualmente apenas as medianas e seus intervalos de confiança permanecem para compatibilidade com versões anteriores.

Mais ou menos na mesma época em que as duas funções de Axes.boxplotforam divididas em cbook.boxplot_statscomputação e desenho Axes.bxp, ambas foram escritas para aceitar parâmetros que alternam individualmente o desenho de todos os componentes dos boxplots e parâmetros que configuram individualmente o estilo desses artistas. No entanto, para manter a compatibilidade com versões anteriores, o parâmetro (anteriormente usado para especificar o símbolo dos panfletos) foi mantido. Este parâmetro em si requer uma lógica bastante complexa para reconciliar os parâmetros com o parâmetro mais recente no estilo padrão especificado por .Axes.boxplotAxes.bxpsymsymflierpropsmatplotlibrc

Este MEP procura simplificar drasticamente a criação de boxplots para usuários novatos e avançados. É importante ressaltar que as alterações propostas aqui também estarão disponíveis para pacotes downstream como o seaborn, pois o seaborn permite que os usuários passem dicionários arbitrários de parâmetros através da API do seaborn para as funções subjacentes do matplotlib.

Isso será conseguido da seguinte forma:

  1. cbook.boxplot_statsserá modificado para permitir que as funções de transformação pré e pós-computação sejam passadas (por exemplo, np.log e np.exppara dados distribuídos lognormalmente)

  2. Axes.boxplotserão modificados para também aceitá-los e passá-los ingenuamente para cbook.boxplots_stats(Alt: passar a função stat e um dict de seus parâmetros opcionais).

  3. Parâmetros desatualizados de Axes.boxplotserão obsoletos e posteriormente removidos.

Importância #

Como os limites dos bigodes são calculados aritmeticamente, há uma suposição implícita de normalidade nos gráficos de caixas e bigodes. Isso afeta principalmente quais pontos de dados são classificados como outliers.

Permitir transformações nos dados e nos resultados usados ​​para desenhar boxplots permitirá que os usuários optem por não aceitar essa suposição se os dados forem conhecidos por não se ajustarem a uma distribuição normal.

Abaixo está um exemplo de como Axes.boxplotclassifica outliers de dados lognormal de forma diferente, dependendo de um desses tipos de transformações.

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()

( Código fonte , png )

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

Implementação #

Passando funções de transformação para cbook.boxplots_stats#

Este MEP propõe que dois parâmetros (por exemplo, transform_ine transform_outsejam adicionados à função cookbook que calcula as estatísticas para a função boxplot. Esses serão argumentos opcionais apenas com palavras-chave e podem ser facilmente definidos como não operacionais quando omitidos pelo usuário. A função será aplicada aos dados à medida que a função percorre cada subconjunto dos dados passados ​​para ela. Depois que a lista de dicionários estatísticos é calculada, a função é aplicada a cada valor nos dicionários.lambda x: xtransform_inboxplot_statstransform_out

Essas transformações podem ser adicionadas à assinatura de chamada Axes.boxplotcom pouco impacto na complexidade desse método. Isso ocorre porque eles podem ser passados ​​diretamente para arquivos cbook.boxplot_stats. Alternativamente, Axes.boxplotpode ser modificado para aceitar uma função estatística opcional kwarg e um dicionário de parâmetros a serem passados ​​diretamente para ele.

Neste ponto da implementação, usuários e bibliotecas externas, como seaborn, teriam controle total por meio do Axes.boxplotmétodo. Mais importante, no mínimo, a seaborn não exigiria alterações em sua API para permitir que os usuários tirassem proveito dessas novas opções.

Simplificações para a Axes.boxplotAPI e outras funções #

A simplificação do método boxplot consiste principalmente em depreciar e, em seguida, remover os parâmetros redundantes. Opcionalmente, uma próxima etapa incluiria corrigir pequenas inconsistências terminológicas entre Axes.boxplot e Axes.bxp.

Os parâmetros a serem obsoletos e removidos incluem:

  1. usermedians- processado por 10 SLOC, 3 ifblocos, um forloop

  2. conf_intervals- manipulado por 15 SLOC, 6 ifblocos, um forloop

  3. sym- processado por 12 SLOC, 4 ifblocos

A remoção da symopção permite que todo o código na manipulação dos parâmetros de estilo restantes seja movido para Axes.bxp. Isso não remove nenhuma complexidade, mas reforça o princípio de responsabilidade única entre Axes.bxp, cbook.boxplot_statse Axes.boxplot.

Além disso, o notchparâmetro pode ser renomeado shownotches para ficar consistente com Axes.bxp. Esse tipo de limpeza poderia ser levado um passo adiante e o whis, bootstrap, autorangepoderia ser inserido nos kwargs passados ​​para o novo statfxnparâmetro.

Compatibilidade com versões anteriores #

A implementação desse MEP resultaria eventualmente na descontinuação incompatível com versões anteriores e, em seguida, na remoção dos parâmetros de palavra-chave usermedians, conf_intervalse sym. Pesquisas superficiais no GitHub indicaram que usermedians, conf_intervalssão usados ​​por poucos usuários, que parecem ter um conhecimento muito forte de matplotlib. Um ciclo de descontinuação robusto deve fornecer tempo suficiente para que esses usuários migrem para uma nova API.

A depreciação de sym, no entanto, pode ter um alcance muito mais amplo na base de usuários do matplotlib.

Cronograma #

Uma linha do tempo acelerada pode ter a seguinte aparência:

  1. v2.0.1 adiciona transformações a cbook.boxplots_stats, expõe emAxes.boxplot

  2. v2.1.0 Deprecações iniciais e usando matrizes 2D NumPy como entrada

    1. Usando matrizes NumPy 2D como entrada. A semântica em torno de matrizes 2D geralmente é confusa.

    2. usermedians, conf_intervals, symparâmetros

  3. v2.2.0

    1. remover usermedians, conf_intervals, symparâmetros

    2. depreciar notchem favor de shownotchesser consistente com outros parâmetros eAxes.bxp

  4. v2.3.0
    1. remover notchparâmetro

    2. mover toda a lógica de alternância de estilo e artista para Axes.bxptal Axes.boxplot é pouco mais do que um intermediário entre Axes.bxpecbook.boxplots_stats

Impactos Antecipados aos Usuários #

Conforme descrito acima, obsoleto usermedianse conf_intervals provavelmente afetará alguns usuários. Aqueles que serão afetados quase certamente são usuários avançados que poderão se adaptar à mudança.

Descontinuar a symopção pode importar mais usuários e esforços devem ser feitos para coletar feedback da comunidade sobre isso.

Impactos Antecipados nas Bibliotecas Downstream #

O código-fonte (mestre do GitHub a partir de 17/10/2016) foi inspecionado para seaborn e python-ggplot para ver se essas alterações afetariam seu uso. Nenhum dos parâmetros indicados para remoção neste MEP são utilizados por seaborn. As APIs marítimas que usam a função boxplot do matplotlib permitem que o usuário passe de forma arbitrária **kwargspara a API do matplotlib. Assim, os usuários marítimos com instalações matplotlib modernas poderão aproveitar todas as vantagens de quaisquer novos recursos adicionados como resultado deste MEP.

O Python-ggplot implementou sua própria função para desenhar boxplots. Portanto, nenhum impacto pode advir da implementação deste MEP.

Alternativas #

Variações sobre o tema #

Este MEP pode ser dividido em alguns componentes fracamente acoplados:

  1. Permitindo a função de transformação pré e pós-computação emcbook.boxplot_stats

  2. Expor essa transformação na Axes.boxplotAPI

  3. Removendo opções estatísticas redundantes emAxes.boxplot

  4. Mudando todo o processamento de parâmetros de estilo de Axes.boxplotpara Axes.bxp.

Com essa abordagem, o número 2 depende do número 1 e o número 4 depende do número 3.

Existem duas abordagens possíveis para #2. A primeira e mais direta seria espelhar os parâmetros new transform_ine de in e passá-los diretamente.transform_outcbook.boxplot_statsAxes.boxplot

A segunda abordagem seria adicionar statfxne statfxn_args parâmetros a arquivos Axes.boxplot. Nessa implementação, o valor padrão de statfxnseria cbook.boxplot_stats, mas os usuários poderiam passar sua própria função. Then transform_ine transform_outseriam passados ​​como elementos do statfxn_argsparâmetro.

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, ...)

Ambos os casos permitiriam que os usuários fizessem o seguinte:

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

Mas a Opção Dois permite que um usuário escreva uma função estatística completamente personalizada (por exemplo, my_box_stats) com intervalos de confiança BCA sofisticados e os bigodes definidos de forma diferente, dependendo de algum atributo dos dados.

Isso está disponível na API atual:

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

E seria mais conciso com a Opção Dois

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

Os usuários também podem passar sua própria função para calcular as estatísticas:

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

Dos exemplos acima, a Opção Dois parece ter apenas um benefício marginal, mas no contexto de bibliotecas downstream como seaborn, sua vantagem é mais aparente, pois o seguinte seria possível sem nenhum patch para 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')

Esse tipo de flexibilidade foi a intenção por trás da divisão da API boxplot geral nas três funções atuais. Na prática, no entanto, bibliotecas downstream, como seaborn, suportam versões de matplotlib que datam bem antes da divisão. Portanto, adicionar um pouco mais de flexibilidade ao Axes.boxplotpoderia expor toda a funcionalidade aos usuários das bibliotecas downstream com instalação moderna do matplotlib sem intervenção dos mantenedores da biblioteca downstream.

Fazendo menos #

Outra alternativa óbvia seria omitir a funcionalidade de transformação pré e pós-computação adicionada em cbook.boxplot_statse Axes.boxplot, e simplesmente remover os parâmetros estatísticos e de estilo redundantes conforme descrito acima.

Não fazer nada #

Como em muitas coisas na vida, não fazer nada é uma opção aqui. Isso significa que simplesmente defendemos que os usuários e as bibliotecas downstream aproveitem a divisão cbook.boxplot_statse Axes.bxpdeixemos que eles decidam como fornecer uma interface para isso.