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