From 3458383e52b3a4693cfc95c57e4677c48c95f7d5 Mon Sep 17 00:00:00 2001 From: Hassan Kibirige Date: Wed, 28 Sep 2022 14:58:30 +0300 Subject: [PATCH] Use exponent notation for log_format bases --- doc/changelog.rst | 5 ++++ mizani/formatters.py | 42 ++++++++++++++++++--------------- mizani/tests/test_formatters.py | 21 +++++++---------- 3 files changed, 36 insertions(+), 32 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index 2d5e779..8e72bea 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -13,6 +13,11 @@ Bug Fixes where formatting for bases 2, 8 and 16 would fail if the values were float-integers. +Enhancements +************ +- :class:`~mizani.formatters.log_format` now uses exponent notation + for bases other than base 10. + v0.8.0 ------ diff --git a/mizani/formatters.py b/mizani/formatters.py index e8120d2..a112bec 100644 --- a/mizani/formatters.py +++ b/mizani/formatters.py @@ -489,26 +489,30 @@ def __call__(self, x): fmt = '{:1.0e}' else: fmt = '{:g}' - elif self.base == 2: - fmt = '{:b}' - elif self.base == 8: - fmt = '{:o}' - elif self.base == 16: - fmt = '{:x}' + labels = [fmt.format(num) for num in x] + return self._tidyup_labels(labels) else: - warn("Formating values as base = 10") - fmt = '{:g}' - - # Base 2, 8 & 16 formatters work with integers - if self.base != 10: - x = [ - int(f) - if isinstance(f, float) and float.is_integer(f) - else f - for f in x - ] - labels = [fmt.format(num) for num in x] - return self._tidyup_labels(labels) + def _exp(num, base): + e = np.log(num)/np.log(base) + if float.is_integer(e): + e = int(e) + else: + e = np.round(e, 3) + return e + + base_txt = f'{self.base}' + if self.base == np.e: + base_txt = 'e' + + if self.mathtex: + fmt_parts = (f'${base_txt}^', '{{{e}}}$') + else: + fmt_parts = (f'{base_txt}^', '{e}') + + fmt = ''.join(fmt_parts) + exps = [_exp(num, self.base) for num in x] + labels = [fmt.format(e=e) for e in exps] + return labels class date_format: diff --git a/mizani/tests/test_formatters.py b/mizani/tests/test_formatters.py index d83b8a7..f112d74 100644 --- a/mizani/tests/test_formatters.py +++ b/mizani/tests/test_formatters.py @@ -100,23 +100,15 @@ def test_log_format(): assert formatter([1, 35, 60, 1e6]) == ['1', '4e1', '6e1', '1e6'] formatter = log_format(base=2) - assert formatter([1, 2, 4, 8]) == ['1', '10', '100', '1000'] - assert formatter([0b1, 0b10, 0b11, 0b1011]) == ['1', '10', '11', '1011'] + assert formatter([1, 2, 4, 8]) == ['2^0', '2^1', '2^2', '2^3'] + assert formatter([0b1, 0b10, 0b11]) == ['2^0', '2^1', '2^1.585'] formatter = log_format(base=8) - assert formatter([1, 4, 8, 16, 64]) == ['1', '4', '10', '20', '100'] + assert formatter([1, 4, 8, 64]) == ['8^0', '8^0.667', '8^1', '8^2'] - formatter = log_format(base=16) - assert formatter([1, 8, 16, 32, 256]) == ['1', '8', '10', '20', '100'] - - # Floats - formatter = log_format(base=8) - assert formatter([1., 4., 8., 16.]) == ['1', '4', '10', '20'] - - # Fallback to base 10 formatter = log_format(base=np.e) - with pytest.warns(UserWarning, match=r"base = 10$"): - assert formatter([1, 8, 16, 32]) == ['1', '8', '16', '32'] + assert formatter([1, np.pi, np.e**2, np.e**3]) == \ + ['e^0', 'e^1.145', 'e^2', 'e^3'] # mathtex formatter = log_format(mathtex=True) @@ -125,6 +117,9 @@ def test_log_format(): assert formatter([35, 60]) == ['35', '60'] assert formatter([1, 10000]) == ['$10^{0}$', '$10^{4}$'] + formatter = log_format(base=8, mathtex=True) + assert formatter([1, 4, 64]) == ['$8^{0}$', '$8^{0.667}$', '$8^{2}$'] + def test_date_format(): x = pd.date_range('1/1/2010', periods=4, freq='4AS')