diff options
author | Camil Staps | 2015-10-06 21:52:15 +0200 |
---|---|---|
committer | Camil Staps | 2015-10-06 21:55:42 +0200 |
commit | 97cf67b8ec60674a3b914ef5ffb5c8aa81c9769f (patch) | |
tree | 4e58e5afaca1c65fe73f7be4e4a2d8ef0ad155b9 | |
parent | Initial commit (diff) |
Initial readme; plotgrades.py
-rw-r--r-- | README.md | 7 | ||||
-rwxr-xr-x | plotgrades.py | 125 |
2 files changed, 132 insertions, 0 deletions
@@ -1 +1,8 @@ # blackboard-tools + +Some small tools for the BlackBoard ELO + +Copyright © 2015 [Camil Staps][cs]. Licensed under MIT (see the LICENSE file for more details). + +[cs]:mailto:info@camilstaps.nl + diff --git a/plotgrades.py b/plotgrades.py new file mode 100755 index 0000000..9973627 --- /dev/null +++ b/plotgrades.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +import argparse +import csv +from functools import partial +import matplotlib.pyplot as plt +import numpy +import re + +def readcsv(filename): + """Read a (BlackBoard) CSV file into a header and data""" + with open(filename, 'r') as csvfile: + reader = csv.reader(csvfile, delimiter=',', quotechar='"') + data = [] + header = next(reader) + for row in reader: + data.append(row) + return header, numpy.array(data) + +def parse_float(f): + """Parse a string into a float and return 0. when that fails""" + try: + return float(f) + except ValueError: + return 0. + +def parse_floats(matrix): + """Apply parse_float on all elements of a matrix""" + m_new = [] + for row in matrix: + m_new.append([parse_float(v) for v in row]) + return m_new + +def remove_zeros(matrix): + """Remove zeros from a matrix + + Typically, BlackBoard grade lists will contain many zeros that we don't + want to distort the boxplots. This function removes zeros on a per-column + basis.""" + m_new = [] + for col in numpy.transpose(matrix): + m_new.append([v for v in col if v != 0.0]) + return numpy.transpose(m_new) + +def normalise(matrix): + """Normalise grades to a 0-100 range + + Some grades are given in a 0-10 range, others in a 0-100. This function + normalises *but these two* into a 0-100 range""" + m_new = [] + for row in matrix: + m_new.append(row if max(row) > 10.0 else [10 * i for i in row]) + return m_new + +def strip_header(h): + """Strip common BlackBoard additions in the CSV header""" + return h.split('instructor.download.column.total')[0] + +def remove_empty_lists(headers, data): + """Remove empty columns and the corresponding headers from a matrix""" + new_headers, new_data = [], [] + for h, d in zip(headers, data): + if d != []: + new_headers.append(h) + new_data.append(d) + return new_headers, new_data + +def regex_callback(header, regex='', invert=False): + """Check whether a regex occurs in a header + + This is an example of a possible callback function.""" + match = re.compile(regex).search(header) != None + return match != invert + +def check_callback(headers, data, header_callback): + """For each header, check that we want to show it, and remove data if not""" + new_headers, new_data = [], [] + for h, d in zip(headers, data): + if header_callback(h): + new_headers.append(h) + new_data.append(d) + return new_headers, new_data + +def plotgrades(headers, data, header_callback=lambda x:True): + """Plot grades corresponding to headers in a boxplot""" + data = parse_floats(data) + data = remove_zeros(data) + headers, data = remove_empty_lists(headers, data) + headers, data = check_callback(headers, data, header_callback) + data = normalise(data) + headers = [strip_header(h) for h in headers] + + if len(data) > 0: + plt.boxplot(data) + + ax = plt.gca() + plt.xticks(range(0, len(data) + 1), [''] + headers, rotation=90) + ax.set_ylim([0,100]) + + plt.show() + +def parse_args(): + """Parse command line arguments""" + pars = argparse.ArgumentParser(description='Plot BlackBoard grades') + + pars.add_argument('-s', '--skip', metavar='n', type=int, default=0, + help='Skip the first n columns') + pars.add_argument('-w', '--where', metavar='regex', default='', + help='Restrict what grades are shown with a regex') + pars.add_argument('-i', '--invert', action='store_true', + help='Invert --where regex') + pars.add_argument('filename', metavar='file', + help='The CSV file as exported by BlackBoard') + + return pars.parse_args() + +def main(): + """Plot BlackBoard grades from a CSV file in boxplots""" + args = parse_args() + headers, data = readcsv(args.filename) + plotgrades(headers[args.skip:], data[:,args.skip:], + partial(regex_callback, regex=args.where, invert=args.invert)) + +if __name__ == '__main__': + main() + |