From c818093bf9be12ad66097ffb0deebbc5b9c1b940 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 19 Dec 2024 15:53:07 +0100 Subject: [PATCH 1/3] DOC: Explain parameters linthresh and linscale of symlog scale Closes #29335 via addressing https://github.com/matplotlib/matplotlib/issues/29335#issuecomment-2548980673. --- galleries/examples/scales/symlog_demo.py | 92 ++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 7 deletions(-) diff --git a/galleries/examples/scales/symlog_demo.py b/galleries/examples/scales/symlog_demo.py index e50be0d0c8a8..ff24860b4210 100644 --- a/galleries/examples/scales/symlog_demo.py +++ b/galleries/examples/scales/symlog_demo.py @@ -1,7 +1,12 @@ """ -=========== -Symlog Demo -=========== +============ +Symlog scale +============ + +The symmetric logarithmic scale is an extension of the logarithmic scale that +also covers negative values. As with the logarithmic scale, it is particularly +useful for numerical data that spans a broad range of values, especially when there +are significant differences between the magnitudes of the numbers involved. Example use of symlog (symmetric log) axis scaling. """ @@ -34,12 +39,85 @@ plt.show() # %% -# It should be noted that the coordinate transform used by ``symlog`` -# has a discontinuous gradient at the transition between its linear -# and logarithmic regions. The ``asinh`` axis scale is an alternative -# technique that may avoid visual artifacts caused by these discontinuities. +# Linear threshold +# ---------------- +# Since each decade on a logarithmic scale covers the same amount of visual space +# and there are infinitely many decades between a given number and zero, the symlog +# scale must deviate from logarithmic mapping in a small range (-x0, x0), so that +# that range is mapped to a finite visual space. +# +# The symlog scale achieves this by defining a parameter *linthresh* and switching +# to a linear mapping in the region *(-linthresh, linthresh)*. + + +def format_axes(ax, title=None): + """A helper function to better visualize properties of the symlog scale.""" + ax.xaxis.get_minor_locator().set_params(subs=[2, 3, 4, 5, 6, 7, 8, 9]) + ax.grid() + ax.xaxis.grid(which='minor') # minor grid on too + linthresh = ax.xaxis.get_transform().linthresh + linscale = ax.xaxis.get_transform().linscale + ax.axvspan(-linthresh, linthresh, color='0.9') + if title: + ax.set_title(title.format(linthresh=linthresh, linscale=linscale)) + + +x = np.linspace(-60, 60, 201) +y = np.linspace(0, 100.0, 201) + +fig, (ax1, ax2) = plt.subplots(nrows=2, layout="constrained") + +ax1.plot(x, y) +ax1.set_xscale('symlog', linthresh=1) +format_axes(ax1, title='Linear region: linthresh={linthresh}') + +ax2.plot(x, y) +ax2.set_xscale('symlog', linthresh=5) +format_axes(ax2, title='Linear region: linthresh={linthresh}') # %% +# Generally, *linthresh* should be chosen so that no or only a few +# data points are in the linear region. As a rule of thumb, +# :math:`linthresh \approx \mathrm{min} |x|`. +# +# +# Linear scale +# ------------ +# Additionally, the *linscale* parameter determines how much visual space should be +# used for the linear range. More precisely, it defines the ratio of visual space +# of the region (0, linthresh) relative to one decade. + +fig, (ax1, ax2) = plt.subplots(nrows=2, layout="constrained") + +ax1.plot(x, y) +ax1.set_xscale('symlog', linthresh=1) +format_axes(ax1, title='Linear region: linthresh={linthresh}, linscale={linscale}') + +ax2.plot(x, y) +ax2.set_xscale('symlog', linthresh=1, linscale=0.1) +format_axes(ax2, title='Linear region: linthresh={linthresh}, linscale={linscale}') + +# %% +# The suitable value for linscale depends on the dynamic range of data. As most data +# will be outside the linear region, you typically the linear region only to cover +# a small fraction of the visual area. +# +# Limitations and alternatives +# ---------------------------- +# The coordinate transform used by ``symlog`` has a discontinuous gradient at the +# transition between its linear and logarithmic regions. Depending on data and +# scaling, this will be more or less obvious in the plot. + +fig, ax = plt.subplots() +ax.plot(x, y) +ax.set_xscale('symlog', linscale=0.05) +format_axes(ax, title="Discontinuous gradient at linear/log transition") + +# %% +# The ``asinh`` axis scale is an alternative transformation that supports a wide +# dynamic range with a smooth gradient and thus may avoid such visual artifacts. +# See :doc:`/gallery/scales/asinh_demo`. +# # # .. admonition:: References # From 70382ad7c4191e791dc57b5f0f3bed9c522bc809 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Sat, 21 Dec 2024 00:37:14 +0100 Subject: [PATCH 2/3] Update galleries/examples/scales/symlog_demo.py Co-authored-by: Greg Lucas --- galleries/examples/scales/symlog_demo.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/galleries/examples/scales/symlog_demo.py b/galleries/examples/scales/symlog_demo.py index ff24860b4210..c00a22c73768 100644 --- a/galleries/examples/scales/symlog_demo.py +++ b/galleries/examples/scales/symlog_demo.py @@ -43,11 +43,8 @@ # ---------------- # Since each decade on a logarithmic scale covers the same amount of visual space # and there are infinitely many decades between a given number and zero, the symlog -# scale must deviate from logarithmic mapping in a small range (-x0, x0), so that -# that range is mapped to a finite visual space. -# -# The symlog scale achieves this by defining a parameter *linthresh* and switching -# to a linear mapping in the region *(-linthresh, linthresh)*. +# scale must deviate from logarithmic mapping in a small range *(-linthresh, linthresh)*, so that +# the range is mapped to a finite visual space. def format_axes(ax, title=None): From 6694a9c136c4f5adf1cc72ecf8807c6771d21262 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Sat, 21 Dec 2024 01:32:51 +0100 Subject: [PATCH 3/3] Apply suggestions from code review --- galleries/examples/scales/symlog_demo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/galleries/examples/scales/symlog_demo.py b/galleries/examples/scales/symlog_demo.py index c00a22c73768..47742b853cc9 100644 --- a/galleries/examples/scales/symlog_demo.py +++ b/galleries/examples/scales/symlog_demo.py @@ -43,8 +43,8 @@ # ---------------- # Since each decade on a logarithmic scale covers the same amount of visual space # and there are infinitely many decades between a given number and zero, the symlog -# scale must deviate from logarithmic mapping in a small range *(-linthresh, linthresh)*, so that -# the range is mapped to a finite visual space. +# scale must deviate from logarithmic mapping in a small range +# *(-linthresh, linthresh)*, so that the range is mapped to a finite visual space. def format_axes(ax, title=None):