def infer_scale(self, arg: Any, data: Series) -> Scale: """Given data and a scaling argument, initialize appropriate scale class.""" # TODO infer continuous based on log/sqrt etc? if isinstance(arg, (list, dict)): return Nominal(arg) elif variable_type(data) == "categorical": return Nominal(arg) elif variable_type(data) == "datetime": return Temporal(arg) # TODO other variable types else: return Continuous(arg)
def test_object_defaults(self, x): class MockProperty(ObjectProperty): def _default_values(self, n): return list("xyz"[:n]) s = Nominal()._setup(x, MockProperty()) assert s(x) == ["x", "y", "z", "y"]
def test_interval_with_transform(self, x): class MockProperty(IntervalProperty): _forward = np.square _inverse = np.sqrt s = Nominal((2, 4))._setup(x, MockProperty()) assert_array_equal(s(x), [4, np.sqrt(10), 2, np.sqrt(10)])
def test_coordinate_numeric_data(self, y): ax = mpl.figure.Figure().subplots() s = Nominal()._setup(y, Coordinate(), ax.yaxis) assert_array_equal(s(y), np.array([1, 0, 2, 0], float)) f = ax.yaxis.get_major_formatter() assert f.format_ticks([0, 1, 2]) == ["-1.5", "1.0", "3.0"]
def test_coordinate_axis(self, x): ax = mpl.figure.Figure().subplots() s = Nominal()._setup(x, Coordinate(), ax.xaxis) assert_array_equal(s(x), np.array([0, 1, 2, 1], float)) f = ax.xaxis.get_major_formatter() assert f.format_ticks([0, 1, 2]) == ["a", "c", "b"]
def test_color_numeric_int_float_mix(self): z = pd.Series([1, 2], name="z") s = Nominal(order=[1.0, 2])._setup(z, Color()) c1, c2 = color_palette(n_colors=2) null = (np.nan, np.nan, np.nan) assert_array_equal(s(z), [c1, null, c2])
def test_interval_defaults(self, x): class MockProperty(IntervalProperty): _default_range = (1, 2) s = Nominal().setup(x, MockProperty()) assert_array_equal(s(x), [2, 1.5, 1, 1.5])
def test_coordinate_numeric_data_with_order(self, y): order = [1, 4, -1.5] ax = mpl.figure.Figure().subplots() s = Nominal(order=order)._setup(y, Coordinate(), ax.yaxis) assert_array_equal(s(y), np.array([0, 2, np.nan, 2], float)) f = ax.yaxis.get_major_formatter() assert f.format_ticks([0, 1, 2]) == ["1.0", "4.0", "-1.5"]
def test_coordinate_axis_with_subset_order(self, x): order = ["c", "a"] ax = mpl.figure.Figure().subplots() s = Nominal(order=order)._setup(x, Coordinate(), ax.xaxis) assert_array_equal(s(x), np.array([1, 0, np.nan, 0], float)) f = ax.xaxis.get_major_formatter() assert f.format_ticks([0, 1, 2]) == [*order, ""]
def test_coordinate_axis_with_category_dtype(self, x): order = ["b", "a", "d", "c"] x = x.astype(pd.CategoricalDtype(order)) ax = mpl.figure.Figure().subplots() s = Nominal()._setup(x, Coordinate(), ax.xaxis) assert_array_equal(s(x), np.array([1, 3, 0, 3], float)) f = ax.xaxis.get_major_formatter() assert f.format_ticks([0, 1, 2, 3]) == order
def infer_scale(self, arg: Any, data: Series) -> Scale: # TODO when inferring Continuous without data, verify type # TODO need to rethink the variable type system # (e.g. boolean, ordered categories as Ordinal, etc).. var_type = variable_type(data, boolean_type="categorical") if isinstance(arg, (dict, list)): return Nominal(arg) if isinstance(arg, tuple): if var_type == "categorical": # TODO It seems reasonable to allow a gradient mapping for nominal # scale but it also feels "technically" wrong. Should this infer # Ordinal with categorical data and, if so, verify orderedness? return Nominal(arg) return Continuous(arg) if callable(arg): return Continuous(arg) # TODO Do we accept str like "log", "pow", etc. for semantics? # TODO what about # - Temporal? (i.e. datetime) # - Boolean? if not isinstance(arg, str): msg = " ".join([ f"A single scale argument for {self.variable} variables must be", f"a string, dict, tuple, list, or callable, not {type(arg)}." ]) raise TypeError(msg) if arg in QUAL_PALETTES: return Nominal(arg) elif var_type == "numeric": return Continuous(arg) # TODO implement scales for date variables and any others. else: return Nominal(arg)
def default_scale(self, data: Series) -> Scale: """Given data, initialize appropriate scale class.""" # TODO allow variable_type to be "boolean" if that's a scale? # TODO how will this handle data with units that can be treated as numeric # if passed through a registered matplotlib converter? var_type = variable_type(data, boolean_type="numeric") if var_type == "numeric": return Continuous() elif var_type == "datetime": return Temporal() # TODO others # time-based (TimeStamp, TimeDelta, Period) # boolean scale? else: return Nominal()
def test_interval_tuple(self, x): s = Nominal((1, 2))._setup(x, IntervalProperty()) assert_array_equal(s(x), [2, 1.5, 1, 1.5])
def test_fill_nunique_warning(self): x = pd.Series(["a", "b", "c", "a", "b"], name="x") with pytest.warns(UserWarning, match="The variable assigned to fill"): s = Nominal()._setup(x, Fill()) assert_array_equal(s(x), [True, False, True, True, False])
def test_fill_dict(self): x = pd.Series(["a", "a", "b", "a"], name="x") vs = {"a": False, "b": True} s = Nominal(vs)._setup(x, Fill()) assert_array_equal(s(x), [False, False, True, False])
def test_fill(self): x = pd.Series(["a", "a", "b", "a"], name="x") s = Nominal()._setup(x, Fill()) assert_array_equal(s(x), [True, True, False, True])
def test_alpha_default(self, x): s = Nominal()._setup(x, Alpha()) assert_array_equal(s(x), [.95, .625, .3, .625])
def test_color_numeric_data(self, y): s = Nominal()._setup(y, Color()) cs = color_palette() assert_array_equal(s(y), [cs[1], cs[0], cs[2], cs[0]])
def test_interval_dict(self, x): vs = {"a": 3, "b": 4, "c": 6} s = Nominal(vs)._setup(x, IntervalProperty()) assert_array_equal(s(x), [3, 6, 4, 6])
def test_object_order(self, x): vs = ["x", "y", "z"] s = Nominal(vs, order=["c", "a", "b"])._setup(x, ObjectProperty()) assert s(x) == ["y", "x", "z", "x"]
def test_object_dict(self, x): vs = {"a": "x", "b": "y", "c": "z"} s = Nominal(vs)._setup(x, ObjectProperty()) assert s(x) == ["x", "z", "y", "z"]
def test_color_dict_palette(self, x): cs = color_palette("crest", 3) pal = dict(zip("bac", cs)) s = Nominal(pal)._setup(x, Color()) assert_array_equal(s(x), [cs[1], cs[2], cs[0], cs[2]])
def test_color_unknown_palette(self, x): pal = "not_a_palette" err = f"{pal} is not a valid palette name" with pytest.raises(ValueError, match=err): Nominal(pal)._setup(x, Color())
def test_color_alpha_in_palette(self, x): cs = [(.2, .2, .3, .5), (.1, .2, .3, 1), (.5, .6, .2, 0)] s = Nominal(cs)._setup(x, Color()) assert_array_equal(s(x), [cs[0], cs[1], cs[2], cs[1]])
def test_interval_tuple_numeric(self, y): s = Nominal((1, 2))._setup(y, IntervalProperty()) assert_array_equal(s(y), [1.5, 2, 1, 2])
def test_object_order_subset(self, x): vs = ["x", "y"] s = Nominal(vs, order=["a", "c"])._setup(x, ObjectProperty()) assert s(x) == ["x", "y", None, "y"]
def test_interval_list(self, x): vs = [2, 5, 4] s = Nominal(vs)._setup(x, IntervalProperty()) assert_array_equal(s(x), [2, 5, 4, 5])
def test_objects_that_are_weird(self, x): vs = [("x", 1), (None, None, 0), {}] s = Nominal(vs)._setup(x, ObjectProperty()) assert s(x) == [vs[0], vs[1], vs[2], vs[1]]
def test_object_list(self, x): vs = ["x", "y", "z"] s = Nominal(vs)._setup(x, ObjectProperty()) assert s(x) == ["x", "y", "z", "y"]
def test_color_numeric_with_order_subset(self, y): s = Nominal(order=[-1.5, 1])._setup(y, Color()) c1, c2 = color_palette(n_colors=2) null = (np.nan, np.nan, np.nan) assert_array_equal(s(y), [c2, c1, null, c1])