6
Feature Engineering for Time Series Forecasting
In the previous chapter, we started looking at machine learning (ML) as a tool to solve the problem of time series forecasting. We also discussed a few techniques, such as time delay embedding and temporal embedding, which cast time series forecasting problems as classical regression problems from the ML paradigm. In this chapter, we’ll look at those techniques in detail and go through them in a practical sense, using the dataset we have worked with throughout this book.
In this chapter, we will cover the following topics:
- Understanding feature engineering
- Avoiding data leakage
- Setting a forecast horizon
- Time delay embedding
- Temporal embedding
Technical requirements
You will need to set up the Anaconda environment, following the instructions in the Preface of the book, to get a working environment with all the libraries and datasets required for the code in this book. Any additional libraries needed will be installed while running the notebooks.
You will need to run the following notebooks before using the code in this chapter:
02-Preprocessing_London_Smart_Meter_Dataset.ipynb from Chapter0201-Setting_up_Experiment_Harness.ipynb from Chapter04
The code for this chapter can be found at https://github.com/PacktPublishing/Modern-Time-Series-Forecasting-with-Python-2E/tree/main/notebooks/Chapter06.
Understanding feature engineering
Feature engineering, as the name suggests, is the process of engineering features from data, mostly using domain knowledge, to make the learning process smoother and more efficient. In a typical ML setting, engineering good features is essential to get good performance from any ML model. Feature engineering is a highly subjective part of ML, where each problem at hand has a different path of solution—one that is handcrafted for that problem. Suppose you have a dataset of house prices and you have a feature, Year Built, which tells you the year the house was built. Now, to make the information better, we can create another feature, House Age, from the Year Built feature. This may give the model better information, and this is called feature engineering.
When we are casting a time series problem as a regression problem, there are a few standard techniques that we can apply. This is a key step in the process, as how well an ML model acquires an understanding of time is dependent on how well we engineer features to capture time. The baseline methods we covered in Chapter 4, Setting a Strong Baseline Forecast, are the methods that are created for the specific use case of time series forecasting, and because of that, the temporal aspect of the problem is built into those models. For instance, ARIMA doesn’t need any feature engineering to understand time because it is built into the model. However, a standard regression model has no explicit understanding of time, so we need to create good features to embed the temporal aspect of the problem.
In the previous chapter (Chapter 5, Time Series Forecasting as Regression), we talked about two main ways to encode time in the regression framework: time delay embedding and temporal embedding. Although we touched on these concepts at a high level, it is time to dig deeper and see them in action.
Notebook alert:
To follow along with the complete code, use the 01-Feature_Engineering.ipynb notebook in the Chapter06 folder.
We have already split the dataset that we were working on into train, validation, and test datasets. However, since we are generating features that are based on previous observations, operationally, it is better when we have the train, validation, and test datasets combined. It will be clearer why shortly, but for now, let’s take it on faith and move ahead. Now, let’s combine the two datasets:
# Reading the missing value imputed and train test split data
train_df = pd.read_parquet(preprocessed / "selected_blocks_train_missing_imputed.parquet")
val_df = pd.read_parquet(preprocessed / "selected_blocks_val_missing_imputed.parquet")
test_df = pd.read_parquet(preprocessed / "selected_blocks_test_missing_imputed.parquet")
#Adding train, validation and test tags to distinguish them before combining
train_df['type'] = "train"
val_df['type'] = "val"
test_df['type'] = "test"
full_df = pd.concat([train_df, val_df, test_df]).sort_values(["LCLid", "timestamp"])
del train_df, test_df, val_df
Now, we have a full_df, which combines the train, validation, and test datasets. Some of you may already have alarm bells ringing in your head at combining the train and test sets. What about data leakage? Let’s check it out.
Avoiding data leakage
Data leakage occurs when a model is trained with some information that would not be available at the time of prediction. Typically, this leads to high performance in the training set but very poor performance in unseen data. There are two types of data leakage:
- Target leakage is when the information about the target (that we are trying to predict) leaks into some of the features in the model, leading to an overreliance of the model on those features, ultimately leading to poor generalization. This includes features that use the target in any way.
- Train-test contamination is when there is some information leakage between the train and test datasets. This can happen because of the careless handling and splitting of data. But it can also happen in more subtle ways, such as scaling a dataset before splitting the train and test sets.
When we work with time series forecasting problems, the biggest and most common mistake that we can make is target leakage. We will have to think hard about each of the features to ensure that we don’t use any data that will not be available during prediction. The following diagram will help us remember and internalize this concept:

Figure 6.1: Usable and unusable information to avoid data leakage
To make this concept clearer and more relevant to the time series forecasting context, let’s look at an example. Let’s say we are forecasting sales for shampoo, and we are using sales for conditioner as a feature. We developed the model, trained it on the training data, and tested it on the validation data. The model does very well. The moment we start predicting for the future, we can see a problem. We don’t know what the sales for conditioner are in the future either. While this example is pretty straightforward, there will be times when this becomes not so obvious. And that is why we need to exercise a fair amount of caution while creating features and always evaluate the features through the lens of will this feature be available at the time of prediction?
Best practices:
There are many ways of identifying target leakage, apart from thinking hard about the features:
- If the model you’ve built is too good to be true, you most likely have a leakage problem
- If any single feature has too much weightage in the feature importance of the model, that feature may have a problem with leakage
- Double-check the features that are highly correlated with the target
Although we generated forecasts earlier in this book, we never explicitly discussed forecast horizons. It is an important concept and essential for what we will discuss. Let’s take a bit of time to understand forecast horizons.
Setting a forecast horizon
A forecast horizon is the number of time steps into the future we want to forecast at any point in time. For instance, if we want to forecast the next 24 hours for the electricity consumption dataset that we have worked with, the forecast horizon becomes 48 (because the data is half-hourly). In Chapter 5, Time Series Forecasting as Regression, where we generated baselines, we just predicted the entire test data at once. In such cases, the forecast horizon becomes equal to the length of the test data.
We never had to worry about this until now because, in the classical statistical methods of forecasting, this decision is decoupled from modeling. If we train a model, we can use it to predict any future point without retraining. But with time series forecasting as regression, we have a constraint on the forecast horizon, and it has its roots in data leakage. This might be unclear to you now, so we’ll revisit this point after we have learned about the feature engineering techniques. For now, let’s only look at single-step-ahead forecasting. In the context of the dataset we are working with, this means that we will be answering the question, What is the energy consumption in the next half an hour? We will talk about multi-step forecasting and other mechanics of forecasting in Part 4, The Mechanics of Forecasting.
Now that we have set some ground rules, let’s start looking at the different feature engineering techniques. To follow along with the Jupyter notebook, head over to the Chapter06 folder and use the 01-Feature_Engineering.ipynb file.
Time delay embedding
The basic idea behind time delay embedding is to embed time in terms of recent observations. In Chapter 5, Time Series Forecasting as Regression, we discussed including previous observations of a time series as lags (Figure 5.6 under the subsection Time delay embedding).
However, there are a few more ways to capture recent and seasonal information using this concept.
- Lags
- Rolling window aggregations
- Seasonal rolling window aggregations
- Exponentially weighted moving averages
Let’s take a look.
Lags or backshift
Let’s assume we have a time series with time steps, YL. Consider that we are at time T and that we have a time series where the length of history is L. So our time series will have yT as the latest observation in the time series, and then yT-1, yT-2, and so on as we move back in time. So lags, as explained in Chapter 5, Time Series Forecasting as Regression, are features that include the previous observations in the time series, as shown in the following diagram:

Figure 6.2: Lag features
We can create multiple lags by including observations that are a timesteps before (yT-a); we will call this Lag a. In the preceding diagram, we have shown Lag 1, Lag 2, and Lag 3. However, we can add any number of lags we like. Let’s learn how to do that now in code:
df["lag_1"]=df["column"].shift(1)
Remember when we combined the train and test datasets and I asked you to take it in good faith? It’s time to repay that faith. If we consider the lag operation (or any autoregressive feature), it relies on a continuous representation along the time axis. If we consider the test dataset, for the first few rows (or earliest dates), the lags would be missing because they are part of the training dataset. So by combining the two, we create a continuous representation along the time axis where standard functions in pandas, such as shift, can be utilized to create these features easily and efficiently.
It is as simple as that, but we need to perform the lag operation for each LCLid separately. We have included a helpful method in src.feature_engineering.autoregressive_features called add_lags that adds all the lags you want for each LCLid quickly and efficiently. Let’s see how we can use that.
We are going to import the method and use a few of its parameters to configure the lag operation the way we want:
from src.feature_engineering.autoregressive_features import add_lags
# Creating first 5 lags and then same 5 lags but from previous day and previous week to capture seasonality
lags = (
(np.arange(5) + 1).tolist()
+ (np.arange(5) + 46).tolist()
+ (np.arange(5) + (48 * 7) - 2).tolist()
)
full_df, added_features = add_lags(
full_df, lags=lags, column="energy_consumption", ts_id="LCLid", use_32_bit=True
)
Now, let’s look at the parameters that we used in the previous code snippet:
lags: This parameter takes in a list of integers, denoting all the lags we need to create as features.column: The name of the column to be lagged. In our case, this isenergy_consumption.ts_id: The name of the column that contains the unique ID of a time series. IfNone, it assumes that the DataFrame only contains a single time series. In our case,LCLidis the name of that column.use_32_bit: This parameter doesn’t do anything functionally but makes the DataFrames much smaller in memory, sacrificing the precision of the floating-point numbers.
This method returns the DataFrame with the lags added, as well as a list with the column names of the newly added features.
Rolling window aggregations
With lags, we connect the present points to single points in the past, but with rolling window features, we connect the present with an aggregate statistic of a window from the past. Instead of looking at the observation from previous time steps, we would look at an average of the observations from the last three timesteps. Take a look at the following diagram to understand this better:

Figure 6.3: Rolling window aggregation features
We can calculate rolling statistics with different windows, and each of them will capture slightly different aspects of the history. In the preceding diagram, we can see an example of a window of three and a window of four. When we are at timestep T, a rolling window of three would have yT – 3, yT – 2, yT – 1 as the vector of past observations. Once we have these, we can apply any aggregation functions, such as the mean, standard deviation, min, max, and so on. Once we have a scalar value after the aggregation function, we can include that as a feature for timestep t.
We do not include yT in the vector of past observations because that leads to data leakage.
Let’s see how we can do this with pandas:
# We shift by one to make sure there is no data leakage
df["rolling_3_mean"] = df["column"].shift(1).rolling(3).mean()
Similar to the lags, we need to do this operation for each LCLid column separately. We have included a helpful method in src.feature_engineering.autoregressive_features called add_rolling_features that adds all the rolling features you want for each LCLid quickly and efficiently. Let’s see how we can use that.
We are going to import this method and use a few of its parameters to configure the rolling operation the way we want:
from src.feature_engineering.autoregressive_features import add_rolling_features
full_df, added_features = add_rolling_features(
full_df,
rolls=[3, 6, 12, 48],
column="energy_consumption",
agg_funcs=["mean", "std"],
ts_id="LCLid",
use_32_bit=True,
)
Now, let’s look at the parameters that we used in the previous code snippet:
rolls: This parameter takes in a list of integers denoting all the windows over which we need to calculate the aggregate statistics.column: The name of the column to be lagged. In our case, this isenergy_consumption.agg_funcs: This is a list of aggregations that we want to do for each window we declared inrolls. Allowable aggregation functions include{mean, std, max, min}.n_shift: This is the number of timesteps we need to shift before doing the rolling operation. This parameter avoids data leakage. Although we are shifting by one here, there are cases where we need to shift by more than one as well. This is typically used in multi-step forecasting, which we will cover in Part 4, The Mechanics of Forecasting.ts_id: The name of the column name that contains the unique ID of a time series. IfNone, it assumes that the DataFrame only has a single time series. In our case,LCLidis the name of that column.use_32_bit: This parameter doesn’t do anything functionally but makes the DataFrames much smaller in memory, sacrificing the precision of the floating-point numbers.
This method returns the DataFrame with the rolling features added, as well as a list with the column names of the newly added features.
Seasonal rolling window aggregations
Seasonal rolling window aggregations are very similar to rolling window aggregations, but instead of taking past n consecutive observations in the window, they take a seasonal window, skipping a constant number of timesteps between each item in a window. The following diagram will make this clearer:

Figure 6.4: Seasonal rolling window aggregations
The key parameter here is the seasonality period, which is commonly referred to as M. This is the number of timesteps after which we expect the seasonality pattern to repeat. When we are at timestep T, a rolling window of three would have yT – 3, yT – 2, yT – 1, as the vector of past observations. But the seasonal rolling window would skip m timesteps between each item in the window. This means that the observations that are there in the seasonal rolling window would be yT – M, yT – 2M, yT – 3M. Also, as usual, once we have the window vector, we just need to apply the aggregation function to get a scalar value and include that as a feature.
We do not include yT as an element in the seasonal rolling window vector to avoid data leakage.
This is not an operation that you can do easily and efficiently with pandas. Some fancy NumPy indexing and Python loops should do the trick. We will use an implementation from github.com/jmoralez/window_ops/ that uses NumPy and Numba to make the operation fast and efficient.
Just like the features we saw earlier, we need to do this operation for each LCLid separately. We have included a helpful method in src.feature_engineering.autoregressive_features called add_seasonal_rolling_features that adds all the seasonal rolling features you want for each LCLid quickly and efficiently. Let’s see how we can use that.
We are going to import the method and use a few parameters of the method to configure the seasonal rolling operation the way we want:
from src.feature_engineering.autoregressive_features import add_seasonal_rolling_features
full_df, added_features = add_seasonal_rolling_features(
full_df,
rolls=[3],
seasonal_periods=[48, 48 * 7],
column="energy_consumption",
agg_funcs=["mean", "std"],
ts_id="LCLid",
use_32_bit=True,
)
Now, let’s look at the parameters that we used in the previous code snippet:
seasonal_periods: This is a list of seasonal periods that should be used in the seasonal rolling windows. In the case of multiple seasonalities, we can include the seasonal rolling features of all the seasonalities.rolls: This parameter takes in a list of integers denoting all the windows over which we need to calculate aggregate statistics.column: The name of the column to be lagged. In our case, this isenergy_consumption.agg_funcs: This is a list of aggregations that we want to do for each window we declared inrolls. The allowable aggregation functions are{mean, std, max, min}.n_shift: This is the number of seasonal timesteps we need to shift before doing the rolling operation. This parameter prevents data leakage.ts_id: The name of the column name that contains the unique ID of a time series. IfNone, it assumes that the DataFrame only contains a single time series. In our case,LCLidis the name of that column.Use_32_bit: This parameter doesn’t do anything functionally but makes the DataFrames much smaller in memory, sacrificing the precision of the floating-point numbers.
As always, the method returns the DataFrame with seasonal rolling features and a list containing the column names of the newly added features.
Exponentially weighted moving average (EWMA)
With the rolling window mean operation, we calculated the average of the window, and it works synonymously with the moving average. EWMA is the slightly smarter cousin of the moving average. While the moving average considers a rolling window and considers each item in the window equally on the computed average, EWMA tries to do a weighted average on the window, and the weights decay at an exponential rate. There is a parameter, $\alpha$, that determines how fast the weights decay. Because of this, we can consider all the history available as a window and let the $\alpha$ parameter decide how much recency is included in EWMA. This can be written simply and recursively, as follows:
$$EWMA_T = \alpha \times y_T + (1 - \alpha) \times EWMA_{T-1}$$
Here, we can see that the larger the value of $\alpha$, the more the average is skewed toward recent values (see Figure 6.6 to get a visual impression of how the weights would be). If we expand the recursion, the weights of each term work out to be:
$$W_{T-k} = \alpha \times (1 - \alpha)^k$$
where k is the number of timesteps behind T. If we plot the weights, we can see them in an exponential decay; $\alpha$ determines how fast the decay happens. Another way to think about $\alpha$ is in terms of span. Span is the number of periods at which the decayed weights approach zero (not in a strictly mathematical way but intuitively). $\alpha$ and span are related through this equation:
$$\alpha = \frac{2}{1 + span}$$
This will become clearer in the following diagram, where we have plotted how the weights decay for different values of $\alpha$:

Figure 6.5: Exponential weight decay for different values of $\alpha$
Here, we can see that the weight becomes small by the time we reach the span.
Intuitively, we can think of EWMA as an average of the entire history of the time series, but with parameters such as $\alpha$ and span, we can make different periods of history more representative of the average. If we define a 60-period span, we can think that the last 60 time periods are what majorly drive the average. So making EWMAs with different spans or $\alpha$ s gives us representative features that capture different periods of history.
The overall process is depicted in the following diagram:

Figure 6.6: EWMA features
Now, let’s see how we can do this in pandas:
df["ewma"]=df['column'].shift(1).ewm(alpha=0.5).mean()
Like the other features we discussed earlier, EWMA also needs to be done for each LCLid separately. We have included a helpful method in src.feature_engineering.autoregressive_features called add_ewma that adds all the EWMA features you want for each LCLid quickly and efficiently. Let’s see how we can use that.
We are going to import the method and use a few parameters of the method to configure EWMA the way we want to:
from src.feature_engineering.autoregressive_features import add_ewma
full_df, added_features = add_ewma(
full_df,
spans=[48 * 60, 48 * 7, 48],
column="energy_consumption",
ts_id="LCLid",
use_32_bit=True,
)
Now, let’s look at the parameters that we used in the previous code snippet:
alphas: This is a list of all $\alpha$s we need to calculate the EWMA features for.spans: Alternatively, we can use this to list all the spans we need to calculate the EWMA features for. If you use this feature,alphaswill be ignored.column: The name of the column to be lagged. In our case, this isenergy_consumption.n_shift: This is the number of seasonal timesteps we need to shift before doing the rolling operation. This parameter avoids data leakage.ts_id: The name of the column name that has a unique ID for a time series. IfNone, it assumes the DataFrame only contains a single time series. In our case,LCLidis the name of that column.use_32_bit: This parameter doesn’t do anything functionally but makes the DataFrames much smaller in memory, sacrificing the precision of the floating-point numbers.
As always, the method returns the DataFrame containing the EWMA features, as well as a list with the column names of the newly added features.
These are a few standard ways of including time delay embedding in your ML model, but you are not restricted to just these. As always, feature engineering is a space that is not bound by rules, and we can get as creative as we want and inject domain knowledge into the model. Apart from the features we have seen, we can include the difference in lag as custom lags that inject domain knowledge, and so on. In most practical cases, we end up using more than one way of time delay embedding into our models. The lag feature is the most basic and essential in most cases, but we do end up encoding more information with seasonal lags, rolling features, and so on. As with everything in ML, there is no silver bullet. Each dataset has its own intricacies, which makes feature engineering very important and different for each case.
Now, let’s look at the other class of features we can add via temporal embedding.
Temporal embedding
In Chapter 5, Time Series Forecasting as Regression, we briefly discussed temporal embedding as a process where we try to embed time into features that an ML model can leverage. If we think about time for a second, we can see that two aspects of time are important to us in the context of time series forecasting—the passage of time and the periodicity of time.
There are a few features that we can add to help us capture these aspects in an ML model:
- Calendar features
- Time elapsed
- Fourier terms
Let’s look at each of them.
Calendar features
The first set of features that we can extract are features based on calendars. Although the strict definition of time series is a set of observations taken sequentially in time, more often than not, we will have the timestamps of these collected observations alongside the time series. We can utilize these timestamps and extract calendar features, such as the month, quarter, day of the year, hour, minutes, and so on. These features capture the periodicity of time and help an ML model capture seasonality well. Only the calendar features that are temporally higher than the frequency of the time series make sense. For instance, an hour feature in a time series with a weekly frequency doesn’t make sense, but a month feature and week feature make sense. We can utilize in-built datetime functionalities in pandas to create these features and treat them as categorical features in the model.
Time elapsed
This is another feature that captures the passage of time in an ML model. This feature increases monotonically as time increases, giving the ML model a sense of the passage of time. There are many ways to create this feature, but one of the easiest and most efficient ways is to use the integer representation of dates in NumPy:
df['time_elapsed'] = df['timestamp'].values.astype(np.int64)/(10**9)
We have included a helpful method in src.feature_engineering.temporal_features called add_temporal_features that adds all relevant temporal features automatically. Let’s see how we can use it.
We are going to import the method and use a few parameters of this method to configure and create the temporal features:
full_df, added_features = add_temporal_features(
full_df,
field_name="timestamp",
frequency="30min",
add_elapsed=True,
drop=False,
use_32_bit=True,
)
Now, let’s look at the parameters that we used in the previous code snippet:
field_name: This is the column name that contains the datetime that should be used to create features.frequency: We should provide the frequency of the time series as input so that the method automatically extracts the relevant features. These are standard pandas frequency strings.add_elapsed: This flag turns the creation of the time elapsed feature on or off.use_32_bit: This parameter doesn’t do anything functionally but makes the DataFrames much smaller in memory, sacrificing the precision of the floating-point numbers.
Just like the previous methods we discussed, this also returns the new DataFrame with the temporal features added and a list containing the column names of the newly added features.
Fourier terms
Previously, we extracted a few calendar features such as the month, year, and so on, and we discussed using them as categorical variables in the ML model. Another way we can represent the same information, but on a continuous scale, is by using Fourier terms. We discussed the Fourier series in Chapter 3, Analyzing and Visualizing Time Series Data. Just to reiterate, the sine-cosine form of the Fourier series is as follows:
$$S_{N(x)} = \frac{a_0}{2} + \sum_{n=1}^{N} \left( a_n \cdot \cos\left(\frac{2\pi \cdot n \cdot x}{P}\right) + b_n \cdot \sin\left(\frac{2\pi \cdot n \cdot x}{P}\right) \right)$$
Here, SN is the N-term approximation of the signal, S. Theoretically, when N is infinite, the resulting approximation is equal to the original signal. P is the maximum length of the cycle, an and bn are the coefficients of the cosine and sine term, respectively, of the nth term in the expansion, and a0 is the intercept.
We can create these cosine and sine functions as features to represent the seasonal cycle. If we encode the month, we know that it goes from 1 to 12 and then repeats itself. So P, in this case, will be 12, and x will be 1, 2, …12. Therefore, for each x, we can calculate the cosine and sine terms and add them as features to the ML model. Intuitively, we can think that the model will infer the coefficients based on the data and, thus, help the model predict the time series easier.
The following diagram shows the difference in representations between the month on an ordinal scale and as a Fourier series:

Figure 6.7: Month as an ordinal step function (top) versus Fourier terms (bottom)
The preceding diagram shows just a single Fourier term; we can add multiple Fourier terms to help capture complex seasonality.
We cannot say that continuous representation of seasonality is better than categorical because it depends on the type of model you use and the dataset. This is something we will have to find out empirically.
To make the process of adding Fourier features easy, we have made some easy-to-use methods available in src.feature_engineering.temporal_features, in a file called bulk_add_fourier_features that adds Fourier features for all the calendar features we want automatically. Let’s see how we can use that.
We are going to import the method and use a few of its parameters to configure and create the Fourier series-based features:
full_df, added_features = bulk_add_fourier_features(
full_df,
["timestamp_Month", "timestamp_Hour", "timestamp_Minute"],
max_values=[12, 24, 60],
n_fourier_terms=5,
use_32_bit=True,
)
Now, let’s look at the parameters that we used in the previous code snippet:
columns_to_encode: This is the list of calendar features we need to encode using Fourier terms.max_values: This is a list of max values for the seasonal cycle for the calendar features, in the same order as they are given incolumns_to_encode. For instance, formonthto encode as a column, we give12as the correspondingmax_value. If not given,max_valuewill be inferred. This is only recommended if the data you have contains at least a single complete seasonal cycle.n_fourier_terms: This is the number of Fourier terms to be added. This is synonymous with n in the equation for the Fourier series mentioned previously.use_32_bit: This parameter doesn’t do anything functionally but makes the DataFrames much smaller in memory, sacrificing the precision of the floating-point numbers.
Just like the previous methods we’ve discussed, this also returns a new DataFrame with the Fourier features added, as well as a list with column names of the newly added features.
After executing the 01-Feature_Engineering.ipynb notebook in Chapter06, we will have the following feature-engineered files written to disk:
selected_blocks_train_missing_imputed_feature_engg.parquetselected_blocks_val_missing_imputed_feature_engg.parquetselected_blocks_test_missing_imputed_feature_engg.parquet
In this section, we looked at a few popular and effective ways to generate features for time series. But there are many more, and depending on your problem and the domain, many of them will be relevant.
Additional information:
The world of feature engineering is vast, and there are a few open-source libraries that make exploring that space easier. A few of them are https://github.com/Nixtla/tsfeatures, https://tsfresh.readthedocs.io/en/latest/, and https://github.com/DynamicsAndNeuralSystems/catch22. A preprint by Ben D. Fulcher titled Feature-based time-series analysis at https://arxiv.org/abs/1709.08055 also gives a nice summary of the space.
A newer library called functime (https://github.com/functime-org/functime) also provides fast feature engineering routines, written in Polars, and is worth checking out. A lot of the feature engineering discussed in the book can be made even faster using functime and Polars.
Summary
After a brief overview of the ML for time series forecasting paradigm in the previous chapter, in this chapter, we looked at this practically and saw how we can prepare the dataset with the required features to start using these models. We reviewed a few time series-specific feature engineering techniques, such as lags, rolling, and seasonal features. All the techniques we learned in this chapter are tools with which we can quickly iterate through experiments to find out what works for our dataset. However, we only talked about feature engineering, which affects one side of the standard regression equation (y = mX + c). The other side, which is the target (y) we predict, is also equally important. In the next chapter, we’ll look at a few concepts such as stationarity and some transformations that affect the target.