Frequencies & Date Ranges
By the end of this lesson you will be able to create any regular calendar in pandas: every day, every Monday, every month start, every quarter of the Indian financial year. You will also check whether a series really is regular and count working days around holidays.
What a frequency is
A frequency is the regular gap between the rows of a time series. Daily milk collection at a dairy in Anand has a daily frequency. GST returns filed every month have a monthly frequency. Many forecasting tools need to know the frequency, because "one step ahead" means one day for daily data and one month for monthly data.
pandas describes frequencies with short codes called offset aliases. You met "D" and "MS" already.
Step 1. Run the code to see five common codes side by side.
import pandas as pd
for code, meaning in [("D", "every day"), ("W-MON", "every Monday"), ("MS", "month start"),
("ME", "month end"), ("QS", "quarter start")]:
dates = pd.date_range("2025-01-01", periods=4, freq=code)
print(f"{code:<6} {meaning:<14}", [d.strftime("%d %b") for d in dates])D every day ['01 Jan', '02 Jan', '03 Jan', '04 Jan'] W-MON every Monday ['06 Jan', '13 Jan', '20 Jan', '27 Jan'] MS month start ['01 Jan', '01 Feb', '01 Mar', '01 Apr'] ME month end ['31 Jan', '28 Feb', '31 Mar', '30 Apr'] QS quarter start ['01 Jan', '01 Apr', '01 Jul', '01 Oct']
You should notice two things. W-MON skips ahead to the first Monday, 6 January, because 1 January 2025 was a Wednesday. And ME lands on the true last day of each month, including 28 February.
pd.date_range needs any two of start, end and periods, plus freq. Here we gave start and periods.
| Code | Meaning | Typical use |
|---|---|---|
h | hourly | electricity load, website visits |
D | calendar day | shop sales, rainfall |
B | business day (Monday to Friday) | stock prices, bank work |
W-SUN, W-MON | weekly, ending or anchored on that day | weekly reports |
MS, ME | month start, month end | monthly sales, bills |
QS, QE | quarter start, quarter end | quarterly results |
YS, YE | year start, year end | annual totals |
Checking the frequency of a series
A series built with date_range remembers its frequency in index.freq. A series read from a file does not, even when the dates are perfectly regular. pd.infer_freq looks at the dates and tells you the pattern, if there is one.
Step 2. Check four weekly readings of a Pune housing society's water meter.
import pandas as pd
idx = pd.DatetimeIndex(["2025-01-06", "2025-01-13", "2025-01-20", "2025-01-27"])
print("freq attribute:", idx.freq)
print("inferred:", pd.infer_freq(idx))
s = pd.Series([410, 395, 430, 445], index=idx)
s = s.asfreq("W-MON")
print(s.index.freq)
broken = pd.DatetimeIndex(["2025-01-06", "2025-01-13", "2025-01-27"])
print("inferred with a gap:", pd.infer_freq(broken))freq attribute: None inferred: W-MON <Week: weekday=0> inferred with a gap: None
The index first has no stored frequency (None), but infer_freq sees a Monday pattern. asfreq("W-MON") sets it. With one week missing, infer_freq returns None: the series is no longer regular. This is a quick health check. If you expect daily data and infer_freq says None, some days are missing or repeated. The next lesson fixes that.
Periods and the Indian financial year
A Timestamp is a moment, such as 15 June 2025 at midnight. A Period is a whole span, such as "June 2025" or "the first quarter". Monthly sales are really about periods: the total for June, not a value at one instant.
India's financial year runs from 1 April to 31 March. pandas handles this with a quarterly frequency that ends in March, Q-MAR.
Step 3. Find which month and which financial-year quarter a sale on 15 June 2025 belongs to.
import pandas as pd
sale_day = pd.Timestamp("2025-06-15")
month = sale_day.to_period("M")
fy_quarter = sale_day.to_period("Q-MAR") # Indian financial year: April to March
print("Month period:", month, " starts", month.start_time.date(), " ends", month.end_time.date())
print("FY quarter:", fy_quarter, " starts", fy_quarter.start_time.date())
print("FY quarters:", list(pd.period_range("2025-04", "2026-03", freq="Q-MAR").astype(str)))Month period: 2025-06 starts 2025-06-01 ends 2025-06-30 FY quarter: 2026Q1 starts 2025-04-01 FY quarters: ['2026Q1', '2026Q2', '2026Q3', '2026Q4']
2026Q1 can look odd at first. pandas names a fiscal year by the year in which it ends. FY 2025-26 ends in March 2026, so its first quarter (April to June 2025) is 2026Q1. That matches how Indian companies say "Q1 FY26". To group sales by financial quarter, use df.groupby(df.index.to_period("Q-MAR")).sum().
Working days and holidays
Offices, banks and schools do not work every day. A courier company in Kochi planning staff needs the number of working days in each month. CustomBusinessDay is a frequency for Monday to Friday that also skips the holidays you list.
Step 4. Count working days in January 2026 with two example holidays.
import pandas as pd
from pandas.tseries.offsets import CustomBusinessDay
holidays = ["2026-01-01", "2026-01-26"] # example list: confirm with your office calendar
working = CustomBusinessDay(holidays=holidays) # Monday to Friday, minus the holidays
days = pd.date_range("2026-01-01", "2026-01-31", freq=working)
print("Working days in January 2026:", len(days))
print("First three:", [d.strftime("%a %d") for d in days[:3]])
print("Plain weekdays:", len(pd.bdate_range("2026-01-01", "2026-01-31")))Working days in January 2026: 20 First three: ['Fri 02', 'Mon 05', 'Tue 06'] Plain weekdays: 22
January 2026 has 22 weekdays. New Year's Day (a Thursday) and Republic Day (a Monday) remove two, leaving 20. The number of working days changes every month, and it often explains a large part of monthly sales or output. You will use this idea again in the festivals lesson.
Common mistakes
- Using
"M"or"H". pandas 3.0 wants"ME"and"h". - Mixing month start and month end. Sales labelled
MSand targets labelledMEwill not line up when joined. - Assuming a file is regular. Run
pd.infer_freqon the index;Nonemeans gaps or repeats. - Using calendar quarters for Indian accounts. Use
Q-MARfor the April to March financial year.
Key points
- Frequency is the regular gap between rows; pandas names it with aliases such as
D,W-MON,MS,ME. pd.date_rangebuilds a regular calendar from two ofstart,endandperiods.pd.infer_freqchecks regularity;asfreqsets a frequency.- Periods describe spans;
Q-MARgives Indian financial-year quarters. CustomBusinessDay(holidays=...)counts real working days.
Exercise
Ananya's bakery in Kochi files a report every quarter of the financial year. Build a monthly date range (month start) from April 2025 to March 2026, convert each date to its Q-MAR period, and print how many months fall in each quarter.
Show solution
import pandas as pd
months = pd.date_range("2025-04-01", "2026-03-01", freq="MS")
quarters = months.to_period("Q-MAR")
print(pd.Series(1, index=quarters).groupby(level=0).sum())2026Q1 3 2026Q2 3 2026Q3 3 2026Q4 3 Freq: Q-MAR, dtype: int64
Every financial quarter holds three months, and all four belong to FY 2025-26, which pandas labels 2026.
Quick Quiz
1. Which alias gives the last day of each month in pandas 3.0?
ME is month end; the old M alias is no longer supported.
2. pd.infer_freq returns None for a daily sales index. What does this suggest?
A regular index has an inferable frequency; gaps or duplicates break it.
3. In pandas, which period is the quarter April to June 2025 with freq="Q-MAR"?
The fiscal year is named by its end year, March 2026, and April to June is its first quarter.
Learn Time Series with a mentor
Free lessons get you started. Our Data Science & Machine Learning training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
