Skip to content
wannacode

Dates and time series

In short: A time series is data ordered by date; in pandas text becomes a date with to_datetime, resample gathers it by day, week and month, and rolling gives a moving average.

Problem

“How is the year going?”

The shop has the orders for a whole year: from November 2025 to the end of October 2026. The manager asks which month is the strongest, which day of the week sells the most, and how the takings change from month to month.

Pythondata: the shop tables

There are 11,116 orders. But the ordered_at column is str: to pandas, “2025-11-01 07:20” is only text. You cannot take the month from it:

Pythondata: the shop tables

The idea

The text becomes a date

parse_dates tells read_csv which columns to read as dates. pd.to_datetime(orders['ordered_at']) does the same on an existing column, as you saw in the lesson on types:

Pythondata: the shop tables

The type is datetime64: a moment in time, with a date and a time. With it you can compare, subtract and take parts. The difference between the last and the first order is 364 days, 15 hours and 41 minutes. It is a time difference (Timedelta), not a number.

A table in which every row is a moment or a period, and the rows follow time, is called a time series.

The idea

Parts of the date: .dt

.dt gives the parts of every date in the column: year, month, day, hour, dayofweek (0 is Monday), day_name(). When during the day do people order the most?

Pythondata: the shop tables

There are two peaks: at lunchtime (between 12 and 14, about 800 orders an hour) and in the evening (between 19 and 21, about 1,200 an hour). In the morning and late in the evening there are fewer than half as many.

day_name() gives the names in English (“Saturday”). For names in another language you will add your own dictionary, a little further on.

The idea

Filtering by date

A date column can be compared with text like “2025-12-01”: pandas turns it into a date. The orders in December:

Pythondata: the shop tables

The right answer is 1285. With <= '2025-12-31' you get 1250, and the last order among them is from 30 December. The reason is the time: “2025-12-31” is 31 December at 00:00, and every order on the 31st comes after midnight.

So for a period you write “from the first day, included, to the first day of the next period, excluded”: >= '2025-12-01' and < '2026-01-01'.

Try it

The week before Christmas

How many orders are there from 22 to 28 December 2025 inclusive? Store those orders in week.

Pythondata: the shop tables

The idea

By month: resample

For the takings by month you first need the revenue of each order. It is the sum of its lines: groupby on order_id, then merge with the orders. Cancelled orders are not revenue, so sold leaves them out.

Then resample gathers the rows into periods of time. It is a groupby whose groups are periods: days ('D'), weeks ('W'), months ('MS'). It works on an index of dates, so first set_index('ordered_at'):

Pythondata: the shop tables

December is the strongest month: €15,915.90. January (€7,228.20) and February (€7,622.30) are weak, as often happens after the holidays. The weakest, though, is September: €7,128.80. Why, we will find out in the project at the end of the course.

More precisely, if you are curious

'MS' (month start) labels each month with its first day, and 'ME' (month end) with its last. The sums are the same.

'W' labels each week with the Sunday it ends on. The first week here is only 1 and 2 November (Saturday and Sunday), so it is much smaller than the others. The first and the last period are often incomplete: check them before you compare them.

If there are no orders on a day, resample('D').sum() still gives the row, with 0. In this data every day has orders.

Try it

The strongest week

Which week brought in the most revenue? Store the revenue by week (without the cancelled orders) in weekly, and the date of the strongest week in best_week.

Pythondata: the shop tables

The idea

Moving average: rolling

The daily takings jump up and down: high on Fridays, low on Sundays. Behind this noise it is hard to see whether things are going up. A moving average works out, for every day, the average of the last 7 days, including that day:

Pythondata: the shop tables

rolling(7) is a window of 7 rows that slides down one row at a time. The first 6 values are NaN: before 7 November there are not 7 days back. On 7 November the average of the first seven days is €374.23. After that, each day one new day comes in and the oldest goes out, so the line changes smoothly.

The idea

Day of the week

What are the average takings on a Monday, on a Tuesday and so on? You group the daily takings by the number of the day from the index, daily.index.dayofweek, and take the mean. You group by the number rather than by day_name(), because names sort alphabetically and Friday would come first. Then a dictionary (and map) gives the numbers their names:

Pythondata: the shop tables

Friday is the strongest: €350.79 on average. Sunday is the weakest: €250.85. This is the weekly rhythm that rolling(7) smooths out: a 7-day window holds every day of the week exactly once.

Here the index is already made of dates, so dayofweek is right on the index. For a column you write .dt.dayofweek.

1. Why does the moving average of the daily takings use a window of 7, not 5 or 10?

The idea

Month on month: pct_change

The manager does not ask “how much”, but “how much more or less than last month”. pct_change() divides each value by the one before and subtracts 1: it gives the change as a share. Times 100, it is a percentage:

Pythondata: the shop tables

November is NaN: there is no month before it. December is 71.2% above November, and January is 54.6% below December. September is 19.1% below August, and October comes back up by 39.2%.

Whether the drop in September is real or just noise, and what caused it, is the question of the project. For now you know that it is there and how big it is.

Exercise

When did coffee get dearer?

Coffee beans (product 17) got dearer at some point in the year. Store in price_by_month the mean price of coffee for every month (in the order of the months in time), and in first_new the date and time of the first sale at the new price of 14.9.

Pythondata: the shop tables

Test

Check yourself

1. The ordered_at column holds a date and a time. Why do you write < "2026-01-01" for the December orders, not <= "2025-12-31"?
2. What does the table need for resample("MS") to work?
3. Why are the first 6 values of daily.rolling(7).mean() NaN?
4. The takings are €8,812.70 in August and €7,128.80 in September. What does pct_change() give for September, times 100 and rounded?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·