8000 Merge pull request #14512 from jb-leger/logit_scale_stuff · matplotlib/matplotlib@b2783ed · GitHub
[go: up one dir, main page]

Skip to content

Commit b2783ed

Browse files
authored
Merge pull request #14512 from jb-leger/logit_scale_stuff
ENH: Logit scale, changes in LogitLocator and LogitFormatter
2 parents 667a100 + e113aff commit b2783ed

File tree

7 files changed

+696
-78
lines changed

7 files changed

+696
-78
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Improvements in Logit scale ticker and formatter
2+
------------------------------------------------
3+
4+
Introduced in version 1.5, the logit scale didn't have an appropriate ticker and
5+
formatter. Previously, the location of ticks was not zoom dependent, too many labels
6+
were displayed causing overlapping which broke readability, and label formatting
7+
did not adapt to precision.
8+
9+
Starting from this version, the logit locator has nearly the same behavior as the
10+
locator for the log scale or the linear
11+
scale, depending on used zoom. The number of ticks is controlled. Some minor
12+
labels are displayed adaptively as sublabels in log scale. Formatting is adapted
13+
for probabilities and the precision is adapts to the scale.

examples/scales/logit_demo.py

Lines changed: 61 additions & 0 deletions
6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""
2+
================
3+
Logit Demo
4+
================
5+
+
Examples of plots with logit axes.
7+
"""
8+
9+
import numpy as np
10+
import matplotlib.pyplot as plt
11+
12+
xmax = 10
13+
x = np.linspace(-xmax, xmax, 10000)
14+
cdf_norm = np.array([np.math.erf(w / np.sqrt(2)) / 2 + 1 / 2 for w in x])
15+
cdf_laplacian = np.array(
16+
[1 / 2 * np.exp(w) if w < 0 else 1 - 1 / 2 * np.exp(-w) for w in x]
17+
)
18+
cdf_cauchy = 1 / np.pi * np.arctan(x) + 1 / 2
19+
20+
fig, axs = plt.subplots(nrows=3, ncols=2, figsize=(6.4, 8.5))
21+
22+
# Common part, for the example, we will do the same plots on all graphs
23+
for i in range(3):
24+
for j in range(2):
25+
axs[i, j].plot(x, cdf_norm, label=r"$\mathcal{N}$")
26+
axs[i, j].plot(x, cdf_laplacian, label=r"$\mathcal{L}$")
27+
axs[i, j].plot(x, cdf_cauchy, label="Cauchy")
28+
axs[i, j].legend()
29+
axs[i, j].grid()
30+
31+
# First line, logitscale, with standard notation
32+
axs[0, 0].set(title="logit scale")
33+
axs[0, 0].set_yscale("logit")
34+
axs[0, 0].set_ylim(1e-5, 1 - 1e-5)
35+
36+
axs[0, 1].set(title="logit scale")
37+
axs[0, 1].set_yscale("logit")
38+
axs[0, 1].set_xlim(0, xmax)
39+
axs[0, 1].set_ylim(0.8, 1 - 5e-3)
40+
41+
# Second line, logitscale, with survival notation (with `use_overline`), and
42+
# other format display 1/2
43+
axs[1, 0].set(title="logit scale")
44+
axs[1, 0].set_yscale("logit", one_half="1/2", use_overline=True)
45+
axs[1, 0].set_ylim(1e-5, 1 - 1e-5)
46+
47+
axs[1, 1].set(title="logit scale")
48+
axs[1, 1].set_yscale("logit", one_half="1/2", use_overline=True)
49+
axs[1, 1].set_xlim(0, xmax)
50+
axs[1, 1].set_ylim(0.8, 1 - 5e-3)
51+
52+
# Third line, linear scale
53+
axs[2, 0].set(title="linear scale")
54+
axs[2, 0].set_ylim(0, 1)
55+
56+
axs[2, 1].set(title="linear scale")
57+
axs[2, 1].set_xlim(0, xmax)
58+
axs[2, 1].set_ylim(0.8, 1)
59+
60+
fig.tight_layout()
61+
plt.show()

examples/scales/scales.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,6 @@
5555
ax.set_yscale('logit')
5656
ax.set_title('logit')
5757
ax.grid(True)
58-
# Format the minor tick labels of the y-axis into empty strings with
59-
# `NullFormatter`, to avoid cumbering the axis with too many labels.
60-
ax.yaxis.set_minor_formatter(NullFormatter())
6158

6259

6360
# Function x**(1/2)

lib/matplotlib/scale.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -654,8 +654,15 @@ class LogitScale(ScaleBase):
654654
"""
655655
name = 'logit'
656656

657-
def __init__(self, axis, nonpos='mask'):
658-
"""
657+
def __init__(
658+
self,
659+
axis,
660+
nonpos='mask',
661+
*,
662+
one_half=r"\frac{1}{2}",
663+
use_overline=False,
664+
):
665+
r"""
659666
Parameters
660667
----------
661668
axis : `matplotlib.axis.Axis`
@@ -664,8 +671,15 @@ def __init__(self, axis, nonpos='mask'):
664671
Determines the behavior for values beyond the open interval ]0, 1[.
665672
They can either be masked as invalid, or clipped to a number very
666673
close to 0 or 1.
674+
use_overline : bool, default: False
675+
Indicate the usage of survival notation (\overline{x}) in place of
676+
standard notation (1-x) for probability close to one.
677+
one_half : str, default: r"\frac{1}{2}"
678+
The string used for ticks formatter to represent 1/2.
667679
"""
668680
self._transform = LogitTransform(nonpos)
681+
self._use_overline = use_overline
682+
self._one_half = one_half
669683

670684
def get_transform(self):
671685
"""Return the `.LogitTransform` associated with this scale."""
@@ -675,9 +689,20 @@ def set_default_locators_and_formatters(self, axis):
675689
# docstring inherited
676690
# ..., 0.01, 0.1, 0.5, 0.9, 0.99, ...
677691
axis.set_major_locator(LogitLocator())
678-
axis.set_major_formatter(LogitFormatter())
692+
axis.set_major_formatter(
693+
LogitFormatter(
694+
one_half=self._one_half,
695+
use_overline=self._use_overline
696+
)
697+
)
679698
axis.set_minor_locator(LogitLocator(minor=True))
680-
axis.set_minor_formatter(LogitFormatter())
699+
axis.set_minor_formatter(
700+
LogitFormatter(
701+
minor=True,
702+
one_half=self._one_half,
703+
use_overline=self._use_overline
704+
)
705+
)
681706

682707
def limit_range_for_scale(self, vmin, vmax, minpos):
683708
"""

0 commit comments

Comments
 (0)
0