# 03_ML_Log_Target_PCF.ipynb
This experiment tests log-transformed PCF as the target.
The important difference from Notebook 02 is:
| Component | Experiment 01 | Experiment 02 | Experiment 03 |
|---|---|---|---|
| PCF target | Raw | Raw | log1p(PCF) |
| Product Weight | Raw | Log | Raw |
| Train/test split | Same | Same | Same |
| Models | 7 | 7 | 7 |
| Tuning | 3 | 3 | 3 |
| Evaluation | MAE/RMSE/R² | MAE/RMSE/R² | MAE/RMSE/R² on original PCF scale |
There is one crucial technical difference: the model will train on log1p(PCF), but we must convert predictions back to the original PCF scale before calculating MAE, RMSE and R².
Otherwise, Experiment 03 would not be directly comparable with Experiments 01 and 02.
Notebook 03 — 03_ML_Log_Target_PCF.ipynb
Cell 1 — Markdown
# BEACON Machine Learning — Log Target Experiment
## Experiment 03: Log-Transformed PCF Target
### Purpose
This experiment investigates whether logarithmic transformation of the
PCF target improves machine-learning prediction performance.
The PCF target is highly right-skewed. Therefore, `log1p(PCF)` is used
during model training to reduce the influence of extreme target values.
Product Weight remains in its original representation.
The experiment follows the same dataset, train-test split,
preprocessing strategy, regression models and evaluation procedure used
in Experiments 01 and 02.
### Experimental comparison
Experiment 01:
- Raw PCF target
- Raw Product Weight
Experiment 02:
- Raw PCF target
- Log-transformed Product Weight
Experiment 03:
- Log-transformed PCF target
- Raw Product Weight
### Evaluation
Models are trained using the logarithmic PCF target. Predictions are
converted back to the original PCF scale using `expm1()` before
calculating MAE, RMSE and R².
This allows the results to remain directly comparable with Experiments
01 and 02.
Cell 2 — Imports
# ============================================================
# 1. IMPORT LIBRARIES
# ============================================================
import numpy as np
import pandas as pd
from sklearn.model_selection import (
train_test_split,
GridSearchCV
)
from sklearn.preprocessing import OneHotEncoder
from sklearn.linear_model import (
LinearRegression,
ElasticNet,
BayesianRidge
)
from sklearn.ensemble import (
RandomForestRegressor,
ExtraTreesRegressor,
HistGradientBoostingRegressor
)
from sklearn.metrics import (
mean_absolute_error,
mean_squared_error,
r2_score
)
from xgboost import XGBRegressor
from category_encoders import TargetEncoder
import warnings
warnings.filterwarnings("ignore")
RANDOM_STATE = 42
Cell 3 — Load dataset
# ============================================================
# 2. LOAD ORIGINAL CARBON CATALOGUE DATA
# ============================================================
DATA_PATH = (
"/kaggle/input/datasets/"
"sajjadrahman56/product-level-of-pcf/"
"ProductLevel.csv"
)
df = pd.read_csv(
DATA_PATH,
encoding="latin1"
)
print("Original dataset shape:", df.shape)
Cell 4 — Create ML dataset
# ============================================================
# 3. CREATE ML DATASET
# ============================================================
df_ml = pd.DataFrame()
df_ml["Year"] = df["Year of reporting"]
df_ml["Company"] = df["Company"]
df_ml["Stage_Level_CO2e_Available"] = (
df["*Stage-level CO2e available"]
)
df_ml["Country"] = (
df["Country (where company is incorporated)"]
)
df_ml["Industry"] = (
df["Company's GICS Industry"]
)
df_ml["Product_Weight"] = (
df["Product weight (kg)"]
)
df_ml["PCF_Protocol"] = (
df["Protocol used for PCF"]
)
df_ml["PCF"] = (
df["Product's carbon footprint (PCF, kg CO2e)"]
)
print("ML dataset shape:", df_ml.shape)
display(df_ml.head())
Cell 5 — Clean PCF
# ============================================================
# 4. CLEAN PCF TARGET
# ============================================================
df_ml["PCF"] = pd.to_numeric(
df_ml["PCF"],
errors="coerce"
)
df_ml = df_ml[
df_ml["PCF"].notna()
].copy()
df_ml = df_ml[
df_ml["PCF"] >= 0
].copy()
print("Usable rows:", len(df_ml))
print("\nOriginal PCF summary:")
display(
df_ml["PCF"].describe()
)
print(
"\nOriginal PCF skewness:",
df_ml["PCF"].skew()
)
You should again have:
866
Cell 6 — Define X and raw y
# ============================================================
# 5. DEFINE FEATURES AND RAW TARGET
# ============================================================
FEATURES = [
"Year",
"Company",
"Stage_Level_CO2e_Available",
"Country",
"Industry",
"Product_Weight",
"PCF_Protocol"
]
TARGET = "PCF"
X = df_ml[
FEATURES
].copy()
y = df_ml[
TARGET
].copy()
print("X shape:", X.shape)
print("y shape:", y.shape)
Cell 7 — Train/test split
Exactly the same as the first two notebooks.
# ============================================================
# 6. TRAIN / TEST SPLIT
# ============================================================
X_train, X_test, y_train_raw, y_test_raw = train_test_split(
X,
y,
test_size=0.20,
random_state=RANDOM_STATE
)
print("Training rows:", len(X_train))
print("Testing rows :", len(X_test))
Expected:
Training rows: 692
Testing rows : 174
Cell 8 — Create safe copies
# ============================================================
# 7. CREATE SAFE COPIES
# ============================================================
X_train = X_train.copy()
X_test = X_test.copy()
y_train_raw = y_train_raw.copy()
y_test_raw = y_test_raw.copy()
Cell 9 — Create log target
This is the major change in Notebook 03.
# ============================================================
# 8. LOG-TRANSFORM PCF TARGET
#
# log1p(x) = log(1 + x)
# ============================================================
y_train = np.log1p(
y_train_raw
)
y_test = np.log1p(
y_test_raw
)
print("Raw PCF skewness:")
print(
y_train_raw.skew()
)
print(
"\nLog-transformed PCF skewness:"
)
print(
y_train.skew()
)
You should see something similar to:
Raw PCF skewness: ~15+
Log-transformed PCF skewness: much lower
Cell 10 — Keep original y for evaluation
This is very important.
# ============================================================
# 9. STORE ORIGINAL TARGET FOR FINAL EVALUATION
# ============================================================
y_test_original = y_test_raw.copy()
print(
"Training target used by models: log1p(PCF)"
)
print(
"Evaluation target: original PCF"
)
Cell 11 — Country → Region
Same as the previous notebooks.
# ============================================================
# 10. COUNTRY → REGION
# ============================================================
country_to_region = {
# North America
"USA": "North America",
"Canada": "North America",
# Europe
"Germany": "Europe",
"Netherlands": "Europe",
"United Kingdom": "Europe",
"Switzerland": "Europe",
"Sweden": "Europe",
"Finland": "Europe",
"Italy": "Europe",
"France": "Europe",
"Spain": "Europe",
"Belgium": "Europe",
"Ireland": "Europe",
"Luxembourg": "Europe",
"Lithuania": "Europe",
"Greece": "Europe",
# East Asia
"Japan": "East Asia",
"Taiwan": "East Asia",
"South Korea": "East Asia",
"China": "East Asia",
# South Asia
"India": "South Asia",
# Southeast Asia
"Malaysia": "Southeast Asia",
"Indonesia": "Southeast Asia",
# South America
"Brazil": "South America",
"Chile": "South America",
"Colombia": "South America",
# Africa
"South Africa": "Africa",
# Oceania
"Australia": "Oceania"
}
X_train["Region"] = X_train[
"Country"
].map(country_to_region)
X_test["Region"] = X_test[
"Country"
].map(country_to_region)
X_train["Region"] = (
X_train["Region"].fillna("Other")
)
X_test["Region"] = (
X_test["Region"].fillna("Other")
)
X_train.drop(
columns="Country",
inplace=True
)
X_test.drop(
columns="Country",
inplace=True
)
Cell 12 — Rare Industry
# ============================================================
# 11. RARE INDUSTRY → OTHER
# ============================================================
industry_counts = X_train[
"Industry"
].value_counts()
rare_industries = industry_counts[
industry_counts < 10
].index
X_train["Industry"] = X_train[
"Industry"
].replace(
rare_industries,
"Other"
)
X_test["Industry"] = X_test[
"Industry"
].replace(
rare_industries,
"Other"
)
Cell 13 — PCF Protocol
# ============================================================
# 12. PCF PROTOCOL → TOP 5 + OTHER
# ============================================================
top_protocols = [
"ISO",
"Not reported",
"GHGP",
"PAS2050",
"TRACI 2.1"
]
X_train["PCF_Protocol"] = X_train[
"PCF_Protocol"
].where(
X_train["PCF_Protocol"].isin(
top_protocols
),
"Other"
)
X_test["PCF_Protocol"] = X_test[
"PCF_Protocol"
].where(
X_test["PCF_Protocol"].isin(
top_protocols
),
"Other"
)
Cell 14 — Stage-level CO2e
# ============================================================
# 13. STAGE-LEVEL CO2e → BINARY
# ============================================================
binary_map = {
"No": 0,
"Yes": 1
}
X_train[
"Stage_Level_CO2e_Available"
] = (
X_train[
"Stage_Level_CO2e_Available"
]
.map(binary_map)
.fillna(0)
)
X_test[
"Stage_Level_CO2e_Available"
] = (
X_test[
"Stage_Level_CO2e_Available"
]
.map(binary_map)
.fillna(0)
)
Cell 15 — Product Weight winsorisation
Here we return to the raw Product Weight representation.
# ============================================================
# 14. PRODUCT WEIGHT — WINSORISATION
#
# No logarithmic transformation is applied to Product Weight
# in Experiment 03.
# ============================================================
Q1 = X_train[
"Product_Weight"
].quantile(0.25)
Q3 = X_train[
"Product_Weight"
].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
print("Winsorisation bounds")
print("Lower:", lower)
print("Upper:", upper)
X_train[
"Product_Weight"
] = X_train[
"Product_Weight"
].clip(
lower=lower,
upper=upper
)
X_test[
"Product_Weight"
] = X_test[
"Product_Weight"
].clip(
lower=lower,
upper=upper
)
Cell 16 — Company target encoding
# ============================================================
# 15. COMPANY → TARGET ENCODING
#
# IMPORTANT:
# The encoder is fitted using y_train, which is the log PCF
# target in this experiment.
# ============================================================
te = TargetEncoder(
cols=["Company"],
min_samples_leaf=20,
smoothing=10
)
X_train = te.fit_transform(
X_train,
y_train
)
X_test = te.transform(
X_test
)
Cell 17 — One-hot encoding
# ============================================================
# 16. ONE-HOT ENCODING
# ============================================================
categorical_features = [
"Industry",
"PCF_Protocol",
"Region"
]
encoder = OneHotEncoder(
drop="first",
handle_unknown="ignore",
sparse_output=False
)
encoded_train = encoder.fit_transform(
X_train[
categorical_features
]
)
encoded_test = encoder.transform(
X_test[
categorical_features
]
)
encoded_train_df = pd.DataFrame(
encoded_train,
columns=encoder.get_feature_names_out(
categorical_features
),
index=X_train.index
)
encoded_test_df = pd.DataFrame(
encoded_test,
columns=encoder.get_feature_names_out(
categorical_features
),
index=X_test.index
)
X_train = pd.concat(
[
X_train.drop(
columns=categorical_features
),
encoded_train_df
],
axis=1
)
X_test = pd.concat(
[
X_test.drop(
columns=categorical_features
),
encoded_test_df
],
axis=1
)
Cell 18 — Final feature check
# ============================================================
# 17. FINAL FEATURE CHECK
# ============================================================
print(
"Final X_train shape:",
X_train.shape
)
print(
"Final X_test shape:",
X_test.shape
)
print(
"\nProduct Weight present:",
"Product_Weight" in X_train.columns
)
print(
"Log Product Weight present:",
"Log_Product_Weight" in X_train.columns
)
print(
"Target transformation: log1p(PCF)"
)
print(
"\nAll features numerical:",
X_train.select_dtypes(
exclude="number"
).empty
)
Expected:
Product Weight present: True
Log Product Weight present: False
Target transformation: log1p(PCF)
All features numerical: True
Cell 19 — Seven baseline models
# ============================================================
# 18. DEFINE BASELINE ML MODELS
# ============================================================
models = {
"Linear Regression":
LinearRegression(),
"ElasticNet":
ElasticNet(
random_state=RANDOM_STATE
),
"Bayesian Ridge":
BayesianRidge(),
"Random Forest":
RandomForestRegressor(
random_state=RANDOM_STATE
),
"Extra Trees":
ExtraTreesRegressor(
random_state=RANDOM_STATE
),
"HistGradientBoosting":
HistGradientBoostingRegressor(
random_state=RANDOM_STATE
),
"XGBoost":
XGBRegressor(
random_state=RANDOM_STATE
)
}
Cell 20 — Train and evaluate correctly
This cell is different from Notebooks 01 and 02.
The model predicts log-PCF.
We convert it back:
log-PCF prediction → expm1() → PCF prediction
Then evaluate against original PCF.
# ============================================================
# 19. BASELINE MODEL EVALUATION
#
# Models are trained on log(PCF).
# Predictions are transformed back to the original PCF scale
# before calculating MAE, RMSE and R².
# ============================================================
baseline_results = []
for name, model in models.items():
print(
f"Training: {name}"
)
# Train using log-transformed target
model.fit(
X_train,
y_train
)
# Predict log-PCF
y_pred_log = model.predict(
X_test
)
# Convert predictions back to original PCF scale
y_pred = np.expm1(
y_pred_log
)
# Prevent tiny numerical negative values
y_pred = np.maximum(
y_pred,
0
)
# Evaluate on original PCF scale
mae = mean_absolute_error(
y_test_original,
y_pred
)
rmse = np.sqrt(
mean_squared_error(
y_test_original,
y_pred
)
)
r2 = r2_score(
y_test_original,
y_pred
)
baseline_results.append({
"Model": name,
"MAE": mae,
"RMSE": rmse,
"R²": r2
})
Cell 21 — Baseline results
# ============================================================
# 20. BASELINE RESULTS
# ============================================================
baseline_results_df = pd.DataFrame(
baseline_results
)
baseline_results_df = (
baseline_results_df
.sort_values(
"R²",
ascending=False
)
.reset_index(drop=True)
)
display(
baseline_results_df.style.format({
"MAE": "{:,.2f}",
"RMSE": "{:,.2f}",
"R²": "{:.4f}"
})
)
Cell 22 — Models for tuning
Same three as the other experiments.
# ============================================================
# 21. SELECT MODELS FOR HYPERPARAMETER TUNING
# ============================================================
tuning_models = {
"Random Forest":
RandomForestRegressor(
random_state=RANDOM_STATE
),
"Extra Trees":
ExtraTreesRegressor(
random_state=RANDOM_STATE
),
"XGBoost":
XGBRegressor(
random_state=RANDOM_STATE
)
}
print(
"Models selected for tuning:"
)
for name in tuning_models:
print("-", name)
Cell 23 — RF GridSearch
# ============================================================
# 22. RANDOM FOREST — GRID SEARCH
# ============================================================
rf_param_grid = {
"n_estimators": [
100,
200,
300
],
"max_depth": [
None,
10,
20
],
"min_samples_split": [
2,
5
],
"min_samples_leaf": [
1,
2
]
}
rf_grid = GridSearchCV(
estimator=tuning_models[
"Random Forest"
],
param_grid=rf_param_grid,
scoring="neg_root_mean_squared_error",
cv=5,
n_jobs=-1,
verbose=1
)
rf_grid.fit(
X_train,
y_train
)
print(
"\nBest Random Forest parameters:"
)
print(
rf_grid.best_params_
)
Cell 24 — Tuned RF evaluation
Again, convert back to original PCF.
# ============================================================
# 23. EVALUATE TUNED RANDOM FOREST
# ============================================================
best_rf = rf_grid.best_estimator_
rf_pred_log = best_rf.predict(
X_test
)
rf_pred = np.expm1(
rf_pred_log
)
rf_pred = np.maximum(
rf_pred,
0
)
rf_mae = mean_absolute_error(
y_test_original,
rf_pred
)
rf_rmse = np.sqrt(
mean_squared_error(
y_test_original,
rf_pred
)
)
rf_r2 = r2_score(
y_test_original,
rf_pred
)
print("Random Forest")
print("MAE :", rf_mae)
print("RMSE:", rf_rmse)
print("R² :", rf_r2)
Cell 25 — Extra Trees GridSearch
# ============================================================
# 24. EXTRA TREES — GRID SEARCH
# ============================================================
et_param_grid = {
"n_estimators": [
100,
200,
300
],
"max_depth": [
None,
10,
20
],
"min_samples_split": [
2,
5
],
"min_samples_leaf": [
1,
2
]
}
et_grid = GridSearchCV(
estimator=tuning_models[
"Extra Trees"
],
param_grid=et_param_grid,
scoring="neg_root_mean_squared_error",
cv=5,
n_jobs=-1,
verbose=1
)
et_grid.fit(
X_train,
y_train
)
print(
"\nBest Extra Trees parameters:"
)
print(
et_grid.best_params_
)
Cell 26 — Tuned Extra Trees evaluation
# ============================================================
# 25. EVALUATE TUNED EXTRA TREES
# ============================================================
best_et = et_grid.best_estimator_
et_pred_log = best_et.predict(
X_test
)
et_pred = np.expm1(
et_pred_log
)
et_pred = np.maximum(
et_pred,
0
)
et_mae = mean_absolute_error(
y_test_original,
et_pred
)
et_rmse = np.sqrt(
mean_squared_error(
y_test_original,
et_pred
)
)
et_r2 = r2_score(
y_test_original,
et_pred
)
print("Extra Trees")
print("MAE :", et_mae)
print("RMSE:", et_rmse)
print("R² :", et_r2)
Cell 27 — XGBoost GridSearch
# ============================================================
# 26. XGBOOST — GRID SEARCH
# ============================================================
xgb_param_grid = {
"n_estimators": [
100,
200,
300
],
"max_depth": [
3,
5,
7
],
"learning_rate": [
0.01,
0.05,
0.1
],
"subsample": [
0.8,
1.0
],
"colsample_bytree": [
0.8,
1.0
]
}
xgb_grid = GridSearchCV(
estimator=tuning_models[
"XGBoost"
],
param_grid=xgb_param_grid,
scoring="neg_root_mean_squared_error",
cv=5,
n_jobs=-1,
verbose=1
)
xgb_grid.fit(
X_train,
y_train
)
print(
"\nBest XGBoost parameters:"
)
print(
xgb_grid.best_params_
)
Cell 28 — Tuned XGBoost evaluation
# ============================================================
# 27. EVALUATE TUNED XGBOOST
# ============================================================
best_xgb = xgb_grid.best_estimator_
xgb_pred_log = best_xgb.predict(
X_test
)
xgb_pred = np.expm1(
xgb_pred_log
)
xgb_pred = np.maximum(
xgb_pred,
0
)
xgb_mae = mean_absolute_error(
y_test_original,
xgb_pred
)
xgb_rmse = np.sqrt(
mean_squared_error(
y_test_original,
xgb_pred
)
)
xgb_r2 = r2_score(
y_test_original,
xgb_pred
)
print("XGBoost")
print("MAE :", xgb_mae)
print("RMSE:", xgb_rmse)
print("R² :", xgb_r2)
Cell 29 — Tuned comparison
# ============================================================
# 28. TUNED MODEL COMPARISON
# ============================================================
tuned_results_df = pd.DataFrame([
{
"Model": "Random Forest",
"MAE": rf_mae,
"RMSE": rf_rmse,
"R²": rf_r2
},
{
"Model": "Extra Trees",
"MAE": et_mae,
"RMSE": et_rmse,
"R²": et_r2
},
{
"Model": "XGBoost",
"MAE": xgb_mae,
"RMSE": xgb_rmse,
"R²": xgb_r2
}
])
tuned_results_df = (
tuned_results_df
.sort_values(
"R²",
ascending=False
)
.reset_index(drop=True)
)
display(
tuned_results_df.style.format({
"MAE": "{:,.2f}",
"RMSE": "{:,.2f}",
"R²": "{:.4f}"
})
)
Cell 30 — Baseline vs tuned
# ============================================================
# 29. BASELINE VS TUNED
# ============================================================
baseline_selected = (
baseline_results_df[
baseline_results_df["Model"].isin([
"Random Forest",
"Extra Trees",
"XGBoost"
])
][
["Model", "MAE", "RMSE", "R²"]
]
.copy()
)
baseline_selected["Stage"] = "Baseline"
tuned_comparison = tuned_results_df.copy()
tuned_comparison["Stage"] = "Tuned"
baseline_vs_tuned = pd.concat(
[
baseline_selected,
tuned_comparison
],
ignore_index=True
)
display(
baseline_vs_tuned.sort_values(
["Model", "Stage"]
)
)
Cell 31 — Save baseline results
# ============================================================
# 30. SAVE BASELINE RESULTS
# ============================================================
baseline_results_df.to_csv(
"baseline_log_target_pcf_results.csv",
index=False
)
print(
"Saved baseline results."
)
Cell 32 — Save tuned results
# ============================================================
# 31. SAVE TUNED RESULTS
# ============================================================
tuned_results_df.to_csv(
"tuned_log_target_pcf_results.csv",
index=False
)
print(
"Saved tuned results."
)
Cell 33 — Save hyperparameters
# ============================================================
# 32. SAVE BEST HYPERPARAMETERS
# ============================================================
best_parameters_df = pd.DataFrame({
"Model": [
"Random Forest",
"Extra Trees",
"XGBoost"
],
"Best Parameters": [
rf_grid.best_params_,
et_grid.best_params_,
xgb_grid.best_params_
]
})
display(
best_parameters_df
)
best_parameters_df.to_csv(
"tuned_log_target_pcf_best_parameters.csv",
index=False
)
Cell 34 — Experiment summary
# ============================================================
# 33. EXPERIMENT SUMMARY
# ============================================================
print("=" * 80)
print("EXPERIMENT 03 — LOG TARGET PCF")
print("=" * 80)
print(
"\nDataset rows:",
len(df_ml)
)
print(
"Training rows:",
len(X_train)
)
print(
"Testing rows:",
len(X_test)
)
print(
"\nTarget transformation: log1p(PCF)"
)
print(
"Product Weight transformation: None"
)
best_baseline = baseline_results_df.iloc[0]
print(
"\nBest baseline model by R²:"
)
print(
best_baseline["Model"],
"→ R² =",
round(
best_baseline["R²"],
4
)
)
best_tuned = tuned_results_df.iloc[0]
print(
"\nBest tuned model by R²:"
)
print(
best_tuned["Model"],
"→ R² =",
round(
best_tuned["R²"],
4
)
)
print("\n" + "=" * 80)
Cell 35 — Important experiment note
Don't fill this with guesses. Run the notebook first, then insert the actual values.
## Experiment Summary
This experiment investigated whether logarithmic transformation of the
PCF target improved prediction performance. The transformation was
motivated by the substantial positive skewness of the PCF distribution.
The same 866 observations, 80/20 train-test split, random state,
feature preprocessing, regression models and evaluation metrics used in
Experiments 01 and 02 were retained to support a controlled comparison.
The PCF target was transformed using `log1p(PCF)` before model training.
Product Weight remained in its original representation, subject only to
the established winsorisation procedure. Model predictions were
converted back to the original PCF scale using `expm1()` before
calculating MAE, RMSE and R².
The strongest baseline model achieved an R² of [XX], while the best
tuned model achieved an R² of [XX].
Compared with the raw-target experiment, the log-target transformation
produced [an improvement / a reduction / a mixed effect] in predictive
performance. These results are used to determine whether target
transformation should be retained in the final machine-learning
configuration.
⚠️ One critical point about Notebook 03
There is a subtle issue we need to handle correctly.
In Notebook 03:
y_train = np.log1p(y_train_raw)
means Company target encoding is also being fitted against log-PCF, because:
te.fit_transform(X_train, y_train)
uses the transformed target.
That is logically consistent with a log-target experiment, but it means the experiment changes both the modelling target and the target encoding scale.
I do not want you to silently change this now, because your previous notebooks already use target encoding and we want methodological consistency.
However, when we write the dissertation methodology, we should explicitly document that the target encoding is fitted using the training target representation used by each experiment.
Your three notebooks are now cleanly defined
01_ML_Baseline_Raw_PCF.ipynb
Raw PCF
Raw Product Weight
↓
02_ML_Log_Product_Weight.ipynb
Raw PCF
Log Product Weight
↓
03_ML_Log_Target_PCF.ipynb
Log PCF
Raw Product Weight
Run Notebook 03 through Cell 21 first and send me the baseline_results_df. We can then check the baseline before you spend time running all three GridSearchCV experiments.
Top comments (0)