【问题标题】:GUI for printing pandas data frame用于打印熊猫数据框的 GUI
【发布时间】:2019-08-25 01:46:38
【问题描述】:

我有 10 个 CSV 文件,每个 CSV 文件都有相同数量的列,我以 pandas 数据框的形式一一读取数据。我希望这些数据显示在窗口中或以某种表格形式显示。它应该就像每次数据进入新行时一样。对此有什么建议吗?

以下是我的示例 CSV 文件:

像这样,有 10 个或更多的 CSV 文件,我将从这些文件中逐一读取数据并希望在 GUI 中显示。

我的应用程序简介

我有一台机器在一定时间间隔后生成 CSV 文件到一个文件夹中。我正在使用 Watchdog 库监视生成 CSV 文件的文件夹。当我收到一个 CSV 文件时,我将它读入 pandas 数据框。上面给出了示例 CSV 文件。

只要机器正在运行,它就会不断生成 CSV 文件。因此,如果我想查看需要打开每个 CSV 文件的数据,相反,我需要一个视图,当生成新的 CSV 文件时,数据会在其中更新。

所以从技术上讲,一个 CSV 文件被读取并转换为数据框,然后插入到某种表格视图中。当生成一个新的 CSV 文件时,这个过程会再次发生,但是现在数据应该保存在同一个 Table View 的下一行中。

这是我的主要文件:

import time
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler
import pandas as pd
from Append_Function import append_df_to_excel
import os.path
import sys

class Watcher:
    def __init__(self, args):
        self.watch_dir = os.getcwd()
        print(args[0])
        self.directory_to_watch = os.path.join(self.watch_dir, args[1])
        self.observer = Observer()
        self.event_handler = Handler(patterns=["*.CSV"], ignore_patterns=["*.tmp"], ignore_directories=True)

    def run(self):
        self.observer.schedule(self.event_handler, self.directory_to_watch, recursive=False)
        self.observer.start()
        try:
            while True:
                time.sleep(1)
        except:
            self.observer.stop()
            print("Error")

        self.observer.join()


class Handler(PatternMatchingEventHandler):
    @staticmethod
    def on_any_event(event):
        if event.is_directory:
            return None
        elif event.event_type == 'created':
            # Take any action here when a file is first created.
            print("Received created event - %s." % event.src_path)
            df = pd.read_csv(event.src_path, header=1, index_col=0)
            append_df_to_excel(os.path.join(os.getcwd(), "myfile.xlsx"), df)
        elif event.event_type == 'modified':
            # Taken any actionc here when a file is modified.
            df = pd.read_csv(event.src_path, header=0, index_col=0)
            append_df_to_excel(os.path.join(os.getcwd(), "myfile.xlsx"), df)
            print("Received modified event - %s." % event.src_path)


if __name__ == '__main__':
    print(sys.argv)
    w = Watcher(sys.argv)
    w.run()

这是我的附加函数:

import pandas as pd
import openpyxl as ox


def append_df_to_excel(filename, df, sheet_name='Sheet1', startrow=None,
                       truncate_sheet=False,
                       **to_excel_kwargs):
    # ignore [engine] parameter if it was passed

    if 'engine' in to_excel_kwargs:
        to_excel_kwargs.pop('engine')

    writer = pd.ExcelWriter(filename, engine='openpyxl')

    # Python 2.x: define [FileNotFoundError] exception if it doesn't exist
    try:
        FileNotFoundError
    except NameError:
        FileNotFoundError = IOError

    try:
        # try to open an existing workbook
        writer.book = ox.load_workbook(filename,keep_vba=True)

        # get the last row in the existing Excel sheet
        # if it was not specified explicitly
        if startrow is None and sheet_name in writer.book.sheetnames:
            startrow = writer.book[sheet_name].max_row

        # truncate sheet
        if truncate_sheet and sheet_name in writer.book.sheetnames:
            # index of [sheet_name] sheet
            idx = writer.book.sheetnames.index(sheet_name)
            # remove [sheet_name]
            writer.book.remove(writer.book.worksheets[idx])
            # create an empty sheet [sheet_name] using old index
            writer.book.create_sheet(sheet_name, idx)

        # copy existing sheets
        writer.sheets = {ws.title: ws for ws in writer.book.worksheets}
    except FileNotFoundError:
        # file does not exist yet, we will create it
        pass

    if startrow is None:
        startrow = 0

    # write out the new sheet
    df.to_excel(writer, sheet_name, startrow=startrow, **to_excel_kwargs, header=True)

    # save the workbook
    writer.save()

【问题讨论】:

  • 您是否将 csv 中的数据直接读取到数据框中?而你只是想要一种可视化的方式?
  • 是的,这正是我想要做的......
  • 您想要一个带有表格小部件的完整 GUI(可以实时更新)吗?我可能会推荐 pyqt5:stackoverflow.com/a/44605011/8150685。否则,如果您想要轻量级的东西,请查看stackoverflow.com/a/37441348/8150685
  • @Error-SyntacticalRemorse 谢谢,我一定会调查的..
  • @S.Nick 当然先生,我会更新我的帖子

标签: python python-3.x pandas pyqt pyqt5


【解决方案1】:

您必须通过循环添加数据框:

import pandas as pd
from PyQt5 import QtCore, QtWidgets

class DataFrameTableWidget(QtWidgets.QTableWidget):
    def append_dataframe(self, df):
        df = df.copy()
        if df.columns.size > self.columnCount():
            self.setColumnCount(df.columns.size)
        r = self.rowCount()
        self.insertRow(r)
        for c, column in enumerate(df):
            it = QtWidgets.QTableWidgetItem(column)
            self.setItem(r, c, it)
        i = self.rowCount()
        for r, row in df.iterrows():
            self.insertRow(self.rowCount())
            for c, (column, value) in enumerate(row.iteritems()):
                it = QtWidgets.QTableWidgetItem(str(value))
                self.setItem(i+r , c, it)

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    import numpy as np
    w = DataFrameTableWidget()
    df = pd.DataFrame(np.random.randint(0, 100,size=(4, 4)), columns=list('ABCD'))
    w.append_dataframe(df)

    def after_show():
        df = pd.DataFrame(np.random.randint(0, 100,size=(4, 4)), columns=list('ABCD'))
        w.append_dataframe(df)
    QtCore.QTimer.singleShot(2*1000, after_show)
    w.resize(640, 480)
    w.show()
    sys.exit(app.exec_())

更新:

观察者在另一个线程上运行,因此它无法从该线程更新 GUI,因此必须使用信号来传输信息:

import os
import time
import pandas as pd
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler
from PyQt5 import QtCore, QtWidgets

from Append_Function import append_df_to_excel

class Emitter(QtCore.QObject):
    newDataFrameSignal = QtCore.pyqtSignal(pd.DataFrame)

class Watcher:
    def __init__(self, filename):
        self.watch_dir = os.getcwd()
        self.directory_to_watch = os.path.join(self.watch_dir, filename)
        self.emitter = Emitter()
        self.observer = Observer()
        self.event_handler = Handler(
            emitter=self.emitter,
            patterns=["*.CSV"], 
            ignore_patterns=["*.tmp"], 
            ignore_directories=True
        )

    def run(self):
        self.observer.schedule(self.event_handler, self.directory_to_watch, recursive=False)
        self.observer.start()


class Handler(PatternMatchingEventHandler):
    def __init__(self, *args, emitter=None, **kwargs):
        super(Handler, self).__init__(*args, **kwargs)
        self._emitter = emitter
    def on_any_event(self, event):
        if event.is_directory:
            return None
        elif event.event_type == 'created':
            # Take any action here when a file is first created.
            print("Received created event - %s." % event.src_path)
            df = pd.read_csv(event.src_path, header=1)
            self._emitter.newDataFrameSignal.emit(df.copy())
            df.set_index(df.columns.values.tolist()[0], inplace=True)
            append_df_to_excel(os.path.join(os.getcwd(), "myfile.xlsx"), df)
        elif event.event_type == 'modified':
            # Taken any actionc here when a file is modified.
            df = pd.read_csv(event.src_path, header=1)
            self._emitter.newDataFrameSignal.emit(df.copy())
            df.set_index(df.columns.values.tolist()[0], inplace=True)
            append_df_to_excel(os.path.join(os.getcwd(), "myfile.xlsx"), df)
            print("Received modified event - %s." % event.src_path)

class DataFrameTableWidget(QtWidgets.QTableWidget):
    @QtCore.pyqtSlot(pd.DataFrame)
    def append_dataframe(self, df):
        df = df.copy()
        if df.columns.size > self.columnCount():
            self.setColumnCount(df.columns.size)
        r = self.rowCount()
        self.insertRow(r)
        for c, column in enumerate(df):
            it = QtWidgets.QTableWidgetItem(column)
            self.setItem(r, c, it)
        i = self.rowCount()
        for r, row in df.iterrows():
            self.insertRow(self.rowCount())
            for c, (column, value) in enumerate(row.iteritems()):
                it = QtWidgets.QTableWidgetItem(str(value))
                self.setItem(i+r , c, it)

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = DataFrameTableWidget()
    w.resize(640, 480)
    w.show()
    watcher = Watcher(sys.argv[1])
    watcher.run()
    watcher.emitter.newDataFrameSignal.connect(w.append_dataframe)
    sys.exit(app.exec_())

【讨论】:

  • 哇!你这么快就实施了!棒极了!让我试试,我会告诉你的!
  • 我还需要使用 PandasModel 类吗?
  • @ViralParmar 没必要。
  • 好的,现在我有一个问题,我希望在向视图中添加一个数据框后更新 Pyqt 表视图小部件,依此类推。您在这里所做的是附加所有数据,然后显示表格视图。
  • @ViralParmar 我已经修改了示例,以便您可以看到可以在显示小部件之前或之后添加数据框。
【解决方案2】:

您可能正在寻找:

  • Jupyter 笔记本,能够将 pandas 数据帧显示为 HTML 格式的表格。
  • Jupyter 实验室,其中包括一个 GUI CSV viewer
  • jupyter 笔记本的 qgrid 扩展,可让您以交互方式过滤和编辑数据。

如果您的 CSV 文件具有相同的标题,您可能需要将数据连接到 create one single table 以供审核。

【讨论】:

  • 能否在 CSV 查看器的下一行中逐个添加数据框?
  • 不,它是查看器,而不是编辑器。如果您想共同连接您的文件,请查看我的回答中的最后一个链接。
  • 我正在更新我的问题,那么我想你会知道确切的问题。
猜你喜欢
  • 2013-09-02
  • 1970-01-01
  • 1970-01-01
  • 2017-10-02
  • 1970-01-01
  • 2020-03-19
  • 1970-01-01
  • 2014-08-29
相关资源
最近更新 更多