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"];
 
From The DO Loop
Want more? Visit our blog for more articles like these.
Discussion stats
  • 13 replies
  • 466 views
  • 7 likes
  • 4 in conversation