Clean up and improve case code.
[pspp-builds.git] / src / language / stats / rank.q
1 /* PSPP - RANK. -*-c-*-
2 Copyright (C) 2005, 2006, 2007 Free Software Foundation, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License as
6 published by the Free Software Foundation; either version 2 of the
7 License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful, but
10 WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA. */
18
19 #include <config.h>
20
21 #include "sort-criteria.h"
22
23 #include <data/dictionary.h>
24 #include <data/format.h>
25 #include <data/missing-values.h>
26 #include <data/procedure.h>
27 #include <data/variable.h>
28 #include <data/case.h>
29 #include <data/casefile.h>
30 #include <data/fastfile.h>
31 #include <data/storage-stream.h>
32 #include <language/command.h>
33 #include <language/stats/sort-criteria.h>
34 #include <limits.h>
35 #include <libpspp/compiler.h>
36 #include <math/sort.h>
37 #include <output/table.h>
38 #include <output/manager.h>
39
40 #include <gsl/gsl_cdf.h>
41 #include <math.h>
42
43 #include "gettext.h"
44 #define _(msgid) gettext (msgid)
45
46 /* (headers) */
47
48 /* (specification)
49    "RANK" (rank_):
50    *^variables=custom;
51    +rank=custom;
52    +normal=custom;
53    +percent=custom;
54    +ntiles=custom;
55    +rfraction=custom;
56    +proportion=custom;
57    +n=custom;
58    +savage=custom;
59    +print=print:!yes/no;
60    +fraction=fraction:!blom/tukey/vw/rankit;
61    +ties=ties:!mean/low/high/condense;
62    missing=miss:!exclude/include.
63 */
64 /* (declarations) */
65 /* (functions) */
66
67 typedef double (*rank_function_t) (double c, double cc, double cc_1,
68                                  int i, double w);
69
70 static double rank_proportion (double c, double cc, double cc_1,
71                                int i, double w);
72
73 static double rank_normal (double c, double cc, double cc_1,
74                            int i, double w);
75
76 static double rank_percent (double c, double cc, double cc_1,
77                             int i, double w);
78
79 static double rank_rfraction (double c, double cc, double cc_1,
80                               int i, double w);
81
82 static double rank_rank (double c, double cc, double cc_1,
83                          int i, double w);
84
85 static double rank_n (double c, double cc, double cc_1,
86                       int i, double w);
87
88 static double rank_savage (double c, double cc, double cc_1,
89                       int i, double w);
90
91 static double rank_ntiles (double c, double cc, double cc_1,
92                       int i, double w);
93
94
95 enum RANK_FUNC
96   {
97     RANK,
98     NORMAL,
99     PERCENT,
100     RFRACTION,
101     PROPORTION,
102     N,
103     NTILES,
104     SAVAGE,
105     n_RANK_FUNCS
106   };
107
108 static const struct fmt_spec dest_format[n_RANK_FUNCS] = {
109   {FMT_F, 9, 3}, /* rank */
110   {FMT_F, 6, 4}, /* normal */
111   {FMT_F, 6, 2}, /* percent */
112   {FMT_F, 6, 4}, /* rfraction */
113   {FMT_F, 6, 4}, /* proportion */
114   {FMT_F, 6, 0}, /* n */
115   {FMT_F, 3, 0}, /* ntiles */
116   {FMT_F, 8, 4}  /* savage */
117 };
118
119 static const char * const function_name[n_RANK_FUNCS] = {
120   "RANK",
121   "NORMAL",
122   "PERCENT",
123   "RFRACTION",
124   "PROPORTION",
125   "N",
126   "NTILES",
127   "SAVAGE"
128 };
129
130 static const rank_function_t rank_func[n_RANK_FUNCS] = {
131   rank_rank,
132   rank_normal,
133   rank_percent,
134   rank_rfraction,
135   rank_proportion,
136   rank_n,
137   rank_ntiles,
138   rank_savage
139   };
140
141
142 struct rank_spec
143 {
144   enum RANK_FUNC rfunc;
145   struct variable **destvars;
146 };
147
148
149 /* Categories of missing values to exclude. */
150 static enum mv_class exclude_values;
151
152 static struct rank_spec *rank_specs;
153 static size_t n_rank_specs;
154
155 static struct sort_criteria *sc;
156
157 static struct variable **group_vars;
158 static size_t n_group_vars;
159
160 static struct variable **src_vars;
161 static size_t n_src_vars;
162
163
164 static int k_ntiles;
165
166 static struct cmd_rank cmd;
167
168 static struct casefile *rank_sorted_casefile (struct casefile *cf,
169                                               const struct sort_criteria *,
170                                               const struct dictionary *,
171                                               const struct rank_spec *rs,
172                                               int n_rank_specs,
173                                               int idx,
174                                               const struct missing_values *miss
175                                               );
176 static const char *
177 fraction_name(void)
178 {
179   static char name[10];
180   switch ( cmd.fraction )
181     {
182     case RANK_BLOM:
183       strcpy (name, "BLOM");
184       break;
185     case RANK_RANKIT:
186       strcpy (name, "RANKIT");
187       break;
188     case RANK_TUKEY:
189       strcpy (name, "TUKEY");
190       break;
191     case RANK_VW:
192       strcpy (name, "VW");
193       break;
194     default:
195       NOT_REACHED ();
196     }
197   return name;
198 }
199
200 /* Create a label on DEST_VAR, describing its derivation from SRC_VAR and F */
201 static void
202 create_var_label (struct variable *dest_var,
203                   const struct variable *src_var, enum RANK_FUNC f)
204 {
205   struct string label;
206   ds_init_empty (&label);
207
208   if ( n_group_vars > 0 )
209     {
210       struct string group_var_str;
211       int g;
212
213       ds_init_empty (&group_var_str);
214
215       for (g = 0 ; g < n_group_vars ; ++g )
216         {
217           if ( g > 0 ) ds_put_cstr (&group_var_str, " ");
218           ds_put_cstr (&group_var_str, var_get_name (group_vars[g]));
219         }
220
221       ds_put_format (&label, _("%s of %s by %s"), function_name[f],
222                      var_get_name (src_var), ds_cstr (&group_var_str));
223       ds_destroy (&group_var_str);
224     }
225   else
226     ds_put_format (&label, _("%s of %s"),
227                    function_name[f], var_get_name (src_var));
228
229   var_set_label (dest_var, ds_cstr (&label));
230
231   ds_destroy (&label);
232 }
233
234
235 static bool
236 rank_cmd (struct dataset *ds, const struct sort_criteria *sc,
237           const struct rank_spec *rank_specs, int n_rank_specs)
238 {
239   struct sort_criteria criteria;
240   bool result = true;
241   int i;
242   const int n_splits = dict_get_split_cnt (dataset_dict (ds));
243
244   criteria.crit_cnt = n_splits + n_group_vars + 1;
245   criteria.crits = xnmalloc (criteria.crit_cnt, sizeof *criteria.crits);
246   for (i = 0; i < n_splits ; i++)
247     {
248       struct variable *v = dict_get_split_vars (dataset_dict (ds))[i];
249       criteria.crits[i].fv = var_get_case_index (v);
250       criteria.crits[i].width = var_get_width (v);
251       criteria.crits[i].dir = SRT_ASCEND;
252     }
253   for (i = 0; i < n_group_vars; i++)
254     {
255       criteria.crits[i + n_splits].fv = var_get_case_index (group_vars[i]);
256       criteria.crits[i + n_splits].width = var_get_width (group_vars[i]);
257       criteria.crits[i + n_splits].dir = SRT_ASCEND;
258     }
259   for (i = 0 ; i < sc->crit_cnt ; ++i )
260     {
261       struct casefile *out ;
262       struct casefile *cf ;
263       struct casereader *reader ;
264       struct casefile *sorted_cf ;
265
266       /* Obtain active file in CF. */
267       if (!procedure (ds, NULL, NULL))
268         goto error;
269
270       cf = proc_capture_output (ds);
271
272       /* Sort CF into SORTED_CF. */
273       reader = casefile_get_destructive_reader (cf) ;
274       criteria.crits[criteria.crit_cnt - 1] = sc->crits[i];
275       assert ( sc->crits[i].fv == var_get_case_index (src_vars[i]) );
276       sorted_cf = sort_execute (reader, &criteria, NULL);
277       casefile_destroy (cf);
278
279       out = rank_sorted_casefile (sorted_cf, &criteria,
280                                   dataset_dict (ds),
281                                   rank_specs, n_rank_specs,
282                                   i, var_get_missing_values (src_vars[i]));
283       if ( NULL == out )
284         {
285           result = false ;
286           continue ;
287         }
288
289       proc_set_source (ds, storage_source_create (out));
290     }
291
292   free (criteria.crits);
293   return result ;
294
295 error:
296   free (criteria.crits);
297   return false ;
298 }
299
300 /* Hardly a rank function !! */
301 static double
302 rank_n (double c UNUSED, double cc UNUSED, double cc_1 UNUSED,
303           int i UNUSED, double w)
304 {
305   return w;
306 }
307
308
309 static double
310 rank_rank (double c, double cc, double cc_1,
311           int i, double w UNUSED)
312 {
313   double rank;
314   if ( c >= 1.0 )
315     {
316       switch (cmd.ties)
317         {
318         case RANK_LOW:
319           rank = cc_1 + 1;
320           break;
321         case RANK_HIGH:
322           rank = cc;
323           break;
324         case RANK_MEAN:
325           rank = cc_1 + (c + 1.0)/ 2.0;
326           break;
327         case RANK_CONDENSE:
328           rank = i;
329           break;
330         default:
331           NOT_REACHED ();
332         }
333     }
334   else
335     {
336       switch (cmd.ties)
337         {
338         case RANK_LOW:
339           rank = cc_1;
340           break;
341         case RANK_HIGH:
342           rank = cc;
343           break;
344         case RANK_MEAN:
345           rank = cc_1 + c / 2.0 ;
346           break;
347         case RANK_CONDENSE:
348           rank = i;
349           break;
350         default:
351           NOT_REACHED ();
352         }
353     }
354
355   return rank;
356 }
357
358
359 static double
360 rank_rfraction (double c, double cc, double cc_1,
361                 int i, double w)
362 {
363   return rank_rank (c, cc, cc_1, i, w) / w ;
364 }
365
366
367 static double
368 rank_percent (double c, double cc, double cc_1,
369                 int i, double w)
370 {
371   return rank_rank (c, cc, cc_1, i, w) * 100.0 / w ;
372 }
373
374
375 static double
376 rank_proportion (double c, double cc, double cc_1,
377                  int i, double w)
378 {
379   const double r =  rank_rank (c, cc, cc_1, i, w) ;
380
381   double f;
382
383   switch ( cmd.fraction )
384     {
385     case RANK_BLOM:
386       f =  (r - 3.0/8.0) / (w + 0.25);
387       break;
388     case RANK_RANKIT:
389       f = (r - 0.5) / w ;
390       break;
391     case RANK_TUKEY:
392       f = (r - 1.0/3.0) / (w + 1.0/3.0);
393       break;
394     case RANK_VW:
395       f = r / ( w + 1.0);
396       break;
397     default:
398       NOT_REACHED ();
399     }
400
401
402   return (f > 0) ? f : SYSMIS;
403 }
404
405 static double
406 rank_normal (double c, double cc, double cc_1,
407              int i, double w)
408 {
409   double f = rank_proportion (c, cc, cc_1, i, w);
410
411   return gsl_cdf_ugaussian_Pinv (f);
412 }
413
414 static double
415 rank_ntiles (double c, double cc, double cc_1,
416                 int i, double w)
417 {
418   double r = rank_rank (c, cc, cc_1, i, w);
419
420
421   return ( floor (( r * k_ntiles) / ( w + 1) ) + 1);
422 }
423
424 /* Expected value of the order statistics from an exponential distribution */
425 static double
426 ee (int j, double w_star)
427 {
428   int k;
429   double sum = 0.0;
430
431   for (k = 1 ; k <= j; k++)
432     sum += 1.0 / ( w_star + 1 - k );
433
434   return sum;
435 }
436
437
438 static double
439 rank_savage (double c, double cc, double cc_1,
440                 int i UNUSED, double w)
441 {
442   double int_part;
443   const int i_1 = floor (cc_1);
444   const int i_2 = floor (cc);
445
446   const double w_star = (modf (w, &int_part) == 0 ) ? w : floor (w) + 1;
447
448   const double g_1 = cc_1 - i_1;
449   const double g_2 = cc - i_2;
450
451   /* The second factor is infinite, when the first is zero.
452      Therefore, evaluate the second, only when the first is non-zero */
453   const double expr1 =  (1 - g_1) ? (1 - g_1) * ee(i_1+1, w_star) : ( 1 - g_1);
454   const double expr2 =  g_2 ? g_2 * ee (i_2+1, w_star) : g_2 ;
455
456   if ( i_1 == i_2 )
457     return ee (i_1 + 1, w_star) - 1;
458
459   if ( i_1 + 1 == i_2 )
460     return ( ( expr1 + expr2 )/c ) - 1;
461
462   if ( i_1 + 2 <= i_2 )
463     {
464       int j;
465       double sigma = 0.0;
466       for (j = i_1 + 2 ; j <= i_2; ++j )
467         sigma += ee (j, w_star);
468       return ( (expr1 + expr2 + sigma) / c) -1;
469     }
470
471   NOT_REACHED();
472 }
473
474
475 /* Rank the casefile belonging to CR, starting from the current
476    postition of CR continuing up to and including the ENDth case.
477
478    RS points to an array containing  the rank specifications to
479    use. N_RANK_SPECS is the number of elements of RS.
480
481
482    DEST_VAR_INDEX is the index into the rank_spec destvar element
483    to be used for this ranking.
484
485    Prerequisites: 1. The casefile must be sorted according to CRITERION.
486                   2. W is the sum of the non-missing caseweights for this
487                   range of the casefile.
488 */
489 static void
490 rank_cases (struct casereader *cr,
491             unsigned long end,
492             const struct dictionary *dict,
493             const struct sort_criterion *criterion,
494             const struct missing_values *mv,
495             double w,
496             const struct rank_spec *rs,
497             int n_rank_specs,
498             int dest_var_index,
499             struct casefile *dest)
500 {
501   bool warn = true;
502   double cc = 0.0;
503   double cc_1;
504   int iter = 1;
505
506   const int fv = criterion->fv;
507   const int width = criterion->width;
508
509   while (casereader_cnum (cr) < end)
510     {
511       struct casereader *lookahead;
512       const union value *this_value;
513       bool this_value_is_missing;
514       struct ccase this_case, lookahead_case;
515       double c;
516       int i;
517       size_t n = 0;
518
519       if (!casereader_read_xfer (cr, &this_case))
520         break;
521
522       this_value = case_data_idx (&this_case, fv);
523       this_value_is_missing = mv_is_value_missing (mv, this_value,
524                                                    exclude_values);
525       c = dict_get_case_weight (dict, &this_case, &warn);
526
527       lookahead = casereader_clone (cr);
528       n = 0;
529       while (casereader_cnum (lookahead) < end
530              && casereader_read_xfer (lookahead, &lookahead_case))
531         {
532           const union value *lookahead_value = case_data_idx (&lookahead_case, fv);
533           int diff = compare_values (this_value, lookahead_value, width);
534
535           if (diff != 0)
536             {
537               /* Make sure the casefile was sorted */
538               assert ( diff == ((criterion->dir == SRT_ASCEND) ? -1 :1));
539
540               case_destroy (&lookahead_case);
541               break;
542             }
543
544           c += dict_get_case_weight (dict, &lookahead_case, &warn);
545           case_destroy (&lookahead_case);
546           n++;
547         }
548       casereader_destroy (lookahead);
549
550       cc_1 = cc;
551       if ( !this_value_is_missing )
552         cc += c;
553
554       do
555         {
556           for (i = 0; i < n_rank_specs; ++i)
557             {
558               const struct variable *dst_var = rs[i].destvars[dest_var_index];
559
560               if  (this_value_is_missing)
561                 case_data_rw (&this_case, dst_var)->f = SYSMIS;
562               else
563                 case_data_rw (&this_case, dst_var)->f =
564                   rank_func[rs[i].rfunc](c, cc, cc_1, iter, w);
565             }
566           casefile_append_xfer (dest, &this_case);
567         }
568       while (n-- > 0 && casereader_read_xfer (cr, &this_case));
569
570       if ( !this_value_is_missing )
571         iter++;
572     }
573
574   /* If this isn't true, then all the results will be wrong */
575   assert ( w == cc );
576 }
577
578 static bool
579 same_group (const struct ccase *a, const struct ccase *b,
580             const struct sort_criteria *crit)
581 {
582   size_t i;
583
584   for (i = 0; i < crit->crit_cnt - 1; i++)
585     {
586       struct sort_criterion *c = &crit->crits[i];
587       if (compare_values (case_data_idx (a, c->fv),
588                           case_data_idx (b, c->fv), c->width) != 0)
589         return false;
590     }
591
592   return true;
593 }
594
595 static struct casefile *
596 rank_sorted_casefile (struct casefile *cf,
597                       const struct sort_criteria *crit,
598                       const struct dictionary *dict,
599                       const struct rank_spec *rs,
600                       int n_rank_specs,
601                       int dest_idx,
602                       const struct missing_values *mv)
603 {
604   struct casefile *dest = fastfile_create (casefile_get_value_cnt (cf));
605   struct casereader *lookahead = casefile_get_reader (cf, NULL);
606   struct casereader *pos = casereader_clone (lookahead);
607   struct ccase group_case;
608   bool warn = true;
609
610   struct sort_criterion *ultimate_crit = &crit->crits[crit->crit_cnt - 1];
611
612   if (casereader_read (lookahead, &group_case))
613     {
614       struct ccase this_case;
615       const union value *this_value ;
616       double w = 0.0;
617       this_value = case_data_idx( &group_case, ultimate_crit->fv);
618
619       if ( !mv_is_value_missing (mv, this_value, exclude_values) )
620         w = dict_get_case_weight (dict, &group_case, &warn);
621
622       while (casereader_read (lookahead, &this_case))
623         {
624           const union value *this_value =
625             case_data_idx(&this_case, ultimate_crit->fv);
626           double c = dict_get_case_weight (dict, &this_case, &warn);
627           if (!same_group (&group_case, &this_case, crit))
628             {
629               rank_cases (pos, casereader_cnum (lookahead) - 1,
630                           dict,
631                           ultimate_crit,
632                           mv, w,
633                           rs, n_rank_specs,
634                           dest_idx, dest);
635
636               w = 0.0;
637               case_destroy (&group_case);
638               case_move (&group_case, &this_case);
639             }
640           if ( !mv_is_value_missing (mv, this_value, exclude_values) )
641             w += c;
642           case_destroy (&this_case);
643         }
644       case_destroy (&group_case);
645       rank_cases (pos, ULONG_MAX, dict, ultimate_crit, mv, w,
646                   rs, n_rank_specs, dest_idx, dest);
647     }
648
649   if (casefile_error (dest))
650     {
651       casefile_destroy (dest);
652       dest = NULL;
653     }
654
655   casefile_destroy (cf);
656   return dest;
657 }
658
659
660 /* Transformation function to enumerate all the cases */
661 static int
662 create_resort_key (void *key_var_, struct ccase *cc, casenumber case_num)
663 {
664   struct variable *key_var = key_var_;
665
666   case_data_rw(cc, key_var)->f = case_num;
667
668   return TRNS_CONTINUE;
669 }
670
671
672 /* Create and return a new variable in which to store the ranks of SRC_VAR
673    accoring to the rank function F.
674    VNAME is the name of the variable to be created.
675    If VNAME is NULL, then a name will be automatically chosen.
676  */
677 static struct variable *
678 create_rank_variable (struct dictionary *dict, enum RANK_FUNC f,
679                       const struct variable *src_var,
680                       const char *vname)
681 {
682   int i;
683   struct variable *var = NULL;
684   char name[SHORT_NAME_LEN + 1];
685
686   if ( vname )
687     var = dict_create_var(dict, vname, 0);
688
689   if ( NULL == var )
690     {
691       snprintf (name, SHORT_NAME_LEN + 1, "%c%s",
692                 function_name[f][0], var_get_name (src_var));
693
694       var = dict_create_var(dict, name, 0);
695     }
696
697   i = 1;
698   while( NULL == var )
699     {
700       char func_abb[4];
701       snprintf(func_abb, 4, "%s", function_name[f]);
702       snprintf(name, SHORT_NAME_LEN + 1, "%s%03d", func_abb,
703                i);
704
705       var = dict_create_var(dict, name, 0);
706       if (i++ >= 999)
707         break;
708     }
709
710   i = 1;
711   while ( NULL == var )
712     {
713       char func_abb[3];
714       snprintf(func_abb, 3, "%s", function_name[f]);
715
716       snprintf(name, SHORT_NAME_LEN + 1,
717                "RNK%s%02d", func_abb, i);
718
719       var = dict_create_var(dict, name, 0);
720       if ( i++ >= 99 )
721         break;
722     }
723
724   if ( NULL == var )
725     {
726       msg(ME, _("Cannot create new rank variable.  All candidates in use."));
727       return NULL;
728     }
729
730   var_set_both_formats (var, &dest_format[f]);
731
732   return var;
733 }
734
735
736 static void
737 rank_cleanup(void)
738 {
739   int i;
740
741   free (group_vars);
742   group_vars = NULL;
743   n_group_vars = 0;
744
745   for (i = 0 ; i <  n_rank_specs ; ++i )
746       free (rank_specs[i].destvars);
747
748   free (rank_specs);
749   rank_specs = NULL;
750   n_rank_specs = 0;
751
752   sort_destroy_criteria (sc);
753   sc = NULL;
754
755   free (src_vars);
756   src_vars = NULL;
757   n_src_vars = 0;
758 }
759
760 int
761 cmd_rank (struct lexer *lexer, struct dataset *ds)
762 {
763   bool result;
764   struct variable *order;
765   size_t i;
766   n_rank_specs = 0;
767
768   if ( !parse_rank (lexer, ds, &cmd, NULL) )
769     {
770       rank_cleanup ();
771     return CMD_FAILURE;
772     }
773
774   /* If /MISSING=INCLUDE is set, then user missing values are ignored */
775   exclude_values = cmd.miss == RANK_INCLUDE ? MV_SYSTEM : MV_ANY;
776
777   /* Default to /RANK if no function subcommands are given */
778   if ( !( cmd.sbc_normal  || cmd.sbc_ntiles || cmd.sbc_proportion ||
779           cmd.sbc_rfraction || cmd.sbc_savage || cmd.sbc_n ||
780           cmd.sbc_percent || cmd.sbc_rank ) )
781     {
782       assert ( n_rank_specs == 0 );
783
784       rank_specs = xmalloc (sizeof (*rank_specs));
785       rank_specs[0].rfunc = RANK;
786       rank_specs[0].destvars =
787         xcalloc (sc->crit_cnt, sizeof (struct variable *));
788
789       n_rank_specs = 1;
790     }
791
792   assert ( sc->crit_cnt == n_src_vars);
793
794   /* Create variables for all rank destinations which haven't
795      already been created with INTO.
796      Add labels to all the destination variables.
797   */
798   for (i = 0 ; i <  n_rank_specs ; ++i )
799     {
800       int v;
801       for ( v = 0 ; v < n_src_vars ;  v ++ )
802         {
803           if ( rank_specs[i].destvars[v] == NULL )
804             {
805               rank_specs[i].destvars[v] =
806                 create_rank_variable (dataset_dict(ds), rank_specs[i].rfunc, src_vars[v], NULL);
807             }
808
809           create_var_label ( rank_specs[i].destvars[v],
810                              src_vars[v],
811                              rank_specs[i].rfunc);
812         }
813     }
814
815   if ( cmd.print == RANK_YES )
816     {
817       int v;
818
819       tab_output_text (0, _("Variables Created By RANK"));
820       tab_output_text (0, "\n");
821
822       for (i = 0 ; i <  n_rank_specs ; ++i )
823         {
824           for ( v = 0 ; v < n_src_vars ;  v ++ )
825             {
826               if ( n_group_vars > 0 )
827                 {
828                   struct string varlist;
829                   int g;
830
831                   ds_init_empty (&varlist);
832                   for ( g = 0 ; g < n_group_vars ; ++g )
833                     {
834                       ds_put_cstr (&varlist, var_get_name (group_vars[g]));
835
836                       if ( g < n_group_vars - 1)
837                         ds_put_cstr (&varlist, " ");
838                     }
839
840                   if ( rank_specs[i].rfunc == NORMAL ||
841                        rank_specs[i].rfunc == PROPORTION )
842                     tab_output_text (TAT_PRINTF,
843                                      _("%s into %s(%s of %s using %s BY %s)"),
844                                      var_get_name (src_vars[v]),
845                                      var_get_name (rank_specs[i].destvars[v]),
846                                      function_name[rank_specs[i].rfunc],
847                                      var_get_name (src_vars[v]),
848                                      fraction_name(),
849                                      ds_cstr (&varlist)
850                                      );
851
852                   else
853                     tab_output_text (TAT_PRINTF,
854                                      _("%s into %s(%s of %s BY %s)"),
855                                      var_get_name (src_vars[v]),
856                                      var_get_name (rank_specs[i].destvars[v]),
857                                      function_name[rank_specs[i].rfunc],
858                                      var_get_name (src_vars[v]),
859                                      ds_cstr (&varlist)
860                                      );
861                   ds_destroy (&varlist);
862                 }
863               else
864                 {
865                   if ( rank_specs[i].rfunc == NORMAL ||
866                        rank_specs[i].rfunc == PROPORTION )
867                     tab_output_text (TAT_PRINTF,
868                                      _("%s into %s(%s of %s using %s)"),
869                                      var_get_name (src_vars[v]),
870                                      var_get_name (rank_specs[i].destvars[v]),
871                                      function_name[rank_specs[i].rfunc],
872                                      var_get_name (src_vars[v]),
873                                      fraction_name()
874                                      );
875
876                   else
877                     tab_output_text (TAT_PRINTF,
878                                      _("%s into %s(%s of %s)"),
879                                      var_get_name (src_vars[v]),
880                                      var_get_name (rank_specs[i].destvars[v]),
881                                      function_name[rank_specs[i].rfunc],
882                                      var_get_name (src_vars[v])
883                                      );
884                 }
885             }
886         }
887     }
888
889   if ( cmd.sbc_fraction &&
890        ( ! cmd.sbc_normal && ! cmd.sbc_proportion) )
891     msg(MW, _("FRACTION has been specified, but NORMAL and PROPORTION rank functions have not been requested.  The FRACTION subcommand will be ignored.") );
892
893   /* Add a variable which we can sort by to get back the original
894      order */
895   order = dict_create_var_assert (dataset_dict (ds), "$ORDER_", 0);
896
897   add_transformation (ds, create_resort_key, 0, order);
898
899   /* Do the ranking */
900   result = rank_cmd (ds, sc, rank_specs, n_rank_specs);
901
902   /* Put the active file back in its original order */
903   {
904     struct sort_criteria criteria;
905     struct sort_criterion restore_criterion ;
906     restore_criterion.fv = var_get_case_index (order);
907     restore_criterion.width = 0;
908     restore_criterion.dir = SRT_ASCEND;
909
910     criteria.crits = &restore_criterion;
911     criteria.crit_cnt = 1;
912
913     sort_active_file_in_place (ds, &criteria);
914   }
915
916   /* ... and we don't need our sort key anymore. So delete it */
917   dict_delete_var (dataset_dict (ds), order);
918
919   rank_cleanup();
920
921
922   return (result ? CMD_SUCCESS : CMD_CASCADING_FAILURE);
923 }
924
925
926 /* Parser for the variables sub command
927    Returns 1 on success */
928 static int
929 rank_custom_variables (struct lexer *lexer, struct dataset *ds, struct cmd_rank *cmd UNUSED, void *aux UNUSED)
930 {
931   static const int terminators[2] = {T_BY, 0};
932
933   lex_match (lexer, '=');
934
935   if ((lex_token (lexer) != T_ID || dict_lookup_var (dataset_dict (ds), lex_tokid (lexer)) == NULL)
936       && lex_token (lexer) != T_ALL)
937       return 2;
938
939   sc = sort_parse_criteria (lexer, dataset_dict (ds),
940                             &src_vars, &n_src_vars, 0, terminators);
941
942   if ( lex_match (lexer, T_BY)  )
943     {
944       if ((lex_token (lexer) != T_ID || dict_lookup_var (dataset_dict (ds), lex_tokid (lexer)) == NULL))
945         {
946           return 2;
947         }
948
949       if (!parse_variables (lexer, dataset_dict (ds),
950                             &group_vars, &n_group_vars,
951                             PV_NO_DUPLICATE | PV_NO_SCRATCH) )
952         {
953           free (group_vars);
954           return 0;
955         }
956     }
957
958   return 1;
959 }
960
961
962 /* Parse the [/rank INTO var1 var2 ... varN ] clause */
963 static int
964 parse_rank_function (struct lexer *lexer, struct dictionary *dict, struct cmd_rank *cmd UNUSED, enum RANK_FUNC f)
965 {
966   int var_count = 0;
967
968   n_rank_specs++;
969   rank_specs = xnrealloc(rank_specs, n_rank_specs, sizeof *rank_specs);
970   rank_specs[n_rank_specs - 1].rfunc = f;
971   rank_specs[n_rank_specs - 1].destvars = NULL;
972
973   rank_specs[n_rank_specs - 1].destvars =
974             xcalloc (sc->crit_cnt, sizeof (struct variable *));
975
976   if (lex_match_id (lexer, "INTO"))
977     {
978       struct variable *destvar;
979
980       while( lex_token (lexer) == T_ID )
981         {
982
983           if ( dict_lookup_var (dict, lex_tokid (lexer)) != NULL )
984             {
985               msg(SE, _("Variable %s already exists."), lex_tokid (lexer));
986               return 0;
987             }
988           if ( var_count >= sc->crit_cnt )
989             {
990               msg(SE, _("Too many variables in INTO clause."));
991               return 0;
992             }
993
994           destvar = create_rank_variable (dict, f, src_vars[var_count], lex_tokid (lexer));
995           rank_specs[n_rank_specs - 1].destvars[var_count] = destvar ;
996
997           lex_get (lexer);
998           ++var_count;
999         }
1000     }
1001
1002   return 1;
1003 }
1004
1005
1006 static int
1007 rank_custom_rank (struct lexer *lexer, struct dataset *ds, struct cmd_rank *cmd, void *aux UNUSED )
1008 {
1009   struct dictionary *dict = dataset_dict (ds);
1010
1011   return parse_rank_function (lexer, dict, cmd, RANK);
1012 }
1013
1014 static int
1015 rank_custom_normal (struct lexer *lexer, struct dataset *ds, struct cmd_rank *cmd, void *aux UNUSED )
1016 {
1017   struct  dictionary *dict = dataset_dict (ds);
1018
1019   return parse_rank_function (lexer, dict, cmd, NORMAL);
1020 }
1021
1022 static int
1023 rank_custom_percent (struct lexer *lexer, struct dataset *ds, struct cmd_rank *cmd, void *aux UNUSED )
1024 {
1025   struct dictionary *dict = dataset_dict (ds);
1026
1027   return parse_rank_function (lexer, dict, cmd, PERCENT);
1028 }
1029
1030 static int
1031 rank_custom_rfraction (struct lexer *lexer, struct dataset *ds, struct cmd_rank *cmd, void *aux UNUSED )
1032 {
1033   struct dictionary *dict = dataset_dict (ds);
1034
1035   return parse_rank_function (lexer, dict, cmd, RFRACTION);
1036 }
1037
1038 static int
1039 rank_custom_proportion (struct lexer *lexer, struct dataset *ds, struct cmd_rank *cmd, void *aux UNUSED )
1040 {
1041   struct dictionary *dict = dataset_dict (ds);
1042
1043   return parse_rank_function (lexer, dict, cmd, PROPORTION);
1044 }
1045
1046 static int
1047 rank_custom_n (struct lexer *lexer, struct dataset *ds, struct cmd_rank *cmd, void *aux UNUSED )
1048 {
1049   struct dictionary *dict = dataset_dict (ds);
1050
1051   return parse_rank_function (lexer, dict, cmd, N);
1052 }
1053
1054 static int
1055 rank_custom_savage (struct lexer *lexer, struct dataset *ds, struct cmd_rank *cmd, void *aux UNUSED )
1056 {
1057   struct dictionary *dict = dataset_dict (ds);
1058
1059   return parse_rank_function (lexer, dict, cmd, SAVAGE);
1060 }
1061
1062
1063 static int
1064 rank_custom_ntiles (struct lexer *lexer, struct dataset *ds, struct cmd_rank *cmd, void *aux UNUSED )
1065 {
1066   struct dictionary *dict = dataset_dict (ds);
1067
1068   if ( lex_force_match (lexer, '(') )
1069     {
1070       if ( lex_force_int (lexer) )
1071         {
1072           k_ntiles = lex_integer (lexer);
1073           lex_get (lexer);
1074           lex_force_match (lexer, ')');
1075         }
1076       else
1077         return 0;
1078     }
1079   else
1080     return 0;
1081
1082   return parse_rank_function (lexer, dict, cmd, NTILES);
1083 }