Skip to content

table.table fails with KeyError (legacy python and mpl 2.2.0 only) #10740

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
TomAugspurger opened this issue Mar 9, 2018 · 7 comments
Closed
Assignees
Milestone

Comments

@TomAugspurger
Copy link
Contributor

Bug report

Bug summary

In matplotlib 2.2 with python 2.7, the following code raises a KeyError. It succeeded in 1.5.3

Code for reproduction

import numpy as np
import matplotlib
import matplotlib.pyplot as plt


fig, ax = plt.subplots()
data = np.random.randn(10, 3)
rowLabels = list('abcdefghij')
colLabels = [0, 1, 2]

table = matplotlib.table.table(ax, cellText=data.T, rowLabels=colLabels,
                               colLabels=rowLabels)
fig.savefig('foo.png')

Actual outcome

Traceback (most recent call last):
  File "foo.py", line 14, in <module>
    fig.savefig('foo.png')
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/matplotlib/figure.py", line 2035, in savefig
    self.canvas.print_figure(fname, **kwargs)
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/matplotlib/backend_bases.py", line 2261, in print_figure
    **kwargs)
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/matplotlib/backends/backend_agg.py", line 511, in print_png
    FigureCanvasAgg.draw(self)
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/matplotlib/backends/backend_agg.py", line 431, in draw
    self.figure.draw(self.renderer)
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/matplotlib/artist.py", line 55, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/matplotlib/figure.py", line 1475, in draw
    renderer, self, artists, self.suppressComposite)
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/matplotlib/image.py", line 141, in _draw_list_compositing_images
    a.draw(renderer)
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/matplotlib/artist.py", line 55, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/matplotlib/axes/_base.py", line 2607, in draw
    mimage._draw_list_compositing_images(renderer, self, artists)
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/matplotlib/image.py", line 141, in _draw_list_compositing_images
    a.draw(renderer)
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/matplotlib/artist.py", line 55, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/matplotlib/table.py", line 350, in draw
    self._update_positions(renderer)
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/matplotlib/table.py", line 536, in _update_positions
    self._auto_set_font_size(renderer)
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/matplotlib/table.py", line 494, in _auto_set_font_size
    size = cell.auto_set_font_size(renderer)
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/matplotlib/table.py", line 95, in auto_set_font_size
    required = self.get_required_width(renderer)
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/matplotlib/table.py", line 144, in get_required_width
    l, b, w, h = self.get_text_bounds(renderer)
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/matplotlib/table.py", line 138, in get_text_bounds
    bbox = self._text.get_window_extent(renderer)
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/matplotlib/text.py", line 922, in get_window_extent
    bbox, info, descent = self._get_layout(self._renderer)
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/matplotlib/text.py", line 300, in _get_layout
    ismath=False)
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/matplotlib/backends/backend_agg.py", line 239, in get_text_width_height_descent
    font = self._get_agg_font(prop)
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/matplotlib/backends/backend_agg.py", line 273, in _get_agg_font
    fname = findfont(prop)
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/matplotlib/font_manager.py", line 1475, in findfont
    font = fontManager.findfont(prop, **kw)
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/matplotlib/font_manager.py", line 1290, in findfont
    rc_params)
  File "/Users/taugspurger/Envs/py27/lib/python2.7/site-packages/backports/functools_lru_cache.py", line 156, in wrapper
    del cache[oldkey]
KeyError: (<matplotlib.font_manager.FontManager object at 0x107661250>, <matplotlib.font_manager.FontProperties object at 0x10c3a11d0>, 'ttf', None, True, True, (('DejaVu Serif', 'Bitstream Vera Serif', 'Computer Modern Roman', 'New Century Schoolbook', 'Century Schoolbook L', 'Utopia', 'ITC Bookman', 'Bookman', 'Nimbus Roman No9 L', 'Times New Roman', 'Times', 'Palatino', 'Charter', 'serif'), ('DejaVu Sans', 'Bitstream Vera Sans', 'Computer Modern Sans Serif', 'Lucida Grande', 'Verdana', 'Geneva', 'Lucid', 'Arial', 'Helvetica', 'Avant Garde', 'sans-serif'), ('Apple Chancery', 'Textile', 'Zapf Chancery', 'Sand', 'Script MT', 'Felipa', 'cursive'), ('Comic Sans MS', 'Chicago', 'Charcoal', 'ImpactWestern', 'Humor Sans', 'xkcd', 'fantasy'), ('DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Computer Modern Typewriter', 'Andale Mono', 'Nimbus Mono L', 'Courier New', 'Courier', 'Fixed', 'Terminal', 'monospace')))

Expected outcome

Same as mpl 2.2

Matplotlib version

  • Operating system: MacOS 10.13.3
  • Matplotlib version: 2.2.0
  • Matplotlib backend: TkAgg
  • Python version: 2.7 (works correctly with 3.6)

Installed from wheels of PyPI.

@TomAugspurger
Copy link
Contributor Author

cc @tacaswell this came up in the pandas tests, but this example only uses NumPy.

One other thing to note, the code works fine when the data are not transposed. i.e.

import numpy as np
import matplotlib
import matplotlib.pyplot as plt


fig, ax = plt.subplots()
data = np.random.randn(10, 3)
rowLabels = list('abcdefghij')
colLabels = [0, 1, 2]

table = matplotlib.table.table(ax, cellText=data, rowLabels=rowLabels,
                               colLabels=colLabels)
fig.savefig('foo.png')

Doesn't throw an error on 2.7. Not sure if that helps or is just a red herring.

@tacaswell
Copy link
Member

This looks like a bug in functools.lru_cache that we are using to memoize some of the font look up.

Really odd that it only shows up with table.....

attn @anntzer

@anntzer
Copy link
Contributor

anntzer commented Mar 9, 2018

Right now I have no idea what is happening. Would suggest just reverting #9677 in 2.2.1. if you don't know either.

@tacaswell
Copy link
Member

I intend to revert #9677 on the 2.2.x branch and then deal with the merge-to-master conflicts.

@tacaswell tacaswell self-assigned this Mar 12, 2018
@QuLogic
Copy link
Member

QuLogic commented Mar 13, 2018

I don't know what's up with the error, but the non-transposed version does not fail simply because it hits the cache less. Probably it's because with a 3 column table, most of the rows end up off the figure and clipped.

The crash is definitely related to the cache being full, but I don't know why it fails with the backport and not the Python 3 version. Since all calls are using essentially the same font properties, the only thing that can change is self and prop. There's only one font manager, so it's really only prop:

$ cut -c 133-146 log | sort | uniq -c 
     16 0x7ff864e9c0d0
     17 0x7ff864e9c210
     16 0x7ff864e9c350
     14 0x7ff864e9c490
     16 0x7ff864e9c5d0
     16 0x7ff864e9cc10
     16 0x7ff864e9cd50
     16 0x7ff864ea8150
     16 0x7ff864ea8290
     16 0x7ff864ea8510
     16 0x7ff864ea8650
     16 0x7ff864ea8790
     16 0x7ff864ea8a10
     16 0x7ff864ea8b50
     16 0x7ff864ea8f10
     16 0x7ff864eb2090
      9 0x7ff864eb2310
      2 0x7ff864eb2450
      2 0x7ff864eb2590
      2 0x7ff864eb2810
      2 0x7ff864eb2950
      2 0x7ff864eb2bd0
      2 0x7ff864ebe250
      2 0x7ff864ebe390
      2 0x7ff864f0dcd0
     16 0x7ff864f0df50

The one with 17 calls is the one that raises. However, the first 16 instances are within the first 22 calls, and the failure is at call 296. So there's either a bug of evicting stuff from the cache earlier or at this point.

A workaround we could use is to pass maxsize=None which uses a simpler implementation but that removes the limit.

@QuLogic
Copy link
Member

QuLogic commented Mar 13, 2018

OK, so it turns out the difference between the two is that non-transposed can fit all text without shrinking text, so it always asks for 10 pt text, which never overflows the cache.

With the transposed table, the text does not fit, so it tries 10 pt, then decreasing sizes until it fits, which seems to be 3 pt, making for 8 times as many calls that miss the cache more often as it fills much quicker.

So easy workaround is to set the font size explicitly yourself: table.set_fontsize(3) Better workaround is to switch to Python 3 😉

tacaswell added a commit to tacaswell/matplotlib that referenced this issue Mar 16, 2018
This reverts commit 5730794, reversing
changes made to 1ca3d1a.

Fixes matplotlib#10740 by avoiding suspected bugs in backports.lru_cache
@tacaswell
Copy link
Member

closed by #10804

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants