AB Testing On PythonAnywhere and MySQL
AB Testing On PythonAnywhere and MySQL
A/B Testing on
PythonAnywhere
and MySQL
This is an ambitious chapter, so we’ll limit the scope in order to distill the essence of this
rich topic without going overboard. We’ll start by building a simple MySQL database
and table to track whether a visitor liked or didn’t like the art work on the landing page.
Because this is A/B Testing, we’re going to create two landing pages and switch them
randomly when users visit the site (Figure 14-1).
In analytics, A/B testing means using two different versions of something and
measuring how people react to each. This is commonly used in websites to try out
new designs, products, sales, etc. In our case, we’re going to expose our visitors to two
different versions of our landing page. The versions are going to be assigned randomly
and the visitor will be offered the opportunity to give the page a thumbs-up if they liked
it. In the background, we’re going to be tracking this traffic and whether or not a user
gives a thumbs-up. If the user doesn’t give the thumbs-up, we’ll assume that it was a
down vote.
This is an important topic and can yield valuable knowledge about your business
and your users. There is a famous anecdote where Marisa Meyer, while at Google, ran an
A/B test to determine which shade of blue, out of 40, the users preferred.1 Obviously, one
can go overboard with these types of tests.
A/B Testing
The goal of A/B testing is to expose different products to the public and measure their
reactions. In our web application, we’re going to show a web page with two different
images: an angry face and a friendly one. We will add a simple label to the page asking
the visitor to give the image a thumbs-up if they liked it. In the background we’re going
to count each visit and count each thumbs-up. To keep things simple, we’ll count an
initial visit as a thumbs-down and update it to a thumbs-up if the visitor clicks the voting
button. See Figures 14-2 and 14-3 for a version of each image.
1
https://iterativepath.wordpress.com/2012/10/29/testing-40-shades-of-blue-ab-testing/
402
Chapter 14 A/B Testing on PythonAnywhere and MySQL
Tracking Users
There are various ways of tracking anonymous visitors. Popular methods include the
usage of cookies and databases. Each has its advantage and purpose. A cookie offers the
advantage of tracking a user over longer periods regardless of whether they closed their
browser or turned their computer off. A web page can easily check the visitor’s computer
for previous cookies and compare it with their database to determine if this is a repeat
visitor or not.
We won’t need to use cookies, as we will only consider single visits. Instead, we’ll
keep track of users using an HTML hidden tag and send that tag back using a post
request. When a visitor first visits the page, we’ll insert a row in the database with the
page background image, a timestamp, and a unique identifier (a very long string that is
unique to that user; the odds of creating two of the same are infinitesimal) referred to as a
UUID. As mentioned, we assume that a first page visit is a thumbs-down and write it to the
database. As we build the page, we insert a hidden HTML tag containing the UUID so that
if the user interacts with the page by clicking the thumbs-up button, we’ll pass the UUID
back to the web server, so we can update the row previously entered in the database. This
approach allows us to serve many visitors at the same time without worrying about who
has what page. There are many ways you can tweak and improve this process depending
on your needs. You can even pass that UUID from client to server and back as many times
as you want and always know which session and user you are dealing with.
UUID
The Universally Unique Identifier (UUID) is 128 bits long strong, and is guaranteed
to be unique. We’ll use the handy “uuid” Python library to automatically generate a
guaranteed unique identifier (Listing 14-1).
Input:
import uuid
str(uuid.uuid4())
Output:
'e7b1b80e-1eca-43a7-90a3-f01927ace7c9'
404
Chapter 14 A/B Testing on PythonAnywhere and MySQL
In the “uuid” library, the “uuid4()” function generates a new random ID without
relying on your computer’s identifier, so it is unique and private. Check out the docs
for additional UUID details and options at https://docs.python.org/3/library/
uuid.html.
MySQL
We’re going to use the MySQL Community Server, which is a great and popular free
database. It is perfect to support our A/B testing needs. It is an open-source relational
database that can support a wide range of needs and is being used by big players
including WordPress, and large media companies like Google and Facebook.
Go ahead and download the version of MySQL Community Server for your OS at
https://dev.mysql.com/downloads. You will also find the installation instruction for
your OS if you have any questions or issues. We won’t use any front end, though there are
quite a few of them available in case you want to use one (Figure 14-4).
Figure 14-4. Find and download the correct version for your operating system
405
Chapter 14 A/B Testing on PythonAnywhere and MySQL
You will be prompted with a series of questions including setting up root password
and password encryption type (Figure 14-5).
Figure 14-5. Keeping it simple and using the legacy password system
You can also start and stop your database through the control center for your
operating system (this can also be done through the command line; Figure 14-6).
406
Chapter 14 A/B Testing on PythonAnywhere and MySQL
Figure 14-6. Setting MySQL server to start automatically when the computer
starts
407
Chapter 14 A/B Testing on PythonAnywhere and MySQL
$ export PATH=$PATH:/usr/local/mysql/bin
$ mysql -u root -p
408
Chapter 14 A/B Testing on PythonAnywhere and MySQL
You will know that you entered the monitor once your prompt changes to “mysql>.”
Let’s create a user, a database, and a table for our A/B testing.
Creating a Database
Let’s create a database named “ABTesting” (Listing 14-7).
Creating a Table
Let’s create a new table using the “CREATE TABLE” statement. Whenever you are
creating a new table, it is a good idea to drop it first, otherwise you will get an error
(but make sure that you really do want to drop it as you will lose all data contained
therein). We will create a table called “tblFrontPageOptions” that will have a unique
identifier field called “uuid,” a Boolean flag called “liked” to hold whether or not the user
clicked the thumbs up, a page_id to mark whether this was an “A” or “B” page, and an
automated timestamp field (Listing 14-8).
409
Chapter 14 A/B Testing on PythonAnywhere and MySQL
You can easily test that your table is working by inserting some data into it using an
“INSERT INTO” statement (Listing 14-9).
To check that the data did indeed make it into the table, we use a “SELECT *”
statement (Listing 14-10).
Input:
Output:
+------+-------+---------+---------------------+
| uuid | liked | page_id | time_stamp |
+------+-------+---------+---------------------+
| 9999 | 1 | 2 | 2018-05-19 14:28:44 |
+------+-------+---------+---------------------+
1 row in set (0.00 sec)
We’re looking good; the table now has a new row in it. If you want to start with a
clean state, you can drop and re-create the table with the previous code. There are plenty
of great primers on SQL syntax on the Internet, but a great place to start is the w3schools
at https://www.w3schools.com/sql. Exit out of the “mysql>” prompt and open up the
Jupyter notebook for the chapter to practice inserting data into our table and reading it
out through the “mysql.connector” Python library.
410
Chapter 14 A/B Testing on PythonAnywhere and MySQL
Next, we will grant this user all privileges, and once you are more comfortable with
MySQL (no, not my SQL, the MySQL product… you know they’re probably joking like
that all day long over at the MySQL headquarters…), you can tone this down to just read/
write permissions for specific tables). See Listing 14-12.
Finally, you can check that the “webuser” user was successfully added with the
following handy command (Listing 14-13).
+------------------+
| User |
+------------------+
| mysql.infoschema |
| mysql.session |
| mysql.sys |
| root |
| webuser |
+------------------+
5 rows in set (0.00 sec)
You can exit out of the MySQL command line tool by simply entering the “exit”
command.
411
Chapter 14 A/B Testing on PythonAnywhere and MySQL
Input:
412
Chapter 14 A/B Testing on PythonAnywhere and MySQL
Output (example, your output will only contain what's in the table so far):
Again, don’t forget to call the “commit()” function to commit your changes before
closing the connection (if you don’t, your changes will get ignored).
414
Chapter 14 A/B Testing on PythonAnywhere and MySQL
We also can abstract the Uuid-generating code to keep things clean and simple
(Listing 14-18).
def GetUUID():
return (str(uuid.uuid4()))
Next, we create a function to insert new visits into the database. This function will
get a new UUID from “GetUUID(),” set the “liked” to false as we assume all new visits
don’t like or don’t want to interact with the site, the “pageid” representing the image that
was randomly selected for them, and the timestamp that is automatically generated by
MySQL (Listing 14-19).
When a user interacts with the page and clicks the thumbs-up button, Flask uses the
“UpdateVisitWithLike()” function to update the row using the unique identifier for the
session and turns the “liked” flag to true (Listing 14-20).
415
Chapter 14 A/B Testing on PythonAnywhere and MySQL
def UpdateVisitWithLike(uuid_):
try:
cnx = mysql.connector.connect(user=mysql_account, password=mysql_
password, database=mysql_database, host=mysql_host)
cursor = cnx.cursor()
query = "UPDATE ABTesting.tblFrontPageOptions SET liked = %s WHERE
uuid = %s;"
args = (1, uuid_)
cursor.execute(query, args)
cursor.close()
cnx.commit()
cnx.close()
except mysql.connector.Error as err:
app.logger.error("Something went wrong: {}".format(err))
Finally, we create the administrative dashboard to view how the A/B testing is going
by offering total visit counts, total thumbs up and down, and how many thumbs up for
each image. We also offer a log view where we dump all the content from the ABTesting
table (Listing 14-21).
def GetVoteResults():
results = "
total_votes = 0
total_up_votes = 0
total_up_votes_page_1 = 0
total_up_votes_page_2 = 0
try:
cnx = mysql.connector.connect(user=mysql_account, password=mysql_
password, database=mysql_database, host=mysql_host)
cursor = cnx.cursor()
query = "SELECT * FROM ABTesting.tblFrontPageOptions"
cursor.execute(query)
416
Chapter 14 A/B Testing on PythonAnywhere and MySQL
total_votes += 1
if liked==1 and pageid==1:
total_up_votes_page_1 += 1
if liked==1 and pageid==2:
total_up_votes_page_2 += 1
if liked == 1:
total_up_votes += 1
results += ("uuid: {} liked:{} pageid: {} on {:%m/%d/%Y
%H:%M:%S}".format(uuid_, liked, pageid, time_stamp)) + "<br />"
cursor.close()
cnx.close()
except mysql.connector.Error as err:
app.logger.error("Something went wrong: {}".format(err))
web-application
├── main.py
├── static
└──images
├── background1.jpg
├── background2.jpg
└── templates
├── admin.html
└── index.html
417
Chapter 14 A/B Testing on PythonAnywhere and MySQL
After you initialize MySQL, you will be able to create a database and get into
the MySQL console to create the “tblFrontPageOptions.” There are two caveats
you will have to contend with. First, PythonAnywhere appends your account
name in front of the database name. In my case, database “ABTesting” becomes
“amunateguioutloo$ABTesting.” This isn’t a big deal, but we will have to update any
code that talks to the database. The second issue is that the user it creates for you is the
one you will have to add to your script, as it won’t let you create additional users using
the “CREATE USER” command (Figure 14-10).
Figure 14-10. Creating the ABTesting database and clicking the console link to set
things up
419
Chapter 14 A/B Testing on PythonAnywhere and MySQL
Click the console link for the “…$ABTesting” database and create the
“tblFrontPageOptions” table and “webuser” account. Make sure to update the database
to reflect your database name (Listing 14-23).
Output:
That is all we need to do in the console; everything else will be done through Python.
Figure 14-11. Creating the new folder to host our ABTesting site
420
Chapter 14 A/B Testing on PythonAnywhere and MySQL
Next, we need to upload all the files, one-by-one, up to the site just like we did with
the other PythonAnywhere projects we already did (Figures 14-12 through 14-14).
Once you have uploaded all files, you need to go into “main.py” and update
the database account and all table references. You will need to update the following
variables with the ones assigned to you by PythonAnywhere. Click “Databases” in the
upper right corner of your PythonAnywhere dashboard to access the variables
(Listing 14-24).
421
Chapter 14 A/B Testing on PythonAnywhere and MySQL
mysql_account='<<ENTER-YOUR-DATABASE-USERNAME>>'
mysql_password='thesecret'
mysql_database='<<ENTER-YOUR-DATABASE-USERNAME>>$ABTesting'
mysql_host="<<ENTER-YOUR-DATABASE-USERNAME>>.mysql.pythonanywhere-services.com"
Figure 14-15. The “Do You Like Me?” web application running on
PythonAnywhere
422
Chapter 14 A/B Testing on PythonAnywhere and MySQL
Figure 14-16. A simple dashboard with the latest results of our A/B test
We will keep things simple here and offer the total votes, the total up and down votes,
the up votes per image, and the full log of all participants.
423
Chapter 14 A/B Testing on PythonAnywhere and MySQL
Conclusion
A/B testing is one of the popular tools to better understand your users. It is also a loaded
science with many ways to approach it. Here, we made the assumption that any new visit
doesn’t like the site, thus defaults with a thumbs-down. This doesn’t necessarily mean
they thought the page was bad, as it could also mean they didn’t have time to read the
question. So, in this scenario, I would look closer at the number of up votes per image
rather than worry about the down votes; in either case you can extract which image was
favored by the majority.
Another tool we introduced here is MySQL; it is a great open-source and free
relational database that is widely used and supported.
424