Page MenuHomec4science

lib_amcstats_new.php
No OneTemporary

File Metadata

Created
Wed, Apr 24, 17:46

lib_amcstats_new.php

<?php
# some functions for array_maps
function clean_array($string) { return preg_replace('/"/', '', $string); }
function decimal_conversion($string) { return preg_replace('/,/', '.', $string); }
function square($n) { return($n*$n); }
# standard deviation function (if PECL stats not available)
if (!function_exists('stats_standard_deviation')) {
function stats_standard_deviation(array $a, $sample = false) {
$n = count($a);
if ($n === 0) {
trigger_error("The array has zero elements", E_USER_WARNING);
return false;
}
if ($sample && $n === 1) {
trigger_error("The array has only 1 element", E_USER_WARNING);
return false;
}
$mean = array_sum($a)/$n;
$carry = 0.0;
foreach ($a as $val) {
$d = ((double) $val) - $mean;
$carry += $d * $d;
}
if ($sample) --$n;
return sqrt($carry / $n);
}
}
class AmcReader {
protected $filename = null;
protected $teacher = null;
protected $raw_data = null;
protected $columns = null;
protected $students = null;
protected $exam_points = null;
public function __construct($filename, $teacher, $exam_points, $only_questions = null, $inverse_filter = false, $external = null) {
if (!file_exists($filename))
throw new Exception('File not found: '.$filename);
$this->filename = $filename;
$this->teacher = $teacher;
$this->exam_points = $exam_points;
$raw_data = file($this->filename, FILE_IGNORE_NEW_LINES);
$this->raw_data = array();
foreach($raw_data as $line) {
$line = array_map("clean_array", explode(';', $line));
$line = array_map("decimal_conversion", $line);
$this->raw_data[] = $line;
}
$this->parseHeader();
$this->parseStudents($only_questions, $inverse_filter, $external);
}
public function getStudents() {
return $this->students;
}
protected function parseHeader() {
if (isset($this->raw_data[0])) //is_array($this->raw_data[0]) || is_object($this->raw_data[0]))
{
foreach($this->raw_data[0] as $col_id => $value) {
// Analyse header from CSV file, based on content
$item = array();
switch ($value) {
case "ID":
case "NAME":
case "EMAIL":
case "SECTION":
case "Mark":
$item['name'] = $value;
$item['type'] = "info";
break;
case "SCIPER":
$item['name'] = $value;
$item['type'] = "unique_id";
break;
default:
$item['name'] = $value;
if (preg_match('/^TICKED:/', $value)) {
$item['name'] = preg_replace('/^TICKED:/', '', $value);
$item['type'] = "ticked";
} else {
$item['type'] = "question";
$item['subtype'] = $this->guessSubtype_new($col_id);
}
}
// Stats will be computed at a later stage
$item['stats'] = null;
$this->columns[] = $item;
}
}
}
protected function guessSubtype($col_id) {
$subtype = null;
$min_points = 0;
$max_points = 0;
$decimal = false;
foreach($this->raw_data as $line) {
//echo $line[$col_id];
if (preg_match('/\./', $line[$col_id])) $decimal = true;
if ($line[$col_id] > $max_points) $max_points = $line[$col_id];
}
//Tmp Global Issues
//if ($max_points == 1)return 'mc'; else return 'tf';
//Tmp Euler
//if ($max_points == 1)return 'mc'; else return 'open';
//return 'open';
// This only works for MATHS exams...
//Tmp mc 5 points
//if ($max_points == 5) return 'mc';
//K. Mulleners
//if ($max_points == 1) return 'mc';
//return 'open';
//Droit archi
//return 'mc';
if ($decimal or $max_points > 3) {
// Only open questions have decimal points
return 'open';
}
//if ($max_points == 2) return 'mc';
if ($max_points == 3) return 'mc';
if ($max_points == 1) return 'tf';
return 'open';
}
protected function guessSubtype_new($col_id) {
$subtype = null;
$min_points = 0;
$max_points = 0;
$decimal = false;
//get first line header and question ids
$line = $this->raw_data[0];
//find question tex file
$dir = dirname(getcwd())."/*.tex";
foreach(glob($dir) as $tex_file)
{
if( !empty($line[$col_id]) && strpos(file_get_contents($tex_file),$line[$col_id]) !== false) {
$tex_lines = file($tex_file);
$lineNum = 0;
$questionSearchTxt = "{".$line[$col_id]."}";
foreach ($tex_lines as $tex_line) {
if (strpos($tex_line, $questionSearchTxt) !== false) {
if (strpos($tex_line,"{questionmult}") !== false) {
return 'mcm';
} else if (strpos($tex_line,"\corrector") !== false) {
return 'open';
} else {
$tmp_file = fopen($tex_file,"r");
$tmp_lineNum = 0;
while(! feof($tmp_file)) {
$tmp_line = fgets($tmp_file);
//echo "\n".$line[$col_id].";";
if ( $tmp_lineNum >= $lineNum ) {
if (strpos($tmp_line, "\FALSE") !== false || strpos($tmp_line, "\FALSO") !== false || strpos($tmp_line, "\TRUE") !== false || strpos($tmp_line, "\VERO") !== false){
return 'tf';
}
}
$tmp_lineNum++;
}
fclose($tmp_file);
return 'mc';
}
}
$lineNum++;
}
//echo basename($tex_file);
}
}
echo "\n";
}
protected function getColIdByName($name) {
foreach ($this->columns as $id => $col) if ($col['name'] == $name) return $id;
throw new Exception('Column not found: '.$name);
}
protected function getColIdsByType($type, $name = null) {
$ids = array();
if (is_null($name)) {
foreach ($this->columns as $id => $col) if ($col['type'] == $type) $ids[] = $id;
} else {
foreach ($this->columns as $id => $col) if ($col['type'] == $type and $col['name'] == $name) $ids[] = $id;
}
if (count($ids) == 0) {
if (is_null($name)) throw new Exception('Column type not found: '.$type);
throw new Exception('Column type not found: '.$type.'/'.$name);
}
if (count($ids) == 1) return $ids[0];
return $ids;
}
protected function getQuestionNameByColId($id) {
if (array_key_exists($id, $this->columns) and $this->columns[$id]['type'] == 'question')
return $this->columns[$id]['name'];
throw new Exception('Column not found, or is not a question: '.$id);
}
protected function getMaximumPointsByColId($id) {
$maximum = 0.0;
foreach($this->raw_data as $student) if ((float)$student[$id] > $maximum) $maximum = (float)$student[$id];
return $maximum;
}
protected function parseStudents($only_questions, $inverse_filter, $external) {
foreach($this->raw_data as $line => $student) {
if ($line == 0) continue; // skip header
$data = array('teacher' => $this->teacher);
foreach(array('ID', 'SCIPER', 'NAME', 'EMAIL', 'SECTION') as $key) {
$data[$key] = $student[$this->getColIdByName($key)];
}
// Get points
$points = array();
$data['items'] = array();
foreach($this->getColIdsByType('question') as $col) {
$item = array();
$item['name'] = $this->getQuestionNameByColId($col);
// Should we filter this question ?
if (is_null($only_questions)
or (!$inverse_filter and in_array($item['name'], $only_questions))
or ($inverse_filter and !in_array($item['name'], $only_questions))
) {
// Take this question into account
$item['points'] = (float)$student[$col];
$item['max_points'] = $this->getMaximumPointsByColId($col);
if ($item['max_points'] == 0) {
// Cancelled question ? Count it right for everyone
$item['right'] = 1;
} else {
$item['right'] = max((float)($item['points']/$item['max_points']), 0.0);
}
$points[] = $item['points'];
$item['ticked'] = $student[$this->getColIdsByType('ticked', $item['name'])];
$item['type'] = $this->columns[$col]['type'];
$item['subtype'] = $this->columns[$col]['subtype'];
$data['items'][] = $item;
}
}
$data['total'] = array_sum($points);
$data['present'] = (int)(array_sum(array_map("square", $points))>0);
$data['exam_points'] = $this->exam_points;
// Compute marks
if ($data['present']) {
$data['positive_total'] = (float)max($data['total'], 0.0);
if (is_null($external)) {
$data['mark6'] = (float)min($data['positive_total']/($this->exam_points)*5.0+1, 6.0);
} else {
$output = array();
exec("./".$external." ".$data['total'], $output);
$data['mark6'] = (float)trim($output[0]);
}
$data['quarter_mark6'] = (float)round($data['mark6']*4.0, 0)/4.0;
} else {
$data['positive_total'] = 'n/a';
$data['mark6'] = 'abs';
$data['quarter_mark6'] = 'abs';
}
if (preg_match('/^FAKE/', $data['SCIPER'])) {
if ($data['present']) $data['type'] = 'unregistered';
else $data['type'] = 'unused';
} else $data['type'] = 'student';
$this->students[] = $data;
}
}
}
// Compare students on total (higher to lower)
function cmp_total($a, $b)
{
if ($a['total'] == $b['total']) {
return 0;
}
return ($a['total'] < $b['total']) ? 1 : -1;
}
class ExamCalcs {
protected $dataset = null;
protected $tmp_dataset = null;
public function __construct($dataset = null) {
$this->dataset = array();
if (!is_null($dataset)) $this->dataset = $dataset;
}
public function addFile($teacher, $teacher_file, $max_points, $only_questions = null, $inverse_filter = false, $external = null) {
#echo "Adding $teacher ($teacher_file) to the dataset ($max_points points).\n";
$AR = new AmcReader($teacher_file, $teacher, $max_points, $only_questions, $inverse_filter, $external);
$this->addDataSet($AR->getStudents());
}
public function addDataSet($data) {
if (is_array($data) || is_object($data))
{
foreach ($data as $student) $this->dataset[] = $student;
}
}
public function filterBySections($sections, $update = true) {
$dataset = array();
foreach ($this->dataset as $student) {
if (is_array($sections)) {
if (in_array($student['SECTION'], $sections))
$dataset[] = $student;
} else {
if ($student['SECTION'] == $sections)
$dataset[] = $student;
}
}
if ($update) $this->dataset = $dataset;
return $dataset;
}
public function filterByTeachers($teachers, $update = true) {
$dataset = array();
foreach ($this->dataset as $student) {
if (is_array($teachers)) {
if (in_array($student['teacher'], $teachers))
$dataset[] = $student;
} else {
if ($student['teacher'] == $teachers)
$dataset[] = $student;
}
}
if ($update) $this->dataset = $dataset;
return $dataset;
}
public function getTeachers() {
$teachers = array();
foreach ($this->dataset as $student) {
if (!in_array($student['teacher'], $teachers)) $teachers[] = $student['teacher'];
}
return $teachers;
}
public function getSections() {
$sections = array();
foreach ($this->dataset as $student) {
$section = $student['SECTION'];
if (!in_array($section, $sections) and $section != 'XXX') $sections[] = $section;
}
return $sections;
}
public function getQuestionsTxt() {
$questions = array();
$items = array();
foreach ($this->dataset[0]['items'] as $item){
$questions[] = $item['name'];
echo $item['name'];
}
return $questions;
}
public function printStatsOnCommonItems() {
// Sort dataset by points
$dataset = $this->sortByTotalPoints(false);
if (count($dataset) < 3) throw new Exception('Dataset is too small.');
// Get items from the first student
$items = array();
foreach ($this->dataset[0]['items'] as $item) $items[] = $item['name'];
foreach ($dataset as $student) {
// Get items for current student
$tmp_items = array();
foreach ($student['items'] as $item) $tmp_items[] = $item['name'];
// Keep only items in both '$items' AND '$tmp_items'
$items = array_intersect($items, $tmp_items);
}
// Now, filter items in the dataset
$filtered_dataset = array();
foreach ($dataset as $student) {
if (!$student['present']) continue;
$filtered_items = array();
foreach ($student['items'] as $item) {
if (in_array($item['name'], $items)) $filtered_items[] = $item;
}
if (count($filtered_items)) {
$student['items'] = $filtered_items;
$filtered_dataset[] = $student;
}
}
$dataset = $filtered_dataset;
if (count($dataset) < 3) throw new Exception('Dataset is too small.');
// Compute limits
$nb_students = count($dataset);
$twenty_seven = (int)($nb_students*27.0/100);
$upper_stop = $twenty_seven-1;
$lower_start = $nb_students-$twenty_seven+1;
#echo "$nb_students / $twenty_seven / 0 -> $upper_stop / $lower_start -> $nb_students \n";
$stats = array();
foreach ($dataset as $i => $student) {
foreach ($student['items'] as $item) {
$name = $item['name'];
if (!array_key_exists($name, $stats))
$stats[$name] = array( '27%' => $twenty_seven,
'upper' => 0,
'lower' => 0,
'valid' => null,
'ticked'=> null,
'ticked_count'=> 0,
'empty_count'=> 0,
'type' => null,
'subtype' => null,
'max_points' => null,
);
$stats[$name]['max_points'] = $item['max_points'];
$stats[$name]['type'] = $item['type'];
$stats[$name]['subtype'] = $item['subtype'];
$stats[$name]['question_id'] = $name;
// Initialise 'ticked' table
if (is_null($stats[$name]['ticked'])) {
switch ($stats[$name]['subtype']) {
case 'mc':
$stats[$name]['ticked'] = array( 'A' => 0, 'B' => 0, 'C' => 0, 'D' => 0, 'multiple' => 0);
break;
case 'mcm':
$stats[$name]['ticked'] = array();//array( 'A' => 0, 'B' => 0, 'C' => 0, 'D' => 0, 'E' => 0, 'F' => 0, 'G' => 0 );
break;
case 'tf':
$stats[$name]['ticked'] = array( 'TRUE' => 0, 'FALSE' => 0, 'multiple' => 0);
break;
default:
$stats[$name]['ticked'] = array();
break;
}
}
// Count right answers
if ($item['right'] > 0) {
// Save valid answer
if (is_null($stats[$name]['valid'])) {
switch ($stats[$name]['subtype']) {
case 'tf':
if ($item['ticked'] == 'A')
$stats[$name]['valid'] = 'TRUE';
else
$stats[$name]['valid'] = 'FALSE';
break;
case 'mc':
$stats[$name]['valid'] = $item['ticked'];
break;
case 'mcm':
$stats[$name]['valid'] = 'n/a';;
break;
case 'open':
$stats[$name]['valid'] = 'n/a';
break;
default:
$stats[$name]['valid'] = 'n/a';
break;
}
}
// 'upper 27%' and 'lower 27%' counters
switch ($stats[$name]['subtype']) {
case 'mcm':
if ($i <= $upper_stop) $stats[$name]['upper'] += $item['points'];
if ($i >= $lower_start) $stats[$name]['lower'] += $item['points'];
break;
case 'mc':
case 'tf':
if ($i <= $upper_stop) $stats[$name]['upper']++;
if ($i >= $lower_start) $stats[$name]['lower']++;
break;
case 'open':
if ($i <= $upper_stop) $stats[$name]['upper'] += $item['points'];
if ($i >= $lower_start) $stats[$name]['lower'] += $item['points'];
break;
}
}
// Count empty answers
if (empty($item['ticked'])) {
$stats[$name]['empty_count']++;
} else {
// Stats on non-empty answers
$stats[$name]['ticked_count']++;
if (strlen($item['ticked']) > 1 && $item['subtype'] != 'mcm') {
$stats[$name]['ticked'] = $this->createAndIncrement($stats[$name]['ticked'], 'multiple');
} else {
switch ($item['subtype']) {
case 'tf':
if ($item['ticked'] == 'A') $field = 'TRUE';
if ($item['ticked'] == 'B') $field = 'FALSE';
$stats[$name]['ticked'] = $this->createAndIncrement($stats[$name]['ticked'], $field);
break;
case 'mc':
$stats[$name]['ticked'] = $this->createAndIncrement($stats[$name]['ticked'], $item['ticked']);
break;
case 'mcm':
$stats[$name]['ticked'] = $this->createAndIncrement($stats[$name]['ticked'], $item['ticked']);
break;
default:
$stats[$name]['ticked'] = $this->createAndIncrement($stats[$name]['ticked'], $item['ticked']);
break;
}
}
}
}
}
// Compute more stats
$tmp = array();
foreach ($stats as $name => $stat) {
// Discrimination index
// NA if stat 27% is zero
if ($stat['27%'] == 0){
$stat['upper'] = "NA";
$stat['lower'] = "NA";
$stat['27%'] = "NA";
$stat['DI'] = "NA";
}else{
// For open and mcm questions, change the '27%' value.
if ($stat['subtype'] == 'open' || $stat['subtype'] == 'mcm'){
$stat['upper'] = $stat['upper']/$stat['27%'];
$stat['lower'] = $stat['lower']/$stat['27%'];
$stat['27%'] = $stat['max_points']; //$stat['27%']*$stat['max_points'];
}
$stat['DI'] = ($stat['upper']-$stat['lower'])/(1.0*$stat['27%']);
}
// Calculate percentages
$ticked_percentage = array();
foreach ($stat['ticked'] as $t => $n) {
$ticked_percentage[$t] = array( 'n' => $n, '%' => (float)(100.0*$n/$stat['ticked_count']), 'valid' => (int)($t == $stat['valid']));
}
$stat['ticked'] = $ticked_percentage;
$tmp[$name] = $stat;
}
$stats = $tmp;
// Order array by question type, name
uasort($stats, function($a, $b){
if($a["subtype"] == $b["subtype"]){
return strcmp($a["question_id"], $b["question_id"]);
}
return strcmp($a["subtype"], $b["subtype"]);
});
// Print CSV
$previous_subtype = null;
$header = '"question_id","subtype","27 %","upper","lower","DI","count","valid"';
$headerOpenMcm = '"question_id","subtype","question pts %","avg pts upper","avg pts lower","DI","count"';
$lastStatTickedSize = 0;
foreach ($stats as $name => $stat) {
if ($stat['subtype'] != $previous_subtype || count($stat['ticked']) != $lastStatTickedSize) {
#if (!is_null($previous_subtype)) echo "\n";
if ($stat['subtype'] == 'open' || $stat['subtype'] == 'mcm'){
echo "\n".$headerOpenMcm;
} else {
echo "\n".$header;
}
if ($stat['subtype'] != 'open') {
if($stat['subtype'] != 'tf'){
ksort($stat['ticked']);
}
foreach ($stat['ticked'] as $answer => $data) echo ",\"[$answer] count\"";
foreach ($stat['ticked'] as $answer => $data) echo ",\"[$answer] %\"";
}
echo "\n";
$previous_subtype = $stat['subtype'];
}
$lastStatTickedSize = count($stat['ticked']);
echo "$name,{$stat['subtype']},{$stat['27%']},{$stat['upper']},{$stat['lower']},{$stat['DI']},{$stat['ticked_count']}";
if ($stat['subtype'] != 'open' && $stat['subtype'] != 'mcm'){
foreach ($stat['ticked'] as $answer => $data) if ($data['valid'] == 1) echo ",\"$answer\"";
foreach ($stat['ticked'] as $answer => $data) echo ",{$data['n']}";
foreach ($stat['ticked'] as $answer => $data) echo ",{$data['%']}";
} else if($stat['subtype'] == 'mcm'){
ksort($stat['ticked']);
foreach ($stat['ticked'] as $answer => $data) echo ",{$data['n']}";
foreach ($stat['ticked'] as $answer => $data) echo ",{$data['%']}";
}
echo "\n";
}
echo "\n\n";
}
public function printLatexHistoCommandsOnCommonItems() {
// Sort dataset by points
$dataset = $this->sortByTotalPoints(false);
if (count($dataset) < 3) throw new Exception('Dataset is too small.');
// Get items from the first student
$items = array();
foreach ($this->dataset[0]['items'] as $item) $items[] = $item['name'];
foreach ($dataset as $student) {
// Get items for current student
$tmp_items = array();
foreach ($student['items'] as $item) $tmp_items[] = $item['name'];
// Keep only items in both '$items' AND '$tmp_items'
$items = array_intersect($items, $tmp_items);
}
// Now, filter items in the dataset
$filtered_dataset = array();
foreach ($dataset as $student) {
if (!$student['present']) continue;
$filtered_items = array();
foreach ($student['items'] as $item) {
if (in_array($item['name'], $items)) $filtered_items[] = $item;
}
if (count($filtered_items)) {
$student['items'] = $filtered_items;
$filtered_dataset[] = $student;
}
}
$dataset = $filtered_dataset;
if (count($dataset) < 3) throw new Exception('Dataset is too small.');
// Compute limits
$nb_students = count($dataset);
$twenty_seven = (int)($nb_students*27.0/100);
$upper_stop = $twenty_seven-1;
$lower_start = $nb_students-$twenty_seven+1;
#echo "$nb_students / $twenty_seven / 0 -> $upper_stop / $lower_start -> $nb_students \n";
$stats = array();
foreach ($dataset as $i => $student) {
foreach ($student['items'] as $item) {
$name = $item['name'];
$teacher = $student['teacher'];
$name = $name . ";" . $teacher;
if (!array_key_exists($name, $stats))
$stats[$name] = array(
'teacher' => null,
'valid' => null,
'ticked'=> null,
'ticked_count'=> 0,
'empty_count'=> 0,
'type' => null,
'subtype' => null,
);
$stats[$name]['type'] = $item['type'];
$stats[$name]['subtype'] = $item['subtype'];
$stats[$name]['teacher'] = $student['teacher'];
$stats[$name]['question_id'] = $name;
// Initialise 'ticked' table
if (is_null($stats[$name]['ticked'])) {
switch ($stats[$name]['subtype']) {
case 'mc':
$stats[$name]['ticked'] = array( 'A' => 0, 'B' => 0, 'C' => 0, 'D' => 0);
break;
case 'mcm':
$stats[$name]['ticked'] = array( 'A' => 0, 'B' => 0, 'C' => 0, 'D' => 0, 'E' => 0, 'F' => 0, 'G' => 0);
break;
case 'tf':
$stats[$name]['ticked'] = array( 'TRUE' => 0, 'FALSE' => 0);
break;
default:
$stats[$name]['ticked'] = array();
break;
}
}
// Count right answers
if ($item['right'] > 0) {
// Save valid answer
if (is_null($stats[$name]['valid'])) {
switch ($stats[$name]['subtype']) {
case 'tf':
if ($item['ticked'] == 'A')
$stats[$name]['valid'] = 'TRUE';
else
$stats[$name]['valid'] = 'FALSE';
break;
case 'mc':
$stats[$name]['valid'] = $item['ticked'];
break;
case 'mcm':
$stats[$name]['valid'] = 'n/a';
break;
case 'open':
$stats[$name]['valid'] = 'n/a';
break;
default:
$stats[$name]['valid'] = 'n/a';
break;
}
}
}
// Count empty answers
if (empty($item['ticked'])) {
$stats[$name]['empty_count']++;
} else {
// Stats on non-empty answers
$stats[$name]['ticked_count']++;
if (strlen($item['ticked']) == 1) {
switch ($item['subtype']) {
case 'tf':
if ($item['ticked'] == 'A') $field = 'TRUE';
if ($item['ticked'] == 'B') $field = 'FALSE';
$stats[$name]['ticked'] = $this->createAndIncrement($stats[$name]['ticked'], $field);
break;
case 'mc':
$stats[$name]['ticked'] = $this->createAndIncrement($stats[$name]['ticked'], $item['ticked']);
break;
case 'mcm':
$stats[$name]['ticked'] = $this->createAndIncrement($stats[$name]['ticked'], $item['ticked']);
break;
default:
$stats[$name]['ticked'] = $this->createAndIncrement($stats[$name]['ticked'], $item['ticked']);
break;
}
}
}
}
}
// Compute more stats
$tmp = array();
foreach ($stats as $name => $stat) {
// Calculate percentages
$ticked_percentage = array();
foreach ($stat['ticked'] as $t => $n) {
$ticked_percentage[$t] = array( 'n' => $n, '%' => (float)(100.0*$n/$stat['ticked_count']), 'valid' => (int)($t == $stat['valid']));
}
$stat['ticked'] = $ticked_percentage;
$tmp[$name] = $stat;
}
$stats = $tmp;
// Print CSV, by question, then teacher
//histo colors
$i = 0;
foreach($this->getTeachers() as $teacher){
$i++;
switch ($i) {
case 1: echo "\\newcommand{\\" . $teacher . "}{\\textbf{" . $teacher . "}} \n"; break;
case 2: echo "\\newcommand{\\" . $teacher . "}{{\color{red}\\textbf{" . $teacher . "}}} \n"; break;
case 3: echo "\\newcommand{\\" . $teacher . "}{{\color{blue}\\textbf{" . $teacher . "}}} \n"; break;
case 4: echo "\\newcommand{\\" . $teacher . "}{{\color{magenta}\\textbf{" . $teacher . "}}} \n"; break;
case 5: echo "\\newcommand{\\" . $teacher . "}{{\color{cyan}\\textbf{" . $teacher . "}}} \n"; break;
case 6: echo "\\newcommand{\\" . $teacher . "}{{\color{olive}\\textbf{" . $teacher . "}}} \n"; break;
case 7: echo "\\newcommand{\\" . $teacher . "}{{\color{orange}\\textbf{" . $teacher . "}}} \n"; break;
case 8: echo "\\newcommand{\\" . $teacher . "}{{\color{black}\\textbf{" . $teacher . "}}} \n "; break;
}
}
echo " \n";
//histo teachers present students
foreach($this->getTeachers() as $teacher){
echo "\\newcommand{\N" . $teacher . "}{" . $this->getTotStudentsByTeacher($teacher) . "} \n";
}
echo " \n";
$items = array();
foreach ($this->dataset[0]['items'] as $item){
if($item['subtype'] != 'open'){
echo "\inputFR{" . $item['name'] . ".tex} \n";
foreach($this->getTeachers() as $teacher){
foreach ($stats as $name => $stat) {
// if($stat['teacher'] == $teacher){
// echo $name . " - " . $item['name'];
// }
//echo "[" . $stat['teacher'] . " - " . $teacher . " | " . $name . " - " . $item['name'] . "]";
if($stat['teacher'] == $teacher && explode(';',$name)[0] == $item['name']){
if (strpos($item['name'], 'TF') || strpos($item['name'], 'tf') !== false) {
krsort($stat['ticked']);
echo "\histoTF{\\" . $teacher . "}{\N" . $teacher . "}{" . $stat['ticked_count'] . "}";
}else{
ksort($stat['ticked']);
echo "\histoQCM{\\" . $teacher . "}{\N" . $teacher . "}{" . $stat['ticked_count'] . "}";
}
foreach ($stat['ticked'] as $answer => $data) echo "{{$data['n']}}";
}
}
echo "\n";
}
echo "\n";
echo "\n";
}
}
}
public function sortByTotalPoints($update = true) {
if ($update) {
usort($this->dataset, "cmp_total");
return $this->dataset;
} else {
$dataset = $this->dataset;
usort($dataset, "cmp_total");
return $dataset;
}
}
public function getDataSet() {
return $this->dataset;
}
protected function createAndIncrement($table, $field, $increment = 1) {
if (!array_key_exists($field, $table)) {
$table[$field] = 0;
}
$table[$field] += $increment;
return $table;
}
public function getMarks() {
$marks = array();
foreach($this->dataset as $student) {
$tmp = array();
$tmp['teacher'] = $student['teacher'];
$tmp['ID'] = $student['ID'];
$tmp['SECTION'] = $student['SECTION'];
$tmp['exam_points'] = $student['exam_points'];
$tmp['total'] = $student['total'];
$tmp['present'] = $student['present'];
$tmp['SCIPER'] = $student['SCIPER'];
$tmp['quarter_mark6'] = $student['quarter_mark6'];
$marks[] = $tmp;
}
return $marks;
}
public function getTotStudentsByTeacher($teacher){
$nb_students = 0;
foreach($this->dataset as $student){
if($student['teacher'] == $teacher && $student['present']){
$nb_students++;
}
}
return $nb_students;
}
public function getQuestions() {
$questions = array();
foreach($this->dataset as $student) {
//print_r($student['items']);
$tmp = array();
$tmp['teacher'] = $student['teacher'];
$tmp['ID'] = $student['ID'];
$tmp['SECTION'] = $student['SECTION'];
$tmp['SCIPER'] = $student['SCIPER'];
if (!$student['present']) {
$tmp['present'] = "ABS";
} else {
$tmp['present'] = 1;
}
$tmp['exam_points'] = $student['exam_points'];
$tmp['total'] = $student['total'];
# Stats on questions
$tmp['nb_questions'] = count($student['items']);
$tmp['not_answered'] = 0;
$tmp['right'] = 0;
$tmp['wrong'] = 0;
foreach($student['items'] as $question) {
if (empty($question['ticked'])) {
// Question was not answered
$tmp['not_answered'] += 1;
} else {
if ($question['right']) $tmp['right'] += 1; else $tmp['wrong'] += 1;
}
}
$questions[] = $tmp;
}
return $questions;
}
public function getStats() {
$stats = array();
# Presence
$stats['presence'] = array();
$marks = array();
$stats['quarter_mark6'] = array( 'n' => 0, 'tot' => 0, 'average' => null, 'stddev' => null, 'median' => null);
foreach($this->dataset as $student) {
// Presence
$stats['presence'] = $this->createAndIncrement($stats['presence'], 'total');
switch ($student['type']) {
case 'student':
if ($student['present']) {
$stats['presence'] = $this->createAndIncrement($stats['presence'], 'present');
} else {
$stats['presence'] = $this->createAndIncrement($stats['presence'], 'absent');
}
break;
case 'unused':
$stats['presence'] = $this->createAndIncrement($stats['presence'], 'unsused');
break;
default:
$stats['presence'] = $this->createAndIncrement($stats['presence'], 'unknown');
}
// Average
if ($student['present']) $marks[] = $student['quarter_mark6'];
}
$stats['quarter_mark6']['n'] = count($marks);
if ($stats['quarter_mark6']['n'] > 0) {
$stats['quarter_mark6']['tot'] = array_sum($marks);
$stats['quarter_mark6']['average'] = $stats['quarter_mark6']['tot']/$stats['quarter_mark6']['n'];
$stats['quarter_mark6']['stddev'] = stats_standard_deviation($marks);
if (count($marks) >=3) {
sort($marks);
$stats['quarter_mark6']['median'] = $marks[round(count($marks)/2)];
} else {
$stats['quarter_mark6']['median'] = 'n/a';
}
} else {
$stats['quarter_mark6']['tot'] = 0;
$stats['quarter_mark6']['average'] = 0;
$stats['quarter_mark6']['stddev'] = 0;
$stats['quarter_mark6']['median'] = 0;
}
// Distribution (of marks)
$distribution = array();
for ($m = 1.0 ; $m <= 6.0 ; $m += 0.25) $distribution[(string)$m] = 0;
$stats['distribution_total'] = 0;
foreach ($marks as $mark) {
$distribution[(string)$mark]++;
$stats['distribution_total']++;
}
$stats['distribution'] = $distribution;
$stats['distribution_percentage'] = array();
if ($stats['quarter_mark6']['n']) {
foreach ($stats['distribution'] as $mark => $count) $stats['distribution_percentage'][$mark] = $count*100.0/$stats['distribution_total'];
} else {
$stats['distribution_percentage'] = $stats['distribution'];
}
return($stats);
}
public function array_orderby()
{
$args = func_get_args();
$data = array_shift($args);
foreach ($args as $n => $field) {
if (is_string($field)) {
$tmp = array();
foreach ($data as $key => $row)
$tmp[$key] = $row[$field];
$args[$n] = $tmp;
}
}
$args[] = &$data;
call_user_func_array('array_multisort', $args);
return array_pop($args);
}
}
?>

Event Timeline