Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork7.9k
Description
Bug summary:
SymmetricalLogLocator
appears to suffer a floating-point precision error in calculating the boundaries of the log regions of asymlog
plot, which results in the addition of extra major ticks in the linear region of the plot.
Code reproducing the issue:
importnumpyasnpimportmatplotlib.pyplotasplt# v3.2.2x=y=np.linspace(-1e5,1e5,100)plt.plot(x,y)plt.xscale('symlog',linthreshx=1e3,subsx=[2,3,4,5,6,7,8,9])
(OS=Windows 10, Python=3.8.3, Anaconda=2020.07)
Output (with error highlighted in red):
Discussion:
Major ticks generated bySymmetricalLogLocator
sometimes include an extra tick in the linear region:
>>>locator=mpl.ticker.SymmetricalLogLocator(linthresh=1e3,base=10)>>>locator.tick_values(vmin=0,vmax=1e6)# array([0.e+00, 1.e+02, 1.e+03, 1.e+04, 1.e+05, 1.e+06]) <-- 1.e+02 is in the linear region
SymmetricalLogLocator
calculates the inner (towards zero) boundaries of the log regions via the following base-agnostic logarithm:
lo=np.floor(np.log(linthresh)/np.log(base))
However, whenbase=10
, some power-of-ten thresholds cause floating-point precision problems:
>>>base=10>>> [np.log(linthresh)/np.log(base)forlinthreshinbase**np.arange(10)]# [0.0, 1.0, 2.0, 2.9999999999999996, 4.0, 5.0, 5.999999999999999, 7.0, 8.0, 8.999999999999998]
With thenp.floor()
calculation, this introduces an additional erroneous innermost major tick.
@dstansby proposed a possible fix to a related issue in#14309, which changesnp.floor()
tonp.ceil()
. This looks like it would work with the precision errors I identified above, which all err towards zero, but it would fail with bases that err away from zero, e.g.,base=5
:
>>>base=5>>> [np.log(linthresh)/np.log(base)forlinthreshinbase**np.arange(10)]# [0.0, 1.0, 2.0, 3.0000000000000004, 4.0, 5.0, 6.000000000000001, 7.0, 8.0, 9.0]
Other possible solutions I can think of: (1) rounding tobase
within a certain proximity, or (2) adding a special case whenbase=10
whereinnp.log10()
is used instead ofnp.log()/np.log(base)
.