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