Data analysis skill 06 of 6
Time-series exploration
The Time-series Exploration skill makes Claude, Codex or another AI agent take a careful first look at data indexed by time before any forecast. It parses the timestamp, confirms the real frequency and finds the gaps, plots the raw series, resamples with the aggregate that fits a flow or a stock, reads the trend with rolling windows, separates trend from seasonality from noise, and watches timezone and daylight-saving traps.
time-series-exploration/SKILL.md, version 1.0.0, about 3,600 words.
By Afeez Adebayo, Marketing, SEO & Growth Expert with 10+ years of experience.
Created 28 October 2026. Updated 28 October 2026. Free to use under CC BY 4.0.
Time-indexed data breaks the usual assumptions. The rows are not independent, the order matters, and three different things, the level the series sits at, the direction it is trending, and the pattern that repeats every week or year, are tangled together in one line. The common mistakes all come from skipping the look: resampling a series that turns out to have month-long gaps, averaging a flow that should have been summed, reading a seasonal peak as a change in trend, fitting an additive model to seasonality that clearly grows with the level, and trusting local timestamps across a daylight-saving change that duplicated or dropped an hour. This skill does the first look properly, in a fixed order, so that whatever comes next, a forecast, a dashboard, an alert, is built on a series you have actually understood.
The discipline is to establish the real frequency before resampling, to pick the aggregate from what the measure is, and to separate trend, seasonality and noise before reading any one of them.
When to use this, and when not to
Use this skill when:
- Someone gives you a series indexed by time (a timestamp or date column plus a value) and wants to know what it looks like over time.
- A question involves trend, seasonality, a spike or dip, smoothing, or resampling to a coarser frequency ("make this weekly", "show me the monthly total").
- A forecast, alert or time-based dashboard is about to be built on a series nobody has looked at yet.
- A number "changed" and someone needs to see whether it is trend, season or a one-off before reacting.
Do not use this skill for:
- Building, tuning or judging a forecast model (ARIMA, exponential smoothing, Prophet, a machine-learning model). This skill ends where forecasting begins; it hands over a clean, understood series.
- Anomaly-detection modelling. It can point at a spike; it does not fit a detector.
- A first-pass profile of a dataset that is not primarily about time. Use
exploratory-data-analysis. - The shape of one variable ignoring time (
data-distributions) or a metric compared across non-time groups (grouping-aggregation). - Reading out an experiment. Use the experiment method for that.
Inputs you must have before starting
Ask for anything missing. Do not assume it.
- The timestamp column and what one row represents. One row per event, per transaction, or already one row per day, are three different starting points and change whether you aggregate first.
- The measure, and whether it is a flow or a stock. A flow accumulates over an interval (sales, clicks, signups) and is summed when you resample. A stock is a level at a point in time (headcount, inventory, price) and is taken as the last or the mean. Getting this wrong corrupts every resampled figure.
- The timezone of the timestamps, and whether they are local or UTC. Local timestamps across a daylight-saving boundary have a duplicated and a missing hour.
- The expected frequency, if any (hourly readings, daily totals), so you can tell a genuine gap from the normal spacing.
- Why you need the look: to spot a trend, confirm seasonality, explain a spike, choose a resample frequency, or prepare the series for forecasting. The reason decides where to spend effort.
- Sensitivity. If the series is personal, health, financial, legal or employment data, follow the PII rules below; a timestamped series tied to a person can be re-identifying.
If the person gives you a timestamp and a value and nothing else, confirm the flow-versus-stock question (input 2) and the timezone (input 3) before resampling, because both silently change the numbers.
Raw SKILL.md file
This is the exact file agents read, including the credit header and licence notice. Save it as time-series-exploration/SKILL.md.
---
name: time-series-exploration
description: >-
Runs a careful first look at data indexed by time before any forecast: it
parses the timestamp correctly, confirms the real frequency and finds gaps,
duplicates and irregular spacing, plots the raw series, resamples at an honest
frequency with the aggregate that fits a flow or a stock, uses rolling windows
to read the trend, separates trend from seasonality from noise with an additive
or multiplicative decomposition, and watches timezone and daylight-saving
traps. Use when someone hands you a dated series and asks to "look at this over
time", "plot the trend", "is there seasonality", "resample to weekly/monthly",
"why did this spike", "smooth this", "what's the pattern in these daily/hourly
numbers", or gives you a column of timestamps with a value. Do NOT use for:
building or evaluating a forecast model (ARIMA, Prophet, ML), anomaly-detection
models, a first-pass profile of a non-time dataset (use
exploratory-data-analysis), the shape of one variable (use data-distributions),
comparing a metric across non-time groups (use grouping-aggregation), or A/B
test read-outs.
---
# Time-series exploration
Time-indexed data breaks the usual assumptions. The rows are not independent, the order matters, and three different things, the level the series sits at, the direction it is trending, and the pattern that repeats every week or year, are tangled together in one line. The common mistakes all come from skipping the look: resampling a series that turns out to have month-long gaps, averaging a flow that should have been summed, reading a seasonal peak as a change in trend, fitting an additive model to seasonality that clearly grows with the level, and trusting local timestamps across a daylight-saving change that duplicated or dropped an hour. This skill does the first look properly, in a fixed order, so that whatever comes next, a forecast, a dashboard, an alert, is built on a series you have actually understood.
The discipline is to establish the real frequency before resampling, to pick the aggregate from what the measure is, and to separate trend, seasonality and noise before reading any one of them.
## When to use this, and when not to
Use this skill when:
- Someone gives you a series indexed by time (a timestamp or date column plus a value) and wants to know what it looks like over time.
- A question involves trend, seasonality, a spike or dip, smoothing, or resampling to a coarser frequency ("make this weekly", "show me the monthly total").
- A forecast, alert or time-based dashboard is about to be built on a series nobody has looked at yet.
- A number "changed" and someone needs to see whether it is trend, season or a one-off before reacting.
Do not use this skill for:
- Building, tuning or judging a forecast model (ARIMA, exponential smoothing, Prophet, a machine-learning model). This skill ends where forecasting begins; it hands over a clean, understood series.
- Anomaly-detection modelling. It can point at a spike; it does not fit a detector.
- A first-pass profile of a dataset that is not primarily about time. Use `exploratory-data-analysis`.
- The shape of one variable ignoring time (`data-distributions`) or a metric compared across non-time groups (`grouping-aggregation`).
- Reading out an experiment. Use the experiment method for that.
## Inputs you must have before starting
Ask for anything missing. Do not assume it.
1. **The timestamp column and what one row represents.** One row per event, per transaction, or already one row per day, are three different starting points and change whether you aggregate first.
2. **The measure, and whether it is a flow or a stock.** A flow accumulates over an interval (sales, clicks, signups) and is summed when you resample. A stock is a level at a point in time (headcount, inventory, price) and is taken as the last or the mean. Getting this wrong corrupts every resampled figure.
3. **The timezone of the timestamps,** and whether they are local or UTC. Local timestamps across a daylight-saving boundary have a duplicated and a missing hour.
4. **The expected frequency,** if any (hourly readings, daily totals), so you can tell a genuine gap from the normal spacing.
5. **Why you need the look:** to spot a trend, confirm seasonality, explain a spike, choose a resample frequency, or prepare the series for forecasting. The reason decides where to spend effort.
6. **Sensitivity.** If the series is personal, health, financial, legal or employment data, follow the PII rules below; a timestamped series tied to a person can be re-identifying.
If the person gives you a timestamp and a value and nothing else, confirm the flow-versus-stock question (input 2) and the timezone (input 3) before resampling, because both silently change the numbers.
## Workflow
Work on a copy of the data. This skill does not alter the source.
### Step 1: Parse the time correctly and sort
1. Parse the timestamp explicitly rather than letting the loader guess. Pass the format when you know it, and read as UTC when the data is UTC, so a mixed or ambiguous column does not parse half one way and half another.
2. Handle unparseable values on purpose: coerce them to "not a time" and count them, rather than letting one bad string stop the load or shift a column.
3. Set the timestamp as the index and sort by it. Many time-series operations give wrong or surprising results on an unsorted index.
4. Note the timezone. If the timestamps are naive local times and cross a daylight-saving change, flag it now; you will deal with the duplicated and missing hour in step 8.
### Step 2: Establish the real frequency, and find gaps, duplicates and irregular spacing
Never assume the series is evenly spaced. Measure it:
- Compute the gaps between consecutive timestamps. A single dominant gap (every row 1 hour apart) means regular spacing; a spread of gaps means irregular data that must be resampled before most operations make sense.
- Find missing periods: expected timestamps with no row. A week of zero sales and a week of missing data look identical in a total and mean opposite things; a gap is not a zero.
- Find duplicate timestamps: two rows for the same instant. Decide whether to sum them (two events) or treat one as an error, before aggregating.
Decision rule: if the spacing is irregular or has gaps, resample to a fixed frequency (step 4) before computing any rolling statistic or decomposition, and decide explicitly whether a missing period is a zero or a true gap. Do not fill gaps silently.
### Step 3: Plot the raw series first
Draw the line before computing anything. One plot shows the level, the overall direction, any repeating pattern, outliers, a sudden step (a structural break), and the gaps from step 2. For a long or noisy series, also plot a smoothed version (step 5) over the raw one. Read the plot before you trust a number; a decomposition of a series with an obvious break in it will smear the break across the trend and the residual.
### Step 4: Resample to an honest frequency with the aggregate that fits
Resampling to a coarser frequency (daily to weekly, hourly to daily) makes trend and seasonality legible. Pick the aggregate from input 2:
| The measure is | Resample with | Because |
|---|---|---|
| A flow (sales, clicks, signups) | sum | The weekly total is the sum of the daily totals |
| A stock (inventory, price, headcount) | last, or mean | A total of a level is meaningless; you want the level |
| A rate (conversion, defect rate) | sum(numerator) / sum(denominator) over the bin | Averaging the daily rates is the averaging-averages trap (see grouping-aggregation) |
Two cautions. First, know how the bins are labelled: by convention most fixed frequencies are left-closed and left-labelled, while some period-end frequencies (month-end, quarter-end, year-end, weekly) are right-labelled, so a bin can be stamped with its start or its end; confirm the `closed` and `label` behaviour in the resample reference before relying on it (verify before relying on this; pandas resample API). Second, do not upsample to a finer frequency and interpolate without a reason: inventing values between real observations creates data that was never measured.
### Step 5: Smooth with a rolling window to read the trend
A rolling mean or median over a sensible window reveals the trend under the noise. State the window (a 7-day window removes weekly wiggle; a 28-day window removes it more firmly) and whether it is centred or trailing: a centred window aligns the smoothed value with the middle of the window but needs future points, so it cannot reach the latest date; a trailing window reaches the end but lags. A rolling window also produces missing values at the edges where the window is not yet full; say so rather than letting the trend line start late without explanation.
### Step 6: Separate trend, seasonality and noise, and choose the model by eye
To read seasonality and trend as separate things, decompose the series into trend, a repeating seasonal component and a residual. Two decisions:
- **Additive or multiplicative.** If the size of the seasonal swing stays roughly constant as the level rises, use an additive model (series = trend + seasonal + residual). If the swing grows with the level (December is always 30% above the year, not a fixed amount above it), use a multiplicative model (series = trend x seasonal x residual), or take the logarithm and decompose additively (statsmodels seasonal_decompose, verified 2026-10-27).
- **The period.** You must tell the decomposition the length of the cycle (7 for weekly structure in daily data, 12 for monthly-in-yearly), and you need at least two full cycles of data for it to work at all (statsmodels seasonal_decompose, verified 2026-10-27).
Treat the classical moving-average decomposition as a first read: the documentation itself calls it naive and points to more robust methods such as STL (season-trend decomposition using LOESS) for anything load-bearing (statsmodels seasonal_decompose, verified 2026-10-27). Then look at the residual: if it still has a visible pattern, the model or the period is wrong, because a good decomposition leaves residual that looks like noise.
### Step 7: Read trend, seasonality and breaks as separate findings
Report them one at a time: the overall trend (rising, flat, falling, and roughly how fast), the seasonality (its period and rough size), any structural break or regime change (a step after a launch, an outage, a pricing change), and any outliers. A seasonal peak is not a trend change, and a one-off spike is not seasonality; keeping them separate is the whole point of the look. A rough stationarity read (does the rolling mean or variance drift over time?) is useful only to tell the next step what it is dealing with; this skill does not test for it formally or difference the series.
### Step 8: Handle timezone and calendar effects honestly
- If the timestamps are naive local times crossing a daylight-saving change, one local hour is duplicated and one is missing. Localise to the real timezone so the duplicated hour is disambiguated and the missing hour is handled, or work in UTC and convert only for display (pandas time series user guide, verified 2026-10-27).
- Remember that calendar arithmetic and elapsed-time arithmetic differ across a DST boundary: adding "one day" as a calendar offset keeps the wall-clock time, while adding 24 hours as elapsed time does not (pandas time series user guide, verified 2026-10-27).
- Watch month length (February totals look low per month but not per day), business days versus calendar days, and holidays, before reading a dip as a real change.
### Step 9: Write it up, then verify
Report the span and frequency, the gaps and duplicates found, the trend, the seasonality with its period, any breaks or outliers, and what it means for the next step. Then run the quality bar below. Recompute one resampled figure a second way from the raw rows (a weekly total by summing the raw daily rows in that week); if it does not match, the resampling or the aggregate is wrong.
## Worked example
**Input:** "Here are our daily signups for the last 18 months, one row per day, `date` and `signups`. What's going on over time?"
**Step 1 to 2.** Dates parse cleanly and sort. The spacing is one day, but 11 dates are missing in a single stretch in March: a known outage, a true gap, not zeros. No duplicate dates.
**Step 3, plot.** The raw line rises over the 18 months, wiggles within each week, has one sharp spike in July (a campaign), and the March gap.
**Step 4, resample.** Signups are a flow, so weekly figures are sums. The March gap is left as missing, not summed to zero, so the trend is not dragged down by a fake low week.
**Step 5, smooth.** A 28-day centred rolling mean on the daily series shows a steady upward trend with the July spike standing clearly above it.
**Step 6, decompose.** Weekly structure in daily data means period 7. The weekly swing is a roughly constant number of signups regardless of the level, so an additive model fits; the residual is flat apart from July, which confirms the spike is a one-off, not seasonality. Weekends are consistently low.
**Step 7 to 8.** Trend: rising, roughly steady. Seasonality: weekly (period 7), weekends down. Break/outlier: the July campaign spike, and the March outage gap. Timestamps are dates with no time, so no DST issue, but the weekend effect means a Monday public holiday will read low.
**Write-up (shortened):**
> **Daily signups, 18 months (one row per day), 2026-10-27.** Clear upward trend; weekly seasonality (period 7) with weekends low; one campaign spike in July; an 11-day data gap in March (outage), left as missing, not zero. Weekly resample uses sums (signups are a flow). An additive decomposition fits (constant weekly swing) and leaves clean residual apart from July. **For forecasting:** use weekly seasonality of 7, treat the March gap and July spike explicitly, and expect Monday-holiday weeks to read low. Hand the modelling to a forecasting method.
## Templates
### Time-series look write-up (use verbatim)
```
# <measure> over time, <grain>, <date>
Span: <start> to <end> Frequency: <real spacing> Measure type: <flow / stock / rate>
Gaps/duplicates: <missing periods (true gap vs zero), duplicate timestamps>
Resample: <target freq> with <sum / last / mean / ratio of sums>, bins <closed/label>
Trend: <rising / flat / falling, rough rate>
Seasonality: <period, rough size, additive or multiplicative>
Breaks / outliers: <steps, spikes, their dates and likely cause>
Timezone: <UTC / local; DST handled how>
What it means: <the honest read, and the next step; forecasting handed off>
```
### Opening questions when inputs are thin
```
Three things so the series reads honestly:
1. What does one row represent, and is the value a flow (sum it) or a stock (take the level)?
2. What timezone are the timestamps in, and are they local or UTC?
3. What's the expected spacing, so I can tell a real gap from normal spacing?
```
## Failure modes and anti-patterns
1. **Assuming even spacing.** Running a rolling window or a decomposition on a series with gaps, duplicates or irregular spacing, so the window covers the wrong span and the seasonality is misaligned. Spot it by measuring the gaps between timestamps before anything else. Fix it by resampling to a fixed frequency and deciding gap-versus-zero explicitly.
2. **The wrong resample aggregate.** Averaging a flow (so a quiet half of a month halves the monthly "total") or summing a stock (so inventory "accumulates"). Spot it by asking whether the measure is a flow or a stock. Fix it with sum for flows, last or mean for stocks, and a ratio of sums for rates.
3. **Confusing trend with seasonality.** Reading a seasonal peak as a change in direction, or a rising trend as a run of good months. Spot it when a claim about trend has not separated out the repeating pattern. Fix it by decomposing first and reading trend, seasonality and residual as separate components.
4. **The wrong decomposition model.** Fitting an additive model when the seasonal swing clearly grows with the level, so the residual keeps a seasonal shape. Spot it when the residual still has a pattern, or when the seasonal band fans out on the raw plot. Fix it with a multiplicative model or a log transform, and prefer STL over the naive method for anything that matters.
5. **Timezone and DST mishandling.** Treating naive local timestamps as if time were continuous across a daylight-saving change, so an hour is silently duplicated or missing, or comparing times from two zones as if they were the same. Spot it by checking the timezone and whether the span crosses a DST boundary. Fix it by localising to the real timezone or working in UTC and converting only for display.
## Quality bar
Run this against your own output. Every item must be verifiable.
- [ ] The timestamp was parsed explicitly, the index sorted, and unparseable values counted rather than dropped silently.
- [ ] The real frequency was measured, and gaps, duplicates and irregular spacing were found and reported.
- [ ] Missing periods were treated as gaps or as zeros on purpose, not filled silently.
- [ ] The raw series was plotted before any statistic was trusted.
- [ ] The resample aggregate matches the measure type (sum for flows, last or mean for stocks, ratio of sums for rates).
- [ ] The rolling window size and whether it is centred or trailing were stated, and edge gaps acknowledged.
- [ ] A decomposition separated trend, seasonality and residual, with the model (additive or multiplicative) and period justified, and the residual checked for leftover pattern.
- [ ] Trend, seasonality, breaks and outliers were reported as separate findings.
- [ ] Timezone was stated and any DST boundary handled.
- [ ] One resampled figure was recomputed from the raw rows and matches.
- [ ] If the series is sensitive, no re-identifying raw timeline was printed or sent anywhere.
## Guardrails
- **Do not fabricate.** Every figure comes from the data. The flow-versus-stock rule, the decomposition models, the two-cycle minimum and the DST cautions are method, labelled as such. Anything version-dependent (the default bin labelling of a resample frequency) carries a "verify before relying on this" marker, because it has changed between tool versions.
- **Work on a copy; confirm before changing data.** Filling a gap, interpolating, dropping an outlier or smoothing in place are changes the person approves first, and the reader is told the series was altered. Report the series as observed before reshaping it.
- **PII and credentials.** A timestamped series tied to a person (logins, locations, transactions) can re-identify them even when aggregated finely. Never print a raw personal timeline, aggregate to a coarse enough frequency that no individual's pattern is exposed, and do not paste personal time-series into a third-party service.
- **Not advice.** When the series concerns health, money, legal standing or employment, the output describes the series over time only. It is not medical, financial, legal or HR advice, and a trend is not a forecast; a decision needs a qualified human.
- **Cost.** Plotting, resampling and rolling windows are cheap and local. A decomposition or an STL fit on a long, high-frequency series can be slow and memory-hungry; resample to a sensible frequency first and say the analysis is on the resampled series.
- **Scope and no duplication.** This skill looks at a series over time and hands a clean, understood series to whatever comes next. It does not forecast, fit an anomaly detector or test for stationarity formally. For a non-time dataset profile use `exploratory-data-analysis`; for one variable's shape use `data-distributions`; for a metric across non-time groups use `grouping-aggregation`.
## Tool appendix (Python / pandas, statsmodels)
```
This section is tool-specific. The workflow above is tool-agnostic and works
the same in R (xts, forecast, stl) or a time-series database.
```
- Parse and index: `df["ts"] = pd.to_datetime(df["ts"], utc=True, format="...", errors="coerce")`; `df = df.set_index("ts").sort_index()`. `errors="coerce"` turns bad values into `NaT` so you can count them; the default `"raise"` stops the load (pandas time series user guide, verified 2026-10-27).
- Measure spacing and gaps: `df.index.to_series().diff().value_counts()` shows the real frequency; `pd.date_range(start, end, freq=...).difference(df.index)` lists missing periods; `df.index.duplicated().sum()` finds duplicate timestamps.
- Resample a flow: `df["signups"].resample("W").sum()`; a stock: `.resample("D").last()` or `.mean()`; a rate: resample numerator and denominator separately and divide. Confirm `closed`/`label` defaults in the `resample` API reference before relying on bin edges (verify before relying on this).
- `asfreq` versus `resample`: `asfreq` reindexes to a frequency and picks the value at each stamp without aggregating; `resample` aggregates within each bin. Use `resample` when changing granularity.
- Smooth: `df["signups"].rolling(window=28, center=True).mean()`; `center=True` aligns to the middle but cannot reach the latest dates; edges are `NaN` until the window fills.
- Decompose: `from statsmodels.tsa.seasonal import seasonal_decompose; seasonal_decompose(series, model="additive", period=7)`; use `model="multiplicative"` (or log then additive) when the seasonal swing grows with the level; needs at least two full cycles. Prefer `from statsmodels.tsa.seasonal import STL; STL(series, period=7).fit()` for a more robust decomposition (statsmodels, verified 2026-10-27).
- Timezone: `df.index.tz_localize("Europe/London")` attaches a zone and resolves DST; `df.index.tz_convert("UTC")` changes zone keeping the instant. Localise naive local times before comparing across a DST boundary (pandas time series user guide, verified 2026-10-27).
## Sources
- pandas, "Time series / date functionality" user guide (pandas 3.1.0; `to_datetime` parsing and `errors`, timezone localisation and conversion, DST and calendar-versus-elapsed arithmetic, resample example), pandas.pydata.org (last verified 2026-10-27).
- pandas, `DataFrame.resample` API reference (pandas 3.1.0; `closed`/`label` conventions by frequency), pandas.pydata.org (last verified 2026-10-27).
- statsmodels, `tsa.seasonal.seasonal_decompose` API reference (v0.14.4; additive and multiplicative models, period requirement, two-cycle minimum, naive method, STL pointer), statsmodels.org (last verified 2026-10-27).
- R. J. Hyndman and G. Athanasopoulos, "Forecasting: Principles and Practice" (3rd ed.), ch. on time-series decomposition (trend, seasonal and remainder; STL), otexts.com/fpp3 (last verified 2026-10-27).
- Anthropic, Agent Skills repository and skill-authoring guidance, github.com/anthropics/skills (reviewed as the reference for skill triggering and structure; no time-series skill exists there) (last verified 2026-10-27).
- Representative community "data-analysis" SKILL.md files (for example Mindrally/skills), reviewed as existing implementations: generic pandas best-practice lists with no frequency check, resample-aggregate rule or DST handling (last verified 2026-10-27).
Use, adapt and share this skill under CC BY 4.0: credit Afeez Adebayo, link to afeezadebayo.com and to this skill's page. LinkedIn: linkedin.com/in/afeez-adebayo.
Workflow
Work on a copy of the data. This skill does not alter the source.
Step 1: Parse the time correctly and sort
- Parse the timestamp explicitly rather than letting the loader guess. Pass the format when you know it, and read as UTC when the data is UTC, so a mixed or ambiguous column does not parse half one way and half another.
- Handle unparseable values on purpose: coerce them to "not a time" and count them, rather than letting one bad string stop the load or shift a column.
- Set the timestamp as the index and sort by it. Many time-series operations give wrong or surprising results on an unsorted index.
- Note the timezone. If the timestamps are naive local times and cross a daylight-saving change, flag it now; you will deal with the duplicated and missing hour in step 8.
Step 2: Establish the real frequency, and find gaps, duplicates and irregular spacing
Never assume the series is evenly spaced. Measure it:
- Compute the gaps between consecutive timestamps. A single dominant gap (every row 1 hour apart) means regular spacing; a spread of gaps means irregular data that must be resampled before most operations make sense.
- Find missing periods: expected timestamps with no row. A week of zero sales and a week of missing data look identical in a total and mean opposite things; a gap is not a zero.
- Find duplicate timestamps: two rows for the same instant. Decide whether to sum them (two events) or treat one as an error, before aggregating.
Decision rule: if the spacing is irregular or has gaps, resample to a fixed frequency (step 4) before computing any rolling statistic or decomposition, and decide explicitly whether a missing period is a zero or a true gap. Do not fill gaps silently.
Step 3: Plot the raw series first
Draw the line before computing anything. One plot shows the level, the overall direction, any repeating pattern, outliers, a sudden step (a structural break), and the gaps from step 2. For a long or noisy series, also plot a smoothed version (step 5) over the raw one. Read the plot before you trust a number; a decomposition of a series with an obvious break in it will smear the break across the trend and the residual.
Step 4: Resample to an honest frequency with the aggregate that fits
Resampling to a coarser frequency (daily to weekly, hourly to daily) makes trend and seasonality legible. Pick the aggregate from input 2:
| The measure is | Resample with | Because |
|---|---|---|
| A flow (sales, clicks, signups) | sum | The weekly total is the sum of the daily totals |
| A stock (inventory, price, headcount) | last, or mean | A total of a level is meaningless; you want the level |
| A rate (conversion, defect rate) | sum(numerator) / sum(denominator) over the bin | Averaging the daily rates is the averaging-averages trap (see grouping-aggregation) |
Two cautions. First, know how the bins are labelled: by convention most fixed frequencies are left-closed and left-labelled, while some period-end frequencies (month-end, quarter-end, year-end, weekly) are right-labelled, so a bin can be stamped with its start or its end; confirm the closed and label behaviour in the resample reference before relying on it (verify before relying on this; pandas resample API). Second, do not upsample to a finer frequency and interpolate without a reason: inventing values between real observations creates data that was never measured.
Step 5: Smooth with a rolling window to read the trend
A rolling mean or median over a sensible window reveals the trend under the noise. State the window (a 7-day window removes weekly wiggle; a 28-day window removes it more firmly) and whether it is centred or trailing: a centred window aligns the smoothed value with the middle of the window but needs future points, so it cannot reach the latest date; a trailing window reaches the end but lags. A rolling window also produces missing values at the edges where the window is not yet full; say so rather than letting the trend line start late without explanation.
Step 6: Separate trend, seasonality and noise, and choose the model by eye
To read seasonality and trend as separate things, decompose the series into trend, a repeating seasonal component and a residual. Two decisions:
- Additive or multiplicative. If the size of the seasonal swing stays roughly constant as the level rises, use an additive model (series = trend + seasonal + residual). If the swing grows with the level (December is always 30% above the year, not a fixed amount above it), use a multiplicative model (series = trend x seasonal x residual), or take the logarithm and decompose additively (statsmodels seasonal_decompose, verified 2026-10-27).
- The period. You must tell the decomposition the length of the cycle (7 for weekly structure in daily data, 12 for monthly-in-yearly), and you need at least two full cycles of data for it to work at all (statsmodels seasonal_decompose, verified 2026-10-27).
Treat the classical moving-average decomposition as a first read: the documentation itself calls it naive and points to more robust methods such as STL (season-trend decomposition using LOESS) for anything load-bearing (statsmodels seasonal_decompose, verified 2026-10-27). Then look at the residual: if it still has a visible pattern, the model or the period is wrong, because a good decomposition leaves residual that looks like noise.
Step 7: Read trend, seasonality and breaks as separate findings
Report them one at a time: the overall trend (rising, flat, falling, and roughly how fast), the seasonality (its period and rough size), any structural break or regime change (a step after a launch, an outage, a pricing change), and any outliers. A seasonal peak is not a trend change, and a one-off spike is not seasonality; keeping them separate is the whole point of the look. A rough stationarity read (does the rolling mean or variance drift over time?) is useful only to tell the next step what it is dealing with; this skill does not test for it formally or difference the series.
Step 8: Handle timezone and calendar effects honestly
- If the timestamps are naive local times crossing a daylight-saving change, one local hour is duplicated and one is missing. Localise to the real timezone so the duplicated hour is disambiguated and the missing hour is handled, or work in UTC and convert only for display (pandas time series user guide, verified 2026-10-27).
- Remember that calendar arithmetic and elapsed-time arithmetic differ across a DST boundary: adding "one day" as a calendar offset keeps the wall-clock time, while adding 24 hours as elapsed time does not (pandas time series user guide, verified 2026-10-27).
- Watch month length (February totals look low per month but not per day), business days versus calendar days, and holidays, before reading a dip as a real change.
Step 9: Write it up, then verify
Report the span and frequency, the gaps and duplicates found, the trend, the seasonality with its period, any breaks or outliers, and what it means for the next step. Then run the quality bar below. Recompute one resampled figure a second way from the raw rows (a weekly total by summing the raw daily rows in that week); if it does not match, the resampling or the aggregate is wrong.
Worked example
Input: "Here are our daily signups for the last 18 months, one row per day, date and signups. What's going on over time?"
Step 1 to 2. Dates parse cleanly and sort. The spacing is one day, but 11 dates are missing in a single stretch in March: a known outage, a true gap, not zeros. No duplicate dates.
Step 3, plot. The raw line rises over the 18 months, wiggles within each week, has one sharp spike in July (a campaign), and the March gap.
Step 4, resample. Signups are a flow, so weekly figures are sums. The March gap is left as missing, not summed to zero, so the trend is not dragged down by a fake low week.
Step 5, smooth. A 28-day centred rolling mean on the daily series shows a steady upward trend with the July spike standing clearly above it.
Step 6, decompose. Weekly structure in daily data means period 7. The weekly swing is a roughly constant number of signups regardless of the level, so an additive model fits; the residual is flat apart from July, which confirms the spike is a one-off, not seasonality. Weekends are consistently low.
Step 7 to 8. Trend: rising, roughly steady. Seasonality: weekly (period 7), weekends down. Break/outlier: the July campaign spike, and the March outage gap. Timestamps are dates with no time, so no DST issue, but the weekend effect means a Monday public holiday will read low.
Write-up (shortened):
> Daily signups, 18 months (one row per day), 2026-10-27. Clear upward trend; weekly seasonality (period 7) with weekends low; one campaign spike in July; an 11-day data gap in March (outage), left as missing, not zero. Weekly resample uses sums (signups are a flow). An additive decomposition fits (constant weekly swing) and leaves clean residual apart from July. For forecasting: use weekly seasonality of 7, treat the March gap and July spike explicitly, and expect Monday-holiday weeks to read low. Hand the modelling to a forecasting method.
Templates
Time-series look write-up (use verbatim)
# <measure> over time, <grain>, <date>
Span: <start> to <end> Frequency: <real spacing> Measure type: <flow / stock / rate>
Gaps/duplicates: <missing periods (true gap vs zero), duplicate timestamps>
Resample: <target freq> with <sum / last / mean / ratio of sums>, bins <closed/label>
Trend: <rising / flat / falling, rough rate>
Seasonality: <period, rough size, additive or multiplicative>
Breaks / outliers: <steps, spikes, their dates and likely cause>
Timezone: <UTC / local; DST handled how>
What it means: <the honest read, and the next step; forecasting handed off>Opening questions when inputs are thin
Three things so the series reads honestly:
1. What does one row represent, and is the value a flow (sum it) or a stock (take the level)?
2. What timezone are the timestamps in, and are they local or UTC?
3. What's the expected spacing, so I can tell a real gap from normal spacing?Failure modes and anti-patterns
- Assuming even spacing. Running a rolling window or a decomposition on a series with gaps, duplicates or irregular spacing, so the window covers the wrong span and the seasonality is misaligned. Spot it by measuring the gaps between timestamps before anything else. Fix it by resampling to a fixed frequency and deciding gap-versus-zero explicitly.
- The wrong resample aggregate. Averaging a flow (so a quiet half of a month halves the monthly "total") or summing a stock (so inventory "accumulates"). Spot it by asking whether the measure is a flow or a stock. Fix it with sum for flows, last or mean for stocks, and a ratio of sums for rates.
- Confusing trend with seasonality. Reading a seasonal peak as a change in direction, or a rising trend as a run of good months. Spot it when a claim about trend has not separated out the repeating pattern. Fix it by decomposing first and reading trend, seasonality and residual as separate components.
- The wrong decomposition model. Fitting an additive model when the seasonal swing clearly grows with the level, so the residual keeps a seasonal shape. Spot it when the residual still has a pattern, or when the seasonal band fans out on the raw plot. Fix it with a multiplicative model or a log transform, and prefer STL over the naive method for anything that matters.
- Timezone and DST mishandling. Treating naive local timestamps as if time were continuous across a daylight-saving change, so an hour is silently duplicated or missing, or comparing times from two zones as if they were the same. Spot it by checking the timezone and whether the span crosses a DST boundary. Fix it by localising to the real timezone or working in UTC and converting only for display.
Quality bar
Run this against your own output. Every item must be verifiable.
- [ ] The timestamp was parsed explicitly, the index sorted, and unparseable values counted rather than dropped silently.
- [ ] The real frequency was measured, and gaps, duplicates and irregular spacing were found and reported.
- [ ] Missing periods were treated as gaps or as zeros on purpose, not filled silently.
- [ ] The raw series was plotted before any statistic was trusted.
- [ ] The resample aggregate matches the measure type (sum for flows, last or mean for stocks, ratio of sums for rates).
- [ ] The rolling window size and whether it is centred or trailing were stated, and edge gaps acknowledged.
- [ ] A decomposition separated trend, seasonality and residual, with the model (additive or multiplicative) and period justified, and the residual checked for leftover pattern.
- [ ] Trend, seasonality, breaks and outliers were reported as separate findings.
- [ ] Timezone was stated and any DST boundary handled.
- [ ] One resampled figure was recomputed from the raw rows and matches.
- [ ] If the series is sensitive, no re-identifying raw timeline was printed or sent anywhere.
Guardrails
- Do not fabricate. Every figure comes from the data. The flow-versus-stock rule, the decomposition models, the two-cycle minimum and the DST cautions are method, labelled as such. Anything version-dependent (the default bin labelling of a resample frequency) carries a "verify before relying on this" marker, because it has changed between tool versions.
- Work on a copy; confirm before changing data. Filling a gap, interpolating, dropping an outlier or smoothing in place are changes the person approves first, and the reader is told the series was altered. Report the series as observed before reshaping it.
- PII and credentials. A timestamped series tied to a person (logins, locations, transactions) can re-identify them even when aggregated finely. Never print a raw personal timeline, aggregate to a coarse enough frequency that no individual's pattern is exposed, and do not paste personal time-series into a third-party service.
- Not advice. When the series concerns health, money, legal standing or employment, the output describes the series over time only. It is not medical, financial, legal or HR advice, and a trend is not a forecast; a decision needs a qualified human.
- Cost. Plotting, resampling and rolling windows are cheap and local. A decomposition or an STL fit on a long, high-frequency series can be slow and memory-hungry; resample to a sensible frequency first and say the analysis is on the resampled series.
- Scope and no duplication. This skill looks at a series over time and hands a clean, understood series to whatever comes next. It does not forecast, fit an anomaly detector or test for stationarity formally. For a non-time dataset profile use
exploratory-data-analysis; for one variable's shape usedata-distributions; for a metric across non-time groups usegrouping-aggregation.
Tool appendix (Python / pandas, statsmodels)
This section is tool-specific. The workflow above is tool-agnostic and works
the same in R (xts, forecast, stl) or a time-series database.- Parse and index:
df["ts"] = pd.to_datetime(df["ts"], utc=True, format="...", errors="coerce");df = df.set_index("ts").sort_index().errors="coerce"turns bad values intoNaTso you can count them; the default"raise"stops the load (pandas time series user guide, verified 2026-10-27). - Measure spacing and gaps:
df.index.to_series().diff().value_counts()shows the real frequency;pd.date_range(start, end, freq=...).difference(df.index)lists missing periods;df.index.duplicated().sum()finds duplicate timestamps. - Resample a flow:
df["signups"].resample("W").sum(); a stock:.resample("D").last()or.mean(); a rate: resample numerator and denominator separately and divide. Confirmclosed/labeldefaults in theresampleAPI reference before relying on bin edges (verify before relying on this). asfreqversusresample:asfreqreindexes to a frequency and picks the value at each stamp without aggregating;resampleaggregates within each bin. Useresamplewhen changing granularity.- Smooth:
df["signups"].rolling(window=28, center=True).mean();center=Truealigns to the middle but cannot reach the latest dates; edges areNaNuntil the window fills. - Decompose:
from statsmodels.tsa.seasonal import seasonal_decompose; seasonal_decompose(series, model="additive", period=7); usemodel="multiplicative"(or log then additive) when the seasonal swing grows with the level; needs at least two full cycles. Preferfrom statsmodels.tsa.seasonal import STL; STL(series, period=7).fit()for a more robust decomposition (statsmodels, verified 2026-10-27). - Timezone:
df.index.tz_localize("Europe/London")attaches a zone and resolves DST;df.index.tz_convert("UTC")changes zone keeping the instant. Localise naive local times before comparing across a DST boundary (pandas time series user guide, verified 2026-10-27).
Sources
- pandas, "Time series / date functionality" user guide (pandas 3.1.0;
to_datetimeparsing anderrors, timezone localisation and conversion, DST and calendar-versus-elapsed arithmetic, resample example), pandas.pydata.org (last verified 2026-10-27). - pandas,
DataFrame.resampleAPI reference (pandas 3.1.0;closed/labelconventions by frequency), pandas.pydata.org (last verified 2026-10-27). - statsmodels,
tsa.seasonal.seasonal_decomposeAPI reference (v0.14.4; additive and multiplicative models, period requirement, two-cycle minimum, naive method, STL pointer), statsmodels.org (last verified 2026-10-27). - R. J. Hyndman and G. Athanasopoulos, "Forecasting: Principles and Practice" (3rd ed.), ch. on time-series decomposition (trend, seasonal and remainder; STL), otexts.com/fpp3 (last verified 2026-10-27).
- Anthropic, Agent Skills repository and skill-authoring guidance, github.com/anthropics/skills (reviewed as the reference for skill triggering and structure; no time-series skill exists there) (last verified 2026-10-27).
- Representative community "data-analysis" SKILL.md files (for example Mindrally/skills), reviewed as existing implementations: generic pandas best-practice lists with no frequency check, resample-aggregate rule or DST handling (last verified 2026-10-27).
Use, adapt and share this skill under CC BY 4.0: credit Afeez Adebayo, link to afeezadebayo.com and to this skill's page. LinkedIn: linkedin.com/in/afeez-adebayo.
On this page
- When to use this skill, and when not to
- Who it is for
- What is inside
- Example prompts
- Example output
- What the agent needs from you
- How to install and use it
- How to run a time-series look step by step
- Worked example
- Situations this skill handles
- What makes this skill different
- Common mistakes
- Related skills
- Frequently asked questions
- Changelog
When to use this skill, and when not to
Use it when someone gives you a series indexed by time and wants to know what it looks like: a trend, seasonality, a spike or dip, smoothing, or resampling to a coarser frequency. It also fits preparing a series before a forecast, alert or dashboard, and checking whether a number that changed is trend, season or a one-off.
Do not use it for building or judging a forecast model (ARIMA, Prophet, a machine-learning model), anomaly-detection modelling, a first-pass profile of a non-time dataset (exploratory-data-analysis), the shape of one variable (data-distributions), a metric compared across non-time groups (grouping-aggregation), or experiment read-outs.
Who it is for
Analysts, data scientists, growth, finance and operations people, and founders who watch a metric over time and need to tell a real trend from a seasonal wiggle or a one-off. It suits anyone who has reacted to a dip that turned out to be the usual quiet week.
What is inside
The skill carries a nine-step workflow with decision rules: explicit timestamp parsing; a frequency and gap check so a gap is never read as a zero; a resample-aggregate table that sums a flow and takes the level of a stock; rolling windows for the trend with the window stated; an additive-or-multiplicative decomposition with the period set and at least two cycles required; and honest timezone and daylight-saving handling. It includes a write-up template, a worked example, five failure modes, a quality bar, and a Python appendix (pandas, statsmodels) kept separate from the tool-agnostic core.
Example prompts
- “Look at these daily signups over time.”
- “Is there weekly seasonality in this?”
- “Resample this to monthly totals.”
- “Why did this spike in July?”
Example output
The skill returns a read that keeps trend, season and one-offs apart:
Daily signups, 18 months. Clear upward trend; weekly seasonality (period 7) with weekends low; one campaign spike in July; an 11-day data gap in March, left as missing, not zero. Weekly resample uses sums. An additive decomposition fits and leaves clean residual apart from July. For forecasting, use weekly seasonality of 7 and treat the gap and spike explicitly.
What the agent needs from you
The timestamp column and what one row represents, whether the measure is a flow (summed when you resample) or a stock (taken as a level), the timezone and whether the times are local or UTC, the expected spacing so a real gap is clear, and why you need the look: a trend, seasonality, a spike, a resample frequency, or preparing the series for a forecast.
How to install and use it
In the Claude apps, download the SKILL.md from this page, put it in a folder named time-series-exploration, and upload the folder as a skill in settings. In Claude Code, save it as ~/.claude/skills/time-series-exploration/SKILL.md, or inside a project under .claude/skills/. In Codex and other Agent Skills tools, use the tool’s skills folder, often .agents/skills/. Then ask about a series over time in plain language and the description triggers it.
How to run a time-series look step by step
- Install the skill as
time-series-exploration/SKILL.md. - Give the agent the timestamp, whether the measure is a flow or a stock, and the timezone.
- It parses the time, sorts, and measures the real frequency, gaps and duplicates.
- It plots the raw series, then resamples with the aggregate that fits the measure.
- It reads the trend with a rolling window and decomposes trend, seasonality and noise.
- It reports trend, seasonality and one-offs separately, and hands a clean series to forecasting.
Worked example
Given 18 months of daily signups, the skill found 11 missing dates in March (an outage) and treated them as a gap rather than zeros. It resampled to weekly sums because signups are a flow, smoothed the daily series with a 28-day rolling mean to show a steady upward trend, and decomposed with period 7. The weekly swing was a constant size, so an additive model fitted and left clean residual apart from a July campaign spike. The write-up separated the trend, the weekly seasonality and the one-off spike, and handed the series to forecasting with the gap and spike flagged.
Situations this skill handles
| Situation | What the skill does |
|---|---|
| A series with gaps or irregular spacing | Measures the real frequency and treats a gap as a gap, not a zero |
| A flow resampled with a mean | Sums flows and takes the level of stocks when resampling |
| A seasonal peak read as a trend change | Decomposes first and reports trend and seasonality separately |
| Seasonality that grows with the level | Uses a multiplicative model or a log transform, not additive |
| Local timestamps across a DST change | Localises or works in UTC so no hour is duplicated or lost |
| A series about to be forecast | Hands over a clean, understood series with the period and gaps flagged |
What makes this skill different
Most tooling plots the line and fits a trend without checking the series underneath. The expensive mistakes follow: a rolling mean over month-long gaps, a flow averaged instead of summed, a seasonal peak reported as a trend change, an additive model on seasonality that clearly grows with the level, and local timestamps trusted across a daylight-saving change. This skill builds the right checks into the workflow, with a frequency and gap check first, the resample aggregate chosen from the measure, a decomposition that separates trend, seasonality and noise, and explicit timezone handling. It stays tool-agnostic, so the discipline carries to R or a time-series database.
Common mistakes
Assuming even spacing and running a window over gaps, resampling a flow with a mean or a stock with a sum, confusing a seasonal peak with a trend change, fitting an additive model to seasonality that grows with the level, and mishandling timezones across a daylight-saving change. The skill names each, explains how to spot it, and gives the fix.
Related skills
Run Exploratory data analysis first for a non-time profile of the dataset. For the shape of one variable see Data distributions and for its centre and spread Descriptive statistics. Use Correlation analysis for two variables and Grouping and aggregation for a metric across non-time groups. Browse all Data analysis skills in the Data category.
Frequently asked questions
Is my time series evenly spaced?
Do not assume so. Measure the gaps between consecutive timestamps: one dominant gap means regular spacing, a spread of gaps means irregular data that must be resampled before most operations make sense. Check for missing periods and duplicate timestamps too, and decide whether a missing period is a true gap or a genuine zero.
Should I sum or average when I resample?
It depends on the measure. A flow such as sales or signups accumulates over the interval, so you sum it. A stock such as inventory or a price is a level at a point in time, so you take the last value or the mean. A rate is resampled as a ratio of summed parts, not an average of the per-period rates.
How do I tell trend from seasonality?
Decompose the series into a trend, a repeating seasonal component and a residual, and read them separately. A seasonal peak is not a change in direction, and a rising trend is not a run of lucky months. Set the period of the cycle (7 for weekly structure in daily data) and give it at least two full cycles.
Additive or multiplicative decomposition?
Look at whether the seasonal swing stays a constant size as the level rises (additive) or grows with the level (multiplicative). If it grows, use a multiplicative model or take the logarithm and decompose additively. If the residual still shows a seasonal shape, the model or the period is wrong.
Why are my hourly numbers off by an hour twice a year?
Daylight-saving. Naive local timestamps duplicate one hour and skip another at the change, so a series read as continuous local time is wrong around those dates. Localise the index to the real timezone so the duplicated hour is disambiguated, or work in UTC and convert only for display.
Does this skill forecast?
No. It is the careful first look that comes before forecasting: it parses, checks the frequency, separates trend from seasonality, and hands a clean, understood series to a forecasting method with the period, gaps and one-offs flagged. The modelling itself is out of scope.
Changelog
- 1.0.0, 28 October 2026: first release.
Use, adapt and share this skill under CC BY 4.0: credit Afeez Adebayo and link to this page.
