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