diff --git a/data_handler.py b/data_handler.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d6789b4b7be344e7d069f92387937062f207d6d0 100644 --- a/data_handler.py +++ b/data_handler.py @@ -0,0 +1,22 @@ +import pandas as pd + +import pandas as pd + +def load_csv(file_path): + if not isinstance(file_path, str) or not file_path.endswith('.csv'): + raise ValueError("Invalid file path provided for CSV loading.") + + df = pd.read_csv(file_path).fillna("None") + if "referral" in df.columns: + df["referral"] = df["referral"].replace({1.0: "Need Referral", 0.0: "No Need for Referral"}) + + return df + + +def filter_referrals(df): + if "referral" in df.columns: + return df[df["referral"] == "Need Referral"].copy() + return df + +def reset_table(df): + return df.copy() \ No newline at end of file diff --git a/main.py b/main.py index 488161263ceec7fed43cb785f23250fdb1b582b4..d120c5765894210f4a67dd874e7342ed61d632f9 100644 --- a/main.py +++ b/main.py @@ -1,159 +1,86 @@ import sys -import pandas as pd -from PyQt6.QtWidgets import ( - QApplication, QMainWindow, QWidget, QVBoxLayout, QPushButton, - QFileDialog, QLabel, QTableWidget, QTableWidgetItem, QTabWidget, QTextEdit -) +from PyQt6.QtWidgets import QApplication, QMainWindow, QTabWidget, QFileDialog, QTableWidgetItem from PyQt6.QtGui import QColor -from PyQt6.QtCore import Qt -import matplotlib.pyplot as plt -from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from ui_design import create_upload_tab, create_dashboard_tab, create_analytics_tab, create_help_tab +from data_handler import load_csv, filter_referrals, reset_table +from visualizer import display_graphs - -class FeedingDashboard(QMainWindow): +class CCUDashboard(QMainWindow): def __init__(self): super().__init__() - - self.setWindowTitle("Feeding Dashboard") - self.setGeometry(100, 100, 1000, 600) - - # Main Tab Widget + + self.setWindowTitle("CCU Patients Data Dashboard") + self.setGeometry(100, 100, 1500, 1000) + self.tabs = QTabWidget() self.setCentralWidget(self.tabs) - - # Create Tabs - self.upload_tab = QWidget() - self.dashboard_tab = QWidget() - self.analytics_tab = QWidget() - self.help_tab = QWidget() - - # Add tabs to the tab widget - self.tabs.addTab(self.upload_tab, "Upload CSV File") + + self.df = None # DataFrame to store loaded data + self.filtered_df = None # Copy of data for filtering + + # Initialize tabs using imported functions + self.csvuploading_tab = create_upload_tab(self) + self.dashboard_tab = create_dashboard_tab(self) + self.analytics_tab = create_analytics_tab(self) + self.help_tab = create_help_tab() + + # Add tabs to the interface + self.tabs.addTab(self.csvuploading_tab, "Upload CSV File") self.tabs.addTab(self.dashboard_tab, "View Dashboard") self.tabs.addTab(self.analytics_tab, "Analytics") self.tabs.addTab(self.help_tab, "Help") - # Initialize tab UI - self.init_upload_tab() - self.init_dashboard_tab() - self.init_analytics_tab() - self.init_help_tab() - - self.df = None # Store the loaded data - - def init_upload_tab(self): - """Upload CSV File Tab""" - layout = QVBoxLayout() - - self.upload_button = QPushButton("Upload CSV File") - self.upload_button.clicked.connect(self.load_csv) - - self.file_label = QLabel("No file selected") - - layout.addWidget(self.upload_button) - layout.addWidget(self.file_label) - - self.upload_tab.setLayout(layout) - - def init_dashboard_tab(self): - """Dashboard Tab - Displays CSV Data in a Table""" - layout = QVBoxLayout() - - self.table_widget = QTableWidget() - layout.addWidget(self.table_widget) - - self.dashboard_tab.setLayout(layout) - - def init_analytics_tab(self): - """Analytics Tab - Displays Graphs""" - layout = QVBoxLayout() - - self.canvas = FigureCanvas(plt.figure(figsize=(6, 4))) - layout.addWidget(self.canvas) - - self.analytics_tab.setLayout(layout) - - def init_help_tab(self): - """Help Tab - Displays Instructions""" - layout = QVBoxLayout() - - help_text = QTextEdit() - help_text.setReadOnly(True) - help_text.setText( - "Welcome to the Feeding Dashboard!\n\n" - "1. Upload a CSV file in the 'Upload CSV File' tab.\n" - "2. View patient data in the 'View Dashboard' tab.\n" - "3. Analyze trends in the 'Analytics' tab.\n" - "4. Refer to this guide for help.\n" - ) - - layout.addWidget(help_text) - self.help_tab.setLayout(layout) - - def load_csv(self): - """Load CSV File""" + def load_csv_file(self): + """Load the CSV file and update the UI.""" file_path, _ = QFileDialog.getOpenFileName(self, "Open CSV", "", "CSV Files (*.csv)") - if file_path: - self.file_label.setText(f"Loaded: {file_path}") - self.df = pd.read_csv(file_path) - self.df = self.df.fillna("None") #handle empty values - - - if "referral" in self.df.columns: - self.df["referral"] = self.df["referral"].replace({1.0: "Need Referral", 0.0: "No Need for Referral"}) + + if not file_path: # Ensure a valid file path is selected + print("No file selected.") + return + + self.file_label.setText(f"You have succesfully uploaded the CSV: {file_path}") + self.df = load_csv(file_path) + self.filtered_df = self.df.copy() + self.display_data() + display_graphs(self.df, self.analytics_tab) # Update graphs + + def filter_referrals(self): + """Filter to show only patients needing referral.""" + if self.df is not None: + self.filtered_df = filter_referrals(self.df) + self.display_data() + def reset_table(self): + """Reset the table to show all patients.""" + if self.df is not None: + self.filtered_df = reset_table(self.df) self.display_data() - self.display_graphs() def display_data(self): - """Display CSV Data in Dashboard Table""" - if self.df is None: + """Update the table with the current data.""" + if self.filtered_df is None: return - self.table_widget.setRowCount(self.df.shape[0]) - self.table_widget.setColumnCount(self.df.shape[1]) - self.table_widget.setHorizontalHeaderLabels(self.df.columns) - - for row in range(self.df.shape[0]): - for col in range(self.df.shape[1]): - cell_value = str(self.df.iat[row, col]) - if cell_value.strip() == "": - cell_value = "None" + self.table_widget.setRowCount(self.filtered_df.shape[0]) + self.table_widget.setColumnCount(self.filtered_df.shape[1]) + self.table_widget.setHorizontalHeaderLabels(self.filtered_df.columns) + for row in range(self.filtered_df.shape[0]): + for col in range(self.filtered_df.shape[1]): + cell_value = str(self.filtered_df.iat[row, col]) item = QTableWidgetItem(cell_value) - - # highlight Referral Patients - if "referral" in self.df.columns and self.df.columns[col] == "referral": + + # Modify the referral column to identify with red and green colour the outcome + if "referral" in self.filtered_df.columns and self.filtered_df.columns[col] == "referral": if cell_value == "Need Referral": - item.setBackground(QColor(255, 102, 102)) # light Red + item.setBackground(QColor(255, 102, 102)) # Red for need Referral elif cell_value == "No Need for Referral": - item.setBackground(QColor(144, 238, 144)) # light Green - + item.setBackground(QColor(144, 238, 144)) # Green for No Need for Referral + self.table_widget.setItem(row, col, item) - def display_graphs(self): - """Display Analytics Graphs""" - if self.df is None or "referral" not in self.df.columns: - return - - # Count referrals - referral_counts = self.df["referral"].value_counts() - - # Clear previous graph - self.canvas.figure.clear() - - # Create new graph - ax = self.canvas.figure.add_subplot(111) - ax.bar(referral_counts.index, referral_counts.values, color=["green", "red"]) - ax.set_title("Patient Referrals") - ax.set_ylabel("Count") - - # Update canvas - self.canvas.draw() - - if __name__ == "__main__": app = QApplication(sys.argv) - window = FeedingDashboard() + window = CCUDashboard() window.show() sys.exit(app.exec()) diff --git a/ui_design.py b/ui_design.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..20b0ea0006a84e4b122f8f8be3ae6c2a68f8ded2 100644 --- a/ui_design.py +++ b/ui_design.py @@ -0,0 +1,100 @@ +from PyQt6.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel, QTableWidget, QTextEdit, QHBoxLayout + +def create_upload_tab(parent): + tab = QWidget() + layout = QVBoxLayout() + parent.upload_button = QPushButton("Upload CSV File") + parent.upload_button.clicked.connect(parent.load_csv_file) + parent.file_label = QLabel("No file selected") + layout.addWidget(parent.upload_button) + layout.addWidget(parent.file_label) + tab.setLayout(layout) + return tab + +def create_dashboard_tab(parent): + tab = QWidget() + layout = QVBoxLayout() + parent.table_widget = QTableWidget() + layout.addWidget(parent.table_widget) + + button_layout = QHBoxLayout() + parent.referral_button = QPushButton("Need Referral") + parent.referral_button.clicked.connect(parent.filter_referrals) + parent.reset_button = QPushButton("Show All") + parent.reset_button.clicked.connect(parent.reset_table) + + button_layout.addWidget(parent.referral_button) + button_layout.addWidget(parent.reset_button) + layout.addLayout(button_layout) + + tab.setLayout(layout) + return tab + +def create_analytics_tab(parent): + tab = QWidget() + layout = QVBoxLayout() + parent.patient_count_label = QLabel("Total Patients: 0") + layout.addWidget(parent.patient_count_label) + tab.setLayout(layout) + return tab + +def create_help_tab(): + tab = QWidget() + layout = QVBoxLayout() + help_text = QTextEdit() + help_text.setReadOnly(True) + help_text.setStyleSheet("background-color: #EAEAEA; color: #222222; padding: 10px; border-radius: 8px;") + help_text.setHtml(""" + <html> + <head> + <style> + h1 { font-size: 22px; font-weight: bold; color: #333366; } + h2 { font-size: 18px; font-weight: bold; color: #444488; margin-top: 15px; } + p { font-size: 14px; color: #222222; margin-left: 10px; } + ul { font-size: 14px; color: #222222; margin-left: 20px; } + </style> + </head> + <body> + <h1>How to Use the Feeding Dashboard</h1> + + <p>This program is designed and developed for the CCU staff in order to help monitor patients and identify which patients need referral to a dietitian. + Below are the instructions for each tab:</p> + + <h2>1. Upload CSV File Tab</h2> + <ul> + <li>Click the <b>"Upload CSV File"</b> button.</li> + <li>Select a CSV file containing patient data.</li> + <li>The file will be loaded (it can take a while depending on the file size) and now you can see a list with all the patients and their data on the View Dashboard Tab</li> + </ul> + + <h2>2. View Dashboard Tab</h2> + <ul> + <li>Displays a table of all admitted patients.</li> + <li>Click <b>"Need Referral"</b> to filter and show only patients requiring referral.</li> + <li>Click <b>"Show All"</b> to reset the table and display all patients.</li> + </ul> + + <h2>3. Analytics Tab</h2> + <ul> + <li>Shows a <b>total count of current patients</b>.</li> + <li>Displays a <b>pie chart</b> with the percentage and exact number of patients who need and don’t need referral.</li> + <li>Displays a <b>bar chart</b> for missing patient data, showing the exact number of missing entries for each category.</li> + </ul> + + <h2>4. Help Tab</h2> + <ul> + <li>Provides an overview of how to use the application.</li> + + </ul> + + <h2>Additional Notes</h2> + <ul> + <li>Make sure that the CSV files are not corrupted and include ideally all the data for the best results. </li> + <li>The graphs and charts on the analytics tab, refresh automatically with every new csv upload.</li> + </ul> + </body> + </html> + """) + layout.addWidget(help_text) + tab.setLayout(layout) + return tab \ No newline at end of file diff --git a/visualizer.py b/visualizer.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..58a93d0858f9b4828a0d1952ab2d84e3553b1ed2 100644 --- a/visualizer.py +++ b/visualizer.py @@ -0,0 +1,57 @@ +import matplotlib.pyplot as plt +import pandas as pd +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from PyQt6.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel, QTableWidget, QTextEdit, QHBoxLayout + +def display_graphs(df, analytics_tab): + if df is None or "referral" not in df.columns: + return + + # Find patient_count_label before clearing widgets + patient_count_label = None + for i in reversed(range(analytics_tab.layout().count())): + widget = analytics_tab.layout().itemAt(i).widget() + if isinstance(widget, QLabel) and "Total Patients" in widget.text(): + patient_count_label = widget # Store reference to patient count label + else: + widget.deleteLater() # Remove only charts + + # If patient_count_label exists, update its text + if patient_count_label: + patient_count_label.setText(f"Total Patients: {len(df)}") + else: + patient_count_label = QLabel(f"Total Patients: {len(df)}") + analytics_tab.layout().addWidget(patient_count_label) # Re-add if deleted + + # Add new graphs + analytics_tab.layout().addWidget(create_pie_chart(df["referral"].value_counts())) + analytics_tab.layout().addWidget(create_missing_data_chart(df)) + + + + +def create_pie_chart(data): + fig, ax = plt.subplots(figsize=(6, 4)) + labels = [f"{label} ({count})" for label, count in zip(data.index, data.values)] + ax.pie(data.values, labels=labels, autopct='%1.1f%%', startangle=90, colors=["green", "red"]) + ax.set_title("Referral Distribution") + return FigureCanvas(fig) + +def create_missing_data_chart(df): + missing_counts = df.replace("None", pd.NA).isna().sum() + missing_counts = missing_counts[missing_counts > 0] + + fig, ax = plt.subplots(figsize=(10, 6)) + bars = ax.bar(missing_counts.index, missing_counts.values, color="blue") + + ax.set_xticks(range(len(missing_counts))) # Ensure tick positions match the labels + ax.set_xticklabels(missing_counts.index.tolist(), rotation=30, ha='right', fontsize=9) + + for bar in bars: + height = bar.get_height() + ax.text(bar.get_x() + bar.get_width()/2, height, str(int(height)), ha='center', va='bottom', fontsize=10, color='black') + + ax.set_title("Patients Without Data") + ax.set_ylabel("Number of Patients") + + return FigureCanvas(fig)