day 1 giga-dump just to be safe
This commit is contained in:
@@ -0,0 +1,484 @@
|
|||||||
|
from typing import Tuple, List
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
import pandera.pandas as pa
|
||||||
|
from pandera import Column, Check
|
||||||
|
|
||||||
|
from zenml.steps import step
|
||||||
|
from zenml import pipeline
|
||||||
|
from zenml.client import Client
|
||||||
|
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
|
||||||
|
import xgboost as xgb
|
||||||
|
|
||||||
|
from sklearn.model_selection import train_test_split
|
||||||
|
from sklearn.metrics import mean_absolute_error, mean_squared_error
|
||||||
|
from sklearn.preprocessing import StandardScaler
|
||||||
|
from sklearn.model_selection import TimeSeriesSplit
|
||||||
|
from sklearn.model_selection import RandomizedSearchCV
|
||||||
|
from sklearn.ensemble import RandomForestRegressor
|
||||||
|
from sklearn.ensemble import VotingRegressor
|
||||||
|
from sklearn.linear_model import LinearRegression
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
import joblib
|
||||||
|
import json
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import math
|
||||||
|
|
||||||
|
POSTGRES_SECRET_NAME = "postgres_credentials"
|
||||||
|
|
||||||
|
@step
|
||||||
|
def load_scrape_interval_data_from_pgsql() -> pd.DataFrame:
|
||||||
|
# Fetch secret from ZenML Secret Manager
|
||||||
|
secret = Client().get_secret(POSTGRES_SECRET_NAME)
|
||||||
|
db_host = secret.secret_values["host"]
|
||||||
|
db_port = secret.secret_values["port"]
|
||||||
|
db_name = secret.secret_values["dbname"]
|
||||||
|
db_user = secret.secret_values["username"]
|
||||||
|
db_password = secret.secret_values["password"]
|
||||||
|
|
||||||
|
# Build the Postgres connection URL
|
||||||
|
postgres_url = (
|
||||||
|
f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create SQLAlchemy engine
|
||||||
|
engine = create_engine(postgres_url)
|
||||||
|
|
||||||
|
# Define the query
|
||||||
|
query = """
|
||||||
|
SELECT period_start, period_end, duration, u_diff, d_diff, t_diff, u_rate, d_rate, t_rate
|
||||||
|
FROM "SECV_SCRAPE".scrape_data_intervals
|
||||||
|
WHERE duration > interval '0 seconds'
|
||||||
|
AND u_diff >= 0 AND d_diff >= 0 AND t_diff >= 0
|
||||||
|
AND u_rate >= 0 AND d_rate >= 0 AND t_rate >= 0
|
||||||
|
and d_rate <= 125000000
|
||||||
|
ORDER BY period_start ASC;
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Execute query and load into DataFrame
|
||||||
|
with engine.connect() as connection:
|
||||||
|
df = pd.read_sql_query(query, connection)
|
||||||
|
|
||||||
|
df["duration"] = df["duration"].dt.total_seconds()
|
||||||
|
|
||||||
|
# there is already some filtering in the query to remove straight-up impossible values (i.e. greater than 1gbps)
|
||||||
|
# but we can do some additional filtering to remove outliers with some basic statistics
|
||||||
|
Q1 = df['t_rate'].quantile(0.25)
|
||||||
|
Q3 = df['t_rate'].quantile(0.75)
|
||||||
|
IQR = Q3 - Q1
|
||||||
|
upper_bound = Q3 + 1.5 * IQR
|
||||||
|
|
||||||
|
# Filter out outliers
|
||||||
|
df_filtered = df[df['t_rate'] <= upper_bound]
|
||||||
|
print(f"Rows before: {len(df)}, after filtering: {len(df_filtered)}")
|
||||||
|
|
||||||
|
plt.figure(figsize=(8, 6))
|
||||||
|
plt.boxplot(df_filtered['t_rate'], vert=False, showfliers=True)
|
||||||
|
#plt.boxplot(df['t_rate'], vert=False, showfliers=True)
|
||||||
|
plt.title('Boxplot of t_rate (bytes/sec)')
|
||||||
|
plt.xlabel('t_rate (bytes/sec)')
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig('t_rate_boxplot.png')
|
||||||
|
plt.close()
|
||||||
|
print("Boxplot saved as t_rate_boxplot.png")
|
||||||
|
|
||||||
|
return df_filtered
|
||||||
|
#return df
|
||||||
|
|
||||||
|
@step
|
||||||
|
def validate_data(df: pd.DataFrame) -> bool:
|
||||||
|
"""
|
||||||
|
Validates the input DataFrame using Pandera.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Define the schema with non-negative checks on Float64 columns
|
||||||
|
schema = pa.DataFrameSchema(
|
||||||
|
columns={
|
||||||
|
"period_start": Column(pa.DateTime, nullable=False),
|
||||||
|
"period_end": Column(pa.DateTime, nullable=False),
|
||||||
|
"duration": Column(
|
||||||
|
pa.Float64,
|
||||||
|
nullable=False,
|
||||||
|
checks=Check.greater_than_or_equal_to(0),
|
||||||
|
),
|
||||||
|
"u_diff": Column(
|
||||||
|
pa.Float64,
|
||||||
|
nullable=False,
|
||||||
|
checks=Check.greater_than_or_equal_to(0),
|
||||||
|
),
|
||||||
|
"d_diff": Column(
|
||||||
|
pa.Float64,
|
||||||
|
nullable=False,
|
||||||
|
checks=Check.greater_than_or_equal_to(0),
|
||||||
|
),
|
||||||
|
"t_diff": Column(
|
||||||
|
pa.Float64,
|
||||||
|
nullable=False,
|
||||||
|
checks=Check.greater_than_or_equal_to(0),
|
||||||
|
),
|
||||||
|
"u_rate": Column(
|
||||||
|
pa.Float64,
|
||||||
|
nullable=False,
|
||||||
|
checks=Check.greater_than_or_equal_to(0),
|
||||||
|
),
|
||||||
|
"d_rate": Column(
|
||||||
|
pa.Float64,
|
||||||
|
nullable=False,
|
||||||
|
checks=Check.greater_than_or_equal_to(0),
|
||||||
|
),
|
||||||
|
"t_rate": Column(
|
||||||
|
pa.Float64,
|
||||||
|
nullable=False,
|
||||||
|
checks=Check.greater_than_or_equal_to(0),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
strict=True, # Ensure no extra columns
|
||||||
|
coerce=True, # Coerce data types
|
||||||
|
)
|
||||||
|
|
||||||
|
# Validate the DataFrame
|
||||||
|
schema.validate(df, lazy=False) # lazy=False raises all errors at once
|
||||||
|
|
||||||
|
print("Data validation successful!")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except pa.errors.SchemaError as e:
|
||||||
|
print("Data validation failed!")
|
||||||
|
print(e)
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error during validation: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
@step
|
||||||
|
def feature_engineering(
|
||||||
|
df: pd.DataFrame,
|
||||||
|
) -> pd.DataFrame:
|
||||||
|
"""
|
||||||
|
Engineers time-based features and rolling statistics
|
||||||
|
(1-day, 7-day, 30-day) on variable-interval data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 1. Time-Based Features
|
||||||
|
df["hour"] = df["period_start"].dt.hour
|
||||||
|
df["day_of_week"] = df["period_start"].dt.dayofweek # Monday=0, Sunday=6
|
||||||
|
df["month"] = df["period_start"].dt.month
|
||||||
|
df["quarter"] = df["period_start"].dt.quarter
|
||||||
|
df["year"] = df["period_start"].dt.year
|
||||||
|
df["day_of_year"] = df["period_start"].dt.dayofyear
|
||||||
|
df["is_weekend"] = df["day_of_week"].isin([5, 6]).astype(int)
|
||||||
|
|
||||||
|
# 2. Sort by period_start to maintain time order
|
||||||
|
df = df.sort_values("period_start").reset_index(drop=True)
|
||||||
|
|
||||||
|
# 3. Rolling statistics for numeric columns (1D, 7D, 30D)
|
||||||
|
# numeric_cols = ["u_diff", "d_diff", "t_diff", "u_rate", "d_rate", "t_rate", "duration"]
|
||||||
|
# windows = ["1D", "7D", "30D"]
|
||||||
|
# for col in numeric_cols:
|
||||||
|
# for window in windows:
|
||||||
|
# df[f"{col}_rolling_mean_{window}"] = (
|
||||||
|
# df.set_index("period_start")[col]
|
||||||
|
# .rolling(window, closed="left")
|
||||||
|
# .mean()
|
||||||
|
# .reset_index(drop=True)
|
||||||
|
# )
|
||||||
|
# df[f"{col}_rolling_std_{window}"] = (
|
||||||
|
# df.set_index("period_start")[col]
|
||||||
|
# .rolling(window, closed="left")
|
||||||
|
# .std()
|
||||||
|
# .reset_index(drop=True)
|
||||||
|
# )
|
||||||
|
# df[f"{col}_rolling_min_{window}"] = (
|
||||||
|
# df.set_index("period_start")[col]
|
||||||
|
# .rolling(window, closed="left")
|
||||||
|
# .min()
|
||||||
|
# .reset_index(drop=True)
|
||||||
|
# )
|
||||||
|
# df[f"{col}_rolling_max_{window}"] = (
|
||||||
|
# df.set_index("period_start")[col]
|
||||||
|
# .rolling(window, closed="left")
|
||||||
|
# .max()
|
||||||
|
# .reset_index(drop=True)
|
||||||
|
# )
|
||||||
|
|
||||||
|
# # 4. Handle missing values in rolling features
|
||||||
|
# rolling_cols = [col for col in df.columns if "_rolling_" in col]
|
||||||
|
# df[rolling_cols] = df[rolling_cols].fillna(method="bfill").fillna(method="ffill")
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
|
@step
|
||||||
|
def model_training_and_evaluation(df: pd.DataFrame, target_col: str, test_size: float = 0.2) -> Tuple[str, str]:
|
||||||
|
"""
|
||||||
|
Trains an XGBoost model and a Random Forest model on the engineered features and evaluates their performance.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 1. Prepare the Data
|
||||||
|
# Define Features and Target
|
||||||
|
features = [col for col in df.columns if col not in [target_col, "period_start", "period_end"]]
|
||||||
|
target = target_col
|
||||||
|
|
||||||
|
with open("feature_list.json", "w") as f:
|
||||||
|
json.dump(features, f)
|
||||||
|
|
||||||
|
# Split Data into Training and Testing Sets (Time-Series-Aware)
|
||||||
|
X = df[features]
|
||||||
|
y = df[target]
|
||||||
|
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=test_size, shuffle=False)
|
||||||
|
|
||||||
|
# Scale Features
|
||||||
|
scaler = StandardScaler()
|
||||||
|
X_train = scaler.fit_transform(X_train)
|
||||||
|
X_test = scaler.transform(X_test)
|
||||||
|
|
||||||
|
# # 2. XGBoost Model Training and Evaluation
|
||||||
|
# print("Training XGBoost Model...")
|
||||||
|
# # Define your parameter search space
|
||||||
|
# param_dist_xgb = {
|
||||||
|
# 'n_estimators': [100, 200, 300, 400, 500],
|
||||||
|
# 'max_depth': [3, 5, 7, 10, 12, 15],
|
||||||
|
# 'learning_rate': [0.005, 0.01, 0.025, 0.05, 0.1, 0.2, 0.3],
|
||||||
|
# 'subsample': [0.5, 0.6, 0.7, 0.8, 0.9, 1.0],
|
||||||
|
# 'colsample_bytree': [0.5, 0.6, 0.7, 0.8, 0.9, 1.0],
|
||||||
|
# 'reg_alpha': [0, 0.01, 0.05, 0.1, 0.5, 1, 2, 5, 10],
|
||||||
|
# 'reg_lambda': [0.5, 1, 1.5, 2, 5, 10, 20],
|
||||||
|
# 'min_child_weight': [1, 2, 3, 5, 7, 10],
|
||||||
|
# 'gamma': [0, 0.01, 0.1, 0.2, 0.5, 1, 2, 5]
|
||||||
|
# }
|
||||||
|
|
||||||
|
# # Initialize the model
|
||||||
|
# xgb_model = xgb.XGBRegressor(objective='reg:squarederror', random_state=42)
|
||||||
|
|
||||||
|
# # Set up the randomized search
|
||||||
|
# search_xgb = RandomizedSearchCV(
|
||||||
|
# estimator=xgb_model,
|
||||||
|
# param_distributions=param_dist_xgb,
|
||||||
|
# n_iter=30, # Reduced n_iter for faster execution
|
||||||
|
# scoring='neg_mean_absolute_error', # Use MAE for scoring
|
||||||
|
# cv=3, # 3-fold cross-validation
|
||||||
|
# verbose=2,
|
||||||
|
# n_jobs=-1 # Use all available cores
|
||||||
|
# )
|
||||||
|
|
||||||
|
# # Fit the search to your training data
|
||||||
|
# search_xgb.fit(X_train, y_train)
|
||||||
|
|
||||||
|
# # Print the best parameters and score
|
||||||
|
# print("Best XGBoost hyperparameters:", search_xgb.best_params_)
|
||||||
|
# print("Best XGBoost MAE (CV):", -search_xgb.best_score_)
|
||||||
|
|
||||||
|
# # Use the best estimator for predictions
|
||||||
|
# best_xgb_model = search_xgb.best_estimator_
|
||||||
|
# y_pred_xgb = best_xgb_model.predict(X_test)
|
||||||
|
|
||||||
|
# # Evaluate the Model
|
||||||
|
# mae_xgb = mean_absolute_error(y_test, y_pred_xgb)
|
||||||
|
# rmse_xgb = np.sqrt(mean_squared_error(y_test, y_pred_xgb))
|
||||||
|
|
||||||
|
# print(f"XGBoost Mean Absolute Error (MAE): {mae_xgb}")
|
||||||
|
# print(f"XGBoost Root Mean Squared Error (RMSE): {rmse_xgb}")
|
||||||
|
|
||||||
|
# # Get feature importance scores from the booster
|
||||||
|
# booster = best_xgb_model.get_booster()
|
||||||
|
# importance_dict = booster.get_score(importance_type='gain') # 'weight', 'gain', or 'cover'
|
||||||
|
|
||||||
|
# # Map feature indices to feature names
|
||||||
|
# feature_names = features # List of feature names
|
||||||
|
# importance_with_names = {feature_names[int(k[1:])]: v for k, v in importance_dict.items()}
|
||||||
|
|
||||||
|
# # Sort features by importance
|
||||||
|
# sorted_importance = sorted(importance_with_names.items(), key=lambda x: x[1], reverse=True)
|
||||||
|
|
||||||
|
# print("XGBoost Feature importance (gain):")
|
||||||
|
# for feature, score in sorted_importance:
|
||||||
|
# print(f"{feature}: {score}")
|
||||||
|
|
||||||
|
# 3. Random Forest Model Training and Evaluation
|
||||||
|
print("Performing Time Series Cross-Validation for Random Forest...")
|
||||||
|
tscv = TimeSeriesSplit(n_splits=5) # You can adjust the number of splits
|
||||||
|
|
||||||
|
param_dist_rf = {
|
||||||
|
'n_estimators': [100, 200, 300, 400, 500, 600],
|
||||||
|
'max_depth': [None, 5, 10, 15, 20, 25, 30],
|
||||||
|
'min_samples_split': [2, 5, 10, 15, 20],
|
||||||
|
'min_samples_leaf': [1, 2, 4, 6, 8],
|
||||||
|
'max_features': ['sqrt', 'log2', 0.5, 0.7, 1.0], # Removed 'auto'
|
||||||
|
'bootstrap': [True, False]
|
||||||
|
}
|
||||||
|
|
||||||
|
rf_model = RandomForestRegressor(random_state=42)
|
||||||
|
|
||||||
|
search_rf = RandomizedSearchCV(
|
||||||
|
estimator=rf_model,
|
||||||
|
param_distributions=param_dist_rf,
|
||||||
|
n_iter=50, # Adjust as needed
|
||||||
|
scoring='neg_mean_absolute_error',
|
||||||
|
cv=tscv,
|
||||||
|
verbose=2,
|
||||||
|
n_jobs=-1
|
||||||
|
)
|
||||||
|
|
||||||
|
search_rf.fit(X_train, y_train)
|
||||||
|
|
||||||
|
print("Best Random Forest hyperparameters:", search_rf.best_params_)
|
||||||
|
print("Best Random Forest MAE (CV):", -search_rf.best_score_)
|
||||||
|
|
||||||
|
best_rf_model = search_rf.best_estimator_
|
||||||
|
y_pred_rf = best_rf_model.predict(X_test)
|
||||||
|
|
||||||
|
mae_rf = mean_absolute_error(y_test, y_pred_rf)
|
||||||
|
rmse_rf = np.sqrt(mean_squared_error(y_test, y_pred_rf))
|
||||||
|
|
||||||
|
print(f"Random Forest Mean Absolute Error (MAE): {mae_rf}")
|
||||||
|
print(f"Random Forest Root Mean Squared Error (RMSE): {rmse_rf}")
|
||||||
|
|
||||||
|
# Feature Importance for Random Forest
|
||||||
|
rf_feature_importance = best_rf_model.feature_importances_
|
||||||
|
rf_feature_importance_dict = {feature: importance for feature, importance in zip(features, rf_feature_importance)}
|
||||||
|
rf_sorted_importance = sorted(rf_feature_importance_dict.items(), key=lambda x: x[1], reverse=True)
|
||||||
|
|
||||||
|
print("Random Forest Feature Importance:")
|
||||||
|
for feature, importance in rf_sorted_importance:
|
||||||
|
print(f"{feature}: {importance}")
|
||||||
|
|
||||||
|
# # Prepare meta-learner training data (predictions of base models on training set)
|
||||||
|
# meta_X_train = np.column_stack((
|
||||||
|
# best_xgb_model.predict(X_train),
|
||||||
|
# best_rf_model.predict(X_train)
|
||||||
|
# ))
|
||||||
|
# meta_X_test = np.column_stack((
|
||||||
|
# best_xgb_model.predict(X_test),
|
||||||
|
# best_rf_model.predict(X_test)
|
||||||
|
# ))
|
||||||
|
|
||||||
|
# # Train meta-learner
|
||||||
|
# meta_model = LinearRegression()
|
||||||
|
# meta_model.fit(meta_X_train, y_train)
|
||||||
|
|
||||||
|
# # Predict with meta-learner
|
||||||
|
# y_pred_ensemble = meta_model.predict(meta_X_test)
|
||||||
|
|
||||||
|
# # Evaluate stacking ensemble
|
||||||
|
# mae_ensemble = mean_absolute_error(y_test, y_pred_ensemble)
|
||||||
|
# rmse_ensemble = np.sqrt(mean_squared_error(y_test, y_pred_ensemble))
|
||||||
|
|
||||||
|
# print(f"Stacking Ensemble Mean Absolute Error (MAE): {mae_ensemble}")
|
||||||
|
# print(f"Stacking Ensemble Root Mean Squared Error (RMSE): {rmse_ensemble}")
|
||||||
|
|
||||||
|
# # Optional: Save meta-model
|
||||||
|
# joblib.dump(meta_model, "stacking_meta_model.joblib")
|
||||||
|
# print("Stacking meta-model saved to disk.")
|
||||||
|
|
||||||
|
# Optional: Plot the predictions vs. actual values
|
||||||
|
plt.figure(figsize=(12, 6))
|
||||||
|
plt.plot(np.expm1(y_test).values, label="Actual")
|
||||||
|
#plt.plot(y_pred_ensemble, label="Predicted (Ensemble)")
|
||||||
|
plt.plot(y_pred_rf, label="Predicted (Random Forest)")
|
||||||
|
plt.xlabel("Time")
|
||||||
|
plt.ylabel(target_col)
|
||||||
|
plt.title("Actual vs. Predicted Bandwidth Usage")
|
||||||
|
plt.legend()
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig("actual_vs_predicted.png") # Save plot as PNG file
|
||||||
|
plt.close() # Close the figure to free memory
|
||||||
|
print("Plot saved as actual_vs_predicted.png")
|
||||||
|
|
||||||
|
# Save the model and scaler to disk
|
||||||
|
model_path = "random_forest_model.joblib"
|
||||||
|
scaler_path = "scaler.joblib"
|
||||||
|
joblib.dump(best_rf_model, model_path)
|
||||||
|
joblib.dump(scaler, scaler_path)
|
||||||
|
print("Random Forest model and scaler saved to disk.")
|
||||||
|
|
||||||
|
return model_path, scaler_path
|
||||||
|
|
||||||
|
@step
|
||||||
|
def generate_next_30_days_predictions(
|
||||||
|
model_path: str,
|
||||||
|
scaler_path: str,
|
||||||
|
feature_list: List[str]
|
||||||
|
) -> pd.DataFrame:
|
||||||
|
"""
|
||||||
|
Generates bandwidth usage predictions for each hour of the next 30 days from now using a trained Random Forest model.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Load the trained model and scaler
|
||||||
|
rf_model = joblib.load(model_path)
|
||||||
|
scaler = joblib.load(scaler_path)
|
||||||
|
|
||||||
|
# Generate hourly timestamps for the next 30 days from now
|
||||||
|
start_date = datetime.now().replace(minute=0, second=0, microsecond=0)
|
||||||
|
end_date = start_date + timedelta(days=30, hours=23) # 30 full days, last hour included
|
||||||
|
total_hours = int((end_date - start_date).total_seconds() // 3600) + 1
|
||||||
|
date_list = [start_date + timedelta(hours=x) for x in range(total_hours)]
|
||||||
|
|
||||||
|
# Create a DataFrame
|
||||||
|
future_df = pd.DataFrame({'period_start': date_list})
|
||||||
|
future_df['period_end'] = future_df['period_start'] + timedelta(hours=1)
|
||||||
|
future_df['duration'] = 3600 # Duration is 1 hour (3600 seconds)
|
||||||
|
|
||||||
|
# Feature Engineering
|
||||||
|
future_df["hour"] = future_df["period_start"].dt.hour
|
||||||
|
future_df["day_of_week"] = future_df["period_start"].dt.dayofweek # Monday=0, Sunday=6
|
||||||
|
future_df["month"] = future_df["period_start"].dt.month
|
||||||
|
future_df["quarter"] = future_df["period_start"].dt.quarter
|
||||||
|
future_df["year"] = future_df["period_start"].dt.year
|
||||||
|
future_df["day_of_year"] = future_df["period_start"].dt.dayofyear
|
||||||
|
future_df["is_weekend"] = future_df["day_of_week"].isin([5, 6]).astype(int)
|
||||||
|
|
||||||
|
# Define dummy values for the diff and rate columns
|
||||||
|
future_df["u_diff"] = 1000
|
||||||
|
future_df["d_diff"] = 1000
|
||||||
|
future_df["t_diff"] = 2000
|
||||||
|
future_df["u_rate"] = 1
|
||||||
|
future_df["d_rate"] = 1
|
||||||
|
future_df["t_rate"] = 2
|
||||||
|
|
||||||
|
# Select features in the exact order used during training
|
||||||
|
future_features = future_df[feature_list]
|
||||||
|
|
||||||
|
# Scale the features
|
||||||
|
future_scaled = scaler.transform(future_features)
|
||||||
|
|
||||||
|
# Generate predictions
|
||||||
|
predictions = rf_model.predict(future_scaled)
|
||||||
|
|
||||||
|
# Add predictions to the DataFrame
|
||||||
|
future_df['predicted_t_rate'] = predictions
|
||||||
|
|
||||||
|
print(future_df.head())
|
||||||
|
future_df.to_csv('june_predictions.csv', index=False)
|
||||||
|
|
||||||
|
return future_df
|
||||||
|
|
||||||
|
|
||||||
|
@pipeline
|
||||||
|
def secv_bandwidth_predictive_model_pipeline():
|
||||||
|
"""
|
||||||
|
A simple pipeline that queries data from Postgres and validates it.
|
||||||
|
"""
|
||||||
|
df = load_scrape_interval_data_from_pgsql()
|
||||||
|
validate_data(df)
|
||||||
|
df = feature_engineering(df)
|
||||||
|
model_path, scaler_path = model_training_and_evaluation(df=df, target_col="t_rate")
|
||||||
|
|
||||||
|
with open("feature_list.json", "r") as f:
|
||||||
|
feature_list = json.load(f)
|
||||||
|
|
||||||
|
generate_next_30_days_predictions(
|
||||||
|
model_path=model_path,
|
||||||
|
scaler_path=scaler_path,
|
||||||
|
feature_list=feature_list
|
||||||
|
)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
run = secv_bandwidth_predictive_model_pipeline()
|
||||||
Reference in New Issue
Block a user