Представь, пожалуйста, что ты программист и тебе необходимо помочь мне решить проблему. У меня есть код, который предназначен для анализа данных выгрузок:
import os
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import tkinter as tk
from tkinter import filedialog
import plotly.express as px
import plotly.offline as po
from plotly.subplots import make_subplots
import plotly.graph_objs as go


class DataSynchronizer:
    def __init__(self, folder_path, export_type, start_time, end_time, time_step):
        self.folder_path = folder_path
        self.export_type = export_type
        self.start_time = start_time
        self.end_time = end_time
        self.time_step = time_step
        self.df = None
        self.synchronized_df = None

    def read_data_files(self):
        xlsx_files = [
            os.path.join(self.folder_path, f)
            for f in os.listdir(self.folder_path)
            if f.endswith('.xlsx') and not f.startswith('~$')
        ]
        df_list = []
        for file in xlsx_files:
            try:
                temp_df = pd.read_excel(file, header=0)
                df_list.append(temp_df)
            except Exception as e:
                print(f"Ошибка при чтении файла {file}: {e}")
        if df_list:
            self.df = pd.concat(df_list, ignore_index=True)
            print("Данные успешно считаны и объединены.")
        else:
            print("Не найдено файлов для обработки или возникли ошибки при чтении файлов.")
            self.df = pd.DataFrame()

    def process_data(self):
        # Обработка данных в соответствии с типом выгрузки
        if self.export_type == '1':
            self.process_type1()
        elif self.export_type == '2':
            self.process_type2()
        elif self.export_type == '3':
            self.process_type3()
        elif self.export_type == '4':
            self.process_type4()
        else:
            print("Неверный тип выгрузки данных.")
            self.df = pd.DataFrame()

    def process_type1(self):
        # Логика обработки для типа 1 (Тип Приморская ТЭЦ)
        if self.df is None or self.df.empty:
            print("Нет данных для обработки.")
            return

        # Создаем временной ряд на основе пользовательского ввода
        time_range = pd.date_range(start=self.start_time, end=self.end_time, freq=self.time_step)
        self.synchronized_df = pd.DataFrame(index=time_range)
        self.synchronized_df.index.name = 'time'  # Устанавливаем имя индекса для последующего соединения

        # Проходим по каждой паре столбцов (предполагаем, что это столбцы Time и ValueY)
        for i in range(0, len(self.df.columns), 2):
            time_col = self.df.columns[i]
            value_col = self.df.columns[i + 1] if i + 1 < len(self.df.columns) else None

            if value_col is None:
                print(f"Нет соответствующего столбца значений для {time_col}. Пропускаем.")
                continue

            try:
                temp_df = pd.DataFrame({
                    'time': pd.to_datetime(self.df[time_col], format='%d.%m.%Y %H:%M', errors='coerce'),
                    'value': pd.to_numeric(self.df[value_col], errors='coerce')
                }).dropna(subset=['time'])
            except Exception as e:
                print(f"Ошибка при обработке столбцов {time_col} и {value_col}: {e}")
                continue

            # Сортируем по времени
            temp_df = temp_df.sort_values('time')

            # Округляем временные метки до ближайшего временного шага
            temp_df['time'] = temp_df['time'].dt.round(self.time_step)

            # Удаляем дубликаты после округления
            temp_df = temp_df.drop_duplicates(subset='time', keep='first')

            # Выполняем объединение с использованием merge_asof
            merged_df = pd.merge_asof(
                self.synchronized_df.reset_index(),
                temp_df,
                on='time',
                direction='nearest',
                tolerance=pd.Timedelta(self.time_step)
            ).set_index('time')

            self.synchronized_df[value_col] = merged_df['value']

            print(f"Обработан столбец '{value_col}'.")

        # После обработки всех столбцов удаляем дубликаты столбцов, если они появились
        self.synchronized_df = self.synchronized_df.loc[~self.synchronized_df.index.duplicated(keep='first')]

    def process_type2(self):
        # Заглушка для обработки типа 2
        # Здесь можно добавить специфическую обработку для типа 2
        print("Обработка данных для Типа 2 пока не реализована.")
        # Пока просто копируем исходные данные
        self.synchronized_df = self.df

    def process_type3(self):
        # Заглушка для обработки типа 3
        print("Обработка данных для Типа 3 пока не реализована.")
        self.synchronized_df = self.df

    def process_type4(self):
        # Заглушка для обработки типа 4
        print("Обработка данных для Типа 4 пока не реализована.")
        self.synchronized_df = self.df

    # Метод synchronize_data больше не нужен, т.к. синхронизация происходит внутри process_type1

class SummaryTableGenerator:
    def __init__(self, synchronized_df):
        self.synchronized_df = synchronized_df
        self.summary_df = None

    def generate_summary_table(self):
        summary_data = []
        for col in self.synchronized_df.columns:
            col_data = self.synchronized_df[col].dropna()

            if col_data.empty:
                continue

            avg_value = col_data.mean()
            max_value = col_data.max()
            min_value = col_data.min()
            percent_deviation = ((max_value / min_value - 1)* 100) if min_value != 0 else None
            start_value = col_data.iloc[0]
            end_value = col_data.iloc[-1]
            measurement_type = self.determine_measurement_type(col)

            summary_data.append({
                'Параметр': col,
                'Среднее': avg_value,
                'Max': max_value,
                'Min': min_value,
                '% отклонения': percent_deviation,
                'Начальное значение': start_value,
                'Конечное значение': end_value,
                'Тип параметра': measurement_type
            })

        self.summary_df = pd.DataFrame(summary_data)

    def determine_measurement_type(self, parameter_name):
        measurement_types = {
            'Температура': ['температура', 't ', ' t', 'temp', 'temperature'],
            'Давление': ['давление', 'p ', ' p', 'pressure'],
            'Расход': ['расход', 'flow'],
            'Уровень': ['уровень', 'level'],
            'Мощность': ['мощность', 'power'],
            # Добавьте дополнительные типы при необходимости
        }

        for m_type, keywords in measurement_types.items():
            for keyword in keywords:
                if keyword.lower() in parameter_name.lower():
                    return m_type

        return 'Неизвестно'

class PlotGenerator:
    def __init__(self, synchronized_df, save_path):
        self.synchronized_df = synchronized_df
        self.save_path = save_path

    def generate_plots(self):
        print("Генерация графиков...")
        num_params = len(self.synchronized_df.columns)
        fig = make_subplots(rows=num_params, cols=1, shared_xaxes=True, vertical_spacing=0.02)

        for i, col in enumerate(self.synchronized_df.columns, 1):
            fig.add_trace(
                go.Scatter(x=self.synchronized_df.index, y=self.synchronized_df[col], name=col),
                row=i, col=1
            )

        fig.update_layout(height=300 * num_params, showlegend=False)
        print("Сохранение графиков в HTML файл...")
        po.plot(fig, filename=self.save_path, auto_open=False)

class Application:
    def __init__(self):
        self.folder_path = ''
        self.export_type = ''
        self.start_time = None
        self.end_time = None
        self.time_step = ''
        self.output_format = ''
        self.save_path = ''
        self.data_synchronizer = None
        self.summary_generator = None

    def run(self):
        self.select_data_folder()
        self.select_export_type()
        self.input_time_interval()
        self.input_time_step()

        self.data_synchronizer = DataSynchronizer(
            self.folder_path, self.export_type, self.start_time, self.end_time, self.time_step
        )

        self.data_synchronizer.read_data_files()
        self.data_synchronizer.process_data()

        if self.data_synchronizer.synchronized_df is not None and not self.data_synchronizer.synchronized_df.empty:
            self.summary_generator = SummaryTableGenerator(self.data_synchronizer.synchronized_df)
            self.summary_generator.generate_summary_table()

            # Вывод размера данных
            print(f"Размер synchronized_df: {self.data_synchronizer.synchronized_df.shape}")
            print(f"Размер summary_df: {self.summary_generator.summary_df.shape}")

            self.choose_output_format()
            self.choose_save_location()
            self.save_output()
            print("Обработка завершена успешно.")
        else:
            print("Синхронизация данных не выполнена или нет данных для сохранения.")

    def select_data_folder(self):
        root = tk.Tk()
        root.withdraw()
        self.folder_path = filedialog.askdirectory(title='Выберите папку с данными')
        if not self.folder_path:
            print("Папка не выбрана. Завершение работы.")
            exit()
        print(f"Выбранная папка: {self.folder_path}")

    def select_export_type(self):
        print("Выберите тип выгрузки данных:")
        print("1 - Тип Приморская ТЭЦ")
        print("2 - Тип 2")
        print("3 - Тип 3")
        print("4 - Тип 4")

        export_type = input("Введите номер, соответствующий типу выгрузки: ")
        if export_type in ['1', '2', '3', '4']:
            self.export_type = export_type
        else:
            print("Неверный тип выгрузки данных. Завершение работы.")
            exit()

    def input_time_interval(self):
        start_time_str = input("Введите начальное время (например, '23.10.2024 09:00'): ")
        end_time_str = input("Введите конечное время (например, '23.10.2024 16:00'): ")

        try:
            self.start_time = datetime.strptime(start_time_str, '%d.%m.%Y %H:%M')
            self.end_time = datetime.strptime(end_time_str, '%d.%m.%Y %H:%M')
        except ValueError:
            print("Неверный формат даты и времени. Завершение работы.")
            exit()

    def input_time_step(self):
        time_step = input("Введите временной шаг синхронизации (например, '1min', '5min', '1H'): ")
        # Проверяем корректность временного шага
        try:
            pd.date_range(start=self.start_time, end=self.end_time, freq=time_step)
            self.time_step = time_step
        except ValueError as e:
            print(f"Неверный временной шаг: {e}. Завершение работы.")
            exit()

    def choose_output_format(self):
        print("Выберите формат сохранения:")
        print("1 - Excel")
        print("2 - CSV")
        print("3 - HTML (интерактивные графики)")

        output_format = input("Введите номер, соответствующий формату сохранения: ")

        format_mapping = {'1': 'excel', '2': 'csv', '3': 'html'}

        if output_format in format_mapping:
            self.output_format = format_mapping[output_format]
        else:
            print("Неверный формат сохранения. Завершение работы.")
            exit()

    def choose_save_location(self):
        root = tk.Tk()
        root.withdraw()
        extensions = {'excel': '.xlsx', 'csv': '.csv', 'html': '.html'}
        save_extension = extensions.get(self.output_format, '.csv')

        self.save_path = filedialog.asksaveasfilename(title='Сохранить файл как', defaultextension=save_extension)
        if not self.save_path:
            print("Место сохранения не выбрано. Завершение работы.")
            exit()
        print(f"Файл будет сохранен как: {self.save_path}")

    def save_output(self):
        if self.output_format == 'excel':
            self.save_to_excel()
        elif self.output_format == 'csv':
            self.save_to_csv()
        elif self.output_format == 'html':
            self.save_to_html()

    def save_to_excel(self):
        print("Начало сохранения в Excel...")
        with pd.ExcelWriter(self.save_path) as writer:
            print("Сохранение синхронизированных данных на Лист1...")
            self.data_synchronizer.synchronized_df.to_excel(writer, sheet_name='Лист1')
            print("Сохранение сводной таблицы на Лист2...")
            if self.summary_generator.summary_df is not None:
                self.summary_generator.summary_df.to_excel(writer, sheet_name='Лист2', index=False)
        print("Данные успешно сохранены в Excel файл.")

    def save_to_csv(self):
        self.data_synchronizer.synchronized_df.to_csv(self.save_path, encoding='utf-8')
        summary_path = os.path.splitext(self.save_path)[0] + '_summary.csv'
        if self.summary_generator.summary_df is not None:
            self.summary_generator.summary_df.to_csv(summary_path, index=False, encoding='utf-8')
        print("Данные сохранены в CSV файлы.")

    def save_to_html(self):
        print("Начало генерации интерактивных графиков...")
        plot_generator = PlotGenerator(self.data_synchronizer.synchronized_df, self.save_path)
        plot_generator.generate_plots()
        print("Интерактивные графики успешно сохранены в HTML файл.")

if __name__ == "__main__":
    app = Application()
    app.run()
я бы хотел добавить обработку пропущенных значений в столбцах результирующего датасета, например методом ближайших соседей.
А также при попытке выгрузить результаты в html появилась ошибка:
C:\Users\lrshlyogin\PycharmProjects\FastApiTest\.venv\Scripts\python.exe C:\Users\lrshlyogin\PycharmProjects\FastApiTest\app\main.py 
Выбранная папка: C:/Users/lrshlyogin/Desktop/Выгрузки
Выберите тип выгрузки данных:
1 - Тип Приморская ТЭЦ
2 - Тип 2
3 - Тип 3
4 - Тип 4
Введите номер, соответствующий типу выгрузки: 1
Введите начальное время (например, '23.10.2024 09:00'): 12.11.2024 11:30
Введите конечное время (например, '23.10.2024 16:00'): 12.11.2024 13:30
Введите временной шаг синхронизации (например, '1min', '5min', '1H'): 1min
Данные успешно считаны и объединены.
C:\Users\lrshlyogin\PycharmProjects\FastApiTest\app\main.py:37: FutureWarning:

The behavior of DataFrame concatenation with empty or all-NA entries is deprecated. In a future version, this will no longer exclude empty or all-NA columns when determining the result dtypes. To retain the old behavior, exclude the relevant entries before the concat operation.

Обработан столбец 'Расход пара за котлом, датчик-2 ValueY'.
Обработан столбец 'Расход пара за котлом, датчик-1 ValueY'.
Обработан столбец 'Температура пара за котлом ValueY'.
Обработан столбец 'Температура пара перед стопорным клапаном ValueY'.
Обработан столбец 'P ПАРА ПЕРЕД СК ValueY'.
Обработан столбец 'Г-10 P ValueY'.
Обработан столбец 'Реактивная мощность блок 1 ValueY'.
Обработан столбец 'Г-10 Ia ValueY'.
Обработан столбец 'Г-10 Ib ValueY'.
Обработан столбец 'Г-10 Ic ValueY'.
Обработан столбец 'Уровень в деаэраторе, датчик-1 ValueY'.
Обработан столбец 'Уровень в деаэраторе, датчик-2 ValueY'.
Обработан столбец 'Уровень в деаэраторе, датчик-3 ValueY'.
Обработан столбец 'Уровень в конденсаторе ValueY'.
Обработан столбец 'Уровень в конденсатосбонике конденсатора, датчик-1 ValueY'.
Обработан столбец 'Уровень в конденсатосбонике конденсатора, датчик-2 ValueY'.
Обработан столбец 'Уровень в конденсатосбонике конденсатора, датчик-3 ValueY'.
Обработан столбец 'Температура пара к ПНД-3 ValueY'.
Обработан столбец 'Температура пара к ПНД-2 ValueY'.
Обработан столбец 'Температура пара к ПНД-1 ValueY'.
Обработан столбец 'Давление конденсата после конденсатных насосов ValueY'.
Обработан столбец 'Давление конденсата на напоре КЭН-В ValueY'.
Обработан столбец 'Давление конденсата на напоре КЭН-Б ValueY'.
Обработан столбец 'Давление конденсата на напоре КЭН-А ValueY'.
Обработан столбец 'Давление питательной воды к котлу ValueY'.
Обработан столбец 'Давление питательной воды на котел за РК ValueY'.
Обработан столбец 'Давление питательной воды на напоре ПЭН-А ValueY'.
Обработан столбец 'Давление питательной воды напоре ПЭН-Б ValueY'.
Обработан столбец 'Расход на впрыск в пароохладитель пусковой слева ValueY'.
Обработан столбец 'Расход на впрыск в пароохладитель пусковой справа ValueY'.
Обработан столбец 'Расход циркуляционной воды через основной пучок конденсатора-2 ValueY'.
Обработан столбец 'Расход циркуляционной воды через основной пучок конденсатора-1 ValueY'.
Обработан столбец 'Расход непрерывной продувки ValueY'.
Обработан столбец 'Давление пара к ПНД-3 ValueY'.
Обработан столбец 'Давление пара к ПНД-2 ValueY'.
Обработан столбец 'Давление пара к ПНД-1 ValueY'.
Обработан столбец 'Температура основного конденсата на входе в ПНД-1 ValueY'.
Обработан столбец 'Температура основного конденсата на выходе из ПНД-1 ValueY'.
Обработан столбец 'Температура основного конденсата на входе в ПНД-2 ValueY'.
Обработан столбец 'Температура основного конденсата на выходе из ПНД-2 ValueY'.
Обработан столбец 'Температура основного конденсата на входе в ПНД-3 ValueY'.
Обработан столбец 'Температура основного конденсата на выходе из ПНД-3 ValueY'.
Обработан столбец 'Температура основного конденсата перед деаэратором-- ValueY'.
Обработан столбец 'Температура конденсата на выходе из конденсатора ValueY'.
Обработан столбец 'Температура конденсата греющего пара на выходе из ПНД-3 ValueY'.
Обработан столбец 'Температура конденсата греющего пара на выходе из ПНД-2 ValueY'.
Обработан столбец 'Температура конденсата греющего пара на выходе из ПНД-1 ValueY'.
Обработан столбец 'Давление на напоре КЭН ПНД-А ValueY'.
Обработан столбец 'Давление пара в деаэраторе ValueY'.
Обработан столбец 'Давление на напоре КЭН ПНД-Б ValueY'.
Обработан столбец 'Расход основного конденсата на выходе из системы регенерации НД ValueY'.
Обработан столбец 'Расход питательной воды на котел, датчик-1 ValueY'.
Обработан столбец 'Расход питательной воды на котел, датчик-2 ValueY'.
Обработан столбец 'Температура питательной воды за деаэратором ValueY'.
Обработан столбец 'Температура питательной воды на напоре ПЭН-А ValueY'.
Обработан столбец 'Температура питательной воды на напоре ПЭН-Б ValueY'.
Обработан столбец 'Расход питательной воды от ПЭН-Б ValueY'.
Обработан столбец 'Температура питательной воды за ПВД-5 ValueY'.
Обработан столбец 'Температура питательной воды на котел за РК ValueY'.
Обработан столбец 'Температура питательной воды за группой ПВД ValueY'.
Обработан столбец 'Температура конденсата на сливе из ПВД-5 ValueY'.
Обработан столбец 'Температура конденсата на сливе из ПВД-4 ValueY'.
Обработан столбец 'Расход основного добавка в конденсатор ValueY'.
Обработан столбец 'Расход аварийного добавка в расширитель дренажей блока ValueY'.
Обработан столбец 'Температура циркуляционной воды на основной пучок конденсатора-1 ValueY'.
Обработан столбец 'Температура циркуляционной воды на основной пучок конденсатора-2 ValueY'.
Обработан столбец 'Температура циркуляционной воды из основного пучка конденсатора-2 ValueY'.
Обработан столбец 'Температура циркуляционной воды из основного пучка конденсатора-1 ValueY'.
Обработан столбец 'Давление в конденсаторе, датчик-5 ValueY'.
Обработан столбец 'Давление в конденсаторе, датчик-3 ValueY'.
Обработан столбец 'Давление в конденсаторе, датчик-4 ValueY'.
Обработан столбец 'Давление в конденсаторе, датчик-6 ValueY'.
Обработан столбец 'Расход питательной воды от ПЭН-А ValueY'.
Обработан столбец 'Температура питательной воды к котлу ValueY'.
Нет соответствующего столбца значений для time. Пропускаем.
Размер synchronized_df: (121, 74)
Размер summary_df: (73, 8)
Выберите формат сохранения:
1 - Excel
2 - CSV
3 - HTML (интерактивные графики)
Введите номер, соответствующий формату сохранения: 3
Файл будет сохранен как: C:/Users/lrshlyogin/Desktop/Выгрузки/test_html.html
Traceback (most recent call last):
  File "C:\Users\lrshlyogin\PycharmProjects\FastApiTest\app\main.py", line 346, in <module>
    app.run()
  File "C:\Users\lrshlyogin\PycharmProjects\FastApiTest\app\main.py", line 236, in run
    self.save_output()
  File "C:\Users\lrshlyogin\PycharmProjects\FastApiTest\app\main.py", line 319, in save_output
    self.save_to_html()
  File "C:\Users\lrshlyogin\PycharmProjects\FastApiTest\app\main.py", line 341, in save_to_html
    plot_generator.generate_plots()
  File "C:\Users\lrshlyogin\PycharmProjects\FastApiTest\app\main.py", line 189, in generate_plots
    fig = make_subplots(rows=num_params, cols=1, shared_xaxes=True, vertical_spacing=0.02)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\lrshlyogin\PycharmProjects\FastApiTest\.venv\Lib\site-packages\plotly\subplots.py", line 305, in make_subplots
    return _sub.make_subplots(
           ^^^^^^^^^^^^^^^^^^^
  File "C:\Users\lrshlyogin\PycharmProjects\FastApiTest\.venv\Lib\site-packages\plotly\_subplots.py", line 565, in make_subplots
    _check_hv_spacing(rows, vertical_spacing, "Vertical", "rows", "rows")
  File "C:\Users\lrshlyogin\PycharmProjects\FastApiTest\.venv\Lib\site-packages\plotly\_subplots.py", line 538, in _check_hv_spacing
    raise ValueError(
ValueError: Vertical spacing cannot be greater than (1 / (rows - 1)) = 0.013699.
The resulting plot would have 74 rows (rows=74).
Начало генерации интерактивных графиков...
Генерация графиков...

Process finished with exit code 1