Inf1-CG Assignment 1 Marking Results

In [6]:
import pandas
from holoviews import *
from holoviews.interface.seaborn import DFrame
import numpy as np
%load_ext holoviews.ipython
Welcome to the HoloViews IPython extension! (http://ioam.github.io/holoviews/)
Available magics: %compositor, %opts, %output, %params, %%labels, %%opts, %%output
In [9]:
marking_df = pandas.read_csv('./Student_Marks.csv')[0:84]
marking_df['Total'] = [float(t.split('%')[0]) for t in marking_df['Total']]
marking_df['Index'] = range(1,len(marking_df)+1)
In [10]:
%output size=200

Marking Bias?

First things first we'll check whether the marking process didn't suffer from considerable bias over time, which is possible since the scores are indexed consecutively according to how I marked them.

In [11]:
%%opts Regression.By_Index [apply_databounds=True aspect=0.6] Regression.By_Group [apply_databounds=True aspect=0.05]
DFrame(marking_df).regression('Index', 'Total', group='By_Index', reduce_fn=np.mean, extents=(1,0,84,100)) +\
DFrame(marking_df).regression('Tutorial Group', 'Total', group='By_Group', reduce_fn=np.mean, extents=(1,0,8,100))
Out[11]:

As we can see the data does not show any trend that isn't explained by the variability in performance between the Tutorial Groups.

In [12]:
%%output dpi=120
%%opts Regression [apply_databounds=True]
DFrame(marking_df).regression('Index', 'Total', mdims=['Tutorial Group'], extents=(None, 0, None, 100)).layout(['Tutorial Group'])
Out[12]:

We can break this down by Tutorial Group to check if there are any trends in each group, we may have missed. Trends that carry through multiple groups would be indicative of bias, this is not the case.

The Distribution of Student Scores

In [13]:
%%opts Distribution (bins=20 hist_kws={'range':(0,100)} kde_kws={'cut':0} rug=True color='indianred') DFrame (cut=0)
Distribution(marking_df.Total, key_dimensions=['Total Score']) +\
DFrame(marking_df, group='Total Score', plot_type='violinplot', x='Tutorial Group', y='Total')
Out[13]:

Now the mean total score broken down by Tutorial Group.

In [14]:
%%opts DFrame (kind='bar' aspect=2)
DFrame(marking_df, plot_type='factorplot', x='Tutorial Group', y='Total')
Out[14]:

Finally a breakdown into degree classification:

In [15]:
first = sum(marking_df.Total > 70)
twoone = sum((60 <= marking_df.Total) & (marking_df.Total < 70))
twotwo = sum((50 <= marking_df.Total) & (marking_df.Total < 60))
third = sum((40 <= marking_df.Total) & (marking_df.Total< 50))
fail = sum(marking_df.Total < 40)
nstudents = float(len(marking_df.Total))
In [16]:
print "Total with a First: {} ({} %)".format(first, 100*first/float(nstudents))
print "Total with a 2:1: {} ({} %)".format(twoone, 100*twoone/float(nstudents))
print "Total with a 2:2: {} ({} %)".format(twotwo, 100*twotwo/float(nstudents))
print "Total with a Third: {} ({} %)".format(third, 100*third/float(nstudents))
print "Total with a Fail: {} ({} %)".format(fail, 100*fail/float(nstudents))
Total with a First: 47 (55.9523809524 %)
Total with a 2:1: 10 (11.9047619048 %)
Total with a 2:2: 6 (7.14285714286 %)
Total with a Third: 5 (5.95238095238 %)
Total with a Fail: 15 (17.8571428571 %)

And here are some basic stats:

In [17]:
marking_df.Total.describe()
Out[17]:
count    84.000000
mean     66.455357
std      25.484408
min       7.500000
25%      51.875000
50%      75.000000
75%      87.625000
max      96.000000
Name: Total, dtype: float64

The Numerical Answers

In [18]:
answers = [c for c in marking_df.columns if 'A1' in c]
stacked = []
for sn, group in marking_df.groupby('Student Number'):
    group_df = pandas.DataFrame(group.filter(answers).stack()).reset_index(level=1)
    group_df.insert(0, 'Student Number', sn)
    tg = list(marking_df[marking_df['Student Number'] == sn]['Tutorial Group'])[0]
    group_df.insert(0, 'Tutorial Group', tg)
    stacked.append(group_df)
stacked_answers_df = pandas.concat(stacked)
stacked_answers_df.columns =['Tutorial Group', 'Student Number', 'Answer', 'Result']
stacked_answers_df['Answer'] = [int(q[1]) for q in stacked_answers_df['Answer']]
stacked_answers_df.index = list(range(len(stacked_answers_df)))

Let's get an overview of the Distributions of answers.

In [19]:
%%output size=100 dpi=100
np.sum([DFrame(stacked_answers_df, plot_type='boxplot',
               x='Answer', y='Result', label='Answer %d' % a).select(Answer=a)
        for a in set(stacked_answers_df.Answer)])
Out[19]:

The correct or acceptable answers were:

2. 0.0479
3. 0/0.1712/0.2166
4. 0
5. 3/1000
7. 0.1667
9. 0.01-0.2

Now let's have a more detailed look at the scores for each question

In [20]:
questions = ['Question %d' % q for q in range(1,12)]
In [21]:
stacked = []
for idx, (sn, group) in enumerate(marking_df.groupby('Student Number')):
    group_df = pandas.DataFrame(group.filter(questions).stack()).reset_index(level=1)
    group_df.insert(0, 'Student Number', sn)
    tg = list(marking_df[marking_df['Student Number'] == sn]['Tutorial Group'])[0]
    group_df.insert(0, 'Tutorial Group', tg)
    group_df.insert(0, 'Index', idx)
    stacked.append(group_df)
stacked_df = pandas.concat(stacked)
stacked_df.columns =['Index', 'Tutorial Group', 'Student Number', 'Question', 'Mark']
stacked_df['Question'] = [int(q[-2:].strip()) for q in stacked_df['Question']]
stacked_df.index = list(range(len(stacked_df)))
qdim = Dimension('Question', type=int)
In [22]:
print "The overall mean score per question was:", stacked_df.Mark.mean()
print "With a standard deviation of:", stacked_df.Mark.std()
The overall mean score per question was: 0.705368763557
With a standard deviation of: 0.392465583352

By Tutorial Group

In [23]:
%%output size=300
%%opts DFrame (kind='point' aspect=2 palette='Set2')
DFrame(stacked_df, plot_type='factorplot', x='Question', y='Mark', x2='Tutorial Group')
Out[23]:

And again a broken down individually:

In [24]:
%%output dpi=120
%%opts DFrame (kind='point' hue='Tutorial Group' col='Tutorial Group' palette='Set2' col_wrap=4)
DFrame(stacked_df, plot_type='factorplot', x='Question', y='Mark')
Out[24]:

By Student

In [25]:
%%output size=400 dpi=80
%%opts Bars [aspect=25.0 xrotation=90]
DFrame(stacked_df, dimensions={'Question': qdim}).bars('Index', 'Mark', reduce_fn=np.mean)
Out[25]:
In [26]:
%%output dpi=120
%%opts DFrame (kind='point' hue='Index' col='Index' palette='Set2' col_wrap=4)
DFrame(stacked_df, plot_type='factorplot', x='Question', y='Mark')
Out[26]:

Finally let's look at the marks for each student and question as a heatmap.

In [27]:
%%output size=450 dpi=100
%%opts HeatMap [yticks=100 show_values=False] Histogram [xticks=6]
hm_df = stacked_df.filter(['Question', 'Index', 'Mark'])
DFrame(hm_df, dimensions={'Question': qdim}).heatmap(['Question', 'Index'], 'Mark').hist(num_bins=11)
Out[27]: