Skip to content

pandas dt.month_name

The readable form of the month — and a locale= argument that is doing more than you think.

What it does

Have you ever put month numbers on a chart axis and watched someone in the room silently count on their fingers? .dt.month_name() is the fix. It returns the full English name of the month for every value in a datetime column: January, February, and so on through December.

It is a method, so it takes parentheses. Its numeric partner, dt.month, is an attribute and does not. Names are methods, numbers are attributes — the rule holds right across the .dt accessor. And the price of the name is the ordering: strings sort alphabetically, so a groupby opens the year with April and closes it with September. That problem is identical for weekdays and months, and I work through the ordered-Categorical solution once, on dt.day_name.

Official documentation: Series.dt.month_name.

A worked example, on real data

Open-Meteo serves historical weather for any coordinate, free and without a key. Eleven years of daily high temperatures in Jerusalem is 4,018 rows:

import pandas as pd

url = ('https://archive-api.open-meteo.com/v1/archive'
       '?latitude=31.7683&longitude=35.2137'
       '&start_date=2015-01-01&end_date=2025-12-31'
       '&daily=temperature_2m_max&timezone=Asia%2FJerusalem')

df = (pd
      .DataFrame(pd.read_json(url)['daily'].to_dict())
      .assign(time=lambda df_: pd.to_datetime(df_['time']))
      .rename(columns={'temperature_2m_max': 'high'}))

df.groupby(df['time'].dt.month_name())['high'].mean().round(1)
time
April        23.2
August       32.7
December     15.1
February     14.0
January      12.8
July         32.7
June         30.6
March        17.7
May          27.8
November     20.4
October      26.5
September    30.8
Name: high, dtype: float64

Twelve correct averages in an order that makes the seasons invisible. Group on the number and carry the name alongside it, and the same twelve values become a climate:

(df
 .groupby([df['time'].dt.month.rename('n'),
           df['time'].dt.month_name().rename('month')])['high']
 .mean().round(1)
 .reset_index('month'))
        month  high
n
1     January  12.8
2    February  14.0
3       March  17.7
4       April  23.2
5         May  27.8
6        June  30.6
7        July  32.7
8      August  32.7
9   September  30.8
10    October  26.5
11   November  20.4
12   December  15.1

Sort by the number, display the name. That is the cheap fix, and it is enough when the ordering only has to survive one call. When it has to survive a whole chain, make the column an ordered Categorical instead — dt.day_name shows how.

The locale argument

There is exactly one argument, and it is worth knowing what it actually does:

df['time'].dt.month_name(locale='he_IL').head(1).tolist()
df['time'].dt.month_name(locale='fr_FR').head(1).tolist()
['ינואר']
['Janvier']

Pandas is not carrying a translation table. It hands the naming to your operating system's locale database, which means three things. The locale has to be installed — locale='fr_FR' works on my Mac and raises locale.Error: unsupported locale setting on a stripped-down Linux container that never installed it. The exact spelling of the code varies by platform, so 'fr_FR' on macOS may need to be 'fr_FR.UTF-8' elsewhere. And capitalization is the locale's choice, not Pandas' — macOS gives me Janvier where glibc gives janvier.

If a notebook has to run on someone else's machine, either pin the locale in the environment or map the English names yourself with replace.

Mistakes people make

Forgetting the parentheses. This is the one that does not announce itself. df['time'].dt.month_name returns a bound method, and assigning it to a column fills every row with the same method object — no exception, and a groupby on it puts all 4,018 rows in one group. Going the other way is louder: .dt.month() raises TypeError: 'Series' object is not callable.

Wanting Jan and expecting an argument for it. There is none. .dt.strftime('%b') gives the abbreviation and %B the full name; I build those at strfti.me rather than from memory. %b respects the locale too, if you set one.

Calling it on a DatetimeIndex with .dt. On an index there is no accessor: df.index.month_name(), not df.index.dt.month_name().

Calling it on a Period column. to_period('M') keeps the numbers and drops the names — see dt.month for that error and its fix.

Where it shows up in Bamboo Weekly

.dt.month_name() is rare in the Bamboo Weekly archive: two solutions out of 185 use it, and both are worth reading because both hit the ordering problem head-on.

Bamboo Weekly #134: Taiwan weather puts month names into the columns of a pivot_table, gets them back alphabetized, and fixes it a third way — sort=False, which keeps the file's own order. That works when the source is already in calendar order, and it is a useful reminder that the Categorical is not the only tool.

Bamboo Weekly #108: Measles calls month_name() on a DatetimeIndex, with no .dt in sight, immediately next to a bare .year. It is where I admit in writing that if you find this inconsistent, you are not alone.

Practice it

Work through a dt.month_name exercise, with instant feedback and no signup required: practice.lernerpython.com/bamboo-weekly/dt-month-name/

Go deeper

dt.day_name is the page for keeping names in calendar order, dt.month for the number and for to_period, and dt.year for the rest of the numeric accessor. Everything here assumes to_datetime has already run. The Pandas user guide's time series chapter has the full accessor listing.

More Pandas videos on Python and Pandas with Reuven Lerner.

Bamboo Weekly is the practice. If you want the structured version — full courses with downloadable Jupyter notebooks, plus live Pandas office hours when you get stuck — that is LernerPython+Data. A paid Bamboo Weekly subscription is included with it.

See it on real data

Below are the 2 Bamboo Weekly exercises that use dt.month_name on real-world data — try each one, then study the worked solution.

Part of the Pandas Methods Index. See also practice by skill.