MEP28: Eliminar la complejidad de Axes.boxplot #

Estado #

Discusión

Sucursales y solicitudes de extracción #

A continuación se enumeran los RP abiertos o sucursales relacionadas con este eurodiputado:

  1. Desactive los kwargs estadísticos redundantes en Axes.boxplot: https://github.com/phobson/matplotlib/tree/MEP28-initial-deprecations

  2. Desactive las opciones de estilo redundantes en Axes.boxplot: https://github.com/phobson/matplotlib/tree/MEP28-initial-deprecations

  3. Desaprobar pasar matrices 2D NumPy como entrada: Ninguno

  4. Agregue opciones de procesamiento previo y posterior a cbook.boxplot_stats: https://github.com/phobson/matplotlib/tree/boxplot-stat-transforms

  5. Exponer cbook.boxplot_statsa través Axes.boxplotde kwargs: Ninguno

  6. Eliminar kwargs estadísticos redundantes en Axes.boxplot: Ninguno

  7. Eliminar opciones de estilo redundantes en Axes.boxplot: Ninguno

  8. Elementos restantes que surgen a través de la discusión: Ninguno

Resumen #

En los últimos lanzamientos, el Axes.boxplotmétodo ha crecido en complejidad para admitir el estilo de artista y el cálculo estadístico totalmente personalizables. Esto llevó a Axes.boxplotque se dividiera en varias partes. Las estadísticas necesarias para dibujar un diagrama de caja se calculan en cbook.boxplot_stats, mientras que los artistas reales se dibujan en Axes.bxp. El método original Axes.boxplotsigue siendo la API más pública que maneja el paso de los datos proporcionados por el usuario cbook.boxplot_stats, la alimentación de los resultados Axes.bxpy el preprocesamiento de la información de estilo para cada faceta de los diagramas de caja.

Este eurodiputado delineará un camino a seguir para revertir la complejidad adicional y simplificar la API mientras mantiene una compatibilidad con versiones anteriores razonable.

Descripción detallada #

Actualmente, el Axes.boxplotmétodo acepta parámetros que permiten a los usuarios especificar medianas e intervalos de confianza para cada caja que se dibujará en el gráfico. Estos se proporcionaron para que los usuarios avanzados pudieran proporcionar estadísticas calculadas de una manera diferente al método simple proporcionado por matplotlib. Sin embargo, manejar esta entrada requiere una lógica compleja para asegurarse de que las formas de la estructura de datos coincidan con lo que se necesita dibujar. Por el momento, esa lógica contiene 9 instrucciones if/else separadas anidadas hasta 5 niveles de profundidad con un bucle for, y puede generar hasta 2 errores. Estos parámetros se agregaron antes de la creación del Axes.bxpmétodo, que dibuja diagramas de caja de una lista de diccionarios que contienen las estadísticas relevantes. Matplotlib también proporciona una función que calcula estas estadísticas a través decbook.boxplot_stats. Tenga en cuenta que los usuarios avanzados ahora pueden a) escribir su propia función para calcular las estadísticas requeridas por Axes.bxp, o b) modificar el resultado devuelto por cbook.boxplots_stats para personalizar completamente la posición de los artistas de las tramas. Con esta flexibilidad, los parámetros para especificar manualmente solo las medianas y sus intervalos de confianza permanecen para la compatibilidad con versiones anteriores.

Casi al mismo tiempo que los dos roles de Axes.boxplotse dividieron en cbook.boxplot_statscomputación y Axes.bxpdibujo, ambos Axes.boxploty Axes.bxpse escribieron para aceptar parámetros que alternan individualmente el dibujo de todos los componentes de los diagramas de caja y parámetros que configuran individualmente el estilo de esos artistas. Sin embargo, para mantener la compatibilidad con versiones anteriores, se mantuvo el symparámetro (anteriormente utilizado para especificar el símbolo de los volantes). Este parámetro en sí requiere una lógica bastante compleja para conciliar los symparámetros con el flierpropsparámetro más nuevo en el estilo predeterminado especificado por matplotlibrc.

Este eurodiputado busca simplificar drásticamente la creación de diagramas de caja para usuarios novatos y avanzados por igual. Es importante destacar que los cambios propuestos aquí también estarán disponibles para paquetes posteriores como seaborn, ya que seaborn inteligentemente permite a los usuarios pasar diccionarios arbitrarios de parámetros a través de la API de seaborn a las funciones subyacentes de matplotlib.

Esto se logrará de la siguiente manera:

  1. cbook.boxplot_statsse modificará para permitir que se pasen funciones de transformación previas y posteriores al cálculo (por ejemplo, np.log y np.exppara datos distribuidos lognormalmente)

  2. Axes.boxplotse modificará para aceptarlos y pasarlos ingenuamente a cbook.boxplots_stats(Alt: pasar la función stat y un dict de sus parámetros opcionales).

  3. Los parámetros obsoletos de Axes.boxplotquedarán en desuso y luego se eliminarán.

Importancia #

Dado que los límites de los bigotes se calculan aritméticamente, existe una suposición implícita de normalidad en los diagramas de caja y bigotes. Esto afecta principalmente qué puntos de datos se clasifican como valores atípicos.

Permitir transformaciones a los datos y los resultados utilizados para dibujar diagramas de caja permitirá a los usuarios optar por no participar en esa suposición si se sabe que los datos no se ajustan a una distribución normal.

A continuación, se muestra un ejemplo de cómo se Axes.boxplotclasifican los valores atípicos de los datos lognormales de manera diferente según uno de estos tipos de transformaciones.

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 fuente , png )

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

Implementación #

Pasar funciones de transformación a cbook.boxplots_stats#

Este eurodiputado propone que se agreguen dos parámetros (p. ej., transform_iny transform_outa la función de libro de recetas que calcula las estadísticas para la función de gráfico de caja). Estos serán argumentos opcionales de solo palabras clave y se pueden configurar fácilmente como no operativos cuando el usuario los omite. La función se aplicará a los datos a medida que la función recorre cada subconjunto de los datos que se le pasan.Después de calcular la lista de diccionarios de estadísticas, la función se aplica a cada valor en los diccionarios.lambda x: xtransform_inboxplot_statstransform_out

Estas transformaciones se pueden agregar a la firma de llamada Axes.boxplotcon poco impacto en la complejidad de ese método. Esto se debe a que se pueden pasar directamente a cbook.boxplot_stats. Alternativamente, Axes.boxplotpodría modificarse para aceptar una función estadística opcional kwarg y un diccionario de parámetros para pasarle directamente.

En este punto de la implementación, los usuarios y las bibliotecas externas como seaborn tendrían un control total a través del Axes.boxplotmétodo. Más importante aún, al menos, seaborn no requeriría cambios en su API para permitir que los usuarios aprovechen estas nuevas opciones.

Simplificaciones a la Axes.boxplotAPI y otras funciones #

La simplificación del método de diagrama de caja consiste principalmente en desaprobar y luego eliminar los parámetros redundantes. Opcionalmente, un próximo paso incluiría la rectificación de inconsistencias terminológicas menores entre Axes.boxplot y Axes.bxp.

Los parámetros que serán obsoletos y eliminados incluyen:

  1. usermedians- procesado por 10 SLOC, 3 ifbloques, un forbucle

  2. conf_intervals- manejado por 15 SLOC, 6 ifbloques, un forbucle

  3. sym- procesado por 12 SLOC, 4 ifbloques

Eliminar la symopción permite que todo el código en el manejo de los parámetros de estilo restantes se mueva a Axes.bxp. Esto no elimina ninguna complejidad, pero refuerza el principio de responsabilidad única entre Axes.bxp, cbook.boxplot_statsy Axes.boxplot.

Además, notchse podría cambiar el nombre del parámetro shownotches para que sea coherente con Axes.bxp. Este tipo de limpieza podría llevarse un paso más allá y el whis, bootstrap, autorangepodría incorporarse a los kwargs pasados ​​al nuevo statfxnparámetro.

Compatibilidad con versiones anteriores #

La implementación de este MEP eventualmente resultaría en la desaprobación incompatible con versiones anteriores y luego en la eliminación de los parámetros de palabra clave usermedians, conf_intervalsy sym. Las búsquedas rápidas en GitHub indicaron que son utilizados por pocos usuarios usermedians, conf_intervalsquienes parecen tener un conocimiento muy sólido de matplotlib. Un ciclo de desuso sólido debería proporcionar tiempo suficiente para que estos usuarios migren a una nueva API.

symSin embargo, la desaprobación puede tener un alcance mucho más amplio en la base de usuarios de matplotlib .

Horario #

Una línea de tiempo acelerada podría tener el siguiente aspecto:

  1. v2.0.1 agregar transformaciones a cbook.boxplots_stats, exponer enAxes.boxplot

  2. v2.1.0 Obsolescencias iniciales y uso de matrices 2D NumPy como entrada

    1. Usando matrices 2D NumPy como entrada. La semántica en torno a las matrices 2D suele ser confusa.

    2. usermedians, conf_intervals, symparámetros

  3. v2.2.0

    1. eliminar usermedians, conf_intervals, symparámetros

    2. desaprobar notcha favor de shownotchesser consistente con otros parámetros yAxes.bxp

  4. v2.3.0
    1. eliminar notchparámetro

    2. mover todo el estilo y la lógica de alternancia del artista a Axes.bxptal Axes.boxplot es poco más que un intermediario entre Axes.bxpycbook.boxplots_stats

Impactos previstos para los usuarios #

Como se describió anteriormente, es obsoleto usermediansy conf_intervals probablemente afectará a pocos usuarios. Los que se verán afectados son casi con certeza usuarios avanzados que podrán adaptarse al cambio.

Desactivar la symopción puede importar más usuarios y se debe hacer un esfuerzo para recopilar comentarios de la comunidad al respecto.

Impactos previstos para las bibliotecas secundarias #

Se inspeccionó el código fuente (maestro de GitHub a partir del 17 de octubre de 2016) en busca de seaborn y python-ggplot para ver si estos cambios afectarían su uso. Ninguno de los parámetros propuestos para su eliminación en este MEP son utilizados por la navegación marítima. Las API marinas que utilizan la función boxplot de matplotlib permiten que los usuarios pasen arbitrariamente **kwargsa través de la API de matplotlib. Por lo tanto, los usuarios marinos con instalaciones modernas de matplotlib podrán aprovechar al máximo las nuevas funciones agregadas como resultado de este MEP.

Python-ggplot ha implementado su propia función para dibujar diagramas de caja. Por lo tanto, ningún impacto puede tener como resultado de la implementación de este MEP.

Alternativas #

Variaciones sobre el tema #

Este eurodiputado se puede dividir en unos pocos componentes débilmente acoplados:

  1. Permitiendo la función de transformación previa y posterior al cálculo encbook.boxplot_stats

  2. Exponiendo esa transformación en la Axes.boxplotAPI

  3. Eliminar opciones estadísticas redundantes enAxes.boxplot

  4. Cambiar todo el procesamiento de parámetros de estilo de Axes.boxplota Axes.bxp.

Con este enfoque, el n.° 2 depende del n.° 1, y el n.° 4 depende del n.° 3.

Hay dos enfoques posibles para el #2. La primera y más directa sería reflejar los nuevos parámetros transform_iny pasarlos directamente.transform_outcbook.boxplot_statsAxes.boxplot

El segundo enfoque sería agregar statfxny statfxn_args parámetros a Axes.boxplot. Bajo esta implementación, el valor predeterminado de statfxnsería cbook.boxplot_stats, pero los usuarios podrían pasar su propia función. Luego transform_iny transform_outluego se pasarían como elementos del 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 casos permitirían a los usuarios hacer lo siguiente:

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

Pero la opción dos le permite al usuario escribir una función estadística completamente personalizada (por ejemplo, my_box_stats) con intervalos de confianza BCA elegantes y los bigotes configurados de manera diferente según algún atributo de los datos.

Esto está disponible bajo la API actual:

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

Y sería más conciso con la opción dos

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

Los usuarios también pueden pasar su propia función para calcular las estadísticas:

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

De los ejemplos anteriores, la Opción Dos parece tener solo un beneficio marginal, pero en el contexto de las bibliotecas posteriores como seaborn, su ventaja es más evidente ya que lo siguiente sería posible sin parches 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')

Este tipo de flexibilidad fue la intención detrás de dividir la API de diagrama de caja general en las tres funciones actuales. Sin embargo, en la práctica, las bibliotecas posteriores, como las versiones de soporte marítimo de matplotlib, se remontan mucho antes de la división. Por lo tanto, agregar un poco más de flexibilidad Axes.boxplotpodría exponer toda la funcionalidad a los usuarios de las bibliotecas posteriores con la instalación moderna de matplotlib sin la intervención de los mantenedores de bibliotecas posteriores.

Haciendo menos #

Otra alternativa obvia sería omitir la funcionalidad de transformación agregada antes y después del cálculo en cbook.boxplot_statsy Axes.boxplot, y simplemente eliminar los parámetros estadísticos y de estilo redundantes como se describió anteriormente.

sin hacer nada #

Como con muchas cosas en la vida, no hacer nada es una opción aquí. Esto significa que simplemente abogamos por que los usuarios y las bibliotecas secundarias aprovechen la división entre cbook.boxplot_statsy Axes.bxpy les permitamos decidir cómo proporcionar una interfaz para eso.