File:
[LON-CAPA] /
loncom /
interface /
statistics /
lonproblemanalysis.pm
Revision
1.42:
download - view:
text,
annotated -
select for diffs
Fri Oct 17 22:05:45 2003 UTC (20 years, 9 months ago) by
matthew
Branches:
MAIN
CVS tags:
HEAD
Many changes, most to internal code structure. Hopefully future changes
will not require such monsterous rewrites.
Foils analysis now shows two graphs, one for the correct choice on the
foils and the other for the incorrect submissions.
Time analysis no longer has editable plot titles.
Added a few helper functions get_time_from_row, get_tries_from_row, and
Process_Row.
1: # The LearningOnline Network with CAPA
2: #
3: # $Id: lonproblemanalysis.pm,v 1.42 2003/10/17 22:05:45 matthew Exp $
4: #
5: # Copyright Michigan State University Board of Trustees
6: #
7: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
8: #
9: # LON-CAPA is free software; you can redistribute it and/or modify
10: # it under the terms of the GNU General Public License as published by
11: # the Free Software Foundation; either version 2 of the License, or
12: # (at your option) any later version.
13: #
14: # LON-CAPA is distributed in the hope that it will be useful,
15: # but WITHOUT ANY WARRANTY; without even the implied warranty of
16: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17: # GNU General Public License for more details.
18: #
19: # You should have received a copy of the GNU General Public License
20: # along with LON-CAPA; if not, write to the Free Software
21: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22: #
23: # /home/httpd/html/adm/gpl.txt
24: #
25: # http://www.lon-capa.org/
26: #
27:
28: package Apache::lonproblemanalysis;
29:
30: use strict;
31: use Apache::lonnet();
32: use Apache::loncommon();
33: use Apache::lonhtmlcommon();
34: use Apache::loncoursedata();
35: use Apache::lonstatistics;
36: use Apache::lonlocal;
37: use HTML::Entities();
38: use Time::Local();
39:
40: my $plotcolors = ['#33ff00',
41: '#0033cc', '#990000', '#aaaa66', '#663399', '#ff9933',
42: '#66ccff', '#ff9999', '#cccc33', '#660000', '#33cc66',
43: ];
44:
45: sub BuildProblemAnalysisPage {
46: my ($r,$c)=@_;
47: $r->print('<h2>'.&mt('Option Response Problem Analysis').'</h2>');
48: $r->print(&CreateInterface());
49: #
50: my @Students = @Apache::lonstatistics::Students;
51: #
52: if (exists($ENV{'form.ClearCache'}) ||
53: exists($ENV{'form.updatecaches'}) ||
54: (exists($ENV{'form.firstanalysis'}) &&
55: $ENV{'form.firstanalysis'} ne 'no')) {
56: &Apache::lonstatistics::Gather_Full_Student_Data($r);
57: }
58: if (! exists($ENV{'form.firstanalysis'})) {
59: $r->print('<input type="hidden" name="firstanalysis" value="yes" />');
60: } else {
61: $r->print('<input type="hidden" name="firstanalysis" value="no" />');
62: }
63: $r->rflush();
64: if (exists($ENV{'form.problemchoice'}) &&
65: ! exists($ENV{'form.SelectAnother'})) {
66: $r->print('<input type="submit" name="ProblemAnalysis" value="'.
67: &mt('Analyze Problem Again').'" />');
68: $r->print(' 'x5);
69: $r->print('<input type="submit" name="ClearCache" value="'.
70: &mt('Clear Caches').'" />');
71: $r->print(' 'x5);
72: $r->print('<input type="submit" name="updatecaches" value="'.
73: &mt('Update Student Data').'" />');
74: $r->print(' 'x5);
75: $r->print('<input type="hidden" name="problemchoice" value="'.
76: $ENV{'form.problemchoice'}.'" />');
77: $r->print('<input type="submit" name="SelectAnother" value="'.
78: &mt('Choose a different resource').'" />');
79: $r->print(' 'x5);
80: #
81: $r->print('<hr />');
82: #
83: my ($symb,$part,$resid) = &get_problem_symb(
84: &Apache::lonnet::unescape($ENV{'form.problemchoice'})
85: );
86: #
87: my $resource = &get_resource_from_symb($symb);
88: if (defined($resource)) {
89: $r->print('<h3>'.$resource->{'src'}.'</h3>');
90: my %Data = &get_problem_data($resource->{'src'});
91: my $ORdata = $Data{$part.'.'.$resid};
92: ##
93: ## Render the problem
94: my $base;
95: ($base,undef) = ($resource->{'src'} =~ m|(.*/)[^/]*$|);
96: $base = "http://".$ENV{'SERVER_NAME'}.$base;
97: my $rendered_problem =
98: &Apache::lonnet::ssi_body($resource->{'src'});
99: $rendered_problem =~ s/<\s*form\s*/<nop /g;
100: $rendered_problem =~ s|(<\s*/form\s*>)|<\/nop>|g;
101: $r->print('<table bgcolor="ffffff"><tr><td>'.
102: '<base href="'.$base.'" />'.
103: $rendered_problem.
104: '</td></tr></table>');
105: ##
106: ## Analyze the problem
107: my $PerformanceData =
108: &Apache::loncoursedata::get_optionresponse_data
109: (\@Students,$symb,$resid);
110: if (defined($PerformanceData) &&
111: ref($PerformanceData) eq 'ARRAY') {
112: if ($ENV{'form.AnalyzeOver'} eq 'Tries') {
113: my $analysis_html = &tries_analysis($PerformanceData,
114: $ORdata);
115: $r->print($analysis_html);
116: } elsif ($ENV{'form.AnalyzeOver'} eq 'Time') {
117: my $analysis_html = &time_analysis($PerformanceData,
118: $ORdata);
119: $r->print($analysis_html);
120: } else {
121: $r->print('<h2>'.
122: &mt('The analysis you have selected is '.
123: 'not supported at this time').
124: '</h2>');
125: }
126: } else {
127: $r->print('<h2>'.
128: &mt('There is no student data for this problem.').
129: '</h2>');
130: }
131: } else {
132: $r->print('resource is undefined');
133: }
134: $r->print('<hr />');
135: } else {
136: $r->print('<input type="submit" name="ProblemAnalysis" value="'.
137: &mt('Analyze Problem').'" />');
138: $r->print(' 'x5);
139: $r->print('<h3>'.&mt('Please select a problem to analyze').'</h3>');
140: $r->print(&OptionResponseProblemSelector());
141: }
142: }
143:
144: #########################################################
145: #########################################################
146: ##
147: ## Misc interface routines use by analysis code
148: ##
149: #########################################################
150: #########################################################
151: sub build_foil_index {
152: my ($ORdata) = @_;
153: return if (! exists($ORdata->{'Foils'}));
154: my %Foildata = %{$ORdata->{'Foils'}};
155: my @Foils = sort(keys(%Foildata));
156: my %Concepts;
157: foreach my $foilid (@Foils) {
158: push(@{$Concepts{$Foildata{$foilid}->{'Concept'}}},
159: $foilid);
160: }
161: undef(@Foils);
162: # Having gathered the concept information in a hash, we now translate it
163: # into an array because we need to be consistent about order.
164: # Also put the foils in order, too.
165: my $sortfunction = sub {
166: my %Numbers = (one => 1,
167: two => 2,
168: three => 3,
169: four => 4,
170: five => 5,
171: six => 6,
172: seven => 7,
173: eight => 8,
174: nine => 9,
175: ten => 10,);
176: my $a1 = $a;
177: my $b1 = $b;
178: if (exists($Numbers{lc($a)})) {
179: $a1 = $Numbers{lc($a)};
180: }
181: if (exists($Numbers{lc($b)})) {
182: $b1 = $Numbers{lc($b)};
183: }
184: $a1 cmp $b1;
185: };
186: my @Concepts;
187: foreach my $concept (sort $sortfunction (keys(%Concepts))) {
188: push(@Concepts,{ name => $concept,
189: foils => [@{$Concepts{$concept}}]});
190: push(@Foils,(@{$Concepts{$concept}}));
191: }
192: #
193: # Build up the table of row labels.
194: my $table = '<table border="1" >'."\n";
195: if (@Concepts > 1) {
196: $table .= '<tr>'.
197: '<th>'.&mt('Concept Number').'</th>'.
198: '<th>'.&mt('Concept').'</th>'.
199: '<th>'.&mt('Foil Number').'</th>'.
200: '<th>'.&mt('Foil Name').'</th>'.
201: '<th>'.&mt('Foil Text').'</th>'.
202: '<th>'.&mt('Correct Value').'</th>'.
203: "</tr>\n";
204: } else {
205: $table .= '<tr>'.
206: '<th>'.&mt('Foil Number').'</th>'.
207: '<th>'.&mt('Foil Name').'</th>'.
208: '<th>'.&mt('Foil Text').'</th>'.
209: '<th>'.&mt('Correct Value').'</th>'.
210: "</tr>\n";
211: }
212: my $conceptindex = 1;
213: my $foilindex = 1;
214: foreach my $concept (@Concepts) {
215: my @FoilsInConcept = @{$concept->{'foils'}};
216: my $firstfoil = shift(@FoilsInConcept);
217: if (@Concepts > 1) {
218: $table .= '<tr>'.
219: '<td>'.$conceptindex.'</td>'.
220: '<td>'.$concept->{'name'}.'</td>'.
221: '<td>'.$foilindex++.'</td>'.
222: '<td>'.$Foildata{$firstfoil}->{'name'}.'</td>'.
223: '<td>'.$Foildata{$firstfoil}->{'text'}.'</td>'.
224: '<td>'.$Foildata{$firstfoil}->{'value'}.'</td>'.
225: "</tr>\n";
226: } else {
227: $table .= '<tr>'.
228: '<td>'.$foilindex++.'</td>'.
229: '<td>'.$Foildata{$firstfoil}->{'name'}.'</td>'.
230: '<td>'.$Foildata{$firstfoil}->{'text'}.'</td>'.
231: '<td>'.$Foildata{$firstfoil}->{'value'}.'</td>'.
232: "</tr>\n";
233: }
234: foreach my $foilid (@FoilsInConcept) {
235: if (@Concepts > 1) {
236: $table .= '<tr>'.
237: '<td></td>'.
238: '<td></td>'.
239: '<td>'.$foilindex.'</td>'.
240: '<td>'.$Foildata{$foilid}->{'name'}.'</td>'.
241: '<td>'.$Foildata{$foilid}->{'text'}.'</td>'.
242: '<td>'.$Foildata{$foilid}->{'value'}.'</td>'.
243: "</tr>\n";
244: } else {
245: $table .= '<tr>'.
246: '<td>'.$foilindex.'</td>'.
247: '<td>'.$Foildata{$foilid}->{'name'}.'</td>'.
248: '<td>'.$Foildata{$foilid}->{'text'}.'</td>'.
249: '<td>'.$Foildata{$foilid}->{'value'}.'</td>'.
250: "</tr>\n";
251: }
252: } continue {
253: $foilindex++;
254: }
255: } continue {
256: $conceptindex++;
257: }
258: $table .= "</table>\n";
259: #
260: # Build option index with color stuff
261: return ($table,\@Foils,\@Concepts);
262: }
263:
264: sub build_option_index {
265: my ($ORdata)= @_;
266: my $table = "<table>\n";
267: my $optionindex = 0;
268: my @Rows;
269: foreach my $option (&mt('correct option chosen'),@{$ORdata->{'Options'}}) {
270: push (@Rows,
271: '<tr>'.
272: '<td bgcolor="'.$plotcolors->[$optionindex++].'">'.
273: (' 'x4).'</td>'.
274: '<td>'.$option.'</td>'.
275: "</tr>\n");
276: }
277: shift(@Rows); # Throw away 'correct option chosen' color
278: $table .= join('',reverse(@Rows));
279: $table .= "</table>\n";
280: }
281:
282: #########################################################
283: #########################################################
284: ##
285: ## Tries Analysis
286: ##
287: #########################################################
288: #########################################################
289: sub tries_analysis {
290: my ($PerformanceData,$ORdata) = @_;
291: my $mintries = 1;
292: my $maxtries = $ENV{'form.NumPlots'};
293: my ($table,$Foils,$Concepts) = &build_foil_index($ORdata);
294: if ((@$Concepts < 2) && ($ENV{'form.AnalyzeAs'} ne 'Foils')) {
295: $table = '<h3>'.
296: &mt('Not enough data for concept analysis. '.
297: 'Performing Foil Analysis').
298: '</h3>'.$table;
299: $ENV{'form.AnalyzeAs'} = 'Foils';
300: }
301: my %ResponseData = &analyze_option_data_by_tries($PerformanceData,
302: $mintries,$maxtries);
303: my $analysis = '';
304: if ($ENV{'form.AnalyzeAs'} eq 'Foils') {
305: $analysis = &Tries_Foil_Analysis($mintries,$maxtries,$Foils,
306: \%ResponseData,$ORdata);
307: } else {
308: $analysis = &Tries_Concept_Analysis($mintries,$maxtries,
309: $Concepts,\%ResponseData,$ORdata);
310: }
311: $table .= $analysis;
312: return $table;
313: }
314:
315: sub Tries_Foil_Analysis {
316: my ($mintries,$maxtries,$Foils,$respdat,$ORdata) = @_;
317: my %ResponseData = %$respdat;
318: #
319: # Compute the data neccessary to make the plots
320: my @PlotData;
321: foreach my $foilid (@$Foils) {
322: for (my $i=$mintries;$i<=$maxtries;$i++) {
323: if ($ResponseData{$foilid}->[$i]->{'_total'} == 0) {
324: push(@{$PlotData[$i]->{'_correct'}},0);
325: } else {
326: push(@{$PlotData[$i]->{'_correct'}},
327: 100*$ResponseData{$foilid}->[$i]->{'_correct'}/
328: $ResponseData{$foilid}->[$i]->{'_total'});
329: }
330: foreach my $option (@{$ORdata->{'Options'}}) {
331: push(@{$PlotData[$i]->{'_total'}},
332: $ResponseData{$foilid}->[$i]->{'_total'});
333: if ($ResponseData{$foilid}->[$i]->{'_total'} == 0) {
334: push (@{$PlotData[$i]->{$option}},0);
335: } else {
336: if ($ResponseData{$foilid}->[$i]->{'_total'} ==
337: $ResponseData{$foilid}->[$i]->{'_correct'}) {
338: push(@{$PlotData[$i]->{$option}},0);
339: } else {
340: push (@{$PlotData[$i]->{$option}},
341: 100 * $ResponseData{$foilid}->[$i]->{$option} /
342: ($ResponseData{$foilid}->[$i]->{'_total'} -
343: $ResponseData{$foilid}->[$i]->{'_correct'}));
344: }
345: }
346: }
347: }
348: }
349: #
350: # Build a table for the plots
351: my $analysis_html = "<table>\n";
352: my $foilkey = &build_option_index($ORdata);
353: for (my $i=$mintries;$i<=$maxtries;$i++) {
354: my $count = $ResponseData{'_total'}->[$i];
355: if ($count == 0) {
356: $count = 'no submissions';
357: } elsif ($count == 1) {
358: $count = '1 submission';
359: } else {
360: $count = $count.' submissions';
361: }
362: my $title = 'Attempt '.$i.', '.$count;
363: my @Datasets;
364: foreach my $option ('_correct',@{$ORdata->{'Options'}}) {
365: next if (! exists($PlotData[$i]->{$option}));
366: push(@Datasets,$PlotData[$i]->{$option});
367: }
368: my $correctgraph = &Apache::loncommon::DrawGraph
369: ($title,'Foil Number','Percent Correct',
370: 100,$plotcolors,$Datasets[0]);
371: $analysis_html.= '<tr><td>'.$correctgraph.'</td>';
372: ##
373: ##
374: for (my $i=0; $i< scalar(@{$Datasets[0]});$i++) {
375: $Datasets[0]->[$i]=0;
376: }
377: $count = $ResponseData{'_total'}->[$i]-$ResponseData{'_correct'}->[$i];
378: if ($count == 0) {
379: $count = 'no submissions';
380: } elsif ($count == 1) {
381: $count = '1 submission';
382: } else {
383: $count = $count.' submissions';
384: }
385: $title = 'Attempt '.$i.', '.$count;
386: my $incorrectgraph = &Apache::loncommon::DrawGraph
387: ($title,'Foil Number','% Option Chosen Incorrectly',
388: 100,$plotcolors,@Datasets);
389: $analysis_html.= '<td>'.$incorrectgraph.'</td>';
390: $analysis_html.= '<td>'.$foilkey."<td></tr>\n";
391: }
392: $analysis_html .= "</table>\n";
393: return $analysis_html;
394: }
395:
396: sub Tries_Concept_Analysis {
397: my ($mintries,$maxtries,$Concepts,$respdat,$ORdata) = @_;
398: my %ResponseData = %$respdat;
399: my $analysis_html = "<table>\n";
400: #
401: # Compute the data neccessary to make the plots
402: my @PlotData;
403: # Concept analysis
404: #
405: # Note: we do not bother with characterizing the students incorrect
406: # answers at the concept level because an incorrect answer for one foil
407: # may be a correct answer for another foil.
408: my %ConceptData;
409: foreach my $concept (@{$Concepts}) {
410: for (my $i=$mintries;$i<=$maxtries;$i++) {
411: #
412: # Gather the per-attempt data
413: my $cdata = $ConceptData{$concept}->[$i];
414: foreach my $foilid (@{$concept->{'foils'}}) {
415: $cdata->{'_correct'} +=
416: $ResponseData{$foilid}->[$i]->{'_correct'};
417: $cdata->{'_total'} +=
418: $ResponseData{$foilid}->[$i]->{'_total'};
419: }
420: push (@{$PlotData[$i]->{'_total'}},$cdata->{'_total'});
421: if ($cdata->{'_total'} == 0) {
422: push (@{$PlotData[$i]->{'_correct'}},0);
423: } else {
424: push (@{$PlotData[$i]->{'_correct'}},
425: 100*$cdata->{'_correct'}/$cdata->{'_total'});
426: }
427: }
428: }
429: # Build a table for the plots
430: for (my $i=$mintries;$i<=$maxtries;$i++) {
431: my $minstu = $PlotData[$i]->{'_total'}->[0];
432: my $maxstu = $PlotData[$i]->{'_total'}->[0];
433: foreach my $count (@{$PlotData[$i]->{'_total'}}) {
434: if ($minstu > $count) {
435: $minstu = $count;
436: }
437: if ($maxstu < $count) {
438: $maxstu = $count;
439: }
440: }
441: $maxstu = 0 if (! defined($maxstu));
442: $minstu = 0 if (! defined($minstu));
443: my $title;
444: my $count = $ResponseData{'_total'}->[$i];
445: if ($count == 0) {
446: $count = 'no submissions';
447: } elsif ($count == 1) {
448: $count = '1 submission';
449: } else {
450: $count = $count.' submissions';
451: }
452: $title = 'Attempt '.$i.', '.$count;
453: my $graphlink = &Apache::loncommon::DrawGraph
454: ($title,'Concept Number','Percent Correct',
455: 100,$plotcolors,$PlotData[$i]->{'_correct'});
456: $analysis_html.= '<tr><td>'.$graphlink."</td></tr>\n";
457: }
458: $analysis_html .= "</table>\n";
459: return $analysis_html;
460: }
461:
462: sub analyze_option_data_by_tries {
463: my ($PerformanceData,$mintries,$maxtries) = @_;
464: my %Trydata;
465: $mintries = 1 if (! defined($mintries) || $mintries < 1);
466: $maxtries = $mintries if (! defined($maxtries) || $maxtries < $mintries);
467: foreach my $row (@$PerformanceData) {
468: next if (! defined($row));
469: my $tries = &get_tries_from_row($row);
470: my %Row = &Process_Row($row);
471: while (my ($foilid,$href) = each(%Row)) {
472: if (! ref($href)) {
473: $Trydata{$foilid}->[$tries] += $href;
474: next;
475: }
476: while (my ($option,$value) = each(%$href)) {
477: $Trydata{$foilid}->[$tries]->{$option}+=$value;
478: }
479: }
480: }
481: return %Trydata;
482: }
483:
484: #########################################################
485: #########################################################
486: ##
487: ## Time Analysis
488: ##
489: #########################################################
490: #########################################################
491: sub time_analysis {
492: my ($PerformanceData,$ORdata) = @_;
493: my ($table,$Foils,$Concepts) = &build_foil_index($ORdata);
494: if ((@$Concepts < 2) && ($ENV{'form.AnalyzeAs'} ne 'Foils')) {
495: $table = '<h3>'.
496: &mt('Not enough data for concept analysis. '.
497: 'Performing Foil Analysis').
498: '</h3>'.$table;
499: $ENV{'form.AnalyzeAs'} = 'Foils';
500: }
501: my $num_plots = $ENV{'form.NumPlots'};
502: my $num_data = scalar(@$PerformanceData)-1;
503: my $percent = sprintf('%2f',100/$num_plots);
504: #
505: $table .= "<table>\n";
506: for (my $i=0;$i<$num_plots;$i++) {
507: ##
508: my $starttime = &Apache::lonhtmlcommon::get_date_from_form
509: ('startdate_'.$i);
510: my $endtime = &Apache::lonhtmlcommon::get_date_from_form
511: ('enddate_'.$i);
512: if (! defined($starttime) || ! defined($endtime)) {
513: my $sec_in_day = 86400;
514: my $last_sub_time = &get_time_from_row($PerformanceData->[-1]);
515: my ($sday,$smon,$syear);
516: (undef,undef,undef,$sday,$smon,$syear) =
517: localtime($last_sub_time - $sec_in_day*$i);
518: $starttime = &Time::Local::timelocal(0,0,0,$sday,$smon,$syear);
519: $endtime = $starttime + $sec_in_day;
520: if ($i == ($num_plots -1 )) {
521: $starttime = &get_time_from_row($PerformanceData->[0]);
522: }
523: }
524: my $startdateform = &Apache::lonhtmlcommon::date_setter
525: ('Statistics','startdate_'.$i,$starttime);
526: my $enddateform = &Apache::lonhtmlcommon::date_setter
527: ('Statistics','enddate_'.$i,$endtime);
528: #
529: my $begin_index;
530: my $end_index;
531: my $j;
532: while (++$j < scalar(@$PerformanceData)) {
533: last if (&get_time_from_row($PerformanceData->[$j]) > $starttime);
534: }
535: $begin_index = $j;
536: while (++$j < scalar(@$PerformanceData)) {
537: last if (&get_time_from_row($PerformanceData->[$j]) > $endtime);
538: }
539: $end_index = $j;
540: ##
541: my $interval = {
542: begin_index => $begin_index,
543: end_index => $end_index,
544: startdateform => $startdateform,
545: enddateform => $enddateform,
546: };
547: if ($ENV{'form.AnalyzeAs'} eq 'Foils') {
548: $table .= &Foil_Time_Analysis($PerformanceData,$ORdata,$Foils,
549: $interval);
550: } else {
551: $table .= &Concept_Time_Analysis($PerformanceData,$ORdata,
552: $Concepts,$interval);
553: }
554: }
555: #
556: return $table;
557: }
558:
559: sub Foil_Time_Analysis {
560: my ($PerformanceData,$ORdata,$Foils,$interval) = @_;
561: my $analysis_html;
562: my $foilkey = &build_option_index($ORdata);
563: my ($begin_index,$end_index) = ($interval->{'begin_index'},
564: $interval->{'end_index'});
565: my %TimeData;
566: #
567: # Compute the number getting the foils correct or incorrects
568: for (my $j=$begin_index;$j<=$end_index;$j++) {
569: my $row = $PerformanceData->[$j];
570: next if (! defined($row));
571: my %Row = &Process_Row($row);
572: while (my ($foilid,$href) = each(%Row)) {
573: if (! ref($href)) {
574: $TimeData{$foilid} += $href;
575: next;
576: }
577: while (my ($option,$value) = each(%$href)) {
578: $TimeData{$foilid}->{$option}+=$value;
579: }
580: }
581: }
582: my @Plotdata;
583: foreach my $foil (@$Foils) {
584: my $total = $TimeData{$foil}->{'_total'};
585: if ($total == 0) {
586: push(@{$Plotdata[0]},0);
587: } else {
588: push(@{$Plotdata[0]},
589: 100 * $TimeData{$foil}->{'_correct'} / $total);
590: }
591: my $total_incorrect = $total - $TimeData{$foil}->{'_correct'};
592: my $optionidx = 1;
593: foreach my $option (@{$ORdata->{'Options'}}) {
594: if ($total_incorrect == 0) {
595: push(@{$Plotdata[$optionidx]},0);
596: } else {
597: push(@{$Plotdata[$optionidx]},
598: 100 * $TimeData{$foil}->{$option} / $total_incorrect);
599: }
600: } continue {
601: $optionidx++;
602: }
603: }
604: #
605: # Create the plot
606: my $count = $end_index-$begin_index;
607: my $title;
608: if ($count == 0) {
609: $title = 'no submissions';
610: } elsif ($count == 1) {
611: $title = 'one submission';
612: } else {
613: $title = $count.' submissions';
614: }
615: my $correctplot = &Apache::loncommon::DrawGraph($title,
616: 'Foil Number',
617: 'Percent Correct',
618: 100,
619: $plotcolors,
620: $Plotdata[0]);
621: for (my $j=0; $j< scalar(@{$Plotdata[0]});$j++) {
622: $Plotdata[0]->[$j]=0;
623: }
624: $count = $end_index-$begin_index-$TimeData{'_correct'};
625: if ($count == 0) {
626: $title = 'no submissions';
627: } elsif ($count == 1) {
628: $title = 'one submission';
629: } else {
630: $title = $count.' submissions';
631: }
632: my $incorrectplot = &Apache::loncommon::DrawGraph($title,
633: 'Foil Number',
634: 'Incorrect Option Choice',
635: 100,
636: $plotcolors,
637: @Plotdata);
638: $analysis_html.='<tr>'.
639: '<td>'.$correctplot.'</td>'.
640: '<td>'.$incorrectplot.'</td>'.
641: '<td align="left" valign="top">'.$foilkey.'</td>'."</tr>\n";
642: $analysis_html.= '<tr>'.'<td colspan="3">'.
643: '<b>Start Time</b>:'.
644: ' '.$interval->{'startdateform'}.'<br />'.
645: '<b>End Time</b> : '.
646: ' '.$interval->{'enddateform'}.'<br />'.
647: # '<b>Plot Title</b> :'.
648: # (" "x3).$interval->{'titleform'}.
649: '</td>'.
650: "</tr>\n";
651: return $analysis_html;
652: }
653:
654: sub Concept_Time_Analysis {
655: my ($PerformanceData,$ORdata,$Concepts,$interval) = @_;
656: my $analysis_html;
657: ##
658: ## Determine starttime, endtime, startindex, endindex
659: my ($begin_index,$end_index) = ($interval->{'begin_index'},
660: $interval->{'end_index'});
661: my %TimeData;
662: #
663: # Compute the number getting the foils correct or incorrects
664: for (my $j=$begin_index;$j<=$end_index;$j++) {
665: my $row = $PerformanceData->[$j];
666: next if (! defined($row));
667: my %Row = &Process_Row($row);
668: while (my ($foilid,$href) = each(%Row)) {
669: if (! ref($href)) {
670: $TimeData{$foilid} += $href;
671: next;
672: }
673: while (my ($option,$value) = each(%$href)) {
674: $TimeData{$foilid}->{$option}+=$value;
675: }
676: }
677: }
678: #
679: # Put the data in plottable form
680: my @Plotdata;
681: foreach my $concept (@$Concepts) {
682: my ($total,$correct);
683: foreach my $foil (@{$concept->{'foils'}}) {
684: $total += $TimeData{$foil}->{'_total'};
685: $correct += $TimeData{$foil}->{'_correct'};
686: }
687: if ($total == 0) {
688: push(@Plotdata,0);
689: } else {
690: push(@Plotdata,100 * $correct / $total);
691: }
692: }
693: #
694: # Create the plot
695: my $title = ($end_index - $begin_index).' submissions';
696: my $correctplot = &Apache::loncommon::DrawGraph($title,
697: 'Concept Number',
698: 'Percent Correct',
699: 100,
700: $plotcolors,
701: \@Plotdata);
702: $analysis_html.='<tr>'.
703: '<td>'.$correctplot.'</td>'.
704: '<td align="left" valign="top">'.
705: '<b>Start Time</b>: '.$interval->{'startdateform'}.'<br />'.
706: '<b>End Time</b> : '.
707: ' '.$interval->{'enddateform'}.'<br />'.
708: # '<b>Plot Title</b> :'.(" "x3).
709: # $interval->{'titleform'}.
710: '</td>'.
711: "</tr>\n";
712: return $analysis_html;
713: }
714:
715: #########################################################
716: #########################################################
717: ##
718: ## Excel output
719: ##
720: #########################################################
721: #########################################################
722: sub prepare_excel_sheet {
723: my ($PerformanceData,$ORdata) = @_;
724: my (undef,$Foils,$Concepts) = &build_foil_index($ORdata);
725: }
726:
727: sub process_data_for_excel_sheet {
728: my ($PerformanceData,$Foils,$Concepts,$ORdata)=@_;
729: }
730:
731: #########################################################
732: #########################################################
733: ##
734: ## Interface
735: ##
736: #########################################################
737: #########################################################
738: sub CreateInterface {
739: ##
740: ## Environment variable initialization
741: if (! exists$ENV{'form.AnalyzeOver'}) {
742: $ENV{'form.AnalyzeOver'} = 'Tries';
743: }
744: ##
745: ## Build the menu
746: my $Str = '';
747: $Str .= '<table cellspacing="5">'."\n";
748: $Str .= '<tr>';
749: $Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>';
750: $Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>';
751: # $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b></td>';
752: $Str .= '<td align="center"> </td>';
753: $Str .= '</tr>'."\n";
754: ##
755: ##
756: $Str .= '<tr><td align="center">'."\n";
757: $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
758: $Str .= '</td>';
759: #
760: $Str .= '<td align="center">';
761: $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
762: $Str .= '</td>';
763: #
764: # $Str .= '<td align="center">';
765: my $only_seq_with_assessments = sub {
766: my $s=shift;
767: if ($s->{'num_assess'} < 1) {
768: return 0;
769: } else {
770: return 1;
771: }
772: };
773: &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
774: $only_seq_with_assessments);
775: ##
776: ##
777: $Str .= '<td>';
778: { # These braces are here to organize the code, not scope it.
779: {
780: $Str .= '<nobr>'.&mt('Analyze Over ');
781: $Str .='<select name="AnalyzeOver" >';
782: $Str .= '<option value="Tries" ';
783: if (! exists($ENV{'form.AnalyzeOver'}) ||
784: $ENV{'form.AnalyzeOver'} eq 'Tries'){
785: # Default to Tries
786: $Str .= ' selected ';
787: }
788: $Str .= '>'.&mt('Tries').'</option>';
789: $Str .= '<option value="Time" ';
790: $Str .= ' selected ' if ($ENV{'form.AnalyzeOver'} eq 'Time');
791: $Str .= '>'.&mt('Time').'</option>';
792: $Str .= '</select></nobr><br />';
793: }
794: {
795: $Str .= '<nobr>'.&mt('Analyze as ');
796: $Str .='<select name="AnalyzeAs" >';
797: $Str .= '<option value="Concepts" ';
798: if (! exists($ENV{'form.AnalyzeAs'}) ||
799: $ENV{'form.AnalyzeAs'} eq 'Concepts'){
800: # Default to Concepts
801: $Str .= ' selected ';
802: }
803: $Str .= '>'.&mt('Concepts').'</option>';
804: $Str .= '<option value="Foils" ';
805: $Str .= ' selected ' if ($ENV{'form.AnalyzeAs'} eq 'Foils');
806: $Str .= '>'.&mt('Foils').'</option>';
807: $Str .= '</select></nobr><br />';
808: }
809: {
810: $Str .= '<br /><nobr>'.&mt('Number of Plots:');
811: $Str .= '<select name="NumPlots">';
812: if (! exists($ENV{'form.NumPlots'})
813: || $ENV{'form.NumPlots'} < 1
814: || $ENV{'form.NumPlots'} > 20) {
815: $ENV{'form.NumPlots'} = 5;
816: }
817: foreach my $i (1,2,3,4,5,6,7,8,10,15,20) {
818: $Str .= '<option value="'.$i.'" ';
819: if ($ENV{'form.NumPlots'} == $i) { $Str.=' selected '; }
820: $Str .= '>'.$i.'</option>';
821: }
822: $Str .= '</select></nobr>';
823: }
824: }
825: $Str .= '</td>';
826: ##
827: ##
828: $Str .= '</tr>'."\n";
829: $Str .= '</table>'."\n";
830: return $Str;
831: }
832:
833: sub OptionResponseProblemSelector {
834: my $Str;
835: $Str = "\n<table>\n";
836: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
837: next if ($seq->{'num_assess'}<1);
838: my $seq_str = '';
839: foreach my $res (@{$seq->{'contents'}}) {
840: next if ($res->{'type'} ne 'assessment');
841: foreach my $part (@{$res->{'parts'}}) {
842: my $partdata = $res->{'partdata'}->{$part};
843: if (! exists($partdata->{'option'}) ||
844: $partdata->{'option'} == 0) {
845: next;
846: }
847: for (my $i=0;$i<scalar(@{$partdata->{'ResponseTypes'}});$i++){
848: my $respid = $partdata->{'ResponseIds'}->[$i];
849: my $resptype = $partdata->{'ResponseTypes'}->[$i];
850: if ($resptype eq 'option') {
851: my $value = &Apache::lonnet::escape($res->{'symb'}.':'.$part.':'.$respid);
852: my $checked = '';
853: if ($ENV{'form.problemchoice'} eq $value) {
854: $checked = 'checked ';
855: }
856: $seq_str .= '<tr><td>'.
857: '<input type="radio" name="problemchoice" value="'.$value.'" '.$checked.'/>'.
858: '</td><td>'.
859: '<a href="'.$res->{'src'}.'">'.$res->{'title'}.'</a> ';
860: if ($partdata->{'option'} > 1) {
861: $seq_str .= &mt('response').' '.$respid;
862: }
863: $seq_str .= "</td></tr>\n";
864: }
865: }
866: }
867: }
868: if ($seq_str ne '') {
869: $Str .= '<tr><td> </td><td><b>'.$seq->{'title'}.'</b></td>'.
870: "</tr>\n".$seq_str;
871: }
872: }
873: $Str .= "</table>\n";
874: return $Str;
875: }
876:
877: #########################################################
878: #########################################################
879: ##
880: ## Misc functions
881: ##
882: #########################################################
883: #########################################################
884: sub get_problem_symb {
885: my $problemstring = shift();
886: my ($symb,$partid,$resid) = ($problemstring=~ /^(.*):([^:]*):([^:]*)$/);
887: return ($symb,$partid,$resid);
888: }
889:
890: sub get_resource_from_symb {
891: my ($symb) = @_;
892: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
893: foreach my $res (@{$seq->{'contents'}}) {
894: if ($res->{'symb'} eq $symb) {
895: return $res;
896: }
897: }
898: }
899: return undef;
900: }
901:
902: sub get_time_from_row {
903: my ($row) = @_;
904: if (ref($row)) {
905: return $row->[3];
906: }
907: return undef;
908: }
909:
910: sub get_tries_from_row {
911: my ($row) = @_;
912: if (ref($row)) {
913: return $row->[4];
914: }
915: return undef;
916: }
917:
918: sub Process_Row {
919: my ($row) = @_;
920: my %RowData;
921: my ($award,$grading,$submission,$time,$tries) = @$row;
922: next if ($award eq 'MISSING_ANSWER');
923: if ($award =~ /(APPROX_ANS|EXACT_ANS)/) {
924: $RowData{'_correct'} = 1;
925: }
926: $RowData{'_total'} = 1;
927: my @Foilgrades = split('&',$grading);
928: my @Foilsubs = split('&',$submission);
929: for (my $j=0;$j<=$#Foilgrades;$j++) {
930: my ($foilid,$correct) = split('=',$Foilgrades[$j]);
931: my (undef,$submission) = split('=',$Foilsubs[$j]);
932: if ($correct) {
933: $RowData{$foilid}->{'_correct'}++;
934: } else {
935: $submission = &Apache::lonnet::unescape($submission);
936: $submission =~ s/\%20/ /g;
937: $RowData{$foilid}->{$submission}++;
938: }
939: $RowData{$foilid}->{'_total'}++;
940: }
941: return %RowData;
942: }
943:
944: ##
945: ## get problem data and put it into a useful data structure.
946: ## note: we must force each foil and option to not begin or end with
947: ## spaces as they are stored without such data.
948: ##
949: sub get_problem_data {
950: my ($url) = @_;
951: my $Answ=&Apache::lonnet::ssi($url,('grade_target' => 'analyze'));
952: (my $garbage,$Answ)=split(/_HASH_REF__/,$Answ,2);
953: my %Answer;
954: %Answer=&Apache::lonnet::str2hash($Answ);
955: my %Partdata;
956: foreach my $part (@{$Answer{'parts'}}) {
957: while (my($key,$value) = each(%Answer)) {
958: next if ($key !~ /^$part/);
959: $key =~ s/^$part\.//;
960: if (ref($value) eq 'ARRAY') {
961: if ($key eq 'options') {
962: $Partdata{$part}->{'Options'}=$value;
963: } elsif ($key eq 'concepts') {
964: $Partdata{$part}->{'Concepts'}=$value;
965: } elsif ($key =~ /^concept\.(.*)$/) {
966: my $concept = $1;
967: foreach my $foil (@$value) {
968: $Partdata{$part}->{'Foils'}->{$foil}->{'Concept'}=
969: $concept;
970: }
971: }
972: } else {
973: if ($key=~ /^foil\.text\.(.*)$/) {
974: my $foil = $1;
975: $Partdata{$part}->{'Foils'}->{$foil}->{'name'}=$foil;
976: $Partdata{$part}->{'Foils'}->{$foil}->{'text'}=$value;
977: } elsif ($key =~ /^foil\.value\.(.*)$/) {
978: my $foil = $1;
979: $Partdata{$part}->{'Foils'}->{$foil}->{'value'}=$value;
980: }
981: }
982: }
983: }
984: return %Partdata;
985: }
986:
987: 1;
988:
989: __END__
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>