Date Ranges, Frequencies and Shifting
Why fixed frequencies matter
A real timestamp column (like order_timeorder_time from the previous page) is often
irregular — orders don’t arrive exactly every hour. But for analysis you frequently
want a fixed-frequency index: every day, every business day, every 15 minutes.
Pandas gives you two tools for this:
pd.date_rangepd.date_range— generate a clean, evenly spacedDatetimeIndexDatetimeIndex..shift().shift()— move values (or timestamps) forward/backward along that index.
Think of date_rangedate_range as drawing the ruler, and shiftshift as sliding your data along it.
Generating date ranges with date_rangedate_range
import pandas as pd
# Start + end (daily by default)
idx = pd.date_range("2024-01-01", "2024-01-06")
print(idx)
# Start + number of periods
idx2 = pd.date_range(start="2024-01-01", periods=5)
print(idx2)
# Business days only (skips weekends)
idx3 = pd.date_range("2024-01-01", periods=5, freq="B")
print(idx3)import pandas as pd
# Start + end (daily by default)
idx = pd.date_range("2024-01-01", "2024-01-06")
print(idx)
# Start + number of periods
idx2 = pd.date_range(start="2024-01-01", periods=5)
print(idx2)
# Business days only (skips weekends)
idx3 = pd.date_range("2024-01-01", periods=5, freq="B")
print(idx3)date_rangedate_range always needs at least two of: startstart, endend, periodsperiods. Give it any two
and it works out the rest.
Frequency strings and multiples
Frequencies combine a base alias with an optional multiplier:
| Alias | Means | Alias | Means |
|---|---|---|---|
DD | Calendar day | MEME | Month end |
BB | Business day | MSMS | Month start |
hh | Hour | QEQE | Quarter end |
minmin | Minute | YEYE | Year end |
ss | Second | W-MONW-MON | Weekly on Monday |
import pandas as pd
print(pd.date_range("2024-01-01", periods=4, freq="4h")) # every 4 hours
print(pd.date_range("2024-01-01", periods=4, freq="5min")) # every 5 minutes
print(pd.date_range("2024-01-01", periods=4, freq="ME")) # last day of each monthimport pandas as pd
print(pd.date_range("2024-01-01", periods=4, freq="4h")) # every 4 hours
print(pd.date_range("2024-01-01", periods=4, freq="5min")) # every 5 minutes
print(pd.date_range("2024-01-01", periods=4, freq="ME")) # last day of each monthShifting data: leading and lagging
shiftshift moves the values, keeping the index fixed — this creates NaNNaN at the start
or end, which is exactly what you want for lag/lead features:
import pandas as pd
ts = pd.Series(
[100, 105, 102, 108],
index=pd.date_range("2024-01-31", periods=4, freq="ME"),
)
print(ts.shift(1)) # lag by 1 period — value moves down
print(ts.shift(-1)) # lead by 1 period — value moves upimport pandas as pd
ts = pd.Series(
[100, 105, 102, 108],
index=pd.date_range("2024-01-31", periods=4, freq="ME"),
)
print(ts.shift(1)) # lag by 1 period — value moves down
print(ts.shift(-1)) # lead by 1 period — value moves upA very common use of shiftshift is computing period-over-period percent change:
pct_change = ts / ts.shift(1) - 1
print(pct_change)pct_change = ts / ts.shift(1) - 1
print(pct_change)If you pass a freqfreq, shiftshift moves the timestamps instead of the values, so no
data is discarded:
print(ts.shift(2, freq="ME")) # every date pushed forward by 2 monthsprint(ts.shift(2, freq="ME")) # every date pushed forward by 2 monthsRolling dates forward with offsets
Pandas offset objects understand calendar rules. Adding MonthEnd()MonthEnd() to any date
“rolls forward” to the next month end — handy for snapping arbitrary dates onto a
frequency grid:
import pandas as pd
from pandas.tseries.offsets import MonthEnd, Day
now = pd.Timestamp("2024-01-17")
print(now + 3 * Day()) # simple: 3 calendar days later
print(now + MonthEnd()) # rolls forward to 2024-01-31
print(now + MonthEnd(2)) # rolls forward 2 month-ends: 2024-02-29
offset = MonthEnd()
print(offset.rollforward(now)) # 2024-01-31
print(offset.rollback(now)) # 2023-12-31 (previous month end)import pandas as pd
from pandas.tseries.offsets import MonthEnd, Day
now = pd.Timestamp("2024-01-17")
print(now + 3 * Day()) # simple: 3 calendar days later
print(now + MonthEnd()) # rolls forward to 2024-01-31
print(now + MonthEnd(2)) # rolls forward 2 month-ends: 2024-02-29
offset = MonthEnd()
print(offset.rollforward(now)) # 2024-01-31
print(offset.rollback(now)) # 2023-12-31 (previous month end)Common pitfalls
- Naive
shift()shift()(nofreqfreq) introducesNaNNaNat the start or end — that’s expected, not a bug. - Legacy aliases
"M""M","T""T","H""H","A""A"are no longer accepted — use"ME""ME","min""min","h""h","YE""YE". date_rangedate_rangeneeds exactly two ofstartstart/endend/periodsperiods— passing all three (when inconsistent) or only one raises an error.
Visualize it
flowchart LR A["pd.date_range(start, periods, freq)"] --> B["Fixed-frequency DatetimeIndex"] B --> C["Series/DataFrame on that index"] C --> D["ts.shift(1)
values slide down, NaN at top"] C --> E["ts.shift(1, freq='ME')
timestamps slide, no NaN"]
🧪 Try It Yourself
Exercise 1 – Build a Fixed-Frequency Range
Exercise 2 – Lag a Series With shift
Exercise 3 – Roll a Date Forward to Month End
Next
Fixed-frequency ranges become tricky once you cross regions — see Time Zone Handling for how to localize and convert timestamps safely.
If this helped you, consider buying me a coffee ☕
Buy me a coffeeWas this page helpful?
Let us know how we did
