Spaces:
Sleeping
Sleeping
Commit ·
0fa83e9
1
Parent(s): 0f952c8
Initialization
Browse files- .DS_Store +0 -0
- .gitignore +5 -0
- .gradio/flagged/dataset1.csv +2 -0
- app.py +92 -0
- dockerfile +23 -0
- models/forecast.py +81 -0
- models/regression.py +61 -0
- packages.txt +0 -0
- requirements.txt +12 -0
- utils/merge_data.py +40 -0
- utils/preprocessing.py +73 -0
- utils/simulate.py +28 -0
.DS_Store
ADDED
|
Binary file (6.15 kB). View file
|
|
|
.gitignore
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.env
|
| 2 |
+
models/__pycache__
|
| 3 |
+
utils/__pycache__
|
| 4 |
+
dataset/*
|
| 5 |
+
.python-version
|
.gradio/flagged/dataset1.csv
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
📥 Load File (MW) CSV,🌡️ Temperature File (MaxTemp) CSV,ΔTemp (°C),📊 Simulated Output (MaxTemp ➜ Load MW),timestamp
|
| 2 |
+
,,1,"{""headers"": [""1"", ""2"", ""3""], ""data"": [], ""metadata"": null}",2025-07-17 23:49:54.330002
|
app.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app.py
|
| 2 |
+
import gradio as gr
|
| 3 |
+
import pandas as pd
|
| 4 |
+
from fastapi import FastAPI
|
| 5 |
+
from gradio.routes import mount_gradio_app
|
| 6 |
+
import uvicorn
|
| 7 |
+
import os
|
| 8 |
+
import numpy as np
|
| 9 |
+
|
| 10 |
+
from utils.preprocessing import preprocess_load_data, preprocess_temperature_data
|
| 11 |
+
from utils.merge_data import merge_load_temp
|
| 12 |
+
from models.forecast import TimeSeriesForecaster
|
| 13 |
+
from models.regression import LoadLimitRegressor
|
| 14 |
+
from utils.simulate import simulate_with_model
|
| 15 |
+
|
| 16 |
+
# --- FastAPI app ---
|
| 17 |
+
app = FastAPI()
|
| 18 |
+
|
| 19 |
+
# --- Globals ---
|
| 20 |
+
df_merged = None # เก็บข้อมูล merge เอาไว้
|
| 21 |
+
forecast_model = None
|
| 22 |
+
reg_model = None
|
| 23 |
+
|
| 24 |
+
# --- Model option lists ---
|
| 25 |
+
forecast_models = ['PatchTST', 'LSTM', 'BiLSTM', 'GRU', 'RNN', 'ARIMA', 'Prophet']
|
| 26 |
+
regression_models = ['linear', 'xgb', 'mlp']
|
| 27 |
+
|
| 28 |
+
def forecast_ui(load_file, temp_file,
|
| 29 |
+
forecast_model_type, regression_model_type,
|
| 30 |
+
forecast_horizon, delta_temp):
|
| 31 |
+
global df_merged, forecast_model, reg_model
|
| 32 |
+
|
| 33 |
+
# 1. อ่านไฟล์
|
| 34 |
+
df_load_raw = pd.read_csv(load_file.name)
|
| 35 |
+
df_temp_raw = pd.read_csv(temp_file.name)
|
| 36 |
+
|
| 37 |
+
# 2. preprocess
|
| 38 |
+
df_load = preprocess_load_data(df_load_raw)
|
| 39 |
+
df_temp = preprocess_temperature_data(df_temp_raw)
|
| 40 |
+
|
| 41 |
+
# 3. merge
|
| 42 |
+
df_merged = merge_load_temp(df_load, df_temp)
|
| 43 |
+
|
| 44 |
+
# 4. สร้างและ train forecasting model (forecast max load)
|
| 45 |
+
forecast_model = TimeSeriesForecaster(model_type=forecast_model_type, horizon=forecast_horizon)
|
| 46 |
+
forecast_model.fit(df_merged, value_col='mw_max')
|
| 47 |
+
forecast_pred = forecast_model.predict(future_steps=forecast_horizon)
|
| 48 |
+
|
| 49 |
+
# 5. train regression model (max temp -> max load limit)
|
| 50 |
+
reg_model = LoadLimitRegressor(model_type=regression_model_type)
|
| 51 |
+
reg_model.fit(df_merged, temp_col='MaxTemp', load_col='mw_max')
|
| 52 |
+
|
| 53 |
+
# 6. simulate load limit with delta_temp
|
| 54 |
+
df_simulated = simulate_with_model(df_merged, delta_temp=delta_temp, model=reg_model)
|
| 55 |
+
|
| 56 |
+
# 7. เตรียมผลลัพธ์สำหรับแสดง
|
| 57 |
+
# forecast results dataframe
|
| 58 |
+
last_date = df_merged['date'].max() if 'date' in df_merged.columns else pd.to_datetime('today')
|
| 59 |
+
forecast_df = pd.DataFrame({
|
| 60 |
+
'Step': np.arange(1, forecast_horizon + 1),
|
| 61 |
+
'Forecast Load MW': forecast_pred
|
| 62 |
+
})
|
| 63 |
+
|
| 64 |
+
# simulation dataframe (limit model)
|
| 65 |
+
# แสดงแค่ columns สำคัญ
|
| 66 |
+
sim_df_show = df_simulated[['MaxTemp', 'mw_max', 'MaxTemp_simulated', 'Load_simulated']]
|
| 67 |
+
|
| 68 |
+
return forecast_df, sim_df_show
|
| 69 |
+
|
| 70 |
+
# --- Gradio UI ---
|
| 71 |
+
demo = gr.Interface(
|
| 72 |
+
fn=forecast_ui,
|
| 73 |
+
inputs=[
|
| 74 |
+
gr.File(label="📥 Load File (MW) CSV"),
|
| 75 |
+
gr.File(label="🌡️ Temperature File (MaxTemp) CSV"),
|
| 76 |
+
gr.Dropdown(choices=forecast_models, label="Forecast Model", value='PatchTST'),
|
| 77 |
+
gr.Dropdown(choices=regression_models, label="Regression Model", value='xgb'),
|
| 78 |
+
gr.Slider(minimum=1, maximum=30, step=1, label="Forecast Horizon (days)", value=7),
|
| 79 |
+
gr.Slider(minimum=-5.0, maximum=5.0, step=0.5, label="ΔTemp (°C)", value=1.0)
|
| 80 |
+
],
|
| 81 |
+
outputs=[
|
| 82 |
+
gr.Dataframe(label="📈 Forecast Load MW"),
|
| 83 |
+
gr.Dataframe(label="📊 Simulated Load Limit")
|
| 84 |
+
],
|
| 85 |
+
title="Submarine Load Forecast & Safety Margin Simulation",
|
| 86 |
+
description="Upload load and temperature CSV data, choose forecasting and regression models, set forecast horizon, and simulate temperature impact on load limit."
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
demo.launch(
|
| 90 |
+
server_name=os.getenv("SERVER_NAME", "0.0.0.0"),
|
| 91 |
+
server_port=int(os.getenv("PORT", 7860)),
|
| 92 |
+
)
|
dockerfile
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dockerfile
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
WORKDIR /app
|
| 5 |
+
|
| 6 |
+
# 1. Install system dependencies
|
| 7 |
+
RUN apt-get update && apt-get install -y \
|
| 8 |
+
git build-essential libglib2.0-0 libsm6 libxext6 libxrender-dev \
|
| 9 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
+
|
| 11 |
+
# 2. Install Python dependencies
|
| 12 |
+
COPY requirements.txt .
|
| 13 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 14 |
+
|
| 15 |
+
# 3. Copy all source code
|
| 16 |
+
COPY . .
|
| 17 |
+
|
| 18 |
+
# 4. Expose port
|
| 19 |
+
ENV PORT=7860
|
| 20 |
+
EXPOSE $PORT
|
| 21 |
+
|
| 22 |
+
# 5. Run app
|
| 23 |
+
CMD ["python", "app.py"]
|
models/forecast.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# models/forecasting.py
|
| 2 |
+
|
| 3 |
+
import pandas as pd
|
| 4 |
+
import numpy as np
|
| 5 |
+
from tsai.all import *
|
| 6 |
+
from typing import Literal, Tuple
|
| 7 |
+
from sklearn.preprocessing import MinMaxScaler
|
| 8 |
+
|
| 9 |
+
ForecastModelType = Literal['PatchTST', 'LSTM', 'BiLSTM', 'GRU', 'RNN', 'ARIMA', 'Prophet']
|
| 10 |
+
|
| 11 |
+
class TimeSeriesForecaster:
|
| 12 |
+
def __init__(self, model_type: ForecastModelType = 'PatchTST', horizon: int = 7):
|
| 13 |
+
self.model_type = model_type
|
| 14 |
+
self.horizon = horizon
|
| 15 |
+
self.scaler = MinMaxScaler()
|
| 16 |
+
self.model = None
|
| 17 |
+
self.dls = None
|
| 18 |
+
self.X_cols = None
|
| 19 |
+
|
| 20 |
+
def preprocess(self, df: pd.DataFrame, value_col: str) -> Tuple[np.ndarray, np.ndarray]:
|
| 21 |
+
"""
|
| 22 |
+
รับ dataframe และคอลัมน์เป้าหมาย แล้วแปลงให้เป็น (X, y) สำหรับ tsai
|
| 23 |
+
"""
|
| 24 |
+
df = df.copy()
|
| 25 |
+
df[value_col] = self.scaler.fit_transform(df[[value_col]])
|
| 26 |
+
ts = df[value_col].values.astype(np.float32)
|
| 27 |
+
X, y = SlidingWindow(window_len=self.horizon, horizon=self.horizon)(ts)
|
| 28 |
+
return X, y
|
| 29 |
+
|
| 30 |
+
def fit(self, df: pd.DataFrame, value_col: str):
|
| 31 |
+
"""
|
| 32 |
+
เทรนโมเดลตาม model_type ที่เลือก
|
| 33 |
+
"""
|
| 34 |
+
if self.model_type in ['PatchTST', 'LSTM', 'BiLSTM', 'GRU', 'RNN']:
|
| 35 |
+
X, y = self.preprocess(df, value_col)
|
| 36 |
+
self.dls = get_ts_dls(X, y, bs=32, splits=TSStandardSplit()(X))
|
| 37 |
+
if self.model_type == 'PatchTST':
|
| 38 |
+
self.model = PatchTST(self.dls.vars, self.dls.len, self.dls.c)
|
| 39 |
+
elif self.model_type == 'LSTM':
|
| 40 |
+
self.model = LSTM(self.dls.vars, self.dls.len, self.dls.c)
|
| 41 |
+
elif self.model_type == 'BiLSTM':
|
| 42 |
+
self.model = LSTM(self.dls.vars, self.dls.len, self.dls.c, bidirectional=True)
|
| 43 |
+
elif self.model_type == 'GRU':
|
| 44 |
+
self.model = GRU(self.dls.vars, self.dls.len, self.dls.c)
|
| 45 |
+
elif self.model_type == 'RNN':
|
| 46 |
+
self.model = RNN(self.dls.vars, self.dls.len, self.dls.c)
|
| 47 |
+
learn = Learner(self.dls, self.model, loss_func=MSELossFlat(), metrics=[mae, rmse])
|
| 48 |
+
learn.fit_one_cycle(20, 1e-3)
|
| 49 |
+
self.learn = learn
|
| 50 |
+
elif self.model_type == 'ARIMA':
|
| 51 |
+
from statsmodels.tsa.arima.model import ARIMA
|
| 52 |
+
self.model = ARIMA(df[value_col], order=(7, 1, 0)).fit()
|
| 53 |
+
elif self.model_type == 'Prophet':
|
| 54 |
+
from prophet import Prophet
|
| 55 |
+
prophet_df = df[['datetime', value_col]].rename(columns={'datetime': 'ds', value_col: 'y'})
|
| 56 |
+
self.model = Prophet()
|
| 57 |
+
self.model.fit(prophet_df)
|
| 58 |
+
else:
|
| 59 |
+
raise ValueError(f"Unsupported model type: {self.model_type}")
|
| 60 |
+
|
| 61 |
+
def predict(self, future_steps: int = None) -> np.ndarray:
|
| 62 |
+
"""
|
| 63 |
+
พยากรณ์ค่าข้างหน้า future_steps (หรือ horizon)
|
| 64 |
+
"""
|
| 65 |
+
if future_steps is None:
|
| 66 |
+
future_steps = self.horizon
|
| 67 |
+
|
| 68 |
+
if self.model_type in ['PatchTST', 'LSTM', 'BiLSTM', 'GRU', 'RNN']:
|
| 69 |
+
x_last = self.dls.items[-1][None, :]
|
| 70 |
+
pred = self.learn.model(x_last).detach().cpu().numpy().flatten()
|
| 71 |
+
pred = self.scaler.inverse_transform(pred.reshape(-1, 1)).flatten()
|
| 72 |
+
return pred[:future_steps]
|
| 73 |
+
elif self.model_type == 'ARIMA':
|
| 74 |
+
forecast = self.model.forecast(steps=future_steps)
|
| 75 |
+
return forecast.values if hasattr(forecast, 'values') else forecast
|
| 76 |
+
elif self.model_type == 'Prophet':
|
| 77 |
+
future = self.model.make_future_dataframe(periods=future_steps)
|
| 78 |
+
forecast = self.model.predict(future)
|
| 79 |
+
return forecast['yhat'][-future_steps:].values
|
| 80 |
+
else:
|
| 81 |
+
raise ValueError("Model not trained or unsupported type")
|
models/regression.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# models/regression.py
|
| 2 |
+
|
| 3 |
+
import pandas as pd
|
| 4 |
+
import numpy as np
|
| 5 |
+
from typing import Literal
|
| 6 |
+
from sklearn.linear_model import LinearRegression
|
| 7 |
+
from sklearn.ensemble import GradientBoostingRegressor
|
| 8 |
+
from sklearn.neural_network import MLPRegressor
|
| 9 |
+
from sklearn.metrics import mean_squared_error, r2_score
|
| 10 |
+
from sklearn.preprocessing import MinMaxScaler
|
| 11 |
+
|
| 12 |
+
RegressionModelType = Literal['linear', 'xgb', 'mlp']
|
| 13 |
+
|
| 14 |
+
class LoadLimitRegressor:
|
| 15 |
+
def __init__(self, model_type: RegressionModelType = 'xgb'):
|
| 16 |
+
self.model_type = model_type
|
| 17 |
+
self.scaler_X = MinMaxScaler()
|
| 18 |
+
self.scaler_y = MinMaxScaler()
|
| 19 |
+
self.model = None
|
| 20 |
+
|
| 21 |
+
def fit(self, df: pd.DataFrame, temp_col: str = 'MaxTemp', load_col: str = 'mw_max'):
|
| 22 |
+
"""
|
| 23 |
+
เทรน regression model เพื่อหา relationship ระหว่างอุณหภูมิ ➜ โหลดสูงสุด
|
| 24 |
+
"""
|
| 25 |
+
X = df[[temp_col]].values
|
| 26 |
+
y = df[[load_col]].values
|
| 27 |
+
|
| 28 |
+
X_scaled = self.scaler_X.fit_transform(X)
|
| 29 |
+
y_scaled = self.scaler_y.fit_transform(y)
|
| 30 |
+
|
| 31 |
+
if self.model_type == 'linear':
|
| 32 |
+
self.model = LinearRegression()
|
| 33 |
+
elif self.model_type == 'xgb':
|
| 34 |
+
self.model = GradientBoostingRegressor(n_estimators=100, learning_rate=0.1, max_depth=3)
|
| 35 |
+
elif self.model_type == 'mlp':
|
| 36 |
+
self.model = MLPRegressor(hidden_layer_sizes=(64, 32), max_iter=1000)
|
| 37 |
+
else:
|
| 38 |
+
raise ValueError(f"Unsupported model type: {self.model_type}")
|
| 39 |
+
|
| 40 |
+
self.model.fit(X_scaled, y_scaled.ravel())
|
| 41 |
+
|
| 42 |
+
def predict(self, max_temp_values: np.ndarray) -> np.ndarray:
|
| 43 |
+
"""
|
| 44 |
+
พยากรณ์โหลดสูงสุดที่เป็นไปได้ จากค่าความร้อน
|
| 45 |
+
"""
|
| 46 |
+
X_scaled = self.scaler_X.transform(max_temp_values.reshape(-1, 1))
|
| 47 |
+
y_scaled = self.model.predict(X_scaled).reshape(-1, 1)
|
| 48 |
+
y = self.scaler_y.inverse_transform(y_scaled)
|
| 49 |
+
return y.flatten()
|
| 50 |
+
|
| 51 |
+
def evaluate(self, df: pd.DataFrame, temp_col: str = 'MaxTemp', load_col: str = 'mw_max') -> dict:
|
| 52 |
+
"""
|
| 53 |
+
ประเมินผลโมเดล regression
|
| 54 |
+
"""
|
| 55 |
+
y_true = df[load_col].values
|
| 56 |
+
y_pred = self.predict(df[temp_col].values)
|
| 57 |
+
|
| 58 |
+
return {
|
| 59 |
+
'rmse': np.sqrt(mean_squared_error(y_true, y_pred)),
|
| 60 |
+
'r2': r2_score(y_true, y_pred)
|
| 61 |
+
}
|
packages.txt
ADDED
|
File without changes
|
requirements.txt
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi
|
| 2 |
+
gradio
|
| 3 |
+
uvicorn
|
| 4 |
+
pandas
|
| 5 |
+
scikit-learn
|
| 6 |
+
xgboost
|
| 7 |
+
numpy
|
| 8 |
+
python-dotenv
|
| 9 |
+
tsai==0.4.0
|
| 10 |
+
fastai==2.7.19
|
| 11 |
+
fastcore==1.7.29
|
| 12 |
+
ipykernel
|
utils/merge_data.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# utils/merge_data.py
|
| 2 |
+
import pandas as pd
|
| 3 |
+
|
| 4 |
+
def aggregate_daily_max(df_load_15min: pd.DataFrame) -> pd.DataFrame:
|
| 5 |
+
"""
|
| 6 |
+
จากข้อมูลที่ resample มาแล้ว (ทุก 15 นาที) รวมให้เหลือแค่ daily max load
|
| 7 |
+
"""
|
| 8 |
+
df = df_load_15min.copy()
|
| 9 |
+
df['date'] = df['datetime'].dt.date # เป็น datetime.date ไม่เอาเวลา
|
| 10 |
+
df_daily = df.groupby('date')['mw'].agg(['max', 'mean']).reset_index()
|
| 11 |
+
df_daily = df_daily.rename(columns={'max': 'mw_max', 'mean': 'mw_mean'})
|
| 12 |
+
return df_daily
|
| 13 |
+
|
| 14 |
+
def merge_load_temp(df_daily_load: pd.DataFrame, df_temp: pd.DataFrame) -> pd.DataFrame:
|
| 15 |
+
"""
|
| 16 |
+
รวมข้อมูล Load (daily) กับ Temperature (daily) โดยใช้ date เป็น key
|
| 17 |
+
df_temp ต้องมีคอลัมน์ 'date' ที่เป็น datetime64[ns] หรือ datetime.date
|
| 18 |
+
"""
|
| 19 |
+
df_temp = df_temp.copy()
|
| 20 |
+
# ถ้าไม่มี 'date' คอลัมน์ ให้สร้างจาก year,month,day
|
| 21 |
+
if 'date' not in df_temp.columns:
|
| 22 |
+
df_temp['date'] = pd.to_datetime(df_temp[['year', 'month', 'day']]).dt.date
|
| 23 |
+
|
| 24 |
+
# มึงอย่าไปแปลงเป็น datetime แล้วเอาเวลาออกอีกที ให้มันมี datatype เดียวกันระหว่าง 2 df นี่แหละดีที่สุด
|
| 25 |
+
|
| 26 |
+
df_merged = pd.merge(df_daily_load, df_temp[['date', 'MaxTemp']], on='date', how='inner')
|
| 27 |
+
return df_merged
|
| 28 |
+
|
| 29 |
+
# ตัวอย่างใช้
|
| 30 |
+
if __name__ == '__main__':
|
| 31 |
+
load_fp = 'load_15min.csv'
|
| 32 |
+
temp_fp = 'temp.csv'
|
| 33 |
+
|
| 34 |
+
df_load_15min = pd.read_csv(load_fp, parse_dates=['datetime'])
|
| 35 |
+
df_temp = pd.read_csv(temp_fp)
|
| 36 |
+
|
| 37 |
+
df_daily = aggregate_daily_max(df_load_15min)
|
| 38 |
+
df_merged = merge_load_temp(df_daily, df_temp)
|
| 39 |
+
|
| 40 |
+
print(df_merged.head())
|
utils/preprocessing.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# utils/preprocessing.py
|
| 2 |
+
import pandas as pd
|
| 3 |
+
|
| 4 |
+
def preprocess_load_data(df_load: pd.DataFrame) -> pd.DataFrame:
|
| 5 |
+
"""
|
| 6 |
+
รับ DataFrame load ดิบ แปลง datetime, sort, resample 15 นาที เติม missing
|
| 7 |
+
"""
|
| 8 |
+
df_load = df_load.copy()
|
| 9 |
+
|
| 10 |
+
# ลบ space รอบคอลัมน์เผื่อมีมั่ว
|
| 11 |
+
df_load.columns = df_load.columns.str.strip()
|
| 12 |
+
|
| 13 |
+
# รวม date + time เป็น datetime พร้อม dayfirst=True (วัน/เดือน/ปี)
|
| 14 |
+
df_load['datetime'] = pd.to_datetime(df_load['date'].astype(str) + ' ' + df_load['time'].astype(str), dayfirst=True)
|
| 15 |
+
|
| 16 |
+
# Drop คอลัมน์เดิม
|
| 17 |
+
df_load = df_load.drop(columns=['date', 'time'])
|
| 18 |
+
|
| 19 |
+
# Sort และตั้ง index เป็น datetime
|
| 20 |
+
df_load = df_load.sort_values('datetime').set_index('datetime')
|
| 21 |
+
|
| 22 |
+
# สร้าง full range datetime ทุก 15 นาที ตั้งแต่วันแรกถึงวันสุดท้าย
|
| 23 |
+
start = df_load.index.min().normalize()
|
| 24 |
+
end = df_load.index.max().normalize() + pd.Timedelta(days=1) - pd.Timedelta(minutes=15)
|
| 25 |
+
full_idx = pd.date_range(start=start, end=end, freq='15T')
|
| 26 |
+
|
| 27 |
+
# Reindex เติมช่วงเวลาทั้งหมด
|
| 28 |
+
df_load = df_load.reindex(full_idx)
|
| 29 |
+
|
| 30 |
+
# เติม missing ด้วย linear interpolation แล้ว forward fill / back fill อีกที
|
| 31 |
+
df_load['mw'] = df_load['mw'].interpolate(method='time').fillna(method='ffill').fillna(method='bfill')
|
| 32 |
+
|
| 33 |
+
# Reset index กลับเป็นคอลัมน์ datetime
|
| 34 |
+
df_load = df_load.reset_index().rename(columns={'index': 'datetime'})
|
| 35 |
+
|
| 36 |
+
return df_load
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def preprocess_temperature_data(df_temp: pd.DataFrame) -> pd.DataFrame:
|
| 40 |
+
"""
|
| 41 |
+
รับ DataFrame temp ดิบที่มี year, month, day, MaxTemp
|
| 42 |
+
รวม 3 คอลัมน์ปีเดือนวันเป็น datetime แล้ว sort
|
| 43 |
+
"""
|
| 44 |
+
df_temp = df_temp.copy()
|
| 45 |
+
|
| 46 |
+
# ลบ space รอบคอลัมน์เผื่อมั่ว
|
| 47 |
+
df_temp.columns = df_temp.columns.str.strip()
|
| 48 |
+
|
| 49 |
+
# แปลงเป็น datetime
|
| 50 |
+
df_temp['date'] = pd.to_datetime(df_temp[['year', 'month', 'day']])
|
| 51 |
+
|
| 52 |
+
# Sort ตามวันที่
|
| 53 |
+
df_temp = df_temp.sort_values('date').reset_index(drop=True)
|
| 54 |
+
|
| 55 |
+
return df_temp
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
if __name__ == '__main__':
|
| 59 |
+
# ทดสอบง่าย ๆ
|
| 60 |
+
df_load = pd.DataFrame({
|
| 61 |
+
'date': ['2024-03-01', '2024-03-01', '2024-03-01', '2024-03-02'],
|
| 62 |
+
'time': ['00:00', '00:15', '01:00', '00:00'],
|
| 63 |
+
'mw': [10, 12, 15, 11]
|
| 64 |
+
})
|
| 65 |
+
print(preprocess_load_data(df_load).head(10))
|
| 66 |
+
|
| 67 |
+
df_temp = pd.DataFrame({
|
| 68 |
+
'year': [2024, 2024, 2024],
|
| 69 |
+
'month': [3, 3, 3],
|
| 70 |
+
'day': [1, 2, 3],
|
| 71 |
+
'MaxTemp': [19.0, 18.5, 19.5]
|
| 72 |
+
})
|
| 73 |
+
print(preprocess_temperature_data(df_temp))
|
utils/simulate.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# utils/simulate.py
|
| 2 |
+
|
| 3 |
+
import pandas as pd
|
| 4 |
+
import numpy as np
|
| 5 |
+
from models.regression import LoadLimitRegressor
|
| 6 |
+
|
| 7 |
+
def simulate_temperature_change(df: pd.DataFrame, delta_temp: float) -> pd.DataFrame:
|
| 8 |
+
"""
|
| 9 |
+
จำลองสถานการณ์เพิ่ม/ลด MaxTemp ทีละ delta_temp
|
| 10 |
+
แบบ simple linear approximation
|
| 11 |
+
"""
|
| 12 |
+
df_sim = df.copy()
|
| 13 |
+
df_sim['MaxTemp_simulated'] = df_sim['MaxTemp'] + delta_temp
|
| 14 |
+
|
| 15 |
+
a, b = np.polyfit(df_sim['MaxTemp'], df_sim['mw_max'], 1)
|
| 16 |
+
df_sim['Load_simulated'] = a * df_sim['MaxTemp_simulated'] + b
|
| 17 |
+
|
| 18 |
+
return df_sim
|
| 19 |
+
|
| 20 |
+
def simulate_with_model(df: pd.DataFrame, delta_temp: float, model: LoadLimitRegressor) -> pd.DataFrame:
|
| 21 |
+
"""
|
| 22 |
+
จำลองสถานการณ์ โดยใช้ regression model ที่ฝึกมาแล้ว
|
| 23 |
+
"""
|
| 24 |
+
df_sim = df.copy()
|
| 25 |
+
df_sim['MaxTemp_simulated'] = df_sim['MaxTemp'] + delta_temp
|
| 26 |
+
df_sim['Load_simulated'] = model.predict(df_sim['MaxTemp_simulated'].values)
|
| 27 |
+
|
| 28 |
+
return df_sim
|