1

With this plot as an example: enter image description here

With the code:

import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.cbook as cbook

data = cbook.get_sample_data('goog.npz', np_load=True)['price_data'][0:200]

fig, ax = plt.subplots(figsize=(10, 7), constrained_layout=True)
ax.plot('date', 'adj_close', data=data)
# ax.xaxis.set_major_locator(mdates.MonthLocator())
# ax.grid(True)
ax.set_ylabel(r'Price [\$]')

ax.xaxis.set_major_formatter(
    mdates.ConciseDateFormatter(ax.xaxis.get_major_locator()))

plt.savefig("chart.png")

And I need to change the months from English to Spanish, ie: Dec to Dic, Apr to Abr, Jan to Ene and so on.

I can do it by changing the locale like so:

locale.setlocale(locale.LC_TIME, 'es_ES')

But I can't use it in the script because it runs on a serverless vm where you can't change any of the os configuration.

So I thought in changing the labels "manually" from an English month to a Spanish one. I've seen examples using DateFormatter but that doesn't work because again it relies in the system locale for the months names using strftime and any other fromatter I've seen has been using numbers not dates. So is there any solution to localize the names of the months?

Update Added solution below

PerseP
  • 1,177
  • 2
  • 14
  • 22

2 Answers2

1

One way is to use babel.dates for the date formatting, which allows for a locale argument without changing the system locale.

The basic syntax is:

babel.dates.format_datetime([datetime object], [format], locale=[locale])

Note that the format does not use % signs. See the package documentation for more information and many examples.

To use this package to format Matplotlib tick labels you can re-implement DateFormatter from the matplotlib source and replace the strftime() call with babel.dates.format_datetime().

As a proof of concept:

import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.cbook as cbook
import babel.dates

class CustomDateFormatter(mdates.ticker.Formatter):
   
    def __init__(self, fmt, fmtloc, tz=None, *, usetex=None):
        self.tz = mdates.UTC
        self.fmt = fmt
        self.fmtloc = fmtloc
        self._usetex = (usetex if usetex is not None else
                        mpl.rcParams['text.usetex'])

    def __call__(self, x, pos=0):
        result = babel.dates.format_datetime(mdates.num2date(x, self.tz), self.fmt, locale=self.fmtloc)
        return mdates._wrap_in_tex(result) if self._usetex else result

data = cbook.get_sample_data('goog.npz', np_load=True)['price_data'][0:200]

fig, ax = plt.subplots(figsize=(10, 7), constrained_layout=True)
ax.plot('date', 'adj_close', data=data)
ax.set_ylabel(r'Price [\$]')
ax.xaxis.set_major_formatter(
    CustomDateFormatter("MMM yyyy", "es"))

plt.savefig("chart.png")

Result:

enter image description here

For ConciseDateFormatter this idea is the same but this class is much more complex so you will need to modify it in many places in order to get the namespaces right (similar to for example mdates.num2date in the code above instead of a plain num2dates call in the original class).

Marijn
  • 1,640
  • 14
  • 24
  • Thanks for your answer I learned a lot from it. And yes the `ConciseDateFormatter ` code is too complicated to understand that's why I thought a sort of label translation would be easier to do. It seems that all date related things are overly complicated – PerseP Jan 09 '23 at 10:50
  • I posted an answer the changes the labels with `set_xticklabels` and Babel – PerseP Jan 10 '23 at 10:34
  • @PerseP that is a good solution, much easier than mine :) – Marijn Jan 10 '23 at 11:49
1

Using this answer and the answer by Marijn I came up with this solution that uses set_xticklabels to change the labels and Babel to change the months to a localized version without changing the locale:

import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.cbook as cbook
import babel.dates
import matplotlib.ticker as mticker

data = cbook.get_sample_data('goog.npz', np_load=True)['price_data'][0:200]

fig, ax = plt.subplots(figsize=(10, 7), constrained_layout=True)
ax.plot('date', 'adj_close', data=data)
# ax.xaxis.set_major_locator(mdates.MonthLocator())
# ax.grid(True)
ax.set_ylabel(r'Price [\$]')

ax.xaxis.set_major_formatter(
    mdates.ConciseDateFormatter(ax.xaxis.get_major_locator()))

ticks_loc = ax.get_xticks().tolist()
ax.xaxis.set_major_locator(mticker.FixedLocator(ticks_loc))
ax.set_xticklabels([babel.dates.format_datetime(mdates.num2date(x),'yyyy',locale='es') 
        if mdates.num2date(x).month == 1
        else babel.dates.format_datetime(mdates.num2date(x),'MMM',locale='es')
        for x in ticks_loc])

plt.savefig("chart.png")

It produces:

enter image description here

PerseP
  • 1,177
  • 2
  • 14
  • 22