MEP28: Rimuovi la complessità da Axes.boxplot #

Stato n.

Discussione

Filiali e Pull request #

Di seguito sono elencati eventuali PR o filiali aperti relativi a questo deputato europeo:

  1. Depreca i kwarg statistici ridondanti in Axes.boxplot: https://github.com/phobson/matplotlib/tree/MEP28-initial-deprecations

  2. Depreca le opzioni di stile ridondanti in Axes.boxplot: https://github.com/phobson/matplotlib/tree/MEP28-initial-deprecations

  3. Deprecare il passaggio degli array NumPy 2D come input: Nessuno

  4. Aggiungi opzioni di pre e post-elaborazione a cbook.boxplot_stats: https://github.com/phobson/matplotlib/tree/boxplot-stat-transforms

  5. Esposizione cbook.boxplot_statstramite Axes.boxplotkwargs: nessuna

  6. Rimuovi i kwarg statistici ridondanti in Axes.boxplot: Nessuno

  7. Rimuovi le opzioni di stile ridondanti in Axes.boxplot: Nessuno

  8. Elementi rimanenti emersi dalla discussione: nessuno

Estratto n.

Nelle ultime versioni, il Axes.boxplotmetodo è cresciuto in complessità per supportare lo stile dell'artista e il calcolo statistico completamente personalizzabili. Questo ha portato ad Axes.boxplotessere diviso in più parti. Le statistiche necessarie per disegnare un boxplot sono calcolate in cbook.boxplot_stats, mentre gli artisti effettivi sono disegnati da Axes.bxp. Il metodo originale Axes.boxplotrimane l'API più pubblica che gestisce il passaggio dei dati forniti dall'utente a cbook.boxplot_stats, l'alimentazione dei risultati a Axes.bxpe le informazioni sullo stile di pre-elaborazione per ogni aspetto dei grafici a scatola.

Questo deputato delineerà un percorso in avanti per ripristinare la complessità aggiunta e semplificare l'API mantenendo una ragionevole compatibilità con le versioni precedenti.

Descrizione dettagliata #

Attualmente, il Axes.boxplotmetodo accetta parametri che consentono agli utenti di specificare mediane e intervalli di confidenza per ogni riquadro che verrà disegnato nel grafico. Questi sono stati forniti in modo che gli utenti avanzati potessero fornire statistiche calcolate in modo diverso rispetto al semplice metodo fornito da matplotlib. Tuttavia, la gestione di questo input richiede una logica complessa per garantire che le forme della struttura dei dati corrispondano a ciò che deve essere disegnato. Al momento, quella logica contiene 9 istruzioni if/else separate annidate fino a 5 livelli di profondità con un ciclo for e può generare fino a 2 errori. Questi parametri sono stati aggiunti prima della creazione del Axes.bxpmetodo, che disegna grafici a scatole da un elenco di dizionari contenenti le statistiche pertinenti. Matplotlib fornisce anche una funzione che calcola queste statistiche tramitecbook.boxplot_stats. Si noti che gli utenti avanzati possono ora a) scrivere la propria funzione per calcolare le statistiche richieste da Axes.bxpoppure b) modificare l'output restituito da cbook.boxplots_stats per personalizzare completamente la posizione degli artisti delle trame. Con questa flessibilità, i parametri per specificare manualmente solo le mediane e i relativi intervalli di confidenza rimangono compatibili con le versioni precedenti.

Più o meno nello stesso periodo in cui i due ruoli di Axes.boxplotsono stati divisi in cbook.boxplot_statsper il calcolo e Axes.bxpper il disegno, entrambi Axes.boxplotsono Axes.bxpstati scritti per accettare parametri che alternano individualmente il disegno di tutti i componenti dei boxplot e parametri che configurano individualmente lo stile di quegli artisti. Tuttavia, per mantenere la retrocompatibilità, il symparametro (precedentemente utilizzato per specificare il simbolo dei volantini) è stato mantenuto. Questo parametro stesso richiede una logica piuttosto complessa per riconciliare i symparametri con il flierpropsparametro più recente nello stile predefinito specificato da matplotlibrc.

Questo MEP cerca di semplificare notevolmente la creazione di boxplot sia per i principianti che per gli utenti avanzati. È importante sottolineare che le modifiche qui proposte saranno disponibili anche per i pacchetti downstream come seaborn, poiché seaborn consente in modo intelligente agli utenti di passare dizionari arbitrari di parametri attraverso l'API seaborn alle funzioni matplotlib sottostanti.

Ciò sarà ottenuto nel seguente modo:

  1. cbook.boxplot_statsverrà modificato per consentire il passaggio di funzioni di trasformazione pre e post calcolo (ad esempio, np.log e np.expper dati distribuiti in modo lognormale)

  2. Axes.boxplotsarà modificato per accettarli e passarli ingenuamente a cbook.boxplots_stats(Alt: passa la funzione stat e un dict dei suoi parametri opzionali).

  3. I parametri obsoleti di Axes.boxplotverranno deprecati e successivamente rimossi.

Importanza #

Poiché i limiti dei baffi sono calcolati aritmeticamente, c'è un presupposto implicito di normalità nei grafici a scatola e baffi. Ciò influisce principalmente su quali punti dati sono classificati come valori anomali.

Consentire le trasformazioni ai dati e ai risultati utilizzati per disegnare grafici a scatole consentirà agli utenti di rinunciare a tale ipotesi se è noto che i dati non si adattano a una distribuzione normale.

Di seguito è riportato un esempio di come Axes.boxplotclassifica i valori anomali dei dati lognormali in modo diverso a seconda di uno di questi tipi di trasformazioni.

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

( Codice sorgente , png )

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

Implementazione n.

Passaggio delle funzioni di trasformazione a cbook.boxplots_stats#

Questo deputato propone che due parametri (ad esempio, transform_ine transform_outvengano aggiunti alla funzione ricettario che calcola le statistiche per la funzione boxplot. Questi saranno argomenti facoltativi solo per parole chiave e possono essere facilmente impostati come no-op se omessi dall'utente. La funzione verrà applicata ai dati mentre la funzione scorre attraverso ogni sottoinsieme dei dati passati ad esso.Dopo che l'elenco dei dizionari statistici è stato calcolato, la funzione viene applicata a ciascun valore nei dizionari.lambda x: xtransform_inboxplot_statstransform_out

Queste trasformazioni possono quindi essere aggiunte alla firma della chiamata Axes.boxplotcon un impatto minimo sulla complessità del metodo. Questo perché possono essere passati direttamente a cbook.boxplot_stats. In alternativa, Axes.boxplotpotrebbe essere modificato per accettare una funzione statistica facoltativa kwarg e un dizionario di parametri da passare direttamente ad essa.

A questo punto dell'implementazione, gli utenti e le librerie esterne come Seaborn avrebbero il controllo completo tramite il Axes.boxplotmetodo. Ancora più importante, come minimo, Seaborn non richiederebbe modifiche alla sua API per consentire agli utenti di sfruttare queste nuove opzioni.

Semplificazioni Axes.boxplotall'API e ad altre funzioni #

La semplificazione del metodo boxplot consiste principalmente nel deprecare e quindi rimuovere i parametri ridondanti. Facoltativamente, un passaggio successivo includerebbe la rettifica di incoerenze terminologiche minori tra Axes.boxplot e Axes.bxp.

I parametri da deprecare e rimuovere includono:

  1. usermedians- elaborato da 10 SLOC, 3 ifblocchi, un forloop

  2. conf_intervals- gestito da 15 SLOC, 6 ifblocchi, un forloop

  3. sym- elaborato da 12 SLOC, 4 ifblocchi

La rimozione symdell'opzione consente di spostare in Axes.bxp. Ciò non elimina alcuna complessità, ma rafforza il principio di responsabilità unica tra Axes.bxp, cbook.boxplot_stats, e Axes.boxplot.

Inoltre, il notchparametro potrebbe essere rinominato shownotches per essere coerente con Axes.bxp. Questo tipo di pulizia potrebbe essere fatto un ulteriore passo avanti e il whis, bootstrap, autorangepotrebbe essere inserito nei kwargs passati al nuovo statfxnparametro.

Compatibilità con le versioni precedenti #

L'implementazione di questo MEP comporterebbe alla fine la deprecazione incompatibile con le versioni precedenti e quindi la rimozione dei parametri delle parole chiave usermedians, conf_intervalse sym. Ricerche superficiali su GitHub hanno indicato che usermedians, conf_intervalssono utilizzati da pochi utenti, che sembrano avere tutti una conoscenza molto approfondita di matplotlib. Un robusto ciclo di deprecazione dovrebbe fornire tempo sufficiente a questi utenti per migrare a una nuova API.

La deprecazione di sym, tuttavia, potrebbe avere una portata molto più ampia nella base utenti di matplotlib.

Programma #

Una sequenza temporale accelerata potrebbe essere simile alla seguente:

  1. v2.0.1 aggiungi trasformazioni a cbook.boxplots_stats, esponi inAxes.boxplot

  2. v2.1.0 Deprecazioni iniziali e utilizzo di array NumPy 2D come input

    1. Utilizzo di array NumPy 2D come input. La semantica degli array 2D è generalmente confusa.

    2. usermedians, conf_intervals, symparametri

  3. v2.2.0

    1. rimuovi usermedians, conf_intervals, symparametri

    2. deprecare notcha favore di shownotchesessere coerente con altri parametri eAxes.bxp

  4. v2.3.0
    1. rimuovere il notchparametro

    2. spostare tutta la logica di commutazione dello stile e dell'artista in Axes.bxptale Axes.boxplot è poco più di un intermediario tra Axes.bxpecbook.boxplots_stats

Impatti previsti per gli utenti #

Come descritto sopra deprecato usermedianse conf_intervals probabilmente avrà un impatto su pochi utenti. Coloro che ne risentiranno sono quasi certamente utenti avanzati che saranno in grado di adattarsi al cambiamento.

Deprecare l' symopzione può importare più utenti e dovrebbe essere fatto uno sforzo per raccogliere il feedback della comunità su questo.

Impatti previsti sulle biblioteche a valle #

Il codice sorgente (GitHub master al 17-10-2016) è stato ispezionato per seaborn e python-ggplot per vedere se queste modifiche avrebbero avuto un impatto sul loro utilizzo. Nessuno dei parametri nominati per la rimozione in questo MEP è utilizzato da Seaborn. Le API seaborn che utilizzano la funzione boxplot di matplotlib consentono all'utente di passare arbitrariamente **kwargsall'API di matplotlib. Pertanto, gli utenti di Seaborn con moderne installazioni di matplotlib saranno in grado di sfruttare appieno tutte le nuove funzionalità aggiunte come risultato di questo MEP.

Python-ggplot ha implementato la propria funzione per disegnare boxplot. Pertanto, nessun impatto può derivare dall'attuazione di questo MEP.

Alternative #

Variazioni sul tema #

Questo eurodeputato può essere suddiviso in alcuni componenti debolmente accoppiati:

  1. Consentire la funzione di trasformazione pre e post calcolo incbook.boxplot_stats

  2. Esponendo tale trasformazione Axes.boxplotnell'API

  3. Rimozione delle opzioni statistiche ridondanti inAxes.boxplot

  4. Spostamento dell'elaborazione di tutti i parametri di stile da Axes.boxplota Axes.bxp.

Con questo approccio, #2 dipende e #1, e #4 dipende da #3.

Ci sono due possibili approcci al n. 2. Il primo e più diretto sarebbe rispecchiare i parametri new transform_ine di in e passarli direttamente.transform_outcbook.boxplot_statsAxes.boxplot

Il secondo approccio sarebbe aggiungere statfxne statfxn_args parametri a Axes.boxplot. In questa implementazione, il valore predefinito di statfxnsarebbe cbook.boxplot_stats, ma gli utenti potrebbero passare la propria funzione. Quindi transform_ine transform_outverrebbero quindi passati come elementi del statfxn_argsparametro.

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

Entrambi i casi consentirebbero agli utenti di eseguire le seguenti operazioni:

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

Ma l'opzione due consente a un utente di scrivere una funzione statistica completamente personalizzata (ad esempio, my_box_stats) con intervalli di confidenza BCA fantasiosi e baffi impostati in modo diverso a seconda di alcuni attributi dei dati.

Questo è disponibile sotto l'API corrente:

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

E sarebbe più conciso con l'opzione due

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

Gli utenti possono anche passare la propria funzione per calcolare le statistiche:

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

Dagli esempi precedenti, l'opzione due sembra avere solo un vantaggio marginale, ma nel contesto delle librerie a valle come seaborn, il suo vantaggio è più evidente in quanto sarebbe possibile quanto segue senza alcuna patch per 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')

Questo tipo di flessibilità era l'intenzione alla base della suddivisione dell'API boxplot complessiva nelle tre funzioni attuali. In pratica, tuttavia, le librerie a valle come Seaborn supportano versioni di matplotlib che risalgono a ben prima della scissione. Pertanto, l'aggiunta di un po' più di flessibilità a Axes.boxplotpotrebbe esporre tutte le funzionalità agli utenti delle librerie downstream con l'installazione moderna di matplotlib senza l'intervento dei manutentori delle librerie downstream.

Fare di meno #

Un'altra ovvia alternativa sarebbe quella di omettere la funzionalità di trasformazione pre e post computazione aggiunta in cbook.boxplot_statse Axes.boxplote rimuovere semplicemente i parametri statistici e di stile ridondanti come descritto sopra.

Non fare niente #

Come per molte cose nella vita, non fare nulla è un'opzione qui. Ciò significa che sosteniamo semplicemente che gli utenti e le biblioteche a valle traggano vantaggio dalla divisione tra cbook.boxplot_statse Axes.bxplasciamo che decidano come fornire un'interfaccia a questo.