The Qualtrics API and Python

In my previous post , I wrote about collecting email addresses via Qualtrics Site Intercept. That is only one half of this project. The other, and arguably more import half, is analyzing and comparing the impressions and response rate. Just because we built it, does not mean people will use it. While Qualtrics provides statistics for surveys and site intercepts in their individual suites (impressions, clicks, responses, etc.), there is not a way to compare them together in the software.

However, Qualtrics does offer multiple an extensive APIs that are capable just about any operation that a user could do in the applications. This allows their more technically adept users to generate their own custom stats and spreadsheets that can span any number of Qualtrics products. The following is how I used Python to track and analyze data from a project. Note: I am not a professional Python developer. Any advice on how to improve my code would be welcomed.

I will explain my process by walking through my program and breaking down each segment. The final output shows: impressions (how many people saw the pop-up), responses (how many people sign-up with their email address), and the responses rate (responses / impressions * 100). Generating this spreadsheet on my machine is just fine, but I would also like to make my program portable so that anyone can pull the data. More on that later…

Desired output

Setup

Variables and Imports

Imports (in alphabetical order, a personal preference)

  • CSV: Output the final data as a CSV
  • Datetime: Sort and organize responses by date
  • JSON: Parse the JSON response from the API in Python
  • Month: My own module. Each month object has a name, total days, total responses for that month, total impressions in that month, and methods for the average number of responses per day and rate of submission.
  • OS: Allows for saving the final file to a specific path instead of the folder the Python file is in
  • Requests: Module for calling the API

The Qualtics Vars are the various API parameters I will use in the API call, and the time ones make it easier for me when categorizing responses based on date. Note that I convert the time objects to strings in order to match them to the strings that a API returns. In the future I will likely do the opposite, but it works here.

Responses

The Qualtrics API offers a ton of data points about survey responses. However, in my case, I only need one: the date that the survey was submitted (referred to as EndDate). From this I can determine how many responses there were in total and when they were submitted. I will then compare these numbers to how many impressions were made. I can generate this list of end dates from a single API call. Let’s go build it.

First, I need the URL that I will be calling with the appropriate parameters. In this case I will be requesting all surveys that were completed up to and including the previous day

https://survey.qualtrics.com//WRAPI/ControlPanel/api.php?API_SELECT=ControlPanel&Version={0}&Request=getLegacyResponseData&User={1}&Token={2}&Format={3}&SurveyID={4}&EndDate={5}%2000%3A00%3A00&LocalTime=1&ResponsesInProgress=0&LocationData=0".format(version, user, token, format, SurveyID, current_full_date)

So I have the URL, but now I need to call it. I use a GET request from the requests module

requests.get(“https://survey.qualtrics.com//WRAPI/ControlPanel/api.php?API_SELECT=ControlPanel&Version={0}&Request=getLegacyResponseData&User={1}&Token={2}&Format={3}&SurveyID={4}&EndDate={5}%2000%3A00%3A00&LocalTime=1&ResponsesInProgress=0&LocationData=0".format(version, user, token, format, SurveyID, current_full_date)

There are several data points that the GET request returns: HTTP status code, request headers, etc. I don’t need any of that, just the actual response data. I specify this by specifying I want the .text of the response object

requests.get(“https://survey.qualtrics.com//WRAPI/ControlPanel/api.php?API_SELECT=ControlPanel&Version={0}&Request=getLegacyResponseData&User={1}&Token={2}&Format={3}&SurveyID={4}&EndDate={5}%2000%3A00%3A00&LocalTime=1&ResponsesInProgress=0&LocationData=0".format(version, user, token, format, SurveyID, current_full_date)).text

Ok so I have the data, but its in JSON. I’ll use the .loads() method from the JSON module to convert the data to a Python object in order to work with it

json.loads(requests.get(“https://survey.qualtrics.com//WRAPI/ControlPanel/api.php?API_SELECT=ControlPanel&Version={0}&Request=getLegacyResponseData&User={1}&Token={2}&Format={3}&SurveyID={4}&EndDate={5}%2000%3A00%3A00&LocalTime=1&ResponsesInProgress=0&LocationData=0".format(version, user, token, format, SurveyID, current_full_date)).text)

Ok I now have a large object that I need to extract data out of and convert it to a usable format. Since I am iterating over an object I will need to use Python’s iteritems() method in my for loop. I am only concerned with reading the values of the key, value thus the underscore.

for _, data in json.loads(requests.get(“https://survey.qualtrics.com//WRAPI/ControlPanel/api.php?API_SELECT=ControlPanel&Version={0}&Request=getLegacyResponseData&User={1}&Token={2}&Format={3}&SurveyID={4}&EndDate={5}%2000%3A00%3A00&LocalTime=1&ResponsesInProgress=0&LocationData=0".format(version, user, token, format, SurveyID, current_full_date)).text).iteritems()

Finally, I declare what exactly I am looking to extract from this object as it iterates. The EndDate object returns “03–06–2016 00:00:00” in unicode. I just need the date so I will split it at the space, select the first item in the split list, and convert it to a string

str(data[‘EndDate’].split(‘ ‘)[0]) for _, data in json.loads(requests.get(“https://survey.qualtrics.com//WRAPI/ControlPanel/api.php?API_SELECT=ControlPanel&Version={0}&Request=getLegacyResponseData&User={1}&Token={2}&Format={3}&SurveyID={4}&EndDate={5}%2000%3A00%3A00&LocalTime=1&ResponsesInProgress=0&LocationData=0".format(version, user, token, format, SurveyID, current_full_date)).text).iteritems()

Finally, lets turn all the extracted dates into a list called “end_dates” using a list comprehension

end_dates = [str(data[‘EndDate’].split(‘ ‘)[0]) for _, data in json.loads(requests.get(“https://survey.qualtrics.com//WRAPI/ControlPanel/api.php?API_SELECT=ControlPanel&Version={0}&Request=getLegacyResponseData&User={1}&Token={2}&Format={3}&SurveyID={4}&EndDate={5}%2000%3A00%3A00&LocalTime=1&ResponsesInProgress=0&LocationData=0".format(version, user, token, format, SurveyID, current_full_date)).text).iteritems()]

Bonus Sublime picture. 416 character line!

So now I have a list of dates that surveys were submitted. Now I need to count them by month and see how many responses there were on the previous day. I don’t think Python has case/switch so its all if/elif/else on this one

Cutoff most, but you get the point

Now each month object holds the number of survey responses for that month in it.

Impressions

Collecting the impressions is a very similar procedure. In this case the API returns the date, and the number of impressions that we made on that date. I will parse the date as a string like before, and the impressions as integers in order to add them. I pair those data points in a tuple, and generate a list of tuples called tuplst.

From tuplst I can quickly calculate the all time total impressions, and the number of days the project has been running for. Note that the date here is different from the survey responses date because this API call returns all dates the intercept was active for regardless if there were any responses or not.

total_imp = sum([impression for _, impression in tuplst])
days_ran = len([item[0] for item in tuplst])

I will then iterate over tuplist and add the impressions collected there to my month objects.

Add impressions to month objects

Finally, I will calculate the total number of impressions made by iterating over the months. Here I will also collect the impressions for the current month in order to call them out later.

CSV

Now that all the data has been collected, I can output it into a csv file. To make things more readable I created a few variables for the calculations:

The str/int conversions may be redundant, but better safe than sorry.
Different rows of the CSV. This creates the CSV file pictured at the top of the post.

Portability

So I wrote a script that generates a csv that other people need to look at every few days. For a while, I would make it myself then email it to the necessary parties. However, that got old after a while. Also, I would rather folks be able to check these stats whenever they want instead of having to wait for me.

My best course of action was to make the script an executable that can be distributed at will. I was able to create a Mac app from a python script using the Py2App module. It was very easy, and you get to choose your own icon!

Reporting app icon

Thanks to the Qualtrics API I was able to tie in statistics from separate projects. With a little bit of work upfront I was able to save myself time by making this information easily digestible and portable.