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