Quelques notions sur Stacked Bar Graph sur le datavizcatalogue.
Ce notebook présente la construction d'un stacked bar graph avec pandas et matplotlib.
Concernant pandas, ce notebook montre comment :
In [1]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(style="white", font_scale=1.5)
%matplotlib inline
In [2]:
N = 10
df = pd.DataFrame({k: np.random.randint(low=1, high=7, size=N) for k in "ABCD"})
df
Out[2]:
L'idée est de transformer le score du questionnaire en une catégorie : "défavorise", "neutre", "favrorise".
Le point le plus difficile est l'écriture de la fonction. Avec df.apply()
on va appliquer la fonction à l'ensemble du tableau. On peut le faire de deux manières différentes.
Ici je vais appliquer la fonction colonne par colonne (axis=0
). Il faut donc que la fonction prenne commme argument la colonne et retourne la liste des nouvelles valeurs de la colonne.
Donc
In [3]:
def cat_column(column):
values = list()
for val in column:
if val <= 2:
cat = "defavorise"
elif val >= 5:
cat = "favorise"
else:
cat = "neutre"
values.append(cat)
return values
Sur le principe, voici comme ça marche avec :
In [4]:
liste = [1, 2, 3, 4, 5, 6]
values = cat_column(liste)
print(values)
Maintenant on applique à notre tableau :
In [5]:
df_cat = df.apply(cat_column, axis=0)
df_cat
Out[5]:
Les opérations ne dépendent que d'une case du tableau. On peut donc utiliser une fonction qui ne connait que le contenu d'une case et retourne la bonne catégorie. La fonction est plus simple (pas de boucle), elle prend comme arguement le contenu d'une case et retourne la catégorie :
In [6]:
def cat_cell(val):
if val <= 2:
cat = "defavorise"
elif val >= 5:
cat = "favorise"
else:
cat = "neutre"
return cat
Par exemple :
In [7]:
print(cat_cell(2), cat_cell(3))
On l'applique au tableau. Maintenant on doit utiliser la méthode df.applymap()
au lieu de apply()
.
In [8]:
df_cat = df.applymap(cat_cell)
df_cat
Out[8]:
In [9]:
df_cat.apply(pd.value_counts)
Out[9]:
Si on veut le pourcentage, il faut savoir combien il y a de lignes. Dans cet exemple, c'est N
que l'on a définit tout au début. Sinon, il faut récupérer le nombre de lignes de la DataFrame
. Cette information est contenu dans df.shape
:
In [10]:
nrows, ncols = df_cat.shape
print(nrows, ncols)
In [11]:
df_percent = df_cat.apply(pd.value_counts) / nrows * 100
df_percent
Out[11]:
In [12]:
df_percent_t = df_percent.transpose()
df_percent_t
Out[12]:
Pour que le graphique soit plus cohérent, on va réorganiser les colonnes de sorte que "neutre"
soit au milieu :
In [13]:
df_percent_t = df_percent_t[["defavorise", "neutre", "favorise"]]
df_percent_t
Out[13]:
Voici une première version, le principe étant de choisir un graphique de type barh
avec stacked
vrai. On choisit ensuite une colormap divergente pour donner un sens aux couleurs.
In [20]:
fig = plt.figure()
ax = fig.add_subplot(111)
df_percent_t.plot(kind="barh", stacked=True, ax=ax, colormap="RdYlGn", alpha=.8, xlim=(0, 101))
ax.legend(ncol=3, loc='upper center', bbox_to_anchor=(0.5, 1.15), frameon=False)
Out[20]:
Un peu plus de détails, la partie compliquée est l'ajout des annotations.
In [19]:
fig = plt.figure()
ax = fig.add_subplot(111)
df_percent_t.plot(kind="barh", stacked=True, ax=ax, colormap="RdYlGn", alpha=.8)
ax.legend(ncol=3, loc='upper center', bbox_to_anchor=(0.5, 1.15), frameon=False)
ax.set_frame_on(False)
ax.set_xticks([])
# add texts
y = 0
for index, row in df_percent_t.iterrows(): # boucle sur les lignes
# on calcule les intervalles
xbounds = [0]
for i in range(len(row)): # len(row) est le nombre d'éléments sur la ligne, 3 ici
xbounds.append(xbounds[i] + row[i])
print(xbounds)
# ajout du texte au centre de chaque intervalle
for i in range(3):
x = (xbounds[i] + xbounds[i+1]) / 2
ax.text(x, y, "%3.0f%%" % row[i],
verticalalignment="center",
horizontalalignment="center")
y += 1
Pour y voir plus clair sur le calcul des intervalles, voici un exemple :
In [16]:
l = [40, 50, 10]
x = [0]
for i in range(3):
x.append(x[i] + l[i])
print(x)