Scientific tick numbers with space as thousands separator in matplotlib

Viewed 651

I want to have ticks on the y-axis formatted the following way:

  • numbers < 1M should be written out, with spaces as thousands separator (e.g. 20 000)
  • numbers > 1M should be in scientific notation, just as the Scalar formatter does (2.0 with x10^6 above the axis).

Now, I can achieve both of these functionalities seperataly.

matplotlib.ticker.FuncFormatter(lambda x, p: format(x, ',').replace(',', ' '))

replaces thousands separator with space.

formatter = matplotlib.ticker.ScalarFormatter()
formatter.set_powerlimits((-4, 6))

gives scientific notation for 1M+.

I guess I have to set a ScalarFormatter and slightly change one of its functions, or I have to write above the y-axis with the FuncFormatter. However, I could not find how to do that.

EDIT

I slightly came closer to my goal. By using format(x, ',.6g').replace(',', ' ') I at least have the desired number formats.
Nevertheless, I'd prefer to have the exponential notation above the axis, and for all of my ticks, if the largest grows too large...

1 Answers

In principle you could just set_useLocale(True) for the scalar formatter and use a modified locale. ScalarFormatter doesn't, however, use groupings (see source), so we must derive our own ScalarFormatter and set grouping=True).

import matplotlib.pyplot as plt
from matplotlib.ticker import ScalarFormatter
import locale

fig, (ax1, ax2) = plt.subplots(ncols=2)

ax1.set_ylim(1, 10_000)
ax2.set_ylim(1, 10_000_000)


class MyScalarFormatter(ScalarFormatter):
    def __call__(self, x, pos=None):
        if len(self.locs) == 0:
            return ''
        else:
            xp = (x - self.offset) / (10. ** self.orderOfMagnitude)
            if abs(xp) < 1e-8:
                xp = 0
            if self._useLocale:
                s = locale.format_string(self.format, (xp,), grouping=True)
            else:
                s = self.format % xp
            return self.fix_minus(s)

sf = MyScalarFormatter(useLocale=True)
locale._override_localeconv = {'thousands_sep': ' ', 'grouping': [3,0]}

ax1.yaxis.set_major_formatter(sf)
ax2.yaxis.set_major_formatter(sf)

plt.show()

enter image description here

Related