ENH: Plotting support for ExtensionArrays - #64535
Conversation
- Add `_is_plotable` flag to ExtensionDtype - Add `_get_plot_converter()` method to ExtensionDtype to get type and ConversionInterface for matplotlib units registry - Include EAs in registration of ConversionInterfaces to matplotlib - Include plotable EA dtypes in `_compute_plot_data` dtype selection
|
@jbrockmendel gentle ping, does the proposed API for plotting looks fine to you? Before merging I would also want to add some tests and see if there are any internal EAs that could profit from adapting this API. |
| types: list[type | str | type[ExtensionDtype]] = [ | ||
| np.number, | ||
| "datetime", | ||
| "datetimetz", |
There was a problem hiding this comment.
i suspect this will go through plottable_ea_dtypes
|
This looks reasonable to me. How much effort is writing a custom converter? Can you reuse or thin-wrap around one of the existing ones? |
I think it is relatively straight forward, see the base implementation of the class ConversionInterface:
"""
The minimal interface for a converter to take custom data types (or
sequences) and convert them to values Matplotlib can use.
"""
@staticmethod
def axisinfo(unit, axis):
"""Return an `.AxisInfo` for the axis with the specified units."""
return None
@staticmethod
def default_units(x, axis):
"""Return the default unit for *x* or ``None`` for the given axis."""
return None
@staticmethod
def convert(obj, unit, axis):
"""
Convert *obj* using *unit* for the specified *axis*.
If *obj* is a sequence, return the converted sequence. The output must
be a sequence of scalars that can be used by the numpy array layer.
"""
return objYou want to implement As an example for astropy Quantity you could have a unit of I am now second guessing that it might be better to keep plotting for pandas internal extensions in |
|
I just saw that I get a <string>:1: UserWarning: Instantiating UnitsDtype without any arguments.Pass a UnitsDtype instance to silence this warning.This originates from: pandas/pandas/core/dtypes/common.py Lines 1895 to 1907 in 1bba48f Call stack:
Specifically the part in pandas/pandas/core/dtypes/common.py Lines 1769 to 1775 in 1bba48f So here we get the However earlier in pandas/pandas/core/dtypes/common.py Lines 1752 to 1755 in 1bba48f Does anyone know why were not also checking for subclasses of ExtensionDtype here? Could be done like so:
if isinstance(dtype, type) and issubclass(dtype, ExtensionDtype):
# Type object from a dtype
return dtype.type |
|
I spent some time on the select_dtypes thing and concluded the thing to do is avoid its usage in the plotting code in favor of something like |
|
Thank you @jbrockmendel for the idea, I finally found the time to make it work (I think), see changes of latest commit. As I a had to switch from string representation for dtypes to types directly, I am not sure if I cover all possible ways. Currently I have:
All tests in Also still did not implement the change suggested by Matthew, keeping |
…ing by default just return `None` in `_get_plot_converter`
| def predicate_for_plottability(blk_vals) -> bool: | ||
| dtype = blk_vals.dtype | ||
| if isinstance(dtype, ArrowDtype): | ||
| dtype = dtype.numpy_dtype |
There was a problem hiding this comment.
what happens with e.g. DecimalDtype? i think that would get selected in main (but isnt tested) but not here
…xtensionDtype: `DatetimeTZDtype`/`PeriodDtype`
…ity: - Includes tests `test_plot_on_x_axis` and `test_plot_on_y_axis` - Extends `ExtensionTests` to also inherit from `BasePlottingTests` - Adds `xfail` or `skip` marker to EAs test that cannot be plotted
|
@jbrockmendel I would welcome another round of review :) I added the
Also I removed the The following things should still be discussed:
|
| converter. The returned type is likely the same as ``cls._type``. The returned | ||
| converter should be a subclass of ``matplotlib.units.ConversionInterface``. | ||
| """ | ||
| return None |
There was a problem hiding this comment.
docstring note to return None for non-plottable?
|
|
||
| @classmethod | ||
| def _get_plot_converter( | ||
| cls, |
There was a problem hiding this comment.
is doing this as a classmethod going to be a problem in cases where dtype.type might vary across instances? e.g. ArrowDtype?
| request.applymarker(mark) | ||
| super().test_loc_setitem_with_expansion_retains_ea_dtype(data) | ||
|
|
||
| @pytest.mark.xfail(raises=TypeError, reason="no numeric data to plot") |
There was a problem hiding this comment.
should these be xfails or testing for the correct exception?
| # GH 18755, include object and category type for scatter plot | ||
| if self._kind == "scatter": | ||
| include_type.extend(["object", "category", "string"]) | ||
| include_type.extend([object, CategoricalDtypeType, str]) |
There was a problem hiding this comment.
i think object being in here means everything matches?
| A DataFrame with two columns: 'Data' containing the provided ExtensionArray and | ||
| 'Numeric' containing a range of integers. | ||
| """ | ||
| return pd.DataFrame({"Data": data, "Numeric": np.arange(len(data))}).dropna() |
There was a problem hiding this comment.
why are you dropping NA? are EAs with NAs not supported?
|
The "Minimum Version" CI jobs seems to fail due to an older I am not sure how to solve this, pin |
Yeah I would just use a pytest filterwarning. You can add it in |
…pyparsing >= 3.3.0 and matplotlib < 3.11
| def test_groupby_extension_agg(self, as_index, data_for_grouping): | ||
| super().test_groupby_extension_agg(as_index, data_for_grouping) | ||
|
|
||
| @pytest.mark.xfail(raises=TypeError, reason="no numeric data to plot") |
There was a problem hiding this comment.
i dont think this is really an xfail. i think this is the correct behavior and we should be testing with pytest.raises(...)
same for some of the other dtypes like this
…class as pyparsing is not available in every CI job
|
@mroeschke after adding the filterwarning to |
doc/source/whatsnew/vX.X.X.rstfile if fixing a bug or adding a new feature.AGENTS.md.Add plotting support for ExtensionArrays:
_is_plotableflag to ExtensionDtype_get_plot_converter()method to ExtensionDtype to get type and ConversionInterface for matplotlib units registry_compute_plot_datadtype selection