Is there a technique that would allow ranking each row of a matrix without looping?
So the returned matrix would have within-row ranks.
For example if A held all non-missing numeric data, then imaginary function rowRank(A) would return values 1:ncol(A) in each row.
a = {1 3 10, 6 9 5};
print a ,,
(rank(a))[l="Ranks from rank(a)"] ,,
({1 2 3, 2 3 1})[l="Desired Ranks (row-wise ranks)"];
A
1 3 10
6 9 5
Ranks from rank(a)
1 2 6
4 5 3
Desired Ranks (row-wise ranks)
1 2 3
2 3 1
I think this can be done with a two pass solution. The initial rank matrix needs to be adjusted so that each row has values that are larger than the row before, then the second ranking is operating within row. For example:
proc iml;
a = {1 3 10, 6 9 5};
print a;
b = row(a) - 1;
r = rank( rank(a) + nrow(a) # ncol(a) # b ) - ncol(a) # b;
print r;
quit;
The ranks given to second row seems to be wrong. I think it has to be 3 1 2. I have less expertise in IML. Do you accept a Data Step solution?
The steps include the sorting of each row by using SORTN() function and at the same time switching the index of the array. Then re-ordered index will be the rank. SMALLEST() takes care of the sorting in the background and returns
the i-th smallest value. Using WHICHN() with the i-th smallest value gives the desired Rank.
Note, no attempt is made to check for ties.
%let vn = 3;
data rank;
set A;
array x x1-x&vn;
array R R1 - R&vn;
do i = 1 to dim(x);
R[i] = whichN(smallest(i, of x[*]), of x[*]);
put R[i] =;
end;
keep R:;
run;
Great ideas -- thank you. The "r2" array in DO loop below is what I have been using.
proc iml;
a = {2 4 6, 15 11 13, 29 28 27};
r1 = rank(a);
r2 = j(nrow(a), ncol(a), .);
do i = 1 to nrow(a);
r2[i,] = rank(a[i,]);
end;
print a ,, r1[l="Rank(a) - whole matrix - don't want"] ,, r2[l="Desired 'Row Ranks' from looping rank(row-by-row)"];
quit;
Ian is a master at these manipulation tricks!
I think you can make a small improvement to his idea to make it more efficient. This computation requires only a translation, a rank, and another translation.
proc iml;
a = {2 4 6,
15 11 13,
29 28 27,
2 4 6,
4 1 6,
2 6 4,
6 4 2 };
maxDiff = range(a) + 1; /* largest difference between elements */
b = a + maxDiff*row(a); /* uniquify each row */
r = rank(b) - ncol(a)* T(0:(nrow(a)-1));
print r;
I like your modification Rick, surely the way to go if the OP is looking for an IML solution.
If your matrix is in the form of a SAS data set, it is a simple 3 step process and 7 statements (including 3 RUN statements):
1. Proc TRANSPOSE.
2. Proc RANK.
3. Proc TRANSPOSE.
Kind regards
Paul D.
Had no SAS access on the first response, but now that I do and can test, consider:
data have ;
input v1-v3 ;
cards ;
6 9 5
1 3 10
0 0 0
0 0 1
0 1 1
;
run ;
proc transpose data = have out = t (drop = _:) ;
run ;
proc rank data = t out = r ;
var col: ;
run ;
proc transpose data = r out = want (drop=_:) prefix=v ;
run ;
Result:
v1 v2 v3 ----------------- 2.0 3.0 1.0 1.0 2.0 3.0 2.0 2.0 2.0 1.5 1.5 3.0 1.0 2.5 2.5
Plus, you have all the bells and whistles of proc RANK to handle the ties differently, should you wish.
Kind regards
Paul D.
If the OP wants to control how to rank tied values, the RANKTIE function in SAS/IML supports exactly the same methods as PROC RANK.
Join us for SAS Innovate 2025, our biggest and most exciting global event of the year, in Orlando, FL, from May 6-9. Sign up by March 14 for just $795.
Learn how to run multiple linear regression models with and without interactions, presented by SAS user Alex Chaplin.
Find more tutorials on the SAS Users YouTube channel.