aboutsummaryrefslogtreecommitdiff
path: root/plotgrades.py
diff options
context:
space:
mode:
Diffstat (limited to 'plotgrades.py')
-rwxr-xr-xplotgrades.py57
1 files changed, 43 insertions, 14 deletions
diff --git a/plotgrades.py b/plotgrades.py
index 165d0bc..f3c8baa 100755
--- a/plotgrades.py
+++ b/plotgrades.py
@@ -2,14 +2,16 @@
import argparse
import csv
from functools import partial
+import itertools
import matplotlib.pyplot as plt
import numpy
+from operator import itemgetter
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='"')
+ reader = csv.reader(csvfile, delimiter='\t', quotechar='"')
data = []
header = next(reader)
for row in reader:
@@ -37,9 +39,9 @@ def remove_zeros(matrix):
want to distort the boxplots. This function removes zeros on a per-column
basis."""
m_new = []
- for col in numpy.transpose(matrix):
+ for col in matrix:
m_new.append([v for v in col if v != 0.0])
- return numpy.transpose(m_new)
+ return m_new
def normalise(matrix):
"""Normalise grades to a 0-100 range
@@ -53,15 +55,16 @@ def normalise(matrix):
def strip_header(h):
"""Strip common BlackBoard additions in the CSV header"""
- return h.split('instructor.download.column.total')[0]
+ return h.split('[Total Pts:')[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 != []:
+ for h, col in zip(headers, numpy.transpose(data)):
+ if not all([x == 0.0 for x in col]):
new_headers.append(h)
- new_data.append(d)
+ new_data.append(col)
+
return new_headers, new_data
def header_regex_callback(header, regex='', invert=False):
@@ -97,23 +100,47 @@ def check_participant_callback(headers, data, participant_callback):
new_data.append(d)
return new_data
+def make_groups(headers, data, column):
+ i = 0
+ for h in headers:
+ if column in h:
+ break
+ i += 1
+
+ data = list(numpy.transpose(data))
+ data.sort(key=itemgetter(i))
+ data = [list(x) for _, x in itertools.groupby(data, itemgetter(i))]
+ new_headers = []
+ new_data = []
+ j = 0
+ for h in headers:
+ for group in data:
+ if not all([x[j] == 0.0 for x in group]):
+ new_headers.append(h + ' (group: ' + str(group[0][i]) + ')')
+ new_data.append([x[j] for x in group])
+ j += 1
+
+ return new_headers, new_data
+
def plotgrades(headers, data, skip=0,
- participant_callback=lambda x:True, header_callback=lambda x:True):
+ participant_callback=lambda x:True, header_callback=lambda x:True,
+ group_participants_column=None):
"""Plot grades corresponding to headers in a boxplot"""
data = check_participant_callback(headers, data, participant_callback)
- headers, data = headers[skip:], data[skip:]
+ headers, data = map(strip_header, headers[skip:]), data[skip:]
data = parse_floats(data)
- data = remove_zeros(data)
headers, data = remove_empty_lists(headers, data)
+ if group_participants_column is not None:
+ headers, data = make_groups(headers, data, group_participants_column)
+ data = remove_zeros(data)
headers, data = check_header_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)
+ plt.xticks(range(1, len(data) + 1), headers, rotation=90)
ax.set_ylim([0,100])
plt.show()
@@ -128,6 +155,8 @@ def parse_args():
help='Restrict what grades are shown with a regex')
pars.add_argument('-wh', '--where-has', metavar='regex', default='',
help='Only count participants with a grade for a matching item')
+ pars.add_argument('-g', '--group-by', metavar='column', default=None,
+ help='Group participants by this column')
pars.add_argument('-iw', '--invert-where', action='store_true',
help='Invert --where regex')
pars.add_argument('-iwh', '--invert-where-has', action='store_true',
@@ -146,8 +175,8 @@ def main():
header_callback=partial(header_regex_callback,
regex=args.where, invert=args.invert_where),
participant_callback=partial(participant_wherehas_callback,
- regex=args.where_has, invert=args.invert_where_has))
+ regex=args.where_has, invert=args.invert_where_has),
+ group_participants_column=args.group_by)
if __name__ == '__main__':
main()
-