stapeln Sie Balkendiagramm in Matplotlib und fügen Sie jedem Abschnitt eine Beschriftung hinzu
Lesezeit: 8 Minuten
Griff
Ich versuche, das folgende Bild in Matplotlib zu replizieren, und es scheint barh ist meine einzige Möglichkeit. Obwohl es scheint, dass Sie nicht stapeln können barh Diagramme, also weiß ich nicht, was ich tun soll
Wenn Sie eine bessere Python-Bibliothek kennen, um so etwas zu zeichnen, lassen Sie es mich bitte wissen.
Das ist alles, was mir als Anfang einfallen würde:
Ich müsste dann Labels einzeln mit hinzufügen ax.text was langweilig wäre. Idealerweise möchte ich nur die Breite des einzufügenden Teils angeben, dann wird die Mitte dieses Abschnitts mit einer Zeichenfolge meiner Wahl aktualisiert. Die Beschriftungen auf der Außenseite (z. B. 3800) kann ich später selbst hinzufügen, es ist hauptsächlich die Beschriftung über dem Balkenabschnitt selbst und das Erstellen dieser Stapelmethode auf eine schöne Weise, mit der ich Probleme habe. Können Sie überhaupt einen „Abstand“, dh eine Farbspanne, angeben?
Bonnenfum
Bearbeiten 2: für heterogenere Daten. (Ich habe die obige Methode verlassen, da ich es üblicher finde, mit der gleichen Anzahl von Datensätzen pro Serie zu arbeiten.)
Beantwortung der beiden Teile der Frage:
ein) barh gibt einen Container mit Handles für alle gezeichneten Patches zurück. Sie können die Koordinaten der Patches verwenden, um die Textpositionen zu unterstützen.
b) Nach diesen beiden Antworten auf die Frage, die ich zuvor notiert habe (siehe Horizontal gestapeltes Balkendiagramm in Matplotlib), können Sie Balkendiagramme horizontal stapeln, indem Sie den ‘linken’ Eingang setzen.
und zusätzlich c) Handhabung von Daten, die eine weniger einheitliche Form haben.
Im Folgenden finden Sie eine Möglichkeit, mit weniger einheitlichen Daten umzugehen, indem Sie einfach jedes Segment einzeln verarbeiten.
import numpy as np
import matplotlib.pyplot as plt
# some labels for each row
people = ('A','B','C','D','E','F','G','H')
r = len(people)
# how many data points overall (average of 3 per person)
n = r * 3
# which person does each segment belong to?
rows = np.random.randint(0, r, (n,))
# how wide is the segment?
widths = np.random.randint(3,12, n,)
# what label to put on the segment (xrange in py2.7, range for py3)
labels = range(n)
colors="rgbwmc"
patch_handles = []
fig = plt.figure(figsize=(10,8))
ax = fig.add_subplot(111)
left = np.zeros(r,)
row_counts = np.zeros(r,)
for (r, w, l) in zip(rows, widths, labels):
print r, w, l
patch_handles.append(ax.barh(r, w, align='center', left=left[r],
color=colors[int(row_counts[r]) % len(colors)]))
left[r] += w
row_counts[r] += 1
# we know there is only one patch but could enumerate if expanded
patch = patch_handles[-1][0]
bl = patch.get_xy()
x = 0.5*patch.get_width() + bl[0]
y = 0.5*patch.get_height() + bl[1]
ax.text(x, y, "%d%%" % (l), ha="center",va="center")
y_pos = np.arange(8)
ax.set_yticks(y_pos)
ax.set_yticklabels(people)
ax.set_xlabel('Distance')
plt.show()
Was eine solche Grafik erzeugt wobei in jeder Serie eine unterschiedliche Anzahl von Segmenten vorhanden ist.
Beachten Sie, dass dies nicht besonders effizient ist, da jedes Segment einen individuellen Anruf verwendet ax.barh. Möglicherweise gibt es effizientere Methoden (z. B. durch Auffüllen einer Matrix mit Segmenten mit einer Breite von Null oder Nan-Werten), aber dies ist wahrscheinlich problemspezifisch und eine andere Frage.
Bearbeiten: aktualisiert, um beide Teile der Frage zu beantworten.
import numpy as np
import matplotlib.pyplot as plt
people = ('A','B','C','D','E','F','G','H')
segments = 4
# generate some multi-dimensional data & arbitrary labels
data = 3 + 10* np.random.rand(segments, len(people))
percentages = (np.random.randint(5,20, (len(people), segments)))
y_pos = np.arange(len(people))
fig = plt.figure(figsize=(10,8))
ax = fig.add_subplot(111)
colors="rgbwmc"
patch_handles = []
left = np.zeros(len(people)) # left alignment of data starts at zero
for i, d in enumerate(data):
patch_handles.append(ax.barh(y_pos, d,
color=colors[i%len(colors)], align='center',
left=left))
# accumulate the left-hand offsets
left += d
# go through all of the bar segments and annotate
for j in range(len(patch_handles)):
for i, patch in enumerate(patch_handles[j].get_children()):
bl = patch.get_xy()
x = 0.5*patch.get_width() + bl[0]
y = 0.5*patch.get_height() + bl[1]
ax.text(x,y, "%d%%" % (percentages[i,j]), ha="center")
ax.set_yticks(y_pos)
ax.set_yticklabels(people)
ax.set_xlabel('Distance')
plt.show()
Sie können ein Ergebnis in dieser Richtung erzielen (Anmerkung: Die Prozentangaben, die ich verwendet habe, haben nichts mit den Balkenbreiten zu tun, da das Verhältnis im Beispiel unklar erscheint):
Siehe Horizontal gestapeltes Balkendiagramm in Matplotlib für einige Ideen zum Stapeln von horizontalen Balkendiagrammen.
Trenton McKinney
Importiert und testet DataFrame
import pandas as pd
import numpy as np
# create sample data as shown in the OP
np.random.seed(365)
people = ('A','B','C','D','E','F','G','H')
bottomdata = 3 + 10 * np.random.rand(len(people))
topdata = 3 + 10 * np.random.rand(len(people))
# create the dataframe
df = pd.DataFrame({'Female': bottomdata, 'Male': topdata}, index=people)
# display(df)
Female Male
A 12.41 7.42
B 9.42 4.10
C 9.85 7.38
D 8.89 10.53
E 8.44 5.92
F 6.68 11.86
G 10.67 12.97
H 6.05 7.87
Getestet mit pandas 1.2.4die verwendet wird matplotlib als Plot-Engine und python 3.8.
ax = df.plot(kind='barh', stacked=True, figsize=(8, 6))
for c in ax.containers:
# customize the label to account for cases when there might not be a bar section
labels = [f'{w:.2f}%' if (w := v.get_width()) > 0 else '' for v in c ]
# set the bar label
ax.bar_label(c, labels=labels, label_type="center")
# uncomment and use the next line if there are no nan or 0 length sections; just use fmt to add a % (the previous two lines of code are not needed, in this case)
# ax.bar_label(c, fmt="%.2f%%", label_type="center")
# move the legend
ax.legend(bbox_to_anchor=(1.025, 1), loc="upper left", borderaxespad=0.)
# add labels
ax.set_ylabel("People", fontsize=18)
ax.set_xlabel("Percent", fontsize=18)
plt.show()
Die Diagramme entsprechen den unten gezeigten.
Anmerkungsressourcen – von matplotlib v3.4.2
Hinzufügen von Wertbeschriftungen zu einem Matplotlib-Balkendiagramm
So kommentieren Sie jedes Segment eines gestapelten Balkendiagramms
Gestapeltes Balkendiagramm mit zentrierten Beschriftungen
So zeichnen und kommentieren Sie mehrere Datenspalten in einem Seaborn-Balkendiagramm
So kommentieren Sie ein seegeborenes Balkendiagramm mit dem aggregierten Wert
So fügen Sie einem Balkendiagramm mehrere Anmerkungen hinzu
So zeichnen und kommentieren Sie ein gruppiertes Balkendiagramm
Ursprüngliche Antwort – vorher matplotlib v3.4.2
Der einfachste Weg, einen horizontal oder vertikal gestapelten Balken zu zeichnen, besteht darin, die Daten in a zu laden pandas.DataFrame
Dadurch wird korrekt geplottet und kommentiert, selbst wenn alle Kategorien ('People'), haben nicht alle Segmente (z. B. ist ein Wert 0 oder NaN)
Sobald sich die Daten im Datenrahmen befinden:
Es ist einfacher zu manipulieren und zu analysieren
Es kann mit geplottet werden matplotlib Motor, mit:
SO: Vertikal gestapeltes Balkendiagramm mit zentrierten Beschriftungen
Diese Methoden geben a zurück matplotlib.axes.Axes oder ein numpy.ndarray von ihnen.
Verwendung der .patches Methode entpackt eine Liste von matplotlib.patches.Rectangle Objekte, eines für jeden Abschnitt der gestapelten Leiste.
Jeder .Rectangle verfügt über Methoden zum Extrahieren der verschiedenen Werte, die das Rechteck definieren.
Jeder .Rectangle ist in der reihenfolge von links nach rechts und von unten nach oben, also alle .Rectangle Objekte für jede Ebene erscheinen beim Durchlaufen der Reihe nach .patches.
Die Etiketten werden mit einem erstellt f-Saite, label_text = f'{width:.2f}%'sodass bei Bedarf zusätzlicher Text hinzugefügt werden kann.
Zeichnen und kommentieren
Das Zeichnen des Balkens ist 1 Linie, der Rest ist das Kommentieren der Rechtecke
# plot the dataframe with 1 line
ax = df.plot.barh(stacked=True, figsize=(8, 6))
# .patches is everything inside of the chart
for rect in ax.patches:
# Find where everything is located
height = rect.get_height()
width = rect.get_width()
x = rect.get_x()
y = rect.get_y()
# The height of the bar is the data value and can be used as the label
label_text = f'{width:.2f}%' # f'{width:.2f}' to format decimal values
# ax.text(x, y, text)
label_x = x + width / 2
label_y = y + height / 2
# only plot labels greater than given width
if width > 0:
ax.text(label_x, label_y, label_text, ha="center", va="center", fontsize=8)
# move the legend
ax.legend(bbox_to_anchor=(1.05, 1), loc="upper left", borderaxespad=0.)
# add labels
ax.set_ylabel("People", fontsize=18)
ax.set_xlabel("Percent", fontsize=18)
plt.show()
Beispiel mit fehlendem Segment
# set one of the dataframe values to 0
df.iloc[4, 1] = 0
Beachten Sie, dass sich die Anmerkungen alle an der richtigen Stelle befinden df.
.
7898300cookie-checkstapeln Sie Balkendiagramm in Matplotlib und fügen Sie jedem Abschnitt eine Beschriftung hinzuyes