Commit 17474d90 authored by Florent Chehab's avatar Florent Chehab
Browse files

Tweaks

parent 6ad4ec3c
%% Cell type:raw id: tags:
<script>
// Add a toggle button for showing (or not showing) source code.
function code_toggle() {
if (code_shown) {
$('div.input').hide('200');
$('.output_prompt').css({ visibility: 'hidden' });
$('#toggleButton').val('Afficher le code source')
} else {
$('div.input').show('200');
$('.output_prompt').removeAttr('style');
$('#toggleButton').val('Cacher le code source')
}
code_shown = !code_shown
}
$(document).ready(function () {
code_shown = false;
$('div.input').hide();
$('.output_prompt').css({ visibility: 'hidden' });
$('table').addClass("table table-hover table-bordered table-striped");
});
</script>
<form action="javascript:code_toggle()">
<input type="submit" id="toggleButton" value="Afficher le code source" class="btn btn-info">
</form>
%% Cell type:markdown id: tags:
<center>
<img src="https://gitlab.utc.fr/LaTeX-UTC/Graphismes-UTC/raw/56dd9762de926727aa45c8279dcdf54a753335c8/logos/UTC/logo_UTC.png" alt="Logo UTC" width="200px"/>
<br>
<font size="15" style="line-height:1;">Observatoire des évaluations d'UVs</font>
</center>
%% Cell type:code id: tags:
``` python
# For auto completion inside jupyter notebook
%config IPCompleter.greedy=True
import yaml # for the config file
import warnings # to issue warnings when needed
import os.path # for easy work with file paths
import copy # for deep copying some dicts
# utils functions
from src.generate_data import generate_data_file
from src.tools import is_connected, to_mardown_table_str
# For managing data
import json
from collections import OrderedDict
from datetime import datetime
import pandas as pd
import numpy as np
# For plotting data
from IPython.core.display import HTML, display, Markdown
from plotly.offline import init_notebook_mode, plot, iplot
import plotly.graph_objs as go
# Some magic for file size optimization
if is_connected(debug=True):
# do not include plotly js directly
init_notebook_mode(connected=True)
else:
# include plotly js directly
import cufflinks as cf
cf.set_config_file(offline=True, world_readable=True, theme='ggplot')
# First we take care of the configuration file
CONFIGURATION = yaml.load(open('./config.yml'))
ALL_SEMESTERS = {info['sem']:info['end_date'] for info in CONFIGURATION["other_semesters"]}
ALL_SEMESTERS[CONFIGURATION["main_semester"]['sem']] = CONFIGURATION["main_semester"]['end_date']
MAIN_SEMESTER = CONFIGURATION["main_semester"]['sem']
OTHER_SEMESTERS = [d['sem'] for d in CONFIGURATION["other_semesters"]]
ALL_SEMESTERS_SORTED = sorted(list(ALL_SEMESTERS.keys()), key=lambda sem:sem[1:5]+str((ord(sem[0]) + 20)%26 ))
for semester in ALL_SEMESTERS.keys():
if not os.path.isfile('./data/' + semester + '.json'):
warnings.warn("""
Il manque des fichiers de données des évaluations d'UVs pour le semestre {}"
""".format(semester))
if CONFIGURATION['allow_generate']:
warnings.warn("Génération aléatoires des données manquantes.")
generate_data_file(semester)
else:
raise ValueError("Données manquantes. Et le fichier de configuration ne permet pas de générer des données.")
ALL_SEMESTERS[semester] = datetime.strptime(ALL_SEMESTERS[semester], '%d/%m/%Y')
class SemesterData(object):
def __init__(self, d):
self.extraction_date = datetime.strptime(d['date'], '%d/%m/%Y')
self.DATA_RAW = OrderedDict(d['data'])
data = copy.deepcopy(self.DATA_RAW)
def clean_date(date):
if date is not None and type(date) != 'datetime.datetime':
date = datetime.strptime(date, '%d/%m/%Y')
return date
# cleaning dates
for uv, description in data.items():
for key in ['date_review_teacher', 'date_review_conseil']:
description[key] = clean_date(description[key])
self.DATA = pd.DataFrame.from_dict(data, orient='index')
d = self.DATA
self.nb_uvs = len(d.index)
taux_eval = d.loc[:, ('nb_evals','nb_etu_registered')].min(axis=1)/d['nb_etu_registered'] # TODO change
taux_eval = d['nb_evals']/(d['nb_etu_registered']-d['nb_etu_abs'])
taux_eval[taux_eval>1]=1
taux_eval = taux_eval.fillna(1) # Sometimes there 0 students in a course but there are evaluations
self.taux_eval_arr = 100 * taux_eval
self.taux_eval = sum(self.taux_eval_arr) / self.nb_uvs
self.uvs_with_evals = d.loc[pd.notna(d['date_review_teacher'])]
self.nb_uvs_with_evals = len(self.uvs_with_evals.index)
self.uvs_with_com = d.loc[ d['teacher_comment'] > '']
self.nb_uvs_with_com = len(self.uvs_with_com)
self.taux_seen = 100 * self.nb_uvs_with_evals / self.nb_uvs
self.taux_comment = 100 * self.nb_uvs_with_com / self.nb_uvs
# import and basic cleaning of data
EVALS = {}
for semester in ALL_SEMESTERS.keys():
data_json = json.load(open('./data/' + semester + '.json'))
if data_json['semester'] != semester:
print(data_json["semester"], semester)
raise ValueError("Inconsistent data file naming !")
EVALS[semester] = SemesterData(data_json)
EVALS_MAIN_SEM = EVALS[MAIN_SEMESTER]
```
%% Cell type:code id: tags:
``` python
str_tmp = """
</br>
<center>
<span style='font-size:40px'>
Semestre d'étude : {0}
</span>
<br><br>
<span style='font-size:30px'>
Autre(s) semestre(s) pour comparaison : {1}
</span>
</center><br>
""".format(MAIN_SEMESTER, ", ".join(x for x in OTHER_SEMESTERS))
HTML(str_tmp)
```
%% Cell type:markdown id: tags:
# Introduction
## Présentation des données
### Âge des données
%% Cell type:code id: tags:
``` python
str_tmp = "Les données pour chaque semestre ont été extraites aux dates : \n\n "
t = [["Semestre", "Date d'extraction"]]
for sem in EVALS.keys():
t.append([sem, EVALS[sem].extraction_date.strftime('%d/%m/%Y')])
Markdown(str_tmp + to_mardown_table_str(t))
```
%% Cell type:markdown id: tags:
### Structure des données
Pour chaque semestre, nous avons les informations des évaluations telles que visibles par les étudiants sur l'ENT aux dates d'extraction précisées ci-dessus.
Nous connaissons pour sûr :
- Le code de l'UV ;
- L'intitulé de l'UV ;
- Le nombre d'étudiants inscrits ;
- Le nombre d'étudiants marqués comme _absents_ ;
- Le nombre d'étudiants qui ont obtenu l'UV ;
- Le nombre d'évaluations saisies par les étudiants ;
- Les statistiques pour chacune des questions (i.e. le nombre de `++`, etc. pour chaque question) ;
- Le nom du ou de la responsable de l'UV ;
Sont éventuellement présents :
- La date à laquelle le responsable de l'UV a pris connaissance des évaluations ;
- Le rapport saisie par le ou la responsable de l'UV ;
- La date à laquelle le conseil de perfectionnement a émis un avis ;
- L'avis du conseil de perfectionnement.
Voici un exemple pour l'UV MT23 :
%% Cell type:code id: tags:
``` python
print(EVALS_MAIN_SEM.DATA.loc["MT23"])
```
%% Cell type:markdown id: tags:
### Remarque
Lorsque les statistiques des évaluations d'UVs (le nombre de `++`, de `+`, etc.) seront analysées de manière quantitative la table de conversion _symétrique_ suivante sera employée :
%% Cell type:code id: tags:
``` python
POSSIBLE_ANSWERS = ["--","-","+","++"]
POSSIBLE_ANSWERS_COEFFS = [-3,-1,1,3]
t = [["Sigle", "Valeur associée"]]
for ans, coeff in zip(POSSIBLE_ANSWERS, POSSIBLE_ANSWERS_COEFFS):
t.append(["`{}`".format(ans), str(coeff)])
Markdown(to_mardown_table_str(t))
```
%% Cell type:code id: tags:
``` python
def get_nb_evals(stats):
return sum([stats['1'][s] for s in POSSIBLE_ANSWERS])
def get_mark_q(stat, q):
if type(q) is int:
q = str(q)
return sum([stat[q][POSSIBLE_ANSWERS[i]] * \
POSSIBLE_ANSWERS_COEFFS[i] \
for i in range(len(POSSIBLE_ANSWERS_COEFFS))])\
/ get_nb_evals(stat)
POSSIBLE_ANSWERS_COL = [
'rgb(255,48,48)',
'rgb(255,165,0)',
'rgb(144,238,144)',
'rgb(48,221,48)'
]
ORANGE = 'rgb(255,126,24)' # Plotly orange
# Lets have some standard scales
SCALE_PERCENTAGE = [-1,101]
SCALE_MARK = [-3.1, 3.1]
```
%% Cell type:markdown id: tags:
## Informations générales
Lorsque'une analyse comparative est réalisée, elle est faite par rapport aux données des précédents semestres.
### Taux de réponse
%% Cell type:code id: tags:
``` python
t = [["Semestre",
"Nombre d'UVs",
"Taux de réponse moyen des étudiants" ,
"Nombre d'UV qui ont été vu",
"avec un commentaire"]]
for sem in ALL_SEMESTERS_SORTED:
d = EVALS[sem]
t.append([sem, str(d.nb_uvs),
"{0:.1f}%".format(d.taux_eval),
str(d.nb_uvs_with_evals)+" ({0:.1f}%)".format(d.taux_seen),
str(d.nb_uvs_with_com)+" ({0:.1f}%)".format(d.taux_comment)])
Markdown(to_mardown_table_str(t))
```
%% Cell type:code id: tags:
``` python
traces = []
for sem in ALL_SEMESTERS_SORTED:
d = EVALS[sem]
traces += [go.Bar(
x=["Taux de réponse moyen des étudiants",
"Taux d'UVs où les resps. ont 'vu' les éval.",
"Taux d'UVs où le resp. a laissé un commentaire"
],
y=[
d.taux_eval,
d.taux_seen,
d.taux_comment
],
name=sem
)]
layout = go.Layout(
barmode='group',
title = "Taux de réponses aux évaluations",
yaxis=dict(
range=SCALE_PERCENTAGE,
title='Pourcentage',
),
)
iplot(go.Figure(data=traces, layout=layout),show_link=False)
```
%% Cell type:code id: tags:
``` python
traces = []
for sem in ALL_SEMESTERS_SORTED:
d = EVALS[sem]
taux_eval = d.taux_eval_arr
traces += [go.Box(
y=taux_eval,
text=taux_eval.index,
name=sem
)]
layout = go.Layout(
title = "Taux de réponse moyen des étudiants aux évaluations",
yaxis=dict(
range=SCALE_PERCENTAGE,
title='Taux de réponse',
),
)
iplot(go.Figure(data=traces, layout=layout), show_link=False)
```
%% Cell type:markdown id: tags:
### Délai de réponse des responsables d'UVs
#### Pour le semestre d'étude principal
%% Cell type:code id: tags:
``` python
uvs_with_evals = EVALS_MAIN_SEM.uvs_with_evals
graph_data = [
go.Scatter(
x=uvs_with_evals['date_review_teacher'].sort_values(),
y=[100*float(i+1)/EVALS_MAIN_SEM.nb_uvs for i,e in enumerate(uvs_with_evals.index)],
text=uvs_with_evals.sort_values(by=['date_review_teacher']).index,
marker=dict(color=ORANGE)
)
]
layout = go.Layout(
title = "Délai de réponse des responsables d'UVs lors du semestre "+MAIN_SEMESTER,
yaxis=dict(
range=SCALE_PERCENTAGE,
title='Taux de réponse',
),
xaxis=dict(
title="Date"
)
)
iplot(go.Figure(data=graph_data, layout=layout), show_link=False)
```
%% Cell type:markdown id: tags:
#### En comparaison avec les précédents semestres
Pour cette comparaison, les dates sont renormalisées par rapport à la date correspondant au dernier jour des finaux du semestre en question. Les dates de fins des finaux sont les suivantes :
%% Cell type:code id: tags:
``` python
t = [["Semestre", "Date de la fin des finaux"]]
for sem in ALL_SEMESTERS_SORTED:
t.append([sem, ALL_SEMESTERS[sem].strftime('%d/%m/%Y')])
Markdown(to_mardown_table_str(t))
```
%% Cell type:code id: tags:
``` python
traces = []
for sem in ALL_SEMESTERS_SORTED:
sem_end_date = ALL_SEMESTERS[sem].toordinal()
d = EVALS[sem]
traces.append(
go.Scatter(
x=d.uvs_with_evals['date_review_teacher'].sort_values().apply(datetime.toordinal)-sem_end_date ,
y=[100*float(i+1)/d.nb_uvs for i,e in enumerate(d.uvs_with_evals.index)],
text=d.uvs_with_evals.sort_values(by=['date_review_teacher']).index,
name=sem
)
)
layout = go.Layout(
title = "Délai de réponse",
yaxis=dict(
range=SCALE_PERCENTAGE,
title="Taux des resps. d'UVs qui ont 'vu' les évaluations",
),
xaxis=dict(
title="Nombre de jours après la fin des finaux."
)
)
iplot(go.Figure(data=traces, layout=layout), show_link=False)
```
%% Cell type:markdown id: tags:
# Analyse quantitative
Pour rappel voici les questions posées :
1. Clarté des objectifs et du programme de l'UV
2. Y a-t-il adéquation entre le programme annoncé et le programme réalisé ?
3. Ma maîtrise des antécédents et pré-requis nécessaires
4. Qualité pédagogique de l'équipe enseignante
5. Articulation et cohérence des activités
6. Qualité des supports pédagogiques
7. Adéquation des moyens matériels
8. Adéquation des contrôles et des évaluations
9. Quantité de travail demandée
10. Appréciation globale de l'UV
## Vue d'ensemble des questions
### Vue synthétique
%% Cell type:code id: tags:
``` python
def get_stat_by_question(question, s):
q = str(question)
res = 0
for i, row in EVALS_MAIN_SEM.DATA.iterrows():
res += row["stats"][q][s] / get_nb_evals(row["stats"])
return res / EVALS_MAIN_SEM.nb_uvs * 100
traces = []
for ans, col in zip(POSSIBLE_ANSWERS, POSSIBLE_ANSWERS_COL):
traces.append(
go.Bar(
x=["Question "+str(i) for i in range(1,11)],
y=[get_stat_by_question(i, ans) for i in range(1,11)],
name=ans,
marker=dict(color=col)
)
)
layout = go.Layout(
barmode='stack',
title="Pourcentage moyen de chaque type d'avis pour chaque question en "+MAIN_SEMESTER,
yaxis=dict(
range=SCALE_PERCENTAGE,
title="Pourcentage (cumulé)",
),
)
iplot(go.Figure(data=traces, layout=layout), show_link=False)
```
%% Cell type:markdown id: tags:
### Vue détaillée
_Ici les évaluations (`++`, ...) sont converties en note numérique._
%% Cell type:code id: tags:
``` python
traces = []
data = EVALS_MAIN_SEM.DATA
for q in [str(i) for i in range(1,11)]:
traces.append(
go.Box(
y=[get_mark_q(row["stats"], q) for ind, row in data.iterrows()],
text=data.index,
name="Question "+q,
)
)
layout = go.Layout(
title = "Répartition des notes obetnues pour chaque question en " + MAIN_SEMESTER,
yaxis=dict(
range=SCALE_MARK,
title='Notes obtenues',
),
)
iplot(go.Figure(data=traces, layout=layout), show_link=False)
```
%% Cell type:markdown id: tags:
## Analyse de la question 10 (Appréciation globale de l'UV)
### Analyse pour le semestre d'étude
%% Cell type:code id: tags:
``` python
Markdown("_("+MAIN_SEMESTER+")_")
Markdown("_Pour rappel, le semestre d'étude principal est : {}._".format(MAIN_SEMESTER))
```
%% Cell type:code id: tags:
``` python
HTML("""
<div class="alert alert-success">
Vous pouvez cliquez sur les graphes pour avoir le détail
<strong>
Pour les graphiques ci-dessous,
en cliquant sur les points, la visualisation en détail d'une UV ce mettra automatiquement à jour
avec les données correspondantes.
</strong>
</div>
""")
```
%% Cell type:code id: tags:
``` python
def get_stat_per_q(row, q, stat):
return row['stats'][q][stat]
def get_nb_mm_q10(row):
return get_stat_per_q(row, '10', '--')
def get_nb_m_q10(row):
return get_stat_per_q(row, '10', '-')
def get_nb_m_or_mm_q10(row):
return get_nb_mm_q10(row) + get_nb_m_q10(row)
traces = []
data = EVALS_MAIN_SEM.DATA
tmp1 = data.apply(get_nb_mm_q10, axis=1) / data['nb_evals']
traces.append(
go.Scatter(
x=tmp1.sort_values().index,
y=tmp1.sort_values()*100,
name='--'
name='<b>--</b>'
)
)
tmp2 = data.apply(get_nb_m_or_mm_q10, axis=1) / data["nb_evals"]
traces.append(
go.Scatter(
x=tmp2[tmp1.sort_values().index].index,
y=tmp2[tmp1.sort_values().index]*100,
name="-- ou -"
name="<b>-</b> ou <b>--</b>"
)
)
layout = go.Layout(
title = "Visualisation du pourcentage d'avis négatifs -- et (- ou --) pour la question 10",
title = "Visualisation du pourcentage d'avis négatifs <b>--</b> et (<b>-</b> ou <b>--</b>) pour la question 10",
yaxis=dict(
range=SCALE_PERCENTAGE,
title="Pourcentage",
),
xaxis=dict(
title="UVs triées selon le pourcentage d'avis -- à la question 10",
title="UVs triées selon le pourcentage d'avis <b>--</b> à la question 10",
)
)
iplot(go.Figure(data=traces, layout=layout), show_link=False)
def link_graph(id):
html = '<div id='+id+"""
"></div>
<script>
$(window).load(function(){
var graph_id = $('#"""+id+"""').closest(".output").find(".js-plotly-plot")[0].id;
var graph = document.getElementById(graph_id);
graph.on("plotly_click", function(data){
$("#UV-select").val(data.points[0].x).change();
});
});
</script>
"""
return html
HTML(link_graph('graph-mm'))
```
%% Cell type:code id: tags:
``` python
data = EVALS_MAIN_SEM.DATA
tmp = data.apply((lambda r : get_mark_q(r["stats"],10)), axis=1).sort_values(ascending = False)
col = data.loc[tmp.index,"teacher_comment"]
col[col>""] = POSSIBLE_ANSWERS_COL[3]
col[col==""] = POSSIBLE_ANSWERS_COL[1]
col[col.isnull()] = POSSIBLE_ANSWERS_COL[0]
graph_data = [
go.Scatter(
x=tmp.index,
y=tmp,
marker=dict(color=col,symbol="cross"),
mode = 'markers',
)
]
layout = go.Layout(
title="Visualisation de l'appréciation globale de chaque UV (question 10) sous forme de note moyenne",
yaxis=dict(
range=SCALE_MARK,
title="Pourcentage",
),
)
iplot(go.Figure(data=graph_data, layout=layout), show_link=False)
HTML(link_graph('graph-report-status'))
```
%% Cell type:markdown id: tags:
### Comparaison avec les précédents semestres
%% Cell type:code id: tags:
``` python
q10_main_sem = EVALS_MAIN_SEM.DATA.apply((lambda r : get_mark_q(r["stats"],10)), axis=1).\
sort_values(ascending = False)
traces = []
for sem in OTHER_SEMESTERS:
tmp = []
eval_sem = EVALS[sem].DATA
for uv in q10_main_sem.index:
if uv in eval_sem.index:
row = eval_sem[eval_sem.index==uv]
tmp.append(get_mark_q(row["stats"][uv],10))
else:
tmp.append(np.nan)
traces.append(
go.Scatter(
x=q10_main_sem.index,
y=tmp,
name=sem,
mode = 'markers',
)
)
traces.append(
go.Scatter(
x=q10_main_sem.index,
y=q10_main_sem,
name=MAIN_SEMESTER
)
)
layout = go.Layout(
title="Visualisation de l'appréciation globale de chaque UV (question 10) sous forme de note moyenne",
yaxis=dict(
range=SCALE_MARK,
title="Pourcentage",
),
)
iplot(go.Figure(data=traces, layout=layout), show_link=False)
HTML(link_graph('graph-comp-prev'))
```
%% Cell type:markdown id: tags:
# Vue précise de chaque UV
%% Cell type:code id: tags:
``` python
simplified_data = {}
for sem in ALL_SEMESTERS_SORTED:
simplified_data[sem] = copy.deepcopy(EVALS[sem].DATA_RAW)
# Simplify data structure to save some space.
for uv in simplified_data[sem].keys():
stats = simplified_data[sem][uv]['stats']
if not isinstance(stats,list):
new_stats = [[stats[str(q)][ans] for ans in POSSIBLE_ANSWERS] for q in range(1,11)]
simplified_data[sem][uv]['stats'] = new_stats
json_datas = json.dumps(simplified_data, ensure_ascii=False)
a = """
<script>var evals_data = {};
var POSSIBLE_ANSWERS = {};
var POSSIBLE_ANSWERS_COL = {};
var POSSIBLE_ANSWERS_COEFFS = {};
var SCALE_MARK= {};
</script>
""".format(json_datas,
json.dumps(POSSIBLE_ANSWERS),
json.dumps(POSSIBLE_ANSWERS_COL),
json.dumps(POSSIBLE_ANSWERS_COEFFS),
json.dumps(SCALE_MARK))
HTML(a)
```
%% Cell type:code id: tags:
``` python
script = open('./src/display_uv.html').read() + open('./src/go_to_uv.html').read()
HTML(script)
```
%% Cell type:code id: tags:
``` python
# Tweak css for displaying data
style = open('./src/style.css').read()
HTML("<style>"+style+"</style>")
```
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment