diff --git a/local/stats/lib/lib_amcstats_new.php b/local/stats/lib/lib_amcstats_new.php index 96fc7a0..22b03ff 100755 --- a/local/stats/lib/lib_amcstats_new.php +++ b/local/stats/lib/lib_amcstats_new.php @@ -1,1008 +1,1008 @@ 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, "\TRUE") !== FALSE){ return 'tf'; } } $tmp_lineNum++; } fclose($tmp_file); return 'mc'; } } $lineNum++; } - echo basename($tex_file); + //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']; // 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 'mc': case 'mcm': if ($i <= $upper_stop) $stats[$name]['upper'] += $item['points']; if ($i >= $lower_start) $stats[$name]['lower'] += $item['points']; break; 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){ 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') { 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() { - echo "test"; + // 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']; // 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') !== false) { echo "\histoTF{\\" . $teacher . "}{\N" . $teacher . "}{" . $stat['ticked_count'] . "}"; }else{ 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); } } ?>