Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Version 2.2.0rc1 triggers TypeError #10476

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
ewels opened this issue Feb 15, 2018 · 15 comments · Fixed by #10538
Closed

Version 2.2.0rc1 triggers TypeError #10476

ewels opened this issue Feb 15, 2018 · 15 comments · Fixed by #10538
Labels
Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.
Milestone

Comments

@ewels
Copy link

ewels commented Feb 15, 2018

Bug report

Bug summary

MatPlotLib release v2.2.0rc1 triggers a previously unseen TypeError:

  File "/Users/philewels/miniconda2/envs/work/lib/python2.7/site-packages/matplotlib/offsetbox.py", line 81, in _get_packed_offsets
    sep = (total - sum(w_list)) / (len(w_list) - 1.)
TypeError: unsupported operand type(s) for -: 'NoneType' and 'float'

Code for reproduction

The line of code that triggers the error is within my Python tool MultiQC here.

Actual outcome

Module bismark raised an exception: Traceback (most recent call last):
  File "/Users/philewels/GitHub/MultiQC/scripts/multiqc", line 427, in multiqc
    output = mod()
  File "/Users/philewels/GitHub/MultiQC/multiqc/modules/bismark/bismark.py", line 167, in __init__
    self.bismark_mbias_plot()
  File "/Users/philewels/GitHub/MultiQC/multiqc/modules/bismark/bismark.py", line 496, in bismark_mbias_plot
    plot = linegraph.plot(datasets, pconfig)
  File "/Users/philewels/GitHub/MultiQC/multiqc/plots/linegraph.py", line 139, in plot
    return matplotlib_linegraph(plotdata, pconfig)
  File "/Users/philewels/GitHub/MultiQC/multiqc/plots/linegraph.py", line 402, in matplotlib_linegraph
    plt.tight_layout(rect=[0,0.08,1,0.92])
  File "/Users/philewels/miniconda2/envs/work/lib/python2.7/site-packages/matplotlib/pyplot.py", line 1366, in tight_layout
    fig.tight_layout(pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect)
  File "/Users/philewels/miniconda2/envs/work/lib/python2.7/site-packages/matplotlib/figure.py", line 2273, in tight_layout
    pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect)
  File "/Users/philewels/miniconda2/envs/work/lib/python2.7/site-packages/matplotlib/tight_layout.py", line 328, in get_tight_layout_figure
    pad=pad, h_pad=h_pad, w_pad=w_pad)
  File "/Users/philewels/miniconda2/envs/work/lib/python2.7/site-packages/matplotlib/tight_layout.py", line 115, in auto_adjust_subplotpars
    if ax.get_visible()])
  File "/Users/philewels/miniconda2/envs/work/lib/python2.7/site-packages/matplotlib/axes/_base.py", line 4179, in get_tightbbox
    bb.append(child._legend_box.get_window_extent(renderer))
  File "/Users/philewels/miniconda2/envs/work/lib/python2.7/site-packages/matplotlib/offsetbox.py", line 262, in get_window_extent
    w, h, xd, yd, offsets = self.get_extent_offsets(renderer)
  File "/Users/philewels/miniconda2/envs/work/lib/python2.7/site-packages/matplotlib/offsetbox.py", line 384, in get_extent_offsets
    for c in self.get_visible_children()]
  File "/Users/philewels/miniconda2/envs/work/lib/python2.7/site-packages/matplotlib/offsetbox.py", line 255, in get_extent
    w, h, xd, yd, offsets = self.get_extent_offsets(renderer)
  File "/Users/philewels/miniconda2/envs/work/lib/python2.7/site-packages/matplotlib/offsetbox.py", line 474, in get_extent_offsets
    sep, self.mode)
  File "/Users/philewels/miniconda2/envs/work/lib/python2.7/site-packages/matplotlib/offsetbox.py", line 81, in _get_packed_offsets
    sep = (total - sum(w_list)) / (len(w_list) - 1.)
TypeError: unsupported operand type(s) for -: 'NoneType' and 'float'

Expected outcome

Last tested on MatPlotLib v2.1.2 and worked fine. Code has been in use for over two years.

Matplotlib version

  • Operating system: OSX, Linux
  • Matplotlib version: v2.2.0rc1
  • Matplotlib backend (print(matplotlib.get_backend())): MacOSX
  • Python version: 2.7, 3.4, 3.5, 3.6
  • Other libraries: MultiQC: https://github.com/ewels/MultiQC

matplotlib installed using pip, python installed using conda locally (OSX) and on Travis CI.

@ImportanceOfBeingErnest
Copy link
Member

@ewels Would you mind providing a minimal self-contained example? Did you try with a different backend an can report any differences?

@ewels
Copy link
Author

ewels commented Feb 15, 2018

Yup, I'll see if I can put something together.

I first found the error because of my tests started failing on Travis (Linux with multiple python versions). The only thing that I could see that had changed since the previous tests was that the matplotlib version had bumped from v2.1.2 to 2.2.0rc1. I then tried locally on my machine (OSX py2.7) and couldn't replicate it until I forced an upgrade to 2.2.0rc1, then I got the same failure. So seems to be the same failure across a range of backends.

Phil

@tacaswell
Copy link
Member

My guess is someplace in tight_layout needs to more aggressively force a draw to make user all of the sizes we sort out at draw time are set before it tries to optimize the sizes.

Strongly suspect that total in that last line is None and tracing back from there.

@tacaswell tacaswell added this to the v2.2.0 milestone Feb 15, 2018
@tacaswell tacaswell added the Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions. label Feb 15, 2018
@ewels
Copy link
Author

ewels commented Feb 15, 2018

Ok, after a lot of trial and error I think I have whittled it down to a minimal example:

#!/usr/bin/env python

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
print matplotlib.__version__ # 2.2.0rc1

fig = plt.figure()
axes = fig.add_subplot(111)

d1 = [29388871, 12448, 40, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
d2 = [28396236, 981940, 22171, 537, 123, 88, 41, 42, 40, 26, 26, 84, 6, 2, 0, 0, 0, 0, 0]
axes.plot(d1, label='series 1')
axes.plot(d2, label='series 2')
axes.legend(mode='expand')

#### THIS IS WHERE THE CRASH HAPPENS
plt.tight_layout(rect=[0,0.08,1,0.92])

fig.savefig('test.png', format='png', bbox_inches='tight')
plt.close(fig)

With the previous version:

$ python mpl_test.py
2.1.0

test

With the new version:

$ python mpl_test.py
2.2.0rc1
Traceback (most recent call last):
  File "mpl_test.py", line 18, in <module>
    plt.tight_layout(rect=[0,0.08,1,0.92])
  File "/Users/ewels/miniconda2/envs/matplotlib_bug/lib/python2.7/site-packages/matplotlib/pyplot.py", line 1366, in tight_layout
    fig.tight_layout(pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect)
  File "/Users/ewels/miniconda2/envs/matplotlib_bug/lib/python2.7/site-packages/matplotlib/figure.py", line 2273, in tight_layout
    pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect)
  File "/Users/ewels/miniconda2/envs/matplotlib_bug/lib/python2.7/site-packages/matplotlib/tight_layout.py", line 328, in get_tight_layout_figure
    pad=pad, h_pad=h_pad, w_pad=w_pad)
  File "/Users/ewels/miniconda2/envs/matplotlib_bug/lib/python2.7/site-packages/matplotlib/tight_layout.py", line 115, in auto_adjust_subplotpars
    if ax.get_visible()])
  File "/Users/ewels/miniconda2/envs/matplotlib_bug/lib/python2.7/site-packages/matplotlib/axes/_base.py", line 4179, in get_tightbbox
    bb.append(child._legend_box.get_window_extent(renderer))
  File "/Users/ewels/miniconda2/envs/matplotlib_bug/lib/python2.7/site-packages/matplotlib/offsetbox.py", line 262, in get_window_extent
    w, h, xd, yd, offsets = self.get_extent_offsets(renderer)
  File "/Users/ewels/miniconda2/envs/matplotlib_bug/lib/python2.7/site-packages/matplotlib/offsetbox.py", line 384, in get_extent_offsets
    for c in self.get_visible_children()]
  File "/Users/ewels/miniconda2/envs/matplotlib_bug/lib/python2.7/site-packages/matplotlib/offsetbox.py", line 255, in get_extent
    w, h, xd, yd, offsets = self.get_extent_offsets(renderer)
  File "/Users/ewels/miniconda2/envs/matplotlib_bug/lib/python2.7/site-packages/matplotlib/offsetbox.py", line 481, in get_extent_offsets
    return width + 2 * pad, height + 2 * pad, \
TypeError: unsupported operand type(s) for +: 'NoneType' and 'float'

@afvincent
Copy link
Contributor

@ewels Thank you for the working example :).

FWIW, git bisect says

fc04c6273f84d043b682e435a04ba3012bbe2343 is the first bad commit
commit fc04c6273f84d043b682e435a04ba3012bbe2343
Author: Jody Klymak <[email protected]>
Date:   Wed Sep 6 17:26:04 2017 -0700

    include overspilling axes legends in ax.get_tightbbox
    
    Added whats-new entry

Pinging @jklymak who is the author of the relevant commit.

@dstansby
Copy link
Member

A bit more digging reveals that I think the problem isn't actually that new, but that commit just exposed it. The following bit of code when mode = 'expand' doesn't handle the case where total = None, and just passes None through, whereas I suspect it should return a number (like the bit in the above if statement)

if mode == "fixed":
offsets_ = np.cumsum([0] + [w + sep for w in w_list])
offsets = offsets_[:-1]
if total is None:
total = offsets_[-1] - sep
return total, offsets
elif mode == "expand":
if len(w_list) > 1:
sep = (total - sum(w_list)) / (len(w_list) - 1.)
else:
sep = 0
offsets_ = np.cumsum([0] + [w + sep for w in w_list])
offsets = offsets_[:-1]
return total, offsets

@afvincent
Copy link
Contributor

afvincent commented Feb 15, 2018

Based on what @dstansby noticed, I may have a fix.

@jklymak
Copy link
Member

jklymak commented Feb 15, 2018

Sorry, at a meeting, and won’t have time or help. Glad I helped expose a bug: of course before my fix, tight layout would have ignored the legend altogether.

@afvincent
Copy link
Contributor

Well it is still a bit unclear to me why the following seems to be fixing the issue:

    elif mode == "expand":
 
       if total is None:
            total = np.nextafter(1, 2) - 1  # <-- ?!!!! Very unrealistic total length...

        if len(w_list) > 1:
            sep = (total - sum(w_list)) / (len(w_list) - 1.)
        else:
            sep = 0
        offsets_ = np.cumsum([0] + [w + sep for w in w_list])
        offsets = offsets_[:-1]

        return total, offsets

Initially I was planning to use something like

...
    if total is None:
        total = 1.0
...

but then I realized that offsetbox seems to be working in display units, which I thought would be a problem as the modified function is not a method and does not know what the renderer is... “Fortunately”, any finite value seems to fix the issue reported in this thread, which seems weird to me :/

@tacaswell
Copy link
Member

@afvincent I suspect the issue is that this is drawn in two passes so the first value does not really matter, but just needs to be not None.

@afvincent
Copy link
Contributor

@tacaswell Thanks for the plausible explanation :), that would indeed make sense. And thank you for the PR (I was waiting to better understand what was going on here before opening the promised PR but as I did not make any progress on it...).

@ewels
Copy link
Author

ewels commented Feb 21, 2018

Thank you very much everyone! That was a speedy diagnosis and fix indeed.. :)

How does the release cycle work for matplotlib? Will this release candidate 1 be superseded in the near future? (I’m wondering whether I need to update my tool requirements to explicitly avoid it).

Phil

@dstansby
Copy link
Member

RC1 will turn into the proper 2.2 release at some point, so no need to explicitly avoid RC1.

@ewels
Copy link
Author

ewels commented Feb 21, 2018

Aha, yes - just found the milestone. Ok great, thanks!

@tacaswell
Copy link
Member

The RC seems to be going relatively well, I don't think there will be an RC2 before 2.2 final.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants