Skip to content

Commit cfbaeb9

Browse files
committed
logitscale: add tests for LogitFormatter
1 parent 3b91e7c commit cfbaeb9

File tree

1 file changed

+138
-0
lines changed

1 file changed

+138
-0
lines changed

lib/matplotlib/tests/test_ticker.py

+138
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import numpy as np
44
from numpy.testing import assert_almost_equal, assert_array_equal
55
import pytest
6+
import re
67

78
import matplotlib
89
import matplotlib.pyplot as plt
@@ -769,6 +770,143 @@ def test_LogFormatter_call(self, val):
769770
assert temp_lf(val) == str(val)
770771

771772

773+
class TestLogitFormatter:
774+
@staticmethod
775+
def logit_deformatter(string):
776+
r"""
777+
Parser to convert string as r'$\mathdefault{1.41\cdot10^{-4}}$' in
778+
float 1.41e-4, as '0.5' or as r'$\mathdefault{\frac{1}{2}}$' in float
779+
0.5,
780+
"""
781+
match = re.match(
782+
r"[^\d]*"
783+
r"(?P<comp>1-)?"
784+
r"(?P<mant>\d*\.?\d*)?"
785+
r"(?:\\cdot)?"
786+
r"(?:10\^\{(?P<expo>-?\d*)})?"
787+
r"[^\d]*$",
788+
string,
789+
)
790+
if match:
791+
comp = match["comp"] is not None
792+
mantissa = (
793+
float(match["mant"])
794+
if (match["mant"] is not None and len(match["mant"]) > 0)
795+
else 1
796+
)
797+
expo = int(match["expo"]) if match["expo"] is not None else 0
798+
value = mantissa * 10 ** expo
799+
if comp:
800+
return 1 - value
801+
return value
802+
match = re.match(
803+
r"[^\d]*\\frac\{(?P<num>\d+)\}\{(?P<deno>\d+)\}[^\d]*$", string
804+
)
805+
if match:
806+
num, deno = float(match["num"]), float(match["deno"])
807+
return num / deno
808+
raise ValueError("not formatted by LogitFormatter")
809+
810+
decade_test = (
811+
[10 ** (-i) for i in range(1, 10)]
812+
+ [1 - 10 ** (-i) for i in range(1, 10)]
813+
+ [1 / 2]
814+
)
815+
decade_test.sort()
816+
817+
@pytest.mark.parametrize("x", decade_test)
818+
def test_basic(self, x):
819+
"""
820+
Test the formatted value correspond to the value for ideal ticks in
821+
logit space.
822+
"""
823+
formatter = mticker.LogitFormatter(use_overline=False)
824+
formatter.set_locs(self.decade_test)
825+
s = formatter(x)
826+
x2 = TestLogitFormatter.logit_deformatter(s)
827+
assert np.abs(x - x2) < 1e-10
828+
829+
@pytest.mark.parametrize("x", 1 / (1 + np.exp(-np.linspace(-7, 7, 10))))
830+
def test_variablelength(self, x):
831+
"""
832+
The format lenght should change depending on the neighbor labels.
833+
"""
834+
formatter = mticker.LogitFormatter(use_overline=False)
835+
for N in (10, 20, 50, 100, 200, 1000, 2000, 5000, 10000):
836+
if x + 1 / N < 1:
837+
formatter.set_locs([x - 1 / N, x, x + 1 / N])
838+
sx = formatter(x)
839+
sx1 = formatter(x + 1 / N)
840+
d = TestLogitFormatter.logit_deformatter(
841+
sx1
842+
) - TestLogitFormatter.logit_deformatter(sx)
843+
assert d > 0
844+
assert d < 2 / N
845+
846+
lims_minor_major = [
847+
((5e-8, 1 - 5e-8), ((25, False), (75, False))),
848+
((5e-5, 1 - 5e-5), ((25, False), (75, True))),
849+
((5e-2, 1 - 5e-2), ((25, True), (75, True))),
850+
]
851+
852+
@pytest.mark.parametrize("lims, cases", lims_minor_major)
853+
def test_minor_vs_major(self, lims, cases):
854+
"""
855+
Test minor/major displays.
856+
"""
857+
858+
min_loc = mticker.LogitLocator(minor=True)
859+
min_form = mticker.LogitFormatter(minor=True)
860+
for threshold, has_minor in cases:
861+
min_form.set_minor_threshold(threshold)
862+
ticks = min_loc.tick_values(*lims)
863+
formatted = min_form.format_ticks(ticks)
864+
labelled = [f for f in formatted if len(f) > 0]
865+
if has_minor:
866+
assert len(labelled) > 0, (threshold, has_minor)
867+
else:
868+
assert len(labelled) == 0, (threshold, has_minor)
869+
870+
def test_minor_number(self):
871+
"""
872+
Test the parameter minor_number
873+
"""
874+
min_loc = mticker.LogitLocator(minor=True)
875+
min_form = mticker.LogitFormatter(minor=True)
876+
ticks = min_loc.tick_values(5e-2, 1 - 5e-2)
877+
for minor_number in (2, 4, 8, 16):
878+
min_form.set_minor_number(minor_number)
879+
formatted = min_form.format_ticks(ticks)
880+
labelled = [f for f in formatted if len(f) > 0]
881+
assert len(labelled) == minor_number
882+
883+
def test_use_overline(self):
884+
"""
885+
Test the parameter use_overline
886+
"""
887+
x = 1 - 1e-2
888+
fx1 = r"$\mathdefault{1-10^{-2}}$"
889+
fx2 = r"$\mathdefault{\overline{10^{-2}}}$"
890+
form = mticker.LogitFormatter(use_overline=False)
891+
assert form(x) == fx1
892+
form.use_overline(True)
893+
assert form(x) == fx2
894+
form.use_overline(False)
895+
assert form(x) == fx1
896+
897+
@pytest.mark.parametrize("N", (100, 253, 754))
898+
def test_format_data_short(self, N):
899+
locs = np.linspace(0, 1, N)[1:-1]
900+
form = mticker.LogitFormatter()
901+
for x in locs:
902+
fx = form.format_data_short(x)
903+
if len(fx) > 2 and fx[0:2] == "1-":
904+
x2 = 1 - float(fx[2:])
905+
else:
906+
x2 = float(fx)
907+
assert np.abs(x - x2) < 1 / N
908+
909+
772910
class TestFormatStrFormatter:
773911
def test_basic(self):
774912
# test % style formatter

0 commit comments

Comments
 (0)