Skip to content

Complete the signature of plot methods with all their kind options #1523

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

Merged
merged 9 commits into from
Mar 18, 2025

Conversation

Azaya89
Copy link
Collaborator

@Azaya89 Azaya89 commented Mar 5, 2025

fixes #1479

this PR:

  • makes sure the options already defined in the plot signature are correctly documented in the docstring
  • adds all the options not defined in the plot signature but in _kind_options to the signature.

@Azaya89 Azaya89 self-assigned this Mar 5, 2025
@Azaya89
Copy link
Collaborator Author

Azaya89 commented Mar 5, 2025

The column keyword in the dataset method was not documented yet 'cos I can't figure out what it does:

import hvplot.pandas
import pandas as pd

data = pd.DataFrame({"x": ['a', 'b', 'c'], "y": [1, 2, 3]})

ds1 = data.hvplot.dataset()
ds2 = data.hvplot.dataset(columns='x')

# Check the underlying DataFrame columns
print("Columns in ds1:", ds1.data.columns)
print("Columns in ds2:", ds2.data.columns)
print("kdims in ds1:", ds1.kdims)
print("kdims in ds2:", ds2.kdims)
print("vdims in ds1:", ds1.vdims)
print("vdims in ds2:", ds2.vdims)

output

Columns in ds1: Index(['x', 'y'], dtype='object')
Columns in ds2: Index(['x', 'y'], dtype='object')
kdims in ds1: [Dimension('x'), Dimension('y')]
kdims in ds2: [Dimension('x'), Dimension('y')]
vdims in ds1: []
vdims in ds2: []

@maximlt

@maximlt
Copy link
Member

maximlt commented Mar 6, 2025

Yep I'm also unsure. Do you know if this line is ever executed part of the test suite?

            return redim_(Dataset(data, self.kwds.get('columns')), **self._redim)

@maximlt
Copy link
Member

maximlt commented Mar 6, 2025

Something else to note is that these methods (e.g. hvPlotTabular.bar) are only called in these cases:

  • import hvplot.pandas; df.hvplot.bar(...)
  • from hvplot import hvPlot(Tabular); hvPlot(Tabular)(df).bar(...)

They're not called in these other cases:

  • from hvplot import hvPlot(Tabular); hvPlot(Tabular)(kind='bar', ...)
  • import hvplot.pandas; df.hvplot(kind='bar', ...)
  • pd.options.ploting.backend = 'hvplot'; df.plot.bar(...)
  • pd.options.ploting.backend = 'hvplot'; df.plot(kind='bar', ...)

By adding new keywords to their signature with their defaults, we enforce the defaults when these methods are called but not in all the other cases, where the defaults are either set in the converter class (e.g. levels = self.kwds.get('levels', 5)) or inherited from whatever default HoloViews has set. So I think that separately, we need to find a way to make sure the defaults are always the same, however a plot is built. Maybe by expanding HoloViewsConverter._kind_options from a dict mapping a kind to a list to a dict mapping a kind to a dict whose values are the defaults:

    _kind_options = {
        'area': {'y2': <default>, 'stacked': <default>},
        ...
}

If we do that, we should also add a test that ensures the defaults defined in the signature and the converter are the same, so they don't drift over time.

@Azaya89
Copy link
Collaborator Author

Azaya89 commented Mar 7, 2025

Yep I'm also unsure. Do you know if this line is ever executed part of the test suite?

            return redim_(Dataset(data, self.kwds.get('columns')), **self._redim)

No it's not
Screenshot 2025-03-07 at 9 56 16 AM

@hoxbro
Copy link
Member

hoxbro commented Mar 7, 2025

Try to replace it with raise ValueError and run pixi run test-example. Examples don't use test coverage.

@maximlt
Copy link
Member

maximlt commented Mar 15, 2025

@Azaya89 I think you have made more changes locally; can you push them please?

@@ -799,7 +799,7 @@ def ohlc(self, x=None, y=None, **kwds):
"""
return self(kind='ohlc', x=x, y=y, **kwds)

def heatmap(self, x=None, y=None, C=None, colorbar=True, **kwds):
def heatmap(self, x=None, y=None, C=None, colorbar=True, logz=False, **kwds):
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason why reduce_function hasn't been added to the signature?

@@ -822,6 +822,8 @@ def heatmap(self, x=None, y=None, C=None, colorbar=True, **kwds):
Whether to apply log scaling to the z-axis. Default is False.
reduce_function : function, optional
Function to compute statistics for heatmap, for example `np.mean`.
If omitted, no aggregation is applied and duplicate values are dropped.
Note: Explicitly setting this parameter to `None` is not allowed and will raise an error.
Copy link
Member

Choose a reason for hiding this comment

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

Hmm this sounds like a bug :) Or put differently, why should hvPlot raise an error if this option is set to None? But I think I understand now why you didn't add it to the signature. Is it because it would raise this error?

Copy link
Member

Choose a reason for hiding this comment

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

Is it because it would raise this error?

If so, I'd say that in the interest of time just skip these cases, we can come back to them later.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmm this sounds like a bug :) Or put differently, why should hvPlot raise an error if this option is set to None? But I think I understand now why you didn't add it to the signature. Is it because it would raise this error?

Yes, although I thought it was by design and not a bug.

Copy link
Member

@maximlt maximlt Mar 17, 2025

Choose a reason for hiding this comment

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

I think it's more about how hvPlot is implemented rather than an API design choice in this case. Options that don't have an explicit default (e.g. set in the signature, obtained with self.kwds.get(<option>, DEFAULT)) aren't necessarily treated consistently.

        if 'reduce_function' in self.kwds:
            hmap = hmap.aggregate(function=self.kwds['reduce_function'])

reduce_function appears to be enabled when set to whatever value. It is interesting to get an error when passing down function=None as the signature of Dataset.aggregate seems to allow that (might be a bug in HoloViews).

    def aggregate(self, dimensions=None, function=None, spreadfn=None, **kwargs):

Ignoring this error, setting reduce_function=None in hvPlot should likely be a no-op.

        if self.kwds.get('reduce_function', None) is not None:
            hmap = hmap.aggregate(function=self.kwds['reduce_function'])

Let's not do that in this PR, we can separately work on improving how hvPlot deals with all these options and their default values.

@Azaya89 Azaya89 requested a review from maximlt March 17, 2025 18:42
@maximlt maximlt marked this pull request as ready for review March 17, 2025 19:20
@maximlt maximlt merged commit ea0a0ee into holoviz:main Mar 18, 2025
11 checks passed
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

Successfully merging this pull request may close these issues.

Complete the signature of plot methods with all their kind options
3 participants