Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
zesje
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package registry
Container Registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Réouven ASSOULY
zesje
Commits
a9754f46
Commit
a9754f46
authored
7 years ago
by
Anton Akhmerov
Browse files
Options
Downloads
Patches
Plain Diff
implement exam summary statistics image
parent
14c0da6a
No related branches found
No related tags found
No related merge requests found
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
zesje/api.py
+6
-0
6 additions, 0 deletions
zesje/api.py
zesje/helpers/db_helper.py
+71
-2
71 additions, 2 deletions
zesje/helpers/db_helper.py
zesje/resources/summary_plot.py
+110
-0
110 additions, 0 deletions
zesje/resources/summary_plot.py
with
187 additions
and
2 deletions
zesje/api.py
+
6
−
0
View file @
a9754f46
...
...
@@ -8,6 +8,7 @@ from .resources.students import Students
from
.resources.submissions
import
Submissions
from
.resources
import
signature
from
.resources
import
images
from
.resources
import
summary_plot
from
.resources.problems
import
Problems
from
.resources.feedback
import
Feedback
...
...
@@ -47,3 +48,8 @@ api_bp.add_url_rule(
'
solution
'
,
images
.
get
,
)
api_bp
.
add_url_rule
(
'
/images/summary/<int:exam_id>
'
,
'
exam_summary
'
,
summary_plot
.
get
,
)
This diff is collapsed.
Click to expand it.
zesje/helpers/db_helper.py
+
71
−
2
View file @
a9754f46
from
..models
import
Problem
import
pandas
from
pony
import
orm
from
collections
import
namedtuple
,
OrderedDict
,
ChainMap
from
..models
import
Exam
,
Problem
,
Student
,
Solution
from
.
import
yaml_helper
def
update_exam
(
exam
,
existing_yaml
,
new_yaml
):
...
...
@@ -16,8 +20,73 @@ def update_exam(exam, existing_yaml, new_yaml):
new_problem_names
=
list
(
name
for
name
in
new_widgets
.
index
if
name
!=
'
studentnr
'
)
problems
=
list
(
Problem
.
select
(
lambda
p
:
p
.
exam
==
exam
)
.
order_by
(
lambda
p
:
p
.
id
))
for
problem
,
name
in
zip
(
problems
,
new_problem_names
):
problem
.
name
=
name
def
solution_data
(
exam_id
,
student_id
):
"""
Return Python datastructures corresponding to the student submission.
"""
with
orm
.
db_session
:
exam
=
Exam
[
exam_id
]
student
=
Student
[
student_id
]
if
any
(
i
is
None
for
i
in
(
exam
,
student
)):
raise
RuntimeError
(
'
Student did not make a
'
'
submission for this exam
'
)
results
=
[]
for
problem
in
exam
.
problems
.
order_by
(
Problem
.
id
):
if
not
orm
.
count
(
problem
.
solutions
.
feedback
):
# Nobody received any grade for this problem
continue
problem_data
=
{
'
name
'
:
problem
.
name
,
'
max_score
'
:
orm
.
max
(
problem
.
feedback_options
.
score
,
default
=
0
)
}
solutions
=
Solution
.
select
(
lambda
s
:
s
.
problem
==
problem
and
s
.
submission
.
student
==
student
)
problem_data
[
'
feedback
'
]
=
[
{
'
short
'
:
fo
.
text
,
'
score
'
:
fo
.
score
,
'
description
'
:
fo
.
description
}
for
solution
in
solutions
for
fo
in
solution
.
feedback
]
problem_data
[
'
score
'
]
=
sum
(
i
[
'
score
'
]
or
0
for
i
in
problem_data
[
'
feedback
'
])
problem_data
[
'
remarks
'
]
=
'
\n\n
'
.
join
(
sol
.
remarks
for
sol
in
solutions
if
sol
.
remarks
)
results
.
append
(
problem_data
)
student
=
student
.
to_dict
()
student
[
'
total
'
]
=
sum
(
i
[
'
score
'
]
for
i
in
results
)
return
student
,
results
def
full_exam_data
(
exam_id
):
"""
Compute all grades of an exam as a pandas DataFrame.
"""
with
orm
.
db_session
:
students
=
sorted
(
Exam
[
exam_id
].
submissions
.
student
.
id
)
data
=
[
solution_data
(
exam_id
,
student_id
)
for
student_id
in
students
]
students
=
pandas
.
DataFrame
({
i
[
0
][
'
id
'
]:
i
[
0
]
for
i
in
data
}).
T
del
students
[
'
id
'
]
results
=
{}
for
result
in
data
:
for
problem
in
result
[
1
]:
name
=
problem
.
pop
(
'
name
'
)
problem
[(
name
,
'
remarks
'
)]
=
problem
.
pop
(
'
remarks
'
)
for
fo
in
problem
.
pop
(
'
feedback
'
):
problem
[(
name
,
fo
[
'
short
'
])]
=
fo
[
'
score
'
]
problem
[(
name
,
'
total
'
)]
=
problem
.
pop
(
'
score
'
)
problem
.
pop
(
'
max_score
'
)
results
[
result
[
0
][
'
id
'
]]
=
dict
(
ChainMap
({(
'
total
'
,
'
total
'
):
result
[
0
][
'
total
'
]},
*
result
[
1
]))
return
pandas
.
DataFrame
(
results
).
T
This diff is collapsed.
Click to expand it.
zesje/resources/summary_plot.py
0 → 100644
+
110
−
0
View file @
a9754f46
import
os
from
io
import
BytesIO
from
flask
import
abort
,
Response
,
current_app
as
app
from
pony
import
orm
import
pandas
import
numpy
as
np
from
..models
import
Exam
,
Submission
from
..helpers.db_helper
import
full_exam_data
import
matplotlib
matplotlib
.
use
(
'
agg
'
)
import
seaborn
from
matplotlib
import
pyplot
@orm.db_session
def
get
(
exam_id
):
"""
Plot exam summary statistics.
Parameters
----------
exam_id : int
Returns
-------
Image (JPEG mimetype)
"""
try
:
exam
=
Exam
[
exam_id
]
except
KeyError
:
abort
(
404
)
scores
=
{
problem
.
name
:
max
(
list
(
problem
.
feedback_options
.
score
)
+
[
0
])
for
problem
in
exam
.
problems
}
scores
[
'
total
'
]
=
sum
(
scores
.
values
())
full_scores
=
full_exam_data
(
exam_id
)
# Full exam data has multilevel columns (includes detailed feedback), we
# flatten them out first.
problem_scores
=
full_scores
.
iloc
[
:,
full_scores
.
columns
.
get_level_values
(
1
)
==
'
total
'
]
problem_scores
.
columns
=
problem_scores
.
columns
.
get_level_values
(
0
)
# Exclude empty columns from statistics
problem_scores
=
problem_scores
.
loc
[:,
~
(
problem_scores
==
0
).
all
()]
seaborn
.
set
()
seaborn
.
set_context
(
"
notebook
"
,
font_scale
=
1.5
,
rc
=
{
"
lines.linewidth
"
:
2.5
})
cm
=
matplotlib
.
cm
.
magma
# define the bins and normalize
bounds
=
np
.
linspace
(
0
,
1
,
21
)
norm
=
matplotlib
.
colors
.
BoundaryNorm
(
bounds
,
cm
.
N
)
maxes
=
pandas
.
DataFrame
(
problem_scores
.
max
())
maxes
[
'
max_rubric
'
]
=
maxes
.
index
maxes
=
maxes
.
replace
({
'
max_rubric
'
:
scores
}).
max
(
axis
=
1
)
corrs
=
{
column
:
(
problem_scores
[
column
]
.
astype
(
float
)
.
corr
(
problem_scores
.
total
.
subtract
(
problem_scores
[
column
])
.
astype
(
float
)
).
round
(
2
)
)
for
column
in
problem_scores
if
column
!=
'
total
'
}
alpha
=
((
len
(
problem_scores
)
-
1
)
/
(
len
(
problem_scores
)
-
2
)
*
(
1
-
problem_scores
.
var
()[:
-
1
].
sum
()
/
problem_scores
.
total
.
var
())
)
vals
=
[
problem_scores
[
i
].
value_counts
(
normalize
=
True
).
sort_index
().
cumsum
()
for
i
in
problem_scores
]
data
=
np
.
array
(
[
(
-
i
,
upper
-
lower
,
lower
,
num
/
maxes
.
ix
[
i
])
for
i
,
val
in
enumerate
(
vals
)
for
num
,
upper
,
lower
in
zip
(
val
.
index
,
val
.
data
,
[
0
]
+
list
(
val
.
data
[:
-
1
])
)
]
).
T
fig
=
pyplot
.
figure
(
figsize
=
(
12
,
9
))
ax
=
fig
.
add_subplot
(
1
,
1
,
1
)
ax
.
barh
(
data
[
0
],
data
[
1
],
0.5
,
data
[
2
],
color
=
cm
(
norm
(
data
[
3
])),
align
=
'
center
'
)
ax
.
set_yticks
(
np
.
arange
(
0
,
-
len
(
problem_scores
.
columns
),
-
1
));
ax
.
set_yticklabels
(
[
f
'
{
i
}
($Rir=
{
corrs
[
i
]
:
.
2
f
}
$)
'
for
i
in
problem_scores
.
columns
[:
-
1
]]
+
[
f
'
total: ($
\\
alpha =
{
alpha
:
.
2
f
}
$)
'
]
)
ax
.
set_xlabel
(
'
fraction of students
'
)
ax
.
set_xlim
(
-
0.025
,
1.025
)
sm
=
matplotlib
.
cm
.
ScalarMappable
(
cmap
=
cm
,
norm
=
norm
)
sm
.
_A
=
[]
colorbar
=
fig
.
colorbar
(
sm
)
colorbar
.
set_ticks
(
np
.
linspace
(
0
,
1
,
11
))
colorbar
.
set_label
(
'
score percentage
'
)
pyplot
.
tight_layout
()
image
=
BytesIO
()
pyplot
.
savefig
(
image
)
return
Response
(
image
.
getvalue
(),
200
,
mimetype
=
'
image/jpeg
'
)
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment