36

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.

Community
  • 1
  • 1
  • 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 Jan 16 '12 at 23:47
  • 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 Jan 16 '12 at 23:55
  • 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... –  Jan 17 '12 at 00:06

2 Answers2

65

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

Joe Kington
  • 275,208
  • 71
  • 604
  • 463
  • I get really strange default x and y limits using base=2 (and actually even without it). It doesn't set it to be the smallest values in either dimension. It picks really strange limits that hide most of the data. How can this be fixed aside from manually setting x/y limits? –  Jan 17 '12 at 00:35
  • 1
    Shouldn't have to override/hack a class in matplotlib to make a freakin' scatter plot in log2 versus log2... it's so sad and frustrating. :( –  Jan 17 '12 at 00:36
  • The symlog part is a bug. The limits you're referring to are just the way that limits behave for a log plot. They "snap" to the nearest power of the base. If you want the limits to strictly end at the min and max of the data, then specify `ax.axis('tight')`. – Joe Kington Jan 17 '12 at 01:03
  • 13
    Use `base=2` in `matplotlib >= 3.3` instead of `basex=2` and `basey=2`. – rvf May 21 '21 at 12:31
16

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()
Jorge Leitao
  • 19,085
  • 19
  • 85
  • 121
  • 1
    Just like [this comment](https://stackoverflow.com/questions/8887544/making-square-axes-plot-with-log2-scales-in-matplotlib#comment119551375_8888110) suggested. This depends on the matplotlib version. Right now the code above will produce `TypeError: LogScale.__init__() got an unexpected keyword argument 'basex'` – Hacker Jun 15 '22 at 14:09
  • 4
    plt.xscale('log', base=2) worked for me – Mike B Aug 01 '22 at 12:51