- Mark as New
- Bookmark
- Subscribe
- Mute
- RSS Feed
- Permalink
- Report Inappropriate Content
Trying to set all of the values in an array to 0. It seems like it would be easy. If you have an array called have, then just have=0;. But then I get an error
ERROR: Array subscript out of range at line 37 column 13.
Not sure how to solve this error. I know I can just do DO OVER have; have=0; END; but I thought that this way should work.
Accepted Solutions
- Mark as New
- Bookmark
- Subscribe
- Mute
- RSS Feed
- Permalink
- Report Inappropriate Content
Hello @hubertsng and welcome to the SAS Support Communities!
There are some CALL routines and a few functions which can change the values of several variables (e.g. the elements of an array) at the same time. In your case CALL STDIZE can do the trick:
If the original values are all non-missing:
data _null_;
array x[4] (2 3 5 7);
call stdize('mult=',0,of x[*]);
put x[*];
run;
If some (but not all) may be missing:
data _null_;
array x[4] (2 3 . 7);
call stdize('replace','mult=',0,of x[*]);
put x[*];
run;
If you cannot rule out that all are missing:
data _null_;
array x[4];
call stdize('replace','mult=',0,of x[*],_iorc_);
put x[*];
run;
(_iorc_ can be replaced by any other variable with a non-missing value, e.g. _n_, _error_, etc.).
Honestly, I have never used PROC CALL STDIZE for this purpose and I don't know if it's slower than the traditional DO-loop approach. Readability of the code is another potential issue.
- Mark as New
- Bookmark
- Subscribe
- Mute
- RSS Feed
- Permalink
- Report Inappropriate Content
The documentation gives examples of doing this.
Paige Miller
- Mark as New
- Bookmark
- Subscribe
- Mute
- RSS Feed
- Permalink
- Report Inappropriate Content
You cannot refer to multiple variables in an array like that. And you cannot assign values to multiple variables at once.
Referencing an array name without an explicit index, as in your example, will just use an implicit index. That is how the DO OVER statement works. If you didn't define a different variable to use for the implicit index then the default is to use _I_. So your error message is saying that the variable _I_ has a value that is not an integer between 1 and the number of elements in the array. Most likely because it has a missing value instead.
You can READ from multiple variables in the array at once by using the syntax of an explicit index with a value of *. For example to find the average value you could do:
mean_have = mean(of have(*));
- Mark as New
- Bookmark
- Subscribe
- Mute
- RSS Feed
- Permalink
- Report Inappropriate Content
@Tom wrote:
And you cannot assign values to multiple variables at once.
The documentation says you can. And it works for me.
Paige Miller
- Mark as New
- Bookmark
- Subscribe
- Mute
- RSS Feed
- Permalink
- Report Inappropriate Content
@PaigeMiller wrote:
@Tom wrote:
And you cannot assign values to multiple variables at once.
The documentation says you can. And it works for me.
That would be interesting development. Can you show an example?
The link you posted before is the syntax for how to set the INITIAL values of an array when the array is defined. Not how to assign values during the execution phase of a data step.
- Mark as New
- Bookmark
- Subscribe
- Mute
- RSS Feed
- Permalink
- Report Inappropriate Content
The documentation says you can do it at compile time, not at run time.
Concur on that with @Tom .
Kind regards
Paul D.
- Mark as New
- Bookmark
- Subscribe
- Mute
- RSS Feed
- Permalink
- Report Inappropriate Content
@Tom :
Methinks by using the [*] directive, you tell SAS to go over all the variables in the list. But it doesn't mean that when SAS executes this directive, it reads all the variables "at once". In fact, I bet that the underlying software reads them one at a time, except it does it much quicker compared to an explicit DO loop in the overlying software.
"And you cannot assign values to multiple variables at once."
That's true if you are talking about the assignment statement. However, there exists a SAS call routine that can set the values of multiple array variables to specific values all at once without ever using the assignment statement. Say, you have an 5-item array with the values 0-4 and you want to change them to 5-9. Then:
data _null_ ;
array have[*] a b c d e (0 1 2 3 4) ;
put "have = " have[*] ;
call pokelong (cats(put(5,rb8.),put(6,rb8.),put(7,rb8.),put(8,rb8.),put(9,rb8.)),addrlong(a),40) ;
put "have = " have[*] ;
run ;
Of course, the concatenation of the replacement values into the string of their respective RB8 memory images can be done much sleeker than shown above - I've dumbed it down just to illustrate the concept. Normally, you'd parameterize the replacement values in some manner and concat them in a loop at _N_=1.
If all the array items have to be set to one and the same value, it's still simpler since you can use the REPEAT function to create the replacement RB8 concatenation string. For example, if, as in the OP's question, you want to replace them all with zeroes:
data _null_ ;
array have[*] a b c d e (0 1 2 3 4) ;
put "have = " have[*] ;
call pokelong (repeat(put(0,rb8.),4),addrlong(a),40) ;
put "have = " have[*] ;
run ;
That said, I'd prefer the @FreelanceReinh method since it doesn't require to understand the intricacies of the physical addressing needed to use the APP functions conscientiously, even though it's a bit (~20%) slower at a high number of iterations:
data _null_ ;
array have[*] a b c d e (0 1 2 3 4) ;
put "have = " have[*] ;
r = put (repeat (put (0, rb8.), 4), $40.) ;
addra = addrlong (a) ;
do i = 1 to 1e8 ;
call pokelong (r, addra, 40) ;
end ;
put "have = " have[*] ;
run ;
data _null_ ;
array have[*] a b c d e (0 1 2 3 4) ;
put "have = " have[*] ;
do i = 1 to 1e8 ;
call stdize('replace','mult=',0,of have[*],_iorc_); ;
end ;
put "have = " have[*] ;
run ;
Kind regards
Paul D.
- Mark as New
- Bookmark
- Subscribe
- Mute
- RSS Feed
- Permalink
- Report Inappropriate Content
Hello @hubertsng and welcome to the SAS Support Communities!
There are some CALL routines and a few functions which can change the values of several variables (e.g. the elements of an array) at the same time. In your case CALL STDIZE can do the trick:
If the original values are all non-missing:
data _null_;
array x[4] (2 3 5 7);
call stdize('mult=',0,of x[*]);
put x[*];
run;
If some (but not all) may be missing:
data _null_;
array x[4] (2 3 . 7);
call stdize('replace','mult=',0,of x[*]);
put x[*];
run;
If you cannot rule out that all are missing:
data _null_;
array x[4];
call stdize('replace','mult=',0,of x[*],_iorc_);
put x[*];
run;
(_iorc_ can be replaced by any other variable with a non-missing value, e.g. _n_, _error_, etc.).
Honestly, I have never used PROC CALL STDIZE for this purpose and I don't know if it's slower than the traditional DO-loop approach. Readability of the code is another potential issue.
- Mark as New
- Bookmark
- Subscribe
- Mute
- RSS Feed
- Permalink
- Report Inappropriate Content
Thanks 1e6 for posting it - always good to learn something new and utile.
Perhaps I coould return the favor by pointing out that, in addition to CALL POKE(LONG) I've mentioned earlier, there's another call routine designed exactly for the purpose - namely, CALL FILLMATRIX. It has a couple of shortcomings, to wit:
- it works only for temporary and 1-based arrays
- it has to be compiled into another call routine via FCMP (though it isn't a big problem - see below)
However, where it's applicable, i.e. temporary, 1-based arrays, it vastly outperforms STDIZE:
%let d = %eval (2**10) ;
%let n = 1e7 ;
data _null_ ;
array h[&d] _temporary_ ;
do n = 1 to &n ;
call stdize ('replace', 'mult=', 0, of h[*], _iorc_) ;
end ;
run ;
proc fcmp outlib=work.f.f ;
subroutine fillarray (a[*], value) ;
outargs a ;
call fillmatrix (a, value) ;
endsub ;
quit ;
option cmplib=work.f ;
data _null_ ;
array h[&d] _temporary_ ;
do n = 1 to &n ;
call fillarray (h, 0) ;
end ;
run ;
Running it shows that FILLARRAY does the job about 15 times faster than STDIZE (4 seconds vs 1 minute in my 9.4 Windows test).
Kind regads
Paul D.
- Mark as New
- Bookmark
- Subscribe
- Mute
- RSS Feed
- Permalink
- Report Inappropriate Content
@hashman: Thanks for reminding me of the matrix operation routines available in PROC FCMP. These can be particularly helpful for users (like me) who don't have a SAS/IML license. They've mostly been under my radar because they are not listed in the table "SAS Functions and CALL Routines by Category," which I use very often. Indeed, that's how I came up with the CALL STDIZE idea: I went through the list of CALL routines ...
I'm not surprised that the CALL FILLMATRIX routine, which is "designed exactly for the purpose," performs better than CALL STDIZE, even after dropping "'replace'" and "_iorc_" (which triples the speed of CALL STDIZE in your example).
Thank you, @hubertsng, for sparking such a fruitful discussion.
- Mark as New
- Bookmark
- Subscribe
- Mute
- RSS Feed
- Permalink
- Report Inappropriate Content
You can use your syntax to do what you need if you've define the array as implicitly subscripted in the first place. E.g.:
data _null_ ;
array have a b c d e ;
do over have ;
have = ceil (ranuni(1) * 9) ;
end ;
put "have = " have[*] ;
do over have ;
have = 0 ;
end ;
put "have = " have[*] ;
run ;
The log will show:
have = 2 9 4 3 9
have = 0 0 0 0 0
If you've defined the array as explicitly subscripted, you'll have to use an explicit index, e.g.:
data _null_ ;
array have[*] a b c d e ;
do i = lbound (have) to hbound (have) ;
have[i] = ceil (ranuni(1) * 9) ;
end ;
put "have = " have[*] ;
do i = lbound (have) to hbound (have) ;
have[i] = 0 ;
end ;
put "have = " have[*] ;
run ;
with the same result.
Kind regards
Paul D.