Документация Engee
Notebook

Инженерный анализ телеметрических данных сталелитейного предприятия

Введение

Любое промышленное предприятие ежедневно генерирует огромные объёмы данных: показания счётчиков, параметры процессов, метеосводки, данные о выбросах. Инженерная ценность этих данных появляется в тот момент, когда мы ищем в них закономерности: как связаны параметры, какие режимы работы существуют, есть ли скрытые циклы для оптимизации.

В этой работе мы последовательно проанализируем телеметрические данные сталелитейного предприятия за 2018 год. Набор содержит 35 040 записей с 15-минутным интервалом: потребление активной и реактивной энергии, выбросы CO₂, коэффициент мощности, температуру и влажность. Мы применим весь инструменты инженерного аналитика: описательные статистики, корреляции, визуализацию, разбиение на сезоны и уровни загрузки, метод главных компонент, кластеризацию и спектральный анализ. Цель не просто построить графики, а понять, как работает предприятие и где скрыты резервы эффективности.

Каждый шаг сопровождается инженерной интерпретацией. Мы увидим, что высокая корреляция между потреблением и выбросами CO₂ позволяет косвенно контролировать экологические показатели. Обнаружим три устойчивых режима работы с разной эффективностью. Найдём суточные и недельные циклы, важные при планировании нагрузки. Анализ коэффициента мощности покажет, где энергия теряется впустую. В итоге получим целостную картину производственной системы, пригодную для принятия инженерных решений.

Подключение библиотек

Подключим библиотеки, необходимые для исследовательского анализа данных. Каждая из них решает свою задачу:

  • Statistics и StatsBase – базовые статистические функции: среднее, стандартное отклонение, квантили, корреляции;

  • StatsPlots и Plots – построение графиков: гистограмм, диаграмм рассеяния, тепловых карт, коробчатых диаграмм;

  • CSV и DataFrames – чтение и обработка табличных данных;

  • Dates – работа с датами и временем, что важно при анализе временных рядов;

  • Clustering – алгоритмы кластеризации, в частности k-средних;

  • MultivariateStats – метод главных компонент для снижения размерности;

  • HypothesisTests – статистические критерии для проверки гипотез;

  • FFTW – быстрое преобразование Фурье для спектрального анализа;

  • Random – воспроизводимость результатов при случайных операциях.

In [ ]:
for pkg in (["Statistics", "StatsBase", "StatsPlots", "CSV", "DataFrames", "Dates", "Clustering", "MultivariateStats", "HypothesisTests", "FFTW", "Random"])
    try
        eval(Meta.parse("using $pkg"))
    catch
        Pkg.add(pkg)
        eval(Meta.parse("using $pkg"))
    end
end

Загрузка набора данных

Загрузим CSV-файл, с телеметрическими данными сталелитейного предприятия. Он содержит 35040 записей с данными энергопотребления, температуры и влажности за 2018 год.

In [ ]:
df = CSV.read("Сталелитейные_данные.csv", DataFrame)
println("Размерность: ", size(df))
println("Столбцы: ", names(df))
Размерность: (35040, 10)
Столбцы: ["date", "Usage_kWh", "Lagging_Current_Reactive_Power_kVarh", "Leading_Current_Reactive_Power_kVarh", "CO2_tCO2_", "Lagging_Current_Power_Factor", "Leading_Current_Power_Factor", "DateTime", "Temperature_C", "Humidity_Pct"]

Преобразование даты

Создадим столбец DateTime для временного анализа.

In [ ]:
df.DateTime = DateTime.(df.date, dateformat"dd/mm/yyyy HH:MM")
println("Диапазон: ", minimum(df.DateTime), " - ", maximum(df.DateTime))
Диапазон: 2018-01-01T00:00:00 - 2018-12-31T23:45:00

Описательная статистика

Первый шаг любого анализа – получить сводку по всем числовым переменным. Функция describe покажет среднее, минимум, максимум, стандартное отклонение и квартили. Это позволяет быстро оценить разброс параметров и обнаружить аномалии.

In [ ]:
числовые = [:Usage_kWh, :Lagging_Current_Reactive_Power_kVarh,
            :Leading_Current_Reactive_Power_kVarh, :CO2_tCO2_,
            :Lagging_Current_Power_Factor, :Leading_Current_Power_Factor,
            :Temperature_C, :Humidity_Pct]
describe(df[:, числовые], :all)
Out[0]:
8×16 DataFrame
Rowvariablemeanstdminq25medianq75maxsumnuniquenuniqueallnmissingnnonmissingfirstlasteltype
SymbolFloat64Float64Float64Float64Float64Float64Float64Float64NothingInt64Int64Int64Float64Float64DataType
1Usage_kWh27.386933.44440.03.24.5751.2375157.189.59637e533430350403.173.67Float64
2Lagging_Current_Reactive_Power_kVarh13.035416.3060.02.35.022.6496.914.5676e519540350402.953.02Float64
3Leading_Current_Reactive_Power_kVarh3.870957.424460.00.00.02.0927.76135638.07680350400.00.07Float64
4CO2_tCO2_0.01152430.01615080.00.00.00.020.07403.8180350400.00.0Float64
5Lagging_Current_Power_Factor80.578118.92130.063.3287.9699.0225100.02.82346e6507903504073.2177.22Float64
6Leading_Current_Power_Factor84.367930.45650.099.7100.0100.0100.02.95625e63366035040100.099.98Float64
7Temperature_C2.3200513.3788-28.9-9.42.113.737.481294.5633035040-12.3-12.8Float64
8Humidity_Pct69.990511.65632.861.670.2578.598.02.45247e662003504091.295.4Float64

Корреляционная матрица

Построим тепловую карту корреляций между всеми числовыми параметрами. Это ключевой инструмент исследовательского анализа: мы сразу видим, какие параметры связаны друг с другом. Например, ожидаем высокую корреляцию между потреблением энергии и выбросами CO₂.

In [ ]:
corr_matrix = cor(Matrix(df[:, числовые]))
heatmap(corr_matrix,
        xticks=(1:length(числовые), string.(числовые)),
        yticks=(1:length(числовые), string.(числовые)),
        xrotation=45, size=(700, 600),
        title="Корреляционная матрица параметров")
Out[0]:

Числовые значения корреляций

Выведем числовые значения корреляций для более точной оценки. Отсортируем пары параметров по убыванию абсолютной величины корреляции.

In [ ]:
println("Топ-10 корреляций:\n")
корреляции = Float64[]
пары = String[]
for i in 1:length(числовые)
    for j in (i+1):length(числовые)
        push!(корреляции, corr_matrix[i, j])
        push!(пары, "$(числовые[i]) - $(числовые[j])")
    end
end
idx = sortperm(abs.(корреляции), rev=true)
for k in 1:10
    println("  $(пары[idx[k]]): $(round(корреляции[idx[k]], digits=3))")
end
Топ-10 корреляций:

  Usage_kWh - CO2_tCO2_: 0.988
  Leading_Current_Reactive_Power_kVarh - Leading_Current_Power_Factor: -0.944
  Usage_kWh - Lagging_Current_Reactive_Power_kVarh: 0.896
  Lagging_Current_Reactive_Power_kVarh - CO2_tCO2_: 0.887
  Temperature_C - Humidity_Pct: -0.747
  Leading_Current_Reactive_Power_kVarh - Lagging_Current_Power_Factor: 0.527
  Lagging_Current_Power_Factor - Leading_Current_Power_Factor: -0.52
  Lagging_Current_Reactive_Power_kVarh - Leading_Current_Power_Factor: 0.408
  Lagging_Current_Reactive_Power_kVarh - Leading_Current_Reactive_Power_kVarh: -0.405
  Usage_kWh - Lagging_Current_Power_Factor: 0.386

График рассеяния с цветовым кодированием

Построим диаграмму рассеяния для двух наиболее связанных параметров. Добавим цветовое кодирование по температуре. Это позволяет увидеть, как третья переменная влияет на взаимосвязь двух других.

In [ ]:
scatter(df.Usage_kWh[1:1000], df.CO2_tCO2_[1:1000],
        marker_z=df.Temperature_C[1:1000],
        xlabel="Энергопотребление, кВт·ч", ylabel="Выбросы CO₂",
        title="Энергопотребление и выбросы СO₂",
        color=:thermal, legend=:bottomright, size=(700, 500))
Out[0]:

Анализ по сезонам

Добавим столбец с сезоном года на основе месяца. Затем сравним среднее потребление по сезонам. Это типичный инженерный анализ: разбить данные на группы и сравнить статистики.

In [ ]:
df.Season = [["Зима", "Зима", "Весна", "Весна", "Весна", "Лето", 
              "Лето", "Лето", "Осень", "Осень", "Осень", "Зима"][month(dt)] 
             for dt in df.DateTime]

season_stats = combine(groupby(df, :Season), 
                       :Usage_kWh => mean => :avg_usage,
                       :Usage_kWh => std => :std_usage,
                       :Temperature_C => mean => :avg_temp)
println("Статистика по сезонам:")
season_stats
Статистика по сезонам:
Out[0]:
4×4 DataFrame
RowSeasonavg_usagestd_usageavg_temp
StringFloat64Float64Float64
1Зима32.080137.1293-12.1306
2Весна26.954232.84788.52577
3Лето24.415630.742916.7532
4Осень26.186632.3212-4.25374

Визуализация по сезонам

Построим диаграмму (boxplot) энергопотребления по сезонам. Диаграмма показывает медиану, квартили и выбросы - это даёт более полную картину распределения, чем просто среднее.

In [ ]:
boxplot(df.Season, df.Usage_kWh,
        xlabel="Сезон", ylabel="Энергопотребление, кВт·ч",
        title="Распределение энергопотребления по сезонам",
        legend=false, size=(700, 450))
Out[0]:

Анализ по времени суток

Выделим час суток из DateTime и проанализируем суточный профиль потребления. Это важно для понимания режимов работы предприятия.

In [ ]:
df.Hour = hour.(df.DateTime)
hourly_stats = combine(groupby(df, :Hour), 
                       :Usage_kWh => mean => :avg_usage,
                       :Usage_kWh => std => :std_usage)
plot(hourly_stats.Hour, hourly_stats.avg_usage,
     ribbon=hourly_stats.std_usage,
     xlabel="Час суток", ylabel="Среднее потребление, кВт·ч",
     title="Суточный профиль энергопотребления",
     legend=false, linewidth=2, size=(700, 400))
Out[0]:

Добавление признаков загрузки

Используем данные об энергопотреблении для классификации уровня загрузки. Разделим на три категории по квартилям: низкая, средняя и высокая загрузка.

In [ ]:
q33, q67 = quantile(df.Usage_kWh, [0.33, 0.67])
df.LoadLevel = [x <= q33 ? "Низкая" : x <= q67 ? "Средняя" : "Высокая" 
                for x in df.Usage_kWh]
println("Границы: низкая < $(round(q33, digits=1)), средняя < $(round(q67, digits=1)), высокая")
println("Распределение по уровням:")
combine(groupby(df, :LoadLevel), nrow => :count)
Границы: низкая < 3.5, средняя < 38.2, высокая
Распределение по уровням:
Out[0]:
3×2 DataFrame
RowLoadLevelcount
StringInt64
1Низкая11634
2Средняя11851
3Высокая11555

Многомерный анализ: PCA

Применим метод главных компонент (Principal Component Analysis) для снижения размерности. Это позволит увидеть структуру данных в двумерном пространстве и обнаружить скрытые закономерности. PCA находит направления максимального разброса данных.

In [ ]:
pca_data = Matrix(df[:, числовые])
pca_data = (pca_data .- mean(pca_data, dims=1)) ./ std(pca_data, dims=1)
pca_model = fit(PCA, pca_data', maxoutdim=2)
проекция = MultivariateStats.transform(pca_model, pca_data')
общая_var = tvar(pca_model)
объясн_var = principalvar(pca_model, 2)
println("Объяснённая дисперсия: ", round.(объясн_var / общая_var, digits=3))

scatter(проекция[1, 1:2000], проекция[2, 1:2000],
        xlabel="PC1", ylabel="PC2",
        title="PCA энергопотребления (первые 2000 точек)",
        alpha=0.5, legend=false, size=(600, 450))
Объяснённая дисперсия: 0.292
Out[0]:

Кластеризация режимов работы

Применим алгоритм k-средних для выделения типичных режимов работы предприятия. Это метод автоматической группировки данных без учителя. Попробуем выделить 3 кластера и посмотрим, совпадают ли они с уровнями загрузки.

In [ ]:
k = 3
cluster_result = kmeans(pca_data', k)
df.Cluster = cluster_result.assignments

println("Размеры кластеров:\n")
for i in 1:k
    println("  Кластер $i: $(sum(df.Cluster .== i)) точек")
end
Размеры кластеров:

  Кластер 1: 7677 точек
  Кластер 2: 12864 точек
  Кластер 3: 14499 точек

Визуализация кластеров

Отобразим кластеры на PCA-проекции разными цветами. Это покажет, насколько хорошо алгоритм разделил данные.

In [ ]:
scatter(проекция[1, 1:2000], проекция[2, 1:2000],
        group=df.Cluster[1:2000],
        xlabel="PC1", ylabel="PC2",
        title="Кластеры режимов работы",
        legend=false,
        alpha=0.5, size=(600, 450))
Out[0]:

Характеристики кластеров

Для каждого кластера вычислим средние значения ключевых параметров. Это позволит интерпретировать кластеры: например, кластер с высоким энергопотреблением и высокой температурой может соответствовать дневной смене летом.

In [ ]:
cluster_stats = combine(groupby(df, :Cluster),
    :Usage_kWh => mean => :avg_usage,
    :Temperature_C => mean => :avg_temp,
    :Humidity_Pct => mean => :avg_humidity,
    :Lagging_Current_Power_Factor => mean => :avg_pf)
println("Характеристики кластеров:")
cluster_stats
Характеристики кластеров:
Out[0]:
3×5 DataFrame
RowClusteravg_usageavg_tempavg_humidityavg_pf
Int64Float64Float64Float64Float64
115.227764.5554966.306199.7512
2266.10334.1131567.071490.5579
334.76931-0.4544874.531361.5717

Проверка гипотезы: будни и выходные

Добавим признак дня недели и проверим статистическую гипотезу: отличается ли энергопотребление в будние и выходные дни. Используем t-критерий Стьюдента для независимых выборок.

In [ ]:
df.DayOfWeek = dayofweek.(df.DateTime)
df.IsWeekend = df.DayOfWeek .>= 6

будни = df.Usage_kWh[.!df.IsWeekend]
выходные = df.Usage_kWh[df.IsWeekend]

println("Среднее в будни: ", round(mean(будни), digits=2))
println("Среднее в выходные: ", round(mean(выходные), digits=2))
println("t-тест: ", UnequalVarianceTTest(будни, выходные))
Среднее в будни: 33.62
Среднее в выходные: 11.73
t-тест: Two sample t-test (unequal variance)
------------------------------------
Population details:
    parameter of interest:   Mean difference
    value under h_0:         0
    point estimate:          21.8924
    95% confidence interval: (21.3, 22.48)

Test summary:
    outcome with 95% confidence: reject h_0
    two-sided p-value:           <1e-99

Details:
    number of observations:   [25056,9984]
    t-statistic:              72.76994286810671
    degrees of freedom:       31323.420564935426
    empirical standard error: 0.3008439944554738

Реактивная мощность и коэффициент мощности

Проанализируем коэффициент мощности - важный показатель эффективности энергопотребления. Низкий коэффициент мощности означает, что предприятие потребляет много реактивной энергии, за которую могут начисляться штрафы.

In [ ]:
histogram(df.Lagging_Current_Power_Factor, bins=40,
          xlabel="Коэффициент мощности, %", ylabel="Частота",
          title="Распределение коэффициента мощности",
          legend=false, size=(600, 400))
vline!([mean(df.Lagging_Current_Power_Factor)], 
       linewidth=2, linestyle=:dash, label="Среднее")
Out[0]:

Спектральный анализ временного ряда

Применим FFT к временному ряду потребления, чтобы найти периодические составляющие. Это поможет выявить суточные, недельные и сезонные циклы.

In [ ]:
signal = df.Usage_kWh[1:4096] .- mean(df.Usage_kWh[1:4096])
spectrum = abs.(fft(signal))
freqs = (0:2047) * (1/0.25) / 4096 * 24  # циклов в сутки

plot(freqs[2:100], spectrum[2:100],
     xlabel="Частота, циклов/сутки", ylabel="Амплитуда",
     title="Спектр энергопотребления",
     linewidth=1.5, legend=false, size=(700, 400))
vline!([1.0], linestyle=:dash, label="Суточный цикл")
vline!([1/7], linestyle=:dot, label="Недельный цикл")
Out[0]:

Сохранение результатов анализа

Сохраним обработанный DataFrame со всеми добавленными признаками (сезон, час, уровень загрузки, кластер) в новый CSV-файл.

In [ ]:
CSV.write("Новые_данные.csv", df)
println("Столбцы: ", names(df))
Столбцы: ["date", "Usage_kWh", "Lagging_Current_Reactive_Power_kVarh", "Leading_Current_Reactive_Power_kVarh", "CO2_tCO2_", "Lagging_Current_Power_Factor", "Leading_Current_Power_Factor", "DateTime", "Temperature_C", "Humidity_Pct", "Season", "Hour", "LoadLevel", "Cluster", "DayOfWeek", "IsWeekend"]

Заключение

Мы провели сквозной исследовательский анализ телеметрических данных сталелитейного предприятия, пройдя путь от исходной таблицы до содержательных инженерных выводов. Данные содержат богатую внутреннюю структуру, и мы научились извлекать её последовательно.

Корреляционный анализ выявил сильнейшую связь между энергопотреблением и выбросами CO₂, что позволяет косвенно контролировать экологию по данным счётчиков. Анализ по сезонам показал рост потребления зимой из-за отопительной нагрузки. Суточный профиль выявил двухпиковый режим с утренним подъёмом и вечерним спадом. Разбиение на уровни загрузки дало три равномерные группы наблюдений.

Метод главных компонент показал структуру данных в двумерном пространстве, а кластеризация выделила три устойчивых режима: высокой загрузки, низкой загрузки с плохой эффективностью и низкой загрузки с отличной эффективностью. Спектральный анализ подтвердил суточный и недельный циклы, а также двухсменный график. Гистограмма коэффициента мощности с двумя пиками дополнила картину.

Все выводы получены без сложных моделей машинного обучения, только статистикой и визуализацией. Полученные закономерности могут стать основой для выбора признаков, постановки задач прогнозирования и разработки систем мониторинга.