<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: TS</title>
    <description>The latest articles on DEV Community by TS (@ts2025).</description>
    <link>https://dev.to/ts2025</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3538468%2Fe9119dc4-88ac-4c84-9ea8-1ad61e981ec2.png</url>
      <title>DEV Community: TS</title>
      <link>https://dev.to/ts2025</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ts2025"/>
    <language>en</language>
    <item>
      <title>Cell-15-FINAL-PREDICTION-ANALYSIS</title>
      <dc:creator>TS</dc:creator>
      <pubDate>Mon, 14 Sep 2026 11:37:25 +0000</pubDate>
      <link>https://dev.to/ts2025/cell-15-final-prediction-analysis-2hb6</link>
      <guid>https://dev.to/ts2025/cell-15-final-prediction-analysis-2hb6</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ============================================================
# FINAL PREDICTION ANALYSIS
# ============================================================
&lt;/span&gt;
&lt;span class="n"&gt;final_predictions_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Actual_PCF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;y_test_final_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Predicted_PCF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;y_test_pred_raw&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# Residual = Actual - Predicted
&lt;/span&gt;&lt;span class="n"&gt;final_predictions_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Residual&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;final_predictions_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Actual_PCF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;final_predictions_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Predicted_PCF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Absolute error
&lt;/span&gt;&lt;span class="n"&gt;final_predictions_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Absolute_Error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;final_predictions_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Residual&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Absolute percentage error
&lt;/span&gt;&lt;span class="n"&gt;final_predictions_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Absolute_Percentage_Error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;final_predictions_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Absolute_Error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;maximum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;final_predictions_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Actual_PCF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="mf"&gt;1e-8&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;

&lt;span class="nf"&gt;display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;final_predictions_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;head&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Final Prediction Analysis — Purpose
&lt;/h3&gt;

&lt;p&gt;This cell creates a &lt;strong&gt;row-by-row comparison between the actual PCF and the final model's predicted PCF&lt;/strong&gt; on the independent test set.&lt;/p&gt;

&lt;p&gt;This is different from the previous evaluation cell where you calculated overall:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MAE&lt;/li&gt;
&lt;li&gt;RMSE&lt;/li&gt;
&lt;li&gt;R²&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here, you are going deeper and asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“For each individual test observation, how close was the prediction to the actual PCF?”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is useful for &lt;strong&gt;error analysis and interpretation&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Create the prediction DataFrame
&lt;/h3&gt;



&lt;p&gt;```python id="v6p2k1"&lt;br&gt;
final_predictions_df = pd.DataFrame({&lt;br&gt;
    "Actual_PCF": y_test_final_raw,&lt;br&gt;
    "Predicted_PCF": y_test_pred_raw&lt;br&gt;
})&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This creates a DataFrame with two columns:



```text id="9n2h7x"
Actual_PCF
Predicted_PCF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;Actual_PCF&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Contains the real PCF values from the independent test set.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;Predicted_PCF&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Contains the PCF values predicted by your final tuned XGBoost model.&lt;/p&gt;

&lt;p&gt;So each row represents:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="j6s8q3"&lt;br&gt;
One test observation&lt;br&gt;
      ↓&lt;br&gt;
Actual PCF vs Predicted PCF&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


For example, your first row is:



```text id="8c4v1z"
Actual     = 2300.00
Predicted  = 1740.69
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  2. Calculate residual
&lt;/h1&gt;



&lt;p&gt;```python id="m3r7k9"&lt;br&gt;
final_predictions_df["Residual"] = (&lt;br&gt;
    final_predictions_df["Actual_PCF"]&lt;br&gt;
    - final_predictions_df["Predicted_PCF"]&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


The residual is:

**Residual = Actual − Predicted**

This tells you the **direction of the prediction error**.

### If residual is positive



```text id="8w1h5d"
Actual &amp;gt; Predicted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The model &lt;strong&gt;underestimated&lt;/strong&gt; the PCF.&lt;/p&gt;
&lt;h3&gt;
  
  
  If residual is negative
&lt;/h3&gt;



&lt;p&gt;```text id="2p7k4m"&lt;br&gt;
Actual &amp;lt; Predicted&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


The model **overestimated** the PCF.

---

### Example: Row 0



```text id="y7k2q1"
Actual     = 2300.00
Predicted  = 1740.69

Residual = 2300 - 1740.69
         = +559.31
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Positive residual → the model &lt;strong&gt;underpredicted&lt;/strong&gt; by approximately 559 kg CO₂e.&lt;/p&gt;


&lt;h3&gt;
  
  
  Example: Row 1
&lt;/h3&gt;



&lt;p&gt;```text id="q8d3m6"&lt;br&gt;
Actual     = 580.00&lt;br&gt;
Predicted  = 688.73&lt;/p&gt;

&lt;p&gt;Residual = 580 - 688.73&lt;br&gt;
         = -108.73&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Negative residual → the model **overpredicted** by approximately 109 kg CO₂e.

---

# 3. Calculate absolute error



```python id="r5n2v8"
final_predictions_df["Absolute_Error"] = (
    final_predictions_df["Residual"].abs()
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;.abs()&lt;/code&gt; takes the absolute value.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="f1s9k4"&lt;br&gt;
Residual = +559 → Absolute Error = 559&lt;br&gt;
Residual = -109 → Absolute Error = 109&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


The sign is removed.

Why?

Because absolute error is concerned only with:

&amp;gt; **How large was the error?**

not whether the model over- or underpredicted.

This is closely related to MAE.

---

# 4. Calculate Absolute Percentage Error



```python id="c7m4x1"
final_predictions_df["Absolute_Percentage_Error"] = (
    final_predictions_df["Absolute_Error"]
    / np.maximum(
        final_predictions_df["Actual_PCF"],
        1e-8
    )
) * 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This calculates the &lt;strong&gt;Absolute Percentage Error (APE)&lt;/strong&gt; for each observation.&lt;/p&gt;

&lt;p&gt;The formula is:&lt;/p&gt;

&lt;p&gt;$$&lt;br&gt;
APE =&lt;br&gt;
\frac{|Actual-Predicted|}&lt;br&gt;
{Actual}&lt;br&gt;
\times100&lt;br&gt;
$$&lt;/p&gt;

&lt;p&gt;So it tells you:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“The prediction error represents what percentage of the actual PCF?”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h3&gt;
  
  
  Example: Row 0
&lt;/h3&gt;

&lt;p&gt;Actual:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="x2q7m4"&lt;br&gt;
2300&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Absolute error:



```text id="r3k8p1"
559.31
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Therefore:&lt;/p&gt;

&lt;p&gt;$$&lt;br&gt;
\frac{559.31}{2300}\times100&lt;br&gt;
\approx24.32\%&lt;br&gt;
$$&lt;/p&gt;

&lt;p&gt;Your table shows:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="s5j1q8"&lt;br&gt;
24.317823%&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


So the prediction is approximately **24.3% away from the actual value**.

---

# 5. Why `np.maximum(..., 1e-8)`?

This part:



```python id="h2v6c9"
np.maximum(
    final_predictions_df["Actual_PCF"],
    1e-8
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;protects against division by zero.&lt;/p&gt;

&lt;p&gt;Suppose:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="p4n7x2"&lt;br&gt;
Actual PCF = 0&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Then:



```text id="k9m3s6"
Absolute error / 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;would create a division-by-zero problem.&lt;/p&gt;

&lt;p&gt;So the denominator is forced to be at least:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="e1r8w5"&lt;br&gt;
0.00000001&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


### Important viva point

This does **not** make percentage error well-defined for zero actual values.

It only prevents a numerical division-by-zero error.

If the dataset contains actual PCF values of zero, percentage-based metrics should be interpreted carefully.

---

# 6. Display first 10 observations



```python id="u4q8z2"
display(
    final_predictions_df.head(10)
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;.head(10)&lt;/code&gt; displays the first 10 rows.&lt;/p&gt;

&lt;p&gt;It lets you inspect individual predictions without printing the entire test dataset.&lt;/p&gt;


&lt;h1&gt;
  
  
  Understanding your actual results
&lt;/h1&gt;

&lt;p&gt;Your first 10 observations show:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Row&lt;/th&gt;
&lt;th&gt;Actual&lt;/th&gt;
&lt;th&gt;Predicted&lt;/th&gt;
&lt;th&gt;Residual&lt;/th&gt;
&lt;th&gt;Absolute Error&lt;/th&gt;
&lt;th&gt;APE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2300.00&lt;/td&gt;
&lt;td&gt;1740.69&lt;/td&gt;
&lt;td&gt;+559.31&lt;/td&gt;
&lt;td&gt;559.31&lt;/td&gt;
&lt;td&gt;24.32%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;580.00&lt;/td&gt;
&lt;td&gt;688.73&lt;/td&gt;
&lt;td&gt;−108.73&lt;/td&gt;
&lt;td&gt;108.73&lt;/td&gt;
&lt;td&gt;18.75%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;58.00&lt;/td&gt;
&lt;td&gt;144.31&lt;/td&gt;
&lt;td&gt;−86.31&lt;/td&gt;
&lt;td&gt;86.31&lt;/td&gt;
&lt;td&gt;148.81%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2219.00&lt;/td&gt;
&lt;td&gt;1447.95&lt;/td&gt;
&lt;td&gt;+771.05&lt;/td&gt;
&lt;td&gt;771.05&lt;/td&gt;
&lt;td&gt;34.75%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;15.00&lt;/td&gt;
&lt;td&gt;16.25&lt;/td&gt;
&lt;td&gt;−1.25&lt;/td&gt;
&lt;td&gt;1.25&lt;/td&gt;
&lt;td&gt;8.33%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;66.16&lt;/td&gt;
&lt;td&gt;96.84&lt;/td&gt;
&lt;td&gt;−30.68&lt;/td&gt;
&lt;td&gt;30.68&lt;/td&gt;
&lt;td&gt;46.37%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;17.10&lt;/td&gt;
&lt;td&gt;18.84&lt;/td&gt;
&lt;td&gt;−1.74&lt;/td&gt;
&lt;td&gt;1.74&lt;/td&gt;
&lt;td&gt;10.15%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;584.00&lt;/td&gt;
&lt;td&gt;655.04&lt;/td&gt;
&lt;td&gt;−71.04&lt;/td&gt;
&lt;td&gt;71.04&lt;/td&gt;
&lt;td&gt;12.16%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;6240.00&lt;/td&gt;
&lt;td&gt;3961.29&lt;/td&gt;
&lt;td&gt;+2278.71&lt;/td&gt;
&lt;td&gt;2278.71&lt;/td&gt;
&lt;td&gt;36.52%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;2000.00&lt;/td&gt;
&lt;td&gt;1740.86&lt;/td&gt;
&lt;td&gt;+259.14&lt;/td&gt;
&lt;td&gt;259.14&lt;/td&gt;
&lt;td&gt;12.96%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h1&gt;
  
  
  The most interesting example: Row 2
&lt;/h1&gt;

&lt;p&gt;Look at:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="k3m7v2"&lt;br&gt;
Actual    = 58&lt;br&gt;
Predicted = 144.31&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


The absolute error is only:



```text id="n6x1q8"
86.31 kg CO₂e
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But the percentage error is:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="w9c4s7"&lt;br&gt;
148.81%&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Why?

Because the actual value is very small.

$$
\frac{86.31}{58}\times100
\approx148.8\%
$$

This demonstrates an important limitation of percentage-based errors:

&amp;gt; **APE can become very large when the actual target is small, even when the absolute error is relatively modest.**

### Viva question

**“Why is the percentage error 148% when the absolute error is only 86?”**

Strong answer:

&amp;gt; “Because percentage error is relative to the actual value. The actual PCF is only 58 kg CO₂e, so an 86 kg CO₂e error is larger than the actual value itself. This is why I would not use APE alone to judge the model, particularly for small PCF values.”

Excellent answer.

---

# Row 8 — Large absolute error

Row 8:



```text id="3y7p2n"
Actual    = 6240
Predicted = 3961.29
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Absolute error:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="7c4m9x"&lt;br&gt;
2278.71 kg CO₂e&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


APE:



```text id="q5n8r1"
36.52%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This shows the opposite situation.&lt;/p&gt;

&lt;p&gt;The percentage error is not as extreme as row 2, but the &lt;strong&gt;absolute error is very large&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is why your project reports multiple metrics rather than relying on one metric.&lt;/p&gt;


&lt;h1&gt;
  
  
  Why do you need both residual and absolute error?
&lt;/h1&gt;

&lt;p&gt;Because they answer different questions.&lt;/p&gt;
&lt;h3&gt;
  
  
  Residual
&lt;/h3&gt;

&lt;p&gt;Shows &lt;strong&gt;direction&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="r8x2k6"&lt;br&gt;
Positive → underprediction&lt;br&gt;
Negative → overprediction&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


### Absolute Error

Shows **magnitude**:



```text id="p4m7q1"
How far away was the prediction?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="c9v3z5"&lt;br&gt;
Residual = -108.73&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


means the model overpredicted.

But:



```text id="a6k1w8"
Absolute Error = 108.73
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;tells us the size of that error without caring about direction.&lt;/p&gt;


&lt;h1&gt;
  
  
  How this connects to MAE
&lt;/h1&gt;

&lt;p&gt;Your:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="x7n2m5"&lt;br&gt;
Absolute_Error&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


column contains the individual absolute errors.

MAE is essentially:

$$
MAE = mean(|Actual-Predicted|)
$$

So:



```text id="h4q9s2"
Absolute_Error
      ↓
average all rows
      ↓
MAE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Therefore, this table lets you understand &lt;strong&gt;what is behind the overall MAE&lt;/strong&gt;.&lt;/p&gt;


&lt;h1&gt;
  
  
  How this connects to RMSE
&lt;/h1&gt;

&lt;p&gt;RMSE is particularly affected by large residuals.&lt;/p&gt;

&lt;p&gt;For example, row 8 has an error of approximately:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="z2k6v9"&lt;br&gt;
2278.71&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Large errors like this receive much more influence under RMSE because the residual is squared.

That's why your earlier:



```text id="v8q3m1"
Mean MAE  ≈ 10,398
Mean RMSE ≈ 86,982
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;can differ dramatically.&lt;/p&gt;


&lt;h1&gt;
  
  
  Important limitation of this analysis
&lt;/h1&gt;

&lt;p&gt;You are displaying only:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```python id="s4m8q2"&lt;br&gt;
.head(10)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


So this cell **does not yet analyse the entire test set**.

It only creates the full table and displays the first 10 observations.

If your supervisor asks:

**“Can you identify which observations have the largest errors?”**

This cell has the information available, but `head(10)` does not show the largest errors.

You would need to sort the DataFrame by `Absolute_Error`, for example:



```python
final_predictions_df.sort_values(
    "Absolute_Error",
    ascending=False
).head(10)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That would show the &lt;strong&gt;10 largest absolute errors&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Similarly, you could sort by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Absolute_Percentage_Error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to investigate the observations with the largest relative errors.&lt;/p&gt;




&lt;h1&gt;
  
  
  Very important viva distinction
&lt;/h1&gt;

&lt;p&gt;If asked:&lt;/p&gt;

&lt;h3&gt;
  
  
  “Is this calculating your final model performance?”
&lt;/h3&gt;

&lt;p&gt;Answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“Not directly. The overall final model performance is calculated using aggregate metrics such as MAE, RMSE and R². This cell performs row-level error analysis by comparing actual and predicted PCF values and calculating residuals, absolute errors and absolute percentage errors.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the correct distinction.&lt;/p&gt;




&lt;h1&gt;
  
  
  30-second viva answer
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“This cell performs row-level error analysis on the independent test predictions. I create a DataFrame containing the actual and predicted PCF values, then calculate residuals as actual minus predicted to identify under- and overprediction. I calculate absolute error to measure the magnitude of each error and absolute percentage error to express the error relative to the actual PCF. I use a small epsilon in the denominator to avoid division by zero. The analysis helps explain individual prediction behaviour behind the aggregate MAE, RMSE and R² results. However, percentage error can become very large for small actual PCF values, so I would not use it as the sole evaluation metric.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Examiner trap
&lt;/h3&gt;

&lt;p&gt;If they point to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;58 actual → 144 predicted → 148.8% error&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and ask &lt;strong&gt;“Is your model bad?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Don't immediately say yes.&lt;/p&gt;

&lt;p&gt;Say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“This individual prediction has a large relative error, but its absolute error is about 86 kg CO₂e. Because the actual value is small, the percentage error is amplified. I would assess it together with MAE, RMSE, R² and the distribution of errors across the complete test set.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That answer shows you understand &lt;strong&gt;both the model and the limitations of the metric&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For your final error analysis&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check whether errors vary by PCF size&lt;/li&gt;
&lt;li&gt;Inspect bias across the test set&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Cell-14-FINAL STRUCTURED FEATURE BUILDER</title>
      <dc:creator>TS</dc:creator>
      <pubDate>Mon, 14 Sep 2026 11:33:41 +0000</pubDate>
      <link>https://dev.to/ts2025/cell-14-final-structured-feature-builder-3pj7</link>
      <guid>https://dev.to/ts2025/cell-14-final-structured-feature-builder-3pj7</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ============================================================
# FINAL STRUCTURED FEATURE BUILDER
#
# Training:
#   Country mapping fitted on the complete development set
#
# Test:
#   Same mapping applied to untouched test data
#
# Test target is NEVER used.
# ============================================================
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_final_structured_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;X_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;y_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;X_test&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;train_part&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X_train&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;test_part&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X_test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;    &lt;span class="c1"&gt;# Product Weight → Winsorization
&lt;/span&gt;    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;
    &lt;span class="n"&gt;lower_limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;upper_limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fit_winsorization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;train_part&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;WEIGHT_COL&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;train_part&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;apply_winsorization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;train_part&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;WEIGHT_COL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;lower_limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;upper_limit&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;test_part&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;apply_winsorization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;test_part&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;WEIGHT_COL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;lower_limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;upper_limit&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;    &lt;span class="c1"&gt;# Numeric features
&lt;/span&gt;    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;
    &lt;span class="n"&gt;numeric_cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;YEAR_COL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;WEIGHT_COL&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;train_numeric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;train_part&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;numeric_cols&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_numeric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coerce&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;test_numeric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;test_part&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;numeric_cols&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_numeric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coerce&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;    &lt;span class="c1"&gt;# Categorical features → One-Hot Encoding
&lt;/span&gt;    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;
    &lt;span class="n"&gt;other_ohe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OneHotEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;handle_unknown&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ignore&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;sparse_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;train_other_ohe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;other_ohe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit_transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;train_part&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;OTHER_CATEGORICAL_FEATURES&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;test_other_ohe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;other_ohe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;test_part&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;OTHER_CATEGORICAL_FEATURES&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;    &lt;span class="c1"&gt;# Country → Target Encoding
&lt;/span&gt;    &lt;span class="c1"&gt;#
&lt;/span&gt;    &lt;span class="c1"&gt;# Mapping is learned from complete development data only.
&lt;/span&gt;    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;
    &lt;span class="n"&gt;country_means&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;train_part&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;COUNTRY_COL&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Target&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;y_train&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Target&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;global_mean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y_train&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="n"&gt;train_country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;train_part&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;COUNTRY_COL&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country_means&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;global_mean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_numpy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reshape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;test_country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;test_part&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;COUNTRY_COL&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country_means&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;global_mean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_numpy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reshape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;    &lt;span class="c1"&gt;# Combine structured features
&lt;/span&gt;    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;
    &lt;span class="n"&gt;X_train_structured&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hstack&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="n"&gt;train_numeric&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_numpy&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;train_other_ohe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;train_country&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;X_test_structured&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hstack&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="n"&gt;test_numeric&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_numpy&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;test_other_ohe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;test_country&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;


    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;X_train_structured&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;X_test_structured&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Final Structured Feature Builder — Purpose
&lt;/h3&gt;

&lt;p&gt;This function prepares the &lt;strong&gt;final structured features&lt;/strong&gt; for the last stage of the project, where you train the final model on the complete development set and then evaluate it on the &lt;strong&gt;untouched independent test set&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The key difference from the previous &lt;code&gt;build_structured_features()&lt;/code&gt; function is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;During CV, preprocessing is fitted separately inside each fold. Here, after model/feature selection is complete, the preprocessing is fitted on the complete development set and then applied to the test set.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most importantly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The test target is never used.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  1. Define the function
&lt;/h3&gt;



&lt;p&gt;```python id="c2r7xq"&lt;br&gt;
def build_final_structured_features(&lt;br&gt;
    X_train,&lt;br&gt;
    y_train,&lt;br&gt;
    X_test&lt;br&gt;
):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


The function receives three inputs:

* `X_train` → complete development features
* `y_train` → development-set target
* `X_test` → untouched test features

Here, `X_train` is actually your **full development set**, not just one CV fold.

The target `y_train` is required because Country uses **target encoding**.

`X_test` contains the test features but its target is deliberately not passed into the function.

### Viva question

**Why does the function need `y_train` but not `y_test`?**

Answer:

&amp;gt; “Because country target encoding requires the training target to calculate country-level target means. I do not need and deliberately do not use `y_test`, because the independent test target must remain unseen until final evaluation.”

---

# 2. Copy the data



```python id="w7w5kq"
train_part = X_train.copy()
test_part = X_test.copy()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Creates copies of both datasets.&lt;/p&gt;

&lt;p&gt;This means the original DataFrames are not modified directly.&lt;/p&gt;


&lt;h1&gt;
  
  
  Product Weight → Winsorization
&lt;/h1&gt;
&lt;h3&gt;
  
  
  3. Learn winsorization limits from development data
&lt;/h3&gt;



&lt;p&gt;```python id="j3f5r9"&lt;br&gt;
lower_limit, upper_limit = fit_winsorization(&lt;br&gt;
    train_part,&lt;br&gt;
    WEIGHT_COL&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This calculates the lower and upper limits for Product Weight using **only the development data**.

Winsorization limits extreme values by capping observations beyond the selected boundaries.

Conceptually:



```text
Very small weight → lower limit
Normal weight     → unchanged
Very large weight → upper limit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Important leakage point
&lt;/h3&gt;

&lt;p&gt;The limits are learned from training/development data only.&lt;/p&gt;

&lt;p&gt;You do &lt;strong&gt;not&lt;/strong&gt; calculate the limits using the test data.&lt;/p&gt;


&lt;h3&gt;
  
  
  4. Apply limits to development data
&lt;/h3&gt;



&lt;p&gt;```python id="6t7y1e"&lt;br&gt;
train_part = apply_winsorization(&lt;br&gt;
    train_part,&lt;br&gt;
    WEIGHT_COL,&lt;br&gt;
    lower_limit,&lt;br&gt;
    upper_limit&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


The previously calculated limits are applied to the development data.

---

### 5. Apply the SAME limits to test data



```python id="4d1q3r"
test_part = apply_winsorization(
    test_part,
    WEIGHT_COL,
    lower_limit,
    upper_limit
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is a very important line.&lt;/p&gt;

&lt;p&gt;You &lt;strong&gt;do not fit new limits on the test set&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Development data
       ↓
learn limits
       ↓
┌───────────────┐
│ same limits   │
│               │
├───────────────┤
│ Development   │
│ Test          │
└───────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps the transformation consistent and prevents test-data information from influencing preprocessing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Viva answer
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“I fit the winsorization thresholds on the complete development set and then applied those same thresholds to the test set. I did not estimate any preprocessing parameter from the test distribution.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Numeric Features
&lt;/h1&gt;

&lt;h3&gt;
  
  
  6. Define numeric columns
&lt;/h3&gt;



&lt;p&gt;```python id="8h9f3s"&lt;br&gt;
numeric_cols = [&lt;br&gt;
    YEAR_COL,&lt;br&gt;
    WEIGHT_COL&lt;br&gt;
]&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


The numeric features are:

* Year of reporting
* Product weight

---

### 7. Convert development numeric columns



```python id="5m2d8a"
train_numeric = train_part[numeric_cols].apply(
    pd.to_numeric,
    errors="coerce"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Attempts to convert the numeric columns into numeric values.&lt;/p&gt;

&lt;p&gt;If something cannot be converted, it becomes &lt;code&gt;NaN&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"2024" → 2024
"10.5" → 10.5
"unknown" → NaN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  8. Convert test numeric columns
&lt;/h3&gt;



&lt;p&gt;```python id="1j8v6c"&lt;br&gt;
test_numeric = test_part[numeric_cols].apply(&lt;br&gt;
    pd.to_numeric,&lt;br&gt;
    errors="coerce"&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


The exact same type of conversion is applied to test data.

No test target is involved.

---

# Categorical Features → One-Hot Encoding

### 9. Create OneHotEncoder



```python id="q5z8n2"
other_ohe = OneHotEncoder(
    handle_unknown="ignore",
    sparse_output=False
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This encoder converts categorical variables into numerical binary columns.&lt;/p&gt;

&lt;p&gt;Your categorical variables include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Company's GICS Industry&lt;/li&gt;
&lt;li&gt;Protocol used for PCF&lt;/li&gt;
&lt;li&gt;Stage-level CO₂e available&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Industry:
Food
Technology
Automotive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;could become columns such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Industry_Food
Industry_Technology
Industry_Automotive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;handle_unknown="ignore"&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is particularly important for the test set.&lt;/p&gt;

&lt;p&gt;Suppose the development data contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Food
Technology
Automotive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but the test set contains a previously unseen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mining
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The encoder does not crash.&lt;/p&gt;

&lt;p&gt;It ignores the unseen category.&lt;/p&gt;

&lt;h3&gt;
  
  
  Viva answer
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“I used &lt;code&gt;handle_unknown='ignore'&lt;/code&gt; because the test set may contain categorical values that were not present in the development set. This allows the transformation to proceed without creating an error or fitting the encoder using test information.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  10. Fit encoder on development data
&lt;/h3&gt;



&lt;p&gt;```python id="q0e7v3"&lt;br&gt;
train_other_ohe = other_ohe.fit_transform(&lt;br&gt;
    train_part[OTHER_CATEGORICAL_FEATURES]&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This is where the encoder **learns the category structure**.

For example, it learns which industry/protocol/stage categories exist in the development data.

---

### 11. Transform test data



```python id="m8v4q6"
test_other_ohe = other_ohe.transform(
    test_part[OTHER_CATEGORICAL_FEATURES]
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Notice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;fit_transform&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is crucial.&lt;/p&gt;

&lt;p&gt;The encoder has already been fitted using development data.&lt;/p&gt;

&lt;p&gt;The test data is only transformed using the existing mapping.&lt;/p&gt;

&lt;h3&gt;
  
  
  Viva question
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Why not use &lt;code&gt;fit_transform()&lt;/code&gt; on the test set?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Because fitting on the test set would allow information from the test distribution to influence the preprocessing. I fit the encoder only on the development data and then use &lt;code&gt;transform()&lt;/code&gt; on the test data.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Country → Target Encoding
&lt;/h1&gt;

&lt;p&gt;This is probably the &lt;strong&gt;most important section&lt;/strong&gt; of this function.&lt;/p&gt;

&lt;h3&gt;
  
  
  12. Calculate country means
&lt;/h3&gt;



&lt;p&gt;```python id="v4k8s2"&lt;br&gt;
country_means = (&lt;br&gt;
    pd.DataFrame({&lt;br&gt;
        "Country": train_part[COUNTRY_COL],&lt;br&gt;
        "Target": y_train&lt;br&gt;
    })&lt;br&gt;
    .groupby("Country")["Target"]&lt;br&gt;
    .mean()&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This creates a mapping:



```text
Country → Mean target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For example, conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Germany → 25.4
France  → 18.7
Japan   → 31.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual values depend on your dataset.&lt;/p&gt;

&lt;h3&gt;
  
  
  Critical point
&lt;/h3&gt;

&lt;p&gt;These means are calculated using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Complete development data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;y_train
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;only.&lt;/p&gt;

&lt;p&gt;There is &lt;strong&gt;no &lt;code&gt;y_test&lt;/code&gt;&lt;/strong&gt; here.&lt;/p&gt;




&lt;h3&gt;
  
  
  13. Calculate global mean
&lt;/h3&gt;



&lt;p&gt;```python id="1k7d3x"&lt;br&gt;
global_mean = np.mean(y_train)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This calculates the overall mean target of the development set.

It is used as a fallback.

---

### 14. Encode development countries



```python id="f0j5c7"
train_country = (
    train_part[COUNTRY_COL]
    .map(country_means)
    .fillna(global_mean)
    .to_numpy()
    .reshape(-1, 1)
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let's break this into functions.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;code&gt;.map(country_means)&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Maps each country to its calculated mean target.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Germany → 25.4
France  → 18.7
Japan   → 31.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;code&gt;.fillna(global_mean)&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;If a country doesn't have a mapping, use the overall development-set mean instead.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;.to_numpy()&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Converts the pandas Series into a NumPy array.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;.reshape(-1, 1)&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Converts it into a two-dimensional column:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(n,)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(n, 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes it suitable for &lt;code&gt;np.hstack()&lt;/code&gt; later.&lt;/p&gt;




&lt;h3&gt;
  
  
  15. Encode test countries
&lt;/h3&gt;



&lt;p&gt;```python id="n9w3p5"&lt;br&gt;
test_country = (&lt;br&gt;
    test_part[COUNTRY_COL]&lt;br&gt;
    .map(country_means)&lt;br&gt;
    .fillna(global_mean)&lt;br&gt;
    .to_numpy()&lt;br&gt;
    .reshape(-1, 1)&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This uses the **same `country_means` learned from the development data**.

This is extremely important.

The test target is not used.

If the test contains a country that wasn't present in development:



```text
Unknown country
       ↓
global development mean
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Viva answer
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“For the final test transformation, country target encoding is fitted using the complete development set. Test countries are mapped using that fixed mapping. If a test country was unseen during development, I use the development global mean as a fallback. Therefore, the test target is never involved.”&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h1&gt;
  
  
  16. Combine structured features
&lt;/h1&gt;
&lt;h3&gt;
  
  
  Development features
&lt;/h3&gt;



&lt;p&gt;```python id="g3w8r1"&lt;br&gt;
X_train_structured = np.hstack([&lt;br&gt;
    train_numeric.to_numpy(),&lt;br&gt;
    train_other_ohe,&lt;br&gt;
    train_country&lt;br&gt;
])&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


`np.hstack()` means **horizontal stacking**.

It combines:



```text
Numeric features
      +
One-hot categorical features
      +
Country target encoding
      ↓
Final structured feature matrix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test features
&lt;/h3&gt;



&lt;p&gt;```python id="k2m6v8"&lt;br&gt;
X_test_structured = np.hstack([&lt;br&gt;
    test_numeric.to_numpy(),&lt;br&gt;
    test_other_ohe,&lt;br&gt;
    test_country&lt;br&gt;
])&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


The same feature structure is created for the test data.

Therefore, the training and test matrices have compatible columns.

---

# 17. Return the final feature matrices



```python id="z4r1t6"
return (
    X_train_structured,
    X_test_structured
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The function returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Final development structured features
+
Final test structured features
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These can then be combined with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SBERT → PCA 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to create the final hybrid features.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why this function is different from the previous one
&lt;/h1&gt;

&lt;p&gt;This is &lt;strong&gt;very important for your viva&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  During CV
&lt;/h3&gt;

&lt;p&gt;You used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;build_structured_features&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;because each fold needs its own preprocessing.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fold 1:
80% training → fit preprocessing
20% validation → transform

Fold 2:
different 80% → fit preprocessing
different 20% → transform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Final model
&lt;/h3&gt;

&lt;p&gt;Now model selection is finished.&lt;/p&gt;

&lt;p&gt;So you use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;build_final_structured_features&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Complete development set
        ↓
fit preprocessing
        ↓
Independent test set
        ↓
transform only
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The workflow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    DEVELOPMENT DATA
                           │
                           ▼
                  Fit preprocessing
                           │
          ┌────────────────┴───────────────┐
          ▼                                ▼
 Complete development                 Test data
      transform                       transform
          │                                │
          ▼                                ▼
   Train final model              Final evaluation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Most important leakage explanation
&lt;/h1&gt;

&lt;p&gt;Your examiner may ask:&lt;/p&gt;

&lt;h3&gt;
  
  
  “How did you prevent test-set leakage?”
&lt;/h3&gt;

&lt;p&gt;Strong answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I kept the test set completely untouched during model selection. In the final structured feature builder, winsorization thresholds, one-hot encoding categories, and country target-encoding mappings are all learned from the complete development set. The test set is only transformed using those learned parameters. The test target is never used during preprocessing or training.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  One subtle point you should know
&lt;/h3&gt;

&lt;p&gt;There is an important distinction between &lt;strong&gt;test leakage&lt;/strong&gt; and &lt;strong&gt;training self-encoding&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;country_means&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is calculated using the complete development set, so each development observation contributes to its own country's mean.&lt;/p&gt;

&lt;p&gt;That is acceptable &lt;strong&gt;for fitting the final model after all CV/model-selection decisions are complete&lt;/strong&gt;, because you are now using all available development information to train the final model.&lt;/p&gt;

&lt;p&gt;It would &lt;strong&gt;not&lt;/strong&gt; be appropriate for generating unbiased CV predictions.&lt;/p&gt;

&lt;p&gt;That's exactly why your earlier CV function used &lt;strong&gt;OOF country target encoding&lt;/strong&gt;, while this final function uses the complete development mapping.&lt;/p&gt;

&lt;h3&gt;
  
  
  Viva question
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;“Why did you use OOF encoding during CV but normal target encoding here?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“During cross-validation, OOF encoding was necessary to prevent an observation's own target from contributing to its encoded training feature. After model selection, I retrain the final model using the complete development data, so I use the full development target information to construct the final training representation. The independent test set is still encoded only from that development-derived mapping.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Full final pipeline
&lt;/h1&gt;

&lt;p&gt;This function fits into the final stage like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Original dataset
       ↓
80% Development / 20% Test
       ↓
CV + PCA selection + Model comparison + XGB tuning
       ↓
All modelling decisions fixed
       ↓
Complete Development Set
       │
       ├── Winsorization limits
       ├── OHE mapping
       └── Country target mapping
       ↓
Final structured features
       │
       └──────────────┐
                      ▼
Test data ──transform using same mappings
                      │
                      ▼
             Structured test features
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you combine these with the final:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SBERT
  ↓
PCA = 50
  ↓
Hybrid features
  ↓
Final tuned XGBoost
  ↓
Independent test prediction
  ↓
MAE / RMSE / R²
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Best 30-second viva answer
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“This function prepares the structured features for the final independent test evaluation. Unlike the CV preprocessing, I now fit the preprocessing on the complete development set because model selection has already finished. Product weight winsorization thresholds, one-hot encoding categories, and country target-encoding mappings are learned only from the development data. The same learned transformations are then applied to the untouched test data. In particular, the test target is never used. This gives me a leakage-controlled final feature representation for training the final model and evaluating it on genuinely unseen data.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For your final evaluation&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check target-encoding bias in final training&lt;/li&gt;
&lt;li&gt;Verify feature-column consistency&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Cell-13-TUNED XGBOOST — OOF DEVELOPMENT EVALUATION</title>
      <dc:creator>TS</dc:creator>
      <pubDate>Mon, 14 Sep 2026 11:29:22 +0000</pubDate>
      <link>https://dev.to/ts2025/cell-13-tuned-xgboost-oof-development-evaluation-1788</link>
      <guid>https://dev.to/ts2025/cell-13-tuned-xgboost-oof-development-evaluation-1788</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ============================================================
# TUNED XGBOOST — OOF DEVELOPMENT EVALUATION
#
# Target   = log1p(PCF)
# Country  = OOF Target Encoding
# SBERT    = PCA 50
# CV       = Stratified 5-Fold
# ============================================================
&lt;/span&gt;
&lt;span class="n"&gt;BEST_XGB_PARAMS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n_estimators&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;learning_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.08&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_depth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;min_child_weight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subsample&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;colsample_bytree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="n"&gt;xgb_oof_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;


&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;skf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_bins&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Running fold &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/5...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;    &lt;span class="c1"&gt;# 1. Fold data
&lt;/span&gt;    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;
    &lt;span class="n"&gt;X_fold_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;X_fold_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;    &lt;span class="c1"&gt;# 2. Log target
&lt;/span&gt;    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;
    &lt;span class="n"&gt;y_fold_train_log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;y_log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_numpy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;y_fold_valid_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;TARGET&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_numpy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;    &lt;span class="c1"&gt;# 3. Structured features
&lt;/span&gt;    &lt;span class="c1"&gt;#    Country = OOF Target Encoding
&lt;/span&gt;    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;
    &lt;span class="n"&gt;X_struct_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_struct_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;build_structured_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;X_fold_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;y_fold_train_log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;X_fold_valid&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;    &lt;span class="c1"&gt;# 4. SBERT features
&lt;/span&gt;    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;
    &lt;span class="n"&gt;X_sbert_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;X_train_sbert&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;X_sbert_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;X_train_sbert&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;    &lt;span class="c1"&gt;# 5. PCA = 50
&lt;/span&gt;    &lt;span class="c1"&gt;#    Fit only on outer training fold
&lt;/span&gt;    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;X_sbert_train_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;X_sbert_valid_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;apply_pca_to_sbert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;X_sbert_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;X_sbert_valid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;n_components&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;    &lt;span class="c1"&gt;# 6. Hybrid features
&lt;/span&gt;    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;
    &lt;span class="n"&gt;X_hybrid_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fuse_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;X_sbert_train_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;X_struct_train&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;X_hybrid_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fuse_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;X_sbert_valid_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;X_struct_valid&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;    &lt;span class="c1"&gt;# 7. Tuned XGBoost
&lt;/span&gt;    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;XGBRegressor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;objective&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reg:squarederror&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;n_jobs&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;BEST_XGB_PARAMS&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;    &lt;span class="c1"&gt;# 8. Train
&lt;/span&gt;    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;X_hybrid_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;y_fold_train_log&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;    &lt;span class="c1"&gt;# 9. Predict
&lt;/span&gt;    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;
    &lt;span class="n"&gt;y_pred_log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;X_hybrid_valid&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="c1"&gt;# Convert back to original PCF scale
&lt;/span&gt;    &lt;span class="n"&gt;y_pred_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expm1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;y_pred_log&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;y_pred_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;maximum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;y_pred_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;    &lt;span class="c1"&gt;# 10. Metrics
&lt;/span&gt;    &lt;span class="c1"&gt;# --------------------------------------------------------
&lt;/span&gt;
    &lt;span class="n"&gt;mae&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mean_absolute_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;y_fold_valid_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;y_pred_raw&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;rmse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;mean_squared_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;y_fold_valid_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;y_pred_raw&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;r2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;r2_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;y_fold_valid_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;y_pred_raw&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="n"&gt;xgb_oof_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MAE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mae&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RMSE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rmse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r2&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;


    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  MAE=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mae&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RMSE=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rmse&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R²=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# ============================================================
# OOF SUMMARY
# ============================================================
&lt;/span&gt;
&lt;span class="n"&gt;xgb_oof_results_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;xgb_oof_results&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;xgb_oof_summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mean_MAE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;xgb_oof_results_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MAE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mean_RMSE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;xgb_oof_results_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RMSE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mean_R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;xgb_oof_results_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Std_R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;xgb_oof_results_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;std&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;


&lt;span class="nf"&gt;display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xgb_oof_results_df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xgb_oof_summary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tuned XGBoost — OOF Development Evaluation
&lt;/h3&gt;

&lt;p&gt;This cell is the &lt;strong&gt;next step after hyperparameter tuning&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The previous cell asked:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Among the 20 randomly sampled XGBoost configurations, which configuration performs best?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This cell takes the selected configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;BEST_XGB_PARAMS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n_estimators&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;learning_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.08&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_depth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;min_child_weight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subsample&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;colsample_bytree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and evaluates it again using &lt;strong&gt;5-fold cross-validation on the development dataset&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The important point is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;This cell is not searching anymore. It is evaluating the selected/tuned XGBoost configuration across all five development folds.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  1. Selected XGBoost parameters
&lt;/h3&gt;



&lt;p&gt;```python id="n8n1qd"&lt;br&gt;
BEST_XGB_PARAMS = {&lt;br&gt;
    "n_estimators": 200,&lt;br&gt;
    "learning_rate": 0.08,&lt;br&gt;
    "max_depth": 6,&lt;br&gt;
    "min_child_weight": 3,&lt;br&gt;
    "subsample": 0.7,&lt;br&gt;
    "colsample_bytree": 0.8&lt;br&gt;
}&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


These are the hyperparameters selected from the previous tuning experiment.

The values mean:

| Parameter          | Value | Meaning                                  |
| ------------------ | ----: | ---------------------------------------- |
| `n_estimators`     |   200 | Number of boosting trees                 |
| `learning_rate`    |  0.08 | Contribution/shrinkage of each tree      |
| `max_depth`        |     6 | Maximum depth of each tree               |
| `min_child_weight` |     3 | Makes splitting more conservative        |
| `subsample`        |   0.7 | Uses 70% of rows for each boosting stage |
| `colsample_bytree` |   0.8 | Uses 80% of features for each tree       |

### Viva point

Do **not** say:

&amp;gt; “These are the globally optimal parameters.”

Say:

&amp;gt; **“These were the best-performing parameters among the 20 randomly sampled configurations evaluated during tuning.”**

That is scientifically safer.

---

### 2. Create result storage



```python id="psjq8j"
xgb_oof_results = []
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Creates an empty list to store the performance of the tuned XGBoost model for each fold.&lt;/p&gt;

&lt;p&gt;After the loop, there will be five results:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="d5cy1c"&lt;br&gt;
Fold 1&lt;br&gt;
Fold 2&lt;br&gt;
Fold 3&lt;br&gt;
Fold 4&lt;br&gt;
Fold 5&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


---

### 3. Start 5-fold evaluation



```python id="bqj4kq"
for fold, (train_idx, valid_idx) in enumerate(
    skf.split(train_df, target_bins),
    start=1
):
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This uses the same &lt;strong&gt;Stratified 5-Fold CV strategy&lt;/strong&gt; defined earlier.&lt;/p&gt;

&lt;p&gt;For each fold:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="qlz07h"&lt;br&gt;
Development data&lt;br&gt;
       ↓&lt;br&gt;
 ┌───────────────┐&lt;br&gt;
 │ Training fold │&lt;br&gt;
 └───────────────┘&lt;br&gt;
       +&lt;br&gt;
 ┌────────────────┐&lt;br&gt;
 │ Validation fold│&lt;br&gt;
 └────────────────┘&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This happens five times.

### Why `target_bins`?

Because the original PCF target is continuous.

You created approximate quantile bins earlier so that each fold has a reasonably similar distribution of the target.

---

### 4. Print fold progress



```python id="4sfy9f"
print(f"Running fold {fold}/5...")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Just shows which fold is currently being processed.&lt;/p&gt;


&lt;h1&gt;
  
  
  Fold-level preprocessing
&lt;/h1&gt;
&lt;h3&gt;
  
  
  5. Separate training and validation data
&lt;/h3&gt;



&lt;p&gt;```python id="f3k5wo"&lt;br&gt;
X_fold_train = (&lt;br&gt;
    train_df.iloc[train_idx].copy()&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;X_fold_valid = (&lt;br&gt;
    train_df.iloc[valid_idx].copy()&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


The current fold is divided into:

* training portion
* validation portion

The validation portion is not used to fit the model.

This is important for estimating generalisation performance.

---

### 6. Training target



```python id="s0u2tx"
y_fold_train_log = (
    y_log.iloc[train_idx].to_numpy()
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The model is trained using:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="q1x0kd"&lt;br&gt;
log1p(PCF)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


rather than raw PCF.

---

### 7. Validation target



```python id="s2r8c5"
y_fold_valid_raw = (
    train_df.iloc[valid_idx][TARGET].to_numpy()
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The validation target remains in the &lt;strong&gt;original PCF scale&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So the process is:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="u8p5q6"&lt;br&gt;
Training:&lt;br&gt;
log1p(PCF)&lt;/p&gt;

&lt;p&gt;Validation:&lt;br&gt;
original PCF&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This is intentional.

---

# Structured features

### 8. Build structured features



```python id="n7n8gq"
X_struct_train, X_struct_valid = (
    build_structured_features(
        X_fold_train,
        y_fold_train_log,
        X_fold_valid
    )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This generates the structured feature representation.&lt;/p&gt;

&lt;p&gt;It includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Year&lt;/li&gt;
&lt;li&gt;Product weight&lt;/li&gt;
&lt;li&gt;Industry&lt;/li&gt;
&lt;li&gt;Protocol&lt;/li&gt;
&lt;li&gt;Stage-level CO₂e availability&lt;/li&gt;
&lt;li&gt;Country target encoding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most importantly, the country encoding is &lt;strong&gt;OOF target encoded&lt;/strong&gt; for the training portion.&lt;/p&gt;

&lt;p&gt;This is important because country encoding uses the target variable.&lt;/p&gt;


&lt;h1&gt;
  
  
  SBERT representation
&lt;/h1&gt;
&lt;h3&gt;
  
  
  9. Select SBERT features
&lt;/h3&gt;



&lt;p&gt;```python id="e9o6r2"&lt;br&gt;
X_sbert_train = (&lt;br&gt;
    X_train_sbert[train_idx]&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;X_sbert_valid = (&lt;br&gt;
    X_train_sbert[valid_idx]&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


The SBERT embeddings corresponding to the current training and validation observations are selected.

SBERT itself was already generated earlier.

You are not retraining SBERT here.

---

# PCA

### 10. Reduce SBERT to 50 dimensions



```python id="6q6u9m"
(
    X_sbert_train_pca,
    X_sbert_valid_pca,
    _
) = apply_pca_to_sbert(
    X_sbert_train,
    X_sbert_valid,
    n_components=50
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This applies the selected PCA configuration.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="l0xk8b"&lt;br&gt;
SBERT embedding&lt;br&gt;
       ↓&lt;br&gt;
     PCA&lt;br&gt;
       ↓&lt;br&gt;
50 dimensions&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


### Very important line



```python id="q8h5tq"
X_sbert_train,
X_sbert_valid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;are passed separately.&lt;/p&gt;

&lt;p&gt;Inside &lt;code&gt;apply_pca_to_sbert()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```python id="w7w3xj"&lt;br&gt;
pca.fit_transform(X_train_sbert)&lt;br&gt;
pca.transform(X_valid_sbert)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Therefore:

&amp;gt; **PCA is fitted only on the training fold and then applied to the validation fold.**

This prevents validation information from influencing the PCA transformation.

---

# Hybrid features

### 11. Combine SBERT and structured features



```python id="a3x0j4"
X_hybrid_train = fuse_features(
    X_sbert_train_pca,
    X_struct_train
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```python id="6e4jpv"&lt;br&gt;
X_hybrid_valid = fuse_features(&lt;br&gt;
    X_sbert_valid_pca,&lt;br&gt;
    X_struct_valid&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


The model gets both representations:



```text id="qk2h9w"
Semantic text information
        +
Structured information
        ↓
Hybrid feature representation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is the core idea of the hybrid model.&lt;/p&gt;


&lt;h1&gt;
  
  
  Tuned XGBoost
&lt;/h1&gt;
&lt;h3&gt;
  
  
  12. Create the model
&lt;/h3&gt;



&lt;p&gt;```python id="y8zq2a"&lt;br&gt;
model = XGBRegressor(&lt;br&gt;
    objective="reg:squarederror",&lt;br&gt;
    random_state=42,&lt;br&gt;
    n_jobs=-1,&lt;br&gt;
    **BEST_XGB_PARAMS&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Creates an XGBoost regression model using the selected parameters.

The:



```python id="t7jv1z"
**BEST_XGB_PARAMS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;inserts all six tuned hyperparameters.&lt;/p&gt;

&lt;p&gt;So the model is effectively configured with:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="x3v0ms"&lt;br&gt;
200 trees&lt;br&gt;
learning rate = 0.08&lt;br&gt;
depth = 6&lt;br&gt;
min child weight = 3&lt;br&gt;
row sampling = 70%&lt;br&gt;
feature sampling = 80%&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


---

### 13. Train



```python id="c5f4vq"
model.fit(
    X_hybrid_train,
    y_fold_train_log
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;XGBoost learns:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="z6o9wa"&lt;br&gt;
Hybrid features&lt;br&gt;
      ↓&lt;br&gt;
log1p(PCF)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


using only the training fold.

---

# Prediction

### 14. Predict validation fold



```python id="w0p3z8"
y_pred_log = model.predict(
    X_hybrid_valid
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The predictions are initially on the log-transformed scale.&lt;/p&gt;


&lt;h3&gt;
  
  
  15. Convert predictions back
&lt;/h3&gt;



&lt;p&gt;```python id="b6v3h9"&lt;br&gt;
y_pred_raw = np.expm1(&lt;br&gt;
    y_pred_log&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Because training used:



```python
np.log1p(PCF)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;the inverse transformation is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expm1&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Therefore:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="q9q4t1"&lt;br&gt;
log1p(PCF)&lt;br&gt;
     ↓&lt;br&gt;
   model&lt;br&gt;
     ↓&lt;br&gt;
predicted log1p(PCF)&lt;br&gt;
     ↓&lt;br&gt;
   expm1&lt;br&gt;
     ↓&lt;br&gt;
predicted PCF&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


---

### 16. Enforce non-negative predictions



```python id="f7q2k3"
y_pred_raw = np.maximum(
    y_pred_raw,
    0
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Any negative prediction is replaced by zero.&lt;/p&gt;

&lt;p&gt;This makes the prediction consistent with the physical interpretation of PCF.&lt;/p&gt;


&lt;h1&gt;
  
  
  Metrics
&lt;/h1&gt;
&lt;h3&gt;
  
  
  17. MAE
&lt;/h3&gt;



&lt;p&gt;```python id="x0k8s2"&lt;br&gt;
mae = mean_absolute_error(&lt;br&gt;
    y_fold_valid_raw,&lt;br&gt;
    y_pred_raw&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Measures average absolute prediction error.

**Lower is better.**

---

### 18. RMSE



```python id="j3m6v9"
rmse = np.sqrt(
    mean_squared_error(
        y_fold_valid_raw,
        y_pred_raw
    )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;RMSE penalises large errors more heavily than MAE.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lower is better.&lt;/strong&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  19. R²
&lt;/h3&gt;



&lt;p&gt;```python id="p4r7d1"&lt;br&gt;
r2 = r2_score(&lt;br&gt;
    y_fold_valid_raw,&lt;br&gt;
    y_pred_raw&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Measures predictive explanatory performance relative to a baseline.

**Higher is better.**

---

### 20. Store each fold



```python id="w5c2n8"
xgb_oof_results.append({
    "Fold": fold,
    "MAE": mae,
    "RMSE": rmse,
    "R2": r2
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Stores the three metrics for each fold.&lt;/p&gt;


&lt;h1&gt;
  
  
  Your actual results
&lt;/h1&gt;

&lt;p&gt;You obtained:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Fold&lt;/th&gt;
&lt;th&gt;MAE&lt;/th&gt;
&lt;th&gt;RMSE&lt;/th&gt;
&lt;th&gt;R²&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5,195.20&lt;/td&gt;
&lt;td&gt;41,449.90&lt;/td&gt;
&lt;td&gt;0.8475&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;31,455.28&lt;/td&gt;
&lt;td&gt;260,513.71&lt;/td&gt;
&lt;td&gt;0.4092&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1,029.55&lt;/td&gt;
&lt;td&gt;2,780.88&lt;/td&gt;
&lt;td&gt;0.8705&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;11,496.24&lt;/td&gt;
&lt;td&gt;116,050.04&lt;/td&gt;
&lt;td&gt;0.8254&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2,811.77&lt;/td&gt;
&lt;td&gt;14,114.92&lt;/td&gt;
&lt;td&gt;0.5251&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mean&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10,397.61&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;86,981.89&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.6955&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="v4u7n2"&lt;br&gt;
Std R² = 0.2131&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


---

# How to interpret these results

The average performance is:

### Mean MAE

**10,397.61 kg CO₂e**

On average, the absolute prediction error was approximately **10,398 kg CO₂e** across the five validation folds.

### Mean RMSE

**86,981.89 kg CO₂e**

The much larger RMSE compared with MAE tells you that some predictions have **very large errors/outliers**.

This is an important observation.

### Mean R²

**0.6955**

The model achieved an average R² of approximately **0.696** across the five folds.

So the model demonstrates reasonably strong predictive performance overall, but performance is not equally stable across all folds.

---

# The most important result: Fold variation

Look carefully at your R²:



```text
Fold 1 → 0.8475
Fold 2 → 0.4092
Fold 3 → 0.8705
Fold 4 → 0.8254
Fold 5 → 0.5251
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;There is substantial variation.&lt;/p&gt;

&lt;p&gt;That is reflected by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Std R² = 0.2131
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is &lt;strong&gt;not something you should hide in the viva&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead, use it as a limitation/interpretation.&lt;/p&gt;

&lt;p&gt;A strong answer is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The mean R² was approximately 0.696, but the standard deviation was about 0.213, indicating noticeable variation across folds. This suggests that model performance depends on the composition of the validation subset, which may reflect heterogeneity or extreme observations in the PCF dataset.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is much more defensible than saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“My model achieved 69.5% accuracy.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Do not call R² accuracy.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Why is Fold 2 so bad?
&lt;/h1&gt;

&lt;p&gt;Fold 2 has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MAE  = 31,455
RMSE = 260,514
R²   = 0.409
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while Fold 3 has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MAE  = 1,030
RMSE = 2,781
R²   = 0.871
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a huge difference.&lt;/p&gt;

&lt;p&gt;A reasonable interpretation is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The PCF dataset is heterogeneous and likely contains extreme or difficult observations. A validation fold containing influential high-value observations or patterns poorly represented in its training portion can produce much larger errors.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But &lt;strong&gt;do not claim that Fold 2 definitely contains outliers unless you actually inspect the fold data&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If the examiner asks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Why exactly was Fold 2 poor?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The safest answer is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The results indicate that Fold 2 contains observations that were harder for the model to predict, but I would need to inspect the actual observations and residuals in that fold to identify the exact cause. I would not attribute it definitively to outliers without that analysis.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a &lt;strong&gt;very strong viva answer&lt;/strong&gt; because you acknowledge what the experiment shows without inventing a cause.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why is RMSE so much higher than MAE?
&lt;/h1&gt;

&lt;p&gt;Your:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mean MAE  ≈ 10,398
Mean RMSE ≈ 86,982
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;RMSE is dramatically higher.&lt;/p&gt;

&lt;p&gt;That suggests the model has some &lt;strong&gt;very large individual errors&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because RMSE squares errors before averaging:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error
  ↓
error²
  ↓
average
  ↓
square root
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Therefore, a few very large errors can dominate RMSE.&lt;/p&gt;

&lt;h3&gt;
  
  
  Viva answer
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“The substantially higher RMSE compared with MAE indicates that although the typical absolute error is lower, there are some very large prediction errors. RMSE is sensitive to those large errors because it squares the residuals.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  What does “OOF Development Evaluation” mean?
&lt;/h1&gt;

&lt;p&gt;This can easily confuse you.&lt;/p&gt;

&lt;p&gt;Here, &lt;strong&gt;OOF does not mean you are producing one prediction for every observation and using those predictions as the final model output.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this context, you are using &lt;strong&gt;out-of-fold validation within the development dataset&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Each observation acts as validation data in one fold and training data in the other four folds.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fold 1:
80% → train
20% → validation

Fold 2:
80% → train
20% → validation

...

Fold 5:
80% → train
20% → validation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Together, every development observation gets evaluated in a fold where it was not used for training.&lt;/p&gt;




&lt;h1&gt;
  
  
  Very important: this is NOT your final test performance
&lt;/h1&gt;

&lt;p&gt;You should be very clear about this.&lt;/p&gt;

&lt;p&gt;Your workflow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Original dataset
       ↓
80% Development       20% Independent Test
       ↓                       ↓
5-fold CV             KEEP UNTOUCHED
       ↓
PCA selection
       ↓
Model comparison
       ↓
XGBoost tuning
       ↓
Selected XGBoost
       ↓
Final model
       ↓
Evaluate ONCE
       ↓
Independent test performance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Therefore, the:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mean R² = 0.6955&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;is &lt;strong&gt;development cross-validation performance&lt;/strong&gt;, not your final independent test-set R².&lt;/p&gt;

&lt;p&gt;If you have a later cell that trains the final model on the full development set and evaluates it on &lt;code&gt;test_df&lt;/code&gt;, &lt;strong&gt;that test result is the one you should report as your final generalisation result.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Why repeat the evaluation after tuning?
&lt;/h1&gt;

&lt;p&gt;The previous tuning cell already calculated CV performance for the 20 configurations.&lt;/p&gt;

&lt;p&gt;So an examiner might ask:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Why are you doing another 5-fold evaluation?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The tuning stage used cross-validation to compare candidate configurations. After selecting the best configuration, I reran the fixed configuration across the five development folds to obtain a clean fold-level summary of its performance and stability. The independent test set remains untouched for the final evaluation.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a reasonable explanation.&lt;/p&gt;

&lt;p&gt;However, be precise: &lt;strong&gt;the CV estimate after selecting the best configuration is not fully unbiased for model-selection performance&lt;/strong&gt;, because the configuration was selected using the same development CV process. The truly independent estimate comes from the untouched test set.&lt;/p&gt;

&lt;p&gt;If challenged:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The post-selection CV result is useful for describing development performance and fold stability, but I treat the untouched test set as the final unbiased evaluation because the development CV was involved in model selection.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is an excellent research-methodology answer.&lt;/p&gt;




&lt;h1&gt;
  
  
  One-minute viva explanation
&lt;/h1&gt;

&lt;p&gt;If your supervisor asks you to explain the whole cell:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“After tuning XGBoost, I selected the best-performing configuration from the 20 randomly sampled parameter sets. I then evaluated that fixed configuration using stratified 5-fold cross-validation on the development dataset. In each fold, I rebuilt the structured features with leakage-aware OOF country target encoding, generated the hybrid representation by combining structured features with SBERT embeddings reduced to 50 PCA components, and trained XGBoost on the log-transformed PCF target. Predictions were converted back to the original PCF scale using &lt;code&gt;expm1&lt;/code&gt;, and I calculated MAE, RMSE and R². The average R² was 0.6955, with an R² standard deviation of 0.2131, showing reasonable overall predictive performance but noticeable variation across folds. The independent test set was not used in this evaluation.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The 5 numbers you should remember
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Best XGBoost:
n_estimators       = 200
learning_rate      = 0.08
max_depth          = 6
min_child_weight   = 3
subsample          = 0.7
colsample_bytree   = 0.8

Development CV:
Mean MAE  = 10,397.61
Mean RMSE = 86,981.89
Mean R²   = 0.6955
Std R²    = 0.2131
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the &lt;strong&gt;single most important conceptual sentence&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“The 0.6955 R² is a 5-fold development cross-validation result after selecting the XGBoost configuration; it is not the final independent test-set performance.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For your XGBoost evaluation&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check whether Fold 2 contains influential outliers&lt;/li&gt;
&lt;li&gt;Compare pooled OOF metrics with mean fold metrics&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Cell-12-XGBOOST HYPERPARAMETER TUNING</title>
      <dc:creator>TS</dc:creator>
      <pubDate>Mon, 14 Sep 2026 11:26:37 +0000</pubDate>
      <link>https://dev.to/ts2025/cell-12-xgboost-hyperparameter-tuning-1jbf</link>
      <guid>https://dev.to/ts2025/cell-12-xgboost-hyperparameter-tuning-1jbf</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ============================================================
# XGBOOST HYPERPARAMETER TUNING
#
# Fixed:
#   Target    = log1p(PCF)
#   Country   = OOF Target Encoding
#   SBERT     = PCA 50
#   CV        = Stratified 5-Fold
#
# Only XGBoost hyperparameters change
# ============================================================
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sklearn.model_selection&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ParameterSampler&lt;/span&gt;


&lt;span class="c1"&gt;# ============================================================
# 1. Generate 20 parameter combinations
# ============================================================
&lt;/span&gt;
&lt;span class="n"&gt;xgb_param_candidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;ParameterSampler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;xgb_param_grid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;n_iter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Testing &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xgb_param_candidates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; XGBoost parameter sets.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# ============================================================
# 2. Storage
# ============================================================
&lt;/span&gt;
&lt;span class="n"&gt;xgb_tuning_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;


&lt;span class="c1"&gt;# ============================================================
# 3. Parameter search
# ============================================================
&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;param_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;xgb_param_candidates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Parameter set &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;param_number&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xgb_param_candidates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;fold_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;


    &lt;span class="c1"&gt;# ========================================================
&lt;/span&gt;    &lt;span class="c1"&gt;# 4. Stratified 5-Fold CV
&lt;/span&gt;    &lt;span class="c1"&gt;# ========================================================
&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;skf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_bins&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt;

        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;        &lt;span class="c1"&gt;# Fold data
&lt;/span&gt;        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;
        &lt;span class="n"&gt;X_fold_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;X_fold_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;


        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;        &lt;span class="c1"&gt;# Log target
&lt;/span&gt;        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;
        &lt;span class="n"&gt;y_fold_train_log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;y_log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_numpy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;y_fold_valid_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;TARGET&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_numpy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;


        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;        &lt;span class="c1"&gt;# Structured features
&lt;/span&gt;        &lt;span class="c1"&gt;# Country = OOF Target Encoding
&lt;/span&gt;        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;
        &lt;span class="n"&gt;X_struct_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_struct_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;build_structured_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;X_fold_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;y_fold_train_log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;X_fold_valid&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;


        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;        &lt;span class="c1"&gt;# SBERT features
&lt;/span&gt;        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;
        &lt;span class="n"&gt;X_sbert_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;X_train_sbert&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;X_sbert_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;X_train_sbert&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;


        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;        &lt;span class="c1"&gt;# PCA = 50
&lt;/span&gt;        &lt;span class="c1"&gt;# PCA fitted only on training fold
&lt;/span&gt;        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;X_sbert_train_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;X_sbert_valid_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;apply_pca_to_sbert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;X_sbert_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;X_sbert_valid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;n_components&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;


        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;        &lt;span class="c1"&gt;# Hybrid feature fusion
&lt;/span&gt;        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;
        &lt;span class="n"&gt;X_hybrid_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fuse_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;X_sbert_train_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;X_struct_train&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;X_hybrid_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fuse_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;X_sbert_valid_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;X_struct_valid&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;


        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;        &lt;span class="c1"&gt;# Fresh XGBoost model
&lt;/span&gt;        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;XGBRegressor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;objective&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reg:squarederror&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;n_jobs&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;


        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;        &lt;span class="c1"&gt;# Train
&lt;/span&gt;        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;X_hybrid_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;y_fold_train_log&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;


        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;        &lt;span class="c1"&gt;# Predict
&lt;/span&gt;        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;
        &lt;span class="n"&gt;y_pred_log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;X_hybrid_valid&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;y_pred_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expm1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;y_pred_log&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;y_pred_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;maximum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;y_pred_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;


        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;        &lt;span class="c1"&gt;# Metrics
&lt;/span&gt;        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;
        &lt;span class="n"&gt;mae&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mean_absolute_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;y_fold_valid_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;y_pred_raw&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;rmse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;mean_squared_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;y_fold_valid_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;y_pred_raw&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;r2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;r2_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;y_fold_valid_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;y_pred_raw&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;


        &lt;span class="n"&gt;fold_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MAE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mae&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RMSE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rmse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r2&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;


    &lt;span class="c1"&gt;# ========================================================
&lt;/span&gt;    &lt;span class="c1"&gt;# 5. Average CV performance
&lt;/span&gt;    &lt;span class="c1"&gt;# ========================================================
&lt;/span&gt;
    &lt;span class="n"&gt;fold_results_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;fold_results&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;mean_mae&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fold_results_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MAE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;mean_rmse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fold_results_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RMSE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;mean_r2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fold_results_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;std_r2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fold_results_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;std&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


    &lt;span class="c1"&gt;# ========================================================
&lt;/span&gt;    &lt;span class="c1"&gt;# 6. Store results
&lt;/span&gt;    &lt;span class="c1"&gt;# ========================================================
&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Parameter_Set&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;param_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mean_MAE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mean_mae&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mean_RMSE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mean_rmse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mean_R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mean_r2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Std_R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;std_r2&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;xgb_tuning_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Mean R²: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mean_r2&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; | &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mean RMSE: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mean_rmse&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# ============================================================
# XGBOOST TUNING SUMMARY
# ============================================================
&lt;/span&gt;
&lt;span class="n"&gt;xgb_tuning_results_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;xgb_tuning_results&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;xgb_tuning_results_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;xgb_tuning_results_df&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mean_R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ascending&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;drop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nf"&gt;display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;xgb_tuning_results_df&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  XGBoost Hyperparameter Tuning — Overall Purpose
&lt;/h3&gt;

&lt;p&gt;This cell performs &lt;strong&gt;randomized hyperparameter tuning of XGBoost&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The important idea is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Everything in the modelling pipeline is kept fixed, and only the XGBoost hyperparameters are changed.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fixed components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Target → &lt;code&gt;log1p(PCF)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Country → OOF target encoding&lt;/li&gt;
&lt;li&gt;SBERT → PCA with 50 components&lt;/li&gt;
&lt;li&gt;Validation → Stratified 5-fold CV&lt;/li&gt;
&lt;li&gt;Features → SBERT + structured features&lt;/li&gt;
&lt;li&gt;Metrics → MAE, RMSE, R²&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So this experiment specifically answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“Given the selected hybrid feature representation, which XGBoost hyperparameter configuration performs best?”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  1. Import &lt;code&gt;ParameterSampler&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sklearn.model_selection&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ParameterSampler&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ParameterSampler&lt;/code&gt; is used to randomly select combinations of hyperparameters from the search space.&lt;/p&gt;

&lt;p&gt;You previously defined:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;xgb_param_grid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n_estimators&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;learning_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.03&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.08&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_depth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;min_child_weight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subsample&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;colsample_bytree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3 × 3 × 3 × 3 × 3 × 3 = 729 possible combinations.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of testing all 729, you randomly test only 20.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Generate 20 parameter combinations
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;xgb_param_candidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;ParameterSampler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;xgb_param_grid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;n_iter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;code&gt;ParameterSampler(...)&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This randomly samples combinations from &lt;code&gt;xgb_param_grid&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;n_iter=20&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Test only &lt;strong&gt;20 randomly selected hyperparameter combinations&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you used all combinations, there would be 729.&lt;/p&gt;

&lt;p&gt;So this reduces computation considerably.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;random_state=42&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Makes the random selection reproducible.&lt;/p&gt;

&lt;p&gt;If you run the notebook again with the same search space and seed, you should get the same 20 sampled combinations.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;list(...)&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Converts the sampler output into a list so that you can iterate over it multiple times.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Print number of combinations
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Testing &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xgb_param_candidates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; XGBoost parameter sets.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simply tells you how many combinations will actually be tested.&lt;/p&gt;

&lt;p&gt;Expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Testing 20 XGBoost parameter sets.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  4. Create result storage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;xgb_tuning_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An empty list is created to store the performance of each parameter configuration.&lt;/p&gt;

&lt;p&gt;Eventually it will contain approximately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;20 parameter sets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with their:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mean MAE&lt;/li&gt;
&lt;li&gt;Mean RMSE&lt;/li&gt;
&lt;li&gt;Mean R²&lt;/li&gt;
&lt;li&gt;Std R²&lt;/li&gt;
&lt;li&gt;hyperparameter values&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  5. Start parameter search
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;param_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;xgb_param_candidates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This loops through the 20 parameter combinations.&lt;/p&gt;

&lt;p&gt;For example, one iteration might contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n_estimators&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;learning_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_depth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;param_number&lt;/code&gt; gives the configuration number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1
2
3
...
20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;params&lt;/code&gt; contains the actual hyperparameter values.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. Display current parameter set
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Parameter set &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;param_number&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xgb_param_candidates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives progress information.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Parameter set 7/20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It does not affect the model.&lt;/p&gt;




&lt;h3&gt;
  
  
  7. Storage for fold results
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;fold_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the current hyperparameter configuration, this list stores the performance from each of the 5 folds.&lt;/p&gt;

&lt;p&gt;So one parameter set produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fold 1
Fold 2
Fold 3
Fold 4
Fold 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then those five results are averaged.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Stratified 5-Fold Cross-Validation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;skf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_bins&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is very important.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;each XGBoost parameter set&lt;/strong&gt;, you run the model through the same 5 CV folds.&lt;/p&gt;

&lt;p&gt;Therefore:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;20 parameter sets × 5 folds = 100 XGBoost training runs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the main computational cost of the tuning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why use the same folds?
&lt;/h3&gt;

&lt;p&gt;For fair comparison.&lt;/p&gt;

&lt;p&gt;Every hyperparameter configuration sees the same training/validation partitions.&lt;/p&gt;

&lt;p&gt;Therefore, differences in performance are more likely to come from the hyperparameters rather than different data splits.&lt;/p&gt;




&lt;h3&gt;
  
  
  9. Create fold datasets
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;X_fold_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;X_fold_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The indices generated by &lt;code&gt;skf&lt;/code&gt; are used to separate the development dataset into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fold training data&lt;/li&gt;
&lt;li&gt;fold validation data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;.copy()&lt;/code&gt; creates independent DataFrames.&lt;/p&gt;

&lt;p&gt;The independent test set is still untouched.&lt;/p&gt;




&lt;h3&gt;
  
  
  10. Prepare training target
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;y_fold_train_log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;y_log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_numpy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The training target is the &lt;strong&gt;log-transformed PCF&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Earlier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;y_log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log1p&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TARGET&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So XGBoost learns:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;log1p(PCF)&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;rather than raw PCF.&lt;/p&gt;

&lt;p&gt;This helps reduce the influence of extreme values and skew during model training.&lt;/p&gt;




&lt;h3&gt;
  
  
  11. Prepare validation target
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;y_fold_valid_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;TARGET&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_numpy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice something important:&lt;/p&gt;

&lt;p&gt;The validation target is kept in the &lt;strong&gt;original PCF scale&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Training → log scale
Evaluation → original PCF scale
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows the final metrics to have meaningful PCF units.&lt;/p&gt;




&lt;h3&gt;
  
  
  12. Build structured features
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;X_struct_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_struct_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;build_structured_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;X_fold_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;y_fold_train_log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;X_fold_valid&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates the structured feature representation.&lt;/p&gt;

&lt;p&gt;It includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;numeric features&lt;/li&gt;
&lt;li&gt;one-hot encoded categorical features&lt;/li&gt;
&lt;li&gt;country target encoding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important point is that preprocessing is rebuilt &lt;strong&gt;inside every fold&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That helps prevent data leakage.&lt;/p&gt;

&lt;p&gt;In particular, Country target encoding uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;y_fold_train_log&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and generates OOF encodings for the training portion.&lt;/p&gt;




&lt;h3&gt;
  
  
  13. Select SBERT features for the fold
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;X_sbert_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;X_train_sbert&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;X_sbert_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;X_train_sbert&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SBERT embeddings corresponding to the current fold are selected.&lt;/p&gt;

&lt;p&gt;You already generated SBERT embeddings earlier.&lt;/p&gt;

&lt;p&gt;You are not retraining SBERT here.&lt;/p&gt;

&lt;p&gt;You are simply selecting the relevant rows.&lt;/p&gt;




&lt;h3&gt;
  
  
  14. Apply PCA = 50
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;X_sbert_train_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;X_sbert_valid_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;apply_pca_to_sbert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;X_sbert_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;X_sbert_valid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;n_components&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reduces the SBERT representation to &lt;strong&gt;50 dimensions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The important part is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;n_components&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why 50?&lt;/p&gt;

&lt;p&gt;Because your earlier PCA experiment compared:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;50
100
150
200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and &lt;strong&gt;50 performed best among those tested options&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Therefore, 50 is now fixed during XGBoost tuning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Very important viva point
&lt;/h3&gt;

&lt;p&gt;You are &lt;strong&gt;not tuning PCA and XGBoost simultaneously&lt;/strong&gt; here.&lt;/p&gt;

&lt;p&gt;The experimental sequence is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PCA comparison
       ↓
Select PCA = 50
       ↓
Model comparison
       ↓
Choose/tune XGBoost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the experiment easier to interpret.&lt;/p&gt;




&lt;h3&gt;
  
  
  15. Hybrid feature fusion
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;X_hybrid_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fuse_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;X_sbert_train_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;X_struct_train&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;X_hybrid_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fuse_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;X_sbert_valid_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;X_struct_valid&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two feature types are horizontally concatenated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SBERT/PCA features
        +
Structured features
        ↓
Hybrid features
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So XGBoost receives both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;semantic information from text&lt;/li&gt;
&lt;li&gt;explicit structured information such as year, weight, industry, protocol, country, etc.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  16. Create a fresh XGBoost model
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;XGBRegressor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;objective&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reg:squarederror&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;n_jobs&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a &lt;strong&gt;new XGBoost model for every parameter configuration and every fold&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;objective="reg:squarederror"&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Specifies that this is a regression problem using squared-error loss.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;random_state=42&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Makes the model's stochastic behaviour reproducible where applicable.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;n_jobs=-1&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Uses all available CPU cores for parallel computation.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;**params&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is particularly important.&lt;/p&gt;

&lt;p&gt;It inserts the sampled hyperparameters into the model.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;might effectively become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;n_estimators&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;learning_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.03&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;max_depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So each parameter set creates a different XGBoost configuration.&lt;/p&gt;




&lt;h3&gt;
  
  
  17. Train XGBoost
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;X_hybrid_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;y_fold_train_log&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;XGBoost learns the relationship between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hybrid features
        ↓
log1p(PCF)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only the training fold is used here.&lt;/p&gt;

&lt;p&gt;The validation fold is not used for fitting.&lt;/p&gt;




&lt;h2&gt;
  
  
  18. Predict validation data
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;y_pred_log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;X_hybrid_valid&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model predicts PCF on the &lt;strong&gt;log scale&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So these predictions represent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;predicted log1p(PCF)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  19. Convert predictions back to original scale
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;y_pred_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expm1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;y_pred_log&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;expm1&lt;/code&gt; is the inverse of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log1p&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;log1p(PCF)
      ↓
expm1()
      ↓
PCF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now predictions are back in the original PCF scale.&lt;/p&gt;




&lt;h3&gt;
  
  
  20. Prevent negative predictions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;y_pred_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;maximum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;y_pred_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PCF cannot logically be negative.&lt;/p&gt;

&lt;p&gt;This line replaces any negative prediction with zero.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-5 → 0
20 → 20
100 → 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enforces a physically meaningful non-negative prediction.&lt;/p&gt;




&lt;h2&gt;
  
  
  21. Calculate MAE
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;mae&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mean_absolute_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;y_fold_valid_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;y_pred_raw&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MAE = &lt;strong&gt;Mean Absolute Error&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It measures the average absolute difference between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Actual PCF
vs
Predicted PCF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lower is better.&lt;/p&gt;

&lt;p&gt;Because this is calculated on the raw scale, its unit is approximately:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;kg CO₂e&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  22. Calculate RMSE
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;rmse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;mean_squared_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;y_fold_valid_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;y_pred_raw&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;RMSE = &lt;strong&gt;Root Mean Squared Error&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It penalises large errors more strongly than MAE.&lt;/p&gt;

&lt;p&gt;Lower is better.&lt;/p&gt;

&lt;p&gt;This is especially useful when you want to identify whether the model makes some very large prediction errors.&lt;/p&gt;




&lt;h2&gt;
  
  
  23. Calculate R²
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;r2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;r2_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;y_fold_valid_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;y_pred_raw&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;R² measures how much of the variation in the target is explained by the model relative to a baseline.&lt;/p&gt;

&lt;p&gt;Higher is generally better.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;R² = 0.70
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means the model explains substantial variation in the validation data, relative to the baseline, but &lt;strong&gt;does not mean 70% of every individual prediction is correct&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  24. Store fold performance
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;fold_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MAE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mae&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RMSE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rmse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r2&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The results for the current fold are stored.&lt;/p&gt;

&lt;p&gt;After five folds, you have five sets of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MAE
RMSE
R²
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;for that particular hyperparameter configuration.&lt;/p&gt;




&lt;h1&gt;
  
  
  25. Average the 5 folds
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;fold_results_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;fold_results&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Converts the fold results into a DataFrame.&lt;/p&gt;

&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;mean_mae&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fold_results_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MAE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;calculates average MAE across the five folds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;mean_rmse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fold_results_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RMSE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;calculates average RMSE.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;mean_r2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fold_results_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;calculates average R².&lt;/p&gt;

&lt;p&gt;And:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;std_r2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fold_results_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;std&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;calculates the standard deviation of R² across folds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why &lt;code&gt;Std_R2&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;It gives an indication of &lt;strong&gt;performance stability across folds&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mean R² = 0.70
Std R²  = 0.02
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;suggests relatively consistent fold performance.&lt;/p&gt;

&lt;p&gt;Whereas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mean R² = 0.70
Std R²  = 0.20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;suggests much greater variation between folds.&lt;/p&gt;




&lt;h1&gt;
  
  
  26. Store the parameter-set result
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Parameter_Set&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;param_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mean_MAE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mean_mae&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mean_RMSE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mean_rmse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mean_R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mean_r2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Std_R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;std_r2&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates one summary record for one hyperparameter configuration.&lt;/p&gt;

&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;adds the actual hyperparameter values.&lt;/p&gt;

&lt;p&gt;So one row eventually looks conceptually like:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter Set&lt;/th&gt;
&lt;th&gt;Mean MAE&lt;/th&gt;
&lt;th&gt;Mean RMSE&lt;/th&gt;
&lt;th&gt;Mean R²&lt;/th&gt;
&lt;th&gt;Std R²&lt;/th&gt;
&lt;th&gt;n_estimators&lt;/th&gt;
&lt;th&gt;learning_rate&lt;/th&gt;
&lt;th&gt;max_depth&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;0.05&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  27. Save result
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;xgb_tuning_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adds the current parameter configuration to the overall results list.&lt;/p&gt;

&lt;p&gt;After all 20 configurations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;xgb_tuning_results
        ↓
20 parameter-set results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  28. Print progress
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Mean R²: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mean_r2&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; | &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mean RMSE: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mean_rmse&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Displays the average performance for the current configuration.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mean R²: 0.7123 | Mean RMSE: 85000.25
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is just progress output.&lt;/p&gt;




&lt;h1&gt;
  
  
  29. Create final tuning DataFrame
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;xgb_tuning_results_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;xgb_tuning_results&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Converts all 20 parameter-set results into a DataFrame.&lt;/p&gt;




&lt;h3&gt;
  
  
  30. Sort by Mean R²
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;xgb_tuning_results_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;xgb_tuning_results_df&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mean_R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ascending&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;drop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The configurations are sorted from highest to lowest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mean R²
   ↓
highest first
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Therefore, the first row represents the configuration with the &lt;strong&gt;highest mean R² among the 20 tested configurations&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;reset_index(drop=True)&lt;/code&gt; simply gives the sorted DataFrame a clean index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0
1
2
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  31. Display final results
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;xgb_tuning_results_df&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Displays the complete tuning table.&lt;/p&gt;

&lt;p&gt;You can now compare all 20 configurations.&lt;/p&gt;




&lt;h3&gt;
  
  
  The complete workflow
&lt;/h3&gt;

&lt;p&gt;The entire cell can be understood as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;729 possible combinations
          ↓
Randomly sample 20
          ↓
For each parameter set
          ↓
    5-fold CV
          ↓
Build structured features
          ↓
OOF country encoding
          ↓
SBERT features
          ↓
PCA = 50
          ↓
Hybrid fusion
          ↓
Train XGBoost
          ↓
Predict validation fold
          ↓
Convert log prediction → raw PCF
          ↓
MAE / RMSE / R²
          ↓
Average 5 folds
          ↓
Repeat for all 20 sets
          ↓
Rank configurations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The most important viva answer
&lt;/h3&gt;

&lt;p&gt;If your supervisor asks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“What exactly did you do in this cell?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I performed randomized hyperparameter tuning for XGBoost. I defined a search space containing six hyperparameters, which gives 729 possible combinations, but instead of exhaustively testing all of them, I randomly sampled 20 configurations using &lt;code&gt;ParameterSampler&lt;/code&gt;. For each configuration, I used the same stratified 5-fold cross-validation and the same hybrid feature pipeline: OOF country target encoding, SBERT reduced to 50 PCA components, and structured features. Only the XGBoost hyperparameters changed. I then calculated MAE, RMSE, and R² on the original PCF scale and ranked the 20 configurations based on mean R².”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Challenging viva questions
&lt;/h3&gt;

&lt;h4&gt;
  
  
  “Why did you test only 20 combinations when there are 729?”
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“Testing all 729 combinations with 5-fold CV would require 3,645 model fits. I therefore used randomized search to explore 20 combinations within the predefined search space while keeping the computational cost manageable. I acknowledge that this does not guarantee finding the global optimum.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  “Why is this called tuning?”
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“Because I am selecting model hyperparameters based on cross-validation performance. The model's learned parameters are still estimated during training; these are settings controlling how that learning process behaves.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  “Did you use the test set during tuning?”
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“No. The independent test set was kept separate. Hyperparameter selection was performed using cross-validation within the development data.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is one of the most important answers.&lt;/p&gt;

&lt;h4&gt;
  
  
  “Why did you keep PCA fixed at 50?”
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“I previously compared several PCA dimensions using cross-validation. Among the tested values, 50 produced the best validation performance, so I fixed PCA at 50 before tuning XGBoost. This isolates the effect of XGBoost hyperparameters.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  “Are you guaranteed to find the best XGBoost parameters?”
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“No. I only identify the best-performing configuration among the 20 sampled configurations. A larger or exhaustive search could potentially find a better configuration.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  “Why do you calculate the metrics after &lt;code&gt;expm1()&lt;/code&gt;?”
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“Because the model is trained on &lt;code&gt;log1p(PCF)&lt;/code&gt; to manage the skewed target distribution, but I want MAE, RMSE and R² to represent performance in the original PCF scale, which is more interpretable.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  “Why do you use OOF target encoding here?”
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“Country target encoding uses the target variable, so directly calculating the country mean from the same observations could leak target information into the feature. OOF encoding ensures each training observation is encoded using information from other training folds rather than its own target.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  One important distinction to remember
&lt;/h3&gt;

&lt;p&gt;There are &lt;strong&gt;three different things&lt;/strong&gt; happening:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Hyperparameter search&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;20 XGBoost configurations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Cross-validation&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5 folds per configuration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Independent test&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Not touched during tuning
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Therefore:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;20 × 5 = 100 CV model fits, followed later by one final evaluation on the untouched test set.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That distinction is very likely to matter in your viva.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Cell-11-XGBOOST HYPERPARAMETER SEARCH SPACE</title>
      <dc:creator>TS</dc:creator>
      <pubDate>Mon, 14 Sep 2026 08:29:05 +0000</pubDate>
      <link>https://dev.to/ts2025/cell-11-xgboost-hyperparameter-search-space-2ndd</link>
      <guid>https://dev.to/ts2025/cell-11-xgboost-hyperparameter-search-space-2ndd</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ============================================================
# XGBOOST HYPERPARAMETER SEARCH SPACE
# ============================================================
&lt;/span&gt;
&lt;span class="n"&gt;xgb_param_grid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n_estimators&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;learning_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.03&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.08&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_depth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;min_child_weight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subsample&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;colsample_bytree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  XGBoost Hyperparameter Search Space
&lt;/h3&gt;

&lt;p&gt;This cell defines the &lt;strong&gt;hyperparameter search space&lt;/strong&gt; for XGBoost.&lt;/p&gt;

&lt;p&gt;The important distinction is:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="xgbs1"&lt;br&gt;
Model parameters&lt;br&gt;
→ learned automatically during training&lt;/p&gt;

&lt;p&gt;Hyperparameters&lt;br&gt;
→ chosen before training&lt;br&gt;
→ control how the model learns&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Here, you are defining several possible values for each hyperparameter so that a later search procedure can determine which combination performs best.

### 1. Create the parameter grid



```python id="xgb01"
xgb_param_grid = {
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This creates a Python dictionary called &lt;code&gt;xgb_param_grid&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The dictionary has the structure:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="xgb02"&lt;br&gt;
Hyperparameter → possible values&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


For example:



```text id="xgb03"
n_estimators → [200, 300, 500]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A later search algorithm can try combinations from this space.&lt;/p&gt;


&lt;h3&gt;
  
  
  2. &lt;code&gt;n_estimators&lt;/code&gt;
&lt;/h3&gt;



&lt;p&gt;```python id="xgb04"&lt;br&gt;
"n_estimators": [200, 300, 500],&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Controls the **number of boosting trees/iterations**.

You are considering:



```text id="xgb05"
200
300
500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="xgb06"&lt;br&gt;
More trees&lt;br&gt;
   ↓&lt;br&gt;
potentially more learning capacity&lt;br&gt;
   ↓&lt;br&gt;
but also more computation&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Too many trees can contribute to overfitting depending on the other settings.

#### Viva answer

&amp;gt; “I search over 200, 300 and 500 estimators to investigate whether a smaller or larger boosting ensemble gives better validation performance.”

---

### 3. `learning_rate`



```python id="xgb07"
"learning_rate": [0.03, 0.05, 0.08],
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Controls how much each new tree contributes to the overall prediction.&lt;/p&gt;

&lt;p&gt;You test:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="xgb08"&lt;br&gt;
0.03&lt;br&gt;
0.05&lt;br&gt;
0.08&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


A smaller learning rate means the model learns more gradually.

There is generally a trade-off between:



```text id="xgb09"
lower learning rate
+
more trees

vs.

higher learning rate
+
fewer trees
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That is why &lt;code&gt;learning_rate&lt;/code&gt; should not be considered independently of &lt;code&gt;n_estimators&lt;/code&gt;.&lt;/p&gt;


&lt;h3&gt;
  
  
  4. &lt;code&gt;max_depth&lt;/code&gt;
&lt;/h3&gt;



&lt;p&gt;```python id="xgb10"&lt;br&gt;
"max_depth": [3, 4, 6],&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Controls the maximum depth of each decision tree.

You test:



```text id="xgb11"
3
4
6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Smaller depth
&lt;/h3&gt;

&lt;p&gt;Produces simpler trees and can reduce model complexity.&lt;/p&gt;
&lt;h3&gt;
  
  
  Larger depth
&lt;/h3&gt;

&lt;p&gt;Allows the model to capture more complex interactions, but can increase overfitting risk.&lt;/p&gt;
&lt;h4&gt;
  
  
  Viva answer
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“&lt;code&gt;max_depth&lt;/code&gt; controls tree complexity. I include shallow and deeper trees to evaluate the trade-off between capturing complex relationships and controlling overfitting.”&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h3&gt;
  
  
  5. &lt;code&gt;min_child_weight&lt;/code&gt;
&lt;/h3&gt;



&lt;p&gt;```python id="xgb12"&lt;br&gt;
"min_child_weight": [1, 3, 5],&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Controls the minimum amount of weight required in a child node before XGBoost makes a further split.

You test:



```text id="xgb13"
1
3
5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Increasing this value generally makes the model more conservative about creating new branches.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="xgb14"&lt;br&gt;
Lower min_child_weight&lt;br&gt;
→ easier to split&lt;br&gt;
→ more complex trees&lt;/p&gt;

&lt;p&gt;Higher min_child_weight&lt;br&gt;
→ harder to split&lt;br&gt;
→ more conservative trees&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This can therefore help control overfitting.

---

### 6. `subsample`



```python id="xgb15"
"subsample": [0.7, 0.8, 1.0],
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Controls the proportion of training observations used when constructing each boosting tree.&lt;/p&gt;

&lt;p&gt;You test:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="xgb16"&lt;br&gt;
0.7 → 70%&lt;br&gt;
0.8 → 80%&lt;br&gt;
1.0 → 100%&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


For example:



```text id="xgb17"
subsample = 0.8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;means approximately 80% of the available training observations are sampled for a boosting iteration.&lt;/p&gt;

&lt;p&gt;Using less than 1.0 introduces randomness and can help reduce overfitting.&lt;/p&gt;


&lt;h3&gt;
  
  
  7. &lt;code&gt;colsample_bytree&lt;/code&gt;
&lt;/h3&gt;



&lt;p&gt;```python id="xgb18"&lt;br&gt;
"colsample_bytree": [0.7, 0.8, 1.0]&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Controls the proportion of features considered for each tree.

You test:



```text id="xgb19"
0.7 → 70% of features
0.8 → 80% of features
1.0 → 100% of features
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is particularly relevant in your project because your hybrid representation can contain a relatively large number of features:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="xgb20"&lt;br&gt;
PCA-reduced SBERT&lt;br&gt;
+&lt;br&gt;
Structured features&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Using feature subsampling can introduce additional randomness and potentially improve generalisation.

---

# How Large Is Your Search Space?

You have:



```text id="xgb21"
n_estimators       → 3
learning_rate      → 3
max_depth          → 3
min_child_weight   → 3
subsample          → 3
colsample_bytree   → 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Therefore, a full Cartesian grid contains:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="xgb22"&lt;br&gt;
3 × 3 × 3 × 3 × 3 × 3&lt;br&gt;
= 729&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


possible hyperparameter combinations.

That's an important number for your viva.

### Viva question: How many combinations are there?

&amp;gt; “There are 729 possible combinations because each of the six hyperparameters has three candidate values, giving \(3^6 = 729\) combinations.”

If you use ordinary `GridSearchCV` with 5-fold CV, that could mean:



```text id="xgb23"
729 combinations
× 5 folds
= 3,645 model fits
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So this is a &lt;strong&gt;fairly large search&lt;/strong&gt; computationally.&lt;/p&gt;


&lt;h1&gt;
  
  
  Why These Hyperparameters?
&lt;/h1&gt;

&lt;p&gt;These parameters cover several important aspects of XGBoost behaviour:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hyperparameter&lt;/th&gt;
&lt;th&gt;Controls&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;n_estimators&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Number of boosting trees&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;learning_rate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Contribution of each tree&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_depth&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tree complexity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;min_child_weight&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Minimum requirement for further splitting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;subsample&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fraction of observations used&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;colsample_bytree&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fraction of features used&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So you are not tuning only one aspect of the model.&lt;/p&gt;

&lt;p&gt;You are exploring:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="xgb24"&lt;br&gt;
Learning speed&lt;br&gt;
+&lt;br&gt;
Model complexity&lt;br&gt;
+&lt;br&gt;
Number of trees&lt;br&gt;
+&lt;br&gt;
Row sampling&lt;br&gt;
+&lt;br&gt;
Feature sampling&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


---

# Very Important: This Cell Does NOT Perform the Search

This is a likely examiner trap.

This cell only defines:



```text id="xgb25"
SEARCH SPACE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; actually find the best parameters.&lt;/p&gt;

&lt;p&gt;You still need a search method such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;GridSearchCV&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;RandomizedSearchCV&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or another optimisation procedure.&lt;/p&gt;

&lt;p&gt;So if the examiner asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Have you performed hyperparameter tuning here?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“This cell defines the candidate search space. The actual tuning is performed in the subsequent hyperparameter-search step.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Do &lt;strong&gt;not&lt;/strong&gt; say the parameters are already optimised just because you created the grid.&lt;/p&gt;




&lt;h1&gt;
  
  
  Grid Search vs Random Search
&lt;/h1&gt;

&lt;p&gt;Because you have &lt;strong&gt;729 combinations&lt;/strong&gt;, an examiner may ask why you chose grid search or random search.&lt;/p&gt;

&lt;h3&gt;
  
  
  Grid Search
&lt;/h3&gt;

&lt;p&gt;Tests every combination.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="xgb26"&lt;br&gt;
729 combinations&lt;br&gt;
→ all are evaluated&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Advantage:

&amp;gt; systematic and exhaustive within the specified grid.

Disadvantage:

&amp;gt; computationally expensive.

### Randomized Search

Samples a specified number of combinations.

For example:



```text id="xgb27"
729 possible combinations
        ↓
randomly sample 50
        ↓
evaluate only 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Advantage:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;much cheaper computationally.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Disadvantage:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;does not evaluate every possible combination.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Strong viva answer
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“The search space contains 729 combinations, so an exhaustive grid search can be computationally expensive, especially when combined with cross-validation. A randomized search can be more computationally efficient, while grid search is appropriate when exhaustive evaluation of the defined candidate values is required.”&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h1&gt;
  
  
  Very Important for Your Project: Avoiding Leakage During Hyperparameter Search
&lt;/h1&gt;

&lt;p&gt;Because your project uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;target encoding&lt;/li&gt;
&lt;li&gt;winsorization&lt;/li&gt;
&lt;li&gt;PCA&lt;/li&gt;
&lt;li&gt;scaling&lt;/li&gt;
&lt;li&gt;cross-validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;you must be careful about &lt;strong&gt;where each transformation is fitted&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The ideal principle is:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="xgb28"&lt;br&gt;
Training fold&lt;br&gt;
   ↓&lt;br&gt;
Fit preprocessing&lt;br&gt;
   ↓&lt;br&gt;
Fit XGBoost&lt;br&gt;
   ↓&lt;br&gt;
Validation fold&lt;br&gt;
   ↓&lt;br&gt;
Evaluate&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Not:



```text id="xgb29"
Entire development dataset
   ↓
Fit preprocessing
   ↓
Cross-validation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The second approach can produce optimistic results if the preprocessing learns from validation observations.&lt;/p&gt;
&lt;h3&gt;
  
  
  Particularly important for your Country Target Encoding
&lt;/h3&gt;

&lt;p&gt;Because country target encoding uses the PCF target, it has a direct leakage risk.&lt;/p&gt;

&lt;p&gt;Your OOF approach is therefore especially important during CV.&lt;/p&gt;


&lt;h1&gt;
  
  
  Why Tune XGBoost After Selecting PCA = 50?
&lt;/h1&gt;

&lt;p&gt;Your experimental sequence is becoming:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="xgb30"&lt;br&gt;
Step 1&lt;br&gt;
Feature engineering&lt;br&gt;
      ↓&lt;br&gt;
SBERT + structured&lt;/p&gt;

&lt;p&gt;Step 2&lt;br&gt;
PCA selection&lt;br&gt;
50 / 100 / 150 / 200&lt;br&gt;
      ↓&lt;br&gt;
Choose 50&lt;/p&gt;

&lt;p&gt;Step 3&lt;br&gt;
Model comparison&lt;br&gt;
Linear / Ridge / SVR / RF / XGBoost / CatBoost&lt;br&gt;
      ↓&lt;br&gt;
Identify strong candidate&lt;/p&gt;

&lt;p&gt;Step 4&lt;br&gt;
XGBoost hyperparameter search&lt;br&gt;
      ↓&lt;br&gt;
Find better XGBoost configuration&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This is a logical progression.

You first establish the feature representation, then compare algorithms, and then tune the selected candidate model.

---

# Presentation Wording

&amp;gt; “This cell defines the hyperparameter search space for XGBoost. I vary six parameters: the number of estimators, learning rate, maximum tree depth, minimum child weight, row subsampling, and feature subsampling. Each parameter has three candidate values, resulting in 729 possible combinations. These parameters control different aspects of model complexity, learning rate, and regularisation. This cell only defines the search space; the actual hyperparameter optimisation is performed in the subsequent search procedure using cross-validation.”

### The 5 viva points to memorise

1. **This is a search space, not the search itself.**
2. **There are 729 combinations.**
3. `n_estimators` + `learning_rate` control boosting capacity and learning speed.
4. `max_depth` + `min_child_weight` control tree complexity.
5. `subsample` + `colsample_bytree` introduce row/feature sampling and can help generalisation.

For your XGBoost tuning

* Explain why 729 trials may be inefficient
* Show leakage-safe tuning structure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
    </item>
    <item>
      <title>Cell-10-PCA: 50 + OOF Country Target Encoding</title>
      <dc:creator>TS</dc:creator>
      <pubDate>Mon, 14 Sep 2026 08:24:24 +0000</pubDate>
      <link>https://dev.to/ts2025/cell-10-pca-50-oof-country-target-encoding-3702</link>
      <guid>https://dev.to/ts2025/cell-10-pca-50-oof-country-target-encoding-3702</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ============================================================
# MODEL COMPARISON
# PCA = 50 + OOF Country Target Encoding
# Log Target + Stratified 5-Fold CV
# ============================================================
&lt;/span&gt;
&lt;span class="n"&gt;model_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;


&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model_template&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Running: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;model_fold_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;


    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;skf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_bins&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt;

        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;        &lt;span class="c1"&gt;# 1. Fold data
&lt;/span&gt;        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;
        &lt;span class="n"&gt;X_fold_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;X_fold_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;y_fold_train_log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;y_log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_numpy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;y_fold_valid_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;TARGET&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_numpy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;


        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;        &lt;span class="c1"&gt;# 2. Structured features
&lt;/span&gt;        &lt;span class="c1"&gt;#    Country = OOF Target Encoding
&lt;/span&gt;        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;
        &lt;span class="n"&gt;X_struct_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_struct_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;build_structured_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;X_fold_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;y_fold_train_log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;X_fold_valid&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;


        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;        &lt;span class="c1"&gt;# 3. SBERT features
&lt;/span&gt;        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;
        &lt;span class="n"&gt;X_sbert_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X_train_sbert&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;X_sbert_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X_train_sbert&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;        &lt;span class="c1"&gt;# 4. PCA = 50
&lt;/span&gt;        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;X_sbert_train_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;X_sbert_valid_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;apply_pca_to_sbert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;X_sbert_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;X_sbert_valid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;n_components&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;


        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;        &lt;span class="c1"&gt;# 5. Hybrid feature fusion
&lt;/span&gt;        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;
        &lt;span class="n"&gt;X_hybrid_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fuse_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;X_sbert_train_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;X_struct_train&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;X_hybrid_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fuse_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;X_sbert_valid_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;X_struct_valid&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;


        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;        &lt;span class="c1"&gt;# 6. Fresh model instance
&lt;/span&gt;        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_template&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;        &lt;span class="c1"&gt;# 7. Scaling
&lt;/span&gt;        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;
        &lt;span class="n"&gt;scale_features&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model_name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Linear Regression&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ridge Regression&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SVR&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;


        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;        &lt;span class="c1"&gt;# 8. Evaluate
&lt;/span&gt;        &lt;span class="c1"&gt;# ----------------------------------------------------
&lt;/span&gt;
        &lt;span class="n"&gt;mae&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rmse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;evaluate_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;X_train&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;X_hybrid_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;y_train_log&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;y_fold_train_log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;X_valid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;X_hybrid_valid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;y_valid_raw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;y_fold_valid_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;scale_features&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;scale_features&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;


        &lt;span class="n"&gt;model_fold_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MAE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mae&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RMSE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rmse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r2&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;


        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Fold &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MAE=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mae&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RMSE=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rmse&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R²=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="c1"&gt;# Store all folds
&lt;/span&gt;    &lt;span class="n"&gt;model_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model_fold_results&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# ============================================================
# MODEL COMPARISON SUMMARY
# ============================================================
&lt;/span&gt;
&lt;span class="n"&gt;model_results_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model_results&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;model_summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model_results_df&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Mean_MAE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MAE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Mean_RMSE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RMSE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Mean_R2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Std_R2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;std&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset_index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mean_R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ascending&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nf"&gt;display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_summary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Model Comparison
&lt;/h3&gt;

&lt;p&gt;This is the &lt;strong&gt;main model-comparison cell&lt;/strong&gt; in your notebook.&lt;/p&gt;

&lt;p&gt;At this stage, you have already decided:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PCA = 50 components&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Country = OOF target encoding&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Target = log-transformed PCF&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Evaluation = stratified 5-fold cross-validation&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Features = SBERT + structured features&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now the question becomes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Which regression algorithm performs best with this final hybrid feature representation?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Your pipeline is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Development data
      ↓
Stratified 5-Fold CV
      ↓
For each model
      ↓
Build structured features
      +
SBERT features
      ↓
PCA = 50
      ↓
Hybrid features
      ↓
Train model
      ↓
Predict validation fold
      ↓
MAE / RMSE / R²
      ↓
Repeat for all 5 folds
      ↓
Average results
      ↓
Compare models
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. Create result storage
&lt;/h3&gt;



&lt;p&gt;```python id="mc01"&lt;br&gt;
model_results = []&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Creates an empty list where all model/fold evaluation results will be stored.

For example, one result might eventually look like:



```text id="x8g5dk"
{
    "Model": "Random Forest",
    "Fold": 1,
    "MAE": ...,
    "RMSE": ...,
    "R2": ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Because you have multiple models and five folds, the list will contain one record for every &lt;strong&gt;model × fold&lt;/strong&gt; combination.&lt;/p&gt;


&lt;h3&gt;
  
  
  2. Loop through every model
&lt;/h3&gt;



&lt;p&gt;```python id="mc02"&lt;br&gt;
for model_name, model_template in models.items():&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This loops through the model dictionary you created earlier.

For example:



```text id="2nqk3c"
Linear Regression
Ridge Regression
SVR
Random Forest
XGBoost
CatBoost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For each model, the entire five-fold evaluation is performed.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;code&gt;model_name&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;The readable model name:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="mc03"&lt;br&gt;
"Random Forest"&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


#### `model_template`

The corresponding model object/configuration.



```text id="mc04"
RandomForestRegressor(...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Print the current model
&lt;/h3&gt;



&lt;p&gt;```python id="mc05"&lt;br&gt;
print(f"\nRunning: {model_name}")&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Displays which model is currently being evaluated.

For example:



```text id="v7d2fa"
Running: Random Forest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;\n&lt;/code&gt; adds a blank line before the message to make the output easier to read.&lt;/p&gt;


&lt;h3&gt;
  
  
  4. Create fold-result storage
&lt;/h3&gt;



&lt;p&gt;```python id="mc06"&lt;br&gt;
model_fold_results = []&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Creates an empty list for the current model.

For example, while evaluating Random Forest:



```text id="f8n1x4"
Random Forest
    ↓
Fold 1
Fold 2
Fold 3
Fold 4
Fold 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The five results are stored in this list.&lt;/p&gt;


&lt;h1&gt;
  
  
  Five-Fold Cross-Validation
&lt;/h1&gt;



&lt;p&gt;```python id="mc07"&lt;br&gt;
for fold, (train_idx, valid_idx) in enumerate(&lt;br&gt;
    skf.split(train_df, target_bins),&lt;br&gt;
    start=1&lt;br&gt;
):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This generates the five cross-validation folds.

You previously created:



```python id="mc08"
skf = StratifiedKFold(
    n_splits=5,
    shuffle=True,
    random_state=RANDOM_STATE
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```python id="mc09"&lt;br&gt;
target_bins = pd.qcut(...)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


So here you are actually using them.

The output of each iteration is:



```text id="mc10"
train_idx
valid_idx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;train_idx&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Rows used to train the model in this fold.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;valid_idx&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Rows held out for validation in this fold.&lt;/p&gt;

&lt;p&gt;The process looks like:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="q4c7w8"&lt;br&gt;
Fold 1 → 80% train / 20% validation&lt;br&gt;
Fold 2 → 80% train / 20% validation&lt;br&gt;
Fold 3 → 80% train / 20% validation&lt;br&gt;
Fold 4 → 80% train / 20% validation&lt;br&gt;
Fold 5 → 80% train / 20% validation&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Each observation is used for validation once.

---

# 1. Fold Data



```python id="mc11"
X_fold_train = train_df.iloc[train_idx].copy()
X_fold_valid = train_df.iloc[valid_idx].copy()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These lines select the actual rows for the current fold.&lt;/p&gt;
&lt;h3&gt;
  
  
  Training fold
&lt;/h3&gt;



&lt;p&gt;```python id="mc12"&lt;br&gt;
X_fold_train = train_df.iloc[train_idx].copy()&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Selects the training observations.

### Validation fold



```python id="mc13"
X_fold_valid = train_df.iloc[valid_idx].copy()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Selects the validation observations.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;.copy()&lt;/code&gt; creates independent DataFrames so later operations do not unintentionally modify the original &lt;code&gt;train_df&lt;/code&gt;.&lt;/p&gt;


&lt;h3&gt;
  
  
  5. Training target
&lt;/h3&gt;



&lt;p&gt;```python id="mc14"&lt;br&gt;
y_fold_train_log = (&lt;br&gt;
    y_log.iloc[train_idx].to_numpy()&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Selects the log-transformed PCF values corresponding to the training fold.

So the model receives:



```text id="mc15"
X_fold_train
+
y_fold_train_log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Validation target
&lt;/h3&gt;



&lt;p&gt;```python id="mc16"&lt;br&gt;
y_fold_valid_raw = (&lt;br&gt;
    train_df.iloc[valid_idx][TARGET].to_numpy()&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Selects the **original/raw PCF values** for the validation observations.

This is important because you eventually evaluate predictions on the original PCF scale.

So:



```text id="mc17"
Training target
→ log scale

Validation target
→ raw PCF scale
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  2. Build Structured Features
&lt;/h1&gt;



&lt;p&gt;```python id="mc18"&lt;br&gt;
X_struct_train, X_struct_valid = (&lt;br&gt;
    build_structured_features(&lt;br&gt;
        X_fold_train,&lt;br&gt;
        y_fold_train_log,&lt;br&gt;
        X_fold_valid&lt;br&gt;
    )&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This calls your structured-feature builder.

It processes the current fold's structured data.

The function performs:



```text id="mc19"
Year + Weight
       ↓
Numeric features

Industry + Protocol + Stage-level availability
       ↓
One-Hot Encoding

Country + training target
       ↓
OOF Target Encoding
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;and returns:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="mc20"&lt;br&gt;
X_struct_train&lt;br&gt;
X_struct_valid&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


### Why is this inside the fold?

This is a **critical leakage-control mechanism**.

The preprocessing is rebuilt for every fold.

For example:



```text id="mc21"
Fold 1
   ↓
Fold-1 training data
   ↓
Fit preprocessing
   ↓
Transform Fold-1 validation


Fold 2
   ↓
Fold-2 training data
   ↓
Fit preprocessing
   ↓
Transform Fold-2 validation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You are not fitting the structured preprocessing once on the entire development dataset.&lt;/p&gt;
&lt;h4&gt;
  
  
  Viva answer
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“I perform structured feature engineering separately inside each fold because some transformations learn information from the data. Fitting them on the entire development dataset before cross-validation could allow information from the validation fold to influence the transformation.”&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h1&gt;
  
  
  3. Select SBERT Features
&lt;/h1&gt;



&lt;p&gt;```python id="mc22"&lt;br&gt;
X_sbert_train = X_train_sbert[train_idx]&lt;br&gt;
X_sbert_valid = X_train_sbert[valid_idx]&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


You already generated SBERT embeddings for the development dataset.

Here, you simply select the embeddings corresponding to the current fold.

### Training



```python id="mc23"
X_sbert_train = X_train_sbert[train_idx]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Gets the training observations' SBERT embeddings.&lt;/p&gt;
&lt;h3&gt;
  
  
  Validation
&lt;/h3&gt;



&lt;p&gt;```python id="mc24"&lt;br&gt;
X_sbert_valid = X_train_sbert[valid_idx]&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Gets the validation observations' SBERT embeddings.

You are not fitting SBERT here.

It is a **pretrained fixed encoder**.

---

# 4. PCA = 50



```python id="mc25"
(
    X_sbert_train_pca,
    X_sbert_valid_pca,
    _
) = apply_pca_to_sbert(
    X_sbert_train,
    X_sbert_valid,
    n_components=50
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now you apply the PCA configuration selected in your previous experiment.&lt;/p&gt;

&lt;p&gt;You already compared:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="mc26"&lt;br&gt;
50&lt;br&gt;
100&lt;br&gt;
150&lt;br&gt;
200&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


and found that:



```text id="mc27"
50 components
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;performed best among those tested.&lt;/p&gt;

&lt;p&gt;Therefore, this final model-comparison experiment fixes:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```python id="mc28"&lt;br&gt;
n_components=50&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


### What happens inside?

The PCA function does:



```text id="mc29"
Fold-training SBERT
      ↓
fit PCA
      ↓
50 components

Fold-validation SBERT
      ↓
transform using same PCA
      ↓
50 components
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Why is &lt;code&gt;_&lt;/code&gt; used?
&lt;/h3&gt;



&lt;p&gt;```python id="mc30"&lt;br&gt;
_&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


The PCA function returns:



```text id="mc31"
X_train_pca
X_valid_pca
pca
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But here you don't need the fitted PCA object after this operation.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```python id="mc32"&lt;br&gt;
_&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


means:

&amp;gt; “I intentionally don't need this returned value.”

This is a common Python convention.

---

# 5. Hybrid Feature Fusion



```python id="mc33"
X_hybrid_train = fuse_features(
    X_sbert_train_pca,
    X_struct_train
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Combines:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="mc34"&lt;br&gt;
Reduced SBERT features&lt;br&gt;
+&lt;br&gt;
Structured features&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


into one feature matrix.

Similarly:



```python id="mc35"
X_hybrid_valid = fuse_features(
    X_sbert_valid_pca,
    X_struct_valid
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;creates the validation hybrid features.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="mc36"&lt;br&gt;
        SBERT&lt;br&gt;
          +&lt;br&gt;
     Structured&lt;br&gt;
          ↓&lt;br&gt;
       Hybrid&lt;br&gt;
          ↓&lt;br&gt;
     ML algorithm&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This is the representation on which all models are compared.

---

# 6. Fresh Model Instance



```python id="mc37"
model = clone(model_template)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is an important line.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;clone()&lt;/code&gt; creates a &lt;strong&gt;fresh, unfitted copy&lt;/strong&gt; of the model configuration.&lt;/p&gt;

&lt;p&gt;Why is this important?&lt;/p&gt;

&lt;p&gt;Because you are repeatedly training the model across five folds.&lt;/p&gt;

&lt;p&gt;You do not want the model to carry learned information from a previous fold.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="mc38"&lt;br&gt;
Fold 1&lt;br&gt;
Fresh model&lt;br&gt;
   ↓&lt;br&gt;
fit&lt;br&gt;
   ↓&lt;br&gt;
discard&lt;/p&gt;

&lt;p&gt;Fold 2&lt;br&gt;
Fresh model&lt;br&gt;
   ↓&lt;br&gt;
fit&lt;br&gt;
   ↓&lt;br&gt;
discard&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


### Viva question: Why use `clone()`?

&amp;gt; “I use `clone()` to create a fresh unfitted estimator for each fold. This prevents parameters learned in one fold from carrying over into another fold.”

This is a **very good technical point**.

---

# 7. Decide Whether Scaling Is Needed



```python id="mc39"
scale_features = model_name in [
    "Linear Regression",
    "Ridge Regression",
    "SVR"
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This automatically determines whether the current model should receive scaled features.&lt;/p&gt;

&lt;p&gt;If the model is:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="mc40"&lt;br&gt;
Linear Regression&lt;br&gt;
Ridge Regression&lt;br&gt;
SVR&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


then:



```text id="mc41"
scale_features = True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="mc42"&lt;br&gt;
Random Forest&lt;br&gt;
XGBoost&lt;br&gt;
CatBoost&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


it becomes:



```text id="mc43"
scale_features = False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Why?
&lt;/h3&gt;

&lt;p&gt;Because feature scaling is more important for models that depend on feature magnitudes, distances, or coefficient regularisation.&lt;/p&gt;

&lt;p&gt;Tree-based models generally do not require standardisation.&lt;/p&gt;
&lt;h4&gt;
  
  
  Strong viva answer
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“I enable scaling for the linear and SVR models because these algorithms can be sensitive to feature scale, particularly Ridge and RBF-SVR. Tree-based ensemble models generally do not require standardisation, so I leave scaling disabled for them.”&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h1&gt;
  
  
  8. Evaluate the Model
&lt;/h1&gt;



&lt;p&gt;```python id="mc44"&lt;br&gt;
mae, rmse, r2 = evaluate_model(&lt;br&gt;
    model=model,&lt;br&gt;
    X_train=X_hybrid_train,&lt;br&gt;
    y_train_log=y_fold_train_log,&lt;br&gt;
    X_valid=X_hybrid_valid,&lt;br&gt;
    y_valid_raw=y_fold_valid_raw,&lt;br&gt;
    scale_features=scale_features&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This calls your previously defined `evaluate_model()` function.

The function:



```text id="mc45"
1. Optionally scales features
2. Fits the model
3. Predicts log PCF
4. Converts predictions back to raw PCF
5. Prevents negative predictions
6. Calculates MAE
7. Calculates RMSE
8. Calculates R²
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The returned values are:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="mc46"&lt;br&gt;
mae&lt;br&gt;
rmse&lt;br&gt;
r2&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


---

# Store Fold Results



```python id="mc47"
model_fold_results.append({
    "Model": model_name,
    "Fold": fold,
    "MAE": mae,
    "RMSE": rmse,
    "R2": r2
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A dictionary containing the current fold's results is added to the list.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="mc48"&lt;br&gt;
Model = Random Forest&lt;br&gt;
Fold = 1&lt;br&gt;
MAE = ...&lt;br&gt;
RMSE = ...&lt;br&gt;
R2 = ...&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


After five folds, you have five records for that model.

---

# Print Fold Performance



```python id="mc49"
print(
    f"  Fold {fold}: "
    f"MAE={mae:.4f}, "
    f"RMSE={rmse:.4f}, "
    f"R²={r2:.4f}"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Displays the performance for the current fold.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;:.4f&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Means the number is displayed with &lt;strong&gt;four decimal places&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="mc50"&lt;br&gt;
R²=0.6547&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This is mainly for readable output.

---

# Store All Folds for the Model



```python id="mc51"
model_results.extend(
    model_fold_results
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Once all five folds for a model have finished, their results are added to the main &lt;code&gt;model_results&lt;/code&gt; list.&lt;/p&gt;

&lt;p&gt;The process is:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="mc52"&lt;br&gt;
Linear Regression&lt;br&gt;
→ 5 results&lt;br&gt;
       ↓&lt;br&gt;
model_results&lt;/p&gt;

&lt;p&gt;Ridge&lt;br&gt;
→ 5 results&lt;br&gt;
       ↓&lt;br&gt;
model_results&lt;/p&gt;

&lt;p&gt;SVR&lt;br&gt;
→ 5 results&lt;br&gt;
       ↓&lt;br&gt;
model_results&lt;/p&gt;

&lt;p&gt;...&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


---

# MODEL COMPARISON SUMMARY

Now you convert all those fold-level results into a summary table.

### 1. Convert results into DataFrame



```python id="mc53"
model_results_df = pd.DataFrame(
    model_results
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This converts the list of dictionaries into a pandas DataFrame.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Fold&lt;/th&gt;
&lt;th&gt;MAE&lt;/th&gt;
&lt;th&gt;RMSE&lt;/th&gt;
&lt;th&gt;R²&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Linear Regression&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linear Regression&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random Forest&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h1&gt;
  
  
  2. Group by Model
&lt;/h1&gt;



&lt;p&gt;```python id="mc54"&lt;br&gt;
model_summary = (&lt;br&gt;
    model_results_df&lt;br&gt;
    .groupby("Model")&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Groups all five folds belonging to the same model.

For example:



```text id="mc55"
Random Forest
→ Fold 1
→ Fold 2
→ Fold 3
→ Fold 4
→ Fold 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  3. Calculate Mean MAE
&lt;/h1&gt;



&lt;p&gt;```python id="mc56"&lt;br&gt;
Mean_MAE=("MAE", "mean"),&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Calculates the average MAE across the five folds.

Lower is better.

---

# 4. Calculate Mean RMSE



```python id="mc57"
Mean_RMSE=("RMSE", "mean"),
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Calculates average RMSE across the five folds.&lt;/p&gt;

&lt;p&gt;Lower is better.&lt;/p&gt;


&lt;h1&gt;
  
  
  5. Calculate Mean R²
&lt;/h1&gt;



&lt;p&gt;```python id="mc58"&lt;br&gt;
Mean_R2=("R2", "mean"),&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Calculates average R² across the five folds.

Higher is better.

---

# 6. Calculate Standard Deviation of R²



```python id="mc59"
Std_R2=("R2", "std")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is an especially useful addition.&lt;/p&gt;

&lt;p&gt;It measures how much the R² varies between folds.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="mc60"&lt;br&gt;
Mean R² = 0.65&lt;br&gt;
Std R²  = 0.02&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


suggests relatively stable fold performance.

But:



```text id="mc61"
Mean R² = 0.65
Std R²  = 0.20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;would indicate much greater variability.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;Std_R2&lt;/code&gt; helps you understand &lt;strong&gt;stability&lt;/strong&gt;, not just average performance.&lt;/p&gt;


&lt;h1&gt;
  
  
  7. Reset Index
&lt;/h1&gt;



&lt;p&gt;```python id="mc62"&lt;br&gt;
.reset_index()&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Converts the grouped model name back into a normal DataFrame column.

---

# 8. Sort by R²



```python id="mc63"
.sort_values(
    "Mean_R2",
    ascending=False
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Sorts models according to mean R².&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="mc64"&lt;br&gt;
Highest R²&lt;br&gt;
     ↓&lt;br&gt;
Best position&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


because:



```text id="mc65"
ascending=False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;means descending order.&lt;/p&gt;
&lt;h3&gt;
  
  
  Important
&lt;/h3&gt;

&lt;p&gt;This sorting does &lt;strong&gt;not&lt;/strong&gt; mean R² is the only metric that matters.&lt;/p&gt;

&lt;p&gt;You should still examine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mean MAE&lt;/li&gt;
&lt;li&gt;Mean RMSE&lt;/li&gt;
&lt;li&gt;Mean R²&lt;/li&gt;
&lt;li&gt;Std R²&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A model with the highest R² but dramatically worse MAE/RMSE should be investigated rather than automatically declared best.&lt;/p&gt;


&lt;h1&gt;
  
  
  9. Display the Summary
&lt;/h1&gt;



&lt;p&gt;```python id="mc66"&lt;br&gt;
display(model_summary)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Displays the final model-comparison table.

Conceptually:

| Model   | Mean MAE ↓ | Mean RMSE ↓ | Mean R² ↑ | Std R² ↓ |
| ------- | ---------: | ----------: | --------: | -------: |
| Model A |        ... |         ... |       ... |      ... |
| Model B |        ... |         ... |       ... |      ... |
| Model C |        ... |         ... |       ... |      ... |

This becomes the basis for your model selection.

---

# The Most Important Concept in This Cell

Your model comparison is **fairer than simply training all models once** because every model goes through essentially the same evaluation structure:



```text id="mc67"
Same development data
        ↓
Same 5 folds
        ↓
Same PCA = 50
        ↓
Same hybrid representation
        ↓
Same target transformation
        ↓
Same evaluation metrics
        ↓
Different ML algorithm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Therefore, the major experimental difference is the &lt;strong&gt;model algorithm&lt;/strong&gt;, rather than changing the dataset or evaluation strategy for each model.&lt;/p&gt;

&lt;p&gt;That is exactly what you want in a model comparison.&lt;/p&gt;


&lt;h1&gt;
  
  
  Very Important Viva Question: Why Compare Models on the Same Features?
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;“To make the comparison fair. I fixed the feature representation at PCA-50 hybrid features and used the same cross-validation folds, target transformation and evaluation metrics for each algorithm. Therefore, differences in performance are primarily attributable to the modelling algorithm rather than different preprocessing choices.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Excellent answer.&lt;/p&gt;


&lt;h1&gt;
  
  
  Very Important Viva Question: Why Is PCA Fixed at 50 Here?
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;“I previously evaluated 50, 100, 150 and 200 components using five-fold cross-validation. Since 50 produced the strongest average performance among those candidates, I fixed PCA at 50 for the subsequent model comparison. This avoids simultaneously changing the PCA dimension and the regression algorithm.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a &lt;strong&gt;very important experimental-design answer&lt;/strong&gt;.&lt;/p&gt;


&lt;h1&gt;
  
  
  Very Important Viva Question: Is This Data Leakage?
&lt;/h1&gt;

&lt;p&gt;Your answer should distinguish the different stages.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The independent test set is not used in this model comparison. Within each cross-validation fold, the structured preprocessing and PCA are fitted only on the fold-training data and then applied to the validation fold. The regression model is also freshly fitted for each fold. This prevents validation information from influencing model training or learned preprocessing.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Your leakage-control pipeline
&lt;/h3&gt;



&lt;p&gt;```text id="mc68"&lt;br&gt;
Fold training&lt;br&gt;
    ↓&lt;br&gt;
Fit winsorization&lt;br&gt;
Fit OHE&lt;br&gt;
Fit target encoding&lt;br&gt;
Fit PCA&lt;br&gt;
Fit scaler if required&lt;br&gt;
Fit ML model&lt;br&gt;
    ↓&lt;br&gt;
Validation&lt;br&gt;
    ↓&lt;br&gt;
Transform only&lt;br&gt;
    ↓&lt;br&gt;
Predict&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


That is one of the strongest methodological points in your notebook.

---

# Very Important Viva Question: Why `clone()`?

&amp;gt; “Because each fold must start with an unfitted model. `clone()` creates a fresh estimator with the same hyperparameter configuration, preventing learned parameters from one fold carrying into another fold.”

---

# Very Important Viva Question: Why Do You Use Raw Target for Metrics?

&amp;gt; “Although the models are trained on the log-transformed target, I inverse-transform the predictions back to the original PCF scale before calculating MAE, RMSE and R². This makes the evaluation directly interpretable in the original target scale.”

---

# Very Important Viva Question: Why Not Use the Independent Test Set Here?

&amp;gt; “Because the test set should remain untouched during model and feature-selection decisions. I use cross-validation within the development data for model comparison and reserve the independent test set for the final unbiased evaluation.”

This is perhaps one of the **most important answers in your whole viva**.

---

# One Subtle Point You Should Understand

Your code uses:



```python id="mc69"
y_fold_train_log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;for model training, while:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```python id="mc70"&lt;br&gt;
target_bins&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


is used for **stratification**.

These are not the same thing.



```text id="mc71"
y_fold_train_log
→ actual training target

target_bins
→ temporary labels used only to create balanced folds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The model never learns to predict &lt;code&gt;target_bins&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The prediction task remains:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="mc72"&lt;br&gt;
Hybrid features&lt;br&gt;
       ↓&lt;br&gt;
Continuous PCF prediction&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


---

# What This Experiment Ultimately Answers

This cell answers:

&amp;gt; **“Given the same PCA-50 hybrid representation and the same evaluation procedure, which regression algorithm performs best for PCF prediction?”**

Your previous PCA experiment answered:

&amp;gt; **“How many SBERT components should I retain?”**

So your experimental logic is:



```text id="mc73"
Experiment 1
PCA = 50 / 100 / 150 / 200
        ↓
Select best PCA dimension
        ↓
50


Experiment 2
Fix PCA = 50
        ↓
Compare ML algorithms
        ↓
Select best model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a much stronger explanation than simply saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I tried different models.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Presentation Script
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;“After selecting 50 PCA components from the previous cross-validation experiment, I use that configuration for the final model comparison. I evaluate multiple regression algorithms using stratified five-fold cross-validation on the development data. For each fold, I first separate the training and validation observations and build the structured features using only the fold-training information. I then select the corresponding SBERT embeddings, fit PCA with 50 components only on the fold-training embeddings, and transform the validation embeddings using the same PCA transformation. The reduced SBERT features are then fused with the structured features to form the hybrid representation.&lt;/p&gt;

&lt;p&gt;For each model, I create a fresh estimator using &lt;code&gt;clone()&lt;/code&gt; so that models do not carry learned information between folds. Feature scaling is enabled for Linear Regression, Ridge and SVR, while it is disabled for the tree-based ensemble models. Each model is trained on the log-transformed PCF target, and predictions are converted back to the original PCF scale before calculating MAE, RMSE and R². Finally, I average the metrics across the five folds and also calculate the standard deviation of R² to assess performance stability. The models are then ranked by mean R² while considering MAE and RMSE as complementary performance measures.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The 10 lines you should know perfectly for viva
&lt;/h3&gt;



&lt;p&gt;```text id="mc74"&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;skf.split(...)&lt;br&gt;
→ creates the five train/validation folds&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;build_structured_features(...)&lt;br&gt;
→ creates leakage-controlled structured features&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;X_train_sbert[train_idx]&lt;br&gt;
→ selects SBERT features for the training fold&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;apply_pca_to_sbert(..., n_components=50)&lt;br&gt;
→ reduces SBERT dimensions using training-fold-fitted PCA&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;fuse_features(...)&lt;br&gt;
→ combines SBERT + structured features&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;clone(model_template)&lt;br&gt;
→ creates a fresh model for each fold&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;scale_features = ...&lt;br&gt;
→ scaling depends on the algorithm&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;evaluate_model(...)&lt;br&gt;
→ trains, predicts and calculates metrics&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;groupby("Model").agg(...)&lt;br&gt;
→ averages performance across five folds&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;sort_values("Mean_R2", ascending=False)&lt;br&gt;
→ ranks models by average R²&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The one-sentence summary
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“I fixed the selected PCA dimension at 50 and compared different regression algorithms using the same leakage-controlled hybrid features and stratified five-fold development evaluation, selecting the strongest model based on cross-validated predictive performance.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
    </item>
    <item>
      <title>cell-09-MODEL EVALUATION</title>
      <dc:creator>TS</dc:creator>
      <pubDate>Mon, 14 Sep 2026 08:22:44 +0000</pubDate>
      <link>https://dev.to/ts2025/cell-09-model-evaluation-2ea5</link>
      <guid>https://dev.to/ts2025/cell-09-model-evaluation-2ea5</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ============================================================
# MODEL EVALUATION
# ============================================================
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sklearn.preprocessing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StandardScaler&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;evaluate_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;X_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;y_train_log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;X_valid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;y_valid_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;scale_features&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Train on log-transformed PCF and evaluate
    predictions in the original PCF scale.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="c1"&gt;# Feature scaling
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;scale_features&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

        &lt;span class="n"&gt;scaler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StandardScaler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;X_train_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scaler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit_transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X_train&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;X_valid_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scaler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X_valid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

        &lt;span class="n"&gt;X_train_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X_train&lt;/span&gt;
        &lt;span class="n"&gt;X_valid_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X_valid&lt;/span&gt;


    &lt;span class="c1"&gt;# Train model
&lt;/span&gt;    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;X_train_model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;y_train_log&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="c1"&gt;# Predict log(PCF)
&lt;/span&gt;    &lt;span class="n"&gt;y_pred_log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;X_valid_model&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="c1"&gt;# Convert predictions back to PCF scale
&lt;/span&gt;    &lt;span class="n"&gt;y_pred_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expm1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;y_pred_log&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Prevent negative PCF predictions
&lt;/span&gt;    &lt;span class="n"&gt;y_pred_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;maximum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;y_pred_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="c1"&gt;# Evaluation metrics
&lt;/span&gt;    &lt;span class="n"&gt;mae&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mean_absolute_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;y_valid_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;y_pred_raw&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;rmse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;mean_squared_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;y_valid_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;y_pred_raw&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;r2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;r2_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;y_valid_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;y_pred_raw&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;mae&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rmse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Model Evaluation
&lt;/h3&gt;

&lt;p&gt;This function creates a &lt;strong&gt;reusable evaluation pipeline&lt;/strong&gt; for all of your regression models.&lt;/p&gt;

&lt;p&gt;The main purpose is to make sure every model is evaluated in the &lt;strong&gt;same way&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Training features
      ↓
Optional scaling
      ↓
Train using log-transformed PCF
      ↓
Predict log-transformed PCF
      ↓
Convert prediction back to raw PCF
      ↓
Calculate MAE, RMSE, R²
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an important cell because it standardises your model comparison.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Import StandardScaler
&lt;/h3&gt;



&lt;p&gt;```python id="scaler01"&lt;br&gt;
from sklearn.preprocessing import StandardScaler&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This imports `StandardScaler`.

StandardScaler standardises numerical feature values approximately to:



```text
mean = 0
standard deviation = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The transformation is learned from the training data.&lt;/p&gt;

&lt;p&gt;It is particularly relevant for models such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Linear Regression&lt;/li&gt;
&lt;li&gt;Ridge Regression&lt;/li&gt;
&lt;li&gt;SVR&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tree-based models such as Random Forest generally do not require feature scaling.&lt;/p&gt;


&lt;h3&gt;
  
  
  2. Define the evaluation function
&lt;/h3&gt;



&lt;p&gt;```python id="eval02"&lt;br&gt;
def evaluate_model(&lt;br&gt;
    model,&lt;br&gt;
    X_train,&lt;br&gt;
    y_train_log,&lt;br&gt;
    X_valid,&lt;br&gt;
    y_valid_raw,&lt;br&gt;
    scale_features=False&lt;br&gt;
):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


You define a reusable function called:



```text
evaluate_model()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It accepts six inputs.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;code&gt;model&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;The regression model you want to evaluate.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Linear Regression
Ridge
SVR
Random Forest
XGBoost
CatBoost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;code&gt;X_train&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Training feature matrix.&lt;/p&gt;

&lt;p&gt;This may contain your hybrid features:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PCA-reduced SBERT
+
structured features
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;code&gt;y_train_log&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;The log-transformed PCF target used for model training.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;X_valid&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Validation feature matrix.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;y_valid_raw&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;The actual PCF values in their original/raw scale.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;scale_features=False&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This controls whether feature scaling is applied.&lt;/p&gt;

&lt;p&gt;By default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scale_features = False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So no scaling happens unless you explicitly request it.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Function documentation
&lt;/h3&gt;



&lt;p&gt;```python id="eval03"&lt;br&gt;
"""&lt;br&gt;
Train on log-transformed PCF and evaluate&lt;br&gt;
predictions in the original PCF scale.&lt;br&gt;
"""&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This is a **docstring**.

It documents what the function does.

The key information is:



```text
Training → log-transformed PCF

Evaluation → original PCF scale
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That distinction is important in your project.&lt;/p&gt;


&lt;h1&gt;
  
  
  Feature Scaling
&lt;/h1&gt;



&lt;p&gt;```python id="eval04"&lt;br&gt;
if scale_features:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This checks whether the function was called with:



```python
scale_features=True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If yes, scaling is performed.&lt;/p&gt;

&lt;p&gt;If false, the original feature matrices are used.&lt;/p&gt;


&lt;h3&gt;
  
  
  4. Create scaler
&lt;/h3&gt;



&lt;p&gt;```python id="eval05"&lt;br&gt;
scaler = StandardScaler()&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Creates a StandardScaler object.

The scaler will learn:

* feature means
* feature standard deviations

from the training data.

---

### 5. Fit and transform training features



```python id="eval06"
X_train_model = scaler.fit_transform(X_train)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This does two operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fit
+
transform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;code&gt;fit&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Learns the mean and standard deviation from &lt;strong&gt;training data only&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;transform&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Uses those learned values to standardise the training features.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X_train
   ↓
learn mean/std
   ↓
standardise
   ↓
X_train_model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  6. Transform validation features
&lt;/h3&gt;



&lt;p&gt;```python id="eval07"&lt;br&gt;
X_valid_model = scaler.transform(X_valid)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This applies the **same training-derived scaling parameters** to the validation data.

Notice:



```text
Training:
fit_transform()

Validation:
transform()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is very important for avoiding leakage.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why not use &lt;code&gt;fit_transform()&lt;/code&gt; on validation?
&lt;/h3&gt;

&lt;p&gt;Because then the validation data would be used to calculate its own mean and standard deviation.&lt;/p&gt;

&lt;p&gt;That means information from validation would influence the preprocessing.&lt;/p&gt;

&lt;p&gt;Your approach is correct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Training
 ↓
fit scaler
 ↓
learn parameters

Validation
 ↓
use same parameters
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Viva answer
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“I fit the scaler only on the training data and then apply the learned transformation to validation data. This prevents information from the validation set influencing the preprocessing.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  If Scaling Is Not Requested
&lt;/h1&gt;



&lt;p&gt;```python id="eval08"&lt;br&gt;
else:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X_train_model = X_train
X_valid_model = X_valid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


If:



```python
scale_features=False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;the original feature matrices are used without scaling.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scale_features=True
→ StandardScaler

scale_features=False
→ original features
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows the same evaluation function to work with models that do and do not benefit from scaling.&lt;/p&gt;




&lt;h1&gt;
  
  
  Train the Model
&lt;/h1&gt;



&lt;p&gt;```python id="eval09"&lt;br&gt;
model.fit(&lt;br&gt;
    X_train_model,&lt;br&gt;
    y_train_log&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This trains the selected regression model.

The model receives:



```text
X_train_model
+
y_train_log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The target is the &lt;strong&gt;log-transformed PCF&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So the model learns to predict approximately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;log(PCF + 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than the raw PCF.&lt;/p&gt;




&lt;h1&gt;
  
  
  Predict in Log Space
&lt;/h1&gt;



&lt;p&gt;```python id="eval10"&lt;br&gt;
y_pred_log = model.predict(&lt;br&gt;
    X_valid_model&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


The trained model predicts the validation observations.

Because the model was trained using `y_train_log`, these predictions are also in log space.

Therefore:



```text
y_pred_log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;is not yet the final PCF value in the original units.&lt;/p&gt;


&lt;h1&gt;
  
  
  Convert Predictions Back to PCF Scale
&lt;/h1&gt;



&lt;p&gt;```python id="eval11"&lt;br&gt;
y_pred_raw = np.expm1(&lt;br&gt;
    y_pred_log&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This reverses the earlier log transformation.

If your target transformation was:



```text
log(PCF + 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expm1(prediction)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;returns the prediction to approximately the original PCF scale.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Raw PCF
   ↓
log1p()
   ↓
Log PCF
   ↓
ML model
   ↓
Predicted Log PCF
   ↓
expm1()
   ↓
Predicted Raw PCF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why do this before calculating metrics?
&lt;/h3&gt;

&lt;p&gt;Because you want your MAE and RMSE to be expressed in the &lt;strong&gt;original PCF units&lt;/strong&gt;, making them easier to interpret.&lt;/p&gt;




&lt;h1&gt;
  
  
  Prevent Negative PCF Predictions
&lt;/h1&gt;



&lt;p&gt;```python id="eval12"&lt;br&gt;
y_pred_raw = np.maximum(&lt;br&gt;
    y_pred_raw,&lt;br&gt;
    0&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This prevents negative predicted PCF values.

For every prediction:



```text
if prediction &amp;lt; 0
    → 0

if prediction ≥ 0
    → keep it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is a domain-informed constraint because PCF is non-negative.&lt;/p&gt;
&lt;h3&gt;
  
  
  Viva question: Can your model produce negative predictions?
&lt;/h3&gt;

&lt;p&gt;Depending on the regression algorithm and transformation, yes, a model may produce a log-space value that back-transforms to a value that needs domain checking. This line ensures the final reported prediction is not negative.&lt;/p&gt;

&lt;p&gt;A safe answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I apply a non-negativity constraint after inverse transformation because negative PCF values are not physically meaningful.”&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h1&gt;
  
  
  Evaluation Metrics
&lt;/h1&gt;

&lt;p&gt;Now the function calculates three metrics.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```python id="eval13"&lt;br&gt;
mae = mean_absolute_error(&lt;br&gt;
    y_valid_raw,&lt;br&gt;
    y_pred_raw&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


### MAE

Mean Absolute Error measures the average absolute difference between:



```text
actual PCF
vs.
predicted PCF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Lower is better.&lt;/p&gt;

&lt;p&gt;For example, an MAE of 100 means the average absolute prediction error is 100 PCF units, assuming the target's unit is kg CO₂e.&lt;/p&gt;


&lt;h3&gt;
  
  
  RMSE
&lt;/h3&gt;



&lt;p&gt;```python id="eval14"&lt;br&gt;
rmse = np.sqrt(&lt;br&gt;
    mean_squared_error(&lt;br&gt;
        y_valid_raw,&lt;br&gt;
        y_pred_raw&lt;br&gt;
    )&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


First:



```python
mean_squared_error(...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;calculates Mean Squared Error.&lt;/p&gt;

&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;takes the square root.&lt;/p&gt;

&lt;p&gt;This produces RMSE.&lt;/p&gt;

&lt;p&gt;RMSE penalises larger errors more heavily than MAE because the errors are squared before averaging.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lower is better.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  R²
&lt;/h3&gt;



&lt;p&gt;```python id="eval15"&lt;br&gt;
r2 = r2_score(&lt;br&gt;
    y_valid_raw,&lt;br&gt;
    y_pred_raw&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Calculates the coefficient of determination, R².

It evaluates how well the predictions explain variation in the target relative to a baseline.

Generally:



```text
Higher R² → better
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But R² should always be interpreted together with MAE and RMSE.&lt;/p&gt;


&lt;h1&gt;
  
  
  Return the Results
&lt;/h1&gt;



&lt;p&gt;```python id="eval16"&lt;br&gt;
return mae, rmse, r2&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


The function returns three values:



```text
MAE
RMSE
R²
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This makes it easy to store and compare the performance of different models.&lt;/p&gt;

&lt;p&gt;For example, conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Model              MAE       RMSE       R²
------------------------------------------------
Linear Regression  ...       ...        ...
Ridge              ...       ...        ...
SVR                ...       ...        ...
Random Forest      ...       ...        ...
XGBoost            ...       ...        ...
CatBoost           ...       ...        ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  The Most Important Leakage Principle in This Function
&lt;/h1&gt;

&lt;p&gt;Your preprocessing follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TRAIN
 ↓
fit scaler
 ↓
transform train


VALIDATION
 ↓
transform using training scaler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Not:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TRAIN + VALIDATION
 ↓
fit scaler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This same principle applies to your other learned transformations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Winsorization
→ fit on training only

One-Hot Encoder
→ fit on training only

Target Encoder
→ training/OOF strategy

PCA
→ fit on training only

Scaler
→ fit on training only
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a &lt;strong&gt;very strong viva point&lt;/strong&gt; because it shows you understand preprocessing leakage rather than simply applying preprocessing mechanically.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Scale Some Models but Not Others?
&lt;/h1&gt;

&lt;p&gt;This is another likely examiner question.&lt;/p&gt;

&lt;h3&gt;
  
  
  Linear Regression
&lt;/h3&gt;

&lt;p&gt;Scaling can make coefficients numerically more comparable, although ordinary linear regression predictions are invariant to simple feature rescaling under standard conditions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ridge
&lt;/h3&gt;

&lt;p&gt;Scaling is important because Ridge applies regularisation to coefficients. Different feature scales can otherwise cause the penalty to affect features unevenly.&lt;/p&gt;

&lt;h3&gt;
  
  
  SVR
&lt;/h3&gt;

&lt;p&gt;Scaling is particularly important for SVR because distance calculations and the RBF kernel are sensitive to feature scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Random Forest
&lt;/h3&gt;

&lt;p&gt;Usually does not require scaling because tree splits are based on feature thresholds rather than distances or coefficient magnitudes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strong viva answer
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“I made feature scaling optional because its importance depends on the algorithm. Models such as Ridge and especially RBF-SVR are sensitive to feature scale, whereas tree-based models such as Random Forest generally do not require standardisation. The function therefore allows scaling to be enabled when appropriate.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Very Important Question: Why Train on Log Target but Evaluate on Raw Target?
&lt;/h1&gt;

&lt;p&gt;Answer this exactly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The log transformation helps manage the skewness and extreme values of the PCF target during model training. After prediction, I apply the inverse transformation so that the predictions return to the original PCF scale. I then calculate MAE, RMSE and R² against the raw validation target, making the reported performance interpretable in the original PCF units.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Very Important Question: Why Use Three Metrics?
&lt;/h1&gt;

&lt;p&gt;You can say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I use MAE, RMSE and R² because they provide complementary information. MAE represents the average absolute error, RMSE gives greater weight to large errors, and R² indicates how well the predictions explain variation in the target. Using multiple metrics reduces the risk of judging a model from a single perspective.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  One Important Technical Point
&lt;/h1&gt;

&lt;p&gt;Your function itself &lt;strong&gt;does not perform cross-validation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It evaluates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;one training set
+
one validation set
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cross-validation happens outside this function when you repeatedly call it for different folds.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;evaluate_model()
→ one train/validation evaluation

5-fold CV loop
→ calls evaluate_model() five times
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This distinction may come up in your viva.&lt;/p&gt;

&lt;h3&gt;
  
  
  Presentation wording
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“This function standardises the evaluation process across my regression models. It optionally applies feature scaling, fitting the scaler only on the training data and then transforming the validation data. The selected model is trained using the log-transformed PCF target. Predictions are generated in log space and then converted back to the original PCF scale using the inverse transformation. I apply a non-negativity constraint because negative PCF values are not physically meaningful. Finally, I calculate MAE, RMSE and R² on the original PCF scale and return these metrics for model comparison.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Viva checklist for this cell
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fit_transform()&lt;/code&gt; → training data&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;transform()&lt;/code&gt; → validation data&lt;/li&gt;
&lt;li&gt;Scaling → model-dependent&lt;/li&gt;
&lt;li&gt;Training target → log scale&lt;/li&gt;
&lt;li&gt;Prediction → log scale&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;expm1()&lt;/code&gt; → back to raw PCF&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;maximum(..., 0)&lt;/code&gt; → no negative PCF&lt;/li&gt;
&lt;li&gt;MAE → average absolute error&lt;/li&gt;
&lt;li&gt;RMSE → emphasises large errors&lt;/li&gt;
&lt;li&gt;R² → explained variation&lt;/li&gt;
&lt;li&gt;Metrics → calculated on raw PCF scale&lt;/li&gt;
&lt;li&gt;Function → evaluates one train/validation split; CV is handled outside&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this evaluation function&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compare scaled and unscaled models&lt;/li&gt;
&lt;li&gt;Check raw-scale metric bias&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Cell-08-MODEL IMPORTS</title>
      <dc:creator>TS</dc:creator>
      <pubDate>Mon, 14 Sep 2026 08:06:40 +0000</pubDate>
      <link>https://dev.to/ts2025/cell-08-model-imports-4ok5</link>
      <guid>https://dev.to/ts2025/cell-08-model-imports-4ok5</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ============================================================
# MODEL IMPORTS
# ============================================================
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sklearn.linear_model&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LinearRegression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Ridge&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sklearn.svm&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SVR&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sklearn.ensemble&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RandomForestRegressor&lt;/span&gt;

&lt;span class="c1"&gt;# Optional boosting models
&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;xgboost&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;XGBRegressor&lt;/span&gt;
    &lt;span class="n"&gt;XGB_AVAILABLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ImportError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;XGB_AVAILABLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;catboost&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CatBoostRegressor&lt;/span&gt;
    &lt;span class="n"&gt;CATBOOST_AVAILABLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ImportError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;CATBOOST_AVAILABLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;


&lt;span class="c1"&gt;# ============================================================
# MODEL CONFIGURATION
# ============================================================
&lt;/span&gt;
&lt;span class="n"&gt;models&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Linear Regression&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;LinearRegression&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;

    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ridge Regression&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Ridge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SVR&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;SVR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rbf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;epsilon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Random Forest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;RandomForestRegressor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;n_estimators&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;n_jobs&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="c1"&gt;# XGBoost
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;XGB_AVAILABLE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;XGBoost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;XGBRegressor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;n_estimators&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;learning_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;subsample&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;colsample_bytree&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;objective&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reg:squarederror&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;n_jobs&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# CatBoost
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;CATBOOST_AVAILABLE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CatBoost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CatBoostRegressor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;learning_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;loss_function&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RMSE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;random_seed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Models selected:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; -&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Model Imports
&lt;/h3&gt;

&lt;p&gt;This cell defines the &lt;strong&gt;candidate regression models&lt;/strong&gt; that will be compared for PCF prediction.&lt;/p&gt;

&lt;p&gt;The key idea is that you are not relying on only one algorithm. You are testing different model families:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Linear model
     ↓
Linear Regression

Regularised linear model
     ↓
Ridge Regression

Kernel-based model
     ↓
SVR

Tree ensemble
     ↓
Random Forest

Boosting ensembles
     ↓
XGBoost / CatBoost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a broader comparison of modelling approaches.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Import Linear Regression and Ridge
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sklearn.linear_model&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LinearRegression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Ridge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This imports two linear regression algorithms.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;LinearRegression&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Ordinary linear regression assumes the target can be represented as a linear combination of the input features.&lt;/p&gt;

&lt;p&gt;In simplified form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prediction = intercept + weighted features
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is useful as a &lt;strong&gt;simple baseline model&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Viva question: Why include Linear Regression?
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“I included Linear Regression as a simple baseline. It provides a reference point against which the more complex nonlinear models can be compared.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h4&gt;
  
  
  &lt;code&gt;Ridge&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Ridge Regression is a regularised version of linear regression.&lt;/p&gt;

&lt;p&gt;It adds a penalty to large coefficients.&lt;/p&gt;

&lt;p&gt;This can be useful when you have many correlated features, which is particularly relevant when using high-dimensional embedding features.&lt;/p&gt;

&lt;h4&gt;
  
  
  Viva question: Why Ridge?
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“Ridge provides a regularised linear baseline. Since the hybrid representation can contain many correlated features, Ridge can reduce the influence of excessively large coefficients and provide a useful comparison with ordinary linear regression.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  2. Import SVR
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sklearn.svm&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SVR&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This imports &lt;strong&gt;Support Vector Regression&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Your configuration later uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rbf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The RBF kernel allows SVR to model &lt;strong&gt;nonlinear relationships&lt;/strong&gt; between the features and PCF.&lt;/p&gt;

&lt;p&gt;So your model comparison includes a nonlinear kernel-based approach rather than only linear models.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Import Random Forest
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sklearn.ensemble&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RandomForestRegressor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Random Forest is an ensemble of decision trees.&lt;/p&gt;

&lt;p&gt;It can model nonlinear relationships and interactions between features.&lt;/p&gt;

&lt;p&gt;This is particularly useful for your hybrid feature representation because relationships between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SBERT features
+
structured features
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;may not be purely linear.&lt;/p&gt;




&lt;h1&gt;
  
  
  Optional XGBoost Import
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;xgboost&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;XGBRegressor&lt;/span&gt;
    &lt;span class="n"&gt;XGB_AVAILABLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ImportError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;XGB_AVAILABLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a &lt;strong&gt;safe optional import&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;try&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Python first attempts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;xgboost&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;XGBRegressor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If XGBoost is installed, the import succeeds.&lt;/p&gt;

&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;XGB_AVAILABLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means the model is available.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;except ImportError&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;If XGBoost is not installed, Python catches the import error.&lt;/p&gt;

&lt;p&gt;Instead of stopping the entire notebook, you set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;XGB_AVAILABLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means the rest of the notebook can continue without XGBoost.&lt;/p&gt;

&lt;h4&gt;
  
  
  Viva question: Why use &lt;code&gt;try/except&lt;/code&gt;?
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“XGBoost and CatBoost are optional dependencies. I used &lt;code&gt;try/except&lt;/code&gt; so that the notebook remains executable even if one of these external libraries is unavailable.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a good software-engineering decision.&lt;/p&gt;




&lt;h1&gt;
  
  
  Optional CatBoost Import
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;catboost&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CatBoostRegressor&lt;/span&gt;
    &lt;span class="n"&gt;CATBOOST_AVAILABLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ImportError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;CATBOOST_AVAILABLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does the same thing for &lt;strong&gt;CatBoost&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CATBOOST_AVAILABLE = True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CATBOOST_AVAILABLE = False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Therefore, your core scikit-learn models can still run.&lt;/p&gt;




&lt;h1&gt;
  
  
  Model Configuration
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;models&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You create a Python dictionary called &lt;code&gt;models&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The dictionary stores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Model name → Model object
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Random Forest" → RandomForestRegressor(...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is convenient because later you can iterate through all models using something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of writing separate training code for every model.&lt;/p&gt;




&lt;h1&gt;
  
  
  1. Linear Regression
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Linear Regression&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;LinearRegression&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Linear Regression"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is simply the human-readable model name.&lt;/p&gt;

&lt;p&gt;The value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;LinearRegression&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;creates the regression model.&lt;/p&gt;

&lt;p&gt;This serves as a baseline.&lt;/p&gt;




&lt;h1&gt;
  
  
  2. Ridge Regression
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ridge Regression&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Ridge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creates a Ridge Regression model.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;alpha=1.0&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;alpha&lt;/code&gt; controls the strength of the regularisation.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Higher alpha
→ stronger coefficient penalty

Lower alpha
→ weaker coefficient penalty
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here you use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alpha = 1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;as the selected configuration.&lt;/p&gt;

&lt;h4&gt;
  
  
  Viva question: Is &lt;code&gt;alpha=1.0&lt;/code&gt; automatically optimal?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“No. It is a chosen hyperparameter configuration. A more extensive experiment could tune alpha using cross-validation.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is safer than claiming that 1.0 is universally optimal.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. SVR
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SVR&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;SVR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rbf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;epsilon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creates the Support Vector Regression model.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;kernel="rbf"&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;RBF stands for &lt;strong&gt;Radial Basis Function&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It allows SVR to model nonlinear relationships.&lt;/p&gt;

&lt;p&gt;This is useful because PCF relationships may not be linear.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;C=10.0&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;C&lt;/code&gt; controls the trade-off between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fitting the training data closely&lt;/li&gt;
&lt;li&gt;allowing some prediction error&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A larger &lt;code&gt;C&lt;/code&gt; generally puts more emphasis on reducing training errors.&lt;/p&gt;

&lt;p&gt;Again, don't claim 10 is universally optimal.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;epsilon=0.1&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Defines an epsilon-insensitive region around the regression prediction.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prediction
     ↓
small errors inside epsilon
     ↓
not penalised in the same way
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Viva question: Why RBF SVR?
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“I used the RBF kernel because it allows SVR to capture nonlinear relationships between the hybrid features and the continuous PCF target.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  4. Random Forest
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Random Forest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;RandomForestRegressor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;n_estimators&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;n_jobs&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creates a Random Forest regression model.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;n_estimators=300&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Creates an ensemble of &lt;strong&gt;300 trees&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The final prediction is based on the combined predictions of these trees.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;random_state=42&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Makes the stochastic parts reproducible.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;n_jobs=-1&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Uses all available CPU cores for parallel processing.&lt;/p&gt;

&lt;h4&gt;
  
  
  Viva question: Why Random Forest?
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“Random Forest can capture nonlinear relationships and interactions without requiring the feature-target relationship to be linear. It is therefore a useful tree-based model for evaluating the hybrid representation.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  XGBoost
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;XGB_AVAILABLE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This checks whether XGBoost was successfully imported.&lt;/p&gt;

&lt;p&gt;If:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;XGB_AVAILABLE = True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then the following model is added.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;XGBoost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;XGBRegressor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This adds XGBoost to the dictionary.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;code&gt;n_estimators=300&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;n_estimators&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uses 300 boosting iterations/trees.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;learning_rate=0.05&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;learning_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Controls how strongly each new tree contributes to the overall model.&lt;/p&gt;

&lt;p&gt;A smaller learning rate generally means the model learns more gradually.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;max_depth=6&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;max_depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Controls the maximum depth of individual trees.&lt;/p&gt;

&lt;p&gt;Greater depth can capture more complex relationships but can also increase overfitting risk.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;subsample=0.8&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;subsample&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uses approximately 80% of the training observations for each boosting stage.&lt;/p&gt;

&lt;p&gt;This introduces randomness and can help reduce overfitting.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;colsample_bytree=0.8&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;colsample_bytree&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uses approximately 80% of the features for each tree.&lt;/p&gt;

&lt;p&gt;This introduces feature-level randomness.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;objective="reg:squarederror"&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;objective&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reg:squarederror&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Specifies that XGBoost is solving a &lt;strong&gt;regression problem using squared error&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is appropriate because PCF is a continuous target.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;random_state=42&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Provides reproducibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;n_jobs=-1&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Uses available CPU resources for parallel processing.&lt;/p&gt;




&lt;h1&gt;
  
  
  CatBoost
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;CATBOOST_AVAILABLE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Checks whether CatBoost is available.&lt;/p&gt;

&lt;p&gt;If it is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CatBoost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CatBoostRegressor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;adds a CatBoost regression model.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;iterations=300&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uses 300 boosting iterations.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;learning_rate=0.05&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Controls how strongly each boosting iteration contributes.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;depth=6&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Controls tree depth.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;loss_function="RMSE"&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;loss_function&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RMSE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Specifies RMSE as the optimisation loss.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;random_seed=42&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Provides reproducibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;verbose=False&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Suppresses CatBoost's detailed training output.&lt;/p&gt;

&lt;p&gt;This keeps the notebook output cleaner.&lt;/p&gt;




&lt;h1&gt;
  
  
  Print Selected Models
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Models selected:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prints a heading.&lt;/p&gt;

&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;loops through the dictionary keys.&lt;/p&gt;

&lt;p&gt;Each key is a model name.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Linear Regression
Ridge Regression
SVR
Random Forest
XGBoost
CatBoost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; -&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;prints each model name.&lt;/p&gt;

&lt;p&gt;So the output might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Models selected:
 - Linear Regression
 - Ridge Regression
 - SVR
 - Random Forest
 - XGBoost
 - CatBoost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact final two depend on whether those libraries are installed.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Compare These Models?
&lt;/h1&gt;

&lt;p&gt;This is a &lt;strong&gt;very likely viva question&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You are deliberately comparing different model families.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Linear Regression
→ simple linear baseline

Ridge
→ regularised linear model

SVR
→ nonlinear kernel model

Random Forest
→ bagging/tree ensemble

XGBoost
→ gradient boosting

CatBoost
→ gradient boosting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a broader experimental comparison.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strong viva answer
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“I selected models from different algorithmic families so that I could evaluate whether the hybrid PCF features work better with linear, regularised, kernel-based, bagging, or boosting approaches. Linear Regression provides a simple baseline, Ridge addresses regularisation, SVR captures nonlinear relationships through an RBF kernel, and Random Forest, XGBoost and CatBoost provide tree-based ensemble alternatives.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Important Question: Why Not Just Use Random Forest?
&lt;/h1&gt;

&lt;p&gt;If your examiner asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“If Random Forest works well, why did you test all these models?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Because selecting a model based only on prior expectation would introduce unnecessary assumptions. Comparing multiple model families allows me to empirically determine which algorithm works best with the engineered hybrid representation.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Important Question: Are XGBoost and CatBoost Necessary?
&lt;/h1&gt;

&lt;p&gt;Answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“They are optional comparison models rather than mandatory components of the pipeline. I included them to broaden the model comparison. If the libraries are unavailable, the core scikit-learn models can still be evaluated.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Important Question: Are these hyperparameters tuned?
&lt;/h1&gt;

&lt;p&gt;Based &lt;strong&gt;only on this code&lt;/strong&gt;, these are predefined configurations, not a comprehensive hyperparameter search.&lt;/p&gt;

&lt;p&gt;So say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“These are selected model configurations used for comparison. They are not the result of an exhaustive hyperparameter optimisation procedure.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a very defensible answer.&lt;/p&gt;




&lt;h1&gt;
  
  
  Important Question: Which model is best?
&lt;/h1&gt;

&lt;p&gt;You &lt;strong&gt;cannot answer that from this cell alone&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This cell only defines the models.&lt;/p&gt;

&lt;p&gt;The actual answer must come from your later evaluation results.&lt;/p&gt;

&lt;p&gt;The process is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Models defined
      ↓
Train models
      ↓
Generate validation/test predictions
      ↓
Calculate MAE, RMSE, R²
      ↓
Compare models
      ↓
Select best-performing model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So don't say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Random Forest is the best model.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;unless your later results actually demonstrate that.&lt;/p&gt;




&lt;h1&gt;
  
  
  One Critical Point for Your Viva
&lt;/h1&gt;

&lt;p&gt;Your modelling target is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;log-transformed PCF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but your evaluation metrics are calculated after converting predictions back to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;raw PCF scale
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So your complete modelling process is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Raw PCF
   ↓
log1p transformation
   ↓
Model training
   ↓
Prediction in log space
   ↓
expm1
   ↓
Raw PCF prediction
   ↓
MAE / RMSE / R²
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is worth remembering because an examiner may ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Why are you training on log PCF but evaluating on raw PCF?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The log transformation helps manage the skewed target distribution during modelling, while converting predictions back to the original PCF scale makes the evaluation metrics directly interpretable in the original target units.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Presentation wording
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“In this cell, I define the regression models used for comparative evaluation. I include Linear Regression as a simple baseline, Ridge Regression as a regularised linear model, SVR with an RBF kernel to capture nonlinear relationships, and Random Forest as a tree-based ensemble. I also include XGBoost and CatBoost as optional boosting models when their libraries are available. I store all models in a dictionary so that the same evaluation pipeline can be applied consistently across algorithms. The hyperparameters shown here are predefined configurations rather than the result of exhaustive hyperparameter optimisation. The final model selection is therefore based on the comparative validation or test performance obtained in the subsequent evaluation stage.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For your model comparison&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explain why raw-scale metrics can differ&lt;/li&gt;
&lt;li&gt;Check whether scaling changes SVR and Ridge&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Cell-07-PCA COMPONENT SELECTION — 5-FOLD CV</title>
      <dc:creator>TS</dc:creator>
      <pubDate>Mon, 14 Sep 2026 08:04:49 +0000</pubDate>
      <link>https://dev.to/ts2025/cell-07-pca-component-selection-5-fold-cv-1pfd</link>
      <guid>https://dev.to/ts2025/cell-07-pca-component-selection-5-fold-cv-1pfd</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ============================================================
# PCA COMPONENT SELECTION — 5-FOLD CV
# ============================================================
&lt;/span&gt;
&lt;span class="n"&gt;PCA_COMPONENTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;pca_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n_components&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;PCA_COMPONENTS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="n"&gt;fold_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;skf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_bins&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt;

        &lt;span class="c1"&gt;# 1. Split development data
&lt;/span&gt;        &lt;span class="n"&gt;X_fold_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;X_fold_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;y_fold_train_log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;y_log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_numpy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;y_fold_valid_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;TARGET&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_numpy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# 2. Structured features
&lt;/span&gt;        &lt;span class="c1"&gt;#    Country → OOF Target Encoding
&lt;/span&gt;        &lt;span class="n"&gt;X_struct_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_struct_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;build_structured_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;X_fold_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;y_fold_train_log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;X_fold_valid&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# 3. SBERT features
&lt;/span&gt;        &lt;span class="n"&gt;X_sbert_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X_train_sbert&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;X_sbert_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X_train_sbert&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="c1"&gt;# 4. PCA — fit on training fold only
&lt;/span&gt;        &lt;span class="n"&gt;X_sbert_train_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_sbert_valid_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pca&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;apply_pca_to_sbert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;X_sbert_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;X_sbert_valid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;n_components&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;n_components&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# 5. Hybrid feature fusion
&lt;/span&gt;        &lt;span class="n"&gt;X_hybrid_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fuse_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;X_sbert_train_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;X_struct_train&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;X_hybrid_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fuse_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;X_sbert_valid_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;X_struct_valid&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# 6. Random Forest
&lt;/span&gt;        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RandomForestRegressor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;n_estimators&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;n_jobs&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;X_hybrid_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;y_fold_train_log&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# 7. Prediction
&lt;/span&gt;        &lt;span class="n"&gt;y_pred_log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;X_hybrid_valid&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;y_pred_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;maximum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expm1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y_pred_log&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# 8. Evaluation
&lt;/span&gt;        &lt;span class="n"&gt;fold_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PCA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;n_components&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MAE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;mean_absolute_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;y_fold_valid_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;y_pred_raw&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RMSE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nf"&gt;mean_squared_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;y_fold_valid_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;y_pred_raw&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;r2_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;y_fold_valid_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;y_pred_raw&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;pca_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fold_results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# ============================================================
# PCA RESULTS
# ============================================================
&lt;/span&gt;
&lt;span class="n"&gt;pca_results_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;pca_results&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;pca_summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;pca_results_df&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PCA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Mean_MAE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MAE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Mean_RMSE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RMSE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Mean_R2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset_index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pca_summary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  PCA Component Selection — 5-Fold CV
&lt;/h3&gt;

&lt;p&gt;This is an &lt;strong&gt;important experimental cell&lt;/strong&gt; because you are not choosing the PCA dimension arbitrarily. You test several PCA dimensions using &lt;strong&gt;five-fold cross-validation&lt;/strong&gt; and compare their predictive performance.&lt;/p&gt;

&lt;p&gt;Your tested values are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;PCA_COMPONENTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result shows that &lt;strong&gt;50 components performed best&lt;/strong&gt; among the tested options.&lt;/p&gt;

&lt;p&gt;The overall pipeline is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Development data
       ↓
5-fold stratified CV
       ↓
For each PCA size:
       ↓
Structured features
       +
SBERT features
       ↓
PCA
       ↓
Hybrid features
       ↓
Random Forest
       ↓
Prediction
       ↓
MAE / RMSE / R²
       ↓
Average across 5 folds
       ↓
Choose PCA dimension
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. Define PCA candidates
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;PCA_COMPONENTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You are testing four different dimensionalities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;384 original SBERT dimensions
        ↓
50
100
150
200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You are asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Which PCA dimension gives the best downstream PCF prediction performance?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is essentially a &lt;strong&gt;hyperparameter/model-selection experiment&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You are not assuming that retaining more components automatically gives better results.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Create storage for results
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pca_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates an empty Python list.&lt;/p&gt;

&lt;p&gt;You will store the evaluation results from every fold and every PCA configuration.&lt;/p&gt;

&lt;p&gt;Since you have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4 PCA choices × 5 folds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you should get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;20 fold-level result records
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each record contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PCA components&lt;/li&gt;
&lt;li&gt;Fold number&lt;/li&gt;
&lt;li&gt;MAE&lt;/li&gt;
&lt;li&gt;RMSE&lt;/li&gt;
&lt;li&gt;R²&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. Loop through PCA choices
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n_components&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;PCA_COMPONENTS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This starts the outer loop.&lt;/p&gt;

&lt;p&gt;It means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First → PCA = 50
Second → PCA = 100
Third → PCA = 150
Fourth → PCA = 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For every choice, you perform the complete five-fold evaluation.&lt;/p&gt;

&lt;h4&gt;
  
  
  Viva question: Why test several PCA values?
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“Because the optimal dimensionality is data-dependent. Too few components may discard useful information, while too many components may retain redundancy and increase model complexity. I therefore evaluate several candidate dimensions empirically.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  4. Create fold-level storage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;fold_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each PCA setting, you create a new list to store the results from its five folds.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PCA = 50
    ↓
Fold 1 result
Fold 2 result
Fold 3 result
Fold 4 result
Fold 5 result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the same process is repeated for 100, 150 and 200.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Generate the five folds
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;skf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_bins&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a very important line.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;skf.split()&lt;/code&gt; generates the indices for each cross-validation fold.&lt;/p&gt;

&lt;p&gt;It uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;train_df
+
target_bins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;target_bins&lt;/code&gt; are the quantile-based bins you created earlier from the continuous target.&lt;/p&gt;

&lt;p&gt;The result for each fold is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;train_idx
valid_idx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;code&gt;train_idx&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Indices of observations used for training in that fold.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;valid_idx&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Indices of observations used for validation in that fold.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why use &lt;code&gt;target_bins&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;Because your problem is regression.&lt;/p&gt;

&lt;p&gt;The target is continuous, so you created quantile bins earlier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Continuous y_log
      ↓
5 quantile bins
      ↓
StratifiedKFold
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This attempts to keep the distribution of lower and higher target values reasonably balanced across folds.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. &lt;code&gt;enumerate(..., start=1)&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(...,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives each fold a human-readable number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fold 1
Fold 2
Fold 3
Fold 4
Fold 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without &lt;code&gt;start=1&lt;/code&gt;, Python would normally number them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0, 1, 2, 3, 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  1. Split Development Data
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;X_fold_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;X_fold_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These lines use the indices generated by &lt;code&gt;StratifiedKFold&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Training fold
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;X_fold_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Selects the observations belonging to the training portion of the current fold.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validation fold
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;X_fold_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Selects the observations belonging to the validation portion.&lt;/p&gt;

&lt;p&gt;Remember:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is not the final test set.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You are still working inside the development data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Full dataset
   ↓
Development data + independent test
   ↓
Development data
   ↓
5-fold CV
   ├── Fold training
   └── Fold validation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The independent test set remains untouched during this experiment.&lt;/p&gt;




&lt;h3&gt;
  
  
  7. Get training target
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;y_fold_train_log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;y_log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_numpy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This selects the &lt;strong&gt;log-transformed target values&lt;/strong&gt; corresponding to the current training fold.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.iloc[train_idx]&lt;/code&gt; selects the same observations as &lt;code&gt;X_fold_train&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.to_numpy()&lt;/code&gt; converts the pandas Series into a NumPy array.&lt;/p&gt;

&lt;p&gt;So the model will train using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X_fold_train
+
y_fold_train_log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  8. Get validation target in raw scale
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;y_fold_valid_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;TARGET&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_numpy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This retrieves the &lt;strong&gt;original, raw PCF target&lt;/strong&gt; for the validation observations.&lt;/p&gt;

&lt;p&gt;This is important because later you convert the model's log predictions back to the raw PCF scale before calculating the metrics.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Training:
y → log scale

Validation evaluation:
actual y → raw scale
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows your MAE, RMSE and R² to be interpreted in the original PCF units.&lt;/p&gt;




&lt;h1&gt;
  
  
  2. Structured Features
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;X_struct_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_struct_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;build_structured_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;X_fold_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;y_fold_train_log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;X_fold_valid&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This calls your previously defined &lt;code&gt;build_structured_features()&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;It creates the structured representation for this specific fold.&lt;/p&gt;

&lt;p&gt;The inputs are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X_fold_train
y_fold_train_log
X_fold_valid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function performs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Training fold
    ↓
Winsorization
    ↓
Numeric features

Categorical features
    ↓
One-hot encoding

Country + training target
    ↓
OOF target encoding
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X_struct_train
X_struct_valid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Critical leakage point
&lt;/h3&gt;

&lt;p&gt;This is one of the strongest parts of your methodology.&lt;/p&gt;

&lt;p&gt;For every fold, the structured preprocessing is &lt;strong&gt;re-fitted using only the fold-training data&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fold 1

Fold training
   ↓
fit winsorization
fit OHE
fit target encoding
   ↓
transform validation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The validation fold's target is not used to learn these transformations.&lt;/p&gt;

&lt;h4&gt;
  
  
  Viva question: Why rebuild structured features inside every fold?
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“Because preprocessing must be learned independently within each training fold. If I fitted preprocessing once using the entire development dataset before cross-validation, information from the validation folds could influence the preprocessing and lead to optimistic performance estimates.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is an excellent answer to remember.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. SBERT Features
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;X_sbert_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X_train_sbert&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;X_sbert_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X_train_sbert&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here you select the SBERT embeddings corresponding to the current fold.&lt;/p&gt;

&lt;p&gt;Remember:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X_train_sbert
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;contains SBERT embeddings for the &lt;strong&gt;entire development dataset&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You use the fold indices to select the relevant rows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Training SBERT
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;X_sbert_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X_train_sbert&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gets SBERT representations for the fold-training observations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validation SBERT
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;X_sbert_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X_train_sbert&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;valid_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gets SBERT representations for the fold-validation observations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Important subtlety
&lt;/h3&gt;

&lt;p&gt;You are not fitting SBERT here.&lt;/p&gt;

&lt;p&gt;SBERT was already used as a &lt;strong&gt;pretrained fixed encoder&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You are simply selecting the appropriate embeddings for each fold.&lt;/p&gt;




&lt;h1&gt;
  
  
  4. PCA — Fit on Training Fold Only
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;X_sbert_train_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_sbert_valid_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pca&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;apply_pca_to_sbert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;X_sbert_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;X_sbert_valid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;n_components&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;n_components&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This calls your PCA function.&lt;/p&gt;

&lt;p&gt;This is another &lt;strong&gt;major leakage-control step&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Inside the function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pca&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit_transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X_train_sbert&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is applied to the fold-training data.&lt;/p&gt;

&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pca&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X_valid_sbert&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is applied to the validation data.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fold training SBERT
       ↓
    FIT PCA
       ↓
Learn components
       ↓
Transform training


Fold validation SBERT
       ↓
Use SAME PCA
       ↓
Transform validation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You do &lt;strong&gt;not&lt;/strong&gt; fit PCA on the validation fold.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is this important?
&lt;/h3&gt;

&lt;p&gt;Because PCA learns from the feature distribution.&lt;/p&gt;

&lt;p&gt;If validation data were included when fitting PCA, the validation data would influence the representation used to evaluate the model.&lt;/p&gt;

&lt;h4&gt;
  
  
  Strong viva answer
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“For every fold, PCA is fitted only on the fold-training SBERT embeddings and then applied to the validation embeddings. This prevents information from the validation fold influencing the dimensionality-reduction step.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  5. Hybrid Feature Fusion
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;X_hybrid_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fuse_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;X_sbert_train_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;X_struct_train&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This combines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Reduced SBERT features
+
Structured features
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to create the training hybrid representation.&lt;/p&gt;

&lt;p&gt;Similarly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;X_hybrid_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fuse_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;X_sbert_valid_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;X_struct_valid&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;creates the validation hybrid representation.&lt;/p&gt;

&lt;p&gt;The result is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SBERT semantic features
          +
Structured features
          ↓
Hybrid feature matrix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why hybrid?
&lt;/h3&gt;

&lt;p&gt;Because:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SBERT
→ semantic information from text

Structured features
→ explicit numerical/categorical information

Hybrid
→ both information sources
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the core idea of your hybrid modelling approach.&lt;/p&gt;




&lt;h1&gt;
  
  
  6. Random Forest
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RandomForestRegressor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;n_estimators&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;n_jobs&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You create a &lt;strong&gt;Random Forest regression model&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;RandomForestRegressor&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Because your target is continuous PCF, you use the regression version rather than classification.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;n_estimators=300&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The Random Forest contains &lt;strong&gt;300 decision trees&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hybrid features
      ↓
Tree 1
Tree 2
Tree 3
...
Tree 300
      ↓
Average predictions
      ↓
Final prediction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More trees generally make the ensemble more stable, although they increase computation.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;random_state=42&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Makes the random elements of the Random Forest reproducible.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;n_jobs=-1&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Tells scikit-learn to use all available CPU cores for parallel processing.&lt;/p&gt;

&lt;p&gt;It affects computational speed, not the underlying modelling objective.&lt;/p&gt;




&lt;h1&gt;
  
  
  7. Train the Random Forest
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;X_hybrid_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;y_fold_train_log&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This trains the Random Forest.&lt;/p&gt;

&lt;p&gt;The inputs are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X_hybrid_train
       +
y_fold_train_log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the model learns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hybrid features
      ↓
Predict log(PCF + 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important point is that the model is trained on the &lt;strong&gt;log-transformed target&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  8. Generate Predictions
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;y_pred_log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;X_hybrid_valid&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model predicts the PCF values for the validation fold.&lt;/p&gt;

&lt;p&gt;But because the model was trained on &lt;code&gt;y_fold_train_log&lt;/code&gt;, these predictions are also in &lt;strong&gt;log space&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;y_pred_log
=
prediction in log scale
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  9. Convert Predictions Back to Raw PCF
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;y_pred_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;maximum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expm1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y_pred_log&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This line contains two operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;np.expm1()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;np.expm1(x)&lt;/code&gt; calculates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exp(x) - 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reverses the transformation produced by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;log(PCF + 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;log scale
   ↓
expm1
   ↓
original PCF scale
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why &lt;code&gt;expm1&lt;/code&gt; instead of &lt;code&gt;np.exp(x) - 1&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;expm1&lt;/code&gt; is designed specifically to calculate &lt;code&gt;exp(x)-1&lt;/code&gt; accurately, particularly for values close to zero.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;code&gt;np.maximum(..., 0)&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;maximum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expm1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y_pred_log&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that predicted PCF values cannot become negative.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Predicted PCF
     ↓
if negative → 0
if positive → keep it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is consistent with the physical interpretation of PCF as a non-negative quantity.&lt;/p&gt;

&lt;h4&gt;
  
  
  Viva question: Why force predictions to zero?
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“PCF is physically non-negative, so I constrain negative back-transformed predictions to zero. This is a domain-informed post-processing step.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Be careful: don't claim that Random Forest itself cannot produce negative predictions.&lt;/p&gt;




&lt;h1&gt;
  
  
  10. Evaluation
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;fold_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You create a dictionary containing the performance results for this fold.&lt;/p&gt;




&lt;h3&gt;
  
  
  Store PCA size
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PCA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;n_components&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Records whether this result came from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;50
100
150
200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Store fold number
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Records:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fold 1
Fold 2
...
Fold 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  MAE
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MAE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;mean_absolute_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;y_fold_valid_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;y_pred_raw&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MAE = &lt;strong&gt;Mean Absolute Error&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It measures the average absolute difference between actual and predicted PCF.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;actual PCF
     -
predicted PCF
     ↓
absolute error
     ↓
average
     ↓
MAE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Lower is better.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your best PCA configuration has the lowest MAE.&lt;/p&gt;




&lt;h3&gt;
  
  
  RMSE
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RMSE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;mean_squared_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;y_fold_valid_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;y_pred_raw&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;RMSE = &lt;strong&gt;Root Mean Squared Error&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It squares the errors before averaging and taking the square root.&lt;/p&gt;

&lt;p&gt;Because of the squaring, large prediction errors have greater influence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lower is better.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  R²
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;r2_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;y_fold_valid_raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;y_pred_raw&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;R² measures how much variation in the target is explained by the predictions relative to a baseline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Higher is generally better.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PCA 50 → R² = 0.654748
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means the model achieved an R² of approximately &lt;strong&gt;0.655&lt;/strong&gt; in the five-fold average.&lt;/p&gt;

&lt;p&gt;Do not say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The model predicts 65.5% of every PCF correctly.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is incorrect.&lt;/p&gt;

&lt;p&gt;Instead say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The average cross-validated R² was approximately 0.655.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Store Fold Results
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pca_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fold_results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After completing the five folds for a particular PCA configuration, the fold results are added to the main &lt;code&gt;pca_results&lt;/code&gt; list.&lt;/p&gt;

&lt;p&gt;Eventually you have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;50 components
→ 5 results

100 components
→ 5 results

150 components
→ 5 results

200 components
→ 5 results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Total:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;20 fold-level results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  PCA Results
&lt;/h1&gt;

&lt;p&gt;Now the second part of the cell summarises those results.&lt;/p&gt;

&lt;h3&gt;
  
  
  Convert results to DataFrame
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pca_results_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;pca_results&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Converts the list of dictionaries into a pandas DataFrame.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PCA&lt;/th&gt;
&lt;th&gt;Fold&lt;/th&gt;
&lt;th&gt;MAE&lt;/th&gt;
&lt;th&gt;RMSE&lt;/th&gt;
&lt;th&gt;R²&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;and so on.&lt;/p&gt;




&lt;h3&gt;
  
  
  Group by PCA
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pca_summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;pca_results_df&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PCA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This groups all five fold results according to PCA dimension.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PCA = 50
→ Fold 1–5

PCA = 100
→ Fold 1–5

...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Calculate mean MAE
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Mean_MAE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MAE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calculates the average MAE across the five folds.&lt;/p&gt;




&lt;h3&gt;
  
  
  Calculate mean RMSE
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;Mean_RMSE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RMSE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calculates the average RMSE across the five folds.&lt;/p&gt;




&lt;h3&gt;
  
  
  Calculate mean R²
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;Mean_R2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calculates the average R² across the five folds.&lt;/p&gt;




&lt;h3&gt;
  
  
  Reset index
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset_index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Converts &lt;code&gt;PCA&lt;/code&gt; from a grouping index back into a normal DataFrame column.&lt;/p&gt;




&lt;h3&gt;
  
  
  Display summary
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pca_summary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Displays the final comparison table.&lt;/p&gt;

&lt;p&gt;Your results are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PCA Components&lt;/th&gt;
&lt;th&gt;Mean MAE&lt;/th&gt;
&lt;th&gt;Mean RMSE&lt;/th&gt;
&lt;th&gt;Mean R²&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;50&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11,095.20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;93,823.13&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.6547&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;12,920.35&lt;/td&gt;
&lt;td&gt;114,964.07&lt;/td&gt;
&lt;td&gt;0.5302&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;150&lt;/td&gt;
&lt;td&gt;12,901.37&lt;/td&gt;
&lt;td&gt;114,008.82&lt;/td&gt;
&lt;td&gt;0.5352&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;13,032.94&lt;/td&gt;
&lt;td&gt;115,111.65&lt;/td&gt;
&lt;td&gt;0.5363&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What does the result tell you?
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;50-component PCA configuration is clearly the best among the tested choices&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Lowest MAE
Lowest RMSE
Highest R²
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Specifically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;50 components
MAE  = 11,095.20
RMSE = 93,823.13
R²   = 0.6547
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whereas 100, 150 and 200 components perform worse on all three average metrics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Your conclusion
&lt;/h3&gt;

&lt;p&gt;You can say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Among the candidate PCA dimensions tested, 50 components produced the strongest cross-validated performance, achieving the lowest mean MAE and RMSE and the highest mean R². Therefore, I selected 50 PCA components for the subsequent modelling stage.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Very important examiner question: Why did 50 perform better than 200?
&lt;/h3&gt;

&lt;p&gt;Don't say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Because 50 contains more information.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It actually contains &lt;strong&gt;less dimensional information&lt;/strong&gt; than 200.&lt;/p&gt;

&lt;p&gt;A better answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The results suggest that retaining more components did not improve predictive performance in this experiment. The additional components may contain redundant or less useful variation for the downstream Random Forest. However, I would describe this as an empirical result rather than claiming that 50 components is universally optimal.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That last sentence is important because your experiment only tested:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;50, 100, 150, 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You cannot conclude that &lt;strong&gt;50 is globally optimal&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can only conclude:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;50 was the best among the tested candidates.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Very important examiner question: Is this test set?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“No. This is five-fold cross-validation within the development data. The independent test set remains separate and should only be used for the final evaluation after the modelling choices have been made.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This distinction is &lt;strong&gt;extremely important&lt;/strong&gt; for your viva.&lt;/p&gt;

&lt;h3&gt;
  
  
  Very important examiner question: Why not choose PCA based on variance explained alone?
&lt;/h3&gt;

&lt;p&gt;Strong answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Explained variance measures how much variation in the original features is retained, but my ultimate objective is PCF prediction. Therefore, I selected the PCA dimension based on downstream cross-validated predictive performance rather than PCA variance alone.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Most important leakage question
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Examiner: “You already created SBERT embeddings for the whole training dataset. Isn't that leakage?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your defensible answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“SBERT was used as a pretrained fixed encoder and was not fitted or fine-tuned on the PCF dataset. Therefore, generating embeddings for the development observations does not use their PCF targets. For PCA, however, I explicitly fit the transformation separately inside each training fold and only transform the corresponding validation fold.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This distinction is very important:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SBERT
→ pretrained externally
→ no PCF target used

PCA
→ learned from our dataset
→ must be fitted inside each training fold
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  One more important point about your experiment
&lt;/h3&gt;

&lt;p&gt;Your code is actually doing &lt;strong&gt;two levels of selection&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Outer independent test set
        ↑
   kept untouched

Development data
        ↓
5-fold CV
        ↓
Compare PCA = 50, 100, 150, 200
        ↓
Select 50
        ↓
Final model development
        ↓
Independent test evaluation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a strong experimental structure because you are not using the final test set to decide the PCA dimension.&lt;/p&gt;

&lt;h3&gt;
  
  
  Presentation script
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“In this experiment, I selected the PCA dimensionality using five-fold cross-validation on the development data. I tested 50, 100, 150 and 200 components. For each PCA setting, I generated five stratified folds. Within every fold, I independently created the structured features, selected the corresponding SBERT embeddings, and fitted PCA only on the fold-training data. I then fused the reduced SBERT representation with the structured features and trained a Random Forest regressor using the log-transformed PCF target. Predictions were transformed back to the original PCF scale, and I evaluated each fold using MAE, RMSE and R². Finally, I averaged the five fold results for each PCA configuration. The 50-component configuration achieved the lowest mean MAE and RMSE and the highest mean R², so I selected 50 components for the subsequent modelling stage. This selection was made only within the development data, keeping the independent test set untouched.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For your PCA selection&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check fold-to-fold stability&lt;/li&gt;
&lt;li&gt;Compare variance explained&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>CELL-06-FEATURE COLUMN DEFINITIONS-&amp;-PCA FOR SBERT FEATURES</title>
      <dc:creator>TS</dc:creator>
      <pubDate>Mon, 14 Sep 2026 08:02:07 +0000</pubDate>
      <link>https://dev.to/ts2025/cell-06-feature-column-definitions-pca-for-sbert-features-3cah</link>
      <guid>https://dev.to/ts2025/cell-06-feature-column-definitions-pca-for-sbert-features-3cah</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ============================================================
# FEATURE COLUMN DEFINITIONS
# ============================================================
&lt;/span&gt;
&lt;span class="n"&gt;YEAR_COL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Year of reporting&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;WEIGHT_COL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Product weight (kg)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;COUNTRY_COL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Country (where company is incorporated)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;OTHER_CATEGORICAL_FEATURES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Company&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s GICS Industry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Protocol used for PCF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*Stage-level CO2e available&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Feature Column Definitions
&lt;/h3&gt;

&lt;p&gt;This cell defines the column names that will be used later in the &lt;strong&gt;structured feature engineering pipeline&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The main purpose is to avoid repeatedly writing long dataset column names throughout the notebook and to make the code easier to read and maintain.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Year column
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;YEAR_COL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Year of reporting&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a variable called &lt;code&gt;YEAR_COL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It points to the dataset column:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Year of reporting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is treated as a &lt;strong&gt;numeric structured feature&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2021
2022
2023
2024
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model can potentially learn whether reporting year is associated with differences in PCF values.&lt;/p&gt;

&lt;h4&gt;
  
  
  Viva question: Why include reporting year?
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“Reporting year can capture temporal differences in products, reporting practices, technologies, or emission factors that may be associated with PCF values.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Be careful not to claim that year &lt;em&gt;causes&lt;/em&gt; PCF changes. It is simply a predictive feature.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Product weight column
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;WEIGHT_COL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Product weight (kg)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This assigns the dataset's product-weight column to &lt;code&gt;WEIGHT_COL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is another &lt;strong&gt;numeric feature&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.5 kg
2 kg
10 kg
100 kg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You later apply &lt;strong&gt;winsorization&lt;/strong&gt; to this feature.&lt;/p&gt;

&lt;p&gt;The reason is that product weight can have extreme values, and extreme values can disproportionately influence some models.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Country column
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;COUNTRY_COL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Country (where company is incorporated)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This defines the country feature.&lt;/p&gt;

&lt;p&gt;It is treated differently from ordinary categorical variables because you later apply &lt;strong&gt;target encoding&lt;/strong&gt; to it.&lt;/p&gt;

&lt;p&gt;The transformation is approximately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Country
   ↓
Country-specific mean PCF
   ↓
Numerical feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Germany → 12.4
USA     → 18.7
Japan   → 10.9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These values are learned from the training data.&lt;/p&gt;

&lt;p&gt;Because target encoding uses the target variable, you specifically introduced &lt;strong&gt;out-of-fold target encoding&lt;/strong&gt; to reduce target leakage during development.&lt;/p&gt;

&lt;h4&gt;
  
  
  Viva question: Why target-encode country instead of one-hot encoding it?
&lt;/h4&gt;

&lt;p&gt;A good answer is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Country can have many categories, and one-hot encoding would create a separate binary feature for each country. Target encoding provides a compact numerical representation of the relationship between country and the target. However, because it uses target values, I use out-of-fold encoding for the training data to reduce target leakage.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  4. Other categorical features
&lt;/h3&gt;



&lt;p&gt;```python id="8s5x5x"&lt;br&gt;
OTHER_CATEGORICAL_FEATURES = [&lt;br&gt;
    "Company's GICS Industry",&lt;br&gt;
    "Protocol used for PCF",&lt;br&gt;
    "*Stage-level CO2e available"&lt;br&gt;
]&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This creates a list containing three categorical variables.

#### `Company's GICS Industry`

Represents the company's industry classification.

Different industries can have substantially different production processes and emission profiles.

#### `Protocol used for PCF`

Represents the protocol or methodology used to calculate the PCF.

This can be relevant because different calculation/reporting approaches may affect the resulting PCF data.

#### `*Stage-level CO2e available`

Indicates whether stage-level carbon-emission information is available.

This is a categorical/indicator-type feature representing the availability of more detailed PCF information.

---

### Why are these three features grouped together?

Because you process them using **One-Hot Encoding** later.

Your structured-feature pipeline effectively separates the variables into:



```text
Structured Features
│
├── Numeric
│   ├── Year
│   └── Product Weight
│
├── One-Hot Categorical
│   ├── GICS Industry
│   ├── PCF Protocol
│   └── Stage-level CO2e availability
│
└── Target Encoded
    └── Country
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is an important design decision.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why not process all categorical features in the same way?
&lt;/h3&gt;

&lt;p&gt;Because different representations can be appropriate for different characteristics.&lt;/p&gt;

&lt;p&gt;For the three ordinary categorical features:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Industry
Protocol
Stage-level availability
        ↓
One-Hot Encoding
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For country:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Country
   ↓
Target Encoding
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The target encoding gives a compact numerical representation, but because it uses the target, it requires additional leakage control.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why create variables instead of directly using column names?
&lt;/h3&gt;

&lt;p&gt;Instead of repeatedly writing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;X_train&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Product weight (kg)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you can write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;X_train&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;WEIGHT_COL&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This improves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Readability&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maintainability&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistency&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reduced risk of spelling mistakes&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the original dataset column name changes, you can update the definition in one place.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strong viva answer
&lt;/h3&gt;

&lt;p&gt;If the examiner asks &lt;strong&gt;“What is the purpose of this cell?”&lt;/strong&gt;, say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“This cell defines the feature groups used in my structured feature engineering pipeline. I identify year and product weight as numeric features, country as a target-encoded feature, and industry, PCF protocol, and stage-level CO2e availability as categorical features that will be one-hot encoded. Using variables for the column names also makes the later preprocessing functions more readable and maintainable.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  One important connection to your previous cells
&lt;/h3&gt;

&lt;p&gt;This cell provides the column definitions used by your &lt;code&gt;build_structured_features()&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;YEAR_COL
WEIGHT_COL
     ↓
Numeric processing

OTHER_CATEGORICAL_FEATURES
     ↓
One-Hot Encoding

COUNTRY_COL
     ↓
OOF Target Encoding
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So this cell itself &lt;strong&gt;does not transform any data&lt;/strong&gt;. It simply tells the later feature-engineering code &lt;strong&gt;which columns belong to which processing strategy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For your feature pipeline&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explain country encoding leakage&lt;/li&gt;
&lt;li&gt;Check feature availability before splitting
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ============================================================
# PCA FOR SBERT FEATURES
# ============================================================
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sklearn.decomposition&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PCA&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;apply_pca_to_sbert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;X_train_sbert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;X_valid_sbert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;n_components&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;pca&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PCA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;n_components&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;n_components&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;X_train_pca&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pca&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit_transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;X_train_sbert&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;X_valid_pca&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pca&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;X_valid_sbert&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;X_train_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;X_valid_pca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;pca&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  PCA FOR SBERT FEATURES
&lt;/h3&gt;

&lt;p&gt;This function applies &lt;strong&gt;Principal Component Analysis (PCA)&lt;/strong&gt; to the SBERT embeddings.&lt;/p&gt;

&lt;p&gt;The main reason is &lt;strong&gt;dimensionality reduction&lt;/strong&gt;: SBERT produces a relatively high-dimensional vector, and PCA can compress that representation while retaining as much variance/information as possible.&lt;/p&gt;

&lt;p&gt;Your flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Combined Text
      ↓
SBERT
      ↓
High-dimensional embeddings
      ↓
PCA
      ↓
Reduced-dimensional embeddings
      ↓
ML model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. Import PCA
&lt;/h3&gt;



&lt;p&gt;```python id="pca001"&lt;br&gt;
from sklearn.decomposition import PCA&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This imports the `PCA` class from scikit-learn.

PCA stands for **Principal Component Analysis**.

It transforms the original features into a new set of features called **principal components**.

The first principal component captures the largest amount of variance, the second captures the next largest amount, and so on.

---

### 2. Define the PCA function



```python id="pca002"
def apply_pca_to_sbert(
    X_train_sbert,
    X_valid_sbert,
    n_components
):
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You define a reusable function called:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apply_pca_to_sbert()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It takes three inputs.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;X_train_sbert&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;The SBERT embeddings for the training/development portion.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;800 observations × 384 SBERT features
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;code&gt;X_valid_sbert&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;The SBERT embeddings for the validation portion.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;200 observations × 384 features
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;code&gt;n_components&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;The number of dimensions you want after PCA.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;384 → 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means PCA reduces the SBERT representation from 384 dimensions to 50 dimensions.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Create the PCA object
&lt;/h3&gt;



&lt;p&gt;```python id="pca003"&lt;br&gt;
pca = PCA(&lt;br&gt;
    n_components=n_components,&lt;br&gt;
    random_state=42&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This creates the PCA transformation.

#### `n_components=n_components`

This tells PCA how many principal components to retain.

If:



```python
n_components = 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Original SBERT
384 dimensions

        ↓ PCA

Reduced SBERT
50 dimensions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important point is that PCA does &lt;strong&gt;not simply select the first 50 original SBERT features&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead, it creates &lt;strong&gt;new combinations of the original features&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Why reduce the SBERT dimensions?
&lt;/h3&gt;

&lt;p&gt;This is a likely viva question.&lt;/p&gt;

&lt;p&gt;SBERT gives you a dense embedding with many dimensions.&lt;/p&gt;

&lt;p&gt;High-dimensional features can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;increase computational cost&lt;/li&gt;
&lt;li&gt;increase memory requirements&lt;/li&gt;
&lt;li&gt;introduce redundant information&lt;/li&gt;
&lt;li&gt;make downstream ML models more expensive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PCA provides a way to create a smaller representation while retaining major patterns in the original embeddings.&lt;/p&gt;

&lt;h4&gt;
  
  
  Strong viva answer
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“I applied PCA to reduce the dimensionality of the SBERT embeddings. SBERT produces a high-dimensional representation, and PCA allows me to create a more compact representation while retaining the principal sources of variance. This can reduce computational cost and potentially remove redundant information for the downstream ML models.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  5. Fit PCA on training data
&lt;/h3&gt;



&lt;p&gt;```python id="pca004"&lt;br&gt;
X_train_pca = pca.fit_transform(&lt;br&gt;
    X_train_sbert&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This line performs **two operations**:



```text
fit
+
transform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;fit()&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;PCA learns the transformation from the training data.&lt;/p&gt;

&lt;p&gt;It determines the principal directions/components based on the training embeddings.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;code&gt;transform()&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;The training embeddings are then projected onto those learned principal components.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X_train_sbert
      ↓
PCA learns components
      ↓
Training data transformed
      ↓
X_train_pca
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Very important leakage point
&lt;/h3&gt;

&lt;p&gt;PCA must be &lt;strong&gt;fitted only on training data&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You correctly do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pca&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit_transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X_train_sbert&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than fitting PCA on both training and validation data.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because PCA learns information about the distribution of the data.&lt;/p&gt;

&lt;p&gt;If you did:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pca&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit_transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vstack&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;X_train_sbert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_valid_sbert&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then the validation distribution would influence the learned PCA components.&lt;/p&gt;

&lt;p&gt;That would introduce information from the validation set into the preprocessing stage.&lt;/p&gt;

&lt;h4&gt;
  
  
  Viva question: Why fit PCA only on training data?
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“Because PCA learns its components from the data distribution. If I fitted it using both training and validation data, information from the validation set would influence the transformation. Therefore, I fit PCA only on the training data and apply the learned transformation to validation data.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  6. Transform validation data
&lt;/h3&gt;



&lt;p&gt;```python id="pca005"&lt;br&gt;
X_valid_pca = pca.transform(&lt;br&gt;
    X_valid_sbert&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


This applies the **already learned PCA transformation** to the validation embeddings.

Notice the difference:



```text
Training:
pca.fit_transform()

Validation:
pca.transform()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This distinction is extremely important.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;fit_transform&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Used when the transformation needs to &lt;strong&gt;learn parameters from training data and then transform it&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;transform&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Used when the transformation has already been learned and you want to apply the same transformation to new data.&lt;/p&gt;

&lt;p&gt;So your pipeline is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Training SBERT
      ↓
fit PCA
      ↓
learn principal components
      ↓
transform training


Validation SBERT
      ↓
use existing PCA
      ↓
transform validation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You do &lt;strong&gt;not&lt;/strong&gt; calculate new PCA components for validation.&lt;/p&gt;




&lt;h3&gt;
  
  
  7. Return the results
&lt;/h3&gt;



&lt;p&gt;```python id="pca006"&lt;br&gt;
return (&lt;br&gt;
    X_train_pca,&lt;br&gt;
    X_valid_pca,&lt;br&gt;
    pca&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


The function returns three things.

#### `X_train_pca`

The reduced-dimensional training SBERT features.

#### `X_valid_pca`

The reduced-dimensional validation SBERT features.

#### `pca`

The fitted PCA object itself.

Returning the PCA object is useful because it contains the learned transformation and can be reused later.

For example, the same fitted PCA can be applied to another unseen dataset:



```text
New SBERT embeddings
        ↓
fitted PCA
        ↓
same reduced feature space
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The most important concept: &lt;code&gt;fit_transform&lt;/code&gt; vs &lt;code&gt;transform&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Remember this for your viva:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TRAINING
pca.fit_transform(X_train_sbert)
        ↓
Learn + transform


VALIDATION / TEST
pca.transform(X_valid_sbert)
        ↓
Transform only
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the examiner asks &lt;strong&gt;“Why don't you use &lt;code&gt;fit_transform&lt;/code&gt; on validation?”&lt;/strong&gt;, say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Because that would learn a separate transformation from the validation data. I want validation to remain unseen during preprocessing, so I reuse the PCA transformation learned from the training data.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;Suppose SBERT produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Training:   800 × 384
Validation: 200 × 384
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and you select:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;n_components&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After PCA:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Training PCA:   800 × 50
Validation PCA: 200 × 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So you reduce:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;384 → 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while keeping the observations unchanged.&lt;/p&gt;

&lt;p&gt;The number of &lt;strong&gt;rows does not change&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Only the number of &lt;strong&gt;feature columns&lt;/strong&gt; changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  How this connects to your hybrid features
&lt;/h3&gt;

&lt;p&gt;This is particularly important in your project because later you combine the text representation with structured features.&lt;/p&gt;

&lt;p&gt;Your pipeline can be understood as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Combined_Text
      ↓
    SBERT
      ↓
384-dimensional embeddings
      ↓
     PCA
      ↓
Reduced SBERT features
      +
Structured features
      ↓
Hybrid feature representation
      ↓
ML model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So PCA is applied to the &lt;strong&gt;SBERT part&lt;/strong&gt;, not to the entire hybrid feature matrix at this stage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strong presentation wording
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“Here, I define a reusable PCA function for reducing the dimensionality of the SBERT embeddings. The function receives training embeddings, validation embeddings, and the desired number of components. I initialise PCA with that number of components and fit it only on the training SBERT features. The training embeddings are therefore used to learn the principal components and are transformed into the reduced representation. I then apply the same fitted PCA transformation to the validation embeddings without refitting it. Finally, I return both reduced feature matrices and the fitted PCA object. This ensures dimensionality reduction while avoiding information leakage from the validation data.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Examiner trap questions
&lt;/h3&gt;

&lt;h4&gt;
  
  
  “Does PCA select the most important original SBERT features?”
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“No. PCA creates new orthogonal components as linear combinations of the original features. It does not simply select a subset of the original dimensions.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  “Does PCA guarantee better prediction?”
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“No. PCA is a dimensionality-reduction technique, not a prediction-improvement guarantee. It may reduce computational cost and redundancy, but the effect on predictive performance needs to be evaluated empirically.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  “Why not remove PCA completely?”
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“That is a valid alternative. PCA is a modelling choice that can be compared experimentally. If the original SBERT dimensions provide better validation performance, retaining them may be preferable.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  “Does PCA use the target variable?”
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“No. Standard PCA is unsupervised. It learns the principal directions from the feature matrix and does not use the PCF target.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  “Why is that useful for leakage control?”
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“Because PCA itself does not use the target, but it still learns from the feature distribution. Therefore, I must still fit it only on the training data and not on validation or test data.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For your SBERT PCA step&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explain how to choose components&lt;/li&gt;
&lt;li&gt;Check PCA assumptions&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>cell-05</title>
      <dc:creator>TS</dc:creator>
      <pubDate>Mon, 14 Sep 2026 07:52:52 +0000</pubDate>
      <link>https://dev.to/ts2025/cell-05-pfl</link>
      <guid>https://dev.to/ts2025/cell-05-pfl</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 5. Five-Fold Development Evaluation
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sklearn.model_selection&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StratifiedKFold&lt;/span&gt;

&lt;span class="n"&gt;target_bins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;qcut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;y_log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;duplicates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;drop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;skf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StratifiedKFold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;n_splits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;shuffle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;RANDOM_STATE&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Stratified folds:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;skf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n_splits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Five-Fold Development Evaluation
&lt;/h3&gt;

&lt;p&gt;This cell prepares &lt;strong&gt;five-fold cross-validation&lt;/strong&gt; for the development/training data.&lt;/p&gt;

&lt;p&gt;The important point is that your target is a &lt;strong&gt;continuous PCF value&lt;/strong&gt;, so you cannot directly use ordinary stratification on the raw target. Instead, you first divide the continuous target into bins and then use those bins for stratification.&lt;/p&gt;

&lt;p&gt;The overall idea is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Continuous PCF target
        ↓
Log-transformed target (y_log)
        ↓
Divide into 5 target ranges
        ↓
Create stratified 5 folds
        ↓
Each fold has a similar target distribution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. Import StratifiedKFold
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sklearn.model_selection&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StratifiedKFold&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This imports &lt;code&gt;StratifiedKFold&lt;/code&gt; from scikit-learn.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;StratifiedKFold&lt;/code&gt; creates multiple train/validation folds while trying to preserve the distribution of a &lt;strong&gt;categorical class label&lt;/strong&gt; across the folds.&lt;/p&gt;

&lt;p&gt;The problem is that PCF is a &lt;strong&gt;continuous regression target&lt;/strong&gt;, not a class label.&lt;/p&gt;

&lt;p&gt;Therefore, you create target bins first.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Create target bins
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;target_bins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;qcut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;y_log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;duplicates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;drop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the most important part of the cell.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;y_log&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This is your &lt;strong&gt;log-transformed PCF target&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You are using the transformed target rather than the original PCF values.&lt;/p&gt;

&lt;p&gt;A log transformation is commonly used when a continuous target is highly right-skewed, because it can reduce the influence of extremely large values and make the distribution more manageable for modelling.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. &lt;code&gt;pd.qcut()&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;qcut&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;qcut&lt;/code&gt; divides the values into &lt;strong&gt;quantile-based bins&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Unlike a normal fixed-width binning method, it tries to put approximately the same number of observations into each bin.&lt;/p&gt;

&lt;p&gt;For example, conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;y_log values
     ↓
 ┌───────────────┐
 │ Lowest 20%    │ → Bin 0
 ├───────────────┤
 │ 20–40%        │ → Bin 1
 ├───────────────┤
 │ 40–60%        │ → Bin 2
 ├───────────────┤
 │ 60–80%        │ → Bin 3
 ├───────────────┤
 │ Highest 20%   │ → Bin 4
 └───────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So although the original target is continuous, you temporarily create approximately five groups representing different levels of PCF.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. &lt;code&gt;q=5&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This asks &lt;code&gt;qcut&lt;/code&gt; to create &lt;strong&gt;five quantile groups&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Approximately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bin 0 → lowest 20%
Bin 1 → next 20%
Bin 2 → middle 20%
Bin 3 → next 20%
Bin 4 → highest 20%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does &lt;strong&gt;not&lt;/strong&gt; mean that you are converting the regression problem into a classification problem.&lt;/p&gt;

&lt;p&gt;That distinction is very important.&lt;/p&gt;

&lt;h4&gt;
  
  
  Viva question: Are you turning PCF prediction into classification?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“No. The actual task remains regression because the original target is continuous. I only use quantile bins to create stratification labels for cross-validation, so that the folds have a more balanced distribution of target values.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  5. &lt;code&gt;labels=False&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells &lt;code&gt;qcut&lt;/code&gt; to return integer labels instead of interval descriptions.&lt;/p&gt;

&lt;p&gt;So instead of something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(1.2, 2.8]
(2.8, 4.1]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0
1
2
3
4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These integer values can then be used by &lt;code&gt;StratifiedKFold&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. &lt;code&gt;duplicates="drop"&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;duplicates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;drop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This handles situations where quantile boundaries are not unique.&lt;/p&gt;

&lt;p&gt;For example, if many observations have exactly the same target value, &lt;code&gt;qcut&lt;/code&gt; may not be able to create exactly five distinct intervals.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;duplicates="drop"&lt;/code&gt; tells pandas to remove duplicate bin boundaries instead of throwing an error.&lt;/p&gt;

&lt;h4&gt;
  
  
  Viva question: Why did you use &lt;code&gt;duplicates="drop"&lt;/code&gt;?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Because repeated target values can produce identical quantile boundaries. &lt;code&gt;duplicates='drop'&lt;/code&gt; makes the binning more robust by allowing duplicate boundaries to be removed instead of causing an error.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  7. Create StratifiedKFold
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;skf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StratifiedKFold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;n_splits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;shuffle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;RANDOM_STATE&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates the five-fold cross-validation strategy.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;n_splits=5&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;n_splits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The development data is divided into &lt;strong&gt;five folds&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fold 1 → validation
Fold 2 → training
Fold 3 → training
Fold 4 → training
Fold 5 → training
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then it rotates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fold 1 → training
Fold 2 → validation
Fold 3 → training
Fold 4 → training
Fold 5 → training
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and so on.&lt;/p&gt;

&lt;p&gt;Eventually, every observation gets used as validation data exactly once.&lt;/p&gt;




&lt;h3&gt;
  
  
  8. &lt;code&gt;shuffle=True&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;shuffle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This randomly shuffles the observations before creating the folds.&lt;/p&gt;

&lt;p&gt;This helps avoid having the folds determined by the original ordering of the dataset.&lt;/p&gt;

&lt;p&gt;For example, if your dataset happened to be ordered by country, year, or PCF magnitude, not shuffling could produce unrepresentative folds.&lt;/p&gt;

&lt;p&gt;Because you are using stratification, the shuffled data is then distributed while attempting to preserve the target-bin proportions.&lt;/p&gt;




&lt;h3&gt;
  
  
  9. &lt;code&gt;random_state&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;RANDOM_STATE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the random shuffling &lt;strong&gt;reproducible&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you run the notebook again with the same data and the same &lt;code&gt;RANDOM_STATE&lt;/code&gt;, you should obtain the same fold assignment.&lt;/p&gt;

&lt;h4&gt;
  
  
  Viva question: Why is reproducibility important?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“It ensures that the same data partitioning can be reproduced when I rerun the experiment, making model comparison and evaluation more consistent.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  10. Print number of folds
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Stratified folds:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;skf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n_splits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;skf.n_splits&lt;/code&gt; returns the number of folds.&lt;/p&gt;

&lt;p&gt;So this should print:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stratified folds: 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is simply a verification step.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why use StratifiedKFold for a regression problem?
&lt;/h3&gt;

&lt;p&gt;This is &lt;strong&gt;very likely to be asked in your viva&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Normally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Classification
     ↓
StratifiedKFold
     ↓
Preserve class proportions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But your problem is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PCF prediction
     ↓
Regression
     ↓
Continuous target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So you cannot directly stratify the raw continuous target.&lt;/p&gt;

&lt;p&gt;Your solution is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Continuous y_log
      ↓
Quantile binning
      ↓
Target bins
      ↓
StratifiedKFold
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows you to approximately preserve the distribution of the target across the five development folds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Important distinction from ordinary KFold
&lt;/h3&gt;

&lt;p&gt;With ordinary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;KFold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_splits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the folds are created without explicitly trying to preserve the target distribution.&lt;/p&gt;

&lt;p&gt;With your approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;StratifiedKFold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_splits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you provide &lt;code&gt;target_bins&lt;/code&gt; as the stratification labels, so each fold attempts to contain a similar proportion of low, medium, and high target observations.&lt;/p&gt;

&lt;h3&gt;
  
  
  One subtle point the examiner may challenge
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;q=5&lt;/code&gt; and &lt;code&gt;n_splits=5&lt;/code&gt; are &lt;strong&gt;two different things&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;q=5
↓
creates 5 target bins

n_splits=5
↓
creates 5 cross-validation folds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They happen to both be five in your code, but they serve different purposes.&lt;/p&gt;

&lt;p&gt;You could theoretically have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;q = 5
n_splits = 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and that would still be conceptually valid if the sample size and bin frequencies support it.&lt;/p&gt;

&lt;h3&gt;
  
  
  One thing to remember for the next cell
&lt;/h3&gt;

&lt;p&gt;This cell &lt;strong&gt;only creates the cross-validation strategy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You have not actually performed cross-validation yet.&lt;/p&gt;

&lt;p&gt;The actual fold generation will happen later when you do something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;train_idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valid_idx&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;skf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_bins&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At that point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X
+
target_bins
      ↓
skf.split()
      ↓
training indices + validation indices
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Strong presentation explanation
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“Because PCF is a continuous regression target, I cannot directly apply stratification to the raw target values. Therefore, I first use &lt;code&gt;pd.qcut&lt;/code&gt; on the log-transformed target to create five quantile-based target bins. These bins act as temporary stratification labels. I then create a five-fold &lt;code&gt;StratifiedKFold&lt;/code&gt; with shuffling and a fixed random state. This allows the development folds to have approximately similar distributions of low and high target values, providing a more balanced and reproducible cross-validation evaluation. The bins are only used for splitting; the actual prediction task remains regression.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For your five-fold evaluation&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check bin sizes before splitting&lt;/li&gt;
&lt;li&gt;Explain fold-level leakage risks&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>cell-04-SBERT TEXT REPRESENTATION</title>
      <dc:creator>TS</dc:creator>
      <pubDate>Mon, 14 Sep 2026 06:51:52 +0000</pubDate>
      <link>https://dev.to/ts2025/cell-04-sbert-text-representation-2nbb</link>
      <guid>https://dev.to/ts2025/cell-04-sbert-text-representation-2nbb</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ============================================================
# SBERT TEXT REPRESENTATION
# ============================================================
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sentence_transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SentenceTransformer&lt;/span&gt;

&lt;span class="n"&gt;SBERT_MODEL_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;all-MiniLM-L6-v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;sbert_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SentenceTransformer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SBERT_MODEL_NAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;train_texts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Combined_Text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;test_texts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;test_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Combined_Text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;X_train_sbert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sbert_model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;train_texts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;show_progress_bar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;convert_to_numpy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;X_test_sbert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sbert_model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;test_texts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;show_progress_bar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;convert_to_numpy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SBERT dimension:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_train_sbert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Training embeddings:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_train_sbert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Test embeddings:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_test_sbert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# ============================================================
# HYBRID FEATURE FUSION
# ============================================================
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fuse_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sbert_features&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;structured_features&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hstack&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="n"&gt;sbert_features&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;structured_features&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SBERT TEXT REPRESENTATION
&lt;/h3&gt;

&lt;p&gt;This cell converts the combined text feature into &lt;strong&gt;numerical semantic embeddings&lt;/strong&gt; using SBERT. These embeddings will later be combined with the structured features to create the &lt;strong&gt;hybrid feature representation&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Import SBERT
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sentence_transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SentenceTransformer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Imports the &lt;code&gt;SentenceTransformer&lt;/code&gt; class from the &lt;code&gt;sentence_transformers&lt;/code&gt; library.&lt;/li&gt;
&lt;li&gt;SBERT stands for &lt;strong&gt;Sentence-BERT&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;It converts text into dense numerical vectors called &lt;strong&gt;embeddings&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;These embeddings capture the &lt;strong&gt;semantic meaning and relationships between words/text&lt;/strong&gt;, rather than simply representing individual words.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Viva question: Why did you use SBERT?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I used SBERT to convert the combined textual information into dense semantic embeddings. This allows the ML models to capture relationships between product names, companies, and countries that may not be captured by simple categorical or numerical encoding.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  2. Select the SBERT model
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;SBERT_MODEL_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;all-MiniLM-L6-v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Stores the name of the pretrained SBERT model in a variable.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"all-MiniLM-L6-v2"&lt;/code&gt; is a lightweight sentence-transformer model.&lt;/li&gt;
&lt;li&gt;It provides a good balance between &lt;strong&gt;semantic representation, computational cost, and embedding size&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important point is that you are using a &lt;strong&gt;pretrained model&lt;/strong&gt;, rather than training SBERT from scratch on your PCF dataset.&lt;/p&gt;

&lt;h4&gt;
  
  
  Viva question: Why this particular model?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I selected &lt;code&gt;all-MiniLM-L6-v2&lt;/code&gt; because it is a relatively lightweight pretrained sentence-transformer that provides useful semantic embeddings while keeping computational requirements manageable.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  If examiner asks: Why not a larger model?
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“A larger transformer could potentially provide richer representations, but it would increase computational cost. For this project, I prioritised a practical balance between semantic representation and efficiency.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  3. Load the pretrained model
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;sbert_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SentenceTransformer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SBERT_MODEL_NAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates the SBERT model object.&lt;/p&gt;

&lt;p&gt;The process is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"all-MiniLM-L6-v2"
        ↓
SentenceTransformer()
        ↓
Pretrained SBERT model loaded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model is now ready to convert text into embeddings.&lt;/p&gt;

&lt;h4&gt;
  
  
  Important viva point
&lt;/h4&gt;

&lt;p&gt;You are &lt;strong&gt;not fitting SBERT on the PCF dataset here&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You are using an already pretrained language model to generate representations.&lt;/p&gt;

&lt;p&gt;That is different from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;used by conventional ML models.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Get training text
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;train_texts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;train_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Combined_Text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break this line into four operations.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;train_df["Combined_Text"]&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Selects the &lt;code&gt;Combined_Text&lt;/code&gt; column from the training dataset.&lt;/p&gt;

&lt;p&gt;Earlier, you created something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Product: Laptop | Company: Dell | Country: USA
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So each row contains a combined textual representation.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;.fillna("")&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replaces missing text values with an empty string.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NaN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;""
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents missing values from causing problems during text encoding.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;.astype(str)&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Converts every value into a string.&lt;/p&gt;

&lt;p&gt;SBERT expects textual input, so this ensures the input has the appropriate type.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;.tolist()&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Converts the pandas Series into a Python list.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pandas Series
     ↓
Python list
     ↓
["Product: Laptop | Company: Dell | Country: USA",
 "Product: Phone | Company: Apple | Country: USA",
 ...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This list is then passed to SBERT.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Get test text
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;test_texts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;test_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Combined_Text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This performs exactly the same preparation for the &lt;strong&gt;test dataset&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The important difference is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;train_df → train_texts
test_df  → test_texts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two datasets remain separate.&lt;/p&gt;

&lt;h4&gt;
  
  
  Viva question: Why do you encode train and test separately?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I encode the training and test observations separately so that each observation receives its embedding without mixing the datasets. The same pretrained SBERT model is used for both, ensuring that the representations are generated in a consistent way.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  6. Generate training embeddings
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;X_train_sbert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sbert_model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;train_texts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;show_progress_bar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;convert_to_numpy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the &lt;strong&gt;main SBERT operation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The model takes the training text and converts every text record into a numerical vector.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Combined_Text
      ↓
     SBERT
      ↓
Dense numerical embedding
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Product: Laptop | Company: Dell | Country: USA"
                     ↓
        [0.12, -0.34, 0.56, ..., 0.08]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual vector contains many numerical dimensions.&lt;/p&gt;




&lt;h3&gt;
  
  
  7. &lt;code&gt;show_progress_bar=True&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;show_progress_bar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Displays the encoding progress while SBERT processes the observations.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Encoding: 100% |████████████| ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does &lt;strong&gt;not affect the model's predictions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is simply useful for monitoring a potentially time-consuming operation.&lt;/p&gt;

&lt;h4&gt;
  
  
  Viva question: Does this parameter affect accuracy?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“No. It only controls whether a progress bar is displayed during encoding.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  8. &lt;code&gt;convert_to_numpy=True&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;convert_to_numpy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Requests the embeddings as a NumPy array.&lt;/p&gt;

&lt;p&gt;This is important because later you use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hstack&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to combine the SBERT features with structured features.&lt;/p&gt;

&lt;p&gt;So the pipeline becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SBERT
 ↓
NumPy embedding matrix
 ↓
np.hstack()
 ↓
Hybrid features
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  9. Generate test embeddings
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;X_test_sbert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sbert_model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;test_texts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;show_progress_bar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;convert_to_numpy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same pretrained SBERT model converts the test texts into embeddings.&lt;/p&gt;

&lt;p&gt;Notice that you do &lt;strong&gt;not&lt;/strong&gt; train or refit SBERT using the test data.&lt;/p&gt;

&lt;p&gt;You simply apply the already loaded model.&lt;/p&gt;

&lt;p&gt;This is important for maintaining a clean evaluation process.&lt;/p&gt;

&lt;h4&gt;
  
  
  Viva question: Is this data leakage?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“No, because the SBERT model is pretrained independently and is not fitted on the PCF test data. The test text is only passed through the pretrained encoder to generate representations.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A more cautious answer if the examiner is strict:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The encoder itself is pretrained externally rather than learned from this dataset. I use it only as a fixed representation model and do not fine-tune it using the PCF test set.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  10. Print SBERT dimension
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SBERT dimension:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_train_sbert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;X_train_sbert.shape&lt;/code&gt; contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(number of observations, number of embedding dimensions)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(800, 384)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;selects the second dimension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;384
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So this prints the number of features generated by SBERT.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;all-MiniLM-L6-v2&lt;/code&gt;, the embedding dimension is typically &lt;strong&gt;384&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  11. Print training embedding shape
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Training embeddings:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_train_sbert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(number of training observations, embedding dimensions)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Training embeddings: (800, 384)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows you to verify that the number of embeddings corresponds to the number of training observations.&lt;/p&gt;




&lt;h3&gt;
  
  
  12. Print test embedding shape
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Test embeddings:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_test_sbert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly, this verifies the dimensions of the test embeddings.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Test embeddings: (200, 384)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important requirement is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;number of training embeddings = number of training rows

number of test embeddings = number of test rows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  HYBRID FEATURE FUSION
&lt;/h3&gt;

&lt;p&gt;Now you combine the &lt;strong&gt;semantic text representation&lt;/strong&gt; with the &lt;strong&gt;structured representation&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fuse_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sbert_features&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;structured_features&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This defines a function called &lt;code&gt;fuse_features&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It accepts two inputs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sbert_features
structured_features
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;code&gt;sbert_features&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;These are the SBERT embeddings.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;384 dimensions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;code&gt;structured_features&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;These are the features you previously created from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Year&lt;/li&gt;
&lt;li&gt;Product weight&lt;/li&gt;
&lt;li&gt;One-hot encoded categorical variables&lt;/li&gt;
&lt;li&gt;Country target encoding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So you have two different types of information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SBERT
→ semantic information

Structured features
→ explicit numerical/categorical information
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  13. Horizontally concatenate the features
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hstack&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="n"&gt;sbert_features&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;structured_features&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;np.hstack()&lt;/code&gt; means &lt;strong&gt;horizontal stacking&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Suppose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SBERT = 384 features
Structured = 10 features
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;384 + 10 = 394 features
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ SBERT features | Structured features ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For every observation, the two feature vectors are placed side by side.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SBERT
[0.12, -0.34, 0.56, ..., 0.08]
              +
Structured
[2023, 1.5, 0, 1, 0, ...]
              ↓
Hybrid
[0.12, -0.34, 0.56, ..., 0.08, 2023, 1.5, 0, 1, 0, ...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why use hybrid features?
&lt;/h3&gt;

&lt;p&gt;This is one of the &lt;strong&gt;most important viva questions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Your reasoning is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Text alone
   ↓
captures semantic information

Structured features alone
   ↓
captures explicit numerical/categorical information

Both together
   ↓
hybrid representation
   ↓
potentially richer information for ML
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Strong viva answer
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“I used hybrid feature fusion because the two representations capture different types of information. SBERT captures semantic relationships within the combined text, while the structured features explicitly represent numerical and categorical information such as reporting year, product weight, industry, protocol, and country. By horizontally concatenating them, the downstream ML models can use both sources of information simultaneously.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Very important distinction
&lt;/h3&gt;

&lt;p&gt;Do &lt;strong&gt;not&lt;/strong&gt; say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“SBERT understands the exact carbon footprint.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That would overclaim.&lt;/p&gt;

&lt;p&gt;Instead say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“SBERT provides a semantic representation of the textual information, which may contain information useful for predicting PCF.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The PCF prediction itself is performed by your downstream ML model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Overall pipeline for this section
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Product
Company
Country
   ↓
Combined_Text
   ↓
Pretrained SBERT
   ↓
384-dimensional semantic embeddings
   ↓
              ┌────────────────────┐
              │ Structured features│
              │ Year               │
              │ Weight             │
              │ Industry           │
              │ Protocol           │
              │ Country encoding   │
              └────────────────────┘
                         ↓
SBERT embeddings ──→ Feature Fusion ←── Structured features
                         ↓
                  Hybrid Features
                         ↓
                    ML Model
                         ↓
                  PCF Prediction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Most likely examiner questions
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Why SBERT instead of TF-IDF?
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“TF-IDF mainly represents lexical importance based on word frequency, whereas SBERT produces dense contextual semantic embeddings. Since product and company descriptions can contain meaningful relationships beyond exact word matching, SBERT provides a richer semantic representation.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Why combine SBERT with structured features?
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“Because SBERT and structured features contain complementary information. SBERT captures semantic text information, while structured features preserve explicit numerical and categorical information.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Why use the same SBERT model for train and test?
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“To maintain a consistent feature space. The same pretrained encoder transforms both datasets, but the test data is not used to fit or fine-tune the encoder.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  What does &lt;code&gt;np.hstack()&lt;/code&gt; do?
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“It horizontally concatenates the two feature matrices, placing the SBERT and structured features side by side for each observation.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  What is the biggest limitation of this approach?
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;“SBERT was pretrained for general language representation, not specifically for PCF prediction. Therefore, its embeddings may not capture all domain-specific carbon-footprint relationships. The hybrid approach helps by adding explicit structured information, but it does not guarantee that every relevant PCF relationship is represented.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Presentation wording
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“In this stage, I convert the combined textual representation into semantic embeddings using the pretrained &lt;code&gt;all-MiniLM-L6-v2&lt;/code&gt; SBERT model. First, I prepare the training and test text by handling missing values and converting them into lists. I then pass both datasets through the same pretrained SBERT encoder and obtain numerical embeddings. I verify the dimensions of these embeddings to ensure that the transformation was successful. Finally, I define a feature-fusion function that horizontally combines the SBERT embeddings with the structured features. This creates a hybrid representation containing both semantic textual information and explicit numerical and categorical information, which is then used by the downstream machine-learning models.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For your SBERT pipeline&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explain how to verify row alignment&lt;/li&gt;
&lt;li&gt;Explain feature scaling after fusion&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
  </channel>
</rss>
