File size: 3,319 Bytes
47d42b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
"""Feature engineering for train_xgboost."""

from __future__ import annotations

import logging

import numpy as np
import pandas as pd

LOGGER = logging.getLogger("static_xgboost")


def build_feature_frame(df: pd.DataFrame) -> pd.DataFrame:
    """Build a model-ready frame with engineered features and both targets."""
    work = df.copy()
    if "timestamp" not in work.columns:
        raise KeyError("Expected canonical 'timestamp' column after loading data.")

    work["timestamp"] = pd.to_datetime(work["timestamp"], errors="coerce", utc=True)
    work = work.dropna(subset=["timestamp"]).sort_values("timestamp")

    numeric_cols = [
        "temperature",
        "humidity",
        "wind_direction",
        "wind_speed",
        "wind_gust",
        "pressure",
        "ghi",
        "dni",
        "dhi",
    ]
    for col in numeric_cols:
        if col in work.columns:
            work[col] = pd.to_numeric(work[col], errors="coerce")

    present_numeric = [col for col in numeric_cols if col in work.columns]
    work = work.dropna(subset=["ghi"])
    work[present_numeric] = work[present_numeric].interpolate(limit_direction="both")
    work[present_numeric] = work[present_numeric].ffill().bfill()

    # Consolidate duplicate timestamps by averaging numeric weather columns.
    grouped = work.groupby("timestamp", as_index=False)[present_numeric].mean()
    grouped = grouped.sort_values("timestamp").reset_index(drop=True)

    ts = grouped["timestamp"]
    grouped["hour"] = ts.dt.hour
    grouped["day_of_year"] = ts.dt.dayofyear
    grouped["month"] = ts.dt.month

    grouped["hour_sin"] = np.sin(2 * np.pi * grouped["hour"] / 24)
    grouped["hour_cos"] = np.cos(2 * np.pi * grouped["hour"] / 24)
    grouped["doy_sin"] = np.sin(2 * np.pi * grouped["day_of_year"] / 365.25)
    grouped["doy_cos"] = np.cos(2 * np.pi * grouped["day_of_year"] / 365.25)

    for lag in (1, 2, 3, 24):
        grouped[f"ghi_lag_{lag}"] = grouped["ghi"].shift(lag)

    if "temperature" in grouped.columns:
        grouped["temp_lag_1"] = grouped["temperature"].shift(1)

    grouped["ghi_rolling_mean_3h"] = grouped["ghi"].shift(1).rolling(3, min_periods=1).mean()
    grouped["ghi_rolling_std_3h"] = grouped["ghi"].shift(1).rolling(3, min_periods=2).std()
    grouped["target_ghi_next_1h"] = grouped["ghi"].shift(-1)
    grouped["target_temperature_next_1h"] = grouped["temperature"].shift(-1)

    grouped = grouped.dropna(subset=["target_ghi_next_1h", "target_temperature_next_1h"]).copy()
    return grouped.fillna(0.0)


def prepare_features(df: pd.DataFrame, target_mode: str) -> tuple[pd.DataFrame, pd.Series, pd.Series, str]:
    """Build feature matrix and selected target vector."""
    grouped = build_feature_frame(df)

    target_column = {
        "ghi": "target_ghi_next_1h",
        "temperature": "target_temperature_next_1h",
    }[target_mode]

    feature_cols = [
        col
        for col in grouped.columns
        if col
        not in {
            "timestamp",
            "target_ghi_next_1h",
            "target_temperature_next_1h",
        }
    ]
    x = grouped[feature_cols]
    y = grouped[target_column]
    ts = grouped["timestamp"]

    LOGGER.info("Prepared features for %s: rows=%d cols=%d", target_mode, x.shape[0], x.shape[1])
    return x, y, ts, target_column