Scalable Forecasting: modeling thousand time series with a single global model

If you like  Skforecast ,  help us giving a star on   GitHub! ⭐️

Scalable Forecasting: modeling thousand time series with a single global model

Joaquín Amat Rodrigo, Javier Escobar Ortiz
November, 2024

Introduction

When faced with scenarios involving the prediction of hundreds or thousands of time series, a crucial decision arises: should one develop individual models for each series, or should one use a unified model to handle them all at once?

In Single-Series Modeling (Local Forecasting Model), a separate predictive model is created for each time series. While this method provides a comprehensive understanding of each series, its scalability can be challenged by the need to create and maintain hundreds or thousands of models.

Multi-Series Modeling (Global Forecasting Model) involves building a single predictive model that considers all time series simultaneously. It attempts to capture the core patterns that govern the series, thereby mitigating the potential noise that each series might introduce. This approach is computationally efficient, easy to maintain, and can yield more robust generalizations across time series, albeit potentially at the cost of sacrificing some individual insights.

This document shows how to forecast more than 1000 time series with a single model including exogenous features with some of them having different values per series.

Libraries

Libraries used in this document.

In [1]:
# Data management
# ==============================================================================
import numpy as np
import pandas as pd

# Plots
# ==============================================================================
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
plt.style.use('seaborn-v0_8-darkgrid')

# Forecasting
# ==============================================================================
import skforecast
import lightgbm
from lightgbm import LGBMRegressor
from sklearn.preprocessing import OrdinalEncoder
from sklearn.compose import make_column_transformer
from sklearn.feature_selection import RFECV
from skforecast.recursive import ForecasterRecursiveMultiSeries
from skforecast.model_selection import TimeSeriesFold, OneStepAheadFold
from skforecast.model_selection import backtesting_forecaster_multiseries
from skforecast.model_selection import bayesian_search_forecaster_multiseries
from skforecast.feature_selection import select_features_multiseries
from skforecast.preprocessing import RollingFeatures
from skforecast.preprocessing import series_long_to_dict
from skforecast.preprocessing import exog_long_to_dict
from feature_engine.datetime import DatetimeFeatures
from feature_engine.creation import CyclicalFeatures
from skforecast.datasets import fetch_dataset

# Configuration
# ==============================================================================
import warnings
warnings.filterwarnings('once')

print('Versión skforecast:', skforecast.__version__)
print('Versión lightgbm:', lightgbm.__version__)
Versión skforecast: 0.14.0
Versión lightgbm: 4.5.0

Data

Data used in this document has been obtained from the The Building Data Genome Project 2 https://github.com/buds-lab/building-data-genome-project-2. The dataset contains information on the energy consumption of more than 1500 buildings. The time range of the times-series data is the two full years (2016 and 2017) and the frequency is hourly measurements of electricity, heating and cooling water, steam, and irrigation meters. Additionally, the dataset includes information of the characteristics of the buildings and the weather conditions. Data has been aggregated to a daily resolution and only the electricity among the different energy sources has been considered.

In [2]:
# Load data
# ==============================================================================
data = fetch_dataset(name='bdg2_daily')
print("Data shape:", data.shape)
data.head(3)
bdg2_daily
----------
Daily energy consumption data from the The Building Data Genome Project 2 with
building metadata and weather data. https://github.com/buds-lab/building-data-
genome-project-2
Miller, C., Kathirgamanathan, A., Picchetti, B. et al. The Building Data Genome
Project 2, energy meter data from the ASHRAE Great Energy Predictor III
competition. Sci Data 7, 368 (2020). https://doi.org/10.1038/s41597-020-00712-x
Shape of the dataset: (1153518, 17)
Data shape: (1153518, 17)
Out[2]:
building_id meter_reading site_id primaryspaceusage sub_primaryspaceusage sqm lat lng timezone airTemperature cloudCoverage dewTemperature precipDepth1HR precipDepth6HR seaLvlPressure windDirection windSpeed
timestamp
2016-01-01 Bear_assembly_Angel 12808.1620 Bear Entertainment/public assembly Entertainment/public assembly 22117.0 37.871903 -122.260729 US/Pacific 6.1750 1.666667 -5.229167 0.0 0.0 1020.891667 68.750000 3.070833
2016-01-02 Bear_assembly_Angel 9251.0003 Bear Entertainment/public assembly Entertainment/public assembly 22117.0 37.871903 -122.260729 US/Pacific 8.0875 NaN -1.404167 0.0 0.0 1017.687500 76.666667 3.300000
2016-01-03 Bear_assembly_Angel 14071.6500 Bear Entertainment/public assembly Entertainment/public assembly 22117.0 37.871903 -122.260729 US/Pacific 10.1125 NaN 1.708333 -6.0 -2.0 1011.491667 91.666667 3.120833
In [3]:
# Overall range of available dates
# ==============================================================================
print(
    f"Rage of dates available : {data.index.min()} --- {data.index.max()}  "
    f"(n_days={(data.index.max() - data.index.min()).days})"
)
Rage of dates available : 2016-01-01 00:00:00 --- 2017-12-31 00:00:00  (n_days=730)
In [4]:
# Range of available dates per building
# ==============================================================================
available_dates_per_series = (
    data
    .dropna(subset="meter_reading")
    .reset_index()
    .groupby("building_id")
    .agg(
        min_index=("timestamp", "min"),
        max_index=("timestamp", "max"),
        n_values=("timestamp", "nunique")
    )
)
display(available_dates_per_series)
print(f"Unique lenght of series: {available_dates_per_series.n_values.unique()}")
min_index max_index n_values
building_id
Bear_assembly_Angel 2016-01-01 2017-12-31 731
Bear_assembly_Beatrice 2016-01-01 2017-12-31 731
Bear_assembly_Danial 2016-01-01 2017-12-31 731
Bear_assembly_Diana 2016-01-01 2017-12-31 731
Bear_assembly_Genia 2016-01-01 2017-12-31 731
... ... ... ...
Wolf_public_Norma 2016-01-01 2017-12-31 731
Wolf_retail_Harriett 2016-01-01 2017-12-31 731
Wolf_retail_Marcella 2016-01-01 2017-12-31 731
Wolf_retail_Toshia 2016-01-01 2017-12-31 731
Wolf_science_Alfreda 2016-01-01 2017-12-31 731

1578 rows × 3 columns

Unique lenght of series: [731]

All time series have the same length, starting from January 1, 2016 and ending on December 31, 2017. Exogenous variables have few missing values. Skforecast does not require that the time series have the same length, and missing values are allowed as long as the underlying regressor can handle them, which is the case for LightGBM, XGBoost, and HistGradientBoostingRegressor.

In [5]:
# Missing values per feature
# ==============================================================================
data.isna().mean().mul(100).round(2)
Out[5]:
building_id               0.00
meter_reading             0.00
site_id                   0.00
primaryspaceusage         1.20
sub_primaryspaceusage     1.20
sqm                       0.00
lat                      14.83
lng                      14.83
timezone                  0.00
airTemperature            0.02
cloudCoverage             7.02
dewTemperature            0.03
precipDepth1HR            0.02
precipDepth6HR            0.02
seaLvlPressure            9.56
windDirection             0.02
windSpeed                 0.02
dtype: float64

Exogenous variables

Exogenous variables are variables that are external to the time series and can be used as features to improve the forecast. In this case, the exogenous variables used are: the characteristics of the buildings, calendar variables and weather conditions.

Warning

Exogenous variables must be known at the time of the forecast. For example, if temperature is used as an exogenous variable, the temperature value for the next day must be known at the time of the forecast. If the temperature value is not known, the forecast will not be possible.

Building characteristics

One of the key attributes associated with each building is its designated use. This feature may play a crucial role in influencing the energy consumption pattern, as distinct uses can significantly impact both the quantity and timing of energy consumption.

In [6]:
# Number of buildings
# ==============================================================================
print(f"Number of buildings: {data['building_id'].nunique()}")
print(f"Number of building types: {data['primaryspaceusage'].nunique()}")
print(f"Number of building subtypes: {data['sub_primaryspaceusage'].nunique()}")
Number of buildings: 1578
Number of building types: 16
Number of building subtypes: 104

For certain type and subtype categories, there are a limited number of buildings in the dataset. Types with fewer than 100 buildings and subtypes with fewer than 50 buildings are grouped into the "Other" category.

In [7]:
# Group unfrequent categories
# ==============================================================================
infrequent_types = (
    data
    .drop_duplicates(subset=['building_id'])['primaryspaceusage']
    .value_counts()
    .loc[lambda x: x < 100]
    .index
    .tolist()
)
infrequent_subtypes = (
    data
    .drop_duplicates(subset=['building_id'])['sub_primaryspaceusage']
    .value_counts()
    .loc[lambda x: x < 50]
    .index
    .tolist()
)

data['primaryspaceusage'] = np.where(
    data['primaryspaceusage'].isin(infrequent_types),
    'Other',
    data['primaryspaceusage']
)
data['sub_primaryspaceusage'] = np.where(
    data['sub_primaryspaceusage'].isin(infrequent_subtypes),
    'Other',
    data['sub_primaryspaceusage']
)

display(data.drop_duplicates(subset=['building_id'])['primaryspaceusage'].value_counts())
display(data.drop_duplicates(subset=['building_id', 'sub_primaryspaceusage'])['sub_primaryspaceusage'].value_counts())
primaryspaceusage
Education                        604
Office                           296
Entertainment/public assembly    203
Public services                  166
Lodging/residential              149
Other                            141
Name: count, dtype: int64
sub_primaryspaceusage
Other                          612
Office                         295
College Classroom              131
College Laboratory             116
K-12 School                    109
Dormitory                       91
Primary/Secondary Classroom     84
Education                       67
Library                         54
Name: count, dtype: int64

Calendar features

In [8]:
# Calendar features
# ==============================================================================
features_to_extract = [
    'month',
    'week',
    'day_of_week',
]
calendar_transformer = DatetimeFeatures(
                            variables           = 'index',
                            features_to_extract = features_to_extract,
                            drop_original       = False,
                       )
data = calendar_transformer.fit_transform(data)
In [9]:
# Cyclical encoding of calendar features
# ==============================================================================
features_to_encode = [
    "month",
    "week",
    "day_of_week",
]
max_values = {
    "month": 12,
    "week": 52,
    "day_of_week": 6,
}
cyclical_encoder = CyclicalFeatures(
                        variables     = features_to_encode,
                        max_values    = max_values,
                        drop_original = False
                   )

data = cyclical_encoder.fit_transform(data)
data.head(3)
Out[9]:
building_id meter_reading site_id primaryspaceusage sub_primaryspaceusage sqm lat lng timezone airTemperature ... windSpeed month week day_of_week month_sin month_cos week_sin week_cos day_of_week_sin day_of_week_cos
timestamp
2016-01-01 Bear_assembly_Angel 12808.1620 Bear Entertainment/public assembly Other 22117.0 37.871903 -122.260729 US/Pacific 6.1750 ... 3.070833 1 53 4 0.5 0.866025 0.120537 0.992709 -8.660254e-01 -0.5
2016-01-02 Bear_assembly_Angel 9251.0003 Bear Entertainment/public assembly Other 22117.0 37.871903 -122.260729 US/Pacific 8.0875 ... 3.300000 1 53 5 0.5 0.866025 0.120537 0.992709 -8.660254e-01 0.5
2016-01-03 Bear_assembly_Angel 14071.6500 Bear Entertainment/public assembly Other 22117.0 37.871903 -122.260729 US/Pacific 10.1125 ... 3.120833 1 53 6 0.5 0.866025 0.120537 0.992709 -2.449294e-16 1.0

3 rows × 26 columns

✎ Note

For more information about calendar features and cyclical encoding visit Calendar features and Cyclical features in time series forecasting.

Meteorological features

Meteorological variables are recorded at the site level, meaning that weather data varies by building location, even at the same timestamp. In other words, while the exogenous variables are consistent across all series, their values differ by location.

In [10]:
# Values of meteorological features for a guiven date in each site
# ==============================================================================
data.loc["2016-01-01"].groupby("site_id", observed=True).agg(
    {
        "airTemperature": "first",
        "cloudCoverage": "first",
        "dewTemperature": "first",
        "precipDepth1HR": "first",
        "precipDepth6HR": "first",
        "seaLvlPressure": "first",
        "windDirection": "first",
        "windSpeed": "first",
    }
)
Out[10]:
airTemperature cloudCoverage dewTemperature precipDepth1HR precipDepth6HR seaLvlPressure windDirection windSpeed
site_id
Bear 6.175000 1.666667 -5.229167 0.0 0.0 1020.891667 68.750000 3.070833
Bobcat -11.595833 0.000000 -17.041667 0.0 0.0 1034.466667 260.869565 3.033333
Bull 7.612500 4.000000 0.708333 -2.0 -1.0 1031.762500 130.416667 3.712500
Cockatoo -2.000000 4.000000 -4.066667 0.0 0.0 NaN 281.333333 4.826667
Crow -1.787500 NaN -3.595833 15.0 13.0 1011.670833 236.250000 3.666667
Eagle 3.187500 1.000000 -4.487500 0.0 0.0 1017.445833 287.500000 3.470833
Fox 10.200000 1.000000 -2.804167 0.0 0.0 1018.512500 47.083333 0.470833
Gator 23.291667 5.600000 19.625000 -3.0 0.0 1018.663636 150.416667 2.520833
Hog -5.583333 1.142857 -9.741667 -2.0 -1.0 1019.545833 260.416667 4.758333
Lamb 6.913043 0.000000 5.434783 0.0 0.0 NaN 123.181818 8.017391
Moose -1.787500 NaN -3.595833 15.0 13.0 1011.670833 236.250000 3.666667
Mouse 5.387500 0.000000 3.879167 0.0 0.0 1016.941667 116.666667 4.470833
Panther 23.291667 5.600000 19.625000 -3.0 0.0 1018.663636 150.416667 2.520833
Peacock 5.558333 0.000000 -1.541667 0.0 0.0 1019.783333 120.000000 1.275000
Rat 5.633333 4.727273 -2.112500 0.0 0.0 1020.450000 314.782609 3.816667
Robin 5.387500 0.000000 3.879167 0.0 0.0 1016.941667 116.666667 4.470833
Shrew 5.387500 0.000000 3.879167 0.0 0.0 1016.941667 116.666667 4.470833
Swan 6.054167 0.400000 -2.591667 0.0 0.0 1021.216667 154.000000 1.783333
Wolf 5.716667 6.625000 3.208333 0.0 1.0 1007.625000 140.000000 8.875000

Skforecast allows you to include different exogenous variables and/or different values for each series in the dataset (more details in the next section).

Categorical features

LightGBM can handle categorical variables internally without any preprocessing. To allow automatic detection of categorical features based on pandas data types in a Forecaster, categorical variables must first be encoded as integers (ordinal encoding) and then stored as a category type. This is necessary because skforecast uses a numeric array internally to speed up the calculation, and LightGBM requires the categorical features to be encoded as category to be detected automatically. It is also necessary to set the categorical_features parameter to 'auto' during the initialization of the forecaster using fit_kwargs = {'categorical_feature': 'auto'}.

Warning

The four main gradient boosting frameworks – LightGBM, scikit-learn's HistogramGradientBoosting, XGBoost, and CatBoost – are capable of directly handling categorical features within the model. However, it is important to note that each framework has its own configurations, benefits and potential problems. To fully comprehend how to use these frameworks, it is highly recommended to refer to the skforecast user guide for a detailed understanding.
In [11]:
# Transformer: ordinal encoding
# ==============================================================================
# A ColumnTransformer is used to transform categorical (not numerical) features
# using ordinal encoding. Numeric features are left untouched. Missing values
# are coded as -1. If a new category is found in the test set, it is encoded
# as -1.
categorical_features = ['primaryspaceusage', 'sub_primaryspaceusage', 'timezone']
transformer_exog = make_column_transformer(
                       (
                           OrdinalEncoder(
                               dtype=float,
                               handle_unknown="use_encoded_value",
                               unknown_value=np.nan,
                               encoded_missing_value=np.nan
                           ),
                           categorical_features
                       ),
                       remainder="passthrough",
                       verbose_feature_names_out=False,
                   ).set_output(transform="pandas")
transformer_exog
Out[11]:
ColumnTransformer(remainder='passthrough',
                  transformers=[('ordinalencoder',
                                 OrdinalEncoder(dtype=<class 'float'>,
                                                handle_unknown='use_encoded_value',
                                                unknown_value=nan),
                                 ['primaryspaceusage', 'sub_primaryspaceusage',
                                  'timezone'])],
                  verbose_feature_names_out=False)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.

When creating a Forecaster with LGBMRegressor, it is necessary to specify how to handle the categorical columns using the fit_kwargs argument. This is because the categorical_feature argument is only specified in the fit method of LGBMRegressor, and not during its initialization.

Modelling and forecasting

ForecasterRecursiveMultiSeries allows modeling time series of different lengths and using different exogenous variables. When series have different lengths, the data must be transformed into a dictionary. The keys of the dictionary are the names of the series and the values are the series themselves. To do this, the series_long_to_dict function is used, which takes the DataFrame in "long format" and returns a dict of pandas Series. Similarly, when the exogenous variables are different (values or variables) for each series, the data must be transformed into a dictionary. The keys of the dictionary are the names of the series and the values are the exogenous variables themselves. The exog_long_to_dict function is used, which takes the DataFrame in "long format" and returns a dict of exogenous variables (pandas Series or pandas DataFrame).

When all series have the same length and the same exogenous variables, it is not necessary to use dictionaries. Series can be passed as unique DataFrame with each series in a column, and exogenous variables can be passed as a DataFrame with the same length as the series.

✎ Note

For more information about modelling series of different lengths and using different exogenous variables, visit Global Forecasting Models: Time series with different lengths and different exogenous variables.
In [12]:
# Exogenous features selected for modeling
# ==============================================================================
exog_features = [
    "primaryspaceusage",
    "sub_primaryspaceusage",
    "timezone",
    "sqm",
    "airTemperature",
    "cloudCoverage",
    "dewTemperature",
    "precipDepth1HR",
    "precipDepth6HR",
    "seaLvlPressure",
    "windDirection",
    "windSpeed",
    "day_of_week_sin",
    "day_of_week_cos",
    "week_sin",
    "week_cos",
    "month_sin",
    "month_cos",
]
In [13]:
# Transform series and exog to dictionaries
# ==============================================================================
series_dict = series_long_to_dict(
    data      = data.reset_index(),
    series_id = 'building_id',
    index     = 'timestamp',
    values    = 'meter_reading',
    freq      = 'D'
)

exog_dict = exog_long_to_dict(
    data      = data[exog_features + ['building_id']].reset_index(),
    series_id = 'building_id',
    index     = 'timestamp',
    freq      = 'D'
)

To train the models, search for optimal hyperparameters, and evaluate their predictive performance, the data is divided into three separate sets: training, validation, and test.

In [14]:
# Partition data in train and test
# ==============================================================================
data = data.sort_index()
end_train = '2017-08-31 23:59:00'
end_validation = '2017-10-31 23:59:00'
series_dict_train = {k: v.loc[: end_train,] for k, v in series_dict.items()}
series_dict_valid = {k: v.loc[end_train: end_validation,] for k, v in series_dict.items()}
series_dict_test = {k: v.loc[end_validation:,] for k, v in series_dict.items()}
exog_dict_train = {k: v.loc[: end_train,] for k, v in exog_dict.items()}
exog_dict_valid = {k: v.loc[end_train: end_validation,] for k, v in exog_dict.items()}
exog_dict_test = {k: v.loc[end_validation:,] for k, v in exog_dict.items()}

print(
    f"Rage of dates available : {data.index.min()} --- {data.index.max()} "
    f"(n_days={(data.index.max() - data.index.min()).days})"
)
print(
    f"  Dates for training    : {data.loc[: end_train, :].index.min()} --- {data.loc[: end_train, :].index.max()} "
    f"(n_days={(data.loc[: end_train, :].index.max() - data.loc[: end_train, :].index.min()).days})"
)
print(
    f"  Dates for validation  : {data.loc[end_train:end_validation, :].index.min()} --- {data.loc[end_train:end_validation, :].index.max()} "
    f"(n_days={(data.loc[end_train:end_validation, :].index.max() - data.loc[end_train:end_validation, :].index.min()).days})"
)
print(
    f"  Dates for test        : {data.loc[end_validation:, :].index.min()} --- {data.loc[end_validation:, :].index.max()} "
    f"(n_days={(data.loc[end_validation:, :].index.max() - data.loc[end_validation:, :].index.min()).days})"
)
Rage of dates available : 2016-01-01 00:00:00 --- 2017-12-31 00:00:00 (n_days=730)
  Dates for training    : 2016-01-01 00:00:00 --- 2017-08-31 00:00:00 (n_days=608)
  Dates for validation  : 2017-09-01 00:00:00 --- 2017-10-31 00:00:00 (n_days=60)
  Dates for test        : 2017-11-01 00:00:00 --- 2017-12-31 00:00:00 (n_days=60)

Hyperparameter tuning

Hyperparameter and lag tuning involves systematically testing different values or combinations of hyperparameters (and/or lags) to find the optimal configuration that gives the best performance. The skforecast library provides two different methods to evaluate each candidate configuration:

  • Backtesting: In this method, the model predicts several steps ahead in each iteration, using the same forecast horizon and retraining frequency strategy that would be used if the model were deployed. This simulates a real forecasting scenario where the model is retrained and updated over time.

  • One-Step Ahead: Evaluates the model using only one-step-ahead predictions. This method is faster because it requires fewer iterations, but it only tests the model's performance in the immediate next time step (t+1).

Each method uses a different evaluation strategy, so they may produce different results. However, in the long run, both methods are expected to converge to similar selections of optimal hyperparameters. The one-step-ahead method is much faster than backtesting because it requires fewer iterations, but it only tests the model's performance in the immediate next time step. It is recommended to backtest the final model for a more accurate multi-step performance estimate.

In [15]:
# Create forecaster
# ==============================================================================
window_features = RollingFeatures(stats=['mean', 'min', 'max'], window_sizes=7)
forecaster = ForecasterRecursiveMultiSeries(
                regressor          = LGBMRegressor(random_state=8520, verbose=-1),
                lags               = 14,
                window_features    = window_features,
                transformer_series = None,
                transformer_exog   = transformer_exog,
                fit_kwargs         = {'categorical_feature': categorical_features},
                encoding           = "ordinal"
            )
In [16]:
# Bayesian search with OneStepAheadFold
# ==============================================================================
def search_space(trial):
    search_space  = {
        'lags'            : trial.suggest_categorical('lags', [31, 62]),
        'n_estimators'    : trial.suggest_int('n_estimators', 200, 800, step=100),
        'max_depth'       : trial.suggest_int('max_depth', 3, 8, step=1),
        'min_data_in_leaf': trial.suggest_int('min_data_in_leaf', 25, 500),
        'learning_rate'   : trial.suggest_float('learning_rate', 0.01, 0.5),
        'feature_fraction': trial.suggest_float('feature_fraction', 0.5, 0.8, step=0.1),
        'max_bin'         : trial.suggest_int('max_bin', 50, 100, step=25),
        'reg_alpha'       : trial.suggest_float('reg_alpha', 0, 1, step=0.1),
        'reg_lambda'      : trial.suggest_float('reg_lambda', 0, 1, step=0.1)
    }

    return search_space

cv = OneStepAheadFold(initial_train_size=608) # size of the training set

results_search, best_trial = bayesian_search_forecaster_multiseries(
    forecaster        = forecaster,
    series            = {k: v.loc[:end_validation,] for k, v in series_dict.items()},
    exog              = {k: v.loc[:end_validation, exog_features] for k, v in exog_dict.items()},
    cv                = cv,
    search_space      = search_space,
    n_trials          = 10,
    metric            = "mean_absolute_error",
    return_best       = True,
    verbose           = False,
    n_jobs            = "auto",
    show_progress     = True,
    suppress_warnings = True,
)

best_params = results_search.at[0, 'params']
best_lags = results_search.at[0, 'lags']
results_search.head(3)
/home/ubuntu/anaconda3/envs/skforecast_14_py12/lib/python3.12/site-packages/skforecast/recursive/_forecaster_recursive_multiseries.py:1077: MissingValuesWarning: NaNs detected in `X_train`. Some regressors do not allow NaN values during training. If you want to drop them, set `forecaster.dropna_from_series = True`. 
 You can suppress this warning using: warnings.simplefilter('ignore', category=MissingValuesWarning)
  warnings.warn(
`Forecaster` refitted using the best-found lags and parameters, and the whole data set: 
  Lags: [ 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
 49 50 51 52 53 54 55 56 57 58 59 60 61 62] 
  Parameters: {'n_estimators': 600, 'max_depth': 6, 'min_data_in_leaf': 188, 'learning_rate': 0.1590191866233202, 'feature_fraction': 0.6, 'max_bin': 100, 'reg_alpha': 0.9, 'reg_lambda': 0.5}
  Backtesting metric: 265.23255503913776
  Levels: ['Bear_assembly_Angel', 'Bear_assembly_Beatrice', 'Bear_assembly_Danial', 'Bear_assembly_Diana', 'Bear_assembly_Genia', 'Bear_assembly_Harry', 'Bear_assembly_Jose', 'Bear_assembly_Roxy', 'Bear_assembly_Ruby', 'Bear_education_Alfredo', 'Bear_education_Alvaro', 'Bear_education_Arnold', 'Bear_education_Augusta', 'Bear_education_Austin', 'Bear_education_Babara', 'Bear_education_Benita', 'Bear_education_Benjamin', 'Bear_education_Bob', 'Bear_education_Bonita', 'Bear_education_Bulah', 'Bear_education_Carlo', 'Bear_education_Chad', 'Bear_education_Chana', 'Bear_education_Chun', 'Bear_education_Clint', 'Bear_education_Curtis', 'Bear_education_Danna', 'Bear_education_Darrell', 'Bear_education_Deborah', 'Bear_education_Deena', 'Bear_education_Derek', 'Bear_education_Destiny', 'Bear_education_Elise', 'Bear_education_Fannie', 'Bear_education_Gavin', 'Bear_education_Herb', 'Bear_education_Herman', 'Bear_education_Holly', 'Bear_education_Irene', 'Bear_education_Iris', 'Bear_education_Katy', 'Bear_education_Lashanda', 'Bear_education_Laurette', 'Bear_education_Leonel', 'Bear_education_Lewis', 'Bear_education_Lidia', 'Bear_education_Lila', 'Bear_education_Liliana', 'Bear_education_Lorie', 'Bear_education_Mara', 'Bear_education_Marie', 'Bear_education_Marsha', 'Bear_education_Marta', 'Bear_education_Maryjane', 'Bear_education_Merrill', 'Bear_education_Millie', 'Bear_education_Nanette', 'Bear_education_Oscar', 'Bear_education_Owen', 'Bear_education_Paola', 'Bear_education_Patricia', 'Bear_education_Pattie', 'Bear_education_Rebecca', 'Bear_education_Sandy', 'Bear_education_Santos', 'Bear_education_Sharon', 'Bear_education_Shelley', 'Bear_education_Tara', 'Bear_education_Thuy', 'Bear_education_Val', 'Bear_education_Wade', 'Bear_education_Wilton', 'Bear_education_Wm', 'Bear_education_Yuri', 'Bear_education_Yvette', 'Bear_education_Zandra', 'Bear_lodging_Dannie', 'Bear_lodging_Erick', 'Bear_lodging_Esperanza', 'Bear_lodging_Evan', 'Bear_parking_Bridget', 'Bear_parking_Bruce', 'Bear_parking_Gordon', 'Bear_public_Arlean', 'Bear_public_Jocelyn', 'Bear_public_Lowell', 'Bear_public_Orville', 'Bear_public_Rayna', 'Bear_public_Valorie', 'Bear_science_Alison', 'Bear_science_Eleanor', 'Bear_utility_Sidney', 'Bobcat_assembly_Adam', 'Bobcat_assembly_Billy', 'Bobcat_assembly_Camilla', 'Bobcat_assembly_Franklin', 'Bobcat_education_Alissa', 'Bobcat_education_Angela', 'Bobcat_education_Barbra', 'Bobcat_education_Coleman', 'Bobcat_education_Dylan', 'Bobcat_education_Emile', 'Bobcat_education_Hollis', 'Bobcat_education_Jayne', 'Bobcat_education_Karin', 'Bobcat_education_Marisha', 'Bobcat_education_Miki', 'Bobcat_education_Monte', 'Bobcat_education_Rodrick', 'Bobcat_education_Rosalva', 'Bobcat_education_Seth', 'Bobcat_education_Toni', 'Bobcat_education_Whitney', 'Bobcat_lodging_Darin', 'Bobcat_lodging_Melaine', 'Bobcat_lodging_Nickolas', 'Bobcat_office_Alma', 'Bobcat_office_Justine', 'Bobcat_office_Kassandra', 'Bobcat_office_Melody', 'Bobcat_office_Nikita', 'Bobcat_other_Howard', 'Bobcat_other_Jovita', 'Bobcat_other_Timothy', 'Bobcat_public_Angie', 'Bobcat_science_Tammy', 'Bobcat_warehouse_Charlie', 'Bull_assembly_Amalia', 'Bull_assembly_Beau', 'Bull_assembly_Brandon', 'Bull_assembly_Daryl', 'Bull_assembly_Dorethea', 'Bull_assembly_Freddie', 'Bull_assembly_Gerri', 'Bull_assembly_Gigi', 'Bull_assembly_Goldie', 'Bull_assembly_Katia', 'Bull_assembly_Lance', 'Bull_assembly_Lesa', 'Bull_assembly_Maren', 'Bull_assembly_Nathanial', 'Bull_assembly_Newton', 'Bull_assembly_Nick', 'Bull_assembly_Vanessa', 'Bull_education_Annette', 'Bull_education_Antonia', 'Bull_education_Arthur', 'Bull_education_Barry', 'Bull_education_Bernice', 'Bull_education_Brady', 'Bull_education_Brain', 'Bull_education_Brandi', 'Bull_education_Brenda', 'Bull_education_Bryon', 'Bull_education_Carl', 'Bull_education_Clarice', 'Bull_education_Clarita', 'Bull_education_Dakota', 'Bull_education_Dan', 'Bull_education_Dania', 'Bull_education_Delia', 'Bull_education_Dora', 'Bull_education_Dottie', 'Bull_education_Elva', 'Bull_education_Fabiola', 'Bull_education_Geneva', 'Bull_education_Genie', 'Bull_education_Gregory', 'Bull_education_Hayley', 'Bull_education_Jae', 'Bull_education_Jeffery', 'Bull_education_Joseph', 'Bull_education_Juan', 'Bull_education_Kendra', 'Bull_education_Krista', 'Bull_education_Kristal', 'Bull_education_Lenny', 'Bull_education_Linnie', 'Bull_education_Luke', 'Bull_education_Lydia', 'Bull_education_Lyn', 'Bull_education_Magaret', 'Bull_education_Mario', 'Bull_education_Matilda', 'Bull_education_Mervin', 'Bull_education_Miquel', 'Bull_education_Miranda', 'Bull_education_Myra', 'Bull_education_Nichole', 'Bull_education_Nina', 'Bull_education_Nolan', 'Bull_education_Olive', 'Bull_education_Pablo', 'Bull_education_Pamela', 'Bull_education_Patrina', 'Bull_education_Racheal', 'Bull_education_Reina', 'Bull_education_Reynaldo', 'Bull_education_Roland', 'Bull_education_Roseann', 'Bull_education_Sebastian', 'Bull_education_Shona', 'Bull_education_Stewart', 'Bull_education_Summer', 'Bull_education_Tracey', 'Bull_education_Venita', 'Bull_education_Violeta', 'Bull_lodging_Abby', 'Bull_lodging_Allen', 'Bull_lodging_Anibal', 'Bull_lodging_Caren', 'Bull_lodging_Carie', 'Bull_lodging_Charlotte', 'Bull_lodging_Danielle', 'Bull_lodging_Dave', 'Bull_lodging_Elena', 'Bull_lodging_Graciela', 'Bull_lodging_Hugo', 'Bull_lodging_Jeremiah', 'Bull_lodging_Leonard', 'Bull_lodging_Lettie', 'Bull_lodging_Melissa', 'Bull_lodging_Perry', 'Bull_lodging_Terence', 'Bull_lodging_Travis', 'Bull_lodging_Xavier', 'Bull_office_Anne', 'Bull_office_Chantel', 'Bull_office_Claudia', 'Bull_office_Debbie', 'Bull_office_Efren', 'Bull_office_Ella', 'Bull_office_Hilton', 'Bull_office_Ivette', 'Bull_office_Lilla', 'Bull_office_Mai', 'Bull_office_Marco', 'Bull_office_Myron', 'Bull_office_Nicolas', 'Bull_office_Rob', 'Bull_office_Sally', 'Bull_office_Trevor', 'Bull_office_Yvonne', 'Bull_public_Hyun', 'Bull_public_Jefferson', 'Bull_services_Jeanmarie', 'Bull_services_Juanita', 'Bull_services_Nadine', 'Bull_services_Rachelle', 'Bull_services_Winford', 'Cockatoo_assembly_Britt', 'Cockatoo_assembly_Doyle', 'Cockatoo_assembly_Ed', 'Cockatoo_assembly_Emilio', 'Cockatoo_assembly_Evelyn', 'Cockatoo_assembly_Fernanda', 'Cockatoo_assembly_Genoveva', 'Cockatoo_assembly_Griselda', 'Cockatoo_assembly_Heath', 'Cockatoo_assembly_Meredith', 'Cockatoo_assembly_Mimi', 'Cockatoo_assembly_Pierre', 'Cockatoo_assembly_Ralph', 'Cockatoo_assembly_Rodger', 'Cockatoo_assembly_Tabitha', 'Cockatoo_assembly_Valencia', 'Cockatoo_education_Amira', 'Cockatoo_education_Arlen', 'Cockatoo_education_Brendan', 'Cockatoo_education_Brigitte', 'Cockatoo_education_Charity', 'Cockatoo_education_Christi', 'Cockatoo_education_Claudine', 'Cockatoo_education_Clifton', 'Cockatoo_education_Collin', 'Cockatoo_education_Deloris', 'Cockatoo_education_Doreen', 'Cockatoo_education_Erik', 'Cockatoo_education_Eunice', 'Cockatoo_education_Evangeline', 'Cockatoo_education_Flora', 'Cockatoo_education_Gussie', 'Cockatoo_education_Helene', 'Cockatoo_education_Jack', 'Cockatoo_education_Janet', 'Cockatoo_education_Joeann', 'Cockatoo_education_Joel', 'Cockatoo_education_Jon', 'Cockatoo_education_Julia', 'Cockatoo_education_Julio', 'Cockatoo_education_June', 'Cockatoo_education_Latrice', 'Cockatoo_education_Laurence', 'Cockatoo_education_Lionel', 'Cockatoo_education_Magdalena', 'Cockatoo_education_Marva', 'Cockatoo_education_Maynard', 'Cockatoo_education_Mayra', 'Cockatoo_education_Melanie', 'Cockatoo_education_Minh', 'Cockatoo_education_Nelda', 'Cockatoo_education_Oliver', 'Cockatoo_education_Orlando', 'Cockatoo_education_Rita', 'Cockatoo_education_Shawn', 'Cockatoo_education_Sheryl', 'Cockatoo_education_Terrence', 'Cockatoo_education_Tyler', 'Cockatoo_education_Victoria', 'Cockatoo_industrial_Nathaniel', 'Cockatoo_industrial_Sarita', 'Cockatoo_lodging_Aimee', 'Cockatoo_lodging_Albert', 'Cockatoo_lodging_Alicia', 'Cockatoo_lodging_Ana', 'Cockatoo_lodging_Carmen', 'Cockatoo_lodging_Cletus', 'Cockatoo_lodging_Elvia', 'Cockatoo_lodging_Emory', 'Cockatoo_lodging_Eric', 'Cockatoo_lodging_Homer', 'Cockatoo_lodging_Jarrod', 'Cockatoo_lodging_Javier', 'Cockatoo_lodging_Jim', 'Cockatoo_lodging_Jimmie', 'Cockatoo_lodging_Johnathan', 'Cockatoo_lodging_Josephine', 'Cockatoo_lodging_Judi', 'Cockatoo_lodging_Katharine', 'Cockatoo_lodging_Kerri', 'Cockatoo_lodging_Kyle', 'Cockatoo_lodging_Lana', 'Cockatoo_lodging_Linwood', 'Cockatoo_lodging_Lynne', 'Cockatoo_lodging_Mandy', 'Cockatoo_lodging_Olga', 'Cockatoo_lodging_Raphael', 'Cockatoo_lodging_Tesha', 'Cockatoo_lodging_Tessie', 'Cockatoo_office_Ada', 'Cockatoo_office_Alton', 'Cockatoo_office_Christy', 'Cockatoo_office_Delores', 'Cockatoo_office_Elbert', 'Cockatoo_office_Gail', 'Cockatoo_office_Georgia', 'Cockatoo_office_Giovanni', 'Cockatoo_office_Jimmy', 'Cockatoo_office_Jodie', 'Cockatoo_office_Kristin', 'Cockatoo_office_Laila', 'Cockatoo_office_Lorraine', 'Cockatoo_office_Margaret', 'Cockatoo_office_Paige', 'Cockatoo_office_Pansy', 'Cockatoo_office_Rodney', 'Cockatoo_office_Roxanna', 'Cockatoo_public_Caleb', 'Cockatoo_public_Chiquita', 'Cockatoo_public_Harland', 'Cockatoo_public_Leah', 'Cockatoo_public_Shad', 'Cockatoo_public_Valerie', 'Cockatoo_religion_Diedre', 'Cockatoo_science_Rex', 'Cockatoo_utility_Kimiko', 'Cockatoo_utility_Sherri', 'Crow_education_Kate', 'Crow_education_Keisha', 'Crow_education_Marlin', 'Crow_education_Omer', 'Crow_education_Winston', 'Eagle_assembly_Benny', 'Eagle_assembly_Candice', 'Eagle_assembly_Estelle', 'Eagle_assembly_Herbert', 'Eagle_assembly_Ian', 'Eagle_assembly_Josie', 'Eagle_assembly_Lacy', 'Eagle_assembly_Latrina', 'Eagle_assembly_Margret', 'Eagle_assembly_Noel', 'Eagle_assembly_Portia', 'Eagle_education_Alberto', 'Eagle_education_April', 'Eagle_education_Brianne', 'Eagle_education_Brooke', 'Eagle_education_Cassie', 'Eagle_education_Edith', 'Eagle_education_Eileen', 'Eagle_education_Jewell', 'Eagle_education_Lino', 'Eagle_education_Luther', 'Eagle_education_Maragret', 'Eagle_education_Norah', 'Eagle_education_Paul', 'Eagle_education_Peter', 'Eagle_education_Petra', 'Eagle_education_Raul', 'Eagle_education_Roman', 'Eagle_education_Samantha', 'Eagle_education_Shana', 'Eagle_education_Shanna', 'Eagle_education_Shante', 'Eagle_education_Sheena', 'Eagle_education_Sherrill', 'Eagle_education_Teresa', 'Eagle_education_Wesley', 'Eagle_education_Will', 'Eagle_food_Jennifer', 'Eagle_food_Kay', 'Eagle_health_Amy', 'Eagle_health_Athena', 'Eagle_health_Gregoria', 'Eagle_health_Jodi', 'Eagle_health_Lucinda', 'Eagle_health_Margo', 'Eagle_health_Reba', 'Eagle_health_Reuben', 'Eagle_health_Trisha', 'Eagle_health_Vincenza', 'Eagle_lodging_Andy', 'Eagle_lodging_Blake', 'Eagle_lodging_Casey', 'Eagle_lodging_Dawn', 'Eagle_lodging_Edgardo', 'Eagle_lodging_Garland', 'Eagle_lodging_Stephanie', 'Eagle_lodging_Terri', 'Eagle_lodging_Tressa', 'Eagle_lodging_Trina', 'Eagle_office_Amanda', 'Eagle_office_Amie', 'Eagle_office_Bridgett', 'Eagle_office_Chantelle', 'Eagle_office_Chauncey', 'Eagle_office_Dallas', 'Eagle_office_Damian', 'Eagle_office_Demetra', 'Eagle_office_Donovan', 'Eagle_office_Efrain', 'Eagle_office_Elia', 'Eagle_office_Elias', 'Eagle_office_Elvis', 'Eagle_office_Flossie', 'Eagle_office_Francis', 'Eagle_office_Freida', 'Eagle_office_Henriette', 'Eagle_office_Isidro', 'Eagle_office_Jackie', 'Eagle_office_Jeff', 'Eagle_office_Joette', 'Eagle_office_Katheleen', 'Eagle_office_Lamont', 'Eagle_office_Lane', 'Eagle_office_Lillian', 'Eagle_office_Mable', 'Eagle_office_Mandi', 'Eagle_office_Marisela', 'Eagle_office_Michele', 'Eagle_office_Nereida', 'Eagle_office_Norbert', 'Eagle_office_Patrice', 'Eagle_office_Phyllis', 'Eagle_office_Randolph', 'Eagle_office_Remedios', 'Eagle_office_Ryan', 'Eagle_office_Sheree', 'Eagle_office_Sonya', 'Eagle_office_Tia', 'Eagle_office_Yadira', 'Eagle_public_Alvin', 'Eagle_public_Henry', 'Eagle_public_Minnie', 'Eagle_public_Missy', 'Eagle_public_Ola', 'Eagle_public_Pearle', 'Eagle_public_Preston', 'Fox_assembly_Adrianne', 'Fox_assembly_Audrey', 'Fox_assembly_Boyce', 'Fox_assembly_Bradley', 'Fox_assembly_Carlos', 'Fox_assembly_Cathy', 'Fox_assembly_Cecelia', 'Fox_assembly_Christie', 'Fox_assembly_Cindy', 'Fox_assembly_Dixie', 'Fox_assembly_Emma', 'Fox_assembly_Gary', 'Fox_assembly_Jerrod', 'Fox_assembly_Johnnie', 'Fox_assembly_Kathie', 'Fox_assembly_Lakeisha', 'Fox_assembly_Leeanne', 'Fox_assembly_Renna', 'Fox_assembly_Sheldon', 'Fox_assembly_Terrell', 'Fox_assembly_Tony', 'Fox_education_Andre', 'Fox_education_Ashli', 'Fox_education_Burton', 'Fox_education_Carleen', 'Fox_education_Charles', 'Fox_education_Claire', 'Fox_education_Claude', 'Fox_education_Cynthia', 'Fox_education_Delma', 'Fox_education_Dewayne', 'Fox_education_Dominique', 'Fox_education_Eddy', 'Fox_education_Eldon', 'Fox_education_Elizabeth', 'Fox_education_Elois', 'Fox_education_Elvira', 'Fox_education_Etta', 'Fox_education_Gayla', 'Fox_education_Geoffrey', 'Fox_education_Gloria', 'Fox_education_Henrietta', 'Fox_education_Heriberto', 'Fox_education_Jaclyn', 'Fox_education_Jacqueline', 'Fox_education_Janina', 'Fox_education_John', 'Fox_education_Kendrick', 'Fox_education_Kim', 'Fox_education_Kris', 'Fox_education_Leona', 'Fox_education_Leota', 'Fox_education_Lesley', 'Fox_education_Lilly', 'Fox_education_Long', 'Fox_education_Louie', 'Fox_education_Marcelina', 'Fox_education_Maris', 'Fox_education_Marlana', 'Fox_education_Maureen', 'Fox_education_Melinda', 'Fox_education_Melvin', 'Fox_education_Miguelina', 'Fox_education_Nilda', 'Fox_education_Ollie', 'Fox_education_Otilia', 'Fox_education_Ray', 'Fox_education_Rosie', 'Fox_education_Rudolph', 'Fox_education_Shaun', 'Fox_education_Shawanda', 'Fox_education_Shirley', 'Fox_education_Stacia', 'Fox_education_Sterling', 'Fox_education_Suzan', 'Fox_education_Tamika', 'Fox_education_Theodore', 'Fox_education_Tonya', 'Fox_education_Vernon', 'Fox_education_Virgil', 'Fox_education_Virginia', 'Fox_education_Wendell', 'Fox_education_Willis', 'Fox_education_Yolande', 'Fox_food_Francesco', 'Fox_food_Scott', 'Fox_health_Lorena', 'Fox_lodging_Alana', 'Fox_lodging_Angla', 'Fox_lodging_Frances', 'Fox_lodging_Helen', 'Fox_lodging_Isabell', 'Fox_lodging_Jina', 'Fox_lodging_Morris', 'Fox_lodging_Stephan', 'Fox_lodging_Stephen', 'Fox_lodging_Wallace', 'Fox_lodging_Warren', 'Fox_lodging_Winifred', 'Fox_office_Alice', 'Fox_office_Bernard', 'Fox_office_Berniece', 'Fox_office_Brandy', 'Fox_office_Carson', 'Fox_office_Clayton', 'Fox_office_Demetrius', 'Fox_office_Easter', 'Fox_office_Edythe', 'Fox_office_Essie', 'Fox_office_Gaylord', 'Fox_office_Israel', 'Fox_office_Joy', 'Fox_office_Juana', 'Fox_office_Karima', 'Fox_office_Margarita', 'Fox_office_Molly', 'Fox_office_Rowena', 'Fox_office_Sheila', 'Fox_office_Susanne', 'Fox_office_Thelma', 'Fox_office_Vicki', 'Fox_office_Yong', 'Fox_office_Zachary', 'Fox_parking_Felipa', 'Fox_parking_Lynelle', 'Fox_parking_Tommie', 'Fox_public_Bart', 'Fox_public_Belle', 'Fox_public_Denny', 'Fox_public_Lauren', 'Fox_public_Martin', 'Fox_public_Rhonda', 'Fox_religion_Maurice', 'Fox_retail_Manie', 'Fox_utility_Marian', 'Fox_warehouse_Lorretta', 'Fox_warehouse_Pearl', 'Gator_assembly_Alexa', 'Gator_assembly_Bailey', 'Gator_assembly_Beryl', 'Gator_assembly_Blanca', 'Gator_assembly_Daisy', 'Gator_assembly_Elliot', 'Gator_assembly_Enid', 'Gator_assembly_Erich', 'Gator_assembly_Gene', 'Gator_assembly_Hue', 'Gator_assembly_Joni', 'Gator_assembly_Kayleigh', 'Gator_assembly_Kimberly', 'Gator_assembly_Lelia', 'Gator_assembly_Lera', 'Gator_assembly_Lilli', 'Gator_assembly_Loyce', 'Gator_assembly_Lucia', 'Gator_assembly_Marjorie', 'Gator_assembly_Maurine', 'Gator_assembly_Milton', 'Gator_assembly_Regina', 'Gator_assembly_Roy', 'Gator_assembly_Selma', 'Gator_assembly_Stacy', 'Gator_assembly_Virgie', 'Gator_office_August', 'Gator_office_Betty', 'Gator_office_Carrie', 'Gator_office_Hunter', 'Gator_office_Julie', 'Gator_office_Lisa', 'Gator_office_Lucy', 'Gator_office_Merle', 'Gator_other_Cassandra', 'Gator_other_Elfriede', 'Gator_other_Gertrude', 'Gator_other_Glen', 'Gator_other_Minda', 'Gator_other_Refugio', 'Gator_other_Reginald', 'Gator_other_Russel', 'Gator_other_Samuel', 'Gator_public_Alexandra', 'Gator_public_Beulah', 'Gator_public_Cheri', 'Gator_public_Clara', 'Gator_public_Dale', 'Gator_public_Dewey', 'Gator_public_Dionna', 'Gator_public_Erika', 'Gator_public_Everette', 'Gator_public_Geraldine', 'Gator_public_Janna', 'Gator_public_Jayme', 'Gator_public_Jolene', 'Gator_public_Kendall', 'Gator_public_Kenny', 'Gator_public_Latasha', 'Gator_public_Leroy', 'Gator_public_Lindsey', 'Gator_public_Lona', 'Gator_public_Marcie', 'Gator_public_Marissa', 'Gator_public_Maude', 'Gator_public_Natasha', 'Gator_public_Nettie', 'Gator_public_Noe', 'Gator_public_Philip', 'Gator_public_Randall', 'Gator_public_Ross', 'Gator_public_Tiffany', 'Gator_warehouse_Constance', 'Gator_warehouse_Stacie', 'Hog_assembly_Annemarie', 'Hog_assembly_Arlie', 'Hog_assembly_Colette', 'Hog_assembly_Dona', 'Hog_assembly_Edward', 'Hog_assembly_Jasmine', 'Hog_assembly_Letha', 'Hog_assembly_Maribel', 'Hog_assembly_Marilynn', 'Hog_assembly_Pedro', 'Hog_assembly_Una', 'Hog_education_Beth', 'Hog_education_Bruno', 'Hog_education_Caridad', 'Hog_education_Casandra', 'Hog_education_Cathleen', 'Hog_education_Darryl', 'Hog_education_Donnie', 'Hog_education_George', 'Hog_education_Hallie', 'Hog_education_Haywood', 'Hog_education_Janell', 'Hog_education_Jared', 'Hog_education_Jewel', 'Hog_education_Jordan', 'Hog_education_Josh', 'Hog_education_Leandro', 'Hog_education_Luvenia', 'Hog_education_Madge', 'Hog_education_Odell', 'Hog_education_Rachael', 'Hog_education_Robert', 'Hog_education_Roberto', 'Hog_education_Sonia', 'Hog_education_Wayne', 'Hog_food_Morgan', 'Hog_health_Hisako', 'Hog_health_Jenny', 'Hog_health_Kesha', 'Hog_industrial_Jay', 'Hog_industrial_Jeremy', 'Hog_industrial_Joanne', 'Hog_industrial_Mariah', 'Hog_industrial_Quentin', 'Hog_lodging_Brian', 'Hog_lodging_Celeste', 'Hog_lodging_Edgar', 'Hog_lodging_Francisco', 'Hog_lodging_Hal', 'Hog_lodging_Mauricio', 'Hog_lodging_Nikki', 'Hog_lodging_Ora', 'Hog_lodging_Retha', 'Hog_lodging_Shanti', 'Hog_lodging_Shonda', 'Hog_office_Alexis', 'Hog_office_Alisha', 'Hog_office_Almeda', 'Hog_office_Bessie', 'Hog_office_Betsy', 'Hog_office_Bill', 'Hog_office_Bryan', 'Hog_office_Buford', 'Hog_office_Byron', 'Hog_office_Candi', 'Hog_office_Carri', 'Hog_office_Catalina', 'Hog_office_Catharine', 'Hog_office_Charla', 'Hog_office_Clemencia', 'Hog_office_Concetta', 'Hog_office_Cordelia', 'Hog_office_Corey', 'Hog_office_Corie', 'Hog_office_Cornell', 'Hog_office_Cortney', 'Hog_office_Darline', 'Hog_office_Denita', 'Hog_office_Elizbeth', 'Hog_office_Elke', 'Hog_office_Elnora', 'Hog_office_Eloise', 'Hog_office_Elsy', 'Hog_office_Emmanuel', 'Hog_office_Garrett', 'Hog_office_Guadalupe', 'Hog_office_Gustavo', 'Hog_office_Joey', 'Hog_office_Josefina', 'Hog_office_Judith', 'Hog_office_Lanell', 'Hog_office_Lavon', 'Hog_office_Leanne', 'Hog_office_Leon', 'Hog_office_Lizzie', 'Hog_office_Mack', 'Hog_office_Man', 'Hog_office_Mari', 'Hog_office_Marilyn', 'Hog_office_Marlena', 'Hog_office_Marlene', 'Hog_office_Mary', 'Hog_office_Merilyn', 'Hog_office_Migdalia', 'Hog_office_Mike', 'Hog_office_Miriam', 'Hog_office_Myles', 'Hog_office_Nancie', 'Hog_office_Napoleon', 'Hog_office_Nia', 'Hog_office_Patrick', 'Hog_office_Randi', 'Hog_office_Richelle', 'Hog_office_Roger', 'Hog_office_Rolando', 'Hog_office_Sarah', 'Hog_office_Shawna', 'Hog_office_Shawnna', 'Hog_office_Sherrie', 'Hog_office_Shon', 'Hog_office_Simon', 'Hog_office_Sonny', 'Hog_office_Sung', 'Hog_office_Sydney', 'Hog_office_Terry', 'Hog_office_Thomas', 'Hog_office_Valda', 'Hog_office_Vera', 'Hog_other_Lynette', 'Hog_other_Noma', 'Hog_other_Tobias', 'Hog_parking_Antoinette', 'Hog_parking_Bernardo', 'Hog_parking_Cliff', 'Hog_parking_Jean', 'Hog_parking_Jeana', 'Hog_parking_Joan', 'Hog_parking_Marcus', 'Hog_parking_Shannon', 'Hog_public_Brad', 'Hog_public_Crystal', 'Hog_public_Gerard', 'Hog_public_Kevin', 'Hog_public_Octavia', 'Hog_science_Max', 'Hog_services_Adrianna', 'Hog_services_Joe', 'Hog_services_Kerrie', 'Hog_services_Marshall', 'Hog_warehouse_Louise', 'Hog_warehouse_Porsha', 'Hog_warehouse_Rosanna', 'Lamb_assembly_Alden', 'Lamb_assembly_Bertie', 'Lamb_assembly_Cesar', 'Lamb_assembly_Cherie', 'Lamb_assembly_Corliss', 'Lamb_assembly_Delilah', 'Lamb_assembly_Dillon', 'Lamb_assembly_Dorathy', 'Lamb_assembly_Dorothy', 'Lamb_assembly_Dudley', 'Lamb_assembly_Elinor', 'Lamb_assembly_Ethel', 'Lamb_assembly_Eugenia', 'Lamb_assembly_Isa', 'Lamb_assembly_Jerry', 'Lamb_assembly_Katelyn', 'Lamb_assembly_Kurt', 'Lamb_assembly_Librada', 'Lamb_assembly_Louis', 'Lamb_assembly_Nicole', 'Lamb_assembly_Ossie', 'Lamb_assembly_Queen', 'Lamb_assembly_Rosa', 'Lamb_assembly_Steven', 'Lamb_assembly_Tasha', 'Lamb_assembly_Tawana', 'Lamb_assembly_Theresa', 'Lamb_assembly_Walter', 'Lamb_assembly_Zita', 'Lamb_education_Aldo', 'Lamb_education_Alina', 'Lamb_education_Antonio', 'Lamb_education_Armando', 'Lamb_education_Augustine', 'Lamb_education_Bert', 'Lamb_education_Bettye', 'Lamb_education_Camille', 'Lamb_education_Carlton', 'Lamb_education_Chet', 'Lamb_education_Daniel', 'Lamb_education_Darrel', 'Lamb_education_Debby', 'Lamb_education_Dolly', 'Lamb_education_Domitila', 'Lamb_education_Dwayne', 'Lamb_education_Ellen', 'Lamb_education_Emery', 'Lamb_education_Emilie', 'Lamb_education_Eula', 'Lamb_education_Faith', 'Lamb_education_Felicia', 'Lamb_education_Felipe', 'Lamb_education_Fred', 'Lamb_education_Freddy', 'Lamb_education_Gabriel', 'Lamb_education_Gabrielle', 'Lamb_education_Garry', 'Lamb_education_Harold', 'Lamb_education_Heidi', 'Lamb_education_Hellen', 'Lamb_education_Hilary', 'Lamb_education_Hillary', 'Lamb_education_Hubert', 'Lamb_education_Hui', 'Lamb_education_Ira', 'Lamb_education_Isabelle', 'Lamb_education_Jane', 'Lamb_education_Junior', 'Lamb_education_Kasha', 'Lamb_education_Kayla', 'Lamb_education_Larissa', 'Lamb_education_Lawrence', 'Lamb_education_Lazaro', 'Lamb_education_Lemuel', 'Lamb_education_Leopoldo', 'Lamb_education_Logan', 'Lamb_education_Lucas', 'Lamb_education_Luz', 'Lamb_education_Mae', 'Lamb_education_Manuel', 'Lamb_education_Marc', 'Lamb_education_Maritza', 'Lamb_education_Marty', 'Lamb_education_Maxwell', 'Lamb_education_Mckenzie', 'Lamb_education_Moses', 'Lamb_education_Nathan', 'Lamb_education_Nichol', 'Lamb_education_Norris', 'Lamb_education_Patsy', 'Lamb_education_Phil', 'Lamb_education_Philomena', 'Lamb_education_Princess', 'Lamb_education_Randal', 'Lamb_education_Renae', 'Lamb_education_Rick', 'Lamb_education_Robin', 'Lamb_education_Rodrigo', 'Lamb_education_Ruben', 'Lamb_education_Sabrina', 'Lamb_education_Salvador', 'Lamb_education_Sara', 'Lamb_education_Stefan', 'Lamb_education_Sunny', 'Lamb_education_Sylvester', 'Lamb_education_Terina', 'Lamb_education_Traci', 'Lamb_education_Vaughn', 'Lamb_education_Wanda', 'Lamb_education_Wilbert', 'Lamb_education_Willetta', 'Lamb_education_Williams', 'Lamb_food_Sylvia', 'Lamb_health_Ken', 'Lamb_industrial_Carla', 'Lamb_industrial_Enrique', 'Lamb_industrial_Venessa', 'Lamb_industrial_Willard', 'Lamb_lodging_Burt', 'Lamb_lodging_Harley', 'Lamb_office_Bertha', 'Lamb_office_Caitlin', 'Lamb_office_Callie', 'Lamb_office_Corine', 'Lamb_office_Donita', 'Lamb_office_Gerardo', 'Lamb_office_Jo', 'Lamb_office_Joanna', 'Lamb_office_Kent', 'Lamb_office_Kerry', 'Lamb_office_Maggie', 'Lamb_office_Peggy', 'Lamb_office_Raymond', 'Lamb_office_Stefani', 'Lamb_office_Vasiliki', 'Lamb_office_Velma', 'Lamb_office_William', 'Lamb_other_Katharina', 'Lamb_other_Minerva', 'Lamb_public_Angeline', 'Lamb_public_Bradly', 'Lamb_public_Grace', 'Lamb_public_Gracie', 'Lamb_public_Nyla', 'Lamb_public_Vania', 'Lamb_warehouse_Allan', 'Moose_education_Abbie', 'Moose_education_Clark', 'Moose_education_Diane', 'Moose_education_Florence', 'Moose_education_Gladys', 'Moose_education_Leland', 'Moose_education_Lori', 'Moose_education_Maria', 'Moose_education_Marina', 'Moose_education_Marlon', 'Moose_education_Rene', 'Moose_education_Ricardo', 'Moose_education_Sasha', 'Mouse_health_Buddy', 'Mouse_health_Estela', 'Mouse_health_Ileana', 'Mouse_health_Justin', 'Mouse_health_Modesto', 'Mouse_lodging_Vicente', 'Mouse_science_Micheal', 'Panther_assembly_Carrol', 'Panther_assembly_David', 'Panther_assembly_Denice', 'Panther_assembly_Gwyneth', 'Panther_assembly_Pamella', 'Panther_education_Alecia', 'Panther_education_Annetta', 'Panther_education_Aurora', 'Panther_education_Cleopatra', 'Panther_education_Diann', 'Panther_education_Edna', 'Panther_education_Emily', 'Panther_education_Enriqueta', 'Panther_education_Genevieve', 'Panther_education_Gina', 'Panther_education_Hugh', 'Panther_education_Ivan', 'Panther_education_Janis', 'Panther_education_Jerome', 'Panther_education_Jonathan', 'Panther_education_Karri', 'Panther_education_Mattie', 'Panther_education_Misty', 'Panther_education_Mohammad', 'Panther_education_Neal', 'Panther_education_Quintin', 'Panther_education_Rosalie', 'Panther_education_Scarlett', 'Panther_education_Shelton', 'Panther_education_Sophia', 'Panther_education_Teofila', 'Panther_education_Tina', 'Panther_education_Vincent', 'Panther_education_Violet', 'Panther_education_Zelda', 'Panther_lodging_Alita', 'Panther_lodging_Anastasia', 'Panther_lodging_Awilda', 'Panther_lodging_Blaine', 'Panther_lodging_Cora', 'Panther_lodging_Cornelia', 'Panther_lodging_Dianna', 'Panther_lodging_Edison', 'Panther_lodging_Edmond', 'Panther_lodging_Else', 'Panther_lodging_Fausto', 'Panther_lodging_Floyd', 'Panther_lodging_Gale', 'Panther_lodging_Hattie', 'Panther_lodging_Jana', 'Panther_lodging_Janice', 'Panther_lodging_Jorge', 'Panther_lodging_Kaitlin', 'Panther_lodging_Kara', 'Panther_lodging_Kirk', 'Panther_lodging_Marisol', 'Panther_lodging_Myrtle', 'Panther_lodging_Russell', 'Panther_lodging_Sonja', 'Panther_lodging_Teresita', 'Panther_lodging_Tracie', 'Panther_lodging_Willa', 'Panther_office_Antonette', 'Panther_office_Brent', 'Panther_office_Catherine', 'Panther_office_Christian', 'Panther_office_Christin', 'Panther_office_Clementine', 'Panther_office_Danica', 'Panther_office_Garth', 'Panther_office_Graham', 'Panther_office_Hannah', 'Panther_office_Jeane', 'Panther_office_Jesus', 'Panther_office_Karla', 'Panther_office_Kristen', 'Panther_office_Larry', 'Panther_office_Lauretta', 'Panther_office_Lavinia', 'Panther_office_Lois', 'Panther_office_Otto', 'Panther_office_Patti', 'Panther_office_Ruthie', 'Panther_office_Shauna', 'Panther_office_Taryn', 'Panther_office_Valarie', 'Panther_other_Bethel', 'Panther_other_Bettie', 'Panther_other_Lucina', 'Panther_other_Lula', 'Panther_other_Tyrone', 'Panther_parking_Adela', 'Panther_parking_Alaina', 'Panther_parking_Asia', 'Panther_parking_Charlene', 'Panther_parking_Jody', 'Panther_parking_Lorriane', 'Panther_parking_Mellissa', 'Panther_parking_Stanley', 'Panther_retail_Felix', 'Panther_retail_Gilbert', 'Panther_retail_Kristina', 'Panther_retail_Lester', 'Panther_retail_Rachel', 'Panther_retail_Romeo', 'Peacock_assembly_Dena', 'Peacock_assembly_Mamie', 'Peacock_assembly_Russ', 'Peacock_assembly_Socorro', 'Peacock_education_Anita', 'Peacock_education_Bianca', 'Peacock_education_Dustin', 'Peacock_education_Forest', 'Peacock_education_Gilberto', 'Peacock_education_Joshua', 'Peacock_education_Karl', 'Peacock_education_Karyn', 'Peacock_education_Lucie', 'Peacock_education_Lyle', 'Peacock_education_Ophelia', 'Peacock_education_Pasquale', 'Peacock_education_Patience', 'Peacock_education_Robbie', 'Peacock_education_Shelly', 'Peacock_education_Weldon', 'Peacock_education_Yolanda', 'Peacock_lodging_Chloe', 'Peacock_lodging_Francesca', 'Peacock_lodging_Jamaal', 'Peacock_lodging_James', 'Peacock_lodging_Lou', 'Peacock_lodging_Mathew', 'Peacock_lodging_Matthew', 'Peacock_lodging_Nova', 'Peacock_lodging_Sergio', 'Peacock_lodging_Terrie', 'Peacock_lodging_Wes', 'Peacock_office_Annie', 'Peacock_office_Burl', 'Peacock_office_Dara', 'Peacock_office_Effie', 'Peacock_office_Elton', 'Peacock_office_Glenn', 'Peacock_office_Jonathon', 'Peacock_office_Julian', 'Peacock_office_Major', 'Peacock_office_Naomi', 'Peacock_office_Norman', 'Peacock_public_Kelvin', 'Peacock_public_Linda', 'Rat_assembly_Adolfo', 'Rat_assembly_Aisha', 'Rat_assembly_Alex', 'Rat_assembly_Archie', 'Rat_assembly_Aubrey', 'Rat_assembly_Cristina', 'Rat_assembly_Damaris', 'Rat_assembly_Deandre', 'Rat_assembly_Don', 'Rat_assembly_Donny', 'Rat_assembly_Dovie', 'Rat_assembly_Erwin', 'Rat_assembly_Ezequiel', 'Rat_assembly_Francine', 'Rat_assembly_Frieda', 'Rat_assembly_Gerald', 'Rat_assembly_Gwen', 'Rat_assembly_Horace', 'Rat_assembly_Ida', 'Rat_assembly_Jamie', 'Rat_assembly_Jannie', 'Rat_assembly_Jennie', 'Rat_assembly_Kaitlyn', 'Rat_assembly_Karen', 'Rat_assembly_Kelley', 'Rat_assembly_Kenya', 'Rat_assembly_Kimberley', 'Rat_assembly_Kristine', 'Rat_assembly_Kristy', 'Rat_assembly_Lillie', 'Rat_assembly_Michel', 'Rat_assembly_Mirta', 'Rat_assembly_Monica', 'Rat_assembly_Myrna', 'Rat_assembly_Pam', 'Rat_assembly_Pauline', 'Rat_assembly_Rolland', 'Rat_assembly_Rosemarie', 'Rat_assembly_Ruth', 'Rat_assembly_Silvia', 'Rat_assembly_Suzanne', 'Rat_assembly_Teddy', 'Rat_assembly_Teodoro', 'Rat_assembly_Trent', 'Rat_assembly_Trudy', 'Rat_assembly_Victorina', 'Rat_assembly_Viola', 'Rat_education_Abigail', 'Rat_education_Adell', 'Rat_education_Adrian', 'Rat_education_Alfonso', 'Rat_education_Alfred', 'Rat_education_Alonzo', 'Rat_education_Alyson', 'Rat_education_Angelica', 'Rat_education_Barbara', 'Rat_education_Barney', 'Rat_education_Beverly', 'Rat_education_Brett', 'Rat_education_Bryant', 'Rat_education_Calvin', 'Rat_education_Candida', 'Rat_education_Carmela', 'Rat_education_Cecil', 'Rat_education_Cedric', 'Rat_education_Chance', 'Rat_education_Cinthia', 'Rat_education_Colin', 'Rat_education_Conrad', 'Rat_education_Dana', 'Rat_education_Dann', 'Rat_education_Davis', 'Rat_education_Deanna', 'Rat_education_Debra', 'Rat_education_Denise', 'Rat_education_Dianne', 'Rat_education_Donnell', 'Rat_education_Dreama', 'Rat_education_Earl', 'Rat_education_Earnest', 'Rat_education_Edmund', 'Rat_education_Eleonora', 'Rat_education_Elisa', 'Rat_education_Elsie', 'Rat_education_Esther', 'Rat_education_Everett', 'Rat_education_Fernando', 'Rat_education_Francisca', 'Rat_education_Gricelda', 'Rat_education_Guillermo', 'Rat_education_Humberto', 'Rat_education_Imelda', 'Rat_education_Irma', 'Rat_education_Jacob', 'Rat_education_Jame', 'Rat_education_Jeanne', 'Rat_education_Jena', 'Rat_education_Jesse', 'Rat_education_Kandice', 'Rat_education_Kathryn', 'Rat_education_Keith', 'Rat_education_Kelsey', 'Rat_education_Kristie', 'Rat_education_Lee', 'Rat_education_Leonardo', 'Rat_education_Lincoln', 'Rat_education_Liz', 'Rat_education_Lonnie', 'Rat_education_Lynn', 'Rat_education_Mac', 'Rat_education_Marcos', 'Rat_education_Marianna', 'Rat_education_Maricela', 'Rat_education_Marvin', 'Rat_education_Matt', 'Rat_education_Mavis', 'Rat_education_Meghan', 'Rat_education_Milagros', 'Rat_education_Moises', 'Rat_education_Mona', 'Rat_education_Morton', 'Rat_education_Mose', 'Rat_education_Nellie', 'Rat_education_Nona', 'Rat_education_Nydia', 'Rat_education_Pat', 'Rat_education_Patty', 'Rat_education_Paula', 'Rat_education_Penny', 'Rat_education_Renee', 'Rat_education_Robyn', 'Rat_education_Rogelio', 'Rat_education_Romana', 'Rat_education_Rosalyn', 'Rat_education_Roxanne', 'Rat_education_Royal', 'Rat_education_Salvatore', 'Rat_education_Shellie', 'Rat_education_Sherwood', 'Rat_education_Sina', 'Rat_education_Stuart', 'Rat_education_Susana', 'Rat_education_Tania', 'Rat_education_Terese', 'Rat_education_Theo', 'Rat_education_Tim', 'Rat_education_Tristan', 'Rat_education_Ulrike', 'Rat_education_Verna', 'Rat_education_Veronica', 'Rat_education_Vicky', 'Rat_education_Willie', 'Rat_education_Willy', 'Rat_education_Wilmer', 'Rat_education_Winnie', 'Rat_education_Yu', 'Rat_education_Zina', 'Rat_education_Zoe', 'Rat_health_Ann', 'Rat_health_Gaye', 'Rat_health_Guy', 'Rat_health_Mildred', 'Rat_health_Rosaria', 'Rat_health_Shane', 'Rat_health_Tanya', 'Rat_lodging_Ardell', 'Rat_lodging_Ben', 'Rat_lodging_Christine', 'Rat_lodging_Dwight', 'Rat_lodging_Jeannette', 'Rat_lodging_Lakisha', 'Rat_lodging_Lorenzo', 'Rat_lodging_Lucille', 'Rat_lodging_Marguerite', 'Rat_lodging_Marion', 'Rat_lodging_Ted', 'Rat_office_Adele', 'Rat_office_Annis', 'Rat_office_Arron', 'Rat_office_Ashlee', 'Rat_office_Avis', 'Rat_office_Chris', 'Rat_office_Colby', 'Rat_office_Craig', 'Rat_office_Jacinta', 'Rat_office_Jamal', 'Rat_office_Jeannie', 'Rat_office_Jessica', 'Rat_office_Jill', 'Rat_office_Kasey', 'Rat_office_Lora', 'Rat_office_Loyd', 'Rat_office_Mei', 'Rat_office_Olivia', 'Rat_office_Ramiro', 'Rat_office_Randy', 'Rat_office_Ronald', 'Rat_office_Rosemary', 'Rat_office_Sammy', 'Rat_office_Tracy', 'Rat_other_Al', 'Rat_other_Daphne', 'Rat_other_Hazel', 'Rat_other_Lan', 'Rat_parking_Ronnie', 'Rat_public_Alanna', 'Rat_public_Alexander', 'Rat_public_Allie', 'Rat_public_Allyson', 'Rat_public_Amber', 'Rat_public_Andrea', 'Rat_public_Angelina', 'Rat_public_Angle', 'Rat_public_Becky', 'Rat_public_Berry', 'Rat_public_Bronwyn', 'Rat_public_Carole', 'Rat_public_Caroline', 'Rat_public_Chrissy', 'Rat_public_Clyde', 'Rat_public_Corinne', 'Rat_public_Courtney', 'Rat_public_Dalia', 'Rat_public_Damon', 'Rat_public_Darren', 'Rat_public_Deidre', 'Rat_public_Desiree', 'Rat_public_Dexter', 'Rat_public_Duane', 'Rat_public_Elmira', 'Rat_public_Emilee', 'Rat_public_Faye', 'Rat_public_Fern', 'Rat_public_Frank', 'Rat_public_Frederick', 'Rat_public_Fredrick', 'Rat_public_Grover', 'Rat_public_Helena', 'Rat_public_Hortencia', 'Rat_public_Isabel', 'Rat_public_Jason', 'Rat_public_Joann', 'Rat_public_Johnna', 'Rat_public_Johnny', 'Rat_public_Josiah', 'Rat_public_Julieann', 'Rat_public_Kathleen', 'Rat_public_Kelle', 'Rat_public_Kelly', 'Rat_public_Kermit', 'Rat_public_Kimber', 'Rat_public_Laura', 'Rat_public_Laurie', 'Rat_public_Laverne', 'Rat_public_Lea', 'Rat_public_Leo', 'Rat_public_Leta', 'Rat_public_Lloyd', 'Rat_public_Loretta', 'Rat_public_Lynda', 'Rat_public_Mabel', 'Rat_public_Marcellus', 'Rat_public_Margart', 'Rat_public_Mark', 'Rat_public_Maxine', 'Rat_public_Michael', 'Rat_public_Michelle', 'Rat_public_Muriel', 'Rat_public_Nancy', 'Rat_public_Neil', 'Rat_public_Nell', 'Rat_public_Nelson', 'Rat_public_Norene', 'Rat_public_Percy', 'Rat_public_Ramon', 'Rat_public_Ramona', 'Rat_public_Roberta', 'Rat_public_Roma', 'Rat_public_Sade', 'Rat_public_Sana', 'Rat_public_Sean', 'Rat_public_Shanta', 'Rat_public_Sharonda', 'Rat_public_Sharron', 'Rat_public_Stacey', 'Rat_public_Stella', 'Rat_public_Sue', 'Rat_public_Tammara', 'Rat_public_Tamra', 'Rat_public_Tommy', 'Rat_public_Toya', 'Rat_public_Tricia', 'Rat_public_Ulysses', 'Rat_public_Vickie', 'Rat_public_Wilbur', 'Rat_public_Wilma', 'Rat_public_Yessenia', 'Rat_public_Yetta', 'Rat_religion_Kathy', 'Rat_retail_Jeffrey', 'Rat_warehouse_Breanna', 'Rat_warehouse_Doretta', 'Rat_warehouse_Eloisa', 'Rat_warehouse_Maegan', 'Rat_warehouse_Shari', 'Robin_assembly_Colleen', 'Robin_education_Audrea', 'Robin_education_Billi', 'Robin_education_Cecilia', 'Robin_education_Della', 'Robin_education_Derick', 'Robin_education_Derrick', 'Robin_education_Jasper', 'Robin_education_Julius', 'Robin_education_Karyl', 'Robin_education_Kiera', 'Robin_education_Kristopher', 'Robin_education_Lashandra', 'Robin_education_Leslie', 'Robin_education_Lizbeth', 'Robin_education_Madeline', 'Robin_education_Margarito', 'Robin_education_Megan', 'Robin_education_Mercedes', 'Robin_education_So', 'Robin_education_Takako', 'Robin_education_Terrance', 'Robin_education_Zenia', 'Robin_lodging_Armand', 'Robin_lodging_Celia', 'Robin_lodging_Donna', 'Robin_lodging_Dorthy', 'Robin_lodging_Elmer', 'Robin_lodging_Janie', 'Robin_lodging_Oliva', 'Robin_lodging_Phillip', 'Robin_lodging_Pricilla', 'Robin_lodging_Renea', 'Robin_office_Addie', 'Robin_office_Adolph', 'Robin_office_Antonina', 'Robin_office_Dina', 'Robin_office_Donald', 'Robin_office_Erma', 'Robin_office_Gayle', 'Robin_office_Lindsay', 'Robin_office_Maryann', 'Robin_office_Sammie', 'Robin_office_Saul', 'Robin_office_Serena', 'Robin_office_Shirlene', 'Robin_office_Soledad', 'Robin_office_Victor', 'Robin_office_Wai', 'Robin_office_Zelma', 'Robin_public_Cami', 'Robin_public_Carolina', 'Shrew_office_Doris', 'Shrew_office_Doug', 'Shrew_office_Ila', 'Shrew_office_Katherine', 'Shrew_office_Kenneth', 'Shrew_office_Lin', 'Shrew_office_Nora', 'Shrew_office_Rose', 'Shrew_office_Sherill', 'Swan_unknown_Allison', 'Swan_unknown_Andres', 'Swan_unknown_Bette', 'Swan_unknown_Christoper', 'Swan_unknown_Douglas', 'Swan_unknown_Esteban', 'Swan_unknown_Fabian', 'Swan_unknown_Ike', 'Swan_unknown_Isaiah', 'Swan_unknown_Jan', 'Swan_unknown_Jerold', 'Swan_unknown_Noelia', 'Swan_unknown_Raquel', 'Swan_unknown_Reyna', 'Swan_unknown_Rocco', 'Swan_unknown_Rudy', 'Swan_unknown_Tom', 'Swan_unknown_Valeria', 'Swan_unknown_Wendy', 'Wolf_assembly_Elaine', 'Wolf_assembly_Sallie', 'Wolf_education_Anisa', 'Wolf_education_Arnulfo', 'Wolf_education_Bobby', 'Wolf_education_Cheryl', 'Wolf_education_Clarissa', 'Wolf_education_Cody', 'Wolf_education_Dolores', 'Wolf_education_Dorris', 'Wolf_education_Eulalia', 'Wolf_education_Joaquin', 'Wolf_education_Josefa', 'Wolf_education_Katie', 'Wolf_education_Laurinda', 'Wolf_education_Loren', 'Wolf_education_Miguel', 'Wolf_education_Roderick', 'Wolf_education_Tammie', 'Wolf_education_Tori', 'Wolf_education_Ursula', 'Wolf_education_Vivian', 'Wolf_office_Bobbie', 'Wolf_office_Cary', 'Wolf_office_Darleen', 'Wolf_office_Elisabeth', 'Wolf_office_Emanuel', 'Wolf_office_Haydee', 'Wolf_office_Joana', 'Wolf_office_Nadia', 'Wolf_office_Rochelle', 'Wolf_public_Norma', 'Wolf_retail_Harriett', 'Wolf_retail_Marcella', 'Wolf_retail_Toshia', 'Wolf_science_Alfreda']

Out[16]:
levels lags params mean_absolute_error__weighted_average mean_absolute_error__average mean_absolute_error__pooling n_estimators max_depth min_data_in_leaf learning_rate feature_fraction max_bin reg_alpha reg_lambda
0 [Bear_assembly_Angel, Bear_assembly_Beatrice, ... [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14... {'n_estimators': 600, 'max_depth': 6, 'min_dat... 265.232555 265.232555 265.232555 600.0 6.0 188.0 0.159019 0.6 100.0 0.9 0.5
1 [Bear_assembly_Angel, Bear_assembly_Beatrice, ... [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14... {'n_estimators': 500, 'max_depth': 5, 'min_dat... 266.881982 266.881982 266.881982 500.0 5.0 227.0 0.163008 0.6 100.0 1.0 0.5
2 [Bear_assembly_Angel, Bear_assembly_Beatrice, ... [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14... {'n_estimators': 400, 'max_depth': 5, 'min_dat... 267.914068 267.914068 267.914068 400.0 5.0 437.0 0.132723 0.6 100.0 0.5 0.6

Backtesting on test data

In [17]:
# Backtesting on test set
# ==============================================================================
cv = TimeSeriesFold(
    initial_train_size = 608 + 60, # training + validation
    steps              = 7,
    refit              = False
)
metrics, predictions = backtesting_forecaster_multiseries(
                          forecaster        = forecaster,
                          series            = series_dict,
                          exog              = exog_dict,
                          cv                = cv,
                          metric            = 'mean_absolute_error',
                          verbose           = False,
                          show_progress     = True,
                          suppress_warnings = True
                      )

display(predictions.head())
display(metrics)
Bear_assembly_Angel Bear_assembly_Beatrice Bear_assembly_Danial Bear_assembly_Diana Bear_assembly_Genia Bear_assembly_Harry Bear_assembly_Jose Bear_assembly_Roxy Bear_assembly_Ruby Bear_education_Alfredo ... Wolf_office_Emanuel Wolf_office_Haydee Wolf_office_Joana Wolf_office_Nadia Wolf_office_Rochelle Wolf_public_Norma Wolf_retail_Harriett Wolf_retail_Marcella Wolf_retail_Toshia Wolf_science_Alfreda
2017-10-30 10592.607229 1032.696072 4056.629515 27.406207 7486.302404 439.881083 6879.087015 552.453275 1655.853841 38.709664 ... 361.337366 210.060744 321.831843 1673.708462 375.908451 3402.690970 1150.926131 161.710705 1500.964495 1743.394140
2017-10-31 10472.621539 1023.933848 3820.851897 42.918610 7007.321492 417.956425 6512.836993 485.334172 1482.183104 96.075704 ... 395.653458 188.468579 283.600247 1731.282896 410.224543 3771.871009 1238.155550 148.884883 1655.542029 1836.705983
2017-11-01 11252.961732 1165.450647 4115.225136 54.892865 7428.776862 405.362192 6823.843641 444.216678 1540.435683 124.838844 ... 442.972649 221.391340 309.578692 1770.081731 447.536830 4033.487825 1119.896803 189.380181 1754.014707 1872.483485
2017-11-02 10372.171087 1074.100181 4222.252035 57.811276 7436.383635 410.521974 6778.676617 473.724683 1708.998342 133.607039 ... 380.947298 191.304045 266.644451 1778.210035 431.739530 4007.392813 1096.840039 139.286319 1625.423470 1834.694876
2017-11-03 10291.017747 1235.674033 4151.233121 61.884943 7836.754401 410.308345 6481.923827 502.925866 1664.724787 113.980118 ... 383.486604 229.516185 307.146298 1797.058633 407.666263 3863.285633 1096.096599 209.491574 1610.787386 1819.461818

5 rows × 1578 columns

levels mean_absolute_error
0 Bear_assembly_Angel 1242.274464
1 Bear_assembly_Beatrice 209.284077
2 Bear_assembly_Danial 311.835036
3 Bear_assembly_Diana 23.336818
4 Bear_assembly_Genia 643.629645
... ... ...
1576 Wolf_retail_Toshia 536.740018
1577 Wolf_science_Alfreda 160.150092
1578 average 336.441315
1579 weighted_average 336.441315
1580 pooling 336.441315

1581 rows × 2 columns

In [18]:
# Aggregated metric for all buildings
# ==============================================================================
average_metric_all_buildings = metrics.query("levels == 'average'")["mean_absolute_error"].item()
errors_all_buildings = (
    predictions
    - data.pivot(
        columns="building_id",
        values="meter_reading",
    ).loc[predictions.index, predictions.columns]
)
sum_abs_errors_all_buildings = errors_all_buildings.abs().sum().sum()
sum_bias_all_buildings = errors_all_buildings.sum().sum()
print(f"Average mean absolute error for all buildings: {average_metric_all_buildings:.0f}")
print(f"Sum of absolute errors for all buildings (x 10,000): {sum_abs_errors_all_buildings/10000:.0f}")
print(f"Bias (x 10,000): {sum_bias_all_buildings/10000:.0f}")
Average mean absolute error for all buildings: 336
Sum of absolute errors for all buildings (x 10,000): 3345
Bias (x 10,000): 144
In [19]:
# Plot predictions vs real value for 2 random buildings
# ==============================================================================
rng = np.random.default_rng(14793)
n_buildings = 2
selected_buildings = rng.choice(data['building_id'].unique(), size=n_buildings, replace=False)

fig, axs = plt.subplots(n_buildings, 1, figsize=(7, 4.5), sharex=True)
axs = axs.flatten()

for i, building in enumerate(selected_buildings):
    data.query("building_id == @building").loc[predictions.index, 'meter_reading'].plot(ax=axs[i], label='test')
    predictions[building].plot(ax=axs[i], label='predictions')
    axs[i].set_title(f"Building {building}", fontsize=10)
    axs[i].set_xlabel("")
    axs[i].legend()

fig.tight_layout()
plt.show();

Feature selection

Feature selection is the process of selecting a subset of relevant features (variables, predictors) for use in model construction. Feature selection techniques are used for several reasons: to simplify models to make them easier to interpret, to reduce training time, to avoid the curse of dimensionality, to improve generalization by reducing overfitting (formally, variance reduction), and others.

Skforecast is compatible with the feature selection methods implemented in the scikit-learn library. There are several methods for feature selection, but the most common are:

  • Recursive feature elimination (RFE)

  • Sequential Feature Selection (SFS)

  • Feature selection based on threshold (SelectFromModel)

💡 Tip

Feature selection is a powerful tool for improving the performance of machine learning models. However, it is computationally expensive and can be time-consuming. Since the goal is to find the best subset of features, not the best model, it is not necessary to use the entire data set or a highly complex model. Instead, it is recommended to use a small subset of the data and a simple model. Once the best subset of features has been identified, the model can then be trained using the entire dataset and a more complex configuration.
In [20]:
# Feature selection (autoregressive and exog) with scikit-learn RFECV
# ==============================================================================
regressor = LGBMRegressor(n_estimators=100, max_depth=5, random_state=15926, verbose=-1)
selector = RFECV(estimator=regressor, step=1, cv=3, n_jobs=1)
selected_lags, selected_window_features, selected_exog = select_features_multiseries(
    forecaster      = forecaster,
    selector        = selector,
    series          = {k: v.loc[:end_validation,] for k, v in series_dict.items()},
    exog            = {k: v.loc[:end_validation, exog_features] for k, v in exog_dict.items()},
    select_only     = None,
    force_inclusion = None,
    subsample       = 0.2,
    random_state    = 123,
    verbose         = True,
)
/home/ubuntu/anaconda3/envs/skforecast_14_py12/lib/python3.12/site-packages/skforecast/recursive/_forecaster_recursive_multiseries.py:1077: MissingValuesWarning: NaNs detected in `X_train`. Some regressors do not allow NaN values during training. If you want to drop them, set `forecaster.dropna_from_series = True`. 
 You can suppress this warning using: warnings.simplefilter('ignore', category=MissingValuesWarning)
  warnings.warn(
Recursive feature elimination (RFECV)
-------------------------------------
Total number of records available: 959424
Total number of records used for feature selection: 191884
Number of features available: 83
    Lags            (n=62)
    Window features (n=3)
    Exog            (n=18)
Number of features selected: 56
    Lags            (n=39) : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 24, 27, 28, 30, 33, 35, 42, 43, 44, 47, 49, 50, 51, 54, 56, 60, 62]
    Window features (n=3) : ['roll_mean_7', 'roll_min_7', 'roll_max_7']
    Exog            (n=14) : ['sub_primaryspaceusage', 'timezone', 'sqm', 'airTemperature', 'cloudCoverage', 'dewTemperature', 'seaLvlPressure', 'windDirection', 'windSpeed', 'day_of_week_sin', 'day_of_week_cos', 'week_sin', 'week_cos', 'month_cos']
In [21]:
# Backtesting forecaster with selected features
# ==============================================================================
forecaster = ForecasterRecursiveMultiSeries(
                regressor          = LGBMRegressor(**best_params, random_state=8520, verbose=-1),
                lags               = selected_lags,
                window_features    = window_features,
                transformer_series = None,
                transformer_exog   = transformer_exog,
                fit_kwargs         = {'categorical_feature': categorical_features},
                encoding           = "ordinal"
            )
cv = TimeSeriesFold(
    initial_train_size = 608 + 60, # Entreanmiento + validación
    steps              = 7,
    refit              = False
)
metrics, predictions = backtesting_forecaster_multiseries(
                          forecaster        = forecaster,
                          series            = series_dict,
                          exog              = {k: v[exog_features] for k, v in exog_dict.items()},
                          cv                = cv,
                          metric            = 'mean_absolute_error',
                          verbose           = False,
                          show_progress     = True,
                          suppress_warnings = True
                      )

display(predictions.head())
display(metrics)
Bear_assembly_Angel Bear_assembly_Beatrice Bear_assembly_Danial Bear_assembly_Diana Bear_assembly_Genia Bear_assembly_Harry Bear_assembly_Jose Bear_assembly_Roxy Bear_assembly_Ruby Bear_education_Alfredo ... Wolf_office_Emanuel Wolf_office_Haydee Wolf_office_Joana Wolf_office_Nadia Wolf_office_Rochelle Wolf_public_Norma Wolf_retail_Harriett Wolf_retail_Marcella Wolf_retail_Toshia Wolf_science_Alfreda
2017-10-30 9626.737931 986.987146 4115.730889 28.355736 7698.815526 522.436206 6587.544940 545.660487 1725.385404 54.189837 ... 335.216997 170.143318 377.500042 1664.675830 395.541280 3411.830330 983.863534 166.663061 1319.386789 1776.367451
2017-10-31 10305.685639 989.391878 4008.593510 46.501090 7500.576527 501.195886 6627.187744 518.106346 1617.947843 102.646870 ... 350.736801 170.632769 347.939104 1778.520292 401.715195 3866.938793 1160.708035 153.027731 1703.401200 1762.124748
2017-11-01 11580.318132 1010.341555 4406.009428 53.277630 8518.103332 471.589541 7556.909480 539.607718 1651.703194 141.689303 ... 378.593324 185.523419 363.312809 1787.603859 412.526721 4146.799801 1105.229493 163.327361 1850.158677 1827.878745
2017-11-02 10989.821026 1052.330060 4468.847894 61.388218 8772.167019 466.063640 7321.600103 532.769989 1669.995231 162.281039 ... 353.901135 163.600344 343.479467 1696.069030 387.834532 3890.578581 1168.790868 150.456515 1629.042965 1816.280414
2017-11-03 10813.034611 1064.989588 4216.845915 72.815829 8476.272829 464.470173 6967.792821 610.780145 1706.882855 173.805203 ... 371.698302 193.065103 361.759688 1651.279474 405.204180 3806.558155 1110.789100 188.218057 1549.532986 1772.846319

5 rows × 1578 columns

levels mean_absolute_error
0 Bear_assembly_Angel 1072.303860
1 Bear_assembly_Beatrice 215.359461
2 Bear_assembly_Danial 313.559787
3 Bear_assembly_Diana 45.597033
4 Bear_assembly_Genia 579.565743
... ... ...
1576 Wolf_retail_Toshia 478.331909
1577 Wolf_science_Alfreda 192.194401
1578 average 344.573275
1579 weighted_average 344.573275
1580 pooling 344.573275

1581 rows × 2 columns

The number of features included in the model has been reduced without compromising the model's performance. This simplifies the model and speeds up training.

Clustering time series

The idea behind modeling multiple series at the same time is to be able to capture the main patterns that govern the series, thereby reducing the impact of the potential noise that each series may have. This means that series that behave similarly may benefit from being modeled together. One way to identify potential groups of series is to perform a clustering study prior to modeling. If clear groups are identified as a result of clustering, it is appropriate to model each of them separately.

Clustering is an unsupervised analysis technique that groups a set of observations into clusters that contain observations that are considered homogeneous, while observations in different clusters are considered heterogeneous. Algorithms that cluster time series can be divided into two groups: those that use a transformation to create features prior to clustering (feature-driven time series clustering), and those that work directly on the time series (elastic distance measures).

  • Feature-driven time series clustering: Features describing structural characteristics are extracted from each time series, then these features are fed into arbitrary clustering algorithms. This features are obtained by applying statistical operations that best capture the underlying characteristics: trend, seasonality, periodicity, serial correlation, skewness, kurtosis, chaos, nonlinearity, and self-similarity.

  • Elastic distance measures: This approach works directly on the time series, adjusting or "realigning" the series in comparison to each other. The best known of this family of measures is Dynamic Time Warping (DTW).

For a detailed example of how time series clustering can improve the forecasting models, see Global Forecasting Models: Comparative Analysis of Single and Multi-Series Forecasting Modeling.

Session information

In [22]:
import session_info
session_info.show(html=False)
-----
feature_engine      1.8.1
lightgbm            4.5.0
matplotlib          3.9.2
numpy               2.0.2
optuna              3.6.1
pandas              2.2.3
session_info        1.0.0
skforecast          0.14.0
sklearn             1.5.1
-----
IPython             8.27.0
jupyter_client      8.6.3
jupyter_core        5.7.2
notebook            6.4.12
-----
Python 3.12.5 | packaged by Anaconda, Inc. | (main, Sep 12 2024, 18:27:27) [GCC 11.2.0]
Linux-5.15.0-1071-aws-x86_64-with-glibc2.31
-----
Session information updated at 2024-11-06 21:49

Citation

How to cite this document

If you use this document or any part of it, please acknowledge the source, thank you!

Scalable Forecasting: modeling thousand time series with a single global model by Joaquín Amat Rodrigo and Javier Escobar Ortiz, available under a CC BY-NC-SA 4.0 at https://www.cienciadedatos.net/documentos/py59-scalable-forecasting-model.html

How to cite skforecast

If you use skforecast for a scientific publication, we would appreciate it if you cite the published software.

Zenodo:

Amat Rodrigo, Joaquin, & Escobar Ortiz, Javier. (2023). skforecast (v0.14.0). Zenodo. https://doi.org/10.5281/zenodo.8382788

APA:

Amat Rodrigo, J., & Escobar Ortiz, J. (2023). skforecast (Version 0.14.0) [Computer software]. https://doi.org/10.5281/zenodo.8382788

BibTeX:

@software{skforecast, author = {Amat Rodrigo, Joaquin and Escobar Ortiz, Javier}, title = {skforecast}, version = {0.14.0}, month = {11}, year = {2024}, license = {BSD-3-Clause}, url = {https://skforecast.org/}, doi = {10.5281/zenodo.8382788} }


Did you like the article? Your support is important

Website maintenance has high cost, your contribution will help me to continue generating free educational content. Many thanks! 😊


Creative Commons Licence
This work by Joaquín Amat Rodrigo and Javier Escobar Ortiz is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.