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:
Descontinuar kwargs estatísticos redundantes em
Axes.boxplot
: https://github.com/phobson/matplotlib/tree/MEP28-initial-deprecationsDescontinuar as opções de estilo redundante em
Axes.boxplot
: https://github.com/phobson/matplotlib/tree/MEP28-initial-deprecationsReprovar a passagem de arrays 2D NumPy como entrada: Nenhum
Adicione opções de pré e pós-processamento a
cbook.boxplot_stats
: https://github.com/phobson/matplotlib/tree/boxplot-stat-transformsExpondo
cbook.boxplot_stats
através deAxes.boxplot
kwargs: NenhumRemova kwargs estatísticos redundantes em
Axes.boxplot
: NenhumRemova as opções de estilo redundante em
Axes.boxplot
: NenhumItens restantes que surgem através da discussão: Nenhum
Resumo #
Nos últimos lançamentos, o Axes.boxplot
método cresceu em complexidade para suportar o estilo de artista totalmente personalizável e a computação estatística. Isso levou a Axes.boxplot
ser 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.boxplot
permanece 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.boxplot
mé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.bxp
mé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.boxplot
foram divididas em
cbook.boxplot_stats
computaçã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.boxplot
Axes.bxp
sym
sym
flierprops
matplotlibrc
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:
cbook.boxplot_stats
será modificado para permitir que as funções de transformação pré e pós-computação sejam passadas (por exemplo,np.log
enp.exp
para dados distribuídos lognormalmente)
Axes.boxplot
serão modificados para também aceitá-los e passá-los ingenuamente paracbook.boxplots_stats
(Alt: passar a função stat e um dict de seus parâmetros opcionais).Parâmetros desatualizados de
Axes.boxplot
serã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.boxplot
classifica 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 )
Implementação #
Passando funções de transformação para cbook.boxplots_stats
#
Este MEP propõe que dois parâmetros (por exemplo, transform_in
e
transform_out
sejam 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: x
transform_in
boxplot_stats
transform_out
Essas transformações podem ser adicionadas à assinatura de chamada
Axes.boxplot
com pouco impacto na complexidade desse método. Isso ocorre porque eles podem ser passados diretamente para arquivos cbook.boxplot_stats
. Alternativamente, Axes.boxplot
pode 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.boxplot
mé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.boxplot
API 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:
usermedians
- processado por 10 SLOC, 3if
blocos, umfor
loop
conf_intervals
- manipulado por 15 SLOC, 6if
blocos, umfor
loop
sym
- processado por 12 SLOC, 4if
blocos
A remoção da sym
opçã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_stats
e Axes.boxplot
.
Além disso, o notch
parâ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
, autorange
poderia ser inserido nos kwargs passados para o novo statfxn
parâ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_intervals
e sym
. Pesquisas superficiais no GitHub indicaram que usermedians
, conf_intervals
sã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:
v2.0.1 adiciona transformações a
cbook.boxplots_stats
, expõe emAxes.boxplot
v2.1.0 Deprecações iniciais e usando matrizes 2D NumPy como entrada
Usando matrizes NumPy 2D como entrada. A semântica em torno de matrizes 2D geralmente é confusa.
usermedians
,conf_intervals
,sym
parâmetros
v2.2.0
remover
usermedians
,conf_intervals
,sym
parâmetrosdepreciar
notch
em favor deshownotches
ser consistente com outros parâmetros eAxes.bxp
- v2.3.0
remover
notch
parâmetromover toda a lógica de alternância de estilo e artista para
Axes.bxp
talAxes.boxplot
é pouco mais do que um intermediário entreAxes.bxp
ecbook.boxplots_stats
Impactos Antecipados aos Usuários #
Conforme descrito acima, obsoleto usermedians
e 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 sym
opçã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 **kwargs
para 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:
Permitindo a função de transformação pré e pós-computação em
cbook.boxplot_stats
Expor essa transformação na
Axes.boxplot
APIRemovendo opções estatísticas redundantes em
Axes.boxplot
Mudando todo o processamento de parâmetros de estilo de
Axes.boxplot
paraAxes.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_in
e de in e passá-los diretamente.transform_out
cbook.boxplot_stats
Axes.boxplot
A segunda abordagem seria adicionar statfxn
e statfxn_args
parâmetros a arquivos Axes.boxplot
. Nessa implementação, o valor padrão de statfxn
seria cbook.boxplot_stats
, mas os usuários poderiam passar sua própria função. Then transform_in
e transform_out
seriam passados como elementos do statfxn_args
parâ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.boxplot
poderia 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_stats
e
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_stats
e Axes.bxp
deixemos que eles decidam como fornecer uma interface para isso.