5
votes

Monkey patching pandas et matplotlib pour supprimer les épines pour df.plot ()

La question:

J'essaie de saisir le concept de monkey patching et en même temps créer une fonction pour produire le tracé chronologique parfait. Comment puis-je inclure la fonctionnalité matplotlib suivante dans les pandas pandas.DataFrame.plot () ?

# imports
import pandas as pd
import numpy as np
from jupyterthemes import jtplot

# Sample data
np.random.seed(123)
rows = 50
dfx = pd.DataFrame(np.random.randint(90,110,size=(rows, 1)), columns=['Variable Y'])
dfy = pd.DataFrame(np.random.randint(25,68,size=(rows, 1)), columns=[' Variable X'])
df = pd.concat([dfx,dfy], axis = 1)
jtplot.style()

# Plot with default settings
df.plot()

# Wrap df.plot() and matplotlib spine in a function
def plotPerfect(df, spline):

    ax = df.plot()

    if not spline:
        ax.spines['top'].set_visible(False)
        ax.spines['right'].set_visible(False)
        ax.spines['bottom'].set_visible(False)
        ax.spines['left'].set_visible(False)

    return(ax)

# Plot the perfect time-series plot
plotPerfect(df = df, spline = False)

code complet à la fin de la question


Les détails:


Je pense que les paramètres par défaut dans df.plot () sont assez soignés , surtout si vous exécutez un bloc-notes Jupyter avec un thème sombre comme chesterish de dunovank :

 df.plot default

Et j'aimerais l'utiliser autant que possible pour mon flux de travail d'analyse de données, mais j'aimerais vraiment pour supprimer le cadre (ou ce qu'on appelle les épines) comme ceci:

df.plot no frames

C'est peut-être le moment idéal- tracé de série. Mais df.plot () n'a pas d'argument intégré pour cela. La chose la plus proche semble être grid = False , mais cela enlève toute la grille dans la même exécution:

 entrez la description de l'image ici p>


Ce que j'ai essayé


Je sais que je peux envelopper l'extrait de code spine dans une fonction avec df.plot () donc je me retrouve avec ceci:

Extrait 1:

def plotPerfect(df, spline):

    ax = df.plot()

    if not spline:
        ax.spines['top'].set_visible(False)
        ax.spines['right'].set_visible(False)
        ax.spines['bottom'].set_visible(False)
        ax.spines['left'].set_visible(False)

    return(ax)

plotPerfect(df = df, spline = False)

Sortie 1:

entrez la description de l'image ici

Mais est-ce la "meilleure" façon de le faire en ce qui concerne à la flexibilité et à la lisibilité des futurs amendements? Ou même le plus rapide en termes de temps d'exécution si nous parlons de centaines de tracés?

Je sais comment je peux obtenir le df.plot () source , mais tout me quitte déconcerté. Alors, comment faire inclure ces paramètres dans df.plot ? Et peut-être que l'approche de la fonction encapsulée est aussi bonne que le patching de singe?


Extrait de code complet et exemples de données:


reproduisez l'exemple à 100%, collez-le dans une cellule Jupyter Notebook avec le thème chesterish activé:

ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['bottom'].set_visible(False)
ax.spines['left'].set_visible(False)


2 commentaires

Êtes-vous intéressé par le patching de singe ou customizong les tracés ? Cela me semble tout à fait sans rapport avec moi.


@Goyo Mon principal objectif est de modifier efficacement un tas de tracés. Et j'avais l'impression (vague) que cela pourrait être un bon cas d'utilisation pour le patching de singe.


3 Réponses :


1
votes

Je répondrais en partie à votre partie de personnalisation de la question: au lieu de masquer chaque spline par une commande séparée, vous pouvez les placer dans une boucle for comme suit.

def plotPerfect(df, spline):
    ax = df.plot()

    if not spline:
        for i in ax.spines.values():
            i.set_visible(False)

    return(ax)

Si vous voulez masquer les quatre épines et ne voulez pas spécifier manuellement le haut, la droite, etc., vous pouvez le faire de manière plus automatisée comme suit. Le premier vous permet de choisir ceux à masquer.

def plotPerfect(df, spline):
    ax = df.plot()

    if not spline:
        for i in ['top', 'right', 'bottom', 'left']:
            ax.spines[i].set_visible(False)

    return(ax)

# Plot the perfect time-series plot
plotPerfect(df = df, spline = False) 


0 commentaires

1
votes

Des solutions alternatives à l'autre réponse consisteraient à utiliser plt .box (False) ou ax.set_frame_on (False) , ces deux masquent le patch rectangle des axes.

def plotPerfect(df, spline):
    ax = df.plot()

    if not spline:
        ax.set_frame_on(False)
        # plt.box(False)  # another possible option

    return ax

Notez que set_frame_on (Faux) supprime l'arrière-plan en le rendant transparent, ce qui pourrait ne pas être souhaité


0 commentaires

2
votes

Cela ressemble à un xyproblem .

Monkey patching (The Y)

Le question demande la fonction de tracé de pandas de patching de singe pour ajouter des fonctionnalités supplémentaires. Cela peut dans ce cas être fait en remplaçant la fonction pandas.plotting._core.plot_frame par une version personnalisée de celle-ci.

plt.rcParams.update(mystyle)

Ensuite, utilisez-la comme

import pandas as pd
import matplotlib.pyplot as plt

df = pd.DataFrame([[0.1, 0.1], [0.9, 0.9]]).set_index(0)

df.plot()             ## Normal Plot

with plt.style.context(mystyle):
    df.plot()         ## "Despined" Plot 

 entrez la description de l'image ici

Notez que si dans le notebook jupyter, la cellule avec le patch monkey ne peut pas être s'exécuter plus d'une fois, sinon cela finirait par récurer.

Styling (The X)

Ce qui précède est assez exagéré pour changer le style d'un tracé. Il faut plutôt utiliser les options de style de matplotlib .

mystyle = {"axes.spines.left" : False,
           "axes.spines.right" : False,
           "axes.spines.bottom" : False,
           "axes.spines.top" : False,
           "axes.grid" : True,
           "xtick.bottom" : False,
           "ytick.left" : False,}

Ensuite, pour appliquer ceci à certains tracés du notebook, utilisez le gestionnaire plt.style.context ,

df = pd.DataFrame([[0.1, 0.1], [0.9, 0.9]]).set_index(0)
df.plot()             ## Normal Plot
df.plot(spline=False) ## "Despined" Plot 

 enter image description here

Ou, si vous souhaitez appliquer ce style globalement, mettez à jour le rcParams . p>

import pandas as pd
import pandas.plotting._core
orginal = pandas.plotting._core.plot_frame

def myplot(*args, **kwargs):
    spline = kwargs.pop("spline", True)
    ax = orginal(*args, **kwargs)
    ax.set_frame_on(spline)
    ax.grid(not spline)
    ax.tick_params(left=spline, bottom=spline)
    return ax

pandas.plotting._core.plot_frame = myplot


5 commentaires

@ ImportanceOfBeingErnest Merci! Vous illustrez également quelques autres aspects qui m'intéressaient, c'est encore plus que ce que j'espérais! Je suis également émerveillé par le fait que "cela semble être un xyproblem" et le libellé de votre lien est une manière élégante et subtile d'appeler quelqu'un un n00b ignorant. Au début, je pensais que vous parliez de Python (x, y) dont j'avais à peine entendu parler mais avait hâte d'en savoir plus.


Personnellement je regarde les choses d'une autre manière: j'essaye de trouver une solution (spline, ou X) que je pense qu'il serait possible de résoudre d'une autre manière (peut-être même plus "pythonique") (singe patching, ou Y). Et je pense que c'est une excellente façon d'apprendre de nouvelles choses. J'ai l'impression de travailler avec plusieurs pièces d'un puzzle en même temps. Quoi qu'il en soit, merci encore!


Vous avez suffisamment de détails sur ce que vous voulez faire dans votre question (le X), donc pas de soucis - comme vous pouvez le voir, on peut parfaitement y répondre.


Je viens de réaliser comment j'aurais dû poser la question: est-il possible de prendre df.plot () et d'inclure spines = False comme argument? Tout comme box ou kind est un argument en soi? Et remplacer MY df.plot () avec le df.plot original ()? Peut-être utiliser des patchs de singe?


Cela fait un an que vous avez répondu à cette question, mais auriez-vous une question complémentaire? J'essaie d'appliquer ce que vous avez montré ici à d'autres scénarios. Donc, ce que je voudrais faire, c'est soulever une autre question dans laquelle je pose des questions sur les détails de la solution que vous suggérez ici. Serait-ce OK?