Annotation of loncom/interface/statistics/lonproblemanalysis.pm, revision 1.42
1.1 stredwic 1: # The LearningOnline Network with CAPA
2: #
1.42 ! matthew 3: # $Id: lonproblemanalysis.pm,v 1.41 2003/10/16 15:24:49 matthew Exp $
1.1 stredwic 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:
1.11 minaeibi 28: package Apache::lonproblemanalysis;
1.1 stredwic 29:
30: use strict;
31: use Apache::lonnet();
1.25 matthew 32: use Apache::loncommon();
1.7 stredwic 33: use Apache::lonhtmlcommon();
1.23 matthew 34: use Apache::loncoursedata();
35: use Apache::lonstatistics;
36: use Apache::lonlocal;
1.37 matthew 37: use HTML::Entities();
1.42 ! matthew 38: use Time::Local();
1.2 stredwic 39:
1.40 matthew 40: my $plotcolors = ['#33ff00',
41: '#0033cc', '#990000', '#aaaa66', '#663399', '#ff9933',
42: '#66ccff', '#ff9999', '#cccc33', '#660000', '#33cc66',
43: ];
1.39 matthew 44:
1.1 stredwic 45: sub BuildProblemAnalysisPage {
1.23 matthew 46: my ($r,$c)=@_;
1.24 matthew 47: $r->print('<h2>'.&mt('Option Response Problem Analysis').'</h2>');
1.25 matthew 48: $r->print(&CreateInterface());
1.28 matthew 49: #
50: my @Students = @Apache::lonstatistics::Students;
51: #
1.41 matthew 52: if (exists($ENV{'form.ClearCache'}) ||
53: exists($ENV{'form.updatecaches'}) ||
1.33 matthew 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: }
1.39 matthew 63: $r->rflush();
1.33 matthew 64: if (exists($ENV{'form.problemchoice'}) &&
65: ! exists($ENV{'form.SelectAnother'})) {
1.31 matthew 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);
1.33 matthew 72: $r->print('<input type="submit" name="updatecaches" value="'.
73: &mt('Update Student Data').'" />');
74: $r->print(' 'x5);
1.31 matthew 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: #
1.25 matthew 81: $r->print('<hr />');
1.23 matthew 82: #
1.25 matthew 83: my ($symb,$part,$resid) = &get_problem_symb(
1.23 matthew 84: &Apache::lonnet::unescape($ENV{'form.problemchoice'})
85: );
1.28 matthew 86: #
1.23 matthew 87: my $resource = &get_resource_from_symb($symb);
88: if (defined($resource)) {
1.41 matthew 89: $r->print('<h3>'.$resource->{'src'}.'</h3>');
1.25 matthew 90: my %Data = &get_problem_data($resource->{'src'});
91: my $ORdata = $Data{$part.'.'.$resid};
92: ##
1.26 matthew 93: ## Render the problem
1.25 matthew 94: my $base;
95: ($base,undef) = ($resource->{'src'} =~ m|(.*/)[^/]*$|);
96: $base = "http://".$ENV{'SERVER_NAME'}.$base;
1.26 matthew 97: my $rendered_problem =
98: &Apache::lonnet::ssi_body($resource->{'src'});
1.30 matthew 99: $rendered_problem =~ s/<\s*form\s*/<nop /g;
100: $rendered_problem =~ s|(<\s*/form\s*>)|<\/nop>|g;
1.26 matthew 101: $r->print('<table bgcolor="ffffff"><tr><td>'.
1.25 matthew 102: '<base href="'.$base.'" />'.
1.26 matthew 103: $rendered_problem.
104: '</td></tr></table>');
1.25 matthew 105: ##
106: ## Analyze the problem
1.26 matthew 107: my $PerformanceData =
108: &Apache::loncoursedata::get_optionresponse_data
1.28 matthew 109: (\@Students,$symb,$resid);
1.26 matthew 110: if (defined($PerformanceData) &&
111: ref($PerformanceData) eq 'ARRAY') {
1.36 matthew 112: if ($ENV{'form.AnalyzeOver'} eq 'Tries') {
1.33 matthew 113: my $analysis_html = &tries_analysis($PerformanceData,
114: $ORdata);
115: $r->print($analysis_html);
1.36 matthew 116: } elsif ($ENV{'form.AnalyzeOver'} eq 'Time') {
1.33 matthew 117: my $analysis_html = &time_analysis($PerformanceData,
1.28 matthew 118: $ORdata);
1.26 matthew 119: $r->print($analysis_html);
1.28 matthew 120: } else {
121: $r->print('<h2>'.
122: &mt('The analysis you have selected is '.
123: 'not supported at this time').
124: '</h2>');
125: }
1.26 matthew 126: } else {
127: $r->print('<h2>'.
128: &mt('There is no student data for this problem.').
129: '</h2>');
130: }
1.23 matthew 131: } else {
132: $r->print('resource is undefined');
1.7 stredwic 133: }
1.23 matthew 134: $r->print('<hr />');
1.25 matthew 135: } else {
1.31 matthew 136: $r->print('<input type="submit" name="ProblemAnalysis" value="'.
137: &mt('Analyze Problem').'" />');
138: $r->print(' 'x5);
1.27 matthew 139: $r->print('<h3>'.&mt('Please select a problem to analyze').'</h3>');
1.31 matthew 140: $r->print(&OptionResponseProblemSelector());
1.1 stredwic 141: }
142: }
143:
1.33 matthew 144: #########################################################
145: #########################################################
146: ##
147: ## Misc interface routines use by analysis code
148: ##
149: #########################################################
150: #########################################################
151: sub build_foil_index {
152: my ($ORdata) = @_;
1.41 matthew 153: return if (! exists($ORdata->{'Foils'}));
1.36 matthew 154: my %Foildata = %{$ORdata->{'Foils'}};
155: my @Foils = sort(keys(%Foildata));
156: my %Concepts;
1.25 matthew 157: foreach my $foilid (@Foils) {
1.36 matthew 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;
1.39 matthew 178: if (exists($Numbers{lc($a)})) {
179: $a1 = $Numbers{lc($a)};
1.36 matthew 180: }
1.39 matthew 181: if (exists($Numbers{lc($b)})) {
182: $b1 = $Numbers{lc($b)};
1.36 matthew 183: }
184: $a1 cmp $b1;
185: };
186: my @Concepts;
187: foreach my $concept (sort $sortfunction (keys(%Concepts))) {
1.39 matthew 188: push(@Concepts,{ name => $concept,
1.36 matthew 189: foils => [@{$Concepts{$concept}}]});
190: push(@Foils,(@{$Concepts{$concept}}));
1.25 matthew 191: }
1.31 matthew 192: #
193: # Build up the table of row labels.
194: my $table = '<table border="1" >'."\n";
1.39 matthew 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: }
1.36 matthew 212: my $conceptindex = 1;
213: my $foilindex = 1;
214: foreach my $concept (@Concepts) {
215: my @FoilsInConcept = @{$concept->{'foils'}};
216: my $firstfoil = shift(@FoilsInConcept);
1.39 matthew 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 {
1.36 matthew 227: $table .= '<tr>'.
1.39 matthew 228: '<td>'.$foilindex++.'</td>'.
229: '<td>'.$Foildata{$firstfoil}->{'name'}.'</td>'.
230: '<td>'.$Foildata{$firstfoil}->{'text'}.'</td>'.
231: '<td>'.$Foildata{$firstfoil}->{'value'}.'</td>'.
1.36 matthew 232: "</tr>\n";
1.39 matthew 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: }
1.36 matthew 252: } continue {
253: $foilindex++;
254: }
1.31 matthew 255: } continue {
1.36 matthew 256: $conceptindex++;
1.25 matthew 257: }
1.31 matthew 258: $table .= "</table>\n";
1.39 matthew 259: #
260: # Build option index with color stuff
1.36 matthew 261: return ($table,\@Foils,\@Concepts);
1.33 matthew 262: }
263:
1.39 matthew 264: sub build_option_index {
265: my ($ORdata)= @_;
266: my $table = "<table>\n";
267: my $optionindex = 0;
268: my @Rows;
1.41 matthew 269: foreach my $option (&mt('correct option chosen'),@{$ORdata->{'Options'}}) {
1.39 matthew 270: push (@Rows,
271: '<tr>'.
272: '<td bgcolor="'.$plotcolors->[$optionindex++].'">'.
273: (' 'x4).'</td>'.
274: '<td>'.$option.'</td>'.
275: "</tr>\n");
276: }
1.42 ! matthew 277: shift(@Rows); # Throw away 'correct option chosen' color
1.39 matthew 278: $table .= join('',reverse(@Rows));
279: $table .= "</table>\n";
280: }
281:
1.33 matthew 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'};
1.39 matthew 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: }
1.33 matthew 301: my %ResponseData = &analyze_option_data_by_tries($PerformanceData,
1.36 matthew 302: $mintries,$maxtries);
1.42 ! matthew 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;
1.31 matthew 318: #
319: # Compute the data neccessary to make the plots
1.42 ! matthew 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);
1.39 matthew 339: } else {
340: push (@{$PlotData[$i]->{$option}},
341: 100 * $ResponseData{$foilid}->[$i]->{$option} /
1.42 ! matthew 342: ($ResponseData{$foilid}->[$i]->{'_total'} -
! 343: $ResponseData{$foilid}->[$i]->{'_correct'}));
1.39 matthew 344: }
1.36 matthew 345: }
346: }
347: }
1.42 ! matthew 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'});
1.36 matthew 426: }
1.25 matthew 427: }
1.42 ! matthew 428: }
1.31 matthew 429: # Build a table for the plots
1.25 matthew 430: for (my $i=$mintries;$i<=$maxtries;$i++) {
1.39 matthew 431: my $minstu = $PlotData[$i]->{'_total'}->[0];
432: my $maxstu = $PlotData[$i]->{'_total'}->[0];
433: foreach my $count (@{$PlotData[$i]->{'_total'}}) {
1.36 matthew 434: if ($minstu > $count) {
435: $minstu = $count;
1.27 matthew 436: }
1.36 matthew 437: if ($maxstu < $count) {
438: $maxstu = $count;
1.27 matthew 439: }
440: }
1.39 matthew 441: $maxstu = 0 if (! defined($maxstu));
442: $minstu = 0 if (! defined($minstu));
1.35 matthew 443: my $title;
1.42 ! matthew 444: my $count = $ResponseData{'_total'}->[$i];
! 445: if ($count == 0) {
! 446: $count = 'no submissions';
! 447: } elsif ($count == 1) {
! 448: $count = '1 submission';
1.27 matthew 449: } else {
1.42 ! matthew 450: $count = $count.' submissions';
1.39 matthew 451: }
1.42 ! matthew 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";
1.25 matthew 457: }
1.42 ! matthew 458: $analysis_html .= "</table>\n";
! 459: return $analysis_html;
1.25 matthew 460: }
461:
462: sub analyze_option_data_by_tries {
1.26 matthew 463: my ($PerformanceData,$mintries,$maxtries) = @_;
1.25 matthew 464: my %Trydata;
465: $mintries = 1 if (! defined($mintries) || $mintries < 1);
466: $maxtries = $mintries if (! defined($maxtries) || $maxtries < $mintries);
1.26 matthew 467: foreach my $row (@$PerformanceData) {
468: next if (! defined($row));
1.42 ! matthew 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;
1.25 matthew 478: }
479: }
480: }
481: return %Trydata;
482: }
483:
1.33 matthew 484: #########################################################
485: #########################################################
486: ##
487: ## Time Analysis
488: ##
489: #########################################################
490: #########################################################
491: sub time_analysis {
492: my ($PerformanceData,$ORdata) = @_;
1.42 ! matthew 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: }
1.33 matthew 501: my $num_plots = $ENV{'form.NumPlots'};
502: my $num_data = scalar(@$PerformanceData)-1;
503: my $percent = sprintf('%2f',100/$num_plots);
1.42 ! matthew 504: #
1.37 matthew 505: $table .= "<table>\n";
1.33 matthew 506: for (my $i=0;$i<$num_plots;$i++) {
1.42 ! matthew 507: ##
1.34 matthew 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)) {
1.42 ! matthew 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: }
1.34 matthew 524: my $startdateform = &Apache::lonhtmlcommon::date_setter
525: ('Statistics','startdate_'.$i,$starttime);
526: my $enddateform = &Apache::lonhtmlcommon::date_setter
527: ('Statistics','enddate_'.$i,$endtime);
1.42 ! matthew 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: }
1.33 matthew 554: }
1.42 ! matthew 555: #
1.33 matthew 556: return $table;
557: }
558:
1.42 ! matthew 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'});
1.33 matthew 565: my %TimeData;
566: #
567: # Compute the number getting the foils correct or incorrects
1.42 ! matthew 568: for (my $j=$begin_index;$j<=$end_index;$j++) {
! 569: my $row = $PerformanceData->[$j];
1.33 matthew 570: next if (! defined($row));
1.42 ! matthew 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;
1.33 matthew 579: }
580: }
581: }
1.39 matthew 582: my @Plotdata;
1.42 ! matthew 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);
1.37 matthew 599: }
1.42 ! matthew 600: } continue {
! 601: $optionidx++;
1.39 matthew 602: }
1.42 ! matthew 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';
1.39 matthew 612: } else {
1.42 ! matthew 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;
1.37 matthew 672: }
1.42 ! matthew 673: while (my ($option,$value) = each(%$href)) {
! 674: $TimeData{$foilid}->{$option}+=$value;
1.37 matthew 675: }
1.33 matthew 676: }
677: }
678: #
1.42 ! matthew 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: #
1.33 matthew 694: # Create the plot
1.42 ! matthew 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)=@_;
1.1 stredwic 729: }
730:
1.33 matthew 731: #########################################################
732: #########################################################
733: ##
734: ## Interface
735: ##
736: #########################################################
737: #########################################################
1.23 matthew 738: sub CreateInterface {
1.28 matthew 739: ##
740: ## Environment variable initialization
1.36 matthew 741: if (! exists$ENV{'form.AnalyzeOver'}) {
742: $ENV{'form.AnalyzeOver'} = 'Tries';
1.28 matthew 743: }
744: ##
745: ## Build the menu
1.7 stredwic 746: my $Str = '';
1.23 matthew 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>';
1.31 matthew 751: # $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b></td>';
752: $Str .= '<td align="center"> </td>';
1.23 matthew 753: $Str .= '</tr>'."\n";
1.31 matthew 754: ##
755: ##
1.23 matthew 756: $Str .= '<tr><td align="center">'."\n";
757: $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
1.28 matthew 758: $Str .= '</td>';
759: #
760: $Str .= '<td align="center">';
1.23 matthew 761: $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
1.28 matthew 762: $Str .= '</td>';
763: #
1.31 matthew 764: # $Str .= '<td align="center">';
1.23 matthew 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: };
1.31 matthew 773: &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
1.23 matthew 774: $only_seq_with_assessments);
1.36 matthew 775: ##
776: ##
1.28 matthew 777: $Str .= '<td>';
1.36 matthew 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: }
1.28 matthew 824: }
825: $Str .= '</td>';
1.36 matthew 826: ##
827: ##
1.28 matthew 828: $Str .= '</tr>'."\n";
1.23 matthew 829: $Str .= '</table>'."\n";
1.42 ! matthew 830: return $Str;
1.23 matthew 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'}}) {
1.26 matthew 840: next if ($res->{'type'} ne 'assessment');
1.23 matthew 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') {
1.25 matthew 851: my $value = &Apache::lonnet::escape($res->{'symb'}.':'.$part.':'.$respid);
1.23 matthew 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";
1.11 minaeibi 864: }
865: }
866: }
867: }
1.23 matthew 868: if ($seq_str ne '') {
869: $Str .= '<tr><td> </td><td><b>'.$seq->{'title'}.'</b></td>'.
870: "</tr>\n".$seq_str;
871: }
1.11 minaeibi 872: }
1.23 matthew 873: $Str .= "</table>\n";
874: return $Str;
1.33 matthew 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);
1.11 minaeibi 888: }
889:
1.23 matthew 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;
1.2 stredwic 896: }
1.1 stredwic 897: }
898: }
1.23 matthew 899: return undef;
1.42 ! matthew 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;
1.1 stredwic 942: }
943:
1.39 matthew 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: ##
1.25 matthew 949: sub get_problem_data {
950: my ($url) = @_;
951: my $Answ=&Apache::lonnet::ssi($url,('grade_target' => 'analyze'));
1.23 matthew 952: (my $garbage,$Answ)=split(/_HASH_REF__/,$Answ,2);
1.25 matthew 953: my %Answer;
1.23 matthew 954: %Answer=&Apache::lonnet::str2hash($Answ);
1.25 matthew 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) {
1.36 matthew 968: $Partdata{$part}->{'Foils'}->{$foil}->{'Concept'}=
969: $concept;
1.25 matthew 970: }
971: }
972: } else {
973: if ($key=~ /^foil\.text\.(.*)$/) {
974: my $foil = $1;
1.36 matthew 975: $Partdata{$part}->{'Foils'}->{$foil}->{'name'}=$foil;
976: $Partdata{$part}->{'Foils'}->{$foil}->{'text'}=$value;
1.25 matthew 977: } elsif ($key =~ /^foil\.value\.(.*)$/) {
978: my $foil = $1;
1.36 matthew 979: $Partdata{$part}->{'Foils'}->{$foil}->{'value'}=$value;
1.25 matthew 980: }
981: }
982: }
1.23 matthew 983: }
1.25 matthew 984: return %Partdata;
1.1 stredwic 985: }
986:
1.23 matthew 987: 1;
1.1 stredwic 988:
989: __END__
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>