BookmarkSubscribeRSS Feed
☑ This topic is solved. Need further help from the community? Please sign in and ask a new question.
WeiChen
Obsidian | Level 7
I want shift numbres in each row of matrux.  if row is {1 2 3 4} and shift amount is 1, I want row to be {4 1 2 3}. Numbers wrap back when they poke off end. Here bigger example. (Real matrix have > 1000 rows!)
 
 
PROC IML;
A = {1 2 3 4,
     5 6 7 8,
     9 0 1 2} ;
v = {1,3,2} ;
 
/* want shift rows of A by the numbers in v. If pokes out endf wrap on other side */
Want = {4 1 2 3     /* shift by 1 */
        6 7 8 5     /* shift by 3 */
        1 2 9 0} ;  /* shift by 2 */
QUIT;
 
1 ACCEPTED SOLUTION

Accepted Solutions
Rick_SAS
SAS Super FREQ

It sounds like the OP is requesting a CYCLIC shift of each row. A cyclic shift can be accomplished by using the MOD function as follows. Let p=NCOL(A).

  • Create a vector that has the indices 1:p
  • Shift the indices to the right: j ->  p - v[i] + j
  • The MOD function works best for 0-based indices, whereas IML uses 1-based indices. So subtract 1 to get a 0-based index, apply the (MOD p) operation, then add 1 back to return to 1-based indices.

For example, the OP's example, has p=4, so you can use the following to create the shift for the i_th row:

   idx = 1:p;
   shiftIdx = p - v[i] + idx;          * shift to the right ;
   newIdx = mod(shiftIdx-1, p) + 1;    * wrap around ;

Here's how it looks being applied to each row:

/* cyclic rotation of row elements */
proc iml;
A = {1 2 3 4, 
     5 6 7 8, 
     9 0 1 2} ;
v = {1,3,2} ;
p = ncol(A);
idx = 1:p;

/* Demonstrate using the MOD function to shift the indices */
/*
do i = 1 to nrow(v);
   shiftIdx = p - v[i] + idx;          * shift to the right ;
   newIdx = mod(shiftIdx-1, p) + 1;    * wrap around ;
   print (v[i])[L='shift'], newIdx[c=('c1':'c4')];
end;
*/

/* Solve the real problem: use MOD function to apply a cyclic shift of row A[i,] 
   by the number of columns in v[i] */
ShiftA = j(nrow(A), p, .);  /* allocate */
do i = 1 to nrow(A);
   shiftIdx = p - v[i] + idx;      /* shift to the right */
   newIdx = mod(shiftIdx-1, p) + 1; /* wrap around */
   *print i adjIdx;
   ShiftA[i,] = A[i, newIdx];
end;

print ShiftA;

 

View solution in original post

13 REPLIES 13
yabwon
Amethyst | Level 16

How about something like this for start:

PROC IML;
A = {1 2 3 4,
     5 6 7 8,
     9 0 1 2} ;
v = {1,3,2} ;

r=nrow(A);
c=ncol(A);
  do i = 1 to r*(max(v) <= c);
    
    x = a[i,(c-v[i])+1:c] || a[i,1:(c-v[i])];
    B = B // x;
  end;

print B;

quit;

?

Bart

_______________
Polish SAS Users Group: www.polsug.com and communities.sas.com/polsug

"SAS Packages: the way to share" at SGF2020 Proceedings (the latest version), GitHub Repository, and YouTube Video.
Hands-on-Workshop: "Share your code with SAS Packages"
"My First SAS Package: A How-To" at SGF2021 Proceedings

SAS Ballot Ideas: one: SPF in SAS, two, and three
SAS Documentation



yabwon
Amethyst | Level 16

Alternative with the insert() function:

PROC IML;
A = {1 2 3 4,
     5 6 7 8,
     9 0 1 2} ;
v = {1,3,2} ;

r=nrow(A);
c=ncol(A);

B=j(1,c,.);
  do i = 1 to r*(max(v) <= c);
    x = a[i,(c-v[i])+1:c] || a[i,1:(c-v[i])];
    B = insert(B, x, i, 0);
  end;
B = B[1:r,];
print B;

quit;

 

[EDIT:] 

Or with the shape() function:

PROC IML;
A = {1 2 3 4,
     5 6 7 8,
     9 0 1 2} ;
v = {1,3,2} ;

r=nrow(A);
c=ncol(A);

do i = 1 to r*(max(v) <= c);
  B = B || a[i,(c-v[i])+1:c] || a[i,1:(c-v[i])];
end;

B = shape(B,r,c);
print B;

quit;

 

[EDIT 2:]

Or with just proper sub-scripting:

PROC IML;
A = {1 2 3 4,
     5 6 7 8,
     9 0 1 2} ;
v = {1,3,2} ;

r=nrow(A);
c=ncol(A);

B=j(r,c,.);

do i = 1 to r*(max(v) <= c);
  B[i,1:c] = a[i,(c-v[i])+1:c] || a[i,1:(c-v[i])];
end;

print B;

quit;

 

Bart

_______________
Polish SAS Users Group: www.polsug.com and communities.sas.com/polsug

"SAS Packages: the way to share" at SGF2020 Proceedings (the latest version), GitHub Repository, and YouTube Video.
Hands-on-Workshop: "Share your code with SAS Packages"
"My First SAS Package: A How-To" at SGF2021 Proceedings

SAS Ballot Ideas: one: SPF in SAS, two, and three
SAS Documentation



Ksharp
Super User
PROC IML;
A = {1 2 3 4,
     5 6 7 8,
     9 0 1 2} ;
v = {1,3,2} ;

r=nrow(A);
c=ncol(A);
idx=c-v+1;
AA=A||A;  
want=j(r,c,.);
do i=1 to r;
 want[i,]=AA[i,idx[i]:idx[i]+c-1];
end;
print want;
quit;
yabwon
Amethyst | Level 16

@Ksharp thanks for reminding about the AA=A||A trick!

 

Now it can be done "loop-less" with "one" expression (see below):

PROC IML;
A = {1 2 3 4,
     5 6 7 8,
     9 0 1 2} ;
v = {1,3,2} ;

r=nrow(A);
c=ncol(A);

/* step by step */
AA=A||A;  
x=1:2*c;
y1 = repeat(x, r, 1);
y2 = y1 + (c-v);
y3 = y2[,1:c];
y4 = y3 + t(0:r-1)*c*2;
print x,y1,y2,y3,AA,y4;
want = shape(AA[y4],r);
print want;

/* one expression - in Klingons language */
want2 = shape((A||A)[(repeat(1:2*c,r,1)+(c-v))[,1:c]+t(0:r-1)*c*2],r);
print want2;

quit;

Output:

yabwon_0-1763892284478.png

 

All the best

Bart

 

 

_______________
Polish SAS Users Group: www.polsug.com and communities.sas.com/polsug

"SAS Packages: the way to share" at SGF2020 Proceedings (the latest version), GitHub Repository, and YouTube Video.
Hands-on-Workshop: "Share your code with SAS Packages"
"My First SAS Package: A How-To" at SGF2021 Proceedings

SAS Ballot Ideas: one: SPF in SAS, two, and three
SAS Documentation



yabwon
Amethyst | Level 16

@Ksharp , even shorter:

PROC IML;
A = {1 2 3 4,
     5 6 7 8,
     9 0 1 2} ;
v = {1,3,2} ;

r=nrow(A);
c=ncol(A);

/* step by step */
AA=A||A; /* trick by @Ksharp */  
x=1:2*r*c;
y1=shape(x,r);
y2=(y1+(c-v))[,1:c];
print x,y1,y2;
want3 = shape(AA[y2],r);
print want3;

/* one expression - in less Klingons language */
want4 = shape((A||A)[(shape(1:2*r*c,r)+(c-v))[,1:c]],r);
print want4;
quit;

Output:

yabwon_0-1763893078566.png

 

 

Bart

_______________
Polish SAS Users Group: www.polsug.com and communities.sas.com/polsug

"SAS Packages: the way to share" at SGF2020 Proceedings (the latest version), GitHub Repository, and YouTube Video.
Hands-on-Workshop: "Share your code with SAS Packages"
"My First SAS Package: A How-To" at SGF2021 Proceedings

SAS Ballot Ideas: one: SPF in SAS, two, and three
SAS Documentation



yabwon
Amethyst | Level 16

Can't help myself 😛 😛

 

PROC IML;
A = {1 2 3 4,
     5 6 7 8,
     9 0 1 2} ;
v = {1,3,2} ;

r=nrow(A);
c=ncol(A);

/* step by step */
AA=A||A; /* trick by @Ksharp */  
v1 = SHAPE(1:2*r*c,r);
v2 = REPEAT(c-v, 1, 2*c); 
v3 = (v2+v1)[,1:c];
want5 = shape(AA[v3],r);
print AA,v,v1,v2,v3,want5;

/* one expression - in yet another Klingons language */
want6 = shape((A||A)[(REPEAT(c-v,1,2*c)+SHAPE(1:2*r*c,r))[,1:c]],r);
print want6;

quit;

Output:

yabwon_0-1763894010788.png

 

 

 

 

 

Bart

_______________
Polish SAS Users Group: www.polsug.com and communities.sas.com/polsug

"SAS Packages: the way to share" at SGF2020 Proceedings (the latest version), GitHub Repository, and YouTube Video.
Hands-on-Workshop: "Share your code with SAS Packages"
"My First SAS Package: A How-To" at SGF2021 Proceedings

SAS Ballot Ideas: one: SPF in SAS, two, and three
SAS Documentation



Rick_SAS
SAS Super FREQ

It sounds like the OP is requesting a CYCLIC shift of each row. A cyclic shift can be accomplished by using the MOD function as follows. Let p=NCOL(A).

  • Create a vector that has the indices 1:p
  • Shift the indices to the right: j ->  p - v[i] + j
  • The MOD function works best for 0-based indices, whereas IML uses 1-based indices. So subtract 1 to get a 0-based index, apply the (MOD p) operation, then add 1 back to return to 1-based indices.

For example, the OP's example, has p=4, so you can use the following to create the shift for the i_th row:

   idx = 1:p;
   shiftIdx = p - v[i] + idx;          * shift to the right ;
   newIdx = mod(shiftIdx-1, p) + 1;    * wrap around ;

Here's how it looks being applied to each row:

/* cyclic rotation of row elements */
proc iml;
A = {1 2 3 4, 
     5 6 7 8, 
     9 0 1 2} ;
v = {1,3,2} ;
p = ncol(A);
idx = 1:p;

/* Demonstrate using the MOD function to shift the indices */
/*
do i = 1 to nrow(v);
   shiftIdx = p - v[i] + idx;          * shift to the right ;
   newIdx = mod(shiftIdx-1, p) + 1;    * wrap around ;
   print (v[i])[L='shift'], newIdx[c=('c1':'c4')];
end;
*/

/* Solve the real problem: use MOD function to apply a cyclic shift of row A[i,] 
   by the number of columns in v[i] */
ShiftA = j(nrow(A), p, .);  /* allocate */
do i = 1 to nrow(A);
   shiftIdx = p - v[i] + idx;      /* shift to the right */
   newIdx = mod(shiftIdx-1, p) + 1; /* wrap around */
   *print i adjIdx;
   ShiftA[i,] = A[i, newIdx];
end;

print ShiftA;

 

yabwon
Amethyst | Level 16

@Rick_SAS , vectorised version:

proc iml;
A = {1 2 3 4, 
     5 6 7 8, 
     9 0 1 2} ;
v = {1,3,2} ;

c = ncol(A);
r = nrow(A);
idx = repeat(1:c,r);
row = t(0:r-1)*c;
print idx, row;

/* vectorised */
shiftIdx = c - v + idx;
print shiftIdx;
newIdx = mod(shiftIdx-1, c) + 1; 

want = shape(A[newIdx + row], r);
print want;

/* one expression */
want2 = shape(A[(mod((c - v + idx)-1, c) + 1) + t(0:r-1)*c], r);
print want2;

quit;

Output:

yabwon_0-1763897544185.png

 

 

Bart

_______________
Polish SAS Users Group: www.polsug.com and communities.sas.com/polsug

"SAS Packages: the way to share" at SGF2020 Proceedings (the latest version), GitHub Repository, and YouTube Video.
Hands-on-Workshop: "Share your code with SAS Packages"
"My First SAS Package: A How-To" at SGF2021 Proceedings

SAS Ballot Ideas: one: SPF in SAS, two, and three
SAS Documentation



Rick_SAS
SAS Super FREQ

Yes, and you can use the COL function to simplify the creation of the matrix for which M[i,j]=j.

idx = col(A);

The rest of your program uses a formula to convert between subscripts and indices. The NDX2SUB and SUB2NDX functions incorporate this functionality and are sometimes useful in situations like this.

Rick_SAS
SAS Super FREQ

@yabwon BTW, a cyclic shift is one kind of a permutation. If the OP were interested in more general permutations, they could use the ideas in the article Permute elements within each row of a matrix - The DO Loop, which shows how to apply a random permutation to each row of a matrix in a vectorized manner.  Using the program in that article, I suspect you can write a new function called CyclicPerm and replace the call to RANPERM. 

WeiChen
Obsidian | Level 7

Thank you every body who respond. I like MOD function. very elgelant. Also like vectorize program. Need to make timeing to see if makes differnce. My matrix have 15 column and 10.000 row.

@yabwon Are you learning IML? I see you answer other forums like macro and dataq step, but not often IML.

yabwon
Amethyst | Level 16

If there's a question I try to help. I don't distinguish "forums". They all the same SAS programming questions to me.

 

Bart

_______________
Polish SAS Users Group: www.polsug.com and communities.sas.com/polsug

"SAS Packages: the way to share" at SGF2020 Proceedings (the latest version), GitHub Repository, and YouTube Video.
Hands-on-Workshop: "Share your code with SAS Packages"
"My First SAS Package: A How-To" at SGF2021 Proceedings

SAS Ballot Ideas: one: SPF in SAS, two, and three
SAS Documentation



Rick_SAS
SAS Super FREQ

For 10,000 rows,  both the loop and the vectorized methods have approximately the same run time. On my laptop, the looping method takes 0.007 s and the vectorized method takes 0.004 s.  On the one hand, the vectorized method is almost twice as fast. On the other hand, you save only 0.003 s by using it. 

 

If your matrix has 100,000 or more rows, then the performance comparison is:

  • 100,000 rows: the looping method takes 0.07 s and the vectorized method takes 0.04 s. 
  • 1,000,000 rows: the looping method takes 0.7 s and the vectorized method takes 0.4 s. 

Here is the program I use, so you can run your own experiments:

/* Rick's original version: Loop over rows and apply each shift */
%let NCOL = 15;
%let NROW = 1000000;

proc iml;
nr = &NROW;
p = &NCOL;
A = shape(1:(nr*p), nr, p);

call randseed(1234);
v = randfun(nr, "Uniform", 0, p-1);  /* create shift vector */
nRep = 10;    /* repeat the experiment nRep times and report average */

/* use MOD function to apply a cyclic shift of row A[i,] 
   by the number of columns in v[i] */
t0 = time();
do rep = 1 to nRep;
   idx = 1:p;
   ShiftA = j(nr, p, .);  /* allocate */
   do i = 1 to nr;
      shiftIdx = p - v[i] + idx;      /* shift to the right */
      newIdx = mod(shiftIdx-1, p) + 1; /* wrap around */
      ShiftA[i,] = A[i, newIdx];
   end;
end;
tLoop = (time() - t0) / nRep;

/* yabwon's vectorized version */
t0 = time();
do rep = 1 to nRep;
   c = ncol(A);
   r = nrow(A);
   idx = repeat(1:c,r);
   row = t(0:r-1)*c;
   shiftIdx = c - v + idx;
   newIdx = mod(shiftIdx-1, c) + 1; 
   want = shape(A[newIdx + row], r);
end;
tVectorized = (time() - t0) / nRep;

print tLoop tVectorized;
print (max(abs(ShiftA - want)))[L="Diff between answers"];
 

sas-innovate-2026-white.png



April 27 – 30 | Gaylord Texan | Grapevine, Texas

Registration is open

Walk in ready to learn. Walk out ready to deliver. This is the data and AI conference you can't afford to miss.
Register now and lock in 2025 pricing—just $495!

Register now

From The DO Loop
Want more? Visit our blog for more articles like these.
Discussion stats
  • 13 replies
  • 1049 views
  • 7 likes
  • 4 in conversation