J'ai un Pandas DataFrame avec une colonne appelée "AXLES", qui peut prendre une valeur entière entre 3-12. J'essaie d'utiliser l'option countplot () de Seaborn pour obtenir le tracé suivant:
Le code suivant me donne l'intrigue ci-dessous, avec les nombres réels, mais je n'ai pas trouvé de moyen de les convertir en fréquences. Je peux obtenir les fréquences en utilisant df.AXLES.value_counts()/len(df.index)
mais je ne sais pas comment brancher ces informations dans countplot()
de Seaborn.
J'ai également trouvé une solution de contournement pour les annotations, mais je ne sais pas si c'est la meilleure implémentation.
Toute aide serait appréciée!
Merci
plt.figure(figsize=(12,8))
ax = sns.countplot(x="AXLES", data=dfWIM, order=[3,4,5,6,7,8,9,10,11,12])
plt.title('Distribution of Truck Configurations')
plt.xlabel('Number of Axles')
plt.ylabel('Frequency [%]')
for p in ax.patches:
ax.annotate('%{:.1f}'.format(p.get_height()), (p.get_x()+0.1, p.get_height()+50))
Je me suis rapproché de ce dont j'ai besoin avec le code suivant, en utilisant l'intrigue des Pandas, abandonnant Seaborn. J'ai l'impression d'utiliser autant de solutions de contournement, et il doit y avoir un moyen plus facile de le faire. Les problèmes avec cette approche:
order
dans la fonction de tracé de barres des Pandas comme le fait countplot () de Seaborn, donc je ne peux pas tracer toutes les catégories de 3-12 comme je l'ai fait dans countplot (). Je dois les montrer même s'il n'y a pas de données dans cette catégorie.L'axe Y secondaire perturbe les barres et l'annotation pour une raison quelconque (voir le quadrillage blanc dessiné sur le texte et les barres).
plt.figure(figsize=(12,8))
plt.title('Distribution of Truck Configurations')
plt.xlabel('Number of Axles')
plt.ylabel('Frequency [%]')
ax = (dfWIM.AXLES.value_counts()/len(df)*100).sort_index().plot(kind="bar", rot=0)
ax.set_yticks(np.arange(0, 110, 10))
ax2 = ax.twinx()
ax2.set_yticks(np.arange(0, 110, 10)*len(df)/100)
for p in ax.patches:
ax.annotate('{:.2f}%'.format(p.get_height()), (p.get_x()+0.15, p.get_height()+1))
Vous pouvez le faire en créant des axes twinx
pour les fréquences. Vous pouvez changer les deux axes y pour que les fréquences restent à gauche et les comptes à droite, mais sans avoir à recalculer l'axe des comptes (ici, nous utilisons tick_left()
et - tick_right()
pour déplacer les ticks et set_label_position
pour déplacer les étiquettes des axes
Vous pouvez ensuite définir les graduations à l'aide du module matplotlib.ticker
, en particulier ticker.MultipleLocator
et ticker.LinearLocator
=.
Quant à vos annotations, vous pouvez obtenir les emplacements x et y pour les 4 coins de la barre avec patch.get_bbox().get_points()
. Ceci, en plus de définir correctement l'alignement horizontal et vertical, signifie que vous n'avez pas besoin d'ajouter de décalages arbitraires à l'emplacement d'annotation.
Enfin, vous devez désactiver la grille pour l'axe jumelé, pour éviter que les lignes de la grille n'apparaissent au-dessus des barres ( ax2.grid(None)
)
Voici un script de travail:
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
import matplotlib.ticker as ticker
# Some random data
dfWIM = pd.DataFrame({'AXLES': np.random.normal(8, 2, 5000).astype(int)})
ncount = len(dfWIM)
plt.figure(figsize=(12,8))
ax = sns.countplot(x="AXLES", data=dfWIM, order=[3,4,5,6,7,8,9,10,11,12])
plt.title('Distribution of Truck Configurations')
plt.xlabel('Number of Axles')
# Make twin axis
ax2=ax.twinx()
# Switch so count axis is on right, frequency on left
ax2.yaxis.tick_left()
ax.yaxis.tick_right()
# Also switch the labels over
ax.yaxis.set_label_position('right')
ax2.yaxis.set_label_position('left')
ax2.set_ylabel('Frequency [%]')
for p in ax.patches:
x=p.get_bbox().get_points()[:,0]
y=p.get_bbox().get_points()[1,1]
ax.annotate('{:.1f}%'.format(100.*y/ncount), (x.mean(), y),
ha='center', va='bottom') # set the alignment of the text
# Use a LinearLocator to ensure the correct number of ticks
ax.yaxis.set_major_locator(ticker.LinearLocator(11))
# Fix the frequency range to 0-100
ax2.set_ylim(0,100)
ax.set_ylim(0,ncount)
# And use a MultipleLocator to ensure a tick spacing of 10
ax2.yaxis.set_major_locator(ticker.MultipleLocator(10))
# Need to turn the grid on ax2 off, otherwise the gridlines end up on top of the bars
ax2.grid(None)
plt.savefig('snscounter.pdf')
Je l'ai fait fonctionner en utilisant le graphique à barres de core matplotlib
. Je n'avais évidemment pas vos données, mais l'adapter aux vôtres devrait être simple.
J'ai utilisé l'axe double de matplotlib
et tracé les données sous forme de barres sur le deuxième objet Axes
. Le reste est juste un peu de tripotage pour obtenir les tiques et faire des annotations.
J'espère que cela t'aides.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
from mpl_toolkits.mplot3d import Axes3D
import seaborn as sns
tot = np.random.Rand( 1 ) * 100
data = np.random.Rand( 1, 12 )
data = data / sum(data,1) * tot
df = pd.DataFrame( data )
palette = sns.husl_palette(9, s=0.7 )
### Left Axis
# Plot nothing here, autmatically scales to second axis.
fig, ax1 = plt.subplots()
ax1.set_ylim( [0,100] )
# Remove grid lines.
ax1.grid( False )
# Set ticks and add percentage sign.
ax1.yaxis.set_ticks( np.arange(0,101,10) )
fmt = '%.0f%%'
yticks = matplotlib.ticker.FormatStrFormatter( fmt )
ax1.yaxis.set_major_formatter( yticks )
### Right Axis
# Plot data as bars.
x = np.arange(0,9,1)
ax2 = ax1.twinx()
rects = ax2.bar( x-0.4, np.asarray(df.loc[0,3:]), width=0.8 )
# Set ticks on x-axis and remove grid lines.
ax2.set_xlim( [-0.5,8.5] )
ax2.xaxis.set_ticks( x )
ax2.xaxis.grid( False )
# Set ticks on y-axis in 10% steps.
ax2.set_ylim( [0,tot] )
ax2.yaxis.set_ticks( np.linspace( 0, tot, 11 ) )
# Add labels and change colors.
for i,r in enumerate(rects):
h = r.get_height()
r.set_color( palette[ i % len(palette) ] )
ax2.text( r.get_x() + r.get_width()/2.0, \
h + 0.01*tot, \
r'%d%%'%int(100*h/tot), ha = 'center' )
Je pense que vous pouvez d'abord définir manuellement les graduations principales puis modifier chaque étiquette
dfWIM = pd.DataFrame({'AXLES': np.random.randint(3, 10, 1000)})
total = len(dfWIM)*1.
plt.figure(figsize=(12,8))
ax = sns.countplot(x="AXLES", data=dfWIM, order=[3,4,5,6,7,8,9,10,11,12])
plt.title('Distribution of Truck Configurations')
plt.xlabel('Number of Axles')
plt.ylabel('Frequency [%]')
for p in ax.patches:
ax.annotate('{:.1f}%'.format(100*p.get_height()/total), (p.get_x()+0.1, p.get_height()+5))
#put 11 ticks (therefore 10 steps), from 0 to the total number of rows in the dataframe
ax.yaxis.set_ticks(np.linspace(0, total, 11))
#adjust the ticklabel to the desired format, without changing the position of the ticks.
_ = ax.set_yticklabels(map('{:.1f}%'.format, 100*ax.yaxis.get_majorticklocs()/total))