How to access a pandas dataframe from a different class

32 views Asked by At

I am quite new to coding, python and pyside6. I have spent a few months getting the code below which seems to work well.

I have tried many different ways to achieve something quite simple (I think). I want to get access to the dataframe named self.filtered_df which is in the PandasModel class. I want this access to be in the def display_filtered_map(self): which is in the MainWindow class.

This is the full code

from PySide6.QtWidgets import QMainWindow, QApplication, QTableView, QLineEdit, QVBoxLayout, QWidget
from PySide6.QtCore import QAbstractTableModel, Qt, QModelIndex
from PySide6.QtWebEngineWidgets import QWebEngineView

import sys
import os
import sqlite3
import pandas as pd

import folium
from folium.plugins import FastMarkerCluster
from folium import LatLngPopup
from jinja2 import Template

from ui.vrpsselect_window import Ui_mw_Main


# code snippet from here: https://stackoverflow.com/questions/73481046/how-to-overide-a-feature-method-in-folium
class GetLatLngPopup(LatLngPopup):
    _template = Template(u"""
            {% macro script(this, kwargs) %}
                var {{this.get_name()}} = L.popup();
                function latLngPop(e) {
                    {{this.get_name()}}
                        .setLatLng(e.latlng)
                        .setContent("Latitude: " + e.latlng.lat.toFixed(6) +
                                    "<br>Longitude: " + e.latlng.lng.toFixed(6))
                        .openOn({{this._parent.get_name()}});
                    }
                {{this._parent.get_name()}}.on('click', latLngPop);
            {% endmacro %}
            """)

    def __init__(self):
        super(GetLatLngPopup, self).__init__()
        self._name = 'GetLatLngPopup'


class PandasModel(QAbstractTableModel):
    def __init__(self, dataframe: pd.DataFrame, parent=None):
        super().__init__(parent)
        self._dataframe = dataframe
        self.filtered_df = dataframe.copy()  # Initialize filtered DataFrame

    def rowCount(self, parent=QModelIndex()) -> int:
        if parent == QModelIndex():
            return len(self.filtered_df)
        return 0

    def columnCount(self, parent=QModelIndex()) -> int:
        if parent == QModelIndex():
            return len(self.filtered_df.columns)
        return 0

    def data(self, index: QModelIndex, role=Qt.ItemDataRole):
        if not index.isValid():
            return None
        if role == Qt.DisplayRole:
            return str(self.filtered_df.iloc[index.row(), index.column()])
        return None

    def headerData(self, section: int, orientation: Qt.Orientation, role: Qt.ItemDataRole):
        if role == Qt.DisplayRole:
            if orientation == Qt.Horizontal:
                return str(self.filtered_df.columns[section])
            if orientation == Qt.Vertical:
                return str(self.filtered_df.index[section])
        return None

    def filter_data(self, country_text: str, name_text: str, ident_text: str, airport_text: str):
        country_filter = self._dataframe['Country'].str.contains(country_text, case=False)
        name_filter = self._dataframe['Name'].str.contains(name_text, case=False)
        ident_filter = self._dataframe['Ident'].str.contains(ident_text, case=False)
        airport_filter = self._dataframe['Airport'].str.contains(airport_text, case=False)
        self.filtered_df = self._dataframe[country_filter & name_filter & ident_filter & airport_filter]
        self.layoutChanged.emit()  # Update the view


class MainWindow(QMainWindow, Ui_mw_Main):
    def __init__(self):
        super().__init__()
        self.setupUi(self)

        # run on startup
        self.setup()
        self.load_database()
        model = PandasModel(self.df)
        self.tv_RP.setModel(model)
        self.initialise()

        # signals
        self.pb_Close.clicked.connect(self.close)

        self.le_SearchCountry.textChanged.connect(lambda text: model.filter_data(text, self.le_SearchName.text(), self.le_SearchIdent.text(), self.le_SearchAirport.text()))
        self.le_SearchName.textChanged.connect(lambda text: model.filter_data(self.le_SearchCountry.text(), text, self.le_SearchIdent.text(), self.le_SearchAirport.text()))
        self.le_SearchIdent.textChanged.connect(lambda text: model.filter_data(self.le_SearchCountry.text(), self.le_SearchName.text(), text, self.le_SearchAirport.text()))
        self.le_SearchAirport.textChanged.connect(lambda text: model.filter_data(self.le_SearchCountry.text(), self.le_SearchName.text(), self.le_SearchIdent.text(), text))
        self.pb_ResetSearches.clicked.connect(self.reset_searches)

        self.tv_RP.clicked.connect(self.get_row_data)

    def setup(self):
        # connect to the database and create a cursor
        conn = sqlite3.connect('data/VRPsS10.db')
        c = conn.cursor()
        # create a table VRPs
        c.execute("""CREATE TABLE if not exists VRPs(
            Country text,
            Name text,
            Ident text,
            Airport text,
            Source text,
            Cycle text,
            Elevation text,
            Lat_DD text,
            Lon_DD text,
            Lat_DMS text,
            Lon_DMS text)""")
        # create a table Updates
        c.execute("""CREATE TABLE if not exists Updates(
            Effective text,
            Cycle text)""")
        # commit the changes and close connection
        conn.commit()
        conn.close()

    def load_database(self):
        # connect to the database and create a cursor
        conn = sqlite3.connect('data/VRPsS10.db')

        # read from the 'Updates' table
        query = 'SELECT * FROM Updates'
        self.df_update = pd.read_sql_query(query, conn)
        self.setWindowTitle(f'AIRAC: {self.df_update.iat[0, 1]}     Date: {self.df_update.iat[0, 0]}')
        del [[self.df_update]]

        # read from the 'VRPs' table
        query = 'SELECT * FROM VRPs'
        self.df = pd.read_sql_query(query, conn)

    def initialise(self):
        self.tv_RP.setColumnWidth(0, 60)
        self.tv_RP.setColumnWidth(1, 230)
        self.tv_RP.setColumnWidth(2, 60)
        self.tv_RP.setColumnWidth(3, 105)
        self.tv_RP.setColumnWidth(4, 60)
        self.tv_RP.setColumnWidth(5, 60)
        self.tv_RP.setColumnWidth(6, 70)
        self.tv_RP.setColumnWidth(7, 80)
        self.tv_RP.setColumnWidth(8, 80)
        self.tv_RP.setColumnWidth(9, 110)
        self.tv_RP.setColumnWidth(10, 110)
        self.tv_RP.horizontalHeader().setDefaultAlignment(Qt.AlignmentFlag.AlignLeft)
        self.tv_RP.setAlternatingRowColors(True)

        # load map
        self.webView = QWebEngineView()
        location = (50.81506933272997, -1.203825772101116)
        self.display_map(location)

    def reset_searches(self):
        self.le_SearchCountry.setText("")
        self.le_SearchName.setText("")
        self.le_SearchIdent.setText("")
        self.le_SearchAirport.setText("")

    def get_row_data(self, item):
        location = (float(self.df.iloc[item.row(), self.df.columns.get_loc("Lat_DD")]),
                    float(self.df.iloc[item.row(), self.df.columns.get_loc("Lon_DD")]))

        # display the map with this location
        self.display_map(location)

    def display_map(self, location):
        # delete current webview
        self.webView.deleteLater()

        # create a separate map dataframe
        df_map = self.df.copy()
        df_map['Popup'] = "Country: " + df_map['Country'] + "<br>" + \
                          "Name: " + df_map['Name'] + "<br>" + \
                          "Ident: " + df_map['Ident'] + "<br>" + \
                          "Elevation: " + df_map['Elevation'] + " feet" + "<br>" + \
                          "Position DD: " + df_map['Lat_DD'] + ", " + df_map['Lon_DD'] + "<br>" + \
                          "Position DMS: " + df_map['Lat_DMS'] + ", " + df_map['Lon_DMS']

        # change columns to float
        df_map['Lat_DD'] = df_map['Lat_DD'].astype(float)
        df_map['Lon_DD'] = df_map['Lon_DD'].astype(float)

        # set up icons and markers
        vrp_map = folium.Map(location=location, zoom_start=12, control_scale=True)
        vrp_map.add_child(GetLatLngPopup())

        callback = ('function (row) {'
                    'var marker = L.marker(new L.LatLng(row[0], row[1]), {color: "red"});'
                    'var icon = L.AwesomeMarkers.icon({'
                    "icon: 'info-sign',"
                    "iconColor: 'white',"
                    "markerColor: 'blue',"
                    "prefix: 'glyphicon',"
                    "extraClasses: 'fa-rotate-0'"
                    '});'
                    'marker.setIcon(icon);'
                    "var popup = L.popup({maxWidth: '300'});"
                    "const display_text = {text: row[2]};"
                    "var my_text = $(`<div id='my_text' class='display_text' style='width: 100.0%; height: 100.0%;'> "
                    "${display_text.text}</div>`)[0];"
                    "popup.setContent(my_text);"
                    "marker.bindPopup(popup);"
                    'return marker};')
        vrp_map.add_child(FastMarkerCluster(df_map[['Lat_DD', 'Lon_DD', 'Popup']].values.tolist(), callback=callback))

        # display map
        self.webView = QWebEngineView()
        self.webView.setHtml(vrp_map.get_root().render())
        self.vl_Map.addWidget(self.webView)

        # destroy map dataframe
        del [[df_map]]

    def display_filtered_map(self):
        print(self.filtered_df)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
sys.exit(app.exec())

I have tried so many different ways to achieve this that I am now totally confused, but none have got me where I want to go.

I'm grateful for any help you can offer. Thanks in advance

1

There are 1 answers

1
nemo On BEST ANSWER

The two following changes are necessary:

First, in the init of MainWindow, you need the get the instance of PandasModel visible to the whole class:

self.model = PandasModel(self.df)

Second, when you want the filtered_df from the PandasModel instance, you have to specify the instance (here self.model):

def display_filtered_map(self):
    print(self.model.filtered_df)