Quick and dirty histogramming in python

Normally I’d use R but the day job inspired me to take a look at using Qt from Python. Specifically how easy it’d be to hack up a Qt gui around matplotlib charts. Turns out to be pretty easy.

The goal is to load up some time series from a CSV file and choose from them to draw histograms. Getting the data in is straightforward:

# Load some time series from a csv file.
class TimeSeries(object):
  def __init__(self, filename=None):
    self.load(filename)
 
  def load(self, filename=None):
    self.data = {}
    self.names = []        
    if filename:
      for line in csv.reader(open(filename, 'rb')):
        self.names.append(line[0])
        self.data[line[0]] = map(int, line[1:])
        self.datalen = len(line[1:])
 
  def series(self):
    return self.names
 
  def length(self):
    return self.datalen
 
  def count(self):
    return len(self.data)
 
  def timeseries(self, name):
    return self.data[name]

And the end result, after loading in a CSV file of data, looks like this:

histogram

First, we need a whole mess of imports. This could perhaps be cleaned up but I mainly just wanted to get enough of Qt, matplotlib and numpy in.

import sys, os, csv
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import matplotlib
from matplotlib.backends.backend_qt4agg \
 import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg \
 import NavigationToolbar2QTAgg as NavBar
from matplotlib.figure import Figure
import numpy

All the action takes place in this code.. should be refactored really. I just crammed in a couple of matplotlib and numpy calls into the Qt code to get it up and running.

# Main window    
class Form(QMainWindow):
    def __init__(self, parent=None):
        super(Form, self).__init__(parent)
        self.setWindowTitle('Histogram of time series')
        self.data = TimeSeries()
        self.series_list_model = QStandardItemModel()
        self.create_menu()
        self.create_main_frame()
        self.create_status_bar()
        self.on_show()
 
    def load_file(self, filename=None):
        filename = QFileDialog.getOpenFileName(self,
            'Open a file', '.', 'CSV files (*.csv);;All Files (*.*)')
 
        if filename:
            self.data.load(filename)
            self.fill_series_list(self.data.series())
            self.status_text.setText("Loaded " + filename)
 
    def on_show(self):
        self.axes.clear()        
        self.axes.grid(True)
        has_series = False
        for row in range(self.series_list_model.rowCount()):
            model_index = self.series_list_model.index(row, 0)
            checked = self.series_list_model.data(model_index,
                Qt.CheckStateRole) == QVariant(Qt.Checked)
            name 
              = str(self.series_list_model.data(model_index).toString())
            if checked:
                has_series = True
                series = self.data.timeseries(name)
                maxval = numpy.max(series)
                bins = numpy.arange(0, maxval, 5)
                self.axes.hist(series, bins)
                self.axes.axis(xmax=maxval)
        self.canvas.draw()
 
    def fill_series_list(self, names):
        self.series_list_model.clear()
 
        for name in names:
            item = QStandardItem(name)
            item.setCheckState(Qt.Unchecked)
            item.setCheckable(True)
            self.series_list_model.appendRow(item)
 
    def create_main_frame(self):
        self.main_frame = QWidget()
        plot_frame = QWidget()
        self.dpi = 100
        self.fig = Figure((6.0, 4.0), dpi=self.dpi)
        self.canvas = FigureCanvas(self.fig)
        self.canvas.setParent(self.main_frame)
        self.axes = self.fig.add_subplot(111)
        self.mpl_toolbar = NavBar(self.canvas, self.main_frame)
        log_label = QLabel("Data series:")
        self.series_list_view = QListView()
        self.series_list_view.setModel(self.series_list_model)
        self.show_button = QPushButton("&Show")
        self.connect(self.show_button, 
          SIGNAL('clicked()'), self.on_show)
        left_vbox = QVBoxLayout()
        left_vbox.addWidget(self.canvas)
        left_vbox.addWidget(self.mpl_toolbar)
        right_vbox = QVBoxLayout()
        right_vbox.addWidget(log_label)
        right_vbox.addWidget(self.series_list_view)
        right_vbox.addWidget(self.show_button)
        right_vbox.addStretch(1)
        hbox = QHBoxLayout()
        hbox.addLayout(left_vbox)
        hbox.addLayout(right_vbox)
        self.main_frame.setLayout(hbox)
        self.setCentralWidget(self.main_frame)
 
    def create_status_bar(self):
        self.status_text = QLabel("Load a data file to begin")
        self.statusBar().addWidget(self.status_text, 1)
 
    def create_menu(self):        
        self.file_menu = self.menuBar().addMenu("&File")
 
        load_action = self.create_action("&Load file",
            shortcut="Ctrl+L", slot=self.load_file, tip="Load a file")
        quit_action = self.create_action("&Quit", slot=self.close, 
            shortcut="Ctrl+Q", tip="Close the application")
 
        self.add_actions(self.file_menu, 
            (load_action, None, quit_action))
 
    def add_actions(self, target, actions):
        for action in actions:
            if action is None:
                target.addSeparator()
            else:
                target.addAction(action)
 
    def create_action(  self, text, slot=None, shortcut=None, 
                        icon=None, tip=None, checkable=False, 
                        signal="triggered()"):
        action = QAction(text, self)
        if icon is not None:
            action.setIcon(QIcon(":/%s.png" % icon))
        if shortcut is not None:
            action.setShortcut(shortcut)
        if tip is not None:
            action.setToolTip(tip)
            action.setStatusTip(tip)
        if slot is not None:
            self.connect(action, SIGNAL(signal), slot)
        if checkable:
            action.setCheckable(True)
        return action

And finally the de rigueur main program to drive it:

# main
def main():
    app = QApplication(sys.argv)
    form = Form()
    form.show()
    app.exec_()
if __name__ == "__main__":
    main()

Leave a Comment