Utilize your Data Science Project (Part 2)

Using PyQt5 to create a GUI for a LendingClub dashboard

Tim Copeland
Towards Data Science

--

The graphical user interface for my LendingClub dashboard application

This is the second part of a two-part series. To check out the first article, click here.

Over the past couple of years, the importance that the data science job market has placed on software engineering skills, and experience putting machine learning models into production as increased significantly. In part 1 of this story, I went through certain considerations of writing a program that utilized a machine learning model in the context of a final (theoretically user-facing) product. The process brought to light a lot of revelations that I believe edified my knowledge of the data science product process.

In this part, I’m going to focus on a process that might be unfamiliar to many data scientists or students; creating a user interface. If you want to enhance your skillset for the data science job market, learn how to package a machine learning product for a consumer, or create a program that makes it easier to use your projects for personal use, read on!

Choosing the PyQt5 Framework

PyQt5 is a set of Python bindings (a way to call C functions from Python) that allows us to create a GUI (graphical user interface), implementing the Qt library in C++, using only Python, allowing developers to create apps from the ground up fairly easily. I played around with Kivy and Tkinter as well, but PyQt5 seemed to suit my needs best. Tkinter is a fairly standard GUI library, but it is a little bit buggy on Macs, and Kivy is great for mobile app development, but less effective for desktop applications. In the end, I chose PyQt5 because of how intuitive and flexible it was. As long as you’re familiar with creating and defining classes, and general principles of object-oriented programming, it is very manageable to pick up.

I heavily utilized the YouTube Tutorial from the programming channel “Tech With Tim,” to learn the basics of this library and framework. You can find his series of videos here.

Designing the interface with Qt Designer

One of my favorite features of the Qt framework is the ability to lay out windows manually with a tool called Qt Designer. Designer lets you drag-and-drop different elements (i.e. buttons, labels, sliders, etc.) onto a sample window, so you can design a visually appealing program without messing around with x/y coordinates or dimensions in code.

Setting up my dashboard in Qt Desginer

Once you have a layout that you like, you can save it as a .ui file. From there, we can use the pyuic5 command in the command line to generate Python code from our .ui file. After ensuring that you’re in the correct working directory, simply type the following in your command line interface of choice:

pyuic5 -x saved_file.ui -o new_file.py

The “-x,” part of the command adds a snippet of Python code that executes and displays the program when we run the resulting Python script. The “-o,” command specifies the name of the output file.

Once you’ve done this, you can open this file in your favorite text editor and create a working application!

You can download Qt Designer for Mac or Windows here. On Windows, you can also use:

pip install pyqt5-tools

in the command line to install a suite of tools associated with this library.

Adding function to the interface

Once you’ve created a Python file for your user interface, you can try running it. It should display on your screen like a normal program, but none of the buttons or widgets do anything yet. We still need to write the code in Python to instruct our program to do what we want.

This part will obviously vary based on the application you’re creating, but I’ll outline the major steps I took to create my Lending Club program.

Populating the dashboard

In part one, I outlined the steps to downloading live data from Lending Club, and using the model to predict repayment likelihoods. After that’s done, we want to display the loan information, along with our predictions, in our dashboard.

Our autogenerated code creates a class (Mine is called Ui_Form), then a method called setupUi. Our main window will be an instance of this class, and as you can probably guess, setupUi() will create and set up this instance when the script is run. The code to generate our table, or tableWidget, is written for us automatically, as is setting the column dimension. The number of rows is set with the following code (display is the data frame that we want to display):

self.tableWidget.setRowCount(display.shape[0])

More autogenerated code creates the columns of the table, then the table is populated with the following loop:

for r in range(display.shape[0]):
for c in range(7):
self.tableWidget.setItem(
r,c,
QtWidgets.QTableWidgetItem(
str(display.iloc[r,c]))
)

There is likely a more efficient way to do this. However, our data frame likely won’t get larger than 60 or 70 rows. The code didn’t leave anything to be desired speed-wise, so I didn’t go through the trouble of optimizing further.

Adding filters

I wanted users to be able to narrow down the loan options they were viewing to fit their personal preferences. First, I added checkboxes for the various loan classes (Lending Club grades all their loans on a scale from A to G). My program automatically checks the boxes for every grade when the program starts, then connects to a class method called, “redraw,” which will be defined next, whenever it is clicked.

self.a_box.setCheckState(2)  # A state of 2 means the box is checked
self.a_box.clicked.connect(self.redraw)

The function .connect() connects an event (in this case, self.a_box.clicked, or the A box changing states) to a method. This is the primary method through which we give our interface life. The previous code is repeated for every checkbox, with the exception of the “All,” box. This box is connected to a separate function which checks or unchecks all boxes (depending on the state), then calls the redraw method.

Next, I added sliders for users to filter the displayed loans by interest rate or by expected value/average outcome. The process is similar, except that we have to set minimum and maximum values, and potentially a value for the slider to take when initialized.

self.min_int.setMinimum(min(display_raw.intRate)-1)
self.min_int.setMaximum(max(display_raw.intRate)+1)
self.min_int.sliderMoved.connect(self.redraw)
self.max_int.setMinimum(min(display_raw.intRate)-1)
self.max_int.setMaximum(max(display_raw.intRate)+1)
self.max_int.setValue(max(display_raw.intRate)+1)
self.max_int.sliderMoved.connect(self.redraw)

Again, whenever the slider is moved, the action is connected to the redraw function.

The redraw function

The redraw function is called whenever a filter is changed. It does essentially what it sounds like. First, create a list of the filtered loan grades. The method checkState() is 2 whenever a box is checked, so disp_rows is a list of the grades of loans that the user wishes to return. Next, an object, newdf, a subset of the data frame of loans that reflects the state of our filters, is created. Finally, the table is populated as it was originally.

def redraw(self):disp_rows = [(self.a_box.checkState() == 2) * 'A', 
(self.b_box.checkState() == 2) * 'B',
(self.c_box.checkState() == 2) * 'C',
(self.d_box.checkState() == 2) * 'D',
(self.e_box.checkState() == 2) * 'E',
(self.f_box.checkState() == 2) * 'F',
(self.g_box.checkState() == 2) * 'G',
]
newdf = display[[x in disp_rows for x in display.grade] &
(display_raw['intRate']>=self.min_int.value()) &
(display_raw['intRate']<=self.max_int.value()) &
(display_raw['EV'] >= self.min_ev.value()) &
(display_raw['EV'] <= self.max_ev.value())
]

self.tableWidget.setRowCount(newdf.shape[0])
for r in range(newdf.shape[0]):
for c in range(7):
self.tableWidget.setItem(
r,c,
QtWidgets.QTableWidgetItem(
str(newdf.iloc[r,c]))
)

The login screen

The last thing that needs to be done is incorporate a login screen. Besides making it feel more like a “real,” app, a login screen gives the user an easy way to enter their login information (in this case, an API key to connect to the Lending Club API).

To create a separate window, a new class is defined (I used Qt Designer again). This new login window will be what is initialized when the program starts, with the main window being created later.

Once the user enters their API key, we have to connect a button click event to a function.

self.ok_button.clicked.connect(self.logging_in)

When this function is called, the program stores the user’s input as a global variable called apikey, which is called again when we set up the main window. After that, the main window is initialized, and our program as we know it begins.

def logging_in(self):
global apikey
apikey = ui.keybox.text()
Form = QtWidgets.QDialog()
uimain = Ui_Form()
uimain.setupUi(Form)
Form.show()
Form.exec_()

Conclusion

Going through this process taught me a lot about the life cycle of putting a machine learning model into production, object-oriented programming, and app development in general. I’ve barely scratched the surface of what can be accomplished using a framework like PyQt5. If you’re interested in learning more, check out Tim’s (not me) Youtube tutorial mentioned earlier, PyQt5 documentation, or try creating an app or project yourself!

If you want to learn more about the back end of my application, read part one here. Feel free to check out the complete code on my Github here.

--

--

Data Analyst @ Root, Inc | Former Economist and Analytics mentor | Columbus, Ohio