has2k1 / plotnine

A Grammar of Graphics for Python
https://plotnine.org
MIT License
4k stars 213 forks source link

Errors with tz_aware datetime #687

Closed david-waterworth closed 1 year ago

david-waterworth commented 1 year ago

I have data with a datetime64[ns, Australia/Sydney] column which throws an error when I try and plot. A mre is

from plotnine.data import economics

# this works
(ggplot(economics)
 + geom_point(aes('date', 'psavert'))
 + labs(y='personal saving rate')
)

# change the datatype of date from datetime64[ns] (tz_niave] to datetime64[ns, Australia/Sydney]
economics['date'] = economics['date'].dt.tz_localize("Australia/Sydney")

# this throws an exception
(ggplot(economics)
 + geom_point(aes('date', 'psavert'))
 + labs(y='personal saving rate')
)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
File [.venv/lib/python3.10/site-packages/IPython/core/formatters.py:708](.venv/lib/python3.10/site-packages/IPython/core/formatters.py:708), in PlainTextFormatter.__call__(self, obj)
    701 stream = StringIO()
    702 printer = pretty.RepresentationPrinter(stream, self.verbose,
    703     self.max_width, self.newline,
    704     max_seq_length=self.max_seq_length,
    705     singleton_pprinters=self.singleton_printers,
    706     type_pprinters=self.type_printers,
    707     deferred_pprinters=self.deferred_printers)
--> 708 printer.pretty(obj)
    709 printer.flush()
    710 return stream.getvalue()

File [.venv/lib/python3.10/site-packages/IPython/lib/pretty.py:410](.venv/lib/python3.10/site-packages/IPython/lib/pretty.py:410), in RepresentationPrinter.pretty(self, obj)
    407                         return meth(obj, self, cycle)
    408                 if cls is not object \
    409                         and callable(cls.__dict__.get('__repr__')):
--> 410                     return _repr_pprint(obj, self, cycle)
    412     return _default_pprint(obj, self, cycle)
    413 finally:

File [.venv/lib/python3.10/site-packages/IPython/lib/pretty.py:778](.venv/lib/python3.10/site-packages/IPython/lib/pretty.py:778), in _repr_pprint(obj, p, cycle)
    776 """A pprint that just redirects to the normal repr function."""
    777 # Find newlines and replace them with p.break_()
--> 778 output = repr(obj)
    779 lines = output.splitlines()
    780 with p.group():

File [.venv/lib/python3.10/site-packages/plotnine/ggplot.py:114](.venv/lib/python3.10/site-packages/plotnine/ggplot.py:114), in ggplot.__repr__(self)
    110 def __repr__(self) -> str:
    111     """
    112     Print[/show](https://file+.vscode-resource.vscode-cdn.net/show) the plot
    113     """
--> 114     figure = self.draw(show=True)
    116     dpi = figure.get_dpi()
    117     W = int(figure.get_figwidth() * dpi)

File [.venv/lib/python3.10/site-packages/plotnine/ggplot.py:224](.venv/lib/python3.10/site-packages/plotnine/ggplot.py:224), in ggplot.draw(self, show)
    222 self = deepcopy(self)
    223 with plot_context(self, show=show):
--> 224     self._build()
    226     # setup
    227     figure, axs = self._create_figure()

File [.venv/lib/python3.10/site-packages/plotnine/ggplot.py:352](.venv/lib/python3.10/site-packages/plotnine/ggplot.py:352), in ggplot._build(self)
    349     layers.map(npscales)
    351 # Train coordinate system
--> 352 layout.setup_panel_params(self.coordinates)
    354 # fill in the defaults
    355 layers.use_defaults()

File [.venv/lib/python3.10/site-packages/plotnine/facets/layout.py:199](.venv/lib/python3.10/site-packages/plotnine/facets/layout.py:199), in Layout.setup_panel_params(self, coord)
    197 for i, j in self.layout[cols].itertuples(index=False):
    198     i, j = i - 1, j - 1
--> 199     params = coord.setup_panel_params(
    200         self.panel_scales_x[i], self.panel_scales_y[j]
    201     )
    202     self.panel_params.append(params)

File [.venv/lib/python3.10/site-packages/plotnine/coords/coord_cartesian.py:82](.venv/lib/python3.10/site-packages/plotnine/coords/coord_cartesian.py:82), in coord_cartesian.setup_panel_params(self, scale_x, scale_y)
     78     sv = scale.view(limits=coord_limits, range=ranges.range)
     79     return sv
     81 out = panel_view(
---> 82     x=get_scale_view(scale_x, self.limits.x),
     83     y=get_scale_view(scale_y, self.limits.y),
     84 )
     85 return out

File [.venv/lib/python3.10/site-packages/plotnine/coords/coord_cartesian.py:78](.venv/lib/python3.10/site-packages/plotnine/coords/coord_cartesian.py:78), in coord_cartesian.setup_panel_params..get_scale_view(scale, coord_limits)
     74 expansion = scale.default_expansion(expand=self.expand)
     75 ranges = scale.expand_limits(
     76     scale.limits, expansion, coord_limits, identity_trans
     77 )
---> 78 sv = scale.view(limits=coord_limits, range=ranges.range)
     79 return sv

File [.venv/lib/python3.10/site-packages/plotnine/scales/scale.py:884](.venv/lib/python3.10/site-packages/plotnine/scales/scale.py:884), in scale_continuous.view(self, limits, range)
    881     range = self.dimension(limits=limits)
    883 breaks = self.get_bounded_breaks(range)
--> 884 labels = self.get_labels(breaks)
    886 ubreaks = self.get_breaks(range)
    887 minor_breaks = self.get_minor_breaks(ubreaks, range)

File [.venv/lib/python3.10/site-packages/plotnine/scales/scale.py:1041](.venv/lib/python3.10/site-packages/plotnine/scales/scale.py:1041), in scale_continuous.get_labels(self, breaks)
   1038 breaks = self.inverse(breaks)
   1040 if self.labels is True:
-> 1041     labels: Sequence[str] = self.trans.format(breaks)
   1042 elif self.labels in (False, None):
   1043     labels = []

File [.venv/lib/python3.10/site-packages/mizani/formatters.py:613](.venv/lib/python3.10/site-packages/mizani/formatters.py:613), in date_format.__call__(self, x)
    606     if not all(value.tzinfo == tz for value in x):
    607         msg = (
    608             "Dates have different time zones. "
    609             "Choosen `{}` the time zone of the first date. "
    610             "To use a different time zone, create a "
    611             "formatter and pass the time zone."
    612         )
--> 613         warn(msg.format(tz.key))
    615 # The formatter is tied to axes and takes
    616 # breaks in ordinal format.
    617 x = [date2num(val) for val in x]

AttributeError: 'Australia/Sydney' object has no attribute 'key'
has2k1 commented 1 year ago

The bug is in mizani.

has2k1 commented 1 year ago

The fix is out, you can get with pip install --upgrade mizani or conda upgrade mizani.