diff --git a/local/epfl/lib/lib_sel.php b/local/epfl/lib/lib_sel.php index 5ae3fc3..d3b422f 100755 --- a/local/epfl/lib/lib_sel.php +++ b/local/epfl/lib/lib_sel.php @@ -1,206 +1,206 @@ ldap_port = $ldap_port; $this->ldap_host = $ldap_host; $this->ldap_dn = "o=epfl,c=ch"; $this->ldap_connector = ldap_connect($ldap_host, $ldap_port) or die("Could not connect to $ldap_host:$ldap_port."); $this->ldap_attributes = array('personaltitle', 'uniqueidentifier', 'mail', 'sn', 'givenname', 'edupersonaffiliation', 'ou', 'title'); #$this->ldap_attributes = array('*'); } protected function find_by_filter($filter, $limit) { $ldap_results = null; $this->ldap_entries = null; $this->formatted_entries = null; $ldap_results = @ldap_search($this->ldap_connector, $this->ldap_dn, $filter, $this->ldap_attributes, 0, $limit); $this->ldap_entries = ldap_get_entries($this->ldap_connector, $ldap_results); $count = $this->ldap_entries['count']; if ($count) { $this->format_entries(); return $count; } return false; } public function find_by_sciper($sciper, $type = null, $limit = 50) { if (is_null($sciper) or !is_numeric($sciper) or ($sciper < 100000) or ($sciper > 999999)) throw new Exception("Invalid sciper: '$sciper'"); $filter = 'uniqueIdentifier='.$sciper; if (!is_null($type)) { $filter = "(&($filter)(eduPersonAffiliation=$type))"; } return $this->find_by_filter($filter, $limit); } public function find_by_givenname($name, $limit = 50) { $filter = 'givenname='.$name; return $this->find_by_filter($filter, $limit); } public function find_by_surname($name, $limit = 50) { $filter = 'sn='.$name; return $this->find_by_filter($filter, $limit); } public function find_by_ldap_filter($filter, $limit = 50) { return $this->find_by_filter($filter, $limit); } protected function format_entries() { $this->formatted_entries = array(); foreach ($this->ldap_entries as $entry) { if (! is_array($entry)) continue; $formatted_entry = array( 'found' => 1 ); # Sex switch ($entry['personaltitle'][0]) { case 'Madame': - $formatted_entry['sex'] = 'F'; + $formatted_entry['gender'] = 'F'; break; case 'Monsieur': - $formatted_entry['sex'] = 'M'; + $formatted_entry['gender'] = 'M'; break; default: - $formatted_entry['sex'] = 'X'; + $formatted_entry['gender'] = 'X'; } # SCIPER $formatted_entry['sciper'] = $entry['uniqueidentifier'][0]; # email address if (!array_key_exists('mail', $entry)) { $formatted_entry['email'] = $formatted_entry['sciper'].'@epfl.ch'; } else { $formatted_entry['email'] = $entry['mail'][0]; } # Surname $i = null; #if (array_key_exists(2, $entry['sn'])) { # $i = 2; #} elseif (array_key_exists(0, $entry['sn'])) { # $i = 0; #} if (array_key_exists(0, $entry['sn'])) { $i = 0; } if (is_null($i)) { $formatted_entry['surname'] = 'X'; } else { $formatted_entry['surname'] = $entry['sn'][$i]; } # Givenname $i = null; if (array_key_exists(0, $entry['givenname'])) { $i = 0; } if (array_key_exists(2, $entry['givenname'])) { $i = 2; } elseif (array_key_exists(0, $entry['givenname'])) { $i = 0; } if (is_null($i)) { $givenname = 'X'; } else { $givenname = $entry['givenname'][$i]; } if ($formatted_entry['email'] != $formatted_entry['sciper'].'@epfl.ch') { $email_givenname_size = strlen(explode('.', $formatted_entry['email'])[0]); } else { // email address is based on SCIPER... set $email_givenname_size to 1000 to get all surnames. $email_givenname_size = 1000; } $givennames = explode(' ', $givenname); $i = 1; $final_givenname = $givennames[0]; $max = count($givennames)-1; while ((mb_strlen($final_givenname, 'UTF-8') < $email_givenname_size) and ($i <= $max)) { $final_givenname .= ' '.$givennames[$i]; $i++; } # if email as a '-', put it back in the given name if missing if (preg_match('/-/', explode('.', $formatted_entry['email'])[0]) and preg_match('/ /', $final_givenname)) { $final_givenname = preg_replace('/ /', '-', $final_givenname); } $formatted_entry['givenname'] = $final_givenname; $formatted_entry['name'] = $formatted_entry['surname']." ".$formatted_entry['givenname']; # Type $formatted_entry['type'] = $entry['edupersonaffiliation'][0]; # Details switch ($formatted_entry['type']) { case 'student': if (array_key_exists(0, $entry['ou'])) { if (preg_match('/-/', $entry['ou'][0])) { $formatted_entry['section'] = explode('-', $entry['ou'][0])[0]; $formatted_entry['cursus'] = explode('-', $entry['ou'][0])[1]; } else { $formatted_entry['section'] = $entry['ou'][0]; $formatted_entry['cursus'] = 'XX'; } } else { $formatted_entry['section'] = 'XXX'; $formatted_entry['cursus'] = 'XX'; } break; case 'staff': if (array_key_exists('ou', $entry)) { if (array_key_exists(0, $entry['ou'])) $formatted_entry['where'] = $entry['ou'][0]; if (array_key_exists(1, $entry['ou'])) $formatted_entry['where_1'] = $entry['ou'][1]; } if (array_key_exists('title', $entry)) { if (array_key_exists(0, $entry['title'])) $formatted_entry['title'] = $entry['title'][0]; if (array_key_exists(1, $entry['title'])) $formatted_entry['title_1'] = $entry['title'][1]; } break; } # Add the newly formatted entry $this->formatted_entries[] = $formatted_entry; } } public function get_entry() { if (!is_null($this->formatted_entries) and array_key_exists(0, $this->formatted_entries)) return $this->formatted_entries[0]; else return null; } public function get_entries() { return $this->formatted_entries; } } class Exporter { public function to_bash($array) { if (is_array($array)) foreach($array as $key => $value) echo "$key:$value\n"; } } ?> diff --git a/local/stats/amcstats b/local/stats/amcstats index 3064310..2933968 100755 --- a/local/stats/amcstats +++ b/local/stats/amcstats @@ -1,265 +1,273 @@ #!/usr/bin/env php Notes: - Script has be be executable (chmod +x). - INPUT: total points for the student as first parameter (can be negative if negative marking has been used) - OUTPUT: non-rounded mark, with '.' as decimal separator "; exit; } if (!empty($options['current-dir'])) { $total_points = $options['current-dir']; $filename = array(); foreach(scandir('./') as $f) if (preg_match('/\.csv$/', $f)) $filename[] = $f; if (count($filename)) { $csv = ""; $first = true; foreach($filename as $f) { if (!$first) $csv .= ','; $csv .= ucfirst(preg_replace('/\.csv$/', '', $f)); $csv .= "/$f/$total_points"; $first = false; } } else { echo "No AMC stat files found in this directory.\n"; exit; } } $only_questions = null; if (!empty($options['only-questions'])) { $filename = $options['only-questions']; if(!is_file($filename)) { echo "File not found: $filename\n"; exit; } $only_questions = file($filename, FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES); } $external = null; if (!empty($options['external'])) { $external = $options['external']; if(!is_file($external)) { echo "External script/exec not found: $external\n"; exit; } } $inverse_filter = false; if (array_key_exists('inverse-filter', $options)) { $inverse_filter = true; } if (!empty($options['csv'])) $csv = $options['csv']; if (!empty($options['sample'])) $samples = explode(',', $options['sample']); else $samples = array( 'global'); if (!empty($options['type'])) $types = explode(',', $options['type']); else $types = array( 'average' ); // Create ExamCalcs object $Global = new ExamCalcs(); // Load files $loaded = array(); foreach(explode(',', $csv) as $file_info) { $details = explode('/', $file_info); if (count($details) < 3 ) { echo "CSV file list should look like: tournesol/tournesol.csv/30[,favre@favre.csv@29,...]\n"; exit; } $name = $details[0]; $filename = $details[1]; $total = $details[2]; if(!is_file($filename)) { echo "File not found: $filename\n"; exit; } if (in_array($filename, $loaded)) { echo "Trying to load $filename a second time...\n"; exit; } $Global->addFile($name, $filename, $total, $only_questions, $inverse_filter, $external); $loaded[] = $filename; } foreach ($types as $type) { // Build samples foreach ($samples as $sample) { // Print header $sample_header = strtoupper($sample); switch ($type) { case 'average': printAverage($sample_header); break; case 'dist': case 'dist_percentage': printDistribution($sample_header); break; case 'marks': break; } // Build samples $Samples = array(); switch ($sample) { case 'global': $Samples['global'] = new ExamCalcs($Global->getDataSet()); break; case 'sections': foreach ($Global->getSections() as $section) { $tmpSample = new ExamCalcs($Global->getDataSet()); $tmpSample->filterBySections($section); $Samples[$section] = $tmpSample; } break; case 'profs': foreach ($Global->getTeachers() as $prof) { $tmpSample = new ExamCalcs($Global->getDataSet()); $tmpSample->filterByTeachers($prof); $Samples[$prof] = $tmpSample; } break; } // Print stats + $is_first = true; foreach ($Samples as $name => $S) { + if (!$is_first) echo "\n"; else $is_first = false; $stats = $S->getStats(); switch ($type) { case 'average': printAverage($name, $stats); break; case 'dist_percentage': printDistribution($name, $stats, true); break; case 'dist': printDistribution($name, $stats); break; - case 'questions': + case 'discrimination': echo "\"STATS ON QUESTIONS: ".strtoupper($name)."\"\n"; try { $S->printStatsOnCommonItems(); } catch (Exception $e) { echo "\"Not enough data\"\n"; } break; case 'marks': $marks = $S->getMarks(); foreach ($marks as $student) { echo implode(';', $student)."\n"; } break; + case 'questions': + $questions = $S->getQuestions(); + foreach ($questions as $student) { + echo implode(';', $student)."\n"; + } + break; } + } - echo "\n"; } } exit; ?> diff --git a/local/stats/lib/lib_amcstats.php b/local/stats/lib/lib_amcstats.php index ea49887..c7e9557 100755 --- a/local/stats/lib/lib_amcstats.php +++ b/local/stats/lib/lib_amcstats.php @@ -1,593 +1,629 @@ 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() { 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($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) { if (preg_match('/\./', $line[$col_id])) $decimal = true; if ($line[$col_id] > $max_points) $max_points = $line[$col_id]; } // This only works for MATHS exams... if ($decimal or $max_points > 3) { // Only open questions have decimal points return 'open'; } if ($max_points == 3) return 'mc'; if ($max_points == 1) return 'tf'; return 'unknown'; } 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) { 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 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 '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 '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 '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) { $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; default: $stats[$name]['ticked'] = $this->createAndIncrement($stats[$name]['ticked'], $item['ticked']); break; } } } } } // Compute more stats $tmp = array(); foreach ($stats as $name => $stat) { // Discrimination index // For open questions, change the '27%' value. if ($stat['subtype'] == 'open') $stat['27%'] = $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; // Print CSV $previous_subtype = null; $header = '"question_id","subtype","27 %","upper","lower","DI","count","valid"'; foreach ($stats as $name => $stat) { if ($stat['subtype'] != $previous_subtype) { #if (!is_null($previous_subtype)) echo "\n"; echo $header; if ($stat['subtype'] != 'open') { foreach ($stat['ticked'] as $answer => $data) echo ",\"[$answer] count\""; foreach ($stat['ticked'] as $answer => $data) echo ",\"[$answer] %\""; } echo "\n"; $previous_subtype = $stat['subtype']; } echo "$name,{$stat['subtype']},{$stat['27%']},{$stat['upper']},{$stat['lower']},{$stat['DI']},{$stat['ticked_count']}"; if ($stat['subtype'] == 'open') { echo ",\"n/a\""; } else { 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['%']}"; } 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 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); } } ?>