Here is python code for Time Series data editor that implements EmbedDimension from pyEDM to estimate optimal dimension using simplex projection. It has several errors in it, including code logic errros, specifically related to data handling and EmbedDimension implementatation and integration with the rest of the code. Identify each of the errors and fix it. "import sys
import pandas as pd
import numpy as np
from datetime import datetime
from PySide6 import QtCore, QtGui, QtWidgets
from PySide6.QtCore import Qt, QAbstractTableModel, QModelIndex, QSortFilterProxyModel, QDate, QSize
from PySide6.QtGui import QAction, QIcon
from PySide6.QtWidgets import (
    QApplication, QMainWindow, QTableView, QSplitter, QWidget, QVBoxLayout,
    QFileDialog, QTabWidget, QToolBar, QStyledItemDelegate, QMessageBox, QMenu,
    QStatusBar, QLabel, QHBoxLayout, QDialog, QFormLayout, QLineEdit, QComboBox,
    QDateEdit, QSpinBox, QPushButton, QDoubleSpinBox, QGroupBox
)

# Import pyEDM and matplotlib
import pyEDM
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure


# Custom Model to interface pandas DataFrame with QTableView
class PandasModel(QAbstractTableModel):
    dataChangedSignal = QtCore.Signal()

    def __init__(self, df=pd.DataFrame(), parent=None):
        super().__init__(parent)
        self._df = df.copy()

    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if role != Qt.DisplayRole:
            return None
        if orientation == Qt.Horizontal:
            try:
                return str(self._df.columns[section])
            except (IndexError,):
                return None
        elif orientation == Qt.Vertical:
            try:
                return str(self._df.index[section])
            except (IndexError,):
                return None

    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid():
            return None
        if role in (Qt.DisplayRole, Qt.EditRole):
            value = self._df.iat[index.row(), index.column()]
            return str(value)
        return None

    def setData(self, index, value, role=Qt.EditRole):
        if index.isValid():
            try:
                self._df.iat[index.row(), index.column()] = value
                self.dataChanged.emit(index, index, [role])
                self.dataChangedSignal.emit()
                return True
            except Exception as e:
                QMessageBox.warning(None, "Error", f"Failed to set data: {e}")
        return False

    def rowCount(self, parent=QModelIndex()):
        return len(self._df.index)

    def columnCount(self, parent=QModelIndex()):
        return len(self._df.columns)

    def flags(self, index):
        return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable

    def load_data(self, df):
        self.beginResetModel()
        self._df = df.copy()
        self.endResetModel()
        self.dataChangedSignal.emit()

    def get_dataframe(self):
        return self._df.copy()


# Custom Delegate for different data types
class CustomDelegate(QStyledItemDelegate):
    def __init__(self, parent=None):
        super().__init__(parent)

    def createEditor(self, parent, option, index):
        # Implement different editors based on column data type if needed
        return super().createEditor(parent, option, index)


# Variable Model (modified to remove Label)
class VariableModel(QAbstractTableModel):
    def __init__(self, df=pd.DataFrame(), parent=None):
        super().__init__(parent)
        self._df = df.copy()

    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if role != Qt.DisplayRole:
            return None
        if orientation == Qt.Horizontal:
            try:
                return str(self._df.columns[section])
            except IndexError:
                return None
        elif orientation == Qt.Vertical:
            try:
                return str(self._df.index[section])
            except IndexError:
                return None

    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid():
            return None
        if role in (Qt.DisplayRole, Qt.EditRole):
            value = self._df.iat[index.row(), index.column()]
            return str(value)
        return None

    def setData(self, index, value, role=Qt.EditRole):
        # No Label editing anymore
        return False

    def rowCount(self, parent=QModelIndex()):
        return len(self._df.index)

    def columnCount(self, parent=QModelIndex()):
        return len(self._df.columns)

    def flags(self, index):
        # No Label editing anymore
        return Qt.ItemIsSelectable | Qt.ItemIsEnabled

    def load_data(self, df):
        self.beginResetModel()
        self._df = df.copy()
        self.endResetModel()


# Data Viewer Class
class DataViewer(QTableView):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.model = PandasModel()
        self.setModel(self.model)
        self.setSelectionBehavior(QTableView.SelectItems)
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.open_context_menu)
        self.setItemDelegate(CustomDelegate(self))
        # Connect data changes to emit signals
        self.model.dataChangedSignal.connect(self.on_data_changed)

    def on_data_changed(self):
        # Emit a custom signal or handle updates if needed
        pass

    def load_csv(self, file_path):
        try:
            df = pd.read_csv(file_path, parse_dates=True)
            self.model.load_data(df)
        except Exception as e:
            QMessageBox.critical(self, "Error", f"Failed to load CSV file:\n{e}")

    def load_excel(self, file_path):
        try:
            df = pd.read_excel(file_path, parse_dates=True)
            self.model.load_data(df)
        except Exception as e:
            QMessageBox.critical(self, "Error", f"Failed to load Excel file:\n{e}")

    def save_csv(self, file_path):
        try:
            self.model.get_dataframe().to_csv(file_path, index=False)
        except Exception as e:
            QMessageBox.critical(self, "Error", f"Failed to save CSV file:\n{e}")

    def save_excel(self, file_path):
        try:
            self.model.get_dataframe().to_excel(file_path, index=False)
        except Exception as e:
            QMessageBox.critical(self, "Error", f"Failed to save Excel file:\n{e}")

    def open_context_menu(self, position):
        menu = QMenu()
        add_row_action = menu.addAction("Add Row")
        remove_row_action = menu.addAction("Remove Row")
        add_column_action = menu.addAction("Add Column")
        remove_column_action = menu.addAction("Remove Column")

        action = menu.exec_(self.viewport().mapToGlobal(position))
        if action == add_row_action:
            self.add_row()
        elif action == remove_row_action:
            self.remove_row()
        elif action == add_column_action:
            self.add_column()
        elif action == remove_column_action:
            self.remove_column()

    def add_row(self):
        df = self.model.get_dataframe()
        new_row = pd.Series({col: "" for col in df.columns})
        df = df.append(new_row, ignore_index=True)
        self.model.load_data(df)

    def remove_row(self):
        index = self.currentIndex()
        if not index.isValid():
            QMessageBox.warning(self, "Remove Row", "No row selected.")
            return
        df = self.model.get_dataframe()
        row_number = index.row()
        if 0 <= row_number < len(df):
            df = df.drop(index=row_number).reset_index(drop=True)
            self.model.load_data(df)
            QMessageBox.information(self, "Remove Row", f"Row {row_number} removed.")
        else:
            QMessageBox.warning(self, "Remove Row", "Invalid row selected.")

    def add_column(self):
        df = self.model.get_dataframe()
        new_col_name, ok = QtWidgets.QInputDialog.getText(self, "Add Column", "Column Name:")
        if ok and new_col_name:
            if new_col_name in df.columns:
                QMessageBox.warning(self, "Add Column", "Column already exists.")
                return
            df[new_col_name] = ""
            self.model.load_data(df)

    def remove_column(self):
        index = self.currentIndex()
        if not index.isValid():
            QMessageBox.warning(self, "Remove Column", "No column selected.")
            return
        col = self.model._df.columns[index.column()]
        confirmation = QMessageBox.question(
            self, "Remove Column", f"Are you sure you want to remove column '{col}'?",
            QMessageBox.Yes | QMessageBox.No
        )
        if confirmation == QMessageBox.Yes:
            df = self.model.get_dataframe()
            df = df.drop(columns=[col])
            self.model.load_data(df)
            QMessageBox.information(self, "Remove Column", f"Column '{col}' removed.")


# Variable Viewer (modified to remove Label)
class VariableViewer(QTableView):
    def __init__(self, data_viewer, parent=None):
        super().__init__(parent)
        self.data_viewer = data_viewer
        self.model = VariableModel(pd.DataFrame(columns=["Name", "Type"]))  # Removed Label
        self.setModel(self.model)
        self.setEditTriggers(QtWidgets.QAbstractItemView.DoubleClicked | QtWidgets.QAbstractItemView.SelectedClicked)
        self.data_viewer.model.modelReset.connect(self.update_variables)

    def update_variables(self):
        df = self.data_viewer.model.get_dataframe()
        var_info = {
            "Name": df.columns,
            "Type": df.dtypes.astype(str),
            # "Label": [""] * len(df.columns)  # Removed Label
        }
        var_df = pd.DataFrame(var_info)
        self.model.load_data(var_df)


# Dialog for Generating Random Time Series Data
class RandomDataDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Generate Random Time Series Data")
        self.setModal(True)
        self.init_ui()

    def init_ui(self):
        layout = QFormLayout()

        # Start Date
        self.start_date_edit = QDateEdit(self)
        self.start_date_edit.setCalendarPopup(True)
        self.start_date_edit.setDate(QDate.currentDate().addYears(-1))
        layout.addRow("Start Date:", self.start_date_edit)

        # End Date
        self.end_date_edit = QDateEdit(self)
        self.end_date_edit.setCalendarPopup(True)
        self.end_date_edit.setDate(QDate.currentDate())
        layout.addRow("End Date:", self.end_date_edit)

        # Frequency
        self.frequency_combo = QComboBox(self)
        self.frequency_combo.addItems(["Daily", "Weekly", "Monthly", "Quarterly", "Yearly"])
        layout.addRow("Frequency:", self.frequency_combo)

        # Number of Variables
        self.num_vars_spin = QSpinBox(self)
        self.num_vars_spin.setRange(1, 100)
        self.num_vars_spin.setValue(5)
        layout.addRow("Number of Variables:", self.num_vars_spin)

        # Variable Name Prefix
        self.var_prefix_edit = QLineEdit(self)
        self.var_prefix_edit.setText("Variable")
        layout.addRow("Variable Name Prefix:", self.var_prefix_edit)

        # Buttons
        button_layout = QHBoxLayout()
        self.generate_button = QPushButton("Generate", self)
        self.cancel_button = QPushButton("Cancel", self)
        button_layout.addWidget(self.generate_button)
        button_layout.addWidget(self.cancel_button)
        layout.addRow(button_layout)

        self.setLayout(layout)

        # Connect buttons
        self.generate_button.clicked.connect(self.accept)
        self.cancel_button.clicked.connect(self.reject)

    def get_parameters(self):
        start_date = self.start_date_edit.date().toPython()
        end_date = self.end_date_edit.date().toPython()
        frequency = self.frequency_combo.currentText()
        num_vars = self.num_vars_spin.value()
        var_prefix = self.var_prefix_edit.text()
        return start_date, end_date, frequency, num_vars, var_prefix


# Ribbon UI Implementation
class RibbonUI(QToolBar):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setMovable(False)
        self.setIconSize(QSize(32, 32))
        self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
        self.init_ui()

    def init_ui(self):
        self.init_file_actions()
        self.addSeparator()
        self.init_edit_actions()
        self.addSeparator()
        self.init_data_actions()

    def init_file_actions(self):
        self.new_action = self.addAction(QIcon.fromTheme("document-new"), "New")
        self.open_action = self.addAction(QIcon.fromTheme("document-open"), "Open")
        self.save_action = self.addAction(QIcon.fromTheme("document-save"), "Save")
        self.save_as_action = self.addAction(QIcon.fromTheme("document-save-as"), "Save As")
        self.generate_random_action = self.addAction(QIcon.fromTheme("document-new"), "Generate\nRandom Data")

    def init_edit_actions(self):
        self.cut_action = self.addAction(QIcon.fromTheme("edit-cut"), "Cut")
        self.copy_action = self.addAction(QIcon.fromTheme("edit-copy"), "Copy")
        self.paste_action = self.addAction(QIcon.fromTheme("edit-paste"), "Paste")
        self.undo_action = self.addAction(QIcon.fromTheme("edit-undo"), "Undo")
        self.redo_action = self.addAction(QIcon.fromTheme("edit-redo"), "Redo")

    def init_data_actions(self):
        self.sort_action = self.addAction(QIcon.fromTheme("view-sort-ascending"), "Sort")
        self.filter_action = self.addAction(QIcon.fromTheme("view-filter"), "Filter")
        self.compute_action = self.addAction(QIcon.fromTheme("accessories-calculator"), "Compute")
        self.recode_action = self.addAction(QIcon.fromTheme("edit-find-replace"), "Recode")


# Embedding Dimension Analysis Class
class EmbeddingDimensionAnalysis:
    def __init__(self, time_series_data, target_column, e_min, e_max, tau, lib, pred):
        self.time_series_data = time_series_data
        self.target_column = target_column
        self.emin = e_min
        self.emax = e_max
        self.tau = tau
        self.lib = lib  # Store lib as string
        self.pred = pred  # Store pred as string
        self.optimal_E = None
        self.rhos = []

    def run_analysis(self):
        try:
            # Perform EmbedDimension analysis using pyEDM
            results = pyEDM.EmbedDimension(
                dataFrame=self.time_series_data,
                target=self.target_column,
                E=range(self.emin, self.emax + 1),
                tau=self.tau,
                lib=self.lib,  # Pass lib as string
                pred=self.pred,  # Pass pred as string
                columns=self.target_column,
                showPlot=False
            )

            # Extract rho values
            for index, row in results.iterrows():
                self.rhos.append(row["rho"])

            # Determine the optimal E (E with maximum rho)
            self.optimal_E = results.loc[results["rho"].idxmax()]["E"]
        except Exception as e:
            raise RuntimeError(f"Failed to perform embedding dimension analysis: {e}")

    def generate_plot(self):
        fig = Figure(figsize=(6, 4))
        ax = fig.add_subplot(111)
        ax.plot(range(self.emin, self.emax + 1), self.rhos, marker='o', linestyle='-')
        ax.set_xlabel("Embedding Dimension (E)")
        ax.set_ylabel("Forecast Skill (rho)")
        ax.set_title("Optimal Embedding Dimension Analysis")
        ax.grid(True)

        # Highlight the optimal E
        if self.optimal_E:
            ax.axvline(self.optimal_E, color='red', linestyle='--', label=f'Optimal E = {self.optimal_E}')
            ax.legend()

        fig.tight_layout()
        return fig


# Main Window Class
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Time Series Data Editor with Optimal Embedding Dimension Analysis")
        self.resize(1400, 900)

        self.central_widget = QWidget()
        # self.setCentralWidget(self.central_widget)  # Moved below

        # Setup Ribbon UI
        self.ribbon = RibbonUI(self)
        self.addToolBar(self.ribbon)

        # Setup Splitter with DataViewer and VariableViewer
        self.splitter = QSplitter(Qt.Horizontal)
        self.data_viewer = DataViewer()
        self.variable_viewer = VariableViewer(self.data_viewer)

        self.splitter.addWidget(self.data_viewer)
        self.splitter.addWidget(self.variable_viewer)
        self.splitter.setStretchFactor(0, 3)
        self.splitter.setStretchFactor(1, 1)

        # Setup Tab Widget
        self.tab_widget = QTabWidget()
        self.tab_widget.addTab(self.splitter, "Data")

        # Optimal Embedding Dimension Tab
        self.embedding_tab = QWidget()
        embedding_layout = QVBoxLayout()

        # Parameter Input Group
        param_group = QGroupBox("Embedding Dimension Parameters")
        param_layout = QFormLayout()

        # Target Column Selection
        self.target_column_combo = QComboBox(self)
        self.target_column_combo.setPlaceholderText("Select Column")
        self.update_target_columns()
        param_layout.addRow("Target Column:", self.target_column_combo)

        # Minimum Embedding Dimension (Emin)
        self.emin_spin = QSpinBox(self)
        self.emin_spin.setRange(1, 50)
        self.emin_spin.setValue(1)
        param_layout.addRow("Minimum E (Emin):", self.emin_spin)

        # Maximum Embedding Dimension (Emax)
        self.emax_spin = QSpinBox(self)
        self.emax_spin.setRange(1, 50)
        self.emax_spin.setValue(10)
        param_layout.addRow("Maximum E (Emax):", self.emax_spin)

        # Time Lag (tau)
        self.tau_spin = QSpinBox(self)
        self.tau_spin.setRange(1, 100)
        self.tau_spin.setValue(1)
        param_layout.addRow("Time Lag (tau):", self.tau_spin)

        # Library Indices
        self.lib_edit = QLineEdit(self)
        self.lib_edit.setPlaceholderText("e.g., \"1 100\" or \"*\"")
        self.lib_edit.setText("1 75%")  # Default: first 75% as library
        param_layout.addRow("Library Indices (lib):", self.lib_edit)

        # Prediction Indices
        self.pred_edit = QLineEdit(self)
        self.pred_edit.setPlaceholderText("e.g., \"101 200\" or \"*\"")
        self.pred_edit.setText("75% 100%")  # Default: last 25% as prediction
        param_layout.addRow("Prediction Indices (pred):", self.pred_edit)

        param_group.setLayout(param_layout)
        embedding_layout.addWidget(param_group)

        # Calculate and Plot Button
        self.calculate_button = QPushButton("Calculate Optimal E")
        embedding_layout.addWidget(self.calculate_button)

        # Figure Canvas for Plot
        self.canvas = FigureCanvas(Figure(figsize=(6, 4)))
        embedding_layout.addWidget(self.canvas)

        # Status Label within the Embedding Tab
        self.embedding_status_label = QLabel("")
        embedding_layout.addWidget(self.embedding_status_label)

        self.embedding_tab.setLayout(embedding_layout)
        self.tab_widget.addTab(self.embedding_tab, "Optimal Embedding Dimension")

        # Set Tab Widget as Central Widget
        self.setCentralWidget(self.tab_widget)

        # Setup Status Bar
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)
        self.status_label = QLabel("Ready")
        self.status_bar.addWidget(self.status_label)

        self.current_file = None

        self.connect_actions()

    def connect_actions(self):
        # Connect actions from RibbonUI
        self.ribbon.new_action.triggered.connect(self.new_file)
        self.ribbon.open_action.triggered.connect(self.open_file)
        self.ribbon.save_action.triggered.connect(self.save_file)
        self.ribbon.save_as_action.triggered.connect(self.save_file_as)
        self.ribbon.generate_random_action.triggered.connect(self.generate_random_data)

        self.ribbon.cut_action.triggered.connect(self.cut)
        self.ribbon.copy_action.triggered.connect(self.copy)
        self.ribbon.paste_action.triggered.connect(self.paste)
        self.ribbon.undo_action.triggered.connect(self.undo)
        self.ribbon.redo_action.triggered.connect(self.redo)

        self.ribbon.sort_action.triggered.connect(self.sort_data)
        self.ribbon.filter_action.triggered.connect(self.filter_data)
        self.ribbon.compute_action.triggered.connect(self.compute_data)
        self.ribbon.recode_action.triggered.connect(self.recode_data)

        self.calculate_button.clicked.connect(self.calculate_optimal_e)

        # Update target columns when data changes
        self.data_viewer.model.modelReset.connect(self.update_target_columns)

    def update_target_columns(self):
        df = self.data_viewer.model.get_dataframe()
        self.target_column_combo.clear()
        if not df.empty:
            self.target_column_combo.addItems(df.columns.tolist())

    def new_file(self):
        confirmation = QMessageBox.question(
            self, "New File", "Are you sure you want to create a new file?\nUnsaved changes will be lost.",
            QMessageBox.Yes | QMessageBox.No
        )
        if confirmation == QMessageBox.Yes:
            self.data_viewer.model.load_data(pd.DataFrame())
            self.current_file = None
            self.status_label.setText("New file created.")
            self.embedding_status_label.setText("")

    def open_file(self):
        options = QFileDialog.Options()
        file_name, _ = QFileDialog.getOpenFileName(
            self, "Open Data File", "",
            "CSV Files (*.csv);;Excel Files (*.xlsx *.xls);;All Files (*)",
            options=options)
        if file_name:
            if file_name.endswith('.csv'):
                self.data_viewer.load_csv(file_name)
            elif file_name.endswith(('.xlsx', '.xls')):
                self.data_viewer.load_excel(file_name)
            else:
                QMessageBox.warning(self, "Unsupported Format", "Only CSV and Excel files are supported.")
                return
            self.current_file = file_name
            self.status_label.setText(f"Loaded file: {file_name}")
            self.status_label.setToolTip(f"Loaded file: {file_name}")

    def save_file(self):
        if self.current_file:
            if self.current_file.endswith('.csv'):
                self.data_viewer.save_csv(self.current_file)
            elif self.current_file.endswith(('.xlsx', '.xls')):
                self.data_viewer.save_excel(self.current_file)
            self.status_label.setText(f"Saved file: {self.current_file}")
            self.status_label.setToolTip(f"Saved file: {self.current_file}")
        else:
            self.save_file_as()

    def save_file_as(self):
        options = QFileDialog.Options()
        file_name, _ = QFileDialog.getSaveFileName(
            self, "Save Data File As", "",
            "CSV Files (*.csv);;Excel Files (*.xlsx *.xls);;All Files (*)",
            options=options)
        if file_name:
            if file_name.endswith('.csv'):
                self.data_viewer.save_csv(file_name)
            elif file_name.endswith(('.xlsx', '.xls')):
                self.data_viewer.save_excel(file_name)
            else:
                QMessageBox.warning(self, "Unsupported Format",
                                    "Please use a valid file extension (.csv, .xlsx, .xls).")
                return
            self.current_file = file_name
            self.status_label.setText(f"Saved file as: {file_name}")
            self.status_label.setToolTip(f"Saved file as: {file_name}")

    def generate_random_data(self):
        dialog = RandomDataDialog(self)
        if dialog.exec() == QDialog.Accepted:
            start_date, end_date, frequency, num_vars, var_prefix = dialog.get_parameters()
            try:
                # Map frequency to pandas offset aliases
                freq_map = {
                    "Daily": "D",
                    "Weekly": "W",
                    "Monthly": "M",
                    "Quarterly": "Q",
                    "Yearly": "A"
                }
                pandas_freq = freq_map.get(frequency, "D")

                # Generate date range
                dates = pd.date_range(start=start_date, end=end_date, freq=pandas_freq)
                if len(dates) == 0:
                    QMessageBox.warning(self, "Invalid Date Range",
                                        "No dates generated. Please adjust the date range or frequency.")
                    return

                # Generate random data
                data = {}
                for i in range(1, num_vars + 1):
                    col_name = f"{var_prefix}_{i}"
                    data[col_name] = np.random.randn(len(dates))  # Normally distributed data

                df = pd.DataFrame(data, index=dates)
                df.index.name = "Date"

                # Load data into DataViewer
                self.data_viewer.model.load_data(df)
                self.status_label.setText("Random time series data generated.")
                self.embedding_status_label.setText("")

                # Update target column combo box
                self.update_target_columns()

                # Reset lib and pred edits to default values for new data
                self.lib_edit.setText("1 75%")
                self.pred_edit.setText("75% 100%")
            except Exception as e:
                QMessageBox.critical(self, "Error", f"Failed to generate random data:\n{e}")

    def cut(self):
        self.copy()
        indexes = self.data_viewer.selectionModel().selectedIndexes()
        if indexes:
            rows = sorted(index.row() for index in indexes)
            df = self.data_viewer.model.get_dataframe()
            df = df.drop(index=rows).reset_index(drop=True)
            self.data_viewer.model.load_data(df)
            self.status_label.setText("Cut selected cells.")
            self.embedding_status_label.setText("")
        else:
            QMessageBox.warning(self, "Cut", "No cells selected.")

    def copy(self):
        indexes = self.data_viewer.selectionModel().selectedIndexes()
        if indexes:
            # Sort indexes to iterate properly
            indexes = sorted(indexes, key=lambda x: (x.row(), x.column()))
            rows = sorted(set(index.row() for index in indexes))
            cols = sorted(set(index.column() for index in indexes))
            df = self.data_viewer.model.get_dataframe()
            selection = df.iloc[rows, cols]
            clipboard_text = selection.to_csv(sep='\t', index=False, header=False)
            QtGui.QGuiApplication.clipboard().setText(clipboard_text)
            self.status_label.setText("Copied selected cells.")
            self.embedding_status_label.setText("")
        else:
            QMessageBox.warning(self, "Copy", "No cells selected.")

    def paste(self):
        clipboard = QtGui.QGuiApplication.clipboard().text()
        if not clipboard:
            QMessageBox.warning(self, "Paste", "Clipboard is empty.")
            return
        try:
            # Read clipboard data into DataFrame
            from io import StringIO
            data = pd.read_csv(StringIO(clipboard), sep='\t', header=None)
            df = self.data_viewer.model.get_dataframe()
            selected = self.data_viewer.selectionModel().selectedIndexes()
            if not selected:
                QMessageBox.warning(self, "Paste", "No cell selected to paste into.")
                return
            start_row = selected[0].row()
            start_col = selected[0].column()
            for i, row in data.iterrows():
                for j, value in row.iteritems():
                    target_row = start_row + i
                    target_col = start_col + j
                    if target_row < len(df) and target_col < len(df.columns):
                        df.iat[target_row, target_col] = value
            self.data_viewer.model.load_data(df)
            self.status_label.setText("Pasted data from clipboard.")
            self.embedding_status_label.setText("")
        except Exception as e:
            QMessageBox.warning(self, "Paste Error", f"Failed to paste data:\n{e}")

    def undo(self):
        # Placeholder for undo functionality
        QMessageBox.information(self, "Undo", "Undo operation not implemented yet.")

    def redo(self):
        # Placeholder for redo functionality
        QMessageBox.information(self, "Redo", "Redo operation not implemented yet.")

    def sort_data(self):
        # Placeholder for sort dialog and functionality
        QMessageBox.information(self, "Sort", "Sort operation not implemented yet.")

    def filter_data(self):
        # Placeholder for filter dialog and functionality
        QMessageBox.information(self, "Filter", "Filter operation not implemented yet.")

    def compute_data(self):
        # Placeholder for compute dialog and functionality
        QMessageBox.information(self, "Compute", "Compute operation not implemented yet.")

    def recode_data(self):
        # Placeholder for recode dialog and functionality
        QMessageBox.information(self, "Recode", "Recode operation not implemented yet.")

    def calculate_optimal_e(self):
        df = self.data_viewer.model.get_dataframe()
        if df.empty:
            QMessageBox.warning(self, "No Data", "Please load data first.")
            return

        target_column = self.target_column_combo.currentText()
        if not target_column:
            QMessageBox.warning(self, "No Target Column", "Please select a target column for analysis.")
            return

        # Extract parameters
        try:
            emin = self.emin_spin.value()
            emax = self.emax_spin.value()
            tau = self.tau_spin.value()
            lib = self.lib_edit.text().strip()
            pred = self.pred_edit.text().strip()

            if emin > emax:
                QMessageBox.warning(self, "Invalid Parameters",
                                    "Minimum E (Emin) cannot be greater than Maximum E (Emax).")
                return

            if not lib:
                QMessageBox.warning(self, "Invalid Parameters", "Library indices (lib) cannot be empty.")
                return

            if not pred:
                QMessageBox.warning(self, "Invalid Parameters", "Prediction indices (pred) cannot be empty.")
                return

            # Validate lib and pred formats (moved inside the try block)
            # pyEDM accepts lib and pred in various formats, including "1 75%" and "75% 100%"
            # Here, we'll pass them as strings directly

        except Exception as e:
            QMessageBox.warning(self, "Invalid Parameters", f"Error in input parameters: {e}")
            return

        # Prepare the data
        try:
            # Ensure the target column is numeric
            if not pd.api.types.is_numeric_dtype(df[target_column]):
                QMessageBox.warning(self, "Non-numeric Target", "Selected target column must be numeric.")
                return

            # Reset index to have a simple integer index if necessary
            if not pd.api.types.is_integer_dtype(df.index):
                df_reset = df.reset_index(drop=True)
            else:
                df_reset = df.copy()

            # Include the time index as a column if it's not already there
            if "Time" not in df_reset.columns:
                df_reset["Time"] = df_reset.index

            time_series_data = df_reset[[target_column]].copy()  # Only the target column
        except Exception as e:
            QMessageBox.critical(self, "Data Preparation Error", f"Failed to prepare data for analysis: {e}")
            return

        # Perform the embedding dimension analysis
        try:
            self.calculate_button.setEnabled(False)
            self.calculate_button.setText("Calculating...")

            analysis = EmbeddingDimensionAnalysis(
                time_series_data=time_series_data,
                target_column=target_column,
                e_min=emin,
                e_max=emax,
                tau=tau,
                lib=lib,
                pred=pred
            )
            analysis.run_analysis()

            # Generate and display the plot
            fig = analysis.generate_plot()
            self.canvas.figure = fig
            self.canvas.draw()

            # Display the optimal E
            self.embedding_status_label.setText(f"Optimal Embedding Dimension (E): {analysis.optimal_E}")
            QMessageBox.information(self, "Optimal Embedding Dimension",
                                    f"The optimal embedding dimension is: {analysis.optimal_E}")
        except Exception as e:
            QMessageBox.critical(self, "Analysis Error", str(e))
            self.embedding_status_label.setText("Error during analysis.")
        finally:
            self.calculate_button.setEnabled(True)
            self.calculate_button.setText("Calculate Optimal E")


def main():
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()"