1
0
Fork 0
mirror of https://codeberg.org/Mo8it/AdvLabDB.git synced 2024-12-04 22:40:30 +00:00

Fix analysis

This commit is contained in:
Mo 2022-09-24 19:29:56 +02:00
parent a70e01114c
commit 0242b4389d
5 changed files with 212 additions and 159 deletions

View file

@ -1,8 +1,5 @@
from base64 import b64encode
from io import BytesIO
from pathlib import Path
import numpy as np
from flask import flash, has_request_context, redirect
from flask_admin import Admin as FlaskAdmin
from flask_admin import expose
@ -15,7 +12,6 @@ from flask_security.changeable import admin_change_password
from flask_security.utils import hash_password
from flask_wtf import FlaskForm
from flask_wtf.file import FileAllowed, FileField, FileRequired
from matplotlib.figure import Figure
from sqlalchemy import and_, not_, or_, select
from werkzeug.utils import secure_filename
from wtforms.fields import (
@ -60,6 +56,7 @@ from .advlabdb_independent_funs import (
flashRandomPassword,
str_without_semester_formatter,
)
from .analysis import assistant_marks_analysis, final_part_marks_analysis
from .custom_classes import (
SecureAdminBaseView,
SecureAdminIndexView,
@ -1372,154 +1369,22 @@ class ActionsView(SecureAdminBaseView):
class AnalysisView(SecureAdminBaseView):
class AnalysisForm(FlaskForm):
assistantMarksSubmit = SubmitField(
label="Assistant's marks",
assistant_marks_submit = SubmitField(
label="Active assistant's marks in all semesters",
)
finalPartMarksSubmit = SubmitField(
label="Final part marks",
final_part_marks_submit = SubmitField(
label="Final part marks in active semester",
)
@staticmethod
def htmlFig(fig):
buf = BytesIO()
fig.savefig(buf, format="png")
return b64encode(buf.getbuffer()).decode("ascii")
@staticmethod
def markHist(data, title):
fig = Figure()
ax = fig.subplots()
ax.set_xlim(MIN_MARK - 0.5, MAX_MARK + 0.5)
ax.set_xticks(np.arange(MAX_MARK + 1))
ax.set_xlabel("Mark")
N = data.size
title += f"\nN = {N}"
if N > 0:
ax.hist(
data,
bins=np.arange(MAX_MARK) - 0.5,
)
ax.set_yticks(np.arange(N + 1))
title += f" | mean = {round(np.mean(data), 1)}"
else:
ax.set_yticks(np.arange(2))
ax.set_title(title)
return AnalysisView.htmlFig(fig)
@staticmethod
def get_experiment_marks(assistant, attr):
data = []
for experimentMark in assistant.experiment_marks:
mark = getattr(experimentMark, attr)
if mark is not None:
data.append(mark)
return np.array(data)
@staticmethod
def markHists(markType, activeAssistants):
attr = markType.lower() + "_mark"
markTypeTitleAddition = f" | {markType} marks"
marks = [AnalysisView.get_experiment_marks(assistant, attr) for assistant in activeAssistants]
hists = [
AnalysisView.markHist(
data=marks[i],
title=str(activeAssistants[i]) + markTypeTitleAddition,
)
for i in range(len(marks))
]
hists.append(
AnalysisView.markHist(
data=np.hstack(marks),
title="All" + markTypeTitleAddition,
)
)
return hists
@staticmethod
def get_final_part_marks(part):
data = []
for partStudent in part.part_students:
mark = partStudent.final_part_mark
if mark is not None:
data.append(mark)
return np.array(data)
@expose("/", methods=("GET", "POST"))
def index(self):
form = AnalysisView.AnalysisForm()
if form.validate_on_submit():
if form.assistantMarksSubmit.data:
activeAssistants = assistantQueryFactory()
oralMarkHists = AnalysisView.markHists("Oral", activeAssistants)
protocolMarkHists = AnalysisView.markHists("Protocol", activeAssistants)
return self.render(
"analysis/assistant_marks.jinja.html",
histIndices=range(len(oralMarkHists)),
oralMarkHists=oralMarkHists,
protocolMarkHists=protocolMarkHists,
)
if form.finalPartMarksSubmit.data:
parts = current_user.active_semester.parts
activeSemesterFinalPartMarksHists = [
AnalysisView.markHist(
data=AnalysisView.get_final_part_marks(part),
title=str(part),
)
for part in parts
]
semesters = Semester.sortedSemestersStartingWithNewest()
meanFinalPartMarks = np.flip(
[
np.mean(
np.hstack(
[
[partStudent.final_part_mark for partStudent in part.part_students]
for part in semester.parts
]
)
)
for semester in semesters
]
)
fig = Figure()
lenMeanFinalPartMarks = len(meanFinalPartMarks)
if lenMeanFinalPartMarks > 0:
ax = fig.subplots()
x = range(1, lenMeanFinalPartMarks + 1)
ax.plot(
x,
meanFinalPartMarks,
marker="d",
)
# TODO: Change ticks to semester labels
# TODO: Check linestyle
ax.set_xticks(x)
ax.set_xlim(0.5, x[-1] + 0.5)
meanFinalPartMarksPlot = AnalysisView.htmlFig(fig)
return self.render(
"analysis/final_part_marks.jinja.html",
activeSemesterFinalPartMarksHists=activeSemesterFinalPartMarksHists,
meanFinalPartMarksPlot=meanFinalPartMarksPlot,
)
if form.assistant_marks_submit.data:
return assistant_marks_analysis(self)
elif form.final_part_marks_submit.data:
return final_part_marks_analysis(self)
return self.render("analysis/analysis.jinja.html", form=form)

142
advlabdb/analysis.py Normal file
View file

@ -0,0 +1,142 @@
from base64 import b64encode
from io import BytesIO
import numpy as np
from flask_login import current_user
from matplotlib.figure import Figure
from matplotlib.ticker import MaxNLocator
from sqlalchemy import select
from .models import MAX_MARK, MIN_MARK, Assistant, Semester, User, db
def html_fig(fig):
buf = BytesIO()
fig.savefig(buf, format="png")
return b64encode(buf.getbuffer()).decode("ascii")
def mark_hist(data, title):
fig = Figure()
ax = fig.subplots()
ax.set_xlim(MIN_MARK - 0.5, MAX_MARK + 0.5)
ax.set_xticks(np.arange(MAX_MARK + 1))
# Only integer ticks
ax.yaxis.set_major_locator(MaxNLocator(integer=True))
ax.set_xlabel("Mark")
N = data.size
title += f"\nN = {N}"
if N > 0:
ax.hist(
data,
bins=np.arange(MAX_MARK + 2) - 0.5,
)
title += f" | mean = {round(np.mean(data), 1)}"
ax.set_title(title)
return html_fig(fig)
def get_experiment_marks(assistant, attr):
data = []
for experiment_mark in assistant.experiment_marks:
mark = getattr(experiment_mark, attr)
if mark is not None:
data.append(mark)
return np.array(data)
def mark_hists(markType, active_assistants):
attr = markType.lower() + "_mark"
mark_type_title_addition = f" | {markType} marks"
marks = [get_experiment_marks(assistant, attr) for assistant in active_assistants]
hists = [
mark_hist(
data=marks[i],
title=str(active_assistants[i]) + mark_type_title_addition,
)
for i in range(len(marks))
]
hists.append(
mark_hist(
data=np.hstack(marks),
title="All" + mark_type_title_addition,
)
)
return hists
def assistant_marks_analysis(cls):
active_assistants = db.session.scalars(select(Assistant).join(User).where(User.active == True)).all()
oral_mark_hists = mark_hists("Oral", active_assistants)
protocol_mark_hists = mark_hists("Protocol", active_assistants)
return cls.render(
"analysis/assistant_marks.jinja.html",
hist_indices=range(len(oral_mark_hists)),
oral_mark_hists=oral_mark_hists,
protocol_mark_hists=protocol_mark_hists,
)
def get_final_part_marks(part):
data = []
for part_student in part.part_students:
mark = part_student.final_part_mark
if mark is not None:
data.append(mark)
return np.array(data)
def final_part_marks_analysis(cls):
parts = current_user.active_semester.parts
active_semester_final_part_marks_hists = [
mark_hist(
data=get_final_part_marks(part),
title=part.str(),
)
for part in parts
]
semesters = db.session.scalars(select(Semester)).all()
mean_final_part_marks = np.array(
[np.mean(np.hstack([get_final_part_marks(part) for part in semester.parts])) for semester in semesters]
)
fig = Figure()
len_mean_final_part_marks = mean_final_part_marks.size
ax = fig.subplots()
x = range(len_mean_final_part_marks)
ax.plot(
x,
mean_final_part_marks,
marker="d",
)
ax.set_xticks(x, [semester.str() for semester in semesters])
ax.set_xlabel("Semester")
ax.set_ylabel("Mean final experiment mark")
ax.set_title("Mean final experiment mark over all semesters")
mean_final_part_mark_plot = html_fig(fig)
return cls.render(
"analysis/final_part_marks.jinja.html",
active_semester_final_part_marks_hists=active_semester_final_part_marks_hists,
mean_final_part_mark_plot=mean_final_part_mark_plot,
)

View file

@ -7,9 +7,14 @@
<hr>
<form method="POST">
{{ form.csrf_token }}
{{ form.assistantMarksSubmit }}
<hr>
{{ form.finalPartMarksSubmit }}
{% for field in form %}
{% if field.widget.input_type == "submit" %}
{{ field(class="btn btn-primary btn-block") }}
<br>
{% else %}
{{ field() }}
{% endif %}
{% endfor %}
</form>
{% endblock body %}

View file

@ -6,17 +6,36 @@
<hr>
<h2>Assistant's marks analysis</h2>
<p>
This page shows an analysis of all marks of all assistants with an active user.
<br>
The marks are from all semesters, not only the active semester.
The histograms on this page (except the last two histograms) show the oral and protocol marks of each active assistant <em>individually</em>.
</p>
<p>
The last two histograms show the oral and protocol marks of all active assistants <em>together</em>.
</p>
<p>
An active assistant is an assistant with an active user. The marks are from all semesters, not only from the active semester.
</p>
<h4>Export</h4>
<p>
You can export this analysis by printing this page to a PDF file. The shortcut for printing the page is normally <code>Ctrl + p</code>. Select "Save as (PDF) file" afterwards instead of a printer!
</p>
<br>
<hr>
{% for histInd in histIndices %}
<img src="data:image/png;base64,{{ oralMarkHists[histInd]}}">
<img src="data:image/png;base64,{{ protocolMarkHists[histInd]}}">
{% for hist_ind in hist_indices %}
<div class="row text-center">
<div class="col-sm">
<img class="img-fluid" src="data:image/png;base64,{{ oral_mark_hists[hist_ind]}}">
</div>
<div class="col-sm">
<img class="img-fluid" src="data:image/png;base64,{{ protocol_mark_hists[hist_ind]}}">
</div>
</div>
<hr>
{% endfor %}
{% endblock body %}

View file

@ -6,10 +6,32 @@
<hr>
{% for activeSemesterFinalPartMarksHist in activeSemesterFinalPartMarksHists %}
<img src="data:image/png;base64,{{ activeSemesterFinalPartMarksHist }}">
<hr>
{% endfor %}
<h2>Final part marks analysis</h2>
<img src="data:image/png;base64,{{ meanFinalPartMarksPlot }}">
<p>
The histograms on this page show the final experiment marks of each part in the active semester.
</p>
<p>
The plot at the end of the page shows the course of the mean value of the final experiment marks in all parts of each semester over all semesters.
</p>
<h4>Export</h4>
<p>
You can export this analysis by printing this page to a PDF file. The shortcut for printing the page is normally <code>Ctrl + p</code>. Select "Save as (PDF) file" afterwards instead of a printer!
</p>
<br>
<hr>
<div class="text-center">
{% for active_semester_final_part_marks_hist in active_semester_final_part_marks_hists %}
<img class="img-fluid" src="data:image/png;base64,{{ active_semester_final_part_marks_hist }}">
<hr>
{% endfor %}
<img class="img-fluid" src="data:image/png;base64,{{ mean_final_part_mark_plot }}">
</div>
<br>
{% endblock body %}