diff --git a/local/stats/lib/lib_amcstats.php b/local/stats/lib/lib_amcstats.php index c0c6055..48a4879 100755 --- a/local/stats/lib/lib_amcstats.php +++ b/local/stats/lib/lib_amcstats.php @@ -1,417 +1,418 @@ filename = $filename; $this->teacher = $teacher; $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(); } 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; //$ticked_col = $this->getColIdsByType('ticked', $this->getQuestionNameByColId($col_id)); $min_points = 0; $max_points = 0; foreach($this->raw_data as $line) { if (preg_match('/\./', $line[$col_id])) return 'open'; if ($line[$col_id] > $max_points) $max_points = $line[$col_id]; } 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 parseStudents() { 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); $item['points'] = (float)$student[$col]; $item['right'] = (int)($item['points']>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); 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) { echo "Adding $teacher ($teacher_file) to the dataset ($max_points points).\n"; $AR = new AmcReader($teacher_file, $teacher, $max_points); $this->addDataSet($AR->getStudents()); } public function addDataSet($data) { foreach ($data as $student) $this->dataset[] = $student; } 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 doStatsOnCommonItems() { // 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.'); // Computed limits $nb_students = count($dataset); $twenty_seven = (int)($nb_students*27.0/100); $upper_start = 0; $lower_stop = $nb_students-1; $upper_stop = $twenty_seven-1; $lower_start = $lower_stop-$twenty_seven+1; echo "$nb_students / $twenty_seven / $upper_start -> $upper_stop / $lower_start -> $lower_stop \n"; $stats = array(); foreach ($items as $item_name) { echo "$item_name\n"; $stats[$item_name] = array(); $stats[$item_name]['27%'] = $twenty_seven; $stats[$item_name]['upper'] = 0; $stats[$item_name]['lower'] = 0; // Stats for upper 27% for ($i = $upper_start ; $i <= $upper_stop; $i++) { foreach($dataset[$i]['items'] as $cur_item) { if ($cur_item['name'] == $item_name) { if ($cur_item['right']) $stats[$item_name]['upper']++; } } } // Stats for lower 27% for ($i = $lower_start ; $i <= $lower_stop; $i++) { foreach($dataset[$i]['items'] as $cur_item) { if ($cur_item['name'] == $item_name) { if ($cur_item['right']) $stats[$item_name]['lower']++; } } } $stats[$item_name]['DI'] = (float)(($stats[$item_name]['upper']-$stats[$item_name]['lower'])/$twenty_seven); } $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' => '?', 'ticked'=> array(), 'ticked_count'=> 0, 'empty_count'=> 0, 'type' => null, 'subtype' => null, ); $stats[$name]['type'] = $item['type']; $stats[$name]['subtype'] = $item['subtype']; if ($item['right']) { if ($stats[$name]['valid'] == '?') if ($item['subtype'] = 'tf') { if ($item['ticked'] == 'A') $stats[$name]['valid'] = 'TRUE'; else $stats[$name]['valid'] = 'FALSE'; } else { $stats[$name]['valid'] = $item['ticked']; } if ($i <= $upper_stop) { $stats[$name]['upper']++; } if ($i >= $lower_start) { $stats[$name]['lower']++; } } if (empty($item['ticked'])) { $stats[$name]['empty_count']++; } else { $stats[$name]['ticked_count']++; if (strlen($item['ticked']) > 1) { $stats[$name]['ticked'] = $this->createAndIncrement($stats[$name]['ticked'], 'multiple'); } else { if ($item['subtype'] = 'tf') { if ($item['ticked'] == 'A') $field = 'TRUE'; if ($item['ticked'] == 'B') $field = 'FALSE'; $stats[$name]['ticked'] = $this->createAndIncrement($stats[$name]['ticked'], $field); } else { $stats[$name]['ticked'] = $this->createAndIncrement($stats[$name]['ticked'], $item['ticked']); } } } } } // Compute more stats... $tmp = array(); - foreach ($stats as $stat) { + foreach ($stats as $name => $stat) { + // Discrimination index $stat['DI'] = ($stat['upper']-$stat['lower'])/(1.0*$stat['27%']); - $tmp[] = $stat; + $tmp[$name] = $stat; } $stats = $tmp; print_r($stats); } 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 doStats() { $stats = array(); # Presence $stats['presence'] = array(); foreach($this->dataset as $student) { // Create category if need be $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'); } } print_r($stats); } } ?>