diff --git a/Orange/data/tests/test_variable.py b/Orange/data/tests/test_variable.py index 9faf64102e1..2afa27e74ff 100644 --- a/Orange/data/tests/test_variable.py +++ b/Orange/data/tests/test_variable.py @@ -498,23 +498,42 @@ def test_make(self): def test_decimals(self): a = ContinuousVariable("a", 4) - self.assertEqual(a.str_val(4.654321), "4.6543") - self.assertEqual(a.str_val(4.654321654321), "4.6543") + self.assertEqual(a.str_val(4.6543), "4.6543") + self.assertEqual(a.str_val(4.25), "4.2500") self.assertEqual(a.str_val(Unknown), "?") a = ContinuousVariable("a", 5) self.assertEqual(a.str_val(0.000000000001), "0.00000") a = ContinuousVariable("a", 10) self.assertEqual(a.str_val(0.000000000001), "1e-12") + def test_more_decimals(self): + a = ContinuousVariable("a", 0) + self.assertEqual(a.str_val(4), "4") + self.assertEqual(a.str_val(4.1234), "4.12") + + a = ContinuousVariable("a", 2) + self.assertEqual(a.str_val(4), "4.00") + self.assertEqual(a.str_val(4.25), "4.25") + self.assertEqual(a.str_val(4.1234123), "4.1234") + + for cca4 in (4 + 1e-9, 4 - 1e-9): + assert cca4 != 4 + self.assertEqual(a.str_val(cca4), "4.00") + def test_adjust_decimals(self): + # Default is 3 decimals, but format is %g a = ContinuousVariable("a") + self.assertEqual(a.str_val(5), "5") self.assertEqual(a.str_val(4.65432), "4.65432") + + # Change to no decimals a.val_from_str_add("5") - self.assertEqual(a.str_val(4.65432), "5") + self.assertEqual(a.str_val(5), "5") + + # Change to two decimals a.val_from_str_add(" 5.12 ") - self.assertEqual(a.str_val(4.65432), "4.65") - a.val_from_str_add("5.1234") - self.assertEqual(a.str_val(4.65432), "4.6543") + self.assertEqual(a.str_val(4.65), "4.65") + self.assertEqual(a.str_val(5), "5.00") def varcls_modified(self, name): var = super().varcls_modified(name) diff --git a/Orange/data/variable.py b/Orange/data/variable.py index db205960602..874556de94d 100644 --- a/Orange/data/variable.py +++ b/Orange/data/variable.py @@ -529,6 +529,7 @@ def __init__(self, name="", number_of_decimals=None, compute_value=None, *, spar three, but adjusted at the first call of :obj:`to_val`. """ super().__init__(name, compute_value, sparse=sparse) + self._max_round_diff = 0 self.number_of_decimals = number_of_decimals @property @@ -553,6 +554,7 @@ def number_of_decimals(self, x): return self._number_of_decimals = x + self._max_round_diff = 10 ** (-x - 6) self.adjust_decimals = 0 if self._number_of_decimals <= MAX_NUM_OF_DECIMALS: self._format_str = "%.{}f".format(self.number_of_decimals) @@ -580,6 +582,10 @@ def repr_val(self, val): """ if isnan(val): return "?" + if self.format_str != "%g" \ + and abs(round(val, self._number_of_decimals) - val) \ + > self._max_round_diff: + return f"{val:.{self._number_of_decimals + 2}f}" return self._format_str % val str_val = repr_val @@ -593,6 +599,7 @@ def copy(self, compute_value=None, *, name=None, **kwargs): var.number_of_decimals = number_of_decimals else: var._number_of_decimals = self._number_of_decimals + var._max_round_diff = self._max_round_diff var.adjust_decimals = self.adjust_decimals var.format_str = self._format_str return var diff --git a/Orange/tests/test_discretize.py b/Orange/tests/test_discretize.py index 20e3cd60240..f1c9b29bb9a 100644 --- a/Orange/tests/test_discretize.py +++ b/Orange/tests/test_discretize.py @@ -140,13 +140,17 @@ def test_create_discretized_var_formatting(self): dvar = discretize.Discretizer.create_discretized_var( self.var, [10.1234]) - self.assertEqual(dvar.values, ("< 10.1", "≥ 10.1")) + self.assertEqual(dvar.values, ("< 10.123", "≥ 10.123")) self.var.number_of_decimals = 3 + dvar = discretize.Discretizer.create_discretized_var( + self.var, [5, 10.25]) + self.assertEqual(dvar.values, ("< 5", "5 - 10.25", "≥ 10.25")) + dvar = discretize.Discretizer.create_discretized_var( self.var, [5, 10.1234]) - self.assertEqual(dvar.values, ("< 5", "5 - 10.123", "≥ 10.123")) + self.assertEqual(dvar.values, ("< 5", "5 - 10.1234", "≥ 10.1234")) def test_discretizer_computation(self): dvar = discretize.Discretizer.create_discretized_var( diff --git a/benchmark/bench_save.py b/benchmark/bench_save.py new file mode 100644 index 00000000000..74d6d35f563 --- /dev/null +++ b/benchmark/bench_save.py @@ -0,0 +1,61 @@ +from functools import partial +import os + +import numpy as np +import scipy.sparse + +from Orange.data import Table, ContinuousVariable, Domain +from .base import Benchmark, benchmark + + +def save(table, fn): + try: + table.save(fn) + finally: + os.remove(fn) + + +class BenchSave(Benchmark): + + def setup_dense(self, rows, cols, varkwargs=None): + if varkwargs is None: + varkwargs = {} + self.table = Table.from_numpy( # pylint: disable=W0201 + Domain([ContinuousVariable(str(i), **varkwargs) for i in range(cols)]), + np.random.RandomState(0).rand(rows, cols)) + + def setup_sparse(self, rows, cols, varkwargs=None): + if varkwargs is None: + varkwargs = {} + sparse = scipy.sparse.rand(rows, cols, density=0.01, format='csr', random_state=0) + self.table = Table.from_numpy( # pylint: disable=W0201 + Domain([ContinuousVariable(str(i), sparse=True, **varkwargs) for i in range(cols)]), + sparse) + + @benchmark(setup=partial(setup_dense, rows=100, cols=10)) + def bench_print_dense(self): + str(self.table) + + @benchmark(setup=partial(setup_dense, rows=100, cols=10, + varkwargs={"number_of_decimals": 2})) + def bench_print_dense_decimals(self): + str(self.table) + + @benchmark(setup=partial(setup_sparse, rows=100, cols=10), number=5) + def bench_print_sparse(self): + str(self.table) + + @benchmark(setup=partial(setup_sparse, rows=100, cols=10, + varkwargs={"number_of_decimals": 2}), + number=5) + def bench_print_sparse_decimals(self): + str(self.table) + + @benchmark(setup=partial(setup_dense, rows=100, cols=100)) + def bench_save_tab(self): + save(self.table, "temp_save.tab") + + @benchmark(setup=partial(setup_dense, rows=100, cols=100, + varkwargs={"number_of_decimals": 2})) + def bench_save_tab_decimals(self): + save(self.table, "temp_save.tab") diff --git a/benchmark/bench_variable.py b/benchmark/bench_variable.py new file mode 100644 index 00000000000..e81c54d1791 --- /dev/null +++ b/benchmark/bench_variable.py @@ -0,0 +1,19 @@ +from Orange.data import ContinuousVariable +from .base import Benchmark, benchmark + + +class BenchContinuous(Benchmark): + + # pylint: disable=no-self-use + @benchmark() + def bench_str_val_decimals(self): + a = ContinuousVariable("a", 4) + for _ in range(1000): + a.str_val(1.23456) + + # pylint: disable=no-self-use + @benchmark() + def bench_str_val_g(self): + a = ContinuousVariable("a") + for _ in range(1000): + a.str_val(1.23456)