web-dev-qa-db-fra.com

Seaborn: countplot () avec fréquences

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:

  1. l'axe des y gauche montre les fréquences de ces valeurs se produisant dans les données. L'axe s'étend de [0% à 100%], des graduations tous les 10%.
  2. l'axe y droit montre les nombres réels, les valeurs correspondent aux graduations déterminées par l'axe y gauche (marquées tous les 10%).
  3. l'axe des x montre les catégories des diagrammes à barres [3, 4, 5, 6, 7, 8, 9, 10, 11, 12].
  4. Les annotations en haut des barres indiquent le pourcentage réel de cette catégorie.

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

enter image description here

MODIFIER:

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:

  • Il n'y a pas de mot clé 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))
    

enter image description here

25
marillion

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

enter image description here

30
tmdavison

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. enter image description here

Approche

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.

Code

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' )
7
spfrnd

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

enter image description here

3
CT Zhu