Skip to content
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

unified additional properties #16

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,11 @@ venv.bak/

# mypy
.mypy_cache/


# vscode
.vscode/

# examples
.examples/
examples/example.ipynb
45 changes: 44 additions & 1 deletion altair_pandas/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,44 @@ def _valid_column(column_name):
return str(column_name)


def _set_encode(chart, cols, value, attribute, types, alt_obj):
'''handles all possible meanings/values of the attribute'''
if value in cols:
setattr(chart.encoding, attribute, alt_obj(value))
elif isinstance(value, alt_obj):
setattr(chart.encoding, attribute, value)
elif isinstance(value, types):
setattr(chart.encoding, attribute, alt.value(value))
elif isinstance(value, (pd.Series, pd.np.array)):
raise NotImplementedError()
else:
text = f'Supposed to be either column name, or {types}, or {alt_obj} object' # noqa
raise ValueError(text)


def _pass_additional_properties(chart, cols, kwargs):
'''make use of additional properties, such as
color, title, alpha, etc.
'''
if 'title' in kwargs:
chart.title = kwargs['title']

if 'color' in kwargs:
color = kwargs.get('color')
_set_encode(chart, cols, color, 'color', str, alt.Color)

if 'alpha' in kwargs:
alpha = kwargs['alpha']
_set_encode(chart,
cols,
alpha,
'opacity',
(int, float),
alt.Opacity)
Copy link
Member

Choose a reason for hiding this comment

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

I believe alpha can only be a scalar value for pandas plotting. In that case, it would make more sense to set it as a mark property (chart.mark_point(opacity=alpha))

Copy link
Member

Choose a reason for hiding this comment

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

Same for color, I believe.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed alpha is (for some reason) either None or float. But color and size could be an array or dataframe column. But probably we need some uniform interface for them anyway?

Copy link
Member

Choose a reason for hiding this comment

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

I believe that color and size must also be scalar properties. I think data.plot.scatter only accepts column names for x, y, and c arguments, and other plot types only accept column names for x and y.

Copy link
Member

Choose a reason for hiding this comment

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

There are also some arguments that allow passing of arrays of values... we'll have to add those as new columns to the input data in order to use them in Altair.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

does decorator make sense, then? and/or storing processed data as an instance attribute - so that we can access it outside of the plotting function.

Of corse I can also replace every plot function ending with :

chart = ....
return _additional_attributes(chart, ...)

Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure the best design right now, TBH. I don't think t's too important to store processed data as an instance attribute.

Maybe the best approach would be to have utility routines that take a set of kwargs and extract properties based on where they will be needed in an Altair chart. e.g. _extract_mark_properties(kwargs), _extract_additional_encodings(kwargs), _extract_additional_data_columns(kwargs), etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

kk, let me try that approach on some basics (title, alpha, figsize)


return chart


class _PandasPlotter:
"""Base class for pandas plotting."""
@classmethod
Expand Down Expand Up @@ -190,4 +228,9 @@ def plot(data, kind='line', **kwargs):
raise NotImplementedError(
f"kind='{kind}' for data of type {type(data)}")

return plotfunc(**kwargs)
chart = plotfunc(**kwargs)
if isinstance(data, pd.DataFrame):
cols = data.columns.tolist()
else:
cols = [data.name, data.index.name]
return _pass_additional_properties(chart, cols, kwargs)
31 changes: 31 additions & 0 deletions altair_pandas/test_plotting.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest
import numpy as np
import pandas as pd
import altair as alt


@pytest.fixture
Expand Down Expand Up @@ -141,3 +142,33 @@ def test_dataframe_boxplot(dataframe, with_plotting_backend):
assert spec['encoding']['x']['field'] == 'column'
assert spec['encoding']['y']['field'] == 'value'
assert spec['transform'][0]['fold'] == ['x', 'y']


@pytest.mark.parametrize('props', [
{'title': 'Test Title', 'color': 'y', 'alpha': 0.2},
{'title': 'Test Title', 'color': 'purple', 'alpha': 'y'},
{'title': 'Test Title', 'color': 'purple', 'alpha': 'y'},
{'title': 'Test Title', 'color': 'purple', 'alpha': 'y'},
{'title': 'Test Title',
'color': alt.Color('y', scale=alt.Scale(scheme='viridis')),
'alpha': 0.2}
])
def test_additional_properties(dataframe, props, with_plotting_backend):
chart = dataframe.plot.barh(**props)
spec = chart.to_dict()

assert spec['title'] == props['title']

if props['alpha'] == 0.2:
assert spec['encoding']['opacity'] == alt.value(props['alpha'])
else:
assert spec['encoding']['opacity'] == {'field': props['alpha'],
'type': 'quantitative'}

if props['color'] == 'purple':
assert spec['encoding']['color'] == alt.value(props['color'])
elif isinstance(props['color'], alt.Color):
assert spec['encoding']['color'] == props['color'].to_dict()
else:
assert spec['encoding']['color'] == {'field': props['color'],
'type': 'quantitative'}