Commit 77bf1ab2 authored by Florent Chehab's avatar Florent Chehab
Browse files

Disclaimer added and CI update

parent a7c566f2
......@@ -7,12 +7,9 @@ render:
- git clone --depth=1 $DATA_REPO_URL data && rm -rf ./data/.git
- pip install -r ./requierements.txt --quiet --quiet
- jupyter nbconvert --execute --to html_toc analysis.ipynb
- mkdir .public
- mv ./analysis.html .public/index.html
- mv .public public
artifacts:
paths:
- public
- analysis.html
expire_in: 1 year
only:
- master
......
%% Cell type:markdown id: tags:
<div class="alert alert-danger" >
<strong style="font-size:20px;">
Ce document repose en très grande partie sur du <code>JavaScript</code>. Il est fortement recommandé de le consulter depuis un ordinateur avec Firefox ou Chrome à jour.
</strong>
<br>
<br>
Une connexion internet est également requise afin de charger des libraires graphiques extérieures.
<br>
<i>Si vous voyez des lignes de codes et aucun graphique, c'est qu'au moins l'une de ces deux conditions n'est pas remplie.</i>
</div>
%% Cell type:markdown id: tags:
<div class="alert alert-info" >
<i>Tous commentaires ou suggestions sont les bienvenus. Merci de passer par le projet disponible sur le [GitLab](https://gitlab.utc.fr/chehabfl/Observatoire-Des-UVs) et le système de ticket associé. Le code source est libre ; les données ne sont pas publiquement accessibles.</i>
<br>
Les informations contenues dans ce document sont à traiter avec autant de rigueur que des informations internes à l'UTC (accessibles derrière le CAS sur l'ENT).
</div>
%% 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. 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['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.round(4)
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
self.number_of_inscription = sum(d['nb_etu_registered'])
# import
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 suivantes : \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.
<br>
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 ;
<br>
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 est 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
# some functions and constants
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)
val = sum([stat[q][POSSIBLE_ANSWERS[i]] * \
POSSIBLE_ANSWERS_COEFFS[i] \
for i in range(len(POSSIBLE_ANSWERS_COEFFS))])\
/ get_nb_evals(stat)
return round(val,2)
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
_NB : Lorsque'une analyse comparative est réalisée, elle est faite par rapport aux données des précédents semestres._
<br>
### Taux de réponse
%% Cell type:code id: tags:
``` python
t = [["Semestre",
"Nombre d'UVs",
"Nombres d'inscriptions (d'étudiants aux UVs)",
"Participation moyenne des étudiants aux évaluations" ,
"UVs pour laquelle le ou la responsable a indiqué avoir visualisé les évaluations",
"UVs pour laquelle le ou la responsable a saisi un rapport"]]
for sem in ALL_SEMESTERS_SORTED:
d = EVALS[sem]
t.append([sem, str(d.nb_uvs), str(d.number_of_inscription),
"{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=["Participation moyenne des étudiants",
"UVs où le/la resps a <i>vu</i> les éval.",
"UVs où le/la resp. a posté un commentaire"
],
y=[
d.taux_eval,
d.taux_seen,
d.taux_comment
],
name=sem
)]
layout = go.Layout(
barmode='group',
title = "Analyse temporelle de l'engagement dans les évaluations d'UVs",
yaxis=dict(
range=SCALE_PERCENTAGE,
title='Taux',
),
)
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 = "Participation des étudiants au cours des semestres",
yaxis=dict(
range=SCALE_PERCENTAGE,
title='Taux',
),
)
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
X = EVALS_MAIN_SEM.uvs_with_evals['date_review_teacher'].sort_values()
Y = [round(100*float(i+1)/EVALS_MAIN_SEM.nb_uvs,2) for i in range(len(X.index))]
graph_data = [
go.Scatter(
x=X,
y=Y,
text=X.index,
marker=dict(color=ORANGE)
)
]
layout = go.Layout(
title = "Délai de <i>réponse</i> 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]
X = d.uvs_with_evals['date_review_teacher'].sort_values().apply(datetime.toordinal)
Y = [round(100*float(i+1)/d.nb_uvs,2) for i in range(len(X.index))]
traces.append(
go.Scatter(
x=X-sem_end_date ,
y=Y,
text=d.uvs_with_evals.sort_values(by=['date_review_teacher']).index,
name=sem
)
)
layout = go.Layout(
title = "Délai de <i>réponse</i> des responsables d'UVs",
yaxis=dict(
range=SCALE_PERCENTAGE,
title="Taux des UVs qui dont les évals. ont été <i>vues</i> par les resp",
),
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
%% Cell type:code id: tags:
``` python
ASKED_QUESTIONS = """
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î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
"""
Markdown(ASKED_QUESTIONS)
```
%% Cell type:markdown id: tags:
## 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",
),
)
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("_Pour rappel, le semestre d'étude principal est : {}._".format(MAIN_SEMESTER))
```
%% Cell type:markdown id: tags:
<div class="alert alert-success">
<strong>
Pour les graphiques ci-dessous,
en cliquant sur les points, la visualisation en détail d'une UV
(disponible en bas de page) se 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='<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="<b>-</b> ou <b>--</b>"
)
)
layout = go.Layout(
title = """Visualisation du pourcentage d'avis négatifs
<b>--</b> et (<b>-</b> ou <b>--</b>) <br>
concernant l'appréciation globale de l'UV en {}""".format(MAIN_SEMESTER),
yaxis=dict(
range=SCALE_PERCENTAGE,
title="Pourcentage",
),
xaxis=dict(
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:markdown id: tags:
Sur le graphique ci-dessous, les croix :
- `rouges` indiquent que le ou la responsable de l'UV ne semble pas avoir pris connaissance des évaluations ;
- `orange` indiquent que le ou la responsable de l'UV _a pris connaissance_ des évaluations sans saisir de rapport ;
- `vertes` indiquent que le ou la responsable de l'UV _a pris connaissance_ des évaluations et a saisi un rapport.
%% 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<br>
sous forme de note moyenne lors du semestre {}""".format(MAIN_SEMESTER),
yaxis=dict(
range=SCALE_MARK,
title="Pourcentage",
),
xaxis=dict(title="UVs triées selon la note moyenne à la question 10."),
)
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<br>
sous forme de note moyenne et au cours des différents semestres""",
yaxis=dict(
range=SCALE_MARK,
title="Pourcentage",
),
xaxis=dict(title="UVs triées selon la note moyenne à la question 10 en {}".format(MAIN_SEMESTER)),
)
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
Markdown(ASKED_QUESTIONS)
```
%% 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