Skip to content

Commit 0d89bf6

Browse files
authored
Merge pull request #21039 from dstansby/hexbin-marginal
Fix `hexbin` marginals and log scaling
2 parents d0b5bdc + 7c800f9 commit 0d89bf6

File tree

4 files changed

+51
-47
lines changed

4 files changed

+51
-47
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
hexbin with a log norm
2+
----------------------
3+
`~.axes.Axes.hexbin` no longer (incorrectly) adds 1 to every bin value if a
4+
log norm is being used.

lib/matplotlib/axes/_axes.py

+45-46
Original file line numberDiff line numberDiff line change
@@ -4589,6 +4589,11 @@ def reduce_C_function(C: array) -> float
45894589
# Count the number of data in each hexagon
45904590
x = np.array(x, float)
45914591
y = np.array(y, float)
4592+
4593+
if marginals:
4594+
xorig = x.copy()
4595+
yorig = y.copy()
4596+
45924597
if xscale == 'log':
45934598
if np.any(x <= 0.0):
45944599
raise ValueError("x contains non-positive values, so can not"
@@ -4617,10 +4622,6 @@ def reduce_C_function(C: array) -> float
46174622
sx = (xmax - xmin) / nx
46184623
sy = (ymax - ymin) / ny
46194624

4620-
if marginals:
4621-
xorig = x.copy()
4622-
yorig = y.copy()
4623-
46244625
x = (x - xmin) / sx
46254626
y = (y - ymin) / sy
46264627
ix1 = np.round(x).astype(int)
@@ -4746,11 +4747,6 @@ def reduce_C_function(C: array) -> float
47464747
vmin = vmax = None
47474748
bins = None
47484749

4749-
if isinstance(norm, mcolors.LogNorm):
4750-
if (accum == 0).any():
4751-
# make sure we have no zeros
4752-
accum += 1
4753-
47544750
# autoscale the norm with current accum values if it hasn't
47554751
# been set
47564752
if norm is not None:
@@ -4781,40 +4777,42 @@ def reduce_C_function(C: array) -> float
47814777
if not marginals:
47824778
return collection
47834779

4780+
# Process marginals
47844781
if C is None:
47854782
C = np.ones(len(x))
47864783

4787-
def coarse_bin(x, y, coarse):
4788-
ind = coarse.searchsorted(x).clip(0, len(coarse) - 1)
4789-
mus = np.zeros(len(coarse))
4790-
for i in range(len(coarse)):
4791-
yi = y[ind == i]
4784+
def coarse_bin(x, y, bin_edges):
4785+
"""
4786+
Sort x-values into bins defined by *bin_edges*, then for all the
4787+
corresponding y-values in each bin use *reduce_c_function* to
4788+
compute the bin value.
4789+
"""
4790+
nbins = len(bin_edges) - 1
4791+
# Sort x-values into bins
4792+
bin_idxs = np.searchsorted(bin_edges, x) - 1
4793+
mus = np.zeros(nbins) * np.nan
4794+
for i in range(nbins):
4795+
# Get y-values for each bin
4796+
yi = y[bin_idxs == i]
47924797
if len(yi) > 0:
4793-
mu = reduce_C_function(yi)
4794-
else:
4795-
mu = np.nan
4796-
mus[i] = mu
4798+
mus[i] = reduce_C_function(yi)
47974799
return mus
47984800

4799-
coarse = np.linspace(xmin, xmax, gridsize)
4801+
if xscale == 'log':
4802+
bin_edges = np.geomspace(xmin, xmax, nx + 1)
4803+
else:
4804+
bin_edges = np.linspace(xmin, xmax, nx + 1)
4805+
xcoarse = coarse_bin(xorig, C, bin_edges)
48004806

4801-
xcoarse = coarse_bin(xorig, C, coarse)
4802-
valid = ~np.isnan(xcoarse)
48034807
verts, values = [], []
4804-
for i, val in enumerate(xcoarse):
4805-
thismin = coarse[i]
4806-
if i < len(coarse) - 1:
4807-
thismax = coarse[i + 1]
4808-
else:
4809-
thismax = thismin + np.diff(coarse)[-1]
4810-
4811-
if not valid[i]:
4808+
for bin_left, bin_right, val in zip(
4809+
bin_edges[:-1], bin_edges[1:], xcoarse):
4810+
if np.isnan(val):
48124811
continue
4813-
4814-
verts.append([(thismin, 0),
4815-
(thismin, 0.05),
4816-
(thismax, 0.05),
4817-
(thismax, 0)])
4812+
verts.append([(bin_left, 0),
4813+
(bin_left, 0.05),
4814+
(bin_right, 0.05),
4815+
(bin_right, 0)])
48184816
values.append(val)
48194817

48204818
values = np.array(values)
@@ -4829,20 +4827,21 @@ def coarse_bin(x, y, coarse):
48294827
hbar.update(kwargs)
48304828
self.add_collection(hbar, autolim=False)
48314829

4832-
coarse = np.linspace(ymin, ymax, gridsize)
4833-
ycoarse = coarse_bin(yorig, C, coarse)
4834-
valid = ~np.isnan(ycoarse)
4830+
if yscale == 'log':
4831+
bin_edges = np.geomspace(ymin, ymax, 2 * ny + 1)
4832+
else:
4833+
bin_edges = np.linspace(ymin, ymax, 2 * ny + 1)
4834+
ycoarse = coarse_bin(yorig, C, bin_edges)
4835+
48354836
verts, values = [], []
4836-
for i, val in enumerate(ycoarse):
4837-
thismin = coarse[i]
4838-
if i < len(coarse) - 1:
4839-
thismax = coarse[i + 1]
4840-
else:
4841-
thismax = thismin + np.diff(coarse)[-1]
4842-
if not valid[i]:
4837+
for bin_bottom, bin_top, val in zip(
4838+
bin_edges[:-1], bin_edges[1:], ycoarse):
4839+
if np.isnan(val):
48434840
continue
4844-
verts.append([(0, thismin), (0.0, thismax),
4845-
(0.05, thismax), (0.05, thismin)])
4841+
verts.append([(0, bin_bottom),
4842+
(0, bin_top),
4843+
(0.05, bin_top),
4844+
(0.05, bin_bottom)])
48464845
values.append(val)
48474846

48484847
values = np.array(values)

lib/matplotlib/tests/test_axes.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -781,7 +781,8 @@ def test_hexbin_log():
781781
y = np.power(2, y * 0.5)
782782

783783
fig, ax = plt.subplots()
784-
h = ax.hexbin(x, y, yscale='log', bins='log')
784+
h = ax.hexbin(x, y, yscale='log', bins='log',
785+
marginals=True, reduce_C_function=np.sum)
785786
plt.colorbar(h)
786787

787788

0 commit comments

Comments
 (0)