Skip to content

Commit

Permalink
Add basic datetime mapping tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mwaskom committed Oct 11, 2021
1 parent b32633c commit c3ea2a8
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 2 deletions.
21 changes: 19 additions & 2 deletions seaborn/_core/mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,13 @@ def setup(
return LookupMapping(mapping_dict)

if not isinstance(values, tuple):
raise TypeError() # TODO
# What to do here? In existing code we can pass numeric data but
# then request a categorical mapping by using a list or dict for values.
# That is currently not supported because the scale.type dominates in
# the variable type inference. We should basically not get here, either
# passing a list/dict implies a categorical mapping, or the an explicit
# numeric mapping with a categorical set of values should raise before this.
raise TypeError() # TODO FIXME

if map_type == "numeric":

Expand All @@ -228,11 +234,13 @@ def setup(
elif map_type == "datetime":

if scale is not None:
# TODO should this happen upstream, or alternatively inside the norm?
data = scale.cast(data)
data = mpl.dates.date2num(data.dropna())
prepare = lambda x: mpl.dates.date2num(pd.to_datetime(x))

# TODO if norm is tuple, convert to datetime and then to numbers?
# (Or handle that upstream within the DateTimeScale? Probably do this.)

transform = RangeTransform(values)

Expand Down Expand Up @@ -266,9 +274,18 @@ def setup(
norm = None if scale is None else scale.norm
order = None if scale is None else scale.order

# TODO We need to add some input checks ...
# TODO We also need to add some input checks ...
# e.g. specifying a numeric scale and a qualitative colormap should fail nicely.

# TODO FIXME:mappings
# In current function interface, we can assign a numeric variable to hue and set
# either a named qualitative palette or a list/dict of colors.
# In current implementation here, that raises with an unpleasant error.
# The problem is that the scale.type currently dominates.
# How to distinguish between "user set numeric scale and qualitative palette,
# this is an error" from "user passed numeric values but did not set explicit
# scale, then asked for a qualitative mapping by the form of the palette?

map_type = self._infer_map_type(scale, palette, data)

if map_type == "categorical":
Expand Down
3 changes: 3 additions & 0 deletions seaborn/_core/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,9 @@ def _setup_data(self):

def _setup_scales(self) -> None:

# TODO currently typoing variable name in `scale_*`, or scaling a variable that
# isn't defined anywhere, silently does nothing. We should raise/warn on that.

variables = set(self._data.frame)
for layer in self._layers:
variables |= set(layer.data.frame)
Expand Down
81 changes: 81 additions & 0 deletions seaborn/tests/_core/test_mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ def cat_vector(self, long_df):
def cat_order(self, cat_vector):
return categorical_order(cat_vector)

@pytest.fixture
def dt_num_vector(self, long_df):
return long_df["t"]

@pytest.fixture
def dt_cat_vector(self, long_df):
return long_df["d"]

def test_categorical_default_palette(self, cat_vector, cat_order):

expected = dict(zip(cat_order, color_palette()))
Expand Down Expand Up @@ -302,6 +310,60 @@ def test_numeric_multi_lookup(self, num_vector, num_norm):
expected_colors = cmap(num_norm(num_vector.to_numpy()))[:, :3]
assert_array_equal(m(num_vector.to_numpy()), expected_colors)

def test_datetime_default_palette(self, dt_num_vector):

m = ColorSemantic().setup(dt_num_vector)
mapped = m(dt_num_vector)

tmp = dt_num_vector - dt_num_vector.min()
normed = tmp / tmp.max()

expected_cmap = color_palette("ch:", as_cmap=True)
expected = expected_cmap(normed)

assert len(mapped) == len(expected)
for have, want in zip(mapped, expected):
assert to_rgb(have) == to_rgb(want)

def test_datetime_specified_palette(self, dt_num_vector):

palette = "mako"
m = ColorSemantic(palette=palette).setup(dt_num_vector)
mapped = m(dt_num_vector)

tmp = dt_num_vector - dt_num_vector.min()
normed = tmp / tmp.max()

expected_cmap = color_palette(palette, as_cmap=True)
expected = expected_cmap(normed)

assert len(mapped) == len(expected)
for have, want in zip(mapped, expected):
assert to_rgb(have) == to_rgb(want)

@pytest.mark.xfail(reason="No support for norms in datetime scale yet")
def test_datetime_norm_limits(self, dt_num_vector):

norm = (
dt_num_vector.min() - pd.Timedelta(2, "m"),
dt_num_vector.max() - pd.Timedelta(1, "m"),
)
palette = "mako"

scale = ScaleWrapper(LinearScale("color"), "datetime", norm)
m = ColorSemantic(palette=palette).setup(dt_num_vector, scale)
mapped = m(dt_num_vector)

tmp = dt_num_vector - norm[0]
normed = tmp / norm[1]

expected_cmap = color_palette(palette, as_cmap=True)
expected = expected_cmap(normed)

assert len(mapped) == len(expected)
for have, want in zip(mapped, expected):
assert to_rgb(have) == to_rgb(want)

def test_bad_palette(self, num_vector):

with pytest.raises(ValueError):
Expand Down Expand Up @@ -583,6 +645,25 @@ def test_norm_categorical(self):
with pytest.raises(ValueError):
self.semantic().setup(x, scale)

def test_default_datetime(self):

x = pd.Series(np.array([10000, 10100, 10101], dtype="datetime64[D]"))
y = self.semantic().setup(x)(x)
tmp = x - x.min()
normed = tmp / tmp.max()
expected = self.transform(normed, *self.semantic().default_range)
assert_array_equal(y, expected)

def test_range_datetime(self):

values = .2, .9
x = pd.Series(np.array([10000, 10100, 10101], dtype="datetime64[D]"))
y = self.semantic(values).setup(x)(x)
tmp = x - x.min()
normed = tmp / tmp.max()
expected = self.transform(normed, *values)
assert_array_equal(y, expected)


class TestWidth(ContinuousBase):

Expand Down

0 comments on commit c3ea2a8

Please sign in to comment.