Annotation of loncom/interface/statistics/lonproblemstatistics.pm, revision 1.84
1.1 stredwic 1: # The LearningOnline Network with CAPA
2: #
1.84 ! matthew 3: # $Id: lonproblemstatistics.pm,v 1.83 2004/04/06 15:45:13 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: # (Navigate problems for statistical reports
28: #
1.47 matthew 29: ###############################################
30: ###############################################
31:
32: =pod
33:
34: =head1 NAME
35:
36: lonproblemstatistics
37:
38: =head1 SYNOPSIS
39:
40: Routines to present problem statistics to instructors via tables,
41: Excel files, and plots.
42:
43: =over 4
44:
45: =cut
46:
47: ###############################################
48: ###############################################
1.1 stredwic 49:
1.36 minaeibi 50: package Apache::lonproblemstatistics;
1.1 stredwic 51:
52: use strict;
53: use Apache::lonnet();
1.62 matthew 54: use Apache::loncommon();
1.1 stredwic 55: use Apache::lonhtmlcommon;
56: use Apache::loncoursedata;
1.41 matthew 57: use Apache::lonstatistics;
1.84 ! matthew 58: use LONCAPA::lonmetadata();
1.59 matthew 59: use Apache::lonlocal;
1.44 matthew 60: use Spreadsheet::WriteExcel;
1.70 matthew 61: use Apache::lonstathelpers();
1.71 matthew 62: use Time::HiRes;
1.73 matthew 63:
64: my @StatsArray;
1.79 matthew 65: my %SeqStat; # keys are symbs, values are hash refs
1.73 matthew 66:
1.59 matthew 67: ##
68: ## Localization notes:
69: ##
70: ## in @Fields[0]->{'long_title'} is placed in Excel files and is used as the
71: ## header for plots created with Graph.pm, both of which more than likely do
72: ## not support localization.
73: ##
1.79 matthew 74: #
75: #
76: ##
77: ## Description of Field attributes
78: ##
79: ## Attribute Required Value Meaning or Use
80: ##
81: ## name yes any scalar Used to uniquely identify field
82: ## title yes any scalar This is what the user sees to identify
83: ## the field. Passed through &mt().
84: ## long_title yes any scalar Used as graph heading and in excel
85: ## output. NOT translated
86: ## align no (left|right|center) HTML cell contents alignment
87: ## color yes html color HTML cell background color
88: ## used to visually group statistics
89: ## special no (link) Indicates a link, target is name.link
90: ## Currently set in &get_statistics()
91: ## graphable no (yes|no) Can a bar graph of the field be
92: ## produced?
93: ## sortable no (yes|no) Should a sort link be put in the
94: ## column header?
95: ## selectable yes (yes|no) Can the column be removed from the
96: ## statistics display?
97: ## selected yes (yes|no) Is the column selected by default?
98: ##
1.49 matthew 99: my @Fields = (
100: { name => 'problem_num',
101: title => 'P#',
102: align => 'right',
1.76 matthew 103: color => '#FFFFE6',
104: selectable => 'no',
1.80 matthew 105: defaultselected => 'yes',
1.76 matthew 106: },
1.49 matthew 107: { name => 'container',
1.51 matthew 108: title => 'Sequence or Folder',
1.49 matthew 109: align => 'left',
110: color => '#FFFFE6',
1.76 matthew 111: sortable => 'yes',
112: selectable => 'no',
1.80 matthew 113: defaultselected => 'yes',
1.76 matthew 114: },
1.49 matthew 115: { name => 'title',
116: title => 'Title',
117: align => 'left',
118: color => '#FFFFE6',
119: special => 'link',
1.76 matthew 120: sortable => 'yes',
121: selectable => 'no',
1.80 matthew 122: defaultselected => 'yes',
1.76 matthew 123: },
1.49 matthew 124: { name => 'part',
125: title => 'Part',
126: align => 'left',
1.55 matthew 127: color => '#FFFFE6',
1.76 matthew 128: selectable => 'no',
1.80 matthew 129: defaultselected => 'yes',
1.76 matthew 130: },
1.49 matthew 131: { name => 'num_students',
132: title => '#Stdnts',
133: align => 'right',
134: color => '#EEFFCC',
135: format => '%d',
136: sortable => 'yes',
137: graphable => 'yes',
1.76 matthew 138: long_title => 'Number of Students Attempting Problem',
139: selectable => 'yes',
1.80 matthew 140: defaultselected => 'yes',
1.76 matthew 141: },
1.49 matthew 142: { name => 'tries',
143: title => 'Tries',
144: align => 'right',
145: color => '#EEFFCC',
146: format => '%d',
147: sortable => 'yes',
148: graphable => 'yes',
1.76 matthew 149: long_title => 'Total Number of Tries',
150: selectable => 'yes',
1.80 matthew 151: defaultselected => 'yes',
1.76 matthew 152: },
1.49 matthew 153: { name => 'max_tries',
154: title => 'Max Tries',
155: align => 'right',
156: color => '#DDFFFF',
157: format => '%d',
158: sortable => 'yes',
159: graphable => 'yes',
1.76 matthew 160: long_title => 'Maximum Number of Tries',
161: selectable => 'yes',
1.80 matthew 162: defaultselected => 'yes',
1.76 matthew 163: },
1.73 matthew 164: { name => 'min_tries',
165: title => 'Min Tries',
166: align => 'right',
167: color => '#DDFFFF',
168: format => '%d',
169: sortable => 'yes',
170: graphable => 'yes',
1.76 matthew 171: long_title => 'Minumum Number of Tries',
172: selectable => 'yes',
1.80 matthew 173: defaultselected => 'yes',
1.76 matthew 174: },
1.49 matthew 175: { name => 'mean_tries',
176: title => 'Mean Tries',
177: align => 'right',
178: color => '#DDFFFF',
179: format => '%5.2f',
180: sortable => 'yes',
181: graphable => 'yes',
1.76 matthew 182: long_title => 'Average Number of Tries',
183: selectable => 'yes',
1.80 matthew 184: defaultselected => 'yes',
1.76 matthew 185: },
1.49 matthew 186: { name => 'std_tries',
187: title => 'S.D. tries',
188: align => 'right',
189: color => '#DDFFFF',
190: format => '%5.2f',
191: sortable => 'yes',
192: graphable => 'yes',
1.76 matthew 193: long_title => 'Standard Deviation of Number of Tries',
194: selectable => 'yes',
1.80 matthew 195: defaultselected => 'yes',
1.76 matthew 196: },
1.49 matthew 197: { name => 'skew_tries',
198: title => 'Skew Tries',
199: align => 'right',
200: color => '#DDFFFF',
201: format => '%5.2f',
202: sortable => 'yes',
203: graphable => 'yes',
1.76 matthew 204: long_title => 'Skew of Number of Tries',
205: selectable => 'yes',
1.80 matthew 206: defaultselected => 'no',
1.76 matthew 207: },
1.49 matthew 208: { name => 'num_solved',
209: title => '#YES',
210: align => 'right',
211: color => '#FFDDDD',
1.63 matthew 212: format => '%4.1f',# format => '%d',
1.49 matthew 213: sortable => 'yes',
214: graphable => 'yes',
1.76 matthew 215: long_title => 'Number of Students able to Solve',
1.77 matthew 216: selectable => 'yes',
1.80 matthew 217: defaultselected => 'yes',
1.76 matthew 218: },
1.49 matthew 219: { name => 'num_override',
220: title => '#yes',
221: align => 'right',
222: color => '#FFDDDD',
1.63 matthew 223: format => '%4.1f',# format => '%d',
1.49 matthew 224: sortable => 'yes',
225: graphable => 'yes',
1.76 matthew 226: long_title => 'Number of Students given Override',
227: selectable => 'yes',
1.80 matthew 228: defaultselected => 'yes',
1.76 matthew 229: },
1.73 matthew 230: { name => 'num_wrong',
231: title => '#Wrng',
1.49 matthew 232: align => 'right',
1.73 matthew 233: color => '#FFDDDD',
1.49 matthew 234: format => '%4.1f',
235: sortable => 'yes',
236: graphable => 'yes',
1.76 matthew 237: long_title => 'Percent of students whose final answer is wrong',
238: selectable => 'yes',
1.80 matthew 239: defaultselected => 'yes',
1.76 matthew 240: },
1.73 matthew 241: { name => 'deg_of_diff',
242: title => 'DoDiff',
243: align => 'right',
244: color => '#FFFFE6',
245: format => '%5.2f',
246: sortable => 'yes',
247: graphable => 'yes',
248: long_title => 'Degree of Difficulty'.
1.76 matthew 249: '[ 1 - ((#YES+#yes) / Tries) ]',
250: selectable => 'yes',
1.80 matthew 251: defaultselected => 'yes',
1.76 matthew 252: },
1.71 matthew 253: { name => 'deg_of_disc',
1.73 matthew 254: title => 'DoDisc',
1.71 matthew 255: align => 'right',
256: color => '#FFFFE6',
257: format => '%4.2f',
258: sortable => 'yes',
259: graphable => 'yes',
1.76 matthew 260: long_title => 'Degree of Discrimination',
261: selectable => 'yes',
1.80 matthew 262: defaultselected => 'no',
1.76 matthew 263: },
1.49 matthew 264: );
265:
1.79 matthew 266: my @SeqFields = (
267: { name => 'title',
268: title => 'Sequence',
269: align => 'left',
270: color => '#FFFFE6',
271: special => 'no',
272: sortable => 'no',
273: selectable => 'yes',
1.80 matthew 274: defaultselected => 'no',
1.79 matthew 275: },
276: { name => 'items',
277: title => '#Items',
278: align => 'right',
279: color => '#FFFFE6',
280: format => '%4d',
281: sortable => 'no',
282: graphable => 'no',
283: long_title => 'Number of Items in Sequence',
284: selectable => 'yes',
1.80 matthew 285: defaultselected => 'no',
1.79 matthew 286: },
287: { name => 'scoremean',
288: title => 'Score Mean',
289: align => 'right',
290: color => '#FFFFE6',
291: format => '%4.2f',
292: sortable => 'no',
293: graphable => 'no',
294: long_title => 'Mean Sequence Score',
295: selectable => 'yes',
1.80 matthew 296: defaultselected => 'no',
1.79 matthew 297: },
298: { name => 'scorestd',
299: title => 'Score STD',
300: align => 'right',
301: color => '#FFFFE6',
302: format => '%4.2f',
303: sortable => 'no',
304: graphable => 'no',
305: long_title => 'Standard Deviation of Sequence Scores',
306: selectable => 'yes',
1.80 matthew 307: defaultselected => 'no',
1.79 matthew 308: },
309: { name => 'scoremax',
310: title => 'Score Max',
311: align => 'right',
312: color => '#FFFFE6',
313: format => '%4.2f',
314: sortable => 'no',
315: graphable => 'no',
316: long_title => 'Maximum Sequence Score',
317: selectable => 'yes',
1.80 matthew 318: defaultselected => 'no',
1.79 matthew 319: },
320: { name => 'scoremin',
321: title => 'Score Min',
322: align => 'right',
323: color => '#FFFFE6',
324: format => '%4.2f',
325: sortable => 'no',
326: graphable => 'no',
327: long_title => 'Minumum Sequence Score',
328: selectable => 'yes',
1.80 matthew 329: defaultselected => 'no',
1.79 matthew 330: },
331: { name => 'scorecount',
332: title => 'Score N',
333: align => 'right',
334: color => '#FFFFE6',
335: format => '%4d',
336: sortable => 'no',
337: graphable => 'no',
338: long_title => 'Number of Students in score computations',
339: selectable => 'yes',
1.80 matthew 340: defaultselected => 'no',
1.79 matthew 341: },
342: { name => 'countmean',
343: title => 'Count Mean',
344: align => 'right',
345: color => '#FFFFFF',
346: format => '%4.2f',
347: sortable => 'no',
348: graphable => 'no',
349: long_title => 'Mean Sequence Score',
350: selectable => 'yes',
1.80 matthew 351: defaultselected => 'no',
1.79 matthew 352: },
353: { name => 'countstd',
354: title => 'Count STD',
355: align => 'right',
356: color => '#FFFFFF',
357: format => '%4.2f',
358: sortable => 'no',
359: graphable => 'no',
360: long_title => 'Standard Deviation of Sequence Scores',
361: selectable => 'yes',
1.80 matthew 362: defaultselected => 'no',
1.79 matthew 363: },
364: { name => 'countmax',
365: title => 'Count Max',
366: align => 'right',
367: color => '#FFFFFF',
368: format => '%4.2f',
369: sortable => 'no',
370: graphable => 'no',
371: long_title => 'Maximum Number of Correct Problems',
372: selectable => 'yes',
1.80 matthew 373: defaultselected => 'no',
1.79 matthew 374: },
375: { name => 'countmin',
376: title => 'Count Min',
377: align => 'right',
378: color => '#FFFFFF',
379: format => '%4.2f',
380: sortable => 'no',
381: graphable => 'no',
382: long_title => 'Minumum Number of Correct Problems',
383: selectable => 'yes',
1.80 matthew 384: defaultselected => 'no',
1.79 matthew 385: },
386: { name => 'count',
387: title => 'Count N',
388: align => 'right',
389: color => '#FFFFFF',
390: format => '%4d',
391: sortable => 'no',
392: graphable => 'no',
393: long_title => 'Number of Students in score computations',
394: selectable => 'yes',
1.80 matthew 395: defaultselected => 'no',
1.79 matthew 396: },
397: { name => 'KR-21',
398: title => 'KR-21',
399: align => 'right',
400: color => '#FFAAAA',
401: format => '%4.2f',
402: sortable => 'no',
403: graphable => 'no',
404: long_title => 'KR-21 reliability statistic',
405: selectable => 'yes',
1.80 matthew 406: defaultselected => 'no',
1.79 matthew 407: },
408: );
409:
1.76 matthew 410: my %SelectedFields;
411:
412: sub parse_field_selection {
413: #
414: # Pull out the defaults
415: if (! defined($ENV{'form.fieldselections'})) {
416: $ENV{'form.fieldselections'} = [];
417: foreach my $field (@Fields) {
418: next if ($field->{'selectable'} ne 'yes');
1.80 matthew 419: if ($field->{'defaultselected'} eq 'yes') {
1.76 matthew 420: push(@{$ENV{'form.fieldselections'}},$field->{'name'});
421: }
422: }
423: }
424: #
1.80 matthew 425: # Make sure the data we are plotting is there
426: my %NeededFields;
427: if (exists($ENV{'form.plot'}) && $ENV{'form.plot'} ne '' &&
428: $ENV{'form.plot'} ne 'none') {
429: if ($ENV{'form.plot'} eq 'degrees') {
430: $NeededFields{'deg_of_diff'}++;
431: $NeededFields{'deg_of_disc'}++;
432: } elsif ($ENV{'form.plot'} eq 'tries statistics') {
433: $NeededFields{'mean_tries'}++;
434: $NeededFields{'std_tries'}++;
435: $NeededFields{'problem_num'}++;
436: } else {
437: $NeededFields{$ENV{'form.plot'}}++;
438: }
439: }
440: #
1.76 matthew 441: # This should not happen, but in case it does...
442: if (ref($ENV{'form.fieldselections'}) ne 'ARRAY') {
443: $ENV{'form.fieldselections'} = [$ENV{'form.fieldselections'}];
444: }
445: #
446: # Set the field data and the selected fields (for easier checking)
447: undef(%SelectedFields);
448: foreach my $field (@Fields) {
1.80 matthew 449: if ($field->{'selectable'} ne 'yes') {
450: $field->{'selected'} = 'yes';
451: } else {
452: $field->{'selected'} = 'no';
453: }
454: if (exists($NeededFields{$field->{'name'}})) {
455: $field->{'selected'} = 'yes';
456: $SelectedFields{$field->{'name'}}++;
457: }
1.76 matthew 458: foreach my $selection (@{$ENV{'form.fieldselections'}}) {
459: if ($selection eq $field->{'name'} || $selection eq 'all') {
460: $field->{'selected'} = 'yes';
461: $SelectedFields{$field->{'name'}}++;
462: }
463: }
464: }
1.82 matthew 465: #
466: # Always show all the sequence statistics (for now)
467: foreach my $field (@SeqFields) {
468: $field->{'selected'} = 'yes';
469: }
1.76 matthew 470: return;
471: }
472:
473: sub field_selection_input {
474: my $Str = '<select name="fieldselections" multiple size="5">'."\n";
475: $Str .= '<option value="all">all</option>'."\n";
476: foreach my $field (@Fields) {
477: next if ($field->{'selectable'} ne 'yes');
478: $Str .= ' <option value="'.$field->{'name'}.'" ';
479: if ($field->{'selected'} eq 'yes') {
480: $Str .= 'selected ';
481: }
482: $Str .= '>'.$field->{'title'}.'</option>'."\n";
483: }
484: $Str .= "</select>\n";
485: }
486:
1.47 matthew 487: ###############################################
488: ###############################################
489:
490: =pod
491:
492: =item &CreateInterface()
493:
494: Create the main intereface for the statistics page. Allows the user to
495: select sections, maps, and output.
496:
497: =cut
1.1 stredwic 498:
1.47 matthew 499: ###############################################
500: ###############################################
1.41 matthew 501: sub CreateInterface {
1.80 matthew 502: #
1.76 matthew 503: &parse_field_selection();
1.80 matthew 504: #
1.41 matthew 505: my $Str = '';
1.67 matthew 506: $Str .= &Apache::lonhtmlcommon::breadcrumbs
1.69 matthew 507: (undef,'Overall Problem Statistics','Statistics_Overall_Key');
1.41 matthew 508: $Str .= '<table cellspacing="5">'."\n";
509: $Str .= '<tr>';
1.59 matthew 510: $Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>';
511: $Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>';
512: $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b></td>';
1.76 matthew 513: $Str .= '<td align="center"><b>'.&mt('Statistics').'</b></td>';
1.70 matthew 514: $Str .= '<td rowspan="2">'.
515: &Apache::lonstathelpers::limit_by_time_form().'</td>';
1.41 matthew 516: $Str .= '</tr>'."\n";
517: #
518: $Str .= '<tr><td align="center">'."\n";
519: $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
1.50 matthew 520: $Str .= '</td><td align="center">';
521: $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
1.41 matthew 522: $Str .= '</td><td align="center">';
523: #
524: my $only_seq_with_assessments = sub {
525: my $s=shift;
526: if ($s->{'num_assess'} < 1) {
527: return 0;
528: } else {
529: return 1;
530: }
531: };
532: $Str .= &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
533: $only_seq_with_assessments);
1.76 matthew 534: $Str .= '</td><td>'.&field_selection_input();
1.41 matthew 535: $Str .= '</td></tr>'."\n";
536: $Str .= '</table>'."\n";
1.59 matthew 537: $Str .= '<input type="submit" name="GenerateStatistics" value="'.
538: &mt('Generate Statistics').'" />';
1.54 matthew 539: $Str .= ' 'x5;
1.73 matthew 540: $Str .= 'Plot '.&plot_dropdown().(' 'x10);
1.59 matthew 541: $Str .= '<input type="submit" name="ClearCache" value="'.
542: &mt('Clear Caches').'" />';
1.54 matthew 543: $Str .= ' 'x5;
1.73 matthew 544: $Str .= '<input type="submit" name="UpdateCache" value="'.
545: &mt('Update Student Data').'" />';
546: $Str .= ' 'x5;
547: $Str .= '<input type="submit" name="Excel" value="'.
548: &mt('Produce Excel Output').'" />';
549: $Str .= ' 'x5;
550: return $Str;
1.41 matthew 551: }
1.25 stredwic 552:
1.41 matthew 553: ###############################################
554: ###############################################
1.28 stredwic 555:
1.47 matthew 556: =pod
557:
558: =item &BuildProblemStatisticsPage()
559:
560: Main interface to problem statistics.
561:
562: =cut
563:
1.41 matthew 564: ###############################################
565: ###############################################
566: sub BuildProblemStatisticsPage {
567: my ($r,$c)=@_;
1.61 matthew 568: #
569: my %Saveable_Parameters = ('Status' => 'scalar',
570: 'statsoutputmode' => 'scalar',
571: 'Section' => 'array',
572: 'StudentData' => 'array',
1.77 matthew 573: 'Maps' => 'array',
574: 'fieldselections'=> 'array');
1.61 matthew 575: &Apache::loncommon::store_course_settings('statistics',
576: \%Saveable_Parameters);
577: &Apache::loncommon::restore_course_settings('statistics',
578: \%Saveable_Parameters);
579: #
580: &Apache::lonstatistics::PrepareClasslist();
1.41 matthew 581: #
1.73 matthew 582: # Clear the package variables
583: undef(@StatsArray);
1.79 matthew 584: undef(%SeqStat);
1.71 matthew 585: #
1.73 matthew 586: # Finally let the user know we are here
587: my $interface = &CreateInterface();
1.57 matthew 588: $r->print($interface);
1.41 matthew 589: $r->print('<input type="hidden" name="sortby" value="'.$ENV{'form.sortby'}.
590: '" />');
1.73 matthew 591: #
1.41 matthew 592: if (! exists($ENV{'form.statsfirstcall'})) {
1.73 matthew 593: $r->print('<input type="hidden" name="statsfirstcall" value="yes" />');
594: $r->print('<h3>'.
595: &mt('Press "Generate Statistics" when you are ready.').
596: '</h3><p>'.
597: &mt('It may take some time to update the student data '.
598: 'for the first analysis. Future analysis this session '.
599: ' will not have this delay.').
600: '</p>');
1.41 matthew 601: return;
1.73 matthew 602: } elsif ($ENV{'form.statsfirstcall'} eq 'yes' ||
603: exists($ENV{'form.UpdateCache'}) ||
604: exists($ENV{'form.ClearCache'}) ) {
605: $r->print('<input type="hidden" name="statsfirstcall" value="no" />');
606: &Apache::lonstatistics::Gather_Student_Data($r);
607: } else {
608: $r->print('<input type="hidden" name="statsfirstcall" value="no" />');
1.28 stredwic 609: }
1.73 matthew 610: $r->rflush();
1.41 matthew 611: #
1.73 matthew 612: # This probably does not need to be done each time we are called, but
613: # it does not slow things down noticably.
614: &Apache::loncoursedata::populate_weight_table();
1.75 matthew 615: #
1.73 matthew 616: if (exists($ENV{'form.Excel'})) {
617: &Excel_output($r);
618: } else {
1.75 matthew 619: my $count = 0;
620: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
1.78 matthew 621: $count += $seq->{'num_assess_parts'};
1.75 matthew 622: }
623: if ($count > 10) {
624: $r->print('<h2>'.
625: &mt('Compiling statistics for [_1] problems',$count).
626: '</h2>');
627: if ($count > 30) {
628: $r->print('<h3>'.&mt('This will take some time.').'</h3>');
629: }
630: $r->rflush();
631: }
632: #
1.73 matthew 633: my $sortby = $ENV{'form.sortby'};
634: $sortby = 'container' if (! defined($sortby) || $sortby =~ /^\s*$/);
635: my $plot = $ENV{'form.plot'};
1.75 matthew 636: if ($plot eq '' || $plot eq 'none') {
637: undef($plot);
638: }
1.73 matthew 639: if ($sortby eq 'container' && ! defined($plot)) {
1.79 matthew 640: &output_sequence_statistics($r);
1.73 matthew 641: &output_html_by_sequence($r);
642: } else {
643: if (defined($plot)) {
644: &make_plot($r,$plot);
645: }
646: &output_html_stats($r);
1.79 matthew 647: &output_sequence_statistics($r);
1.73 matthew 648: }
649: }
650: return;
651: }
652:
1.79 matthew 653: sub output_sequence_statistics {
654: my ($r) = @_;
655: my $c=$r->connection();
656: $r->print('<h2>'.&mt('Sequence Statistics').'</h2>');
657: $r->print('<table border="0"><tr><td bgcolor="#777777">'."\n".
658: '<table border="0" cellpadding="3">'."\n".
659: '<tr bgcolor="#FFFFE6">');
660: $r->print(&sequence_html_header());
661: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
662: last if ($c->aborted);
663: next if ($seq->{'num_assess'} < 1);
664: &compute_sequence_statistics($seq);
665: $r->print(&sequence_html_output($seq));
666: }
667: $r->print('</table>');
668: $r->print('</table>');
669: $r->rflush();
670: return;
671: }
672:
673:
1.73 matthew 674: ##########################################################
675: ##########################################################
676: ##
677: ## HTML output routines
678: ##
679: ##########################################################
680: ##########################################################
681: sub output_html_by_sequence {
682: my ($r) = @_;
683: my $c = $r->connection();
684: $r->print(&html_preamble());
1.41 matthew 685: #
1.73 matthew 686: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
687: last if ($c->aborted);
688: next if ($seq->{'num_assess'} < 1);
689: $r->print("<h3>".$seq->{'title'}."</h3>".
690: '<table border="0"><tr><td bgcolor="#777777">'."\n".
691: '<table border="0" cellpadding="3">'."\n".
692: '<tr bgcolor="#FFFFE6">'.
693: &statistics_table_header('no container')."</tr>\n");
694: my @Data = &compute_statistics_on_sequence($seq);
695: foreach my $data (@Data) {
696: $r->print('<tr>'.&statistics_html_table_data($data,
697: 'no container').
698: "</tr>\n");
1.70 matthew 699: }
1.73 matthew 700: $r->print('</table>'."\n".'</table>'."\n");
1.41 matthew 701: $r->rflush();
1.28 stredwic 702: }
1.41 matthew 703: return;
704: }
1.21 stredwic 705:
1.73 matthew 706: sub output_html_stats {
707: my ($r)=@_;
708: &compute_all_statistics($r);
709: $r->print(&html_preamble());
710: &sort_data($ENV{'form.sortby'});
711: #
712: my $count=0;
713: foreach my $data (@StatsArray) {
714: if ($count++ % 50 == 0) {
715: $r->print("</table>\n</table>\n");
716: $r->print('<table border="0"><tr><td bgcolor="#777777">'."\n".
717: '<table border="0" cellpadding="3">'."\n".
718: '<tr bgcolor="#FFFFE6">'.
719: '<tr bgcolor="#FFFFE6">'.
720: &statistics_table_header().
721: "</tr>\n");
722: }
723: $r->print('<tr>'.&statistics_html_table_data($data)."</tr>\n");
724: }
725: $r->print("</table>\n</table>\n");
726: return;
727: }
1.47 matthew 728:
1.73 matthew 729: sub html_preamble {
730: my $Str='';
731: $Str .= "<h2>".
732: $ENV{'course.'.$ENV{'request.course.id'}.'.description'}.
733: "</h2>\n";
734: my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
735: if (defined($starttime) || defined($endtime)) {
736: # Inform the user what the time limits on the data are.
737: $Str .= '<h3>'.&mt('Statistics on submissions from [_1] to [_2]',
738: &Apache::lonlocal::locallocaltime($starttime),
739: &Apache::lonlocal::locallocaltime($endtime)
740: ).'</h3>';
741: }
742: $Str .= "<h3>".&mt('Compiled on [_1]',
743: &Apache::lonlocal::locallocaltime(time))."</h3>";
744: return $Str;
745: }
1.47 matthew 746:
747:
1.44 matthew 748: ###############################################
749: ###############################################
1.73 matthew 750: ##
751: ## Misc HTML output routines
752: ##
753: ###############################################
754: ###############################################
755: sub statistics_html_table_data {
756: my ($data,$options) = @_;
757: my $row = '';
758: foreach my $field (@Fields) {
759: next if ($options =~ /no $field->{'name'}/);
1.76 matthew 760: next if ($field->{'selected'} ne 'yes');
1.73 matthew 761: $row .= '<td bgcolor="'.$field->{'color'}.'"';
762: if (exists($field->{'align'})) {
763: $row .= ' align="'.$field->{'align'}.'"';
1.41 matthew 764: }
1.73 matthew 765: $row .= '>';
766: if (exists($field->{'special'}) && $field->{'special'} eq 'link') {
767: $row .= '<a href="'.$data->{$field->{'name'}.'.link'}.'">';
1.41 matthew 768: }
1.73 matthew 769: if (exists($field->{'format'})) {
770: $row .= sprintf($field->{'format'},$data->{$field->{'name'}});
771: } else {
772: $row .= $data->{$field->{'name'}};
773: }
774: if (exists($field->{'special'}) && $field->{'special'} eq 'link') {
775: $row.= '</a>';
776: }
777: $row .= '</td>';
1.21 stredwic 778: }
1.73 matthew 779: return $row;
1.41 matthew 780: }
1.25 stredwic 781:
1.73 matthew 782: sub statistics_table_header {
783: my ($options) = @_;
784: my $header_row;
785: foreach my $field (@Fields) {
786: next if ($options =~ /no $field->{'name'}/);
1.76 matthew 787: next if ($field->{'selected'} ne 'yes');
1.73 matthew 788: $header_row .= '<th>';
789: if (exists($field->{'sortable'}) && $field->{'sortable'} eq 'yes') {
790: $header_row .= '<a href="javascript:'.
791: 'document.Statistics.sortby.value='."'".$field->{'name'}."'".
792: ';document.Statistics.submit();">';
793: }
794: $header_row .= &mt($field->{'title'});
795: if ($options =~ /sortable/) {
796: $header_row.= '</a>';
797: }
798: if ($options !~ /no plots/ &&
799: exists($field->{'graphable'}) &&
800: $field->{'graphable'} eq 'yes') {
801: $header_row.=' (';
802: $header_row .= '<a href="javascript:'.
803: "document.Statistics.plot.value='$field->{'name'}'".
804: ';document.Statistics.submit();">';
805: $header_row .= &mt('plot').'</a>)';
806: }
807: $header_row .= '</th>';
808: }
809: return $header_row;
810: }
1.26 stredwic 811:
1.79 matthew 812: sub sequence_html_header {
813: my $Str .= '<tr>';
814: foreach my $field (@SeqFields) {
815: # next if ($field->{'selected'} ne 'yes');
816: $Str .= '<th bgcolor="'.$field->{'color'}.'"';
817: $Str .= '>'.$field->{'title'}.'</th>';
818: }
819: $Str .= '</tr>';
820: return $Str;
821: }
822:
823:
824: sub sequence_html_output {
825: my ($seq) = @_;
826: my $data = $SeqStat{$seq->{'symb'}};
827: my $row = '<tr>';
828: foreach my $field (@SeqFields) {
1.82 matthew 829: next if ($field->{'selected'} ne 'yes');
1.79 matthew 830: $row .= '<td bgcolor="'.$field->{'color'}.'"';
831: if (exists($field->{'align'})) {
832: $row .= ' align="'.$field->{'align'}.'"';
833: }
834: $row .= '>';
835: if (exists($field->{'format'})) {
836: $row .= sprintf($field->{'format'},$data->{$field->{'name'}});
837: } else {
838: $row .= $data->{$field->{'name'}};
839: }
840: $row .= '</td>';
841: }
842: $row .= '</tr>'."\n";
843: return $row;
844: }
845:
1.73 matthew 846: ####################################################
847: ####################################################
848: ##
849: ## Plotting Routines
850: ##
851: ####################################################
852: ####################################################
853: sub make_plot {
854: my ($r,$plot) = @_;
855: &compute_all_statistics($r);
856: &sort_data($ENV{'form.sortby'});
857: if ($plot eq 'degrees') {
858: °rees_plot($r);
1.74 matthew 859: } elsif ($plot eq 'tries statistics') {
860: &tries_data_plot($r);
1.73 matthew 861: } else {
862: &make_single_stat_plot($r,$plot);
863: }
864: return;
865: }
1.47 matthew 866:
1.73 matthew 867: sub make_single_stat_plot {
868: my ($r,$datafield) = @_;
1.41 matthew 869: #
1.73 matthew 870: my $title; my $yaxis;
871: foreach my $field (@Fields) {
872: next if ($field->{'name'} ne $datafield);
873: $title = $field->{'long_title'};
874: $yaxis = $field->{'title'};
875: last;
876: }
877: if ($title eq '' || $yaxis eq '') {
878: # datafield is something we do not know enough about to plot
879: $r->print('<h3>'.
880: &mt('Unable to plot the requested statistic.').
881: '</h3>');
882: return;
1.49 matthew 883: }
884: #
1.73 matthew 885: # Build up the data sets to plot
886: my @Labels;
887: my @Data;
888: my $max = 1;
889: foreach my $data (@StatsArray) {
890: push(@Labels,$data->{'problem_num'});
891: push(@Data,$data->{$datafield});
892: if ($data->{$datafield}>$max) {
893: $max = $data->{$datafield};
894: }
895: }
896: foreach (1,2,3,4,5,10,15,20,25,40,50,75,100,150,200,250,300,500,600,750,
897: 1000,1500,2000,2500,3000,3500,4000,5000,7500,10000,15000,20000) {
898: if ($max <= $_) {
899: $max = $_;
900: last;
1.42 matthew 901: }
902: }
1.73 matthew 903: if ($max > 20000) {
904: $max = 10000*(int($max/10000)+1);
1.42 matthew 905: }
1.73 matthew 906: #
907: $r->print("<p>".&Apache::loncommon::DrawBarGraph($title,
908: 'Problem Number',
909: $yaxis,
910: $max,
911: undef, # colors
912: \@Labels,
913: \@Data)."</p>\n");
914: return;
915: }
916:
917: sub degrees_plot {
918: my ($r)=@_;
919: my $count = scalar(@StatsArray);
920: my $width = 50 + 10*$count;
921: $width = 300 if ($width < 300);
922: my $height = 300;
923: my $plot = '';
924: my $ymax = 0;
925: my $ymin = 0;
926: my @Disc; my @Diff; my @Labels;
927: foreach my $data (@StatsArray) {
928: push(@Labels,$data->{'problem_num'});
929: my $disc = $data->{'deg_of_disc'};
930: my $diff = $data->{'deg_of_diff'};
931: push(@Disc,$disc);
932: push(@Diff,$diff);
933: #
934: $ymin = $disc if ($ymin > $disc);
935: $ymin = $diff if ($ymin > $diff);
936: $ymax = $disc if ($ymax < $disc);
937: $ymax = $diff if ($ymax < $diff);
938: }
939: #
940: # Make sure we show relevant information.
941: if ($ymin < 0) {
942: if (abs($ymin) < 0.05) {
943: $ymin = 0;
944: } else {
945: $ymin = -1;
1.42 matthew 946: }
947: }
1.73 matthew 948: if ($ymax > 0) {
949: if (abs($ymax) < 0.05) {
950: $ymax = 0;
1.42 matthew 951: } else {
1.73 matthew 952: $ymax = 1;
1.42 matthew 953: }
1.43 matthew 954: }
1.49 matthew 955: #
1.73 matthew 956: my $xmax = $Labels[-1];
957: if ($xmax > 50) {
958: if ($xmax % 10 != 0) {
959: $xmax = 10 * (int($xmax/10)+1);
960: }
961: } else {
962: if ($xmax % 5 != 0) {
963: $xmax = 5 * (int($xmax/5)+1);
1.49 matthew 964: }
1.26 stredwic 965: }
1.41 matthew 966: #
1.73 matthew 967: my $discdata .= '<data>'.join(',',@Labels).'</data>'.$/.
968: '<data>'.join(',',@Disc).'</data>'.$/;
969: #
970: my $diffdata .= '<data>'.join(',',@Labels).'</data>'.$/.
971: '<data>'.join(',',@Diff).'</data>'.$/;
972: #
1.81 matthew 973: my $title = 'Degree of Discrimination\nand Degree of Difficulty';
974: if ($xmax > 50) {
975: $title = 'Degree of Discrimination and Degree of Difficulty';
976: }
977: #
1.73 matthew 978: $plot=<<"END";
979: <gnuplot
980: texfont="10"
981: fgcolor="x000000"
982: plottype="Cartesian"
983: font="large"
984: grid="on"
985: align="center"
986: border="on"
987: transparent="on"
1.81 matthew 988: alttag="Degree of Discrimination and Degree of Difficulty Plot"
1.73 matthew 989: samples="100"
990: bgcolor="xffffff"
991: height="$height"
992: width="$width">
993: <key
994: pos="top right"
995: title=""
996: box="off" />
1.81 matthew 997: <title>$title</title>
1.73 matthew 998: <axis xmin="0" ymin="$ymin" xmax="$xmax" ymax="$ymax" color="x000000" />
999: <xlabel>Problem Number</xlabel>
1000: <curve
1001: linestyle="linespoints"
1002: name="DoDisc"
1003: pointtype="0"
1004: color="x000000">
1005: $discdata
1006: </curve>
1007: <curve
1008: linestyle="linespoints"
1009: name="DoDiff"
1010: pointtype="0"
1011: color="xFF0000">
1012: $diffdata
1013: </curve>
1014: </gnuplot>
1015: END
1016: my $plotresult =
1017: '<p>'.&Apache::lonxml::xmlparse($r,'web',$plot).'</p>'.$/;
1018: $r->print($plotresult);
1.41 matthew 1019: return;
1.42 matthew 1020: }
1021:
1.74 matthew 1022: sub tries_data_plot {
1023: my ($r)=@_;
1024: my $count = scalar(@StatsArray);
1025: my $width = 50 + 10*$count;
1026: $width = 300 if ($width < 300);
1027: my $height = 300;
1028: my $plot = '';
1029: my @STD; my @Mean; my @Max; my @Min;
1030: my @Labels;
1031: my $ymax = 5;
1032: foreach my $data (@StatsArray) {
1033: my $max = $data->{'mean_tries'} + $data->{'std_tries'};
1034: $ymax = $max if ($ymax < $max);
1035: $ymax = $max if ($ymax < $max);
1036: push(@Labels,$data->{'problem_num'});
1037: push(@STD,$data->{'std_tries'});
1038: push(@Mean,$data->{'mean_tries'});
1039: }
1040: #
1041: # Make sure we show relevant information.
1042: my $xmax = $Labels[-1];
1043: if ($xmax > 50) {
1044: if ($xmax % 10 != 0) {
1045: $xmax = 10 * (int($xmax/10)+1);
1046: }
1047: } else {
1048: if ($xmax % 5 != 0) {
1049: $xmax = 5 * (int($xmax/5)+1);
1050: }
1051: }
1052: $ymax = int($ymax)+1+2;
1053: #
1054: my $std_data .= '<data>'.join(',',@Labels).'</data>'.$/.
1055: '<data>'.join(',',@Mean).'</data>'.$/;
1056: #
1057: my $std_error_data .= '<data>'.join(',',@Labels).'</data>'.$/.
1058: '<data>'.join(',',@Mean).'</data>'.$/.
1059: '<data>'.join(',',@STD).'</data>'.$/;
1060: #
1.81 matthew 1061: my $title = 'Mean and S.D. of Tries';
1062: if ($xmax > 25) {
1063: $title = 'Mean and Standard Deviation of Tries';
1064: }
1065: #
1.74 matthew 1066: $plot=<<"END";
1067: <gnuplot
1068: texfont="10"
1069: fgcolor="x000000"
1070: plottype="Cartesian"
1071: font="large"
1072: grid="on"
1073: align="center"
1074: border="on"
1075: transparent="on"
1.81 matthew 1076: alttag="Mean and S.D of Tries Plot"
1.74 matthew 1077: samples="100"
1078: bgcolor="xffffff"
1079: height="$height"
1080: width="$width">
1.81 matthew 1081: <title>$title</title>
1.74 matthew 1082: <axis xmin="0" ymin="0" xmax="$xmax" ymax="$ymax" color="x000000" />
1083: <xlabel>Problem Number</xlabel>
1.81 matthew 1084: <ylabel>Number of Tries</ylabel>
1.74 matthew 1085: <curve
1086: linestyle="yerrorbars"
1087: name="S.D. Tries"
1088: pointtype="1"
1089: color="x666666">
1090: $std_error_data
1091: </curve>
1092: <curve
1093: linestyle="points"
1094: name="Mean Tries"
1095: pointtype="1"
1096: color="xCC4444">
1097: $std_data
1098: </curve>
1099: </gnuplot>
1100: END
1101: my $plotresult =
1102: '<p>'.&Apache::lonxml::xmlparse($r,'web',$plot).'</p>'.$/;
1103: $r->print($plotresult);
1104: return;
1105: }
1106:
1.73 matthew 1107: sub plot_dropdown {
1108: my $current = '';
1109: #
1110: if (defined($ENV{'form.plot'})) {
1111: $current = $ENV{'form.plot'};
1112: }
1113: #
1114: my @Additional_Plots = (
1115: { graphable=>'yes',
1116: name => 'degrees',
1.81 matthew 1117: title => 'Difficulty Indexes' },
1.74 matthew 1118: { graphable=>'yes',
1119: name => 'tries statistics',
1.81 matthew 1120: title => 'Tries Statistics' });
1.73 matthew 1121: #
1122: my $Str= "\n".'<select name="plot" size="1">';
1123: $Str .= '<option name="none"></option>'."\n";
1124: $Str .= '<option name="none2">none</option>'."\n";
1.81 matthew 1125: foreach my $field (@Additional_Plots,@Fields) {
1.73 matthew 1126: if (! exists($field->{'graphable'}) ||
1127: $field->{'graphable'} ne 'yes') {
1128: next;
1129: }
1130: $Str .= '<option value="'.$field->{'name'}.'"';
1131: if ($field->{'name'} eq $current) {
1132: $Str .= ' selected ';
1133: }
1134: $Str.= '>'.&mt($field->{'title'}).'</option>'."\n";
1135: }
1136: $Str .= '</select>'."\n";
1137: return $Str;
1138: }
1139:
1.41 matthew 1140: ###############################################
1141: ###############################################
1.73 matthew 1142: ##
1143: ## Excel output routines
1144: ##
1.41 matthew 1145: ###############################################
1146: ###############################################
1.73 matthew 1147: sub Excel_output {
1.44 matthew 1148: my ($r) = @_;
1.73 matthew 1149: $r->print('<h2>'.&mt('Preparing Excel Spreadsheet').'</h2>');
1150: ##
1151: ## Compute the statistics
1152: &compute_all_statistics($r);
1153: my $c = $r->connection;
1154: return if ($c->aborted());
1155: ##
1156: ## Create the excel workbook
1.44 matthew 1157: my $filename = '/prtspool/'.
1158: $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
1.73 matthew 1159: time.'_'.rand(1000000000).'.xls';
1.70 matthew 1160: my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
1161: #
1.44 matthew 1162: # Create sheet
1.73 matthew 1163: my $excel_workbook = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);
1.44 matthew 1164: #
1165: # Check for errors
1166: if (! defined($excel_workbook)) {
1167: $r->log_error("Error creating excel spreadsheet $filename: $!");
1.59 matthew 1168: $r->print(&mt("Problems creating new Excel file. ".
1.44 matthew 1169: "This error has been logged. ".
1.59 matthew 1170: "Please alert your LON-CAPA administrator."));
1.73 matthew 1171: return 0;
1.44 matthew 1172: }
1173: #
1174: # The excel spreadsheet stores temporary data in files, then put them
1175: # together. If needed we should be able to disable this (memory only).
1176: # The temporary directory must be specified before calling 'addworksheet'.
1177: # File::Temp is used to determine the temporary directory.
1178: $excel_workbook->set_tempdir($Apache::lonnet::tmpdir);
1179: #
1180: # Add a worksheet
1181: my $sheetname = $ENV{'course.'.$ENV{'request.course.id'}.'.description'};
1182: if (length($sheetname) > 31) {
1183: $sheetname = substr($sheetname,0,31);
1184: }
1.73 matthew 1185: my $excel_sheet = $excel_workbook->addworksheet(
1186: &Apache::loncommon::clean_excel_name($sheetname));
1.82 matthew 1187: #
1188: my $format = &Apache::loncommon::define_excel_formats($excel_workbook);
1.73 matthew 1189: ##
1190: ## Begin creating excel sheet
1191: ##
1192: my ($rows_output,$cols_output) = (0,0);
1.44 matthew 1193: #
1194: # Put the course description in the header
1195: $excel_sheet->write($rows_output,$cols_output++,
1.82 matthew 1196: $ENV{'course.'.$ENV{'request.course.id'}.'.description'},
1197: $format->{'h1'});
1.44 matthew 1198: $cols_output += 3;
1199: #
1200: # Put a description of the sections listed
1201: my $sectionstring = '';
1.82 matthew 1202: $excel_sheet->write($rows_output,$cols_output++,
1203: &Apache::lonstathelpers::sections_description
1204: (@Apache::lonstatistics::SelectedSections),
1205: $format->{'h3'});
1206: $cols_output += scalar(@Apache::lonstatistics::SelectedSections);
1.44 matthew 1207: #
1.70 matthew 1208: # Time restrictions
1209: my $time_string;
1210: if (defined($starttime)) {
1211: # call localtime but not lonlocal:locallocaltime because excel probably
1212: # cannot handle localized text. Probably.
1213: $time_string .= 'Data collected from '.localtime($time_string);
1214: if (defined($endtime)) {
1215: $time_string .= ' to '.localtime($endtime);
1216: }
1217: $time_string .= '.';
1218: } elsif (defined($endtime)) {
1219: # See note above about lonlocal:locallocaltime
1220: $time_string .= 'Data collected before '.localtime($endtime).'.';
1221: }
1.82 matthew 1222: if (defined($time_string)) {
1223: $excel_sheet->write($rows_output,$cols_output++,$time_string);
1224: $cols_output+= 5;
1225: }
1.70 matthew 1226: #
1.44 matthew 1227: # Put the date in there too
1228: $excel_sheet->write($rows_output,$cols_output++,
1229: 'Compiled on '.localtime(time));
1230: #
1231: $rows_output++;
1232: $cols_output=0;
1.82 matthew 1233: ##
1234: ## Sequence Statistics
1235: ##
1236: &write_headers($excel_sheet,$format,\$rows_output,\$cols_output,
1237: \@SeqFields);
1238: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
1239: next if ($seq->{'num_assess'} < 1);
1240: my $data = $SeqStat{$seq->{'symb'}};
1241: $cols_output=0;
1242: foreach my $field (@SeqFields) {
1243: next if ($field->{'selected'} ne 'yes');
1.55 matthew 1244: $excel_sheet->write($rows_output,$cols_output++,
1.82 matthew 1245: $data->{$field->{'name'}});
1.55 matthew 1246: }
1.82 matthew 1247: $rows_output++;
1248: $cols_output=0;
1.55 matthew 1249: }
1.82 matthew 1250: ##
1251: ## Resource Statistics
1252: ##
1.55 matthew 1253: $rows_output++;
1254: $cols_output=0;
1.82 matthew 1255: &write_headers($excel_sheet,$format,\$rows_output,\$cols_output,
1256: \@Fields);
1257: #
1.73 matthew 1258: foreach my $data (@StatsArray) {
1259: $cols_output=0;
1260: foreach my $field (@Fields) {
1.76 matthew 1261: next if ($field->{'selected'} ne 'yes');
1.73 matthew 1262: next if ($field->{'name'} eq 'problem_num');
1263: $excel_sheet->write($rows_output,$cols_output++,
1264: $data->{$field->{'name'}});
1.44 matthew 1265: }
1.73 matthew 1266: $rows_output++;
1.82 matthew 1267: $cols_output=0;
1.44 matthew 1268: }
1269: #
1270: $excel_workbook->close();
1.73 matthew 1271: #
1.44 matthew 1272: # Tell the user where to get their excel file
1273: $r->print('<br />'.
1.59 matthew 1274: '<a href="'.$filename.'">'.
1275: &mt('Your Excel Spreadsheet').'</a>'."\n");
1.44 matthew 1276: $r->rflush();
1277: return;
1278: }
1279:
1.82 matthew 1280: ##
1281: ## &write_headers
1282: ##
1283: sub write_headers {
1284: my ($excel_sheet,$format,$rows_output,$cols_output,$Fields) = @_;
1285: ##
1286: ## First the long titles
1287: foreach my $field (@{$Fields}) {
1288: next if ($field->{'name'} eq 'problem_num');
1289: next if ($field->{'selected'} ne 'yes');
1290: if (exists($field->{'long_title'})) {
1291: $excel_sheet->write($$rows_output,${$cols_output},
1292: $field->{'long_title'},
1293: $format->{'bold'});
1294: } else {
1295: $excel_sheet->write($$rows_output,${$cols_output},'');
1296: }
1297: ${$cols_output}+= 1;
1298: }
1299: ${$cols_output} =0;
1300: ${$rows_output}+=1;
1301: ##
1302: ## Then the short titles
1303: foreach my $field (@{$Fields}) {
1304: next if ($field->{'selected'} ne 'yes');
1305: next if ($field->{'name'} eq 'problem_num');
1306: # Use english for excel as I am not sure how well excel handles
1307: # other character sets....
1308: $excel_sheet->write($$rows_output,$$cols_output,
1309: $field->{'title'},
1310: $format->{'bold'});
1311: $$cols_output+=1;
1312: }
1313: ${$cols_output} =0;
1314: ${$rows_output}+=1;
1315: return;
1316: }
1317:
1.73 matthew 1318: ##################################################
1319: ##################################################
1320: ##
1321: ## Statistics Gathering and Manipulation Routines
1322: ##
1323: ##################################################
1324: ##################################################
1325: sub compute_statistics_on_sequence {
1326: my ($seq) = @_;
1327: my @Data;
1328: foreach my $res (@{$seq->{'contents'}}) {
1329: next if ($res->{'type'} ne 'assessment');
1330: foreach my $part (@{$res->{'parts'}}) {
1331: #
1332: # This is where all the work happens
1333: my $data = &get_statistics($seq,$res,$part,scalar(@StatsArray)+1);
1334: push (@Data,$data);
1335: push (@StatsArray,$data);
1.49 matthew 1336: }
1.26 stredwic 1337: }
1.73 matthew 1338: return @Data;
1.41 matthew 1339: }
1.26 stredwic 1340:
1.73 matthew 1341: sub compute_all_statistics {
1342: my ($r) = @_;
1343: if (@StatsArray > 0) {
1344: # Assume we have already computed the statistics
1345: return;
1346: }
1347: my $c = $r->connection;
1348: foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
1349: last if ($c->aborted);
1350: next if ($seq->{'num_assess'} < 1);
1.82 matthew 1351: &compute_sequence_statistics($seq);
1.73 matthew 1352: &compute_statistics_on_sequence($seq);
1.49 matthew 1353: }
1354: }
1355:
1.73 matthew 1356: sub sort_data {
1357: my ($sortkey) = @_;
1358: return if (! @StatsArray);
1.45 matthew 1359: #
1.73 matthew 1360: # Sort the data
1361: my $sortby = undef;
1.49 matthew 1362: foreach my $field (@Fields) {
1.73 matthew 1363: if ($sortkey eq $field->{'name'}) {
1364: $sortby = $field->{'name'};
1.45 matthew 1365: }
1.26 stredwic 1366: }
1.73 matthew 1367: if (! defined($sortby) || $sortby eq '' || $sortby eq 'problem_num') {
1368: $sortby = 'container';
1369: }
1370: if ($sortby ne 'container') {
1371: # $sortby is already defined, so we can charge ahead
1372: if ($sortby =~ /^(title|part)$/i) {
1373: # Alpha comparison
1374: @StatsArray = sort {
1375: lc($a->{$sortby}) cmp lc($b->{$sortby}) ||
1376: lc($a->{'title'}) cmp lc($b->{'title'}) ||
1377: lc($a->{'part'}) cmp lc($b->{'part'});
1378: } @StatsArray;
1.24 stredwic 1379: } else {
1.73 matthew 1380: # Numerical comparison
1381: @StatsArray = sort {
1382: my $retvalue = 0;
1383: if ($b->{$sortby} eq 'nan') {
1384: if ($a->{$sortby} ne 'nan') {
1385: $retvalue = -1;
1386: } else {
1387: $retvalue = 0;
1388: }
1389: }
1390: if ($a->{$sortby} eq 'nan') {
1391: if ($b->{$sortby} ne 'nan') {
1392: $retvalue = 1;
1393: }
1394: }
1395: if ($retvalue eq '0') {
1396: $retvalue = $b->{$sortby} <=> $a->{$sortby} ||
1397: lc($a->{'title'}) <=> lc($b->{'title'}) ||
1398: lc($a->{'part'}) <=> lc($b->{'part'});
1399: }
1400: $retvalue;
1401: } @StatsArray;
1.24 stredwic 1402: }
1403: }
1.45 matthew 1404: #
1.73 matthew 1405: # Renumber the data set
1406: my $count;
1407: foreach my $data (@StatsArray) {
1408: $data->{'problem_num'} = ++$count;
1409: }
1.24 stredwic 1410: return;
1.48 matthew 1411: }
1412:
1.70 matthew 1413: ########################################################
1414: ########################################################
1415:
1416: =pod
1417:
1418: =item &get_statistics()
1419:
1420: Wrapper routine from the call to loncoursedata::get_problem_statistics.
1.73 matthew 1421: Calls lonstathelpers::get_time_limits() to limit the data set by time
1422: and &compute_discrimination_factor
1.70 matthew 1423:
1424: Inputs: $sequence, $resource, $part, $problem_num
1425:
1426: Returns: Hash reference with statistics data from
1427: loncoursedata::get_problem_statistics.
1428:
1429: =cut
1430:
1431: ########################################################
1432: ########################################################
1.48 matthew 1433: sub get_statistics {
1.49 matthew 1434: my ($sequence,$resource,$part,$problem_num) = @_;
1.48 matthew 1435: #
1.70 matthew 1436: my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
1.49 matthew 1437: my $symb = $resource->{'symb'};
1.48 matthew 1438: my $courseid = $ENV{'request.course.id'};
1439: #
1.49 matthew 1440: my $data = &Apache::loncoursedata::get_problem_statistics
1.66 matthew 1441: (\@Apache::lonstatistics::SelectedSections,
1442: $Apache::lonstatistics::enrollment_status,
1.70 matthew 1443: $symb,$part,$courseid,$starttime,$endtime);
1.49 matthew 1444: $data->{'part'} = $part;
1445: $data->{'problem_num'} = $problem_num;
1446: $data->{'container'} = $sequence->{'title'};
1447: $data->{'title'} = $resource->{'title'};
1.53 matthew 1448: $data->{'title.link'} = $resource->{'src'}.'?symb='.
1449: &Apache::lonnet::escape($resource->{'symb'});
1.49 matthew 1450: #
1.76 matthew 1451: if ($SelectedFields{'deg_of_disc'}) {
1452: $data->{'deg_of_disc'} =
1453: &compute_discrimination_factor($resource,$part,$sequence);
1454: }
1.83 matthew 1455: #
1456: # Store in metadata if computations were done for all students
1.84 ! matthew 1457: if ($data->{'num_students'} > 1) {
! 1458: my @Sections = @Apache::lonstatistics::SelectedSections;
! 1459: my $sections = '"'.join(' ',@Sections).'"';
! 1460: $sections =~ s/&+/_/g; # Ensure no special characters
! 1461: $data->{'sections'}=$sections;
! 1462: $data->{'course'} = $ENV{'request.course.id'};
1.83 matthew 1463: my $urlres=(&Apache::lonnet::decode_symb($resource->{'symb'}))[2];
1.84 ! matthew 1464: $data->{'urlres'}=$urlres;
! 1465: my %storestats =
! 1466: &LONCAPA::lonmetadata::dynamic_metadata_storage($data);
1.83 matthew 1467: my ($dom,$user) = $urlres=~/^(\w+)\/(\w+)/;
1468: &Apache::lonnet::put('nohist_resevaldata',\%storestats,$dom,$user);
1469: }
1.49 matthew 1470: return $data;
1.71 matthew 1471: }
1472:
1473: ###############################################
1474: ###############################################
1475:
1476: =pod
1477:
1478: =item &compute_discrimination_factor()
1479:
1480: Inputs: $Resource, $Sequence
1481:
1482: Returns: integer between -1 and 1
1483:
1484: =cut
1485:
1486: ###############################################
1487: ###############################################
1488: sub compute_discrimination_factor {
1489: my ($resource,$part,$sequence) = @_;
1490: my @Resources;
1491: foreach my $res (@{$sequence->{'contents'}}) {
1492: next if ($res->{'symb'} eq $resource->{'symb'});
1493: push (@Resources,$res->{'symb'});
1494: }
1495: #
1496: # rank
1.83 matthew 1497: my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
1.71 matthew 1498: my $ranking =
1499: &Apache::loncoursedata::rank_students_by_scores_on_resources
1500: (\@Resources,
1501: \@Apache::lonstatistics::SelectedSections,
1.83 matthew 1502: $Apache::lonstatistics::enrollment_status,undef,
1503: $starttime,$endtime);
1.71 matthew 1504: #
1505: # compute their percent scores on the problems in the sequence,
1506: my $number_to_grab = int(scalar(@{$ranking})/4);
1507: my $num_students = scalar(@{$ranking});
1508: my @BottomSet = map { $_->[&Apache::loncoursedata::RNK_student()];
1509: } @{$ranking}[0..$number_to_grab];
1510: my @TopSet =
1511: map {
1512: $_->[&Apache::loncoursedata::RNK_student()];
1513: } @{$ranking}[($num_students-$number_to_grab)..($num_students-1)];
1514: my ($bottom_sum,$bottom_max) =
1.83 matthew 1515: &Apache::loncoursedata::get_sum_of_scores($resource,$part,\@BottomSet,
1516: undef,$starttime,$endtime);
1.71 matthew 1517: my ($top_sum,$top_max) =
1.83 matthew 1518: &Apache::loncoursedata::get_sum_of_scores($resource,$part,\@TopSet,
1519: undef,$starttime,$endtime);
1.71 matthew 1520: my $deg_of_disc;
1521: if ($top_max == 0 || $bottom_max==0) {
1522: $deg_of_disc = 'nan';
1523: } else {
1524: $deg_of_disc = ($top_sum/$top_max) - ($bottom_sum/$bottom_max);
1525: }
1526: #&Apache::lonnet::logthis(' '.$top_sum.'/'.$top_max.
1527: # ' - '.$bottom_sum.'/'.$bottom_max);
1528: return $deg_of_disc;
1.1 stredwic 1529: }
1.12 minaeibi 1530:
1.45 matthew 1531: ###############################################
1532: ###############################################
1.79 matthew 1533: ##
1534: ## Compute KR-21
1535: ##
1536: ## To compute KR-21, you need the following information:
1537: ##
1538: ## K=the number of items in your test
1539: ## M=the mean score on the test
1540: ## s=the standard deviation of the scores on your test
1541: ##
1542: ## then:
1543: ##
1544: ## KR-21 rk= [K/(K-1)] * [1- (M*(K-M))/(K*s^2))]
1545: ##
1546: ###############################################
1547: ###############################################
1548: sub compute_sequence_statistics {
1549: my ($seq) = @_;
1550: my $symb = $seq->{'symb'};
1551: my @Resources;
1552: foreach my $res (@{$seq->{'contents'}}) {
1553: next if ($res->{'type'} ne 'assessment');
1554: push (@Resources,$res->{'symb'});
1555: }
1556: my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
1557: #
1558: # First compute statistics based on student scores
1559: my ($smin,$smax,$sMean,$sSTD,$scount,$sMAX) =
1560: &Apache::loncoursedata::score_stats
1561: (\@Apache::lonstatistics::SelectedSections,
1562: $Apache::lonstatistics::enrollment_status,
1563: \@Resources,$starttime,$endtime,undef);
1564: $SeqStat{$symb}->{'title'} = $seq->{'title'};
1565: $SeqStat{$symb}->{'scoremax'} = $smax;
1566: $SeqStat{$symb}->{'scoremin'} = $smin;
1567: $SeqStat{$symb}->{'scoremean'} = $sMean;
1568: $SeqStat{$symb}->{'scorestd'} = $sSTD;
1569: $SeqStat{$symb}->{'scorecount'} = $scount;
1570: $SeqStat{$symb}->{'max_possible'} = $sMAX;
1571: #
1572: # Compute statistics based on the number of correct problems
1573: # 'correct' is taken to mean
1574: my ($cmin,$cmax,$cMean,$cSTD,$ccount)=
1575: &Apache::loncoursedata::count_stats
1576: (\@Apache::lonstatistics::SelectedSections,
1577: $Apache::lonstatistics::enrollment_status,
1578: \@Resources,$starttime,$endtime,undef);
1579: my $K = $seq->{'num_assess_parts'};
1580: my $kr_21;
1581: if ($K > 1 && $cSTD > 0) {
1582: $kr_21 = ($K/($K-1)) * (1 - $cMean*($K-$cMean)/($K*$cSTD**2));
1583: } else {
1584: $kr_21 = 'nan';
1585: }
1586: $SeqStat{$symb}->{'countmax'} = $cmax;
1587: $SeqStat{$symb}->{'countmin'} = $cmin;
1588: $SeqStat{$symb}->{'countstd'} = $cSTD;
1.82 matthew 1589: $SeqStat{$symb}->{'countmean'} = $cMean;
1.79 matthew 1590: $SeqStat{$symb}->{'count'} = $ccount;
1591: $SeqStat{$symb}->{'items'} = $K;
1592: $SeqStat{$symb}->{'KR-21'}=$kr_21;
1593: return;
1594: }
1595:
1596:
1.47 matthew 1597:
1598: =pod
1599:
1.73 matthew 1600: =item ProblemStatisticsLegend
1601:
1602: =over 4
1603:
1604: =item #Stdnts
1605: Total number of students attempted the problem.
1606:
1607: =item Tries
1608: Total number of tries for solving the problem.
1.59 matthew 1609:
1.73 matthew 1610: =item Max Tries
1611: Largest number of tries for solving the problem by a student.
1612:
1613: =item Mean
1614: Average number of tries. [ Tries / #Stdnts ]
1615:
1616: =item #YES
1617: Number of students solved the problem correctly.
1618:
1619: =item #yes
1620: Number of students solved the problem by override.
1621:
1622: =item %Wrong
1623: Percentage of students who tried to solve the problem
1624: but is still incorrect. [ 100*((#Stdnts-(#YES+#yes))/#Stdnts) ]
1625:
1626: =item DoDiff
1627: Degree of Difficulty of the problem.
1628: [ 1 - ((#YES+#yes) / Tries) ]
1629:
1630: =item S.D.
1631: Standard Deviation of the tries.
1632: [ sqrt(sum((Xi - Mean)^2)) / (#Stdnts-1)
1633: where Xi denotes every student\'s tries ]
1634:
1635: =item Skew.
1636: Skewness of the students tries.
1637: [(sqrt( sum((Xi - Mean)^3) / #Stdnts)) / (S.D.^3)]
1638:
1639: =item Dis.F.
1640: Discrimination Factor: A Standard for evaluating the
1641: problem according to a Criterion<br>
1642:
1643: =item [Criterion to group students into %27 Upper Students -
1644: and %27 Lower Students]
1645: 1st Criterion for Sorting the Students:
1646: Sum of Partial Credit Awarded / Total Number of Tries
1647: 2nd Criterion for Sorting the Students:
1648: Total number of Correct Answers / Total Number of Tries
1649:
1650: =item Disc.
1651: Number of Students had at least one discussion.
1652:
1653: =back
1.47 matthew 1654:
1655: =cut
1.73 matthew 1656:
1657: ############################################################
1658: ############################################################
1.4 minaeibi 1659:
1.1 stredwic 1660: 1;
1661: __END__
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>