2017-02-12 3 views
0

Nachdem ich mich mit Matplotlib länger herumgeschlagen habe, als ich gerne zugeben würde, indem ich versuche, etwas zu tun, das in so ziemlich jeder anderen Plot-Bibliothek, die ich je benutzt habe, ein Kinderspiel ist, habe ich beschlossen, das Stackiverse nach einem Einblick zu fragen. Kurz gesagt, was ich brauche, ist die Erstellung mehrerer horizontaler Balkendiagramme, die alle die x-Achse teilen, mit einer unterschiedlichen Anzahl von Werten auf der y-Achse und mit allen Balken gleicher Höhe, während sich die Diagramme selbst auf die Höhe von Einträge. Eine vereinfachte Datenstruktur von dem, was ich brauche, wäre so etwas wie zu zeichnen sein:Matplotlib - Dynamische (Balken) Diagrammhöhe basierend auf Daten?

[ 
    {"name": "Category 1", "entries": [ 
     {"name": "Entry 1", "value": 5}, 
     {"name": "Entry 2", "value": 2}, 
    ]}, 
    {"name": "Category 2", "entries": [ 
     {"name": "Entry 1", "value": 1}, 
    ]}, 
    {"name": "Category 3", "entries": [ 
     {"name": "Entry 1", "value": 1}, 
     {"name": "Entry 2", "value": 10}, 
     {"name": "Entry 3", "value": 4}, 
    ]},  
] 

Und die closesest ich bekam, was ich als Ergebnis möchte verwendet:

import matplotlib.pyplot as plt 

def plot_data(data): 
    total_categories = len(data) # holds how many charts to create 
    max_values = 1 # holds the maximum number of bars to create 
    for category in data: 
     max_values = max(max_values, len(category["entries"])) 
    fig = plt.figure(1) 
    ax = None 
    for index, category in enumerate(data): 
     entries = [] 
     values = [] 
     for entry in category["entries"]: 
      entries.append(entry["name"]) 
      values.append(entry["value"]) 
     if not entries: 
      continue # do not create empty charts 
     y_ticks = range(1, len(entries) + 1) 
     ax = fig.add_subplot(total_categories, 1, index + 1, sharex=ax) 
     ax.barh(y_ticks, values) 
     ax.set_ylim(0, max_values + 1) # limit the y axis for fixed height 
     ax.set_yticks(y_ticks) 
     ax.set_yticklabels(entries) 
     ax.invert_yaxis() 
     ax.set_title(category["name"], loc="left") 
    fig.tight_layout() 

Dies ist die halt Balkenhöhe gleich (zumindest in der Figur) egal wie viele Einträge eine bestimmte Kategorie haben, dank der y-Grenze (set_ylim()) auf die höchste Anzahl von Balken in den Daten gesetzt. Es wird jedoch auch unangenehme Lücken in Kategorien mit weniger als der maximalen Anzahl von Einträgen hinterlassen. Oder in einer visuellen Perspektive alles zu setzen, ich versuche, es zu bekommen von Actual zu Erwartete:

IMG LINK

Ich habe versucht, die Lücken durch gridspec und unterschiedlichen Skalen in Abhängigkeit der Anzahl Entfernen von Einträgen, aber das endete nur noch mehr "schief" und inkonsistent. Ich habe versucht, mehrere Diagramme zu erstellen, die Größe der Figuren zu manipulieren und sie dann in der Nachbearbeitung zusammenzufügen, aber ich konnte keinen Weg finden, die Balkenhöhe zuverlässig gleich bleiben zu lassen, egal welche Daten sie haben. Ich bin mir sicher, dass es einen Weg gibt, die benötigten Metriken für eine präzise Skalierung von einem obskuren Objekt in Matplotlib zu extrahieren, aber zu diesem Zeitpunkt habe ich Angst, dass ich eine weitere wilde Jagd machen werde, wenn ich versuche, den gesamten Ziehvorgang zu verfolgen.

Darüber hinaus, wenn einzelne Subplots um die Daten kollabiert werden können, wie könnte ich die Zahl basierend auf den Daten wachsen lassen? Wenn ich beispielsweise zu den oben genannten Daten eine vierte Kategorie hinzufügen würde, anstatt die Höhe durch ein anderes Diagramm "wachsen" zu lassen, verkleinert es tatsächlich alle Diagramme so, dass sie alle auf die Standardfigurgröße passen. Nun, ich denke, ich verstehe die Logik hinter Matplotlib mit Achseneinheiten und all das, und ich weiß, dass ich die Zahlengröße einstellen kann, um die Gesamthöhe zu erhöhen, aber ich habe keine Ahnung, wie man es konsistent über die Diagramme hält, nämlich wie man es hat die Balkenhöhe genau gleich, unabhängig von den Daten?

Muss ich wirklich alles manuell plotten, um zu bekommen, was ich will? Wenn ja, könnte ich einfach das ganze Matplotlib-Paket ausgeben und meine eigenen SVGs von Grund auf neu erstellen. Im Nachhinein, angesichts der Zeit, die ich damit verbracht habe, hätte ich das wahrscheinlich zuerst tun sollen, aber jetzt bin ich viel zu stur, um es aufzugeben (oder ich bin ein Opfer des gefürchteten Kostenirrtums).

Irgendwelche Ideen?

Dank

Antwort

0

Ich denke, der einzige Weg, zur gleichen Zeit gleich Breite (Breite in vertikaler Richtung) zu haben und subplotsizes unterschiedlich ist wirklich manuell die Achsen in der Figur zu positionieren.

Zu diesem Zweck können Sie angeben, wie groß in Zoll ein Balken sein soll und wie viel Abstand Sie zwischen den Unterplots in Einheiten dieser Balkenbreite haben möchten. Aus diesen Zahlen zusammen mit der Menge der zu plottenden Daten können Sie die Gesamthöhe in Zoll berechnen. Jeder der Teilplots wird dann (über fig.add_axes) entsprechend der Datenmenge und dem Abstand in den vorherigen Teilplots positioniert. Dadurch füllst du die Handlung schön aus. Wenn Sie einen neuen Datensatz hinzufügen, wird die Zahl größer.

data = [ 
    {"name": "Category 1", "entries": [ 
     {"name": "Entry 1", "value": 5}, 
     {"name": "Entry 2", "value": 2}, 
    ]}, 
    {"name": "Category 2", "entries": [ 
     {"name": "Entry 1", "value": 1}, 
    ]}, 
    {"name": "Category 3", "entries": [ 
     {"name": "Entry 1", "value": 1}, 
     {"name": "Entry 2", "value": 10}, 
     {"name": "Entry 3", "value": 4}, 
    ]}, 
    {"name": "Category 4", "entries": [ 
     {"name": "Entry 1", "value": 6}, 
    ]}, 
] 

import matplotlib.pyplot as plt 
import numpy as np 

def plot_data(data, 
       barwidth = 0.2, # inch per bar 
       spacing = 3, # spacing between subplots in units of barwidth 
       figx = 5,  # figure width in inch 
       left = 4,  # left margin in units of bar width 
       right=2):  # right margin in units of bar width 

    tc = len(data) # "total_categories", holds how many charts to create 
    max_values = [] # holds the maximum number of bars to create 
    for category in data: 
     max_values.append(len(category["entries"])) 
    max_values = np.array(max_values) 

    # total figure height: 
    figy = ((np.sum(max_values)+tc) + (tc+1)*spacing)*barwidth #inch 

    fig = plt.figure(figsize=(figx,figy)) 
    ax = None 
    for index, category in enumerate(data): 
     entries = [] 
     values = [] 
     for entry in category["entries"]: 
      entries.append(entry["name"]) 
      values.append(entry["value"]) 
     if not entries: 
      continue # do not create empty charts 
     y_ticks = range(1, len(entries) + 1) 
     # coordinates of new axes [left, bottom, width, height] 
     coord = [left*barwidth/figx, 
       1-barwidth*((index+1)*spacing+np.sum(max_values[:index+1])+index+1)/figy, 
       1-(left+right)*barwidth/figx, 
       (max_values[index]+1)*barwidth/figy ] 

     ax = fig.add_axes(coord, sharex=ax) 
     ax.barh(y_ticks, values) 
     ax.set_ylim(0, max_values[index] + 1) # limit the y axis for fixed height 
     ax.set_yticks(y_ticks) 
     ax.set_yticklabels(entries) 
     ax.invert_yaxis() 
     ax.set_title(category["name"], loc="left") 


plot_data(data) 
plt.savefig(__file__+".png") 
plt.show() 

enter image description here

+0

Dank, es funktioniert! Ich hoffte auf eine collapse_empty-Option oder etwas in der Art, da ich das einfachste Diagramm präsentierte, das zeigt, was ich brauche - wenn ich das Ganze im Wesentlichen messen und zeichnen sollte, könnte ich es auch direkt bei SVG tun. Man könnte meinen, dass eine solche populäre Plot-Bibliothek bereits ein einfaches Layout hat, das in einer benutzerfreundlichen Art und Weise sortiert wurde ... – zwer

+0

Mit dem Skript von oben müssen Sie nichts messen, alles wird automatisch gemacht. Ich denke, Ihre Anforderung ist wirklich so spezifisch, dass Sie eine Automatisierungsfunktion dafür erwarten. – ImportanceOfBeingErnest

+0

Ich habe versucht, nur das y-Abstandsproblem zu lösen (was dein Code tatsächlich löst), anstatt jemanden zu haben, der meine Arbeit für mich erledigt, in Wirklichkeit ist das, was ich versuche, ein bisschen komplexer, aber ich habe 'barh() 'und' text() 'arbeiten zusammen, um alles auf der x-Achse anzupassen, ohne dass vorher gemessen/geschätzt werden muss. Da in diesem Fall 'tight_layout()' nicht mit manuell gesetzten Achsen funktioniert, würden die Eintragsnamen größer sein, wenn sie nicht nach rechts verschoben werden (der linke Parameter), aber dann muss man messen Wie viel Platz würde der längste Eintragsname benötigen. – zwer

Verwandte Themen