aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md7
-rwxr-xr-xplotgrades.py125
2 files changed, 132 insertions, 0 deletions
diff --git a/README.md b/README.md
index 60ba9cf..0f5de98 100644
--- a/README.md
+++ b/README.md
@@ -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()
+