matplotlib Legende bewirkt divide by zero

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
frixhax
User
Beiträge: 39
Registriert: Donnerstag 21. April 2011, 14:06

Hallo Leute!

Ich lasse matplotlib einen Graphen plotten und bekomme nachfolgenden Fehler. In dem, was ich plotte, wird nirgends dividiert, ich weiss also nicht, wo er das "divide by zero hernimmt. Python gibt das eigentlich nur als Warning aus, erst numpy.seterr(all="raise") liefert mir diese Fehlermeldung. Das Resultat ist in jedem Fall, dass der Plot nicht erzeugt wird. Ich habe es soweit runtergebrochen, als dass ich weiss, dass es an der Legende liegt. Diese hat einen einzigen Eintrag:

Code: Alles auswählen

print ax.get_legend_handles_labels()
([<matplotlib.lines.Line2D object at 0x24b9550>], ['i_s'])
und ich passe sie an mit

Code: Alles auswählen

plt.legend(bbox_to_anchor=(0., 1.02, 1., 0.102), loc=3, mode='expand', numpoints=1, borderaxespad=0.)
Wenn ich nun

Code: Alles auswählen

mode='expand'
auskommentiere, geht es (das moechte ich aber nicht). Seltsam ist, dass ich mehrere Datensaetze auf diese Weise plotte und nur dieser springt raus. Der Legendeneintrag ist aber bei allen derselbe...
Kann mir jemand helfen, wie ich rausbekomme, was nun diesen Fehler verursacht?
Hier der Fehler:

Code: Alles auswählen

  
Traceback (most recent call last):
File "bostepp_comb_results_v01.py", line 106, in <module>
    blocklen_i, blocklen_r)
File "/net/home/mars/gds/BOSTEPP/bostepp_defs_v12_2.py", line 1114, in plot_merge_results
    plt.savefig(str(workdir)+'/plots/lc2_'+str("%.2f" % diff)+'_'+str("%.5f" % ra[0])+'_'+str("%.5f" % dec[0])+'_r.png', facecolor='white')
File "/usr/lib64/python2.6/site-packages/matplotlib/pyplot.py", line 472, in savefig
    return fig.savefig(*args, **kwargs)
  File "/usr/lib64/python2.6/site-packages/matplotlib/figure.py", line 1363, in savefig
    self.canvas.print_figure(*args, **kwargs)
  File "/usr/lib64/python2.6/site-packages/matplotlib/backend_bases.py", line 2093, in print_figure
    **kwargs)
  File "/usr/lib64/python2.6/site-packages/matplotlib/backends/backend_agg.py", line 491, in print_png
    FigureCanvasAgg.draw(self)
  File "/usr/lib64/python2.6/site-packages/matplotlib/backends/backend_agg.py", line 439, in draw
    self.figure.draw(self.renderer)
  File "/usr/lib64/python2.6/site-packages/matplotlib/artist.py", line 54, in draw_wrapper
    draw(artist, renderer, *args, **kwargs)
  File "/usr/lib64/python2.6/site-packages/matplotlib/figure.py", line 999, in draw
    func(*args)
  File "/usr/lib64/python2.6/site-packages/matplotlib/artist.py", line 54, in draw_wrapper
    draw(artist, renderer, *args, **kwargs)
  File "/usr/lib64/python2.6/site-packages/matplotlib/axes.py", line 2086, in draw
    a.draw(renderer)
  File "/usr/lib64/python2.6/site-packages/matplotlib/artist.py", line 54, in draw_wrapper
    draw(artist, renderer, *args, **kwargs)
  File "/usr/lib64/python2.6/site-packages/matplotlib/legend.py", line 464, in draw
    bbox = self._legend_box.get_window_extent(renderer)
  File "/usr/lib64/python2.6/site-packages/matplotlib/offsetbox.py", line 242, in get_window_extent
    w, h, xd, yd, offsets = self.get_extent_offsets(renderer)
  File "/usr/lib64/python2.6/site-packages/matplotlib/offsetbox.py", line 334, in get_extent_offsets
    whd_list = [c.get_extent(renderer) for c in self.get_visible_children()]
  File "/usr/lib64/python2.6/site-packages/matplotlib/offsetbox.py", line 235, in get_extent
    w, h, xd, yd, offsets = self.get_extent_offsets(renderer)
  File "/usr/lib64/python2.6/site-packages/matplotlib/offsetbox.py", line 412, in get_extent_offsets
    sep, self.mode)
  File "/usr/lib64/python2.6/site-packages/matplotlib/offsetbox.py", line 76, in _get_packed_offsets
    sep = (total - sum(w_list)) / (len(w_list) - 1.)
FloatingPointError: divide by zero encountered in double_scalars
Danke!

frix
BlackJack

@frixhax: Die Quelltextzeile die letztendlich dafür verantwortlich ist, steht ja ganz unten im Traceback. Wenn die Länge von `w_list` 1 ist, dann bekommt man dort 0 als Nenner. Wenn man in die Funktion schaut, dann ist `w_list` eine Liste mit den Breiten von Kästen die nebeneinander platziert werden sollen und `sep` ist der Abstand zwischen ihnen. Die Programmierer haben hier offenbar den Fall das es nur einen Kasten gibt nicht berücksichtigt. Könnte man als Fehler ansehen.

Falls der zweite Rahmen im Traceback unter Deiner Kontrolle ist: Wie dort der Dateiname zusammen gesetzt wird, ist ja ziemlich haarsträubend. Der ``%``-Operator ist ja ganz offensichtlich bekannt und trotzdem wird das wild mit `str()` und ``+`` vermischt.
Benutzeravatar
frixhax
User
Beiträge: 39
Registriert: Donnerstag 21. April 2011, 14:06

BlackJack hat geschrieben:@frixhax: Die Quelltextzeile die letztendlich dafür verantwortlich ist, steht ja ganz unten im Traceback. Wenn die Länge von `w_list` 1 ist, dann bekommt man dort 0 als Nenner. Wenn man in die Funktion schaut, dann ist `w_list` eine Liste mit den Breiten von Kästen die nebeneinander platziert werden sollen und `sep` ist der Abstand zwischen ihnen. Die Programmierer haben hier offenbar den Fall das es nur einen Kasten gibt nicht berücksichtigt. Könnte man als Fehler ansehen.

Falls der zweite Rahmen im Traceback unter Deiner Kontrolle ist: Wie dort der Dateiname zusammen gesetzt wird, ist ja ziemlich haarsträubend. Der ``%``-Operator ist ja ganz offensichtlich bekannt und trotzdem wird das wild mit `str()` und ``+`` vermischt.
Danke fuer die Antwort. Gibt es eine Moeglichkeit, fuer den zweiten Kasten quasi einen leeren Dummy zu setzen, sodass "expand" auch mit nur einem Legendeneintrag funktioniert? Oder einen besseren Weg?

Zu deinem Tipp: Danke, aber wie wuerdest du diesen String zusammensetzen, ausser mit '%' aus den Floats Strings zu erzeugen? Hast du einen alternativen Code-Vorschlag?
BlackJack

@frixhax: Naja diese ganzen `str()`-Aufrufe und ``+``-Operationen weglassen und *eine* Zeichenkette mit mehreren Platzhaltern verwenden und dann nur einmal mit dem ``%``-Operator die Werte dort hinein formatieren. Zumal die `str()`-Aufrufe auch jetzt schon überflüssig sind, denn das Ergebnis von ``%`` auf eine Zeichenkette angewandt ist ja schon eine Zeichenkette.
Benutzeravatar
/me
User
Beiträge: 3556
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Zusätzlich sollte man dann noch die manuelle Pfadverkettung durch die Verwendung von os.path.join ersetzen.

Code: Alles auswählen

path = os.path.join(workdir,
                    'plots',
                    'lc2_%.2f_%.5f_%.5f_r.png' % (diff, ra[0], dec[0]))
Benutzeravatar
frixhax
User
Beiträge: 39
Registriert: Donnerstag 21. April 2011, 14:06

Danke Leute, das ist super :)

Aber zum urspruenglichen Problem, hat da jemand eine Idee? Also wie das expand-Problem vermeiden kann, wenn ich nur einen Eintrag in der Legende habe?
BlackJack

@frixhax: Ich würde wahrscheinlich per „monkey patching” bei `matplotlib.offsetbox._get_packed_offsets()` eine Funktion dazwischen schieben die den Sonderfall abfängt und entsprechend richtig berechnet.

Und dann auch schauen, dass ein Bugreport bei den `matlotlib`-Leuten ankommt, falls es dazu noch keinen gibt und das Problem in der aktuellen Entwicklerversion auch noch existiert.
Benutzeravatar
frixhax
User
Beiträge: 39
Registriert: Donnerstag 21. April 2011, 14:06

Ok danke. Was mich am meisten aergert ist nicht, dass expand bei einem Legenden-Eintrag nicht funktioniert, sondern dass es eben manchmal doch geht. Ich versteh's einfach nicht.
Benutzeravatar
frixhax
User
Beiträge: 39
Registriert: Donnerstag 21. April 2011, 14:06

Hier uebrigens ein (eben leider nicht) lauffaehiges Beispiel, was den Fehler produziert.

Code: Alles auswählen

import numpy as np
import matplotlib.pyplot as plt

x_i = [11.7574075935, 11.665207135799999, 11.6762413105, 11.6580992311, 11.656368388500001]
x_r = []
dates = [2.83611000e-01,   2.69330463e+02,   2.70280648e+02,   2.71359248e+02,   2.72320822e+02]
        
diff = 0.16
ra = [0., 110.5349726]
dec = [0., -16.1061281]
med_i = np.median(x_i)
med_r = np.median(x_r)

plt.figure("i_only", figsize=(14.40, 9.00), dpi=100)
if x_r == []:
    plt.plot(dates, np.asarray(x_i), 'r-', label = 'i_s')
    plt.title('i_mag', fontsize='16')
else:
    plt.plot(dates, np.asarray(x_r), 'g-', label = 'r_s')
    plt.plot(dates, np.asarray(x_i), 'r-', label = 'i_s')
    plt.title('i_mag', fontsize='16')
plt.rcParams['xtick.major.pad']=10
plt.rcParams['ytick.major.pad']=10
ax = plt.gca()
ax.title.set_y(1.1)
formy = plt.ScalarFormatter()
formy.set_powerlimits((-5, 5))
formy.set_scientific(False)
ax.yaxis.set_major_formatter(formy)
ax.set_ylim(ax.get_ylim()[::-1])
for tick in ax.xaxis.get_major_ticks():
    tick.label.set_fontsize(16)
for tick in ax.yaxis.get_major_ticks():
    tick.label.set_fontsize(16)
plt.xlabel('Days', fontsize='20', labelpad=20)
plt.ylabel('normalized magnitude / mag', fontsize='20', labelpad=20)

if x_r == []:
    plt.legend(bbox_to_anchor=(0., 1.02, 1., 0.102), loc=3, mode='expand',
               numpoints=1, ncol=2, borderaxespad=0.)
else:
    plt.legend(bbox_to_anchor=(0., 1.02, 1., 0.102), loc=3, mode='expand',
               numpoints=1, ncol=2, borderaxespad=0.)
leg = plt.gca().get_legend()
ltext = leg.get_texts()
plt.setp(ltext, fontsize='16')
plt.savefig('lc0.png', facecolor='white', bbox_inches='tight')
plt.close("i_only")
Benutzeravatar
Sr4l
User
Beiträge: 1091
Registriert: Donnerstag 28. Dezember 2006, 20:02
Wohnort: Kassel
Kontaktdaten:

Wie wär es damit einfach kein leeres `x_r = []` zu plotten?

PS: Wenn du keine Werte hast könntest du es mit `NaN`oder ähnlichem füllen.
Benutzeravatar
frixhax
User
Beiträge: 39
Registriert: Donnerstag 21. April 2011, 14:06

Wie aus

Code: Alles auswählen

if x_r == []:
    plt.plot(dates, np.asarray(x_i), 'r-', label = 'i_s')
    plt.title('i_mag', fontsize='16')
ersichtlich, wird x_r nicht geplottet, wenn es leer ist - wenn ich das richtig verstehe. In dem Falle existiert also nur ein Plot und auch nur ein label...
Benutzeravatar
Sr4l
User
Beiträge: 1091
Registriert: Donnerstag 28. Dezember 2006, 20:02
Wohnort: Kassel
Kontaktdaten:

Das stimmt, aber numpy reagiert halt manchmal seltsam auf unerwartete Eingaben.

Code: Alles auswählen

In [2]: np.median([])
C:\Dev\Python27\lib\site-packages\numpy\core\_methods.py:57: RuntimeWarning: invalid value encountered in double_scalars
  ret = ret / float(rcount)
Out[2]: nan

In [3]: np.median([np.nan, np.nan])
Out[3]: nan
Füll die Liste mit nans und lass die Sonderbehandlungen weg und alles sollte gehen.

Code: Alles auswählen

x_r = [np.nan]*len(dates) if x_r == [] else x_r
Auch matplotlib kann super mit nan arbeiten es stellt die Datenlücken einfach nicht dar.
Benutzeravatar
Sr4l
User
Beiträge: 1091
Registriert: Donnerstag 28. Dezember 2006, 20:02
Wohnort: Kassel
Kontaktdaten:

Habe es mir nochmal angeschaut, der Fehler liegt in

Code: Alles auswählen

if x_r == []:
    plt.legend(bbox_to_anchor=(0., 1.02, 1., 0.102), loc=3, mode='expand',
               numpoints=1, ncol=2, borderaxespad=0.)
aus irgendeinem Grund ist mode='expand' bei einer Linie keine gültige Wahl.
Benutzeravatar
frixhax
User
Beiträge: 39
Registriert: Donnerstag 21. April 2011, 14:06

Wie meinst du, bei einer Linie? Expand funktioniert bei allen Plots, wo ich zwei Datensaetze (x_i und x_r) als Linie in eine Figure plotte. Es klappt nur nicht, wenn ich nur x_i plotte.
Benutzeravatar
frixhax
User
Beiträge: 39
Registriert: Donnerstag 21. April 2011, 14:06

Und die Idee war gut, funkioniert aber nicht: Wenn ich das so mache muss ich entweder fuer diesen Dummy-Plot, der in der Figure dann unsichtbar ist, ein label angeben - und das wird dann in der Legende ausgegeben, auch wenn der Graph dazu ja nicht da ist. Oder ich gebe kein Label an und bekomme den altbekannte fehler vom expand.
BlackJack

@frixhax: Der Vorschlag war wohl auch eher nicht 'expand' zu nehmen wenn nur eine Datenreihe geplottet wird.

Ich habe es mal mit einer gepatchten Funktion probiert, dann kracht es aber an anderer Stelle im `matplotlib`-Code.
Benutzeravatar
frixhax
User
Beiträge: 39
Registriert: Donnerstag 21. April 2011, 14:06

Hm ok ;) Ich denke ich werde einfach bei allen plots auf expand verzichten. Schade, es sah so huebsch aus...
Benutzeravatar
Sr4l
User
Beiträge: 1091
Registriert: Donnerstag 28. Dezember 2006, 20:02
Wohnort: Kassel
Kontaktdaten:

du musst nur darauf verzichten, wenn die Legende nur ein Element (Linie) hat
Benutzeravatar
frixhax
User
Beiträge: 39
Registriert: Donnerstag 21. April 2011, 14:06

Jau, aber da ich fuer ein Paper alle Plots einheitlich haben muss... ;) Aber es funktioniert mit folgendem Workaround:

https://github.com/matplotlib/matplotlib/pull/1864
Antworten