← Назад к вопросам

Как рассчитать величину задержки рейса в минутах по историческим данным о перелетах?

2.0 Middle🔥 151 комментариев
#Временные ряды#Машинное обучение#Опыт и проекты

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Расчет задержки рейса по историческим данным

Предсказание задержек рейсов — это важная задача в авиационной индустрии. Это регрессионная задача, где нужно предсказать величину задержки (в минутах) на основе исторических данных о рейсах.

Шаг 1: Подготовка данных

Вначале нужно загрузить и исследовать данные:

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt

# Загрузка данных
df = pd.read_csv('flights.csv')

# Исследование
print(df.head())
print(df.info())
print(df.describe())

# Проверка целевой переменной
print(f"\nЗадержка - Статистика:")
print(df['ArrDelay'].describe())
print(f"Процент пропущенных: {df['ArrDelay'].isna().sum() / len(df) * 100:.2f}%")

# Визуализация распределения задержек
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.hist(df['ArrDelay'], bins=50, edgecolor='black')
plt.xlabel('Задержка (минуты)')
plt.ylabel('Количество рейсов')
plt.title('Распределение задержек рейсов')

plt.subplot(1, 2, 2)
plt.hist(df[df['ArrDelay'] <= 60]['ArrDelay'], bins=50, edgecolor='black')
plt.xlabel('Задержка (минуты)')
plt.ylabel('Количество рейсов')
plt.title('Задержки <= 60 минут')
plt.tight_layout()
plt.show()

Шаг 2: Инженерия признаков

Из исторических данных нужно создать информативные признаки:

# Временные признаки
df['DepTime_hour'] = df['DepTime'] // 100
df['DepTime_minute'] = df['DepTime'] % 100
df['DayOfWeek_sin'] = np.sin(2 * np.pi * df['DayOfWeek'] / 7)
df['DayOfWeek_cos'] = np.cos(2 * np.pi * df['DayOfWeek'] / 7)
df['Month_sin'] = np.sin(2 * np.pi * df['Month'] / 12)
df['Month_cos'] = np.cos(2 * np.pi * df['Month'] / 12)

# Признаки по маршруту
df['route_id'] = df['Origin'] + '_' + df['Dest']

# Исторические признаки — средняя задержка по маршруту
route_delays = df.groupby('route_id')['ArrDelay'].mean()
df['route_avg_delay'] = df['route_id'].map(route_delays)

# Исторические признаки — средняя задержка по авиакомпании
airline_delays = df.groupby('Carrier')['ArrDelay'].mean()
df['airline_avg_delay'] = df['Carrier'].map(airline_delays)

# Исторические признаки — средняя задержка по часу вылета
hour_delays = df.groupby('DepTime_hour')['ArrDelay'].mean()
df['hour_avg_delay'] = df['DepTime_hour'].map(hour_delays)

# Признаки расстояния
df['distance_category'] = pd.cut(
    df['Distance'],
    bins=[0, 500, 1000, 2000, 10000],
    labels=['short', 'medium', 'long', 'ultra_long']
)

# Кодирование категорических признаков
df_encoded = pd.get_dummies(
    df,
    columns=['Origin', 'Dest', 'Carrier', 'distance_category'],
    drop_first=True
)

print(f"Количество признаков: {df_encoded.shape[1]}")

Шаг 3: Обработка пропущенных значений

# Удаление рейсов с пропущенной задержкой
df_clean = df_encoded.dropna(subset=['ArrDelay'])

print(f"До удаления пропусков: {len(df_encoded)} строк")
print(f"После удаления пропусков: {len(df_clean)} строк")

# Обработка пропусков в признаках
df_clean = df_clean.fillna(df_clean.median(numeric_only=True))

# Удаление выбросов (опционально)
Q1 = df_clean['ArrDelay'].quantile(0.25)
Q3 = df_clean['ArrDelay'].quantile(0.75)
IQR = Q3 - Q1
df_clean = df_clean[
    (df_clean['ArrDelay'] >= Q1 - 1.5 * IQR) &
    (df_clean['ArrDelay'] <= Q3 + 1.5 * IQR)
]

print(f"После удаления выбросов: {len(df_clean)} строк")

Шаг 4: Разделение на тренировку и тест

# Выбор признаков и целевой переменной
feature_columns = [col for col in df_clean.columns if col != 'ArrDelay']
X = df_clean[feature_columns]
y = df_clean['ArrDelay']

# Разделение временных рядов (важно для авиа-данных!)
# Последние 20% данных — тест (более новые данные)
test_size = int(len(X) * 0.2)
X_train, X_test = X[:-test_size], X[-test_size:]
y_train, y_test = y[:-test_size], y[-test_size:]

print(f"Тренировка: {len(X_train)} примеров")
print(f"Тест: {len(X_test)} примеров")

# Нормализация
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

Шаг 5: Обучение моделей

from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.linear_model import Ridge
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
import numpy as np

# Модель 1: Random Forest
rf_model = RandomForestRegressor(
    n_estimators=100,
    max_depth=20,
    min_samples_split=5,
    min_samples_leaf=2,
    random_state=42,
    n_jobs=-1
)
rf_model.fit(X_train_scaled, y_train)

# Модель 2: Gradient Boosting
gb_model = GradientBoostingRegressor(
    n_estimators=100,
    learning_rate=0.1,
    max_depth=5,
    min_samples_split=5,
    subsample=0.8,
    random_state=42
)
gb_model.fit(X_train_scaled, y_train)

# Модель 3: XGBoost (более современный вариант)
import xgboost as xgb
xgb_model = xgb.XGBRegressor(
    n_estimators=100,
    learning_rate=0.1,
    max_depth=5,
    subsample=0.8,
    random_state=42
)
xgb_model.fit(X_train_scaled, y_train)

print("Все модели обучены")

Шаг 6: Оценка качества

def evaluate_model(model, X_train, X_test, y_train, y_test, name):
    """Оценка качества модели"""
    
    y_train_pred = model.predict(X_train)
    y_test_pred = model.predict(X_test)
    
    train_mse = mean_squared_error(y_train, y_train_pred)
    test_mse = mean_squared_error(y_test, y_test_pred)
    train_mae = mean_absolute_error(y_train, y_train_pred)
    test_mae = mean_absolute_error(y_test, y_test_pred)
    train_r2 = r2_score(y_train, y_train_pred)
    test_r2 = r2_score(y_test, y_test_pred)
    
    print(f"\n{name}:")
    print(f"  Train RMSE: {np.sqrt(train_mse):.2f} мин, MAE: {train_mae:.2f} мин, R²: {train_r2:.3f}")
    print(f"  Test RMSE: {np.sqrt(test_mse):.2f} мин, MAE: {test_mae:.2f} мин, R²: {test_r2:.3f}")
    print(f"  Переобучение: {np.sqrt(test_mse) - np.sqrt(train_mse):.2f} мин")
    
    return {
        'name': name,
        'test_rmse': np.sqrt(test_mse),
        'test_mae': test_mae,
        'test_r2': test_r2,
        'predictions': y_test_pred
    }

results = []
results.append(evaluate_model(rf_model, X_train_scaled, X_test_scaled, y_train, y_test, "Random Forest"))
results.append(evaluate_model(gb_model, X_train_scaled, X_test_scaled, y_train, y_test, "Gradient Boosting"))
results.append(evaluate_model(xgb_model, X_train_scaled, X_test_scaled, y_train, y_test, "XGBoost"))

Шаг 7: Ансамбль моделей

# Комбинирование предсказаний
y_pred_ensemble = (
    rf_model.predict(X_test_scaled) * 0.3 +
    gb_model.predict(X_test_scaled) * 0.4 +
    xgb_model.predict(X_test_scaled) * 0.3
)

ensemble_mae = mean_absolute_error(y_test, y_pred_ensemble)
ensemble_rmse = np.sqrt(mean_squared_error(y_test, y_pred_ensemble))
ensemble_r2 = r2_score(y_test, y_pred_ensemble)

print(f"\nЭнсамбль (RF 30% + GB 40% + XGB 30%):")
print(f"  Test RMSE: {ensemble_rmse:.2f} мин")
print(f"  Test MAE: {ensemble_mae:.2f} мин")
print(f"  Test R²: {ensemble_r2:.3f}")

Шаг 8: Интерпретация результатов

# Важность признаков
feature_importance = pd.DataFrame({
    'feature': feature_columns,
    'importance': xgb_model.feature_importances_
}).sort_values('importance', ascending=False).head(15)

print("\nТоп-15 важных признаков:")
print(feature_importance)

# Анализ ошибок
errors = y_test - y_pred_ensemble
print(f"\nОшибки предсказания:")
print(f"  Средняя ошибка: {errors.mean():.2f} мин")
print(f"  Стандартное отклонение: {errors.std():.2f} мин")
print(f"  Доля предсказаний в пределах 10 мин: {(abs(errors) <= 10).sum() / len(errors) * 100:.1f}%")
print(f"  Доля предсказаний в пределах 30 мин: {(abs(errors) <= 30).sum() / len(errors) * 100:.1f}%")

Практическое применение

# Функция для предсказания задержки нового рейса
def predict_flight_delay(departure_hour, airline, origin, dest, distance):
    """Предсказание задержки для конкретного рейса"""
    
    # Создание входного примера
    sample = np.zeros((1, len(feature_columns)))
    # Заполнить признаки на основе входных данных
    # ...
    
    # Нормализация
    sample_scaled = scaler.transform(sample)
    
    # Предсказание
    delay_pred = xgb_model.predict(sample_scaled)[0]
    
    return max(0, delay_pred)  # Задержка не может быть отрицательной

# Пример
predicted_delay = predict_flight_delay(
    departure_hour=14,
    airline='UA',
    origin='JFK',
    dest='LAX',
    distance=2500
)
print(f"Предсказанная задержка: {predicted_delay:.0f} минут")

Ключевые метрики

  • RMSE (Root Mean Squared Error) — среднее квадратическое отклонение
  • MAE (Mean Absolute Error) — средняя абсолютная ошибка в минутах (более интерпретируемо)
  • R² (коэффициент детерминации) — доля объяснённой дисперсии
  • Доля точных предсказаний — процент рейсов, предсказанных в пределах N минут

Итоговые рекомендации

  • Используй временное разделение (не random split) для авиа-данных
  • Создавай исторические признаки по маршруту и авиакомпании
  • Комбинируй несколько моделей для лучших результатов
  • Анализируй ошибки по типам рейсов (дневные, ночные, выходные)
  • Регулярно переобучай модель с новыми данными
  • MAE — лучшая метрика для практического применения