Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Re-write sym-log-norm#16391

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Closed
dstansby wants to merge8 commits intomatplotlib:masterfromdstansby:symlog-overhaul
Closed
Show file tree
Hide file tree
Changes fromall 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
12 changes: 11 additions & 1 deletiondoc/api/prev_api_changes/api_changes_3.2.0/behavior.rst
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,9 +2,19 @@
Behavior changes
----------------

Fixed calculations in `.SymLogNorm`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The symmetrical log normalizer was previously returning erroneous values, so has
be re-written to give the correct behaviour. Aside from returning different
values:

- The base in which the number of decades in the log range is calculated can
now be specified by the ``base`` keyword argument, which defaults to 10.
- The behaviour when passing both ``vmin`` and ``vmax`` which were not negative
of each other is ill-defined, so this has been disallowed.

Reduced default value of :rc:`axes.formatter.limits`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Changed the default value of :rc:`axes.formatter.limits` from -7, 7 to
-5, 6 for better readability.

Expand Down
92 changes: 55 additions & 37 deletionslib/matplotlib/colors.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1214,8 +1214,8 @@ class SymLogNorm(Normalize):
(-*linthresh*, *linthresh*).
"""
def __init__(self, linthresh, linscale=1.0,
vmin=None, vmax=None, clip=False):
"""
vmin=None, vmax=None, clip=False, base=10):
r"""
Parameters
----------
linthresh : float
Expand All@@ -1228,12 +1228,34 @@ def __init__(self, linthresh, linscale=1.0,
example, when *linscale* == 1.0 (the default), the space used for
the positive and negative halves of the linear range will be equal
to one decade in the logarithmic range.
base : float, default: 10
The base in which the number of decades in the log range is
calculated. For example, if ``base=2``, ``linthresh=2`` and
``vmax=8``, the number of decades is :math:`\log_{2} (8 / 2) = 2`.

Notes
-----
Currently SymLogNorm only works with ``vmin == -vmax``, such that an
input of 0 always maps to half-way in the scale.
"""
if vmin is not None and vmax is not None and vmin != -vmax:
raise ValueError('SymLogNorm only works for vmin = -vmax '
f'(got vmin={vmin}, vmax={vmax})')
Normalize.__init__(self, vmin, vmax, clip)
self.linthresh = float(linthresh)
self._linscale_adj = (linscale / (1.0 - np.e ** -1))
if vmin is not None and vmax is not None:
self._transform_vmin_vmax()
self.base = float(base)
self.linscale = float(linscale)

@property
def _linear_size(self):
'Return the size of the linear portion in transformed space'
# Number of decades in the logarithmic range
ndec = self._logbase(self.vmax / self.linthresh)
return 1 / (1 + self.linscale / ndec)

def _logbase(self, val):
'Take the log of val in the base `self.base`'
return np.log(val) / np.log(self.base)

def __call__(self, value, clip=None):
if clip is None:
Expand All@@ -1254,57 +1276,53 @@ def __call__(self, value, clip=None):
mask=mask)
# in-place equivalent of above can be much faster
resdat = self._transform(result.data)
resdat -= self._lower
resdat /= (self._upper - self._lower)

if is_scalar:
result = result[0]
return result

def _transform(self, a):
"""Inplace transformation."""
"""In-place mapping from *a* to [0, 1]"""
with np.errstate(invalid="ignore"):
masked = np.abs(a) > self.linthresh
sign = np.sign(a[masked])
log = (self._linscale_adj + np.log(np.abs(a[masked]) / self.linthresh))
log *= sign * self.linthresh
a[masked] = log
a[~masked] *= self._linscale_adj
logregion = np.abs(a) > self.linthresh

# Transform log value
sign = np.sign(a[logregion])
log = ((1 - self._linear_size) * self._logbase(np.abs(a[logregion])) +
self._linear_size)
a[logregion] = log * sign

# Transform linear values
a[~logregion] *= self._linear_size / self.linthresh

# Transform from [-1, 1] to [0, 1]
a += 1
a /= 2
return a

def _inv_transform(self, a):
"""Inverse inplace Transformation."""
masked = np.abs(a) > (self.linthresh * self._linscale_adj)
sign = np.sign(a[masked])
exp = np.exp(sign * a[masked] / self.linthresh - self._linscale_adj)
exp *= sign * self.linthresh
a[masked] = exp
a[~masked] /= self._linscale_adj
# Transform from [0, 1] to [-1, 1]
a *= 2
a -= 1

# Transform back log values
logregion = np.abs(a) > self._linear_size
sign = np.sign(a[logregion])
exp = self.base**((np.abs(a[logregion]) - self._linear_size) /
(1 - self._linear_size))
a[logregion] = exp * sign

# Transform back linear values
a[~logregion] /= self._linear_size / self.linthresh
return a

def _transform_vmin_vmax(self):
"""Calculates vmin and vmax in the transformed system."""
vmin, vmax = self.vmin, self.vmax
arr = np.array([vmax, vmin]).astype(float)
self._upper, self._lower = self._transform(arr)

def inverse(self, value):
if not self.scaled():
raise ValueError("Not invertible until scaled")
val = np.ma.asarray(value)
val = val * (self._upper - self._lower) + self._lower
return self._inv_transform(val)

def autoscale(self, A):
# docstring inherited.
super().autoscale(A)
self._transform_vmin_vmax()

def autoscale_None(self, A):
# docstring inherited.
super().autoscale_None(A)
self._transform_vmin_vmax()


class PowerNorm(Normalize):
"""
Expand Down
44 changes: 35 additions & 9 deletionslib/matplotlib/tests/test_colors.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -395,22 +395,48 @@ def test_TwoSlopeNorm_premature_scaling():


def test_SymLogNorm():
"""
Test SymLogNorm behavior
"""
norm = mcolors.SymLogNorm(3, vmax=5, linscale=1.2)
# Test SymLogNorm behavior
norm = mcolors.SymLogNorm(linthresh=3, vmax=5, linscale=1.2, base=np.e)
vals = np.array([-30, -1, 2, 6], dtype=float)
normed_vals = norm(vals)
expected = [0., 0.53980074, 0.826991, 1.02758204]
expected = [-0.842119, 0.450236, 0.599528, 1.277676]
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

I think these values were just plain wrong before...

  • The first value (-30) is less thanvmax (5), so should come out as less than zero
  • The second value (-1) is less than 0, so should come out as less than 0.5

assert_array_almost_equal(normed_vals, expected)
_inverse_tester(norm, vals)
_scalar_tester(norm, vals)
_mask_tester(norm, vals)

# Ensure that specifying vmin returns the same result as above
norm = mcolors.SymLogNorm(3, vmin=-30, vmax=5, linscale=1.2)

def test_symlognorm_vals():
vals = [-10, -1, 0, 1, 10]

norm = mcolors.SymLogNorm(linthresh=1, vmin=-10, vmax=10, linscale=1)
normed_vals = norm(vals)
assert_array_almost_equal(normed_vals, expected)
expected = [0, 0.25, 0.5, 0.75, 1]
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is consistent withSymmetricLogTransform yet. I'm OK if we want to change that as well to be consistent with this code, but they can't be inconsistent!

importmatplotlib.scaleasmscaletrans=mscale.SymmetricalLogTransform(10,1,1)new=trans.transform([-10,-1,0,1,10])new= (new-new[0])/ (new[-1]-new[0])print(new)
[0.0 0.23684210526315788 0.5 0.7631578947368421 1.0]

I'm fine if this implementation is desired, but then we need to changeSymmetricLogTransform.

dstansby reacted with thumbs up emoji
assert_array_almost_equal(norm(vals), normed_vals)
assert_array_almost_equal(norm.inverse(norm(vals)), vals)

# If we increase linscale to 2, the space for the linear range [0, 1]
# should be twice as large as the space for the logarithmic range [1, 10]
norm = mcolors.SymLogNorm(linthresh=1, vmin=-10, vmax=10, linscale=2)
normed_vals = norm(vals)
expected = [0, 1/6, 0.5, 5/6, 1]
assert_array_almost_equal(norm(vals), normed_vals)
assert_array_almost_equal(norm.inverse(norm(vals)), vals)

# Similarly, going the other way means the linear range should shrink
norm = mcolors.SymLogNorm(linthresh=1, vmin=-10, vmax=10, linscale=0.5)
normed_vals = norm(vals)
expected = [0, 2/6, 0.5, 4/6, 1]
assert_array_almost_equal(norm(vals), normed_vals)
assert_array_almost_equal(norm.inverse(norm(vals)), vals)

# Now check a different base to base 10
vals = [-8, 4, -2, 0, 2, 4, 8]
norm = mcolors.SymLogNorm(linthresh=2, vmax=8, linscale=1, base=2)
normed_vals = norm(vals)
expected = [0, 1/8, 2/8, 3/8, 0.5, 5/8, 6/8, 7/8, 1]
assert_array_almost_equal(norm(vals), normed_vals)
assert_array_almost_equal(norm.inverse(norm(vals)), vals)


def test_SymLogNorm_colorbar():
Expand DownExpand Up@@ -907,7 +933,7 @@ def __add__(self, other):
for norm in [mcolors.Normalize(), mcolors.LogNorm(),
mcolors.SymLogNorm(3, vmax=5, linscale=1),
mcolors.Normalize(vmin=mydata.min(), vmax=mydata.max()),
mcolors.SymLogNorm(3, vmin=mydata.min(), vmax=mydata.max()),
mcolors.SymLogNorm(3, vmin=-10, vmax=10),
mcolors.PowerNorm(1)]:
assert_array_equal(norm(mydata), norm(data))
fig, ax = plt.subplots()
Expand Down

[8]ページ先頭

©2009-2025 Movatter.jp