Skip to content

Logit scale, changes in LogitLocator and LogitFormatter #14512

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jul 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions doc/users/next_whats_new/logit_scale_stuff.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Improvements in Logit scale ticker and formatter
------------------------------------------------

Introduced in version 1.5, the logit scale didn't have an appropriate ticker and
formatter. Previously, the location of ticks was not zoom dependent, too many labels
were displayed causing overlapping which broke readability, and label formatting
did not adapt to precision.

Starting from this version, the logit locator has nearly the same behavior as the
locator for the log scale or the linear
scale, depending on used zoom. The number of ticks is controlled. Some minor
labels are displayed adaptively as sublabels in log scale. Formatting is adapted
for probabilities and the precision is adapts to the scale.
61 changes: 61 additions & 0 deletions examples/scales/logit_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rendered example is at https://22432-1385122-gh.circle-artifacts.com/0/home/circleci/project/doc/build/html/gallery/scales/logit_demo.html#sphx-glr-gallery-scales-logit-demo-py; you may want to play with e.g. figsize a bit...
Also, looking at the example I wonder whether $\frac{1}{2}$ is that nice, perhaps 1/2 would look better?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. There is a way to build the html locally?

For one half, I prefer $\frac{1}{2}$, but it is a personal pov, I can put the choice in a option.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, see https://matplotlib.org/devel/documenting_mpl.html#building-the-docs.
I'll let the next reviewer (we need two to merge) decide on whether to insist on an API for 1/2 vs. \frac...

Copy link
Contributor Author

@jb-leger jb-leger Jun 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the link.

I think both choice should be proposed, but we still have to decide the default choice.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's your PR, you get to pick, unless the other reviewer agrees with me that 1/2 is better :)

Copy link
Contributor Author

@jb-leger jb-leger Jun 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The graph in example should be fixed now.

I have added a option to choose the string for one half. It is illustrated in the example. I can change the default choice. I keep this thread open.

================
Logit Demo
================

Examples of plots with logit axes.
"""

import numpy as np
import matplotlib.pyplot as plt

xmax = 10
x = np.linspace(-xmax, xmax, 10000)
cdf_norm = np.array([np.math.erf(w / np.sqrt(2)) / 2 + 1 / 2 for w in x])
cdf_laplacian = np.array(
[1 / 2 * np.exp(w) if w < 0 else 1 - 1 / 2 * np.exp(-w) for w in x]
)
cdf_cauchy = 1 / np.pi * np.arctan(x) + 1 / 2

fig, axs = plt.subplots(nrows=3, ncols=2, figsize=(6.4, 8.5))

# Common part, for the example, we will do the same plots on all graphs
for i in range(3):
for j in range(2):
axs[i, j].plot(x, cdf_norm, label=r"$\mathcal{N}$")
axs[i, j].plot(x, cdf_laplacian, label=r"$\mathcal{L}$")
axs[i, j].plot(x, cdf_cauchy, label="Cauchy")
axs[i, j].legend()
axs[i, j].grid()

# First line, logitscale, with standard notation
axs[0, 0].set(title="logit scale")
axs[0, 0].set_yscale("logit")
axs[0, 0].set_ylim(1e-5, 1 - 1e-5)

axs[0, 1].set(title="logit scale")
axs[0, 1].set_yscale("logit")
axs[0, 1].set_xlim(0, xmax)
axs[0, 1].set_ylim(0.8, 1 - 5e-3)

# Second line, logitscale, with survival notation (with `use_overline`), and
# other format display 1/2
axs[1, 0].set(title="logit scale")
axs[1, 0].set_yscale("logit", one_half="1/2", use_overline=True)
axs[1, 0].set_ylim(1e-5, 1 - 1e-5)

axs[1, 1].set(title="logit scale")
axs[1, 1].set_yscale("logit", one_half="1/2", use_overline=True)
axs[1, 1].set_xlim(0, xmax)
axs[1, 1].set_ylim(0.8, 1 - 5e-3)

# Third line, linear scale
axs[2, 0].set(title="linear scale")
axs[2, 0].set_ylim(0, 1)

axs[2, 1].set(title="linear scale")
axs[2, 1].set_xlim(0, xmax)
axs[2, 1].set_ylim(0.8, 1)

fig.tight_layout()
plt.show()
3 changes: 0 additions & 3 deletions examples/scales/scales.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,6 @@
ax.set_yscale('logit')
ax.set_title('logit')
ax.grid(True)
# Format the minor tick labels of the y-axis into empty strings with
# `NullFormatter`, to avoid cumbering the axis with too many labels.
ax.yaxis.set_minor_formatter(NullFormatter())


# Function x**(1/2)
Expand Down
33 changes: 29 additions & 4 deletions lib/matplotlib/scale.py
Original file line number Diff line number Diff line change
Expand Up @@ -654,8 +654,15 @@ class LogitScale(ScaleBase):
"""
name = 'logit'

def __init__(self, axis, nonpos='mask'):
"""
def __init__(
self,
axis,
nonpos='mask',
*,
one_half=r"\frac{1}{2}",
use_overline=False,
):
r"""
Parameters
----------
axis : `matplotlib.axis.Axis`
Expand All @@ -664,8 +671,15 @@ def __init__(self, axis, nonpos='mask'):
Determines the behavior for values beyond the open interval ]0, 1[.
They can either be masked as invalid, or clipped to a number very
close to 0 or 1.
use_overline : bool, default: False
Indicate the usage of survival notation (\overline{x}) in place of
standard notation (1-x) for probability close to one.
one_half : str, default: r"\frac{1}{2}"
The string used for ticks formatter to represent 1/2.
"""
self._transform = LogitTransform(nonpos)
self._use_overline = use_overline
self._one_half = one_half

def get_transform(self):
"""Return the `.LogitTransform` associated with this scale."""
Expand All @@ -675,9 +689,20 @@ def set_default_locators_and_formatters(self, axis):
# docstring inherited
# ..., 0.01, 0.1, 0.5, 0.9, 0.99, ...
axis.set_major_locator(LogitLocator())
axis.set_major_formatter(LogitFormatter())
axis.set_major_formatter(
LogitFormatter(
one_half=self._one_half,
use_overline=self._use_overline
)
)
axis.set_minor_locator(LogitLocator(minor=True))
axis.set_minor_formatter(LogitFormatter())
axis.set_minor_formatter(
LogitFormatter(
minor=True,
one_half=self._one_half,
use_overline=self._use_overline
)
)

def limit_range_for_scale(self, vmin, vmax, minpos):
"""
Expand Down
Loading