Monday is 0 and Sunday is 6. Everything awkward about this attribute follows from that one sentence.
What it does
Have you ever written df['date'].dt.dayofweek >= 5 to mean "the weekend," and been right for the country you happened to be in? .dt.dayofweek returns the day of the week as an int32, and it counts from Monday: Monday is 0, Tuesday 1, and Sunday is 6. That matches Python's own datetime.weekday(), and it is not going to change.
I start my week on Sunday. So does the calendar on my phone, and so does Excel's WEEKDAY. Pandas' numbering is a fact about Pandas, not a fact about weeks, and the whole job here is keeping the two apart.
.dt.day_of_week and .dt.weekday are aliases. I compared all three element by element on a year of data; they are identical, so use whichever reads best.
Official documentation: Series.dt.dayofweek.
A worked example, on real data
Wikimedia publishes daily pageview counts for every Wikipedia, free and without a key. A year of the Hebrew Wikipedia is 365 rows, and its readers keep an Israeli week — Sunday through Thursday are workdays, and the weekend is Friday and Saturday:
import pandas as pd
url = ('https://wikimedia.org/api/rest_v1/metrics/pageviews/aggregate/'
'he.wikipedia/all-access/user/daily/20250101/20251231')
df = (pd
.json_normalize(pd.read_json(url,
storage_options={'User-Agent': 'Mozilla/5.0'})['items'])
.assign(date=lambda df_: pd.to_datetime(df_['timestamp'],
format='%Y%m%d%H'))
[['date', 'views']])
df.groupby(df['date'].dt.dayofweek)['views'].mean().round().astype(int)
date
0 1818144
1 1833972
2 1822092
3 1814446
4 1822678
5 1944397
6 1898744
Name: views, dtype: int64
Seven correct numbers, and a reading order that makes them hard to interpret: the two busiest days are 5 and 6, sitting at the bottom, and you have to remember which is which before you can say anything. Rotate the numbering so Sunday is 0 — add one, take the remainder — and bring the names along so nobody has to count:
(df
.groupby([df['date'].dt.dayofweek.add(1).mod(7).rename('n'),
df['date'].dt.day_name().rename('day')])['views']
.mean().round().astype(int)
.reset_index('day'))
day views
n
0 Sunday 1898744
1 Monday 1818144
2 Tuesday 1833972
3 Wednesday 1822092
4 Thursday 1814446
5 Friday 1822678
6 Saturday 1944397
Identical numbers, and now the shape is legible: five flat workdays around 1.82 million, Sunday a little above them, and a real spike on Saturday — Shabbat, the one day when nobody in Israel is at work. The first output contained that finding too. It just did not let you see it.
.add(1).mod(7) is the whole trick. If the ordering has to survive a chain rather than one groupby, dt.day_name has the better answer: an ordered Categorical typed out Sunday through Saturday.
Mistakes people make
>= 5 as a weekend test. It means Saturday and Sunday, which is a claim about the calendar, not a definition of the weekend. On this data:
df.loc[df['date'].dt.dayofweek >= 5, 'views'].mean().round().astype(int)
df.loc[df['date'].dt.dayofweek.isin([4, 5]), 'views'].mean().round().astype(int)
1921570
1883537
Sat–Sun against the actual Israeli Fri–Sat. Both plausible, neither flagged, and only one of them is the weekend for these readers.
Mixing the numbering with a Sunday-first Categorical. If you have taught a name column that Sunday comes first, .dt.dayofweek has not heard about it and still calls Sunday 6. Sort by one or the other, never both in the same chain.
Assuming ISO agrees. .dt.isocalendar()['day'] also gives the day of the week, and it numbers Monday 1 through Sunday 7 — off by one from .dt.dayofweek, and a UInt32 rather than an int32. Excel's WEEKDAY defaults to Sunday 1 through Saturday 7. Three conventions, three answers.
Parentheses. .dt.dayofweek() raises TypeError: 'Series' object is not callable. Numbers are attributes; names are methods.
Where it shows up in Bamboo Weekly
Four Bamboo Weekly solutions use .dt.dayofweek, and it is worth noticing that in three of them I ended up preferring the names.
Bamboo Weekly #152: Congestion pricing is the two-key pattern above, on MTA ridership: group by dayofweek and day_name together, then reset_index the number away so the display keeps the label and the sort keeps the order.
Bamboo Weekly #151: PyPI in 2025 puts the choice plainly — many people would test dt.dayofweek > 4, and I built is_weekend from dt.day_name().isin([...]) instead, because it reads. PyPI serves 2.8 billion downloads on a weekday and 1.7 billion on a weekend day.
Bamboo Weekly #172: World Cup uses the number where the number is the point: .loc[pd.col('datetime').dt.dayofweek > 4] as a filter, to compare who wins at weekend matches.
Bamboo Weekly #162: Spotify and car accidents is where I say out loud that I never remember which number is which, and reach for day_name — and then find that nine of ten hit albums dropped on a Friday.
Practice it
Work through a dt.dayofweek exercise, with instant feedback and no signup required: practice.lernerpython.com/bamboo-weekly/dt-dayofweek/
Go deeper
The names, and the ordered Categorical that keeps them in calendar order, are on dt.day_name. The rest of the numeric accessor is on dt.year, and the time-of-day half on dt.hour. Weekday filters usually want isin, and none of it runs before to_datetime. The Pandas user guide's time series chapter covers isocalendar and the business-day offsets, which take a weekend definition as an argument.
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.
Related methods
.dt.day_name()— when you want the weekday's name rather than its number.isin()— which is how you select a weekend once you know which numbers it is