30
votes

I'd like to make a square axis scatter plot with matplotlib. Normally using set_scale("log") works great, but it limits me to log10. I'd like to make the plot in log2. I saw the solution here: How to produce an exponentially scaled axis?

but it is quite complicated and does not work if you have 0 values in your arrays, which I do. I'd like to simply ignore those like other numpy functions do.

For example:

log2scatter(data1, data2)

where data1 and data2 contain 0s should have a logarithmic scale on the x and y axis, with logarithmic spaced ticks. Just like log10, except log2...

Thanks.

2
Logs are only defined for positive arguments. This holds irrespective of the base, i.e. it holds for natural logs, log base 10, log base 2 etc. Hence you just can't plot something which has zeros in a logscale, unless you do something to these zero values. - ev-br
Use the answer from that question but filter out the y-values that are 0 and their corresponding x values first (that's how numpy ignores them anyway). - mathematical.coffee
What's an elegant way to do that in numpy? The filtering complicates the code since now I have to have a copy of the array unfiltered, and a copy filtered... - user248237

2 Answers

53
votes

Just specify basex=2 or basey=2.

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.set_xscale('log', basex=2)
ax.set_yscale('log', basey=2)

ax.plot(range(1024))
plt.show()

enter image description here

For the zero-crossing behavior, what you're referring to is a "Symmetric Log" plot (a.k.a. "symlog"). For whatever it's worth, data isn't filtered out, it's just a linear plot near 0 and a log plot everywhere else. It's the scale that changes, not the data.

Normally you'd just do ax.set_xscale('symlog', basex=2) but using a non-10 base appears to be buggy at the moment for symlog plots.

Edit: Heh! The bug appears to be due to a classic mistake: using a mutable default argument.
I've filed a bug report, but if you feel like fixing it, you'll need to make a minor edit to lib/matplotlib/ticker.py, around line 1376, in the __init__ method of SymmetricalLogLocator.

Instead of

def __init__(self, transform, subs=[1.0]):
    self._transform = transform
    self._subs = subs
    ...

Change it to something similar to:

def __init__(self, transform, subs=None):
    self._transform = transform
    if subs is None:
        self._subs = [1.0]
    else:
        self._subs = subs
    ....

With that change made, it behaves as expected...

import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots()
ax.set_xscale('symlog', basex=2)
ax.set_yscale('symlog', basey=2)

x = np.arange(-1024, 1024)
ax.plot(x, x)

plt.show()

enter image description here

11
votes

If you are using plt.xscale, you still need to specify basex, not base:

plt.figure()
plt.plot(x, y)
plt.xscale('log', basex=2)
plt.show()