Skip to content

How to resample a time series to daily intervals in Pandas (with Code)

The Short Answer (TL;DR)

Convert timestamps to the intended reporting timezone and use resample("D") with explicit left-closed, left-labelled bins. Choose an aggregation suited to the measurement and retain counts so an empty day is distinguishable from a measured zero.

import pandas as pd
s = pd.Series([1.,3.],index=pd.to_datetime(["2026-10-24T22:00Z","2026-10-25T01:30Z"],utc=True).tz_convert("Europe/Prague"))
print(s.resample("D",closed="left",label="left").sum(min_count=1))

Detailed Implementation

This example uses synthetic data and does not need a network connection. Install the recorded package versions in a separate Python environment before running it.

python -m pip install numpy==2.5.4 pandas==3.0.6
from pathlib import Path
import numpy as np
import pandas as pd

def daily_summary(series):
    if not isinstance(series.index,pd.DatetimeIndex) or series.index.tz is None:
        raise ValueError("Use a timezone-aware DatetimeIndex in the reporting timezone.")
    if series.empty or series.index.hasnans:
        raise ValueError("Provide at least one record and no missing timestamps.")
    if not pd.api.types.is_numeric_dtype(series) or pd.api.types.is_bool_dtype(series):
        raise ValueError("Use numeric measurements.")
    if not np.isfinite(series.dropna().to_numpy(dtype=float)).all():
        raise ValueError("Observed measurements must be finite.")
    daily = series.sort_index().resample("D",closed="left",label="left")
    return pd.DataFrame({"total":daily.sum(min_count=1),"mean":daily.mean(),
                         "rows":daily.size(),"observed":daily.count()})

# UTC instants avoid ambiguous local times at the autumn clock change.
instants = pd.to_datetime(["2026-10-23T22:00Z","2026-10-24T21:59Z",
    "2026-10-24T22:00Z","2026-10-25T00:30Z","2026-10-25T01:30Z",
    "2026-10-26T23:00Z"],utc=True)
series = pd.Series(pd.array([1,3,4,5,None,8],dtype="Float64"),
                   index=instants.tz_convert("Europe/Prague"),name="amount")
result = daily_summary(series)
report = result.to_string(na_rep="NA") + "\n"
Path("daily-local-intervals.txt").write_text(report,encoding="utf-8")
print(report)

The four local days have row counts 2, 3, 0 and 1 and observed counts 2, 2, 0 and 1. Their totals are 4, 9, NA and 8. The checked midnight intervals span 24, 25 and 24 elapsed hours.

Which timezone defines a day?

The synthetic series starts from unambiguous UTC instants and converts them to Europe/Prague before resampling. A reporting day then runs from local midnight to the next local midnight. On October 25, 2026, the checked interval spans 25 elapsed hours; the adjacent checked intervals span 24. Both local 02:30 readings are retained as different instants because their UTC offsets differ. The code does not guess how to interpret ambiguous timezone-naive local timestamps.

What happens at midnight?

The explicit left-closed bins include a reading at their starting midnight and exclude the next midnight. The labels identify the start of each bin. The October 24 22:00 UTC record is October 25 midnight in Prague, so its amount of four belongs to October 25. Changing a label alone does not change which observations enter a bin; the closure policy determines boundary membership.

Why preserve empty days and counts?

October 26 lies between observed days but has no records. Its row and observed counts are zero, while total and mean remain missing. October 25 has three records and two observed amounts, so its total is nine and mean is 4.5. The minimum observed count of one prevents an entirely unobserved total from appearing as zero. A genuinely measured zero is retained as zero with an observed count of one.

Which aggregation matches the measurement?

The example treats each value as an additive amount and also prints an equally weighted mean across observed records. Summing instantaneous temperatures or cumulative meter readings usually answers a different question and may be inappropriate. An observation mean is not automatically a time-weighted daily mean when readings are irregular. A rate integrated over time, a change in a cumulative counter and a total of event amounts need different calculations and explicit units.

What should be audited before reporting?

The four daily rows account for all six source records and five observed amounts, whose total is 21. Independent checks group the original readings by local calendar date and reconstruct each total and mean. Sorting does not change these summaries, and missing timestamps are rejected. The first and last day may represent incomplete observation periods; the helper does not certify day completeness, impute outages or establish a sampling model.

Mathematical Context

Let b_d and b_(d+1) be consecutive local midnight instants in the chosen timezone. Day d contains timestamps t satisfying b_d <= t < b_(d+1). The daily total is the sum of observed additive amounts in that set; it is left undefined when none are observed. The observed-record mean divides that sum by the number of observed amounts. The elapsed duration between local boundaries can differ from 24 hours at a clock change. Counts, totals and means therefore describe different aspects of coverage and must not be substituted for one another.

\[ S_d=\sum_{i:\,b_d\leq t_i<b_{d+1},\ x_i\ \mathrm{observed}}x_i \]

Reproducibility

Executed with Python 3.12.14; numpy 2.5.4, pandas 3.0.6. Automated checks confirm the stated output properties.

Primary documentation

Related guides

When the question concerns a trailing duration around each observation instead of separate daily bins, calculate a time-based rolling mean and state which endpoints and missing observations enter each window.