# Ranking a vector containing ties

How can I rank a vector such that the ties are replaced by their middle ranks. For example, {1, 2, 2, 3}. I want to rank this vector but the ties must be replaced by their mid-rank; i.e., given {1, 2, 2, 3}, I want to get the vector {1, 2.5, 2.5, 4}. Does there exist any fast command or function to do this?

list = {1, 2, 2, 3};

(Ordering@Ordering@# + Reverse@Ordering@Ordering@Reverse@#)/2 &@list


{1, 5/2, 5/2, 4}

As requested, here as a function:

rank[list_] := (Ordering@Ordering@list + Reverse@Ordering@Ordering@Reverse@list)/2

• Nice, ~4x more faster than mine! – István Zachar Sep 28 '13 at 11:57
• @IstvánZachar Thanks! Indeed, Ordering is a much underestimated function with lots of hidden power. It is very useful for fast, partial sorts, for instance. – Sjoerd C. de Vries Sep 28 '13 at 12:04
• @ S.C. de Vries: Can you increase its speed by putting in the form of proper function named Rank[x_]:= ? – Abdul Haq Sep 28 '13 at 12:07
• @AbdulHaq I've added a function definition. As far as I could see there is so speed increase (not that I expected it). – Sjoerd C. de Vries Sep 28 '13 at 12:16
• That's pretty darn clever. +1 – Mr.Wizard Sep 28 '13 at 14:04

Both methods assume continuous sublists of identical elements, i.e., an already sorted vector. Gather is ~10x faster than Union for larger lists.

x = {1, 2, 2, 3};
f1[x_] := x /. (# -> Mean @ Flatten@Position[x, #] & /@ Union@x);
f2[x_] :=
Module[{i = 1}, x /. ((First @ # -> (i + (i = i + Length @ #) - 1)/2) & /@ Gather@x)];

{f1@x, f2@x}

 {{1, 5/2, 5/2, 4}, {1, 5/2, 5/2, 4}}

y = Sort@RandomInteger[{0, 100}, {100000}];
AbsoluteTiming[r1 = f1@y;]
AbsoluteTiming[r2 = f2@y;]
r1 === r2

{2.090404, Null}
{0.327601, Null}
True

• @ István Zachar: Indeed both functions are acceptable depending on time constraints. I really appreciate the effort. – Abdul Haq Sep 28 '13 at 12:10
StatisticsLibraryGetDataRankings[{1, 2, 2, 3}]


{1, 5/2, 5/2, 4}

This is slower than Istvan's f2 but faster than his f1. @Sjoerd's rank is the fastest of the four. With

gdr = StatisticsLibraryGetDataRankings


and using Istvan's setup for timings:

y = Sort@RandomInteger[{0, 100}, {1000000}];
t1 = First@AbsoluteTiming[r1 = f1 @ y;]; (* istvan's answer *)
t2 = First@AbsoluteTiming[r2 = f2 @ y;]; (* istvan's answer *)
t3 = First@AbsoluteTiming[r3 = gdr @ y;];
t4 = First@AbsoluteTiming[r4 = rank@y;]; (* sjoerd's answer *)

r1 === r2 === r3 === r4


True

Grid[Transpose@SortBy[Transpose[{{"f1", "f2", "gdr", "rank"}, {t1, t2, t3, t4}}], Last],
Dividers -> All]


$\begin{array}{|c|c|c|c|} \hline \text{rank} & \text{f2} & \text{gdr} & \text{f1} \\ \hline 0.687670 & 1.440178 & 3.297210 & 8.324320 \\ \hline \end{array}$