FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Improve error messages for unit conversion by timhoffm · Pull Request #13005 · matplotlib/matplotlib · GitHub

Repository navigation

Improve error messages for unit conversion - #13005

Merged
timhoffm merged 1 commit into
matplotlib:masterfrom
timhoffm:unit-conversion-error-message
Jan 12, 2019
Merged

timhoffm merged 1 commit into
matplotlib:masterfrom
timhoffm:unit-conversion-error-message

Conversation

Copy link
Copy Markdown
Member

PR Summary

Fixes #12990. The call stack is a bit lengthy, but that's because the error happens several levels down and at least the reason gets clear.

Assigning categoricals to floats.

from matplotlib import pyplot as plt
xs = [1, 2, 3]
ys = [50, 10, 20]
ticks = ['banana', 'apple', 'pear']
plt.bar(xs, ys)
plt.xticks(ticks)

results in

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
~/dev/matplotlib/lib/matplotlib/axis.py in convert_units(self, x)
   1489         try:
-> 1490             ret = self.converter.convert(x, self.units, self)
   1491         except Exception as e:

~/dev/matplotlib/lib/matplotlib/category.py in convert(value, unit, axis)
     45             raise ValueError(
---> 46                 'Missing unit for StrCategoryConverter. This might be caused'
     47                 'by unintendedly mixing categorical and numeric data.')

ValueError: Missing unit for StrCategoryConverter. This might be causedby unintendedly mixing categorical and numeric data.

The above exception was the direct cause of the following exception:

ConversionError                           Traceback (most recent call last)
<ipython-input-2-8d8628e715b5> in <module>()
      4 ticks = ['banana', 'apple', 'pear']
      5 plt.bar(xs, ys)
----> 6 plt.xticks(ticks)

~/dev/matplotlib/lib/matplotlib/pyplot.py in xticks(ticks, labels, **kwargs)
   1518         labels = ax.get_xticklabels()
   1519     elif labels is None:
-> 1520         locs = ax.set_xticks(ticks)
   1521         labels = ax.get_xticklabels()
   1522     else:

~/dev/matplotlib/lib/matplotlib/axes/_base.py in set_xticks(self, ticks, minor)
   3303             Default is ``False``.
   3304         """
-> 3305         ret = self.xaxis.set_ticks(ticks, minor=minor)
   3306         self.stale = True
   3307         return ret

~/dev/matplotlib/lib/matplotlib/axis.py in set_ticks(self, ticks, minor)
   1689         """
   1690         # XXX if the user changes units, the information will be lost here
-> 1691         ticks = self.convert_units(ticks)
   1692         if len(ticks) > 1:
   1693             xleft, xright = self.get_view_interval()

~/dev/matplotlib/lib/matplotlib/axis.py in convert_units(self, x)
   1491         except Exception as e:
   1492             raise munits.ConversionError('Failed to convert value(s) to axis '
-> 1493                                          'units: %r' % x) from e
   1494         return ret
   1495 

ConversionError: Failed to convert value(s) to axis units: ['banana', 'apple', 'pear']

Assigning categoricals to dates.

from matplotlib import pyplot as plt
xs = [datetime(2015, 1, 1), datetime(2016, 1, 1), datetime(2017, 1, 1)]
ys = [50, 10, 20]
ticks = ['banana', 'apple', 'pear']
plt.bar(xs, ys)
plt.xticks(ticks)

results in

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
~/dev/matplotlib/lib/matplotlib/axis.py in convert_units(self, x)
   1489         try:
-> 1490             ret = self.converter.convert(x, self.units, self)
   1491         except Exception as e:

~/dev/matplotlib/lib/matplotlib/dates.py in convert(value, unit, axis)
   1805         """
-> 1806         return date2num(value)
   1807 

~/dev/matplotlib/lib/matplotlib/dates.py in date2num(d)
    421             return d
--> 422         return _to_ordinalf_np_vectorized(d)
    423 

~/miniconda3/envs/mpl/lib/python3.6/site-packages/numpy/lib/function_base.py in __call__(self, *args, **kwargs)
   2754 
-> 2755         return self._vectorize_call(func=func, args=vargs)
   2756 

~/miniconda3/envs/mpl/lib/python3.6/site-packages/numpy/lib/function_base.py in _vectorize_call(self, func, args)
   2824         else:
-> 2825             ufunc, otypes = self._get_ufunc_and_otypes(func=func, args=args)
   2826 

~/miniconda3/envs/mpl/lib/python3.6/site-packages/numpy/lib/function_base.py in _get_ufunc_and_otypes(self, func, args)
   2784             inputs = [arg.flat[0] for arg in args]
-> 2785             outputs = func(*inputs)
   2786 

~/dev/matplotlib/lib/matplotlib/dates.py in _to_ordinalf(dt)
    225 
--> 226     base = float(dt.toordinal())
    227 

AttributeError: 'numpy.str_' object has no attribute 'toordinal'

The above exception was the direct cause of the following exception:

ConversionError                           Traceback (most recent call last)
<ipython-input-3-e7444cbe73e7> in <module>()
      4 ticks = ['banana', 'apple', 'pear']
      5 plt.bar(xs, ys)
----> 6 plt.xticks(ticks)

~/dev/matplotlib/lib/matplotlib/pyplot.py in xticks(ticks, labels, **kwargs)
   1518         labels = ax.get_xticklabels()
   1519     elif labels is None:
-> 1520         locs = ax.set_xticks(ticks)
   1521         labels = ax.get_xticklabels()
   1522     else:

~/dev/matplotlib/lib/matplotlib/axes/_base.py in set_xticks(self, ticks, minor)
   3303             Default is ``False``.
   3304         """
-> 3305         ret = self.xaxis.set_ticks(ticks, minor=minor)
   3306         self.stale = True
   3307         return ret

~/dev/matplotlib/lib/matplotlib/axis.py in set_ticks(self, ticks, minor)
   1689         """
   1690         # XXX if the user changes units, the information will be lost here
-> 1691         ticks = self.convert_units(ticks)
   1692         if len(ticks) > 1:
   1693             xleft, xright = self.get_view_interval()

~/dev/matplotlib/lib/matplotlib/axis.py in convert_units(self, x)
   1491         except Exception as e:
   1492             raise munits.ConversionError('Failed to convert value(s) to axis '
-> 1493                                          'units: %r' % x) from e
   1494         return ret
   1495 

ConversionError: Failed to convert value(s) to axis units: ['banana', 'apple', 'pear']

timhoffm added this to the v3.0.3 milestone Dec 17, 2018
Comment thread lib/matplotlib/category.py Outdated
"""
if unit is None:
raise ValueError(
'Missing unit for StrCategoryConverter. This might be caused'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

So what's missing here is the mapping from category to float. Worried about using unit in a user facing error because unit is a kwarg that doesn't have to be passed in for categoricals to work.

timhoffm Dec 17, 2018 •
edited
Loading

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Worried about using unit in a user facing error because unit is a kwarg that doesn't have to be passed in for categoricals to work.

That's why I'm casting the error message to "Failed to convert..." using raise ... from.

So what's missing here is the mapping from category to float.

Not sure what that means. Moving the example from pyplot to ax to make it a little more obvious:

ax = plt.gca()
ax.bar([1, 2, 3], [50, 10, 20])
ax.set_xticks(['banana', 'apple', 'pear'])

We are trying to string positions (not labels) to a float axis. IMHO there's nothing missing here and this should error out. Ideally, the error message would be something like "Attemtping to set categorical ticks to a non-categorical axis".

But there's no good place to raise that.

The original stacktrace before this PR was:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-2-13c713392566> in <module>()
      2 ax = plt.gca()
      3 ax.bar([1, 2, 3], [50, 10, 20])
----> 4 ax.set_xticks(['banana', 'apple', 'pear'])

~/dev/matplotlib/lib/matplotlib/axes/_base.py in set_xticks(self, ticks, minor)
   3303             Default is ``False``.
   3304         """
-> 3305         ret = self.xaxis.set_ticks(ticks, minor=minor)
   3306         self.stale = True
   3307         return ret

~/dev/matplotlib/lib/matplotlib/axis.py in set_ticks(self, ticks, minor)
   1686         """
   1687         # XXX if the user changes units, the information will be lost here
-> 1688         ticks = self.convert_units(ticks)
   1689         if len(ticks) > 1:
   1690             xleft, xright = self.get_view_interval()

~/dev/matplotlib/lib/matplotlib/axis.py in convert_units(self, x)
   1488             return x
   1489 
-> 1490         ret = self.converter.convert(x, self.units, self)
   1491         return ret
   1492 

~/dev/matplotlib/lib/matplotlib/category.py in convert(value, unit, axis)
     51 
     52         # force an update so it also does type checking
---> 53         unit.update(values)
     54 
     55         str2idx = np.vectorize(unit._mapping.__getitem__,

AttributeError: 'NoneType' object has no attribute 'update'

So, you can raise

  1. in set_ticks(self, ticks, minor)
    Advantage: You know you are setting ticks, so you could issue "Attemtping to set categorical ticks to a non-categorical axis".
    Disadvantage: You need to explicitly check ticks and the unit of Axis.
  2. in convert_units(self, x)
    Advantage: You can investigate self.units and generate a more appropriate message related to categoricals.
    Disadvantage: You don't know that you are setting ticks here. You would have to explicitly check which converter you are using.
  3. in convert(value, unit, axis)
    Advantage: You know that unit must not be None so the check is trivial.
    Disadvantage: You don't know anything about categorical axes anymore and therefore can only state that unit is missing and add a guess why that might be the case.

In that situation I decided to raise on 3. add add context information in 2. using the raise .. from.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I'm not against the error, I worry that the specific word unit will lead users down a google rabbit hole that's misguided. Basically, there's an xunit/yunit kwarg:
https://matplotlib.org/gallery/units/bar_demo2.html#sphx-glr-gallery-units-bar-demo2-py

axs[1, 0].bar(cms, cms, bottom=bottom, width=width, xunits=inch, yunits=cm)

So I worry an error message with the word unit might make users think the fix is to pass in something, when really the issue is that the axis input data is not a string so the axis is not registered as string units. I wonder if the following is enough:

'Axis does not support StrCategoryConvertor; axis data values must be strings'

Like you, I want to help people catch that the problem is the mismatch between the plot data and the tick data.

timhoffm Jan 6, 2019 •
edited
Loading

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Rephrased to

            raise ValueError(
                'Missing category information for StrCategoryConverter; '
                'this might be caused by unintendedly mixing categorical and '
                'numeric data')

I don't want to mention an Axis as we don't know the call context here. In the end, this is also why we can only guess the reason for the failure but not make a clear statement what to change.

Comment thread lib/matplotlib/units.py Outdated
from matplotlib import cbook


class ConversionError(ValueError):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

this is a bit bikesheddy but it looks more like a TypeError than a ValueError to me.

Comment thread lib/matplotlib/category.py Outdated
if unit is None:
raise ValueError(
'Missing unit for StrCategoryConverter. This might be caused'
'by unintendedly mixing categorical and numeric data.')

anntzer Dec 17, 2018 •
edited
Loading

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Minor preference for error messages that are single sentence and without a final dot, as that's what we mostly have in the codebase (so replace the middle dot by a semicolon).
Also a missing space after "caused".

timhoffm force-pushed the unit-conversion-error-message branch from 387c345 to 83a3631 Compare December 17, 2018 19:21
timhoffm added the API: argument checking Validation of acceptable argument values label Dec 17, 2018
timhoffm force-pushed the unit-conversion-error-message branch 4 times, most recently from 4cebc36 to e38410c Compare January 6, 2019 22:56

Copy link
Copy Markdown
Member Author

Travis CI failure is due to #13137 and unrelated to this PR.

By me, this is ready to go in.

@staticmethod
def convert(value, unit, axis):
"""Converts strings in value to floats using
"""Convert strings in value to floats using

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Just inherit the docstring from units.ConversionInterface perhaps? (and possibly edit that docstring).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I don't think we can reasonably use an inherited docstring here. Using the conversion interface for string-index mapping is a bit of a stretch and justifies explicit description.

  • type of value is a string, which is rather the exception for ConversionInterface
  • mark unit as UnitData
  • note that axis is unused.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

sure

Comment thread lib/matplotlib/category.py Outdated
Comment thread lib/matplotlib/axis.py Outdated
timhoffm force-pushed the unit-conversion-error-message branch from e38410c to c9a0ac7 Compare January 12, 2019 16:14

anntzer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

anyone can merge (including tim) when ci passes.

Copy link
Copy Markdown
Member Author

Self-merging based on the reviews. The Travis CI failure is still due to #13137 (unrelated to this PR).

timhoffm merged commit c8933e0 into matplotlib:master Jan 12, 2019
timhoffm deleted the unit-conversion-error-message branch January 12, 2019 19:58
meeseeksmachine pushed a commit to meeseeksmachine/matplotlib that referenced this pull request Jan 12, 2019
tacaswell modified the milestones: v3.0.3, v3.1 Jan 14, 2019

Copy link
Copy Markdown
Member

Re-milestoned to 3.1 so we do not have to deal with backporting the f string and because there is a subtle API change (the type of the raise exception) in this.

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API: argument checking Validation of acceptable argument values

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants


Back | FazBrowse Home | New Git URL