Skip to content

Instantly share code, notes, and snippets.

@gbishop
Created February 17, 2016 01:43
Show Gist options
  • Save gbishop/9329f1f9e800ac83aa5e to your computer and use it in GitHub Desktop.
Save gbishop/9329f1f9e800ac83aa5e to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"metadata": {},
"cell_type": "markdown",
"source": "# Partial Credit\n\nA simple tool to help me assign partial credit on exams. The notebooks have already been graded by automatic grader. I'm only looking at questions that recieved less than full credit. I select a _tag_ to identify the question and then look at the erroneous solutions all at once. I assign a number 0-4 to determine the fraction of total credit they should recieve."
},
{
"metadata": {
"collapsed": false,
"trusted": true
},
"cell_type": "code",
"source": "from bs4 import BeautifulSoup\nimport pandas\nimport numpy as np\nimport os.path as osp\nimport re\nimport Args\nimport gzip\nfrom ipywidgets import widgets\nfrom IPython.display import display, HTML, Javascript\n\n# course settings\nclassDir = '/afs/unc/proj/courses/comp116-s16/'\ngradedDir = classDir + 'public_html/graded'\ngradedURL = 'https://wwwx.cs.unc.edu/Courses/comp116-s16/graded'\nsubmissions = classDir + 'files'",
"execution_count": 1,
"outputs": [
{
"output_type": "display_data",
"data": {
"application/javascript": "\n require(['base/js/namespace'], function(Jupyter) {\n var ready = function() {\n var query = window.location.search.substring(1);\n Jupyter.notebook.kernel.execute(\"import Args; Args._grabQS('\" + query + \"')\");\n };\n // If the kernel is ready when we get here the event apparently doesn't fire. They should\n // use promises instead.\n if (Jupyter.notebook.kernel) {\n ready();\n } else {\n Jupyter.notebook.events.on('kernel_ready.Kernel', ready);\n }\n });\n ",
"text/plain": "<IPython.core.display.Javascript object>"
},
"metadata": {}
}
]
},
{
"metadata": {},
"cell_type": "markdown",
"source": "## Helpers\n\nShould I move these to another file in the name of not having to step through them?"
},
{
"metadata": {
"collapsed": false,
"trusted": true
},
"cell_type": "code",
"source": "def tagSort(tags):\n '''Sort my tags in a natural order'''\n return sorted(tags,\n key=lambda tag: [ s.isdigit() and int(s) or s\n for s in re.findall(r'\\d+|\\D+', tag)\n ])\n\ndef fetchInOut(URL, tag):\n '''Grab the input and output chunks from the rendered html for the notebook'''\n path = URL.replace(gradedURL, gradedDir) + '.gz'\n soup = BeautifulSoup(gzip.open(path, 'r'))\n reg = re.compile(r'''check\\s*\\(\\s*['\"]%s['\"]''' % tag)\n for e in soup.find_all('div', class_='code_cell'):\n if reg.search(e.text):\n icell = str(e.find('div', class_='input_area'))\n ocell = str(e.find('div', class_='output_wrapper'))\n return (icell, ocell)\n else:\n return ('<div>Not found</div>', '<div>Not found</div>')\n \ndef sortKey(tag, code, result):\n '''return a key to group similar errors'''\n # eliminate html\n result = re.sub(r'<[^>]*>', ' ', result)\n # eliminate comments\n result = re.sub(r'#.*$', '', result, 0, re.M)\n key = []\n # look for exceptions\n m = re.search(r'^\\s*(\\w+)\\s+Traceback', result, re.M)\n if m:\n key.append(m.group(1))\n # look for my type messages\n ndx = result.find(tag + ' incorrect')\n if ndx >= 0:\n key.append(result[ndx:])\n # use the result\n key.append(result)\n key = ' '.join(key)\n # eliminate whitespace\n key = re.sub(r'[\\s\\n]+', ' ', key)\n return key\n\ndef fetchErrors(tag):\n '''Pull the input and output for each erroneous solution for this tag'''\n col = partial[tag]\n errors = []\n for userid in col[col.isnull()].index:\n code, result = fetchInOut(raw.ix[userid, 'url'], tag)\n errors.append((sortKey(tag, code, result), userid, code, result))\n errors.sort()\n return [ t[1:] for t in errors ]",
"execution_count": 2,
"outputs": []
},
{
"metadata": {},
"cell_type": "markdown",
"source": "## Initialize"
},
{
"metadata": {
"collapsed": false,
"trusted": true
},
"cell_type": "code",
"source": "args = Args.Parse(\n exam='A2'\n )\n\n# load the raw grades\nraw = pandas.read_csv(osp.join(args.exam, 'raw.csv'), index_col='userid')\n\n# list the tags\ntags = [ tag for tag in raw.keys() if tag[0].isupper() ]\ntags = tagSort(tags)\n\n# load or create the partials\npname = osp.join(args.exam, 'partial.csv')\nif osp.exists(pname):\n partial = pandas.read_csv(pname, index_col='userid')\n # check for new graded assignments to add to partial\n newusers = raw.index[~raw.index.isin(partial.index)]\n if len(newusers) > 0:\n newpartial = raw.ix[newusers, tags]\n newpartial = newpartial.where(newpartial == 1)\n partial = pandas.concat([partial, newpartial])\nelse:\n partial = raw[tags]\n # set scores < 1 to nan\n partial = partial.where(partial == 1)",
"execution_count": 4,
"outputs": []
},
{
"metadata": {},
"cell_type": "markdown",
"source": "## Hack to let me tab from field to field when grading"
},
{
"metadata": {
"collapsed": false,
"trusted": true
},
"cell_type": "code",
"source": "%%javascript\n$(document).on('focus', '.widget-numeric-text', function() {\n this.scrollIntoView(false);\n});",
"execution_count": 13,
"outputs": [
{
"output_type": "display_data",
"data": {
"application/javascript": "$(document).on('focus', '.widget-numeric-text', function() {\n this.scrollIntoView(false);\n});",
"text/plain": "<IPython.core.display.Javascript object>"
},
"metadata": {}
}
]
},
{
"metadata": {
"collapsed": false,
"trusted": true
},
"cell_type": "code",
"source": "def showTag(sender):\n '''Assign partial credit to a single tag'''\n label = tagChooser.value\n if not label:\n return\n tag = label.split(': ')[1]\n grades = {}\n for userid, code, result in fetchErrors(tag):\n box = widgets.VBox()\n i = widgets.HTML(code)\n o = widgets.HTML(result)\n l = widgets.HTML('''<a href=\"{}\" target=\"_blank\" tabindex=\"-1\">Notebook</a>'''.format(raw.ix[userid, 'url']))\n t = widgets.BoundedIntText(value=0, min=0, max=4, description='Credit')\n h = widgets.HBox()\n h.children = [t, l]\n box.children = [i, o, h]\n box.border_color = 'green'\n box.border_width = 4\n display(box)\n grades[userid] = (t, box)\n b = widgets.Button(description='Submit', width='5em', background_color='red')\n def submit(send):\n for sid, pair in grades.items():\n c, box = pair\n partial.ix[sid, tag] = c.value\n box.close()\n send.close()\n partial.to_csv(pname)\n tagChooser.options = getLabels()\n # scroll back to the chooser\n display(Javascript('''\n $('h2#Grading').get(0).scrollIntoView(true);\n '''))\n b.on_click(submit)\n display(b)\n\ndef getLabels():\n '''Get the ungraded counts for each tag'''\n labels = [ '%3d: %s' % (count, tag) for tag, count in partial.isnull().sum().items()\n if count > 0 ]\n labels.sort()\n return [''] + labels\n\ntagChooser = widgets.Dropdown(description='tag: ', options=getLabels())\ntagChooser.on_trait_change(showTag, 'value')",
"execution_count": 14,
"outputs": []
},
{
"metadata": {},
"cell_type": "markdown",
"source": "## Grading"
},
{
"metadata": {
"collapsed": false,
"trusted": true
},
"cell_type": "code",
"source": "display(tagChooser)",
"execution_count": 15,
"outputs": [
{
"output_type": "display_data",
"data": {
"application/javascript": "\n $('h2#Grading').get(0).scrollIntoView(true);\n ",
"text/plain": "<IPython.core.display.Javascript object>"
},
"metadata": {}
}
]
},
{
"metadata": {
"collapsed": true,
"trusted": true
},
"cell_type": "code",
"source": "",
"execution_count": null,
"outputs": []
}
],
"metadata": {
"kernelspec": {
"name": "python3",
"display_name": "Python 3",
"language": "python"
},
"language_info": {
"mimetype": "text/x-python",
"nbconvert_exporter": "python",
"name": "python",
"pygments_lexer": "ipython3",
"version": "3.4.3",
"file_extension": ".py",
"codemirror_mode": {
"version": 3,
"name": "ipython"
}
}
},
"nbformat": 4,
"nbformat_minor": 0
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment