Spaces:
Sleeping
Sleeping
| import pandas as pd | |
| import numpy as np | |
| from pathlib import Path | |
| import warnings | |
| try: | |
| from prophet import Prophet | |
| PROPHET_AVAILABLE = True | |
| except Exception: | |
| PROPHET_AVAILABLE = False | |
| try: | |
| import tensorflow as tf | |
| from tensorflow.keras.models import Sequential | |
| from tensorflow.keras.layers import LSTM, Bidirectional, GRU, Dense, Dropout | |
| from sklearn.preprocessing import MinMaxScaler | |
| TF_AVAILABLE = True | |
| except Exception: | |
| TF_AVAILABLE = False | |
| def prepare_multivariate_timeseries(df: pd.DataFrame, date_col: str = 'OutageDateTime') -> pd.DataFrame: | |
| """Prepare multivariate time series with multiple features""" | |
| df = df.copy() | |
| df['dt'] = pd.to_datetime(df[date_col], format='%d-%m-%Y %H:%M:%S', errors='coerce') | |
| df = df.dropna(subset=['dt']) | |
| df['day'] = df['dt'].dt.floor('D') | |
| # Aggregate daily data | |
| daily_data = df.groupby('day').agg({ | |
| 'EventNumber': 'count', # daily count | |
| 'Load(MW)': lambda x: pd.to_numeric(x, errors='coerce').mean(), | |
| 'Capacity(kVA)': lambda x: pd.to_numeric(x, errors='coerce').mean(), | |
| 'AffectedCustomer': lambda x: pd.to_numeric(x, errors='coerce').sum(), | |
| 'OpDeviceType': lambda x: x.mode().iloc[0] if len(x) > 0 else 'Unknown', | |
| 'Owner': lambda x: x.mode().iloc[0] if len(x) > 0 else 'Unknown', | |
| 'Weather': lambda x: x.mode().iloc[0] if len(x) > 0 else 'Unknown', | |
| 'EventType': lambda x: x.mode().iloc[0] if len(x) > 0 else 'Unknown' | |
| }).reset_index() | |
| # Rename columns | |
| daily_data = daily_data.rename(columns={ | |
| 'day': 'ds', | |
| 'EventNumber': 'daily_count', | |
| 'Load(MW)': 'avg_load_mw', | |
| 'Capacity(kVA)': 'avg_capacity_kva', | |
| 'AffectedCustomer': 'total_affected_customers' | |
| }) | |
| # Calculate duration if available | |
| if 'LastRestoDateTime' in df.columns: | |
| df['last_dt'] = pd.to_datetime(df.get('LastRestoDateTime'), format='%d-%m-%Y %H:%M:%S', errors='coerce') | |
| df['duration_min'] = (df['last_dt'] - df['dt']).dt.total_seconds() / 60.0 | |
| duration_agg = df.groupby('day')['duration_min'].sum().reset_index() | |
| duration_agg = duration_agg.rename(columns={'day': 'ds', 'duration_min': 'total_downtime_min'}) | |
| daily_data = daily_data.merge(duration_agg, on='ds', how='left') | |
| daily_data['total_downtime_min'] = daily_data['total_downtime_min'].fillna(0) | |
| else: | |
| daily_data['total_downtime_min'] = 0 | |
| # Add time features | |
| daily_data['ds'] = pd.to_datetime(daily_data['ds']) | |
| daily_data['day_of_week'] = daily_data['ds'].dt.dayofweek | |
| daily_data['month'] = daily_data['ds'].dt.month | |
| daily_data['is_weekend'] = daily_data['day_of_week'].isin([5, 6]).astype(int) | |
| # Fill missing numeric values | |
| numeric_cols = ['avg_load_mw', 'avg_capacity_kva', 'total_affected_customers', 'total_downtime_min'] | |
| for col in numeric_cols: | |
| daily_data[col] = pd.to_numeric(daily_data[col], errors='coerce').fillna(daily_data[col].mean()) | |
| # Encode categorical variables | |
| categorical_cols = ['OpDeviceType', 'Owner', 'Weather', 'EventType'] | |
| for col in categorical_cols: | |
| daily_data[col] = daily_data[col].fillna('Unknown') | |
| # Simple frequency encoding | |
| freq_map = daily_data[col].value_counts().to_dict() | |
| daily_data[f'{col}_freq'] = daily_data[col].map(freq_map) | |
| return daily_data | |
| def prepare_multivariate_timeseries(df: pd.DataFrame, date_col: str = 'OutageDateTime', target_metric: str = 'count') -> pd.DataFrame: | |
| """ | |
| Prepare multivariate time series data with multiple features per day. | |
| Args: | |
| df: Input dataframe | |
| date_col: Date column name | |
| target_metric: Target metric ('count' or 'downtime_minutes') | |
| Returns: | |
| DataFrame with daily aggregated features | |
| """ | |
| df = df.copy() | |
| # Convert data types properly | |
| df['dt'] = pd.to_datetime(df[date_col], format='%d-%m-%Y %H:%M:%S', errors='coerce') | |
| df = df.dropna(subset=['dt']) | |
| df['day'] = df['dt'].dt.floor('D') | |
| # Convert numeric columns | |
| numeric_cols = ['Load(MW)', 'Capacity(kVA)', 'AffectedCustomer', 'FirstStepDuration', 'LastStepDuration'] | |
| for col in numeric_cols: | |
| if col in df.columns: | |
| df[col] = pd.to_numeric(df[col], errors='coerce') | |
| # Target variable | |
| if target_metric == 'count': | |
| daily_data = df.groupby('day').size().rename('daily_count').reset_index() | |
| elif target_metric == 'downtime_minutes': | |
| df['last_dt'] = pd.to_datetime(df.get('LastRestoDateTime'), format='%d-%m-%Y %H:%M:%S', errors='coerce') | |
| df['duration_min'] = (df['last_dt'] - df['dt']).dt.total_seconds() / 60.0 | |
| daily_data = df.groupby('day')['duration_min'].sum().rename('total_downtime_min').reset_index() | |
| else: | |
| raise ValueError('Unsupported target_metric') | |
| # Additional features - aggregate per day | |
| # Numeric features | |
| numeric_agg = df.groupby('day').agg({ | |
| 'Load(MW)': 'mean', | |
| 'Capacity(kVA)': 'mean', | |
| 'AffectedCustomer': 'sum', | |
| 'FirstStepDuration': 'mean', | |
| 'LastStepDuration': 'mean' | |
| }).reset_index() | |
| # Time features | |
| time_features = df.groupby('day').agg({ | |
| 'dt': ['count', lambda x: x.dt.hour.mean(), lambda x: x.dt.weekday.mean()] | |
| }).reset_index() | |
| time_features.columns = ['day', 'event_count', 'avg_hour', 'avg_weekday'] | |
| # Categorical features - take most common per day | |
| categorical_features = df.groupby('day').agg({ | |
| 'OpDeviceType': lambda x: x.mode().iloc[0] if len(x.mode()) > 0 else 'Unknown', | |
| 'Owner': lambda x: x.mode().iloc[0] if len(x.mode()) > 0 else 'Unknown', | |
| 'Weather': lambda x: x.mode().iloc[0] if len(x.mode()) > 0 else 'Unknown', | |
| 'EventType': lambda x: x.mode().iloc[0] if len(x.mode()) > 0 else 'Unknown' | |
| }).reset_index() | |
| # Merge all features | |
| daily_data = daily_data.merge(numeric_agg, on='day', how='left') | |
| daily_data = daily_data.merge(time_features, on='day', how='left') | |
| daily_data = daily_data.merge(categorical_features, on='day', how='left') | |
| # Fill missing values | |
| daily_data = daily_data.fillna({ | |
| 'Load(MW)': daily_data['Load(MW)'].mean(), | |
| 'Capacity(kVA)': daily_data['Capacity(kVA)'].mean(), | |
| 'AffectedCustomer': 0, | |
| 'FirstStepDuration': daily_data['FirstStepDuration'].mean(), | |
| 'LastStepDuration': daily_data['LastStepDuration'].mean(), | |
| 'avg_hour': 12, | |
| 'avg_weekday': 3 | |
| }) | |
| # Rename day column to ds for consistency | |
| daily_data = daily_data.rename(columns={'day': 'ds'}) | |
| return daily_data | |
| def prepare_timeseries(df: pd.DataFrame, date_col: str = 'OutageDateTime', metric: str = 'count') -> pd.DataFrame: | |
| """Prepare univariate time series data (original function for backward compatibility)""" | |
| # date_col is in format DD-MM-YYYY HH:MM:SS | |
| df = df.copy() | |
| df['dt'] = pd.to_datetime(df[date_col], format='%d-%m-%Y %H:%M:%S', errors='coerce') | |
| df = df.dropna(subset=['dt']) | |
| df['day'] = df['dt'].dt.floor('D') | |
| if metric == 'count': | |
| ts = df.groupby('day').size().rename('y').reset_index() | |
| elif metric == 'downtime_minutes': | |
| # need LastRestoDateTime | |
| df['last_dt'] = pd.to_datetime(df.get('LastRestoDateTime'), format='%d-%m-%Y %H:%M:%S', errors='coerce') | |
| df['duration_min'] = (df['last_dt'] - df['dt']).dt.total_seconds() / 60.0 | |
| ts = df.groupby('day')['duration_min'].sum().rename('y').reset_index() | |
| else: | |
| raise ValueError('Unsupported metric') | |
| ts = ts.rename(columns={'day':'ds'}) | |
| return ts | |
| def forecast_prophet(ts: pd.DataFrame, periods: int = 7, freq: str = 'D', hyperparams: dict = None) -> pd.DataFrame: | |
| if not PROPHET_AVAILABLE: | |
| raise RuntimeError('Prophet not available') | |
| # Set default hyperparameters | |
| if hyperparams is None: | |
| hyperparams = {} | |
| changepoint_prior_scale = hyperparams.get('changepoint_prior_scale', 0.05) | |
| seasonality_prior_scale = hyperparams.get('seasonality_prior_scale', 10.0) | |
| seasonality_mode = hyperparams.get('seasonality_mode', 'additive') | |
| m = Prophet( | |
| changepoint_prior_scale=changepoint_prior_scale, | |
| seasonality_prior_scale=seasonality_prior_scale, | |
| seasonality_mode=seasonality_mode | |
| ) | |
| m.fit(ts) | |
| future = m.make_future_dataframe(periods=periods, freq=freq) | |
| fcst = m.predict(future) | |
| return fcst[['ds','yhat','yhat_lower','yhat_upper']] | |
| def forecast_naive(ts: pd.DataFrame, periods: int = 7) -> pd.DataFrame: | |
| # naive: use rolling mean of last 7 days as forecast | |
| last_mean = ts['y'].tail(7).mean() if len(ts) >= 7 else ts['y'].mean() | |
| last_date = ts['ds'].max() | |
| future_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=periods, freq='D') | |
| return pd.DataFrame({'ds': future_dates, 'yhat': [last_mean]*periods, 'yhat_lower':[np.nan]*periods, 'yhat_upper':[np.nan]*periods}) | |
| def create_sequences(data, seq_length): | |
| """Create sequences for time series forecasting""" | |
| X, y = [], [] | |
| for i in range(len(data) - seq_length): | |
| X.append(data[i:(i + seq_length)]) | |
| y.append(data[i + seq_length]) | |
| return np.array(X), np.array(y) | |
| def forecast_lstm(ts: pd.DataFrame, periods: int = 7, seq_length: int = 7, hyperparams: dict = None) -> pd.DataFrame: | |
| """Forecast using LSTM model""" | |
| if not TF_AVAILABLE: | |
| raise RuntimeError('TensorFlow not available') | |
| # Set default hyperparameters | |
| if hyperparams is None: | |
| hyperparams = {} | |
| seq_length = hyperparams.get('seq_length', seq_length) # Use hyperparams seq_length if provided | |
| epochs = hyperparams.get('epochs', 100) | |
| batch_size = hyperparams.get('batch_size', 16) | |
| learning_rate = hyperparams.get('learning_rate', 0.001) | |
| units = hyperparams.get('units', 100) | |
| dropout_rate = hyperparams.get('dropout_rate', 0.2) | |
| # Prepare data | |
| data = ts['y'].values.reshape(-1, 1) | |
| scaler = MinMaxScaler(feature_range=(0, 1)) | |
| scaled_data = scaler.fit_transform(data) | |
| # Create sequences | |
| X, y = create_sequences(scaled_data, seq_length) | |
| if len(X) < 10: # Not enough data | |
| return forecast_naive(ts, periods) | |
| # Split data | |
| train_size = int(len(X) * 0.8) | |
| X_train, X_test = X[:train_size], X[train_size:] | |
| y_train, y_test = y[:train_size], y[train_size:] | |
| # Reshape for LSTM [samples, time steps, features] | |
| X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 1)) | |
| X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 1)) | |
| # Build LSTM model | |
| model = Sequential([ | |
| LSTM(units, activation='relu', return_sequences=True, input_shape=(seq_length, 1)), | |
| Dropout(dropout_rate), | |
| LSTM(units//2, activation='relu'), | |
| Dropout(dropout_rate), | |
| Dense(1) | |
| ]) | |
| optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate) | |
| model.compile(optimizer=optimizer, loss='mse') | |
| # Train model | |
| model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size, verbose=0, validation_data=(X_test, y_test)) | |
| # Make predictions | |
| predictions = [] | |
| current_sequence = scaled_data[-seq_length:].reshape(1, seq_length, 1) | |
| for _ in range(periods): | |
| pred = model.predict(current_sequence, verbose=0) | |
| predictions.append(pred[0][0]) | |
| # Update sequence for next prediction | |
| current_sequence = np.roll(current_sequence, -1, axis=1) | |
| current_sequence[0, -1, 0] = pred[0][0] | |
| # Inverse transform predictions | |
| predictions = scaler.inverse_transform(np.array(predictions).reshape(-1, 1)).flatten() | |
| # Create forecast dataframe | |
| last_date = ts['ds'].max() | |
| future_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=periods, freq='D') | |
| return pd.DataFrame({ | |
| 'ds': future_dates, | |
| 'yhat': predictions, | |
| 'yhat_lower': predictions * 0.8, # Simple confidence intervals | |
| 'yhat_upper': predictions * 1.2 | |
| }) | |
| def forecast_bilstm(ts: pd.DataFrame, periods: int = 7, seq_length: int = 7, hyperparams: dict = None) -> pd.DataFrame: | |
| """Forecast using Bi-LSTM model""" | |
| if not TF_AVAILABLE: | |
| raise RuntimeError('TensorFlow not available') | |
| # Set default hyperparameters | |
| if hyperparams is None: | |
| hyperparams = {} | |
| seq_length = hyperparams.get('seq_length', seq_length) # Use hyperparams seq_length if provided | |
| epochs = hyperparams.get('epochs', 50) | |
| batch_size = hyperparams.get('batch_size', 16) | |
| learning_rate = hyperparams.get('learning_rate', 0.001) | |
| units = hyperparams.get('units', 50) | |
| dropout_rate = hyperparams.get('dropout_rate', 0.2) | |
| # Prepare data | |
| data = ts['y'].values.reshape(-1, 1) | |
| scaler = MinMaxScaler(feature_range=(0, 1)) | |
| scaled_data = scaler.fit_transform(data) | |
| # Create sequences | |
| X, y = create_sequences(scaled_data, seq_length) | |
| if len(X) < 10: # Not enough data | |
| return forecast_naive(ts, periods) | |
| # Split data | |
| train_size = int(len(X) * 0.8) | |
| X_train, X_test = X[:train_size], X[train_size:] | |
| y_train, y_test = y[:train_size], y[train_size:] | |
| # Reshape for Bi-LSTM [samples, time steps, features] | |
| X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 1)) | |
| X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 1)) | |
| # Build Bi-LSTM model | |
| model = Sequential([ | |
| Bidirectional(LSTM(units, activation='relu'), input_shape=(seq_length, 1)), | |
| Dropout(dropout_rate), | |
| Dense(1) | |
| ]) | |
| optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate) | |
| model.compile(optimizer=optimizer, loss='mse') | |
| # Train model | |
| model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size, verbose=0, validation_data=(X_test, y_test)) | |
| # Make predictions | |
| predictions = [] | |
| current_sequence = scaled_data[-seq_length:].reshape(1, seq_length, 1) | |
| for _ in range(periods): | |
| pred = model.predict(current_sequence, verbose=0) | |
| predictions.append(pred[0][0]) | |
| # Update sequence for next prediction | |
| current_sequence = np.roll(current_sequence, -1, axis=1) | |
| current_sequence[0, -1, 0] = pred[0][0] | |
| # Inverse transform predictions | |
| predictions = scaler.inverse_transform(np.array(predictions).reshape(-1, 1)).flatten() | |
| # Create forecast dataframe | |
| last_date = ts['ds'].max() | |
| future_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=periods, freq='D') | |
| return pd.DataFrame({ | |
| 'ds': future_dates, | |
| 'yhat': predictions, | |
| 'yhat_lower': predictions * 0.8, | |
| 'yhat_upper': predictions * 1.2 | |
| }) | |
| def forecast_gru(ts: pd.DataFrame, periods: int = 7, seq_length: int = 7, hyperparams: dict = None) -> pd.DataFrame: | |
| """Forecast using GRU model""" | |
| if not TF_AVAILABLE: | |
| raise RuntimeError('TensorFlow not available') | |
| # Set default hyperparameters | |
| if hyperparams is None: | |
| hyperparams = {} | |
| seq_length = hyperparams.get('seq_length', seq_length) # Use hyperparams seq_length if provided | |
| epochs = hyperparams.get('epochs', 50) | |
| batch_size = hyperparams.get('batch_size', 16) | |
| learning_rate = hyperparams.get('learning_rate', 0.001) | |
| units = hyperparams.get('units', 50) | |
| dropout_rate = hyperparams.get('dropout_rate', 0.2) | |
| # Prepare data | |
| data = ts['y'].values.reshape(-1, 1) | |
| scaler = MinMaxScaler(feature_range=(0, 1)) | |
| scaled_data = scaler.fit_transform(data) | |
| # Create sequences | |
| X, y = create_sequences(scaled_data, seq_length) | |
| if len(X) < 10: # Not enough data | |
| return forecast_naive(ts, periods) | |
| # Split data | |
| train_size = int(len(X) * 0.8) | |
| X_train, X_test = X[:train_size], X[train_size:] | |
| y_train, y_test = y[:train_size], y[train_size:] | |
| # Reshape for GRU [samples, time steps, features] | |
| X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 1)) | |
| X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 1)) | |
| # Build GRU model | |
| model = Sequential([ | |
| GRU(units, activation='relu', input_shape=(seq_length, 1)), | |
| Dropout(dropout_rate), | |
| Dense(1) | |
| ]) | |
| optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate) | |
| model.compile(optimizer=optimizer, loss='mse') | |
| # Train model | |
| model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size, verbose=0, validation_data=(X_test, y_test)) | |
| # Make predictions | |
| predictions = [] | |
| current_sequence = scaled_data[-seq_length:].reshape(1, seq_length, 1) | |
| for _ in range(periods): | |
| pred = model.predict(current_sequence, verbose=0) | |
| predictions.append(pred[0][0]) | |
| # Update sequence for next prediction | |
| current_sequence = np.roll(current_sequence, -1, axis=1) | |
| current_sequence[0, -1, 0] = pred[0][0] | |
| # Inverse transform predictions | |
| predictions = scaler.inverse_transform(np.array(predictions).reshape(-1, 1)).flatten() | |
| # Create forecast dataframe | |
| last_date = ts['ds'].max() | |
| future_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=periods, freq='D') | |
| return pd.DataFrame({ | |
| 'ds': future_dates, | |
| 'yhat': predictions, | |
| 'yhat_lower': predictions * 0.8, | |
| 'yhat_upper': predictions * 1.2 | |
| }) | |
| """Forecast using multivariate LSTM model""" | |
| if not TF_AVAILABLE: | |
| raise RuntimeError('TensorFlow not available') | |
| # Select features for multivariate forecasting | |
| feature_cols = [col for col in ts.columns if col not in ['ds', 'OpDeviceType', 'Owner', 'Weather', 'EventType']] | |
| if target_col not in feature_cols: | |
| raise ValueError(f"Target column '{target_col}' not found in features") | |
| # Prepare data | |
| data = ts[feature_cols].values | |
| scaler = MinMaxScaler(feature_range=(0, 1)) | |
| scaled_data = scaler.fit_transform(data) | |
| # Create sequences | |
| X, y = create_sequences(scaled_data, seq_length) | |
| if len(X) < 10: # Not enough data | |
| return forecast_naive(ts[['ds', target_col]].rename(columns={target_col: 'y'}), periods) | |
| # Split data | |
| train_size = int(len(X) * 0.8) | |
| X_train, X_test = X[:train_size], X[train_size:] | |
| y_train, y_test = y[:train_size], y[train_size:] | |
| # Reshape for LSTM [samples, time steps, features] | |
| X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], len(feature_cols))) | |
| X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], len(feature_cols))) | |
| # Build multivariate LSTM model | |
| model = Sequential([ | |
| LSTM(100, activation='relu', return_sequences=True, input_shape=(seq_length, len(feature_cols))), | |
| Dropout(0.2), | |
| LSTM(50, activation='relu'), | |
| Dropout(0.2), | |
| Dense(1) | |
| ]) | |
| model.compile(optimizer='adam', loss='mse') | |
| # Train model | |
| model.fit(X_train, y_train, epochs=100, batch_size=16, verbose=0, validation_data=(X_test, y_test)) | |
| # Make predictions | |
| predictions = [] | |
| current_sequence = scaled_data[-seq_length:].reshape(1, seq_length, len(feature_cols)) | |
| for _ in range(periods): | |
| pred = model.predict(current_sequence, verbose=0) | |
| predictions.append(pred[0][0]) | |
| # Update sequence for next prediction (use predicted value for target, keep other features) | |
| new_row = current_sequence[0, -1, :].copy() | |
| new_row[feature_cols.index(target_col)] = pred[0][0] # Update target with prediction | |
| current_sequence = np.roll(current_sequence, -1, axis=1) | |
| current_sequence[0, -1, :] = new_row | |
| # Inverse transform predictions (only for target column) | |
| target_scaler = MinMaxScaler(feature_range=(0, 1)) | |
| target_scaler.fit(data[:, feature_cols.index(target_col)].reshape(-1, 1)) | |
| predictions = target_scaler.inverse_transform(np.array(predictions).reshape(-1, 1)).flatten() | |
| # Create forecast dataframe | |
| last_date = ts['ds'].max() | |
| future_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=periods, freq='D') | |
| return pd.DataFrame({ | |
| 'ds': future_dates, | |
| 'yhat': predictions, | |
| 'yhat_lower': predictions * 0.8, | |
| 'yhat_upper': predictions * 1.2 | |
| }) | |
| """Forecast using LSTM model""" | |
| if not TF_AVAILABLE: | |
| raise RuntimeError('TensorFlow not available') | |
| # Prepare data | |
| data = ts['y'].values.reshape(-1, 1) | |
| scaler = MinMaxScaler(feature_range=(0, 1)) | |
| scaled_data = scaler.fit_transform(data) | |
| # Create sequences | |
| X, y = create_sequences(scaled_data, seq_length) | |
| if len(X) < 10: # Not enough data | |
| return forecast_naive(ts, periods) | |
| # Split data | |
| train_size = int(len(X) * 0.8) | |
| X_train, X_test = X[:train_size], X[train_size:] | |
| y_train, y_test = y[:train_size], y[train_size:] | |
| # Reshape for LSTM [samples, time steps, features] | |
| X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 1)) | |
| X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 1)) | |
| # Build LSTM model | |
| model = Sequential([ | |
| LSTM(50, activation='relu', input_shape=(seq_length, 1)), | |
| Dropout(0.2), | |
| Dense(1) | |
| ]) | |
| model.compile(optimizer='adam', loss='mse') | |
| # Train model | |
| model.fit(X_train, y_train, epochs=50, batch_size=16, verbose=0, validation_data=(X_test, y_test)) | |
| # Make predictions | |
| predictions = [] | |
| current_sequence = scaled_data[-seq_length:].reshape(1, seq_length, 1) | |
| for _ in range(periods): | |
| pred = model.predict(current_sequence, verbose=0) | |
| predictions.append(pred[0][0]) | |
| # Update sequence for next prediction | |
| current_sequence = np.roll(current_sequence, -1, axis=1) | |
| current_sequence[0, -1, 0] = pred[0][0] | |
| # Inverse transform predictions | |
| predictions = scaler.inverse_transform(np.array(predictions).reshape(-1, 1)).flatten() | |
| # Create forecast dataframe | |
| last_date = ts['ds'].max() | |
| future_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=periods, freq='D') | |
| return pd.DataFrame({ | |
| 'ds': future_dates, | |
| 'yhat': predictions, | |
| 'yhat_lower': predictions * 0.8, # Simple confidence intervals | |
| 'yhat_upper': predictions * 1.2 | |
| }) | |
| def forecast_bilstm_multivariate(ts: pd.DataFrame, periods: int = 7, seq_length: int = 7, target_col: str = 'daily_count', hyperparams: dict = None) -> pd.DataFrame: | |
| """Forecast using multivariate Bi-LSTM model""" | |
| if not TF_AVAILABLE: | |
| raise RuntimeError('TensorFlow not available') | |
| # Set default hyperparameters | |
| if hyperparams is None: | |
| hyperparams = {} | |
| epochs = hyperparams.get('epochs', 100) | |
| batch_size = hyperparams.get('batch_size', 16) | |
| learning_rate = hyperparams.get('learning_rate', 0.001) | |
| units = hyperparams.get('units', 100) | |
| dropout_rate = hyperparams.get('dropout_rate', 0.2) | |
| # Select features for multivariate forecasting | |
| feature_cols = [col for col in ts.columns if col not in ['ds', 'OpDeviceType', 'Owner', 'Weather', 'EventType']] | |
| if target_col not in feature_cols: | |
| raise ValueError(f"Target column '{target_col}' not found in features") | |
| # Prepare data | |
| data = ts[feature_cols].values | |
| scaler = MinMaxScaler(feature_range=(0, 1)) | |
| scaled_data = scaler.fit_transform(data) | |
| # Create sequences | |
| X, y = create_sequences(scaled_data, seq_length) | |
| if len(X) < 10: # Not enough data | |
| return forecast_naive(ts[['ds', target_col]].rename(columns={target_col: 'y'}), periods) | |
| # Split data | |
| train_size = int(len(X) * 0.8) | |
| X_train, X_test = X[:train_size], X[train_size:] | |
| y_train, y_test = y[:train_size], y[train_size:] | |
| # Reshape for Bi-LSTM [samples, time steps, features] | |
| X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], len(feature_cols))) | |
| X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], len(feature_cols))) | |
| # Build multivariate Bi-LSTM model | |
| model = Sequential([ | |
| Bidirectional(LSTM(units, activation='relu', return_sequences=True), input_shape=(seq_length, len(feature_cols))), | |
| Dropout(dropout_rate), | |
| Bidirectional(LSTM(units//2, activation='relu')), | |
| Dropout(dropout_rate), | |
| Dense(1) | |
| ]) | |
| optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate) | |
| model.compile(optimizer=optimizer, loss='mse') | |
| # Train model | |
| model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size, verbose=0, validation_data=(X_test, y_test)) | |
| # Make predictions | |
| predictions = [] | |
| current_sequence = scaled_data[-seq_length:].reshape(1, seq_length, len(feature_cols)) | |
| for _ in range(periods): | |
| pred = model.predict(current_sequence, verbose=0) | |
| predictions.append(pred[0][0]) | |
| # Update sequence for next prediction | |
| new_row = current_sequence[0, -1, :].copy() | |
| new_row[feature_cols.index(target_col)] = pred[0][0] | |
| current_sequence = np.roll(current_sequence, -1, axis=1) | |
| current_sequence[0, -1, :] = new_row | |
| # Inverse transform predictions | |
| target_scaler = MinMaxScaler(feature_range=(0, 1)) | |
| target_scaler.fit(data[:, feature_cols.index(target_col)].reshape(-1, 1)) | |
| predictions = target_scaler.inverse_transform(np.array(predictions).reshape(-1, 1)).flatten() | |
| # Create forecast dataframe | |
| last_date = ts['ds'].max() | |
| future_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=periods, freq='D') | |
| return pd.DataFrame({ | |
| 'ds': future_dates, | |
| 'yhat': predictions, | |
| 'yhat_lower': predictions * 0.8, | |
| 'yhat_upper': predictions * 1.2 | |
| }) | |
| """Forecast using Bi-LSTM model""" | |
| if not TF_AVAILABLE: | |
| raise RuntimeError('TensorFlow not available') | |
| # Prepare data | |
| data = ts['y'].values.reshape(-1, 1) | |
| scaler = MinMaxScaler(feature_range=(0, 1)) | |
| scaled_data = scaler.fit_transform(data) | |
| # Create sequences | |
| X, y = create_sequences(scaled_data, seq_length) | |
| if len(X) < 10: # Not enough data | |
| return forecast_naive(ts, periods) | |
| # Split data | |
| train_size = int(len(X) * 0.8) | |
| X_train, X_test = X[:train_size], X[train_size:] | |
| y_train, y_test = y[:train_size], y[train_size:] | |
| # Reshape for Bi-LSTM [samples, time steps, features] | |
| X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 1)) | |
| X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 1)) | |
| # Build Bi-LSTM model | |
| model = Sequential([ | |
| Bidirectional(LSTM(50, activation='relu'), input_shape=(seq_length, 1)), | |
| Dropout(0.2), | |
| Dense(1) | |
| ]) | |
| model.compile(optimizer='adam', loss='mse') | |
| # Train model | |
| model.fit(X_train, y_train, epochs=50, batch_size=16, verbose=0, validation_data=(X_test, y_test)) | |
| # Make predictions | |
| predictions = [] | |
| current_sequence = scaled_data[-seq_length:].reshape(1, seq_length, 1) | |
| for _ in range(periods): | |
| pred = model.predict(current_sequence, verbose=0) | |
| predictions.append(pred[0][0]) | |
| # Update sequence for next prediction | |
| current_sequence = np.roll(current_sequence, -1, axis=1) | |
| current_sequence[0, -1, 0] = pred[0][0] | |
| # Inverse transform predictions | |
| predictions = scaler.inverse_transform(np.array(predictions).reshape(-1, 1)).flatten() | |
| # Create forecast dataframe | |
| last_date = ts['ds'].max() | |
| future_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=periods, freq='D') | |
| return pd.DataFrame({ | |
| 'ds': future_dates, | |
| 'yhat': predictions, | |
| 'yhat_lower': predictions * 0.8, | |
| 'yhat_upper': predictions * 1.2 | |
| }) | |
| def forecast_multivariate_lstm(ts: pd.DataFrame, target_col: str = 'target_count', periods: int = 7, seq_length: int = 7) -> pd.DataFrame: | |
| """Forecast using multivariate LSTM model""" | |
| if not TF_AVAILABLE: | |
| raise RuntimeError('TensorFlow not available') | |
| # Prepare data - exclude date column and target | |
| feature_cols = [col for col in ts.columns if col not in ['ds', target_col]] | |
| target_data = ts[target_col].values.reshape(-1, 1) | |
| # Handle categorical features - simple label encoding for demo | |
| ts_encoded = ts.copy() | |
| for col in feature_cols: | |
| if ts[col].dtype == 'object': | |
| # Simple label encoding | |
| unique_vals = ts[col].unique() | |
| val_to_int = {val: i for i, val in enumerate(unique_vals)} | |
| ts_encoded[col] = ts[col].map(val_to_int) | |
| feature_data = ts_encoded[feature_cols].values | |
| # Scale features and target separately | |
| feature_scaler = MinMaxScaler(feature_range=(0, 1)) | |
| target_scaler = MinMaxScaler(feature_range=(0, 1)) | |
| scaled_features = feature_scaler.fit_transform(feature_data) | |
| scaled_target = target_scaler.fit_transform(target_data) | |
| # Combine features and target for sequences | |
| combined_data = np.column_stack([scaled_features, scaled_target]) | |
| # Create sequences | |
| X, y = create_sequences(combined_data, seq_length) | |
| if len(X) < 10: # Not enough data | |
| # Fallback to univariate naive | |
| univariate_ts = ts[['ds', target_col]].rename(columns={target_col: 'y'}) | |
| return forecast_naive(univariate_ts, periods) | |
| # Split data | |
| train_size = int(len(X) * 0.8) | |
| X_train, X_test = X[:train_size], X[train_size:] | |
| y_train, y_test = y[:train_size], y[train_size:] | |
| # X shape: [samples, time_steps, features] | |
| n_features = combined_data.shape[1] | |
| # Build multivariate LSTM model | |
| model = Sequential([ | |
| LSTM(64, activation='relu', input_shape=(seq_length, n_features), return_sequences=True), | |
| Dropout(0.2), | |
| LSTM(32, activation='relu'), | |
| Dropout(0.2), | |
| Dense(16, activation='relu'), | |
| Dense(1) | |
| ]) | |
| model.compile(optimizer='adam', loss='mse') | |
| # Train model | |
| model.fit(X_train, y_train, epochs=50, batch_size=16, verbose=0, validation_data=(X_test, y_test)) | |
| # Make predictions | |
| predictions = [] | |
| current_sequence = combined_data[-seq_length:].reshape(1, seq_length, n_features) | |
| for _ in range(periods): | |
| pred = model.predict(current_sequence, verbose=0) | |
| predictions.append(pred[0][0]) | |
| # For next prediction, we need to estimate future features | |
| # For simplicity, use the last known feature values | |
| next_features = current_sequence[0, -1, :-1] # All features except target | |
| next_sequence = np.column_stack([next_features, pred[0][0]]) # Add predicted target | |
| # Update sequence | |
| current_sequence = np.roll(current_sequence, -1, axis=1) | |
| current_sequence[0, -1, :] = next_sequence | |
| # Inverse transform predictions | |
| predictions = target_scaler.inverse_transform(np.array(predictions).reshape(-1, 1)).flatten() | |
| # Create forecast dataframe | |
| last_date = ts['ds'].max() | |
| future_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=periods, freq='D') | |
| return pd.DataFrame({ | |
| 'ds': future_dates, | |
| 'yhat': predictions, | |
| 'yhat_lower': predictions * 0.8, | |
| 'yhat_upper': predictions * 1.2 | |
| }) | |
| def forecast_multivariate_gru(ts: pd.DataFrame, target_col: str = 'target_count', periods: int = 7, seq_length: int = 7) -> pd.DataFrame: | |
| """Forecast using multivariate GRU model""" | |
| if not TF_AVAILABLE: | |
| raise RuntimeError('TensorFlow not available') | |
| # Similar to multivariate LSTM but using GRU layers | |
| feature_cols = [col for col in ts.columns if col not in ['ds', target_col]] | |
| target_data = ts[target_col].values.reshape(-1, 1) | |
| # Handle categorical features | |
| ts_encoded = ts.copy() | |
| for col in feature_cols: | |
| if ts[col].dtype == 'object': | |
| unique_vals = ts[col].unique() | |
| val_to_int = {val: i for i, val in enumerate(unique_vals)} | |
| ts_encoded[col] = ts[col].map(val_to_int) | |
| feature_data = ts_encoded[feature_cols].values | |
| # Scale data | |
| feature_scaler = MinMaxScaler(feature_range=(0, 1)) | |
| target_scaler = MinMaxScaler(feature_range=(0, 1)) | |
| scaled_features = feature_scaler.fit_transform(feature_data) | |
| scaled_target = target_scaler.fit_transform(target_data) | |
| combined_data = np.column_stack([scaled_features, scaled_target]) | |
| # Create sequences | |
| X, y = create_sequences(combined_data, seq_length) | |
| if len(X) < 10: | |
| univariate_ts = ts[['ds', target_col]].rename(columns={target_col: 'y'}) | |
| return forecast_naive(univariate_ts, periods) | |
| # Split data | |
| train_size = int(len(X) * 0.8) | |
| X_train, X_test = X[:train_size], X[train_size:] | |
| y_train, y_test = y[:train_size], y[train_size:] | |
| n_features = combined_data.shape[1] | |
| # Build multivariate GRU model | |
| model = Sequential([ | |
| GRU(64, activation='relu', input_shape=(seq_length, n_features), return_sequences=True), | |
| Dropout(0.2), | |
| GRU(32, activation='relu'), | |
| Dropout(0.2), | |
| Dense(16, activation='relu'), | |
| Dense(1) | |
| ]) | |
| model.compile(optimizer='adam', loss='mse') | |
| # Train model | |
| model.fit(X_train, y_train, epochs=50, batch_size=16, verbose=0, validation_data=(X_test, y_test)) | |
| # Make predictions (same logic as LSTM) | |
| predictions = [] | |
| current_sequence = combined_data[-seq_length:].reshape(1, seq_length, n_features) | |
| for _ in range(periods): | |
| pred = model.predict(current_sequence, verbose=0) | |
| predictions.append(pred[0][0]) | |
| next_features = current_sequence[0, -1, :-1] | |
| next_sequence = np.column_stack([next_features, pred[0][0]]) | |
| current_sequence = np.roll(current_sequence, -1, axis=1) | |
| current_sequence[0, -1, :] = next_sequence | |
| # Inverse transform predictions | |
| predictions = target_scaler.inverse_transform(np.array(predictions).reshape(-1, 1)).flatten() | |
| # Create forecast dataframe | |
| last_date = ts['ds'].max() | |
| future_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=periods, freq='D') | |
| return pd.DataFrame({ | |
| 'ds': future_dates, | |
| 'yhat': predictions, | |
| 'yhat_lower': predictions * 0.8, | |
| 'yhat_upper': predictions * 1.2 | |
| }) | |
| def run_forecast(df: pd.DataFrame, metric: str = 'count', periods: int = 7, model_type: str = 'prophet', multivariate: bool = False, target_col: str = 'daily_count', hyperparams: dict = None): | |
| """ | |
| Run forecasting with specified model type. | |
| Args: | |
| df: Input dataframe | |
| metric: 'count' or 'downtime_minutes' (for univariate) | |
| periods: Number of periods to forecast | |
| model_type: 'prophet', 'lstm', 'bilstm', 'gru', or 'naive' | |
| multivariate: Whether to use multivariate forecasting | |
| target_col: Target column for multivariate forecasting ('daily_count' or 'total_downtime_min') | |
| hyperparams: Dictionary of hyperparameters for the model | |
| """ | |
| if multivariate: | |
| ts = prepare_multivariate_timeseries(df, target_metric=metric) | |
| # Map metric to target column | |
| if metric == 'count': | |
| target_col = 'daily_count' | |
| elif metric == 'downtime_minutes': | |
| target_col = 'total_downtime_min' | |
| else: | |
| target_col = 'daily_count' | |
| if model_type == 'lstm': | |
| if TF_AVAILABLE and len(ts) >= 14: | |
| try: | |
| fcst = forecast_lstm_multivariate(ts, periods=periods, target_col=target_col, hyperparams=hyperparams) | |
| return ts, fcst | |
| except Exception as e: | |
| warnings.warn(f'Multivariate LSTM failed: {e}, falling back to univariate') | |
| # Fallback to univariate | |
| univariate_ts = prepare_timeseries(df, metric=metric) | |
| fcst = forecast_naive(univariate_ts, periods=periods) | |
| return univariate_ts, fcst | |
| elif model_type == 'bilstm': | |
| if TF_AVAILABLE and len(ts) >= 14: | |
| try: | |
| fcst = forecast_bilstm_multivariate(ts, periods=periods, target_col=target_col, hyperparams=hyperparams) | |
| return ts, fcst | |
| except Exception as e: | |
| warnings.warn(f'Multivariate Bi-LSTM failed: {e}, falling back to univariate') | |
| # Fallback to univariate | |
| univariate_ts = prepare_timeseries(df, metric=metric) | |
| fcst = forecast_naive(univariate_ts, periods=periods) | |
| return univariate_ts, fcst | |
| elif model_type == 'gru': | |
| if TF_AVAILABLE and len(ts) >= 14: | |
| try: | |
| fcst = forecast_gru_multivariate(ts, periods=periods, target_col=target_col, hyperparams=hyperparams) | |
| return ts, fcst | |
| except Exception as e: | |
| warnings.warn(f'Multivariate GRU failed: {e}, falling back to univariate') | |
| # Fallback to univariate | |
| univariate_ts = prepare_timeseries(df, metric=metric) | |
| fcst = forecast_naive(univariate_ts, periods=periods) | |
| return univariate_ts, fcst | |
| else: | |
| # For prophet and other models, fall back to univariate | |
| if multivariate: | |
| warnings.warn(f'Model {model_type} does not support multivariate forecasting. Using univariate {model_type} instead.') | |
| univariate_ts = prepare_timeseries(df, metric=metric) | |
| if model_type == 'prophet': | |
| if PROPHET_AVAILABLE and len(univariate_ts) >= 14: | |
| try: | |
| fcst = forecast_prophet(univariate_ts, periods=periods, hyperparams=hyperparams) | |
| return univariate_ts, fcst | |
| except Exception: | |
| warnings.warn('Prophet failed, falling back to naive') | |
| fcst = forecast_naive(univariate_ts, periods=periods) | |
| else: | |
| fcst = forecast_naive(univariate_ts, periods=periods) | |
| return univariate_ts, fcst | |
| else: | |
| # Use univariate approach (original logic) | |
| ts = prepare_timeseries(df, metric=metric) | |
| if model_type == 'prophet': | |
| if PROPHET_AVAILABLE and len(ts) >= 14: | |
| try: | |
| fcst = forecast_prophet(ts, periods=periods, hyperparams=hyperparams) | |
| return ts, fcst | |
| except Exception: | |
| warnings.warn('Prophet failed, falling back to naive') | |
| fcst = forecast_naive(ts, periods=periods) | |
| elif model_type == 'lstm': | |
| if TF_AVAILABLE and len(ts) >= 14: | |
| try: | |
| fcst = forecast_lstm(ts, periods=periods, hyperparams=hyperparams) | |
| return ts, fcst | |
| except Exception as e: | |
| warnings.warn(f'LSTM failed: {e}, falling back to naive') | |
| fcst = forecast_naive(ts, periods=periods) | |
| elif model_type == 'bilstm': | |
| if TF_AVAILABLE and len(ts) >= 14: | |
| try: | |
| fcst = forecast_bilstm(ts, periods=periods, hyperparams=hyperparams) | |
| return ts, fcst | |
| except Exception as e: | |
| warnings.warn(f'Bi-LSTM failed: {e}, falling back to naive') | |
| fcst = forecast_naive(ts, periods=periods) | |
| elif model_type == 'gru': | |
| if TF_AVAILABLE and len(ts) >= 14: | |
| try: | |
| fcst = forecast_gru(ts, periods=periods, hyperparams=hyperparams) | |
| return ts, fcst | |
| except Exception as e: | |
| warnings.warn(f'GRU failed: {e}, falling back to naive') | |
| fcst = forecast_naive(ts, periods=periods) | |
| else: # naive or unknown model_type | |
| fcst = forecast_naive(ts, periods=periods) | |
| return ts, fcst | |
| def forecast_gru_multivariate(ts: pd.DataFrame, periods: int = 7, seq_length: int = 7, target_col: str = 'daily_count', hyperparams: dict = None) -> pd.DataFrame: | |
| """Forecast using multivariate GRU model""" | |
| if not TF_AVAILABLE: | |
| raise RuntimeError('TensorFlow not available') | |
| # Set default hyperparameters | |
| if hyperparams is None: | |
| hyperparams = {} | |
| epochs = hyperparams.get('epochs', 100) | |
| batch_size = hyperparams.get('batch_size', 16) | |
| learning_rate = hyperparams.get('learning_rate', 0.001) | |
| units = hyperparams.get('units', 100) | |
| dropout_rate = hyperparams.get('dropout_rate', 0.2) | |
| # Select features for multivariate forecasting | |
| feature_cols = [col for col in ts.columns if col not in ['ds', 'OpDeviceType', 'Owner', 'Weather', 'EventType']] | |
| if target_col not in feature_cols: | |
| raise ValueError(f"Target column '{target_col}' not found in features") | |
| # Prepare data | |
| data = ts[feature_cols].values | |
| scaler = MinMaxScaler(feature_range=(0, 1)) | |
| scaled_data = scaler.fit_transform(data) | |
| # Create sequences | |
| X, y = create_sequences(scaled_data, seq_length) | |
| if len(X) < 10: # Not enough data | |
| return forecast_naive(ts[['ds', target_col]].rename(columns={target_col: 'y'}), periods) | |
| # Split data | |
| train_size = int(len(X) * 0.8) | |
| X_train, X_test = X[:train_size], X[train_size:] | |
| y_train, y_test = y[:train_size], y[train_size:] | |
| # Reshape for GRU [samples, time steps, features] | |
| X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], len(feature_cols))) | |
| X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], len(feature_cols))) | |
| # Build multivariate GRU model | |
| model = Sequential([ | |
| GRU(units, activation='relu', return_sequences=True, input_shape=(seq_length, len(feature_cols))), | |
| Dropout(dropout_rate), | |
| GRU(units//2, activation='relu'), | |
| Dropout(dropout_rate), | |
| Dense(1) | |
| ]) | |
| optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate) | |
| model.compile(optimizer=optimizer, loss='mse') | |
| # Train model | |
| model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size, verbose=0, validation_data=(X_test, y_test)) | |
| # Make predictions | |
| predictions = [] | |
| current_sequence = scaled_data[-seq_length:].reshape(1, seq_length, len(feature_cols)) | |
| for _ in range(periods): | |
| pred = model.predict(current_sequence, verbose=0) | |
| predictions.append(pred[0][0]) | |
| # Update sequence for next prediction | |
| new_row = current_sequence[0, -1, :].copy() | |
| new_row[feature_cols.index(target_col)] = pred[0][0] | |
| current_sequence = np.roll(current_sequence, -1, axis=1) | |
| current_sequence[0, -1, :] = new_row | |
| # Inverse transform predictions | |
| target_scaler = MinMaxScaler(feature_range=(0, 1)) | |
| target_scaler.fit(data[:, feature_cols.index(target_col)].reshape(-1, 1)) | |
| predictions = target_scaler.inverse_transform(np.array(predictions).reshape(-1, 1)).flatten() | |
| # Create forecast dataframe | |
| last_date = ts['ds'].max() | |
| future_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=periods, freq='D') | |
| return pd.DataFrame({ | |
| 'ds': future_dates, | |
| 'yhat': predictions, | |
| 'yhat_lower': predictions * 0.8, | |
| 'yhat_upper': predictions * 1.2 | |
| }) | |
| """Forecast using GRU model (univariate)""" | |
| if not TF_AVAILABLE: | |
| raise RuntimeError('TensorFlow not available') | |
| # Prepare data | |
| data = ts['y'].values.reshape(-1, 1) | |
| scaler = MinMaxScaler(feature_range=(0, 1)) | |
| scaled_data = scaler.fit_transform(data) | |
| # Create sequences | |
| X, y = create_sequences(scaled_data, seq_length) | |
| if len(X) < 10: # Not enough data | |
| return forecast_naive(ts, periods) | |
| # Split data | |
| train_size = int(len(X) * 0.8) | |
| X_train, X_test = X[:train_size], X[train_size:] | |
| y_train, y_test = y[:train_size], y[train_size:] | |
| # Reshape for GRU [samples, time steps, features] | |
| X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 1)) | |
| X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 1)) | |
| # Build GRU model | |
| model = Sequential([ | |
| GRU(50, activation='relu', input_shape=(seq_length, 1)), | |
| Dropout(0.2), | |
| Dense(1) | |
| ]) | |
| model.compile(optimizer='adam', loss='mse') | |
| # Train model | |
| model.fit(X_train, y_train, epochs=50, batch_size=16, verbose=0, validation_data=(X_test, y_test)) | |
| # Make predictions | |
| predictions = [] | |
| current_sequence = scaled_data[-seq_length:].reshape(1, seq_length, 1) | |
| for _ in range(periods): | |
| pred = model.predict(current_sequence, verbose=0) | |
| predictions.append(pred[0][0]) | |
| # Update sequence for next prediction | |
| current_sequence = np.roll(current_sequence, -1, axis=1) | |
| current_sequence[0, -1, 0] = pred[0][0] | |
| # Inverse transform predictions | |
| predictions = scaler.inverse_transform(np.array(predictions).reshape(-1, 1)).flatten() | |
| # Create forecast dataframe | |
| last_date = ts['ds'].max() | |
| future_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=periods, freq='D') | |
| return pd.DataFrame({ | |
| 'ds': future_dates, | |
| 'yhat': predictions, | |
| 'yhat_lower': predictions * 0.8, | |
| 'yhat_upper': predictions * 1.2 | |
| }) | |
| def forecast_lstm_multivariate(ts: pd.DataFrame, periods: int = 7, seq_length: int = 7, target_col: str = 'daily_count', hyperparams: dict = None) -> pd.DataFrame: | |
| """Forecast using multivariate LSTM model""" | |
| if not TF_AVAILABLE: | |
| raise RuntimeError('TensorFlow not available') | |
| # Set default hyperparameters | |
| if hyperparams is None: | |
| hyperparams = {} | |
| seq_length = hyperparams.get('seq_length', seq_length) # Use hyperparams seq_length if provided | |
| epochs = hyperparams.get('epochs', 100) | |
| batch_size = hyperparams.get('batch_size', 16) | |
| learning_rate = hyperparams.get('learning_rate', 0.001) | |
| units = hyperparams.get('units', 100) | |
| dropout_rate = hyperparams.get('dropout_rate', 0.2) | |
| # Select features for multivariate forecasting | |
| feature_cols = [col for col in ts.columns if col not in ['ds', 'OpDeviceType', 'Owner', 'Weather', 'EventType']] | |
| if target_col not in feature_cols: | |
| raise ValueError(f"Target column '{target_col}' not found in features") | |
| # Prepare data | |
| data = ts[feature_cols].values | |
| scaler = MinMaxScaler(feature_range=(0, 1)) | |
| scaled_data = scaler.fit_transform(data) | |
| # Create sequences | |
| X, y = create_sequences(scaled_data, seq_length) | |
| if len(X) < 10: # Not enough data | |
| return forecast_naive(ts[['ds', target_col]].rename(columns={target_col: 'y'}), periods) | |
| # Split data | |
| train_size = int(len(X) * 0.8) | |
| X_train, X_test = X[:train_size], X[train_size:] | |
| y_train, y_test = y[:train_size], y[train_size:] | |
| # Reshape for LSTM [samples, time steps, features] | |
| X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], len(feature_cols))) | |
| X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], len(feature_cols))) | |
| # Build multivariate LSTM model | |
| model = Sequential([ | |
| LSTM(units, activation='relu', return_sequences=True, input_shape=(seq_length, len(feature_cols))), | |
| Dropout(dropout_rate), | |
| LSTM(units//2, activation='relu'), | |
| Dropout(dropout_rate), | |
| Dense(1) | |
| ]) | |
| optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate) | |
| model.compile(optimizer=optimizer, loss='mse') | |
| # Train model | |
| model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size, verbose=0, validation_data=(X_test, y_test)) | |
| # Make predictions | |
| predictions = [] | |
| current_sequence = scaled_data[-seq_length:].reshape(1, seq_length, len(feature_cols)) | |
| for _ in range(periods): | |
| pred = model.predict(current_sequence, verbose=0) | |
| predictions.append(pred[0][0]) | |
| # Update sequence for next prediction (use predicted value for target, keep other features) | |
| new_row = current_sequence[0, -1, :].copy() | |
| new_row[feature_cols.index(target_col)] = pred[0][0] # Update target with prediction | |
| current_sequence = np.roll(current_sequence, -1, axis=1) | |
| current_sequence[0, -1, :] = new_row | |
| # Inverse transform predictions (only for target column) | |
| target_scaler = MinMaxScaler(feature_range=(0, 1)) | |
| target_scaler.fit(data[:, feature_cols.index(target_col)].reshape(-1, 1)) | |
| predictions = target_scaler.inverse_transform(np.array(predictions).reshape(-1, 1)).flatten() | |
| # Create forecast dataframe | |
| last_date = ts['ds'].max() | |
| future_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=periods, freq='D') | |
| return pd.DataFrame({ | |
| 'ds': future_dates, | |
| 'yhat': predictions, | |
| 'yhat_lower': predictions * 0.8, | |
| 'yhat_upper': predictions * 1.2 | |
| }) | |
| def forecast_bilstm_multivariate(ts: pd.DataFrame, periods: int = 7, seq_length: int = 7, target_col: str = 'daily_count', hyperparams: dict = None) -> pd.DataFrame: | |
| """Forecast using multivariate Bi-LSTM model""" | |
| if not TF_AVAILABLE: | |
| raise RuntimeError('TensorFlow not available') | |
| # Set default hyperparameters | |
| if hyperparams is None: | |
| hyperparams = {} | |
| epochs = hyperparams.get('epochs', 100) | |
| batch_size = hyperparams.get('batch_size', 16) | |
| learning_rate = hyperparams.get('learning_rate', 0.001) | |
| units = hyperparams.get('units', 100) | |
| dropout_rate = hyperparams.get('dropout_rate', 0.2) | |
| # Select features for multivariate forecasting | |
| feature_cols = [col for col in ts.columns if col not in ['ds', 'OpDeviceType', 'Owner', 'Weather', 'EventType']] | |
| if target_col not in feature_cols: | |
| raise ValueError(f"Target column '{target_col}' not found in features") | |
| # Prepare data | |
| data = ts[feature_cols].values | |
| scaler = MinMaxScaler(feature_range=(0, 1)) | |
| scaled_data = scaler.fit_transform(data) | |
| # Create sequences | |
| X, y = create_sequences(scaled_data, seq_length) | |
| if len(X) < 10: # Not enough data | |
| return forecast_naive(ts[['ds', target_col]].rename(columns={target_col: 'y'}), periods) | |
| # Split data | |
| train_size = int(len(X) * 0.8) | |
| X_train, X_test = X[:train_size], X[train_size:] | |
| y_train, y_test = y[:train_size], y[train_size:] | |
| # Reshape for Bi-LSTM [samples, time steps, features] | |
| X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], len(feature_cols))) | |
| X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], len(feature_cols))) | |
| # Build multivariate Bi-LSTM model | |
| model = Sequential([ | |
| Bidirectional(LSTM(units, activation='relu', return_sequences=True), input_shape=(seq_length, len(feature_cols))), | |
| Dropout(dropout_rate), | |
| Bidirectional(LSTM(units//2, activation='relu')), | |
| Dropout(dropout_rate), | |
| Dense(1) | |
| ]) | |
| optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate) | |
| model.compile(optimizer=optimizer, loss='mse') | |
| # Train model | |
| model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size, verbose=0, validation_data=(X_test, y_test)) | |
| # Make predictions | |
| predictions = [] | |
| current_sequence = scaled_data[-seq_length:].reshape(1, seq_length, len(feature_cols)) | |
| for _ in range(periods): | |
| pred = model.predict(current_sequence, verbose=0) | |
| predictions.append(pred[0][0]) | |
| # Update sequence for next prediction | |
| new_row = current_sequence[0, -1, :].copy() | |
| new_row[feature_cols.index(target_col)] = pred[0][0] | |
| current_sequence = np.roll(current_sequence, -1, axis=1) | |
| current_sequence[0, -1, :] = new_row | |
| # Inverse transform predictions | |
| target_scaler = MinMaxScaler(feature_range=(0, 1)) | |
| target_scaler.fit(data[:, feature_cols.index(target_col)].reshape(-1, 1)) | |
| predictions = target_scaler.inverse_transform(np.array(predictions).reshape(-1, 1)).flatten() | |
| # Create forecast dataframe | |
| last_date = ts['ds'].max() | |
| future_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=periods, freq='D') | |
| return pd.DataFrame({ | |
| 'ds': future_dates, | |
| 'yhat': predictions, | |
| 'yhat_lower': predictions * 0.8, | |
| 'yhat_upper': predictions * 1.2 | |
| }) | |
| def forecast_gru_multivariate(ts: pd.DataFrame, periods: int = 7, seq_length: int = 7, target_col: str = 'daily_count', hyperparams: dict = None) -> pd.DataFrame: | |
| """Forecast using multivariate GRU model""" | |
| if not TF_AVAILABLE: | |
| raise RuntimeError('TensorFlow not available') | |
| # Set default hyperparameters | |
| if hyperparams is None: | |
| hyperparams = {} | |
| epochs = hyperparams.get('epochs', 100) | |
| batch_size = hyperparams.get('batch_size', 16) | |
| learning_rate = hyperparams.get('learning_rate', 0.001) | |
| units = hyperparams.get('units', 100) | |
| dropout_rate = hyperparams.get('dropout_rate', 0.2) | |
| # Select features for multivariate forecasting | |
| feature_cols = [col for col in ts.columns if col not in ['ds', 'OpDeviceType', 'Owner', 'Weather', 'EventType']] | |
| if target_col not in feature_cols: | |
| raise ValueError(f"Target column '{target_col}' not found in features") | |
| # Prepare data | |
| data = ts[feature_cols].values | |
| scaler = MinMaxScaler(feature_range=(0, 1)) | |
| scaled_data = scaler.fit_transform(data) | |
| # Create sequences | |
| X, y = create_sequences(scaled_data, seq_length) | |
| if len(X) < 10: # Not enough data | |
| return forecast_naive(ts[['ds', target_col]].rename(columns={target_col: 'y'}), periods) | |
| # Split data | |
| train_size = int(len(X) * 0.8) | |
| X_train, X_test = X[:train_size], X[train_size:] | |
| y_train, y_test = y[:train_size], y[train_size:] | |
| # Reshape for GRU [samples, time steps, features] | |
| X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], len(feature_cols))) | |
| X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], len(feature_cols))) | |
| # Build multivariate GRU model | |
| model = Sequential([ | |
| GRU(units, activation='relu', return_sequences=True, input_shape=(seq_length, len(feature_cols))), | |
| Dropout(dropout_rate), | |
| GRU(units//2, activation='relu'), | |
| Dropout(dropout_rate), | |
| Dense(1) | |
| ]) | |
| optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate) | |
| model.compile(optimizer=optimizer, loss='mse') | |
| # Train model | |
| model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size, verbose=0, validation_data=(X_test, y_test)) | |
| # Make predictions | |
| predictions = [] | |
| current_sequence = scaled_data[-seq_length:].reshape(1, seq_length, len(feature_cols)) | |
| for _ in range(periods): | |
| pred = model.predict(current_sequence, verbose=0) | |
| predictions.append(pred[0][0]) | |
| # Update sequence for next prediction | |
| new_row = current_sequence[0, -1, :].copy() | |
| new_row[feature_cols.index(target_col)] = pred[0][0] | |
| current_sequence = np.roll(current_sequence, -1, axis=1) | |
| current_sequence[0, -1, :] = new_row | |
| # Inverse transform predictions | |
| target_scaler = MinMaxScaler(feature_range=(0, 1)) | |
| target_scaler.fit(data[:, feature_cols.index(target_col)].reshape(-1, 1)) | |
| predictions = target_scaler.inverse_transform(np.array(predictions).reshape(-1, 1)).flatten() | |
| # Create forecast dataframe | |
| last_date = ts['ds'].max() | |
| future_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=periods, freq='D') | |
| return pd.DataFrame({ | |
| 'ds': future_dates, | |
| 'yhat': predictions, | |
| 'yhat_lower': predictions * 0.8, | |
| 'yhat_upper': predictions * 1.2 | |
| }) | |