Pandas resample(): Time-Series Reports with Practical Examples

Use resample() to convert dated observations into reports at another frequency: minute readings into two-minute summaries, or individual sales into weekly and monthly totals. Choose the date column, bucket boundaries and aggregation to match the question you want to answer.

Examples use synthetic data and were executed locally with Pandas 3.0.1. Output formatting may vary by version. Start with the small dataset to understand boundaries, then build the sales reports.

Create a DatetimeIndex

Resampling groups observations into time buckets. Convert the dates first, sort the index, then choose an aggregation. This preserves the original six-observation example.

import pandas as pd

def minute_data():
    df = pd.DataFrame({
        "my_date": pd.date_range("2022-06-01", periods=6, freq="min"),
        "value": [2, 2, 3, 1, 3, 2]
    })
    return df.set_index("my_date").sort_index()

df = minute_data()
print(df)

Expected output

                     value
my_date                   
2022-06-01 00:00:00      2
2022-06-01 00:01:00      2
2022-06-01 00:02:00      3
2022-06-01 00:03:00      1
2022-06-01 00:04:00      3
2022-06-01 00:05:00      2

Sum Values Every Two Minutes

The buckets starting at 00:00, 00:02 and 00:04 contain two observations each. Sum is appropriate for quantities accumulated during each period; mean is appropriate for an average measurement.

df = minute_data()
result = df.resample("2min")["value"].sum()
print(result.to_frame())
assert result.tolist() == [4, 4, 5]

Expected output

                     value
my_date                   
2022-06-01 00:00:00      4
2022-06-01 00:02:00      4
2022-06-01 00:04:00      5

Choose a Frequency Rule

Common rules are D (day), W-SUN (week ending Sunday), MS (month start) and ME (month end). Use min, h and s for minutes, hours and seconds. Older tutorials may use M, H or S; use the current aliases below. A compound duration is useful for fixed elapsed-time buckets, rather than calendar months.

df = minute_data()
print(df.resample("2D5h2min10s")["value"].mean().to_frame())

Expected output

               value
my_date             
2022-06-01  2.166667

closed: Choose Which Boundary Is Included

With left-closed two-minute buckets, 00:02 belongs to [00:02, 00:04). With right-closed buckets, it belongs to (00:00, 00:02]. Set label explicitly so the displayed timestamps identify the intended edges. The first right-closed bucket contains the observation at midnight alone.

df = minute_data()
left = df.resample("2min", closed="left", label="left")["value"].mean()
right = df.resample("2min", closed="right", label="right")["value"].mean()
print("Left closed / left label:")
print(left.to_frame())
print("Right closed / right label:")
print(right.to_frame())

Expected output

Left closed / left label:
                     value
my_date                   
2022-06-01 00:00:00    2.0
2022-06-01 00:02:00    2.0
2022-06-01 00:04:00    2.5
Right closed / right label:
                     value
my_date                   
2022-06-01 00:00:00    2.0
2022-06-01 00:02:00    2.5
2022-06-01 00:04:00    2.0
2022-06-01 00:06:00    2.0

label: Name the Bucket Without Changing Its Contents

Changing label alone does not change which observations are included. Keep closed fixed and compare the two outputs: the sums remain 4, 4 and 5, while the labels move two minutes.

df = minute_data()
print("Left labels:")
print(df.resample("2min", closed="left", label="left")["value"].sum().to_frame())
print("Right labels:")
print(df.resample("2min", closed="left", label="right")["value"].sum().to_frame())

Expected output

Left labels:
                     value
my_date                   
2022-06-01 00:00:00      4
2022-06-01 00:02:00      4
2022-06-01 00:04:00      5
Right labels:
                     value
my_date                   
2022-06-01 00:02:00      4
2022-06-01 00:04:00      4
2022-06-01 00:06:00      5

Why Assigning Results Back Can Produce NaN

Pandas aligns a Series by its index when assigning it to a column. The resampled result has fewer timestamps than the source, so unmatched original rows receive NaN. Keep the report as a separate result unless this alignment is intentional.

df = minute_data()
df["two_minute_total"] = df.resample("2min")["value"].sum()
print(df)

Expected output

                     value  two_minute_total
my_date                                     
2022-06-01 00:00:00      2               4.0
2022-06-01 00:01:00      2               NaN
2022-06-01 00:02:00      3               4.0
2022-06-01 00:03:00      1               NaN
2022-06-01 00:04:00      3               5.0
2022-06-01 00:05:00      2               NaN

Use on Without Changing the Index

The on argument selects a datetime column. Convert string dates with to_datetime before resampling. Select the numeric column so text columns do not enter the aggregation.

df = minute_data().reset_index()
df["my_date"] = pd.to_datetime(df["my_date"], errors="raise")
print(df.resample("2min", on="my_date")["value"].sum().to_frame())

Expected output

                     value
my_date                   
2022-06-01 00:00:00      4
2022-06-01 00:02:00      4
2022-06-01 00:04:00      5

Shift Buckets with origin and offset

For fixed frequencies such as minutes and hours, origin anchors the buckets and offset shifts that anchor. A three-minute shift of a two-minute grid is equivalent to a one-minute shift. Neither argument moves calendar month boundaries.

df = minute_data()
print(df.resample("2min", origin="start_day", offset="3min")["value"].sum().to_frame())

Expected output

                     value
my_date                   
2022-05-31 23:59:00      2
2022-06-01 00:01:00      5
2022-06-01 00:03:00      4
2022-06-01 00:05:00      2

Practical Dataset: Dated Sales Records

This synthetic dataset crosses a month boundary and deliberately omits dates. Each row is one recorded sale; missing dates indicate no recorded observations, not proven zero sales. Revenue is already calculated for this exercise.

def sales_data():
    df = pd.DataFrame({
        "date": ["2026-01-29", "2026-01-30", "2026-02-01", "2026-02-02", "2026-02-05"],
        "revenue": [100, 150, 200, 80, 120],
        "quantity": [2, 3, 4, 1, 2]
    })
    df["date"] = pd.to_datetime(df["date"], errors="raise")
    return df.set_index("date").sort_index()

sales = sales_data()
print(sales)

Expected output

            revenue  quantity
date                         
2026-01-29      100         2
2026-01-30      150         3
2026-02-01      200         4
2026-02-02       80         1
2026-02-05      120         2

Build a Weekly Sales Report

W-SUN produces weeks ending Sunday. Explicit right boundaries include Sunday in that week. Use different aggregations for different questions: revenue sum, units sum and recorded-sale count.

sales = sales_data()
weekly = sales.resample("W-SUN", closed="right", label="right").agg(
    revenue_total=("revenue", "sum"),
    units_total=("quantity", "sum"),
    recorded_sales=("revenue", "count")
)
print(weekly)
assert weekly["revenue_total"].tolist() == [450, 200]
assert weekly["revenue_total"].sum() == sales["revenue"].sum()

Expected output

            revenue_total  units_total  recorded_sales
date                                                  
2026-02-01            450            9               3
2026-02-08            200            3               2

Build Monthly Reports with MS and ME

MS labels January by January 1; ME labels it by January 31. Both reports total January revenue as 250 and February as 400. The February total covers only the dates in this sample, not a complete month.

sales = sales_data()
print("Month-start labels:")
print(sales.resample("MS")["revenue"].sum(min_count=1).to_frame())
print("Month-end labels:")
print(sales.resample("ME")["revenue"].sum(min_count=1).to_frame())
assert sales.resample("MS")["revenue"].sum().tolist() == [250, 400]

Expected output

Month-start labels:
            revenue
date               
2026-01-01      250
2026-02-01      400
Month-end labels:
            revenue
date               
2026-01-31      250
2026-02-28      400

Distinguish Missing Periods from Zero Sales

A plain sum can report zero for an empty bucket. sum(min_count=1) preserves NaN when there are no valid values. Add a count to identify these periods; only fill zero when the collection process confirms that no recorded sale means no sale.

sales = sales_data()
daily = sales.resample("D")["revenue"].sum(min_count=1).to_frame("revenue_total")
daily["valid_records"] = sales.resample("D")["revenue"].count()
print(daily)
assert daily["revenue_total"].isna().sum() == 3

Expected output

            revenue_total  valid_records
date                                    
2026-01-29          100.0              1
2026-01-30          150.0              1
2026-01-31            NaN              0
2026-02-01          200.0              1
2026-02-02           80.0              1
2026-02-03            NaN              0
2026-02-04            NaN              0
2026-02-05          120.0              1

Upsample Measurements with Care

Upsampling introduces more timestamps. asfreq leaves new values missing; ffill carries the last known observation forward. Carrying a temperature reading forward can be useful under a stated assumption. Do not forward-fill transaction revenue: that would invent sales.

temperature = pd.Series([20.0, 22.0, 21.0],
    index=pd.date_range("2026-02-01 09:00", periods=3, freq="h"), name="temperature")
comparison = pd.concat([
    temperature.resample("30min").asfreq().rename("unfilled"),
    temperature.resample("30min").ffill().rename("last_known")
], axis=1)
print(comparison)

Expected output

                     unfilled  last_known
2026-02-01 09:00:00      20.0        20.0
2026-02-01 09:30:00       NaN        20.0
2026-02-01 10:00:00      22.0        22.0
2026-02-01 10:30:00       NaN        22.0
2026-02-01 11:00:00      21.0        21.0

Exercise: Check Three-Minute Totals

Before running the solution, resample minute_data() into left-closed three-minute buckets. Predict both totals, then verify that resampling preserves the original sum. Expected totals: 7 and 6.

Exercise Solution

Check both the bucket values and the grand total. These checks catch accidentally excluded records or an inappropriate aggregation.

df = minute_data()
answer = df.resample("3min", closed="left", label="left")["value"].sum()
print(answer.to_frame())
assert answer.tolist() == [7, 6]
assert answer.sum() == df["value"].sum()

Expected output

                     value
my_date                   
2022-06-01 00:00:00      7
2022-06-01 00:03:00      6

Common Questions and Mistakes

Only valid with DatetimeIndex? Convert dates and set a datetime index, or use on with a datetime column. Why different totals? Check closed, your selected numeric columns, timezone and missing values. Why different timestamps? Check label and the rule. Does resample aggregate automatically? Choose sum, mean, count or agg after resample. Which old arguments should I remove? axis, kind, loffset and base are absent from the current DataFrame.resample API. Use origin/offset for fixed-frequency alignment, and change result labels explicitly when needed. Local-time days around daylight-saving changes may contain 23 or 25 hours; choose your reporting timezone before grouping.

Practice in Google Colab

Open in Google Colab View on GitHub
Run the examples in order, change the frequency rules and try the exercise. All sample data is created in the notebook; no download or database is required.

Related tutorials: Pandas Date & Time, to_datetime(), date_range(), period_range(), strftime(), sum(), mean(), max(), min() and std().

API reference: Pandas DataFrame.resample documentation.




Subscribe to our YouTube Channel here



plus2net.com







Python Video Tutorials
Python SQLite Video Tutorials
Python MySQL Video Tutorials
Python Tkinter Video Tutorials
✖
We use cookies to improve your browsing experience. . Learn more
HTML MySQL PHP JavaScript ASP Photoshop Articles Contact us
© 2000-2026 plus2net.com All rights reserved worldwide Privacy Policy Disclaimer