Fundamental Behavior of dt.week
The dt.week accessor, applied to a Pandas Series with datetime64 values, yields an integer representing the calendar week of the year for each timestamp. This calculation follows the ISO 8601 definition: Monday marks the start of the week, and the first week of the year is the one containing January 4th (or equivalently, the week that includes the year's first Thursday). The returned integer ranges from 1 to 53.
import pandas as pd
ts = pd.to_datetime(["2024-01-01", "2024-12-31", "2026-01-01"])
week_nums = ts.week
# Result: [1, 1, 1] (2024-01-01 is Monday and belongs to week 1; 2026-01-01 is Thursday, also week 1)
print(week_nums.tolist())
Building a Weekly Aggregation Pipeline
A common scenario involves grouping records by week to perform statistical summaries. The snippet below creates a DataFrame, enriches it with week numbers, and computes totals per week.
import numpy as np
dates = pd.date_range("2024-06-01", periods=100, freq="D")
df = pd.DataFrame({
"recorded": dates,
"amount": np.random.randint(50, 300, size=100)
})
df["iso_week"] = df["recorded"].dt.week
weekly_stats = df.groupby("iso_week", as_index=False).agg(
total_sales=("amount", "sum"),
avg_sales=("amount", "mean"),
transaction_count=("amount", "count")
)
print(weekly_stats.head())
To ensure weeks are uniquely identified across years, combine year and week into a single feature:
df["year_week"] = df["recorded"].dt.strftime("%G-W%V")
weekly_by_year = df.groupby("year_week")["amount"].sum().reset_index()
print(weekly_by_year.head())
Handling Non-Standard Week Definitions
Series.dt.week always uses ISO rules. When analysis requires Sunday as the first day, or defines the first week differently, shift the dates before extraction and optionally reverse the shift afterward.
# Trick for Sunday-based weeks: Treat Sunday as if it were Monday
seconds_in_day = pd.Timedelta(days=1)
adjusted = df["recorded"] - seconds_in_day
df["sunday_based_week"] = adjusted.dt.week
This adjustment re-maps Sundays into the previous Monday's ISO week. For a more robust solution, use pd.Timestamp arithmetic together with conditional logic, or adopt a dedicated calendar library when requirements are complex.
Diagnosing Common Issues
Non-datetime Columns
If datetime64 is missing, the .dt accessor raises AttributeError. Convert explicitly:
# When reading CSV
frame = pd.read_csv("records.csv", parse_dates=["timestamp_column"])
# Or after loading
frame["timestamp_column"] = pd.to_datetime(frame["timestamp_column"])
Mixed or Malformed Dates
Unparseable strings result in NaT values. Use errors='coerce' and drop or fill missing values:
dirty_dates = pd.Series(["2025/08/12", "not-a-date", "2025-08-13"])
converted = pd.to_datetime(dirty_dates, errors="coerce")
valid_mask = converted.notna()
clean_weeks = converted[valid_mask].dt.week
Time Zone Awareness
Timestamps with different UTC offsets may produce incorrect week numbers when viewed from a local perspective. Localizee all timestamps to a single time zone first:
localized = df["utc_timestamp"].dt.tz_convert("America/Chicago")
df["week_number_local"] = localized.dt.week
Year-End Edge Cases
Week numbers transition at year boundaries according to ISO rules, sometimes causing confusion. Validate year-week combinations by extracting both components:
df["iso_year"], df["iso_week"] = df["recorded"].dt.isocalendar().year, df["recorded"].dt.isocalendar().week
print(df[["recorded", "iso_year", "iso_week"]].drop_duplicates().head(10))