Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scientific tick numbers with space as thousands separator in matplotlib

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...

like image 440
Dániel Somogyi Avatar asked Nov 07 '22 06:11

Dániel Somogyi


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

like image 189
Stef Avatar answered Nov 14 '22 22:11

Stef