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