Hello I am trying to solve a problem using iteration with do until but I don't get any results. Also I am learning SAS at the moment on my own using books, documentation and videos so I am new to this language. My problem is :
A car delivers a mileage of 20 miles per galon. Write a program so that the program stops generating observations when distance reaches 250 miles or when 10 gallons of fuel have been used
Hint Miles = gallon * mpg
I used the following code:
data mileage;
mpg = 20;
do until (miles le 250);
miles +1;
do until (gallon le 10);
gallon + 1;
miles = gallon * mpg;
end;
end;
output;
run;
Please tell me what am I doing wrong here?
Many thanks for your time and attention !
Because you waited until after the DO loops finished to write out any observations. If you want to write multiple observations you should move your output statement inside the do loop.
Also your program is never initializing gallon so mpg will always be missing and so less than 250 which means your outer DO loop will only execute once.
Your question as written can be answered without a program since 10*20 is less than 250. Assuming that you also want to change the mpg values then perhaps this is more what you wanted?
data mileage;
do mpg = 20 by 1 until (miles ge 250);
do gallon=1 to 10 until (miles ge 250);
miles = gallon * mpg;
output;
end;
end;
run;
The ability to combine both an iterative loop with an UNTIL condition is one of the many nice features of the data step DO loop.
Related
In a clinical trial, Systolic and diastolic blood pressure are measured pre-dose (0 hr) and at 1,2,4,8 hour post- dose.
Twelve subjects were studied. The SAS dataset has the following structure
Variable-Vol Length - 8 Label- Subject Number
Variable- Ntime Length- 8 Label Nominal time post-dose (hours)
Variable- Sups Length- 8 Label- Supine Systolic BP (mmHg)
What SAS code could I use to calculate the change from baseline (Oh) at each time point, and then calculate the mean, minimum, maximum change from baseline for the 12 subjects? Edit: This is what I've tried so far
data postbase;
do until (last.vol);
*** Only keep pre-dose values;
set save.vitals (where=(not(ntime <= 0 )));
by Vol Ntime;
if Ntime <= 0 then bl = Sups;
else do;
chgbl = Sups - bl;
output;
end;
end;
run;
data postbase;
set save.vitals;
by subject time volume;
retain baseline;
if time=0 then baseline=volume;
else change = volume - baseline;
run;
I think your code is too complex by far and I couldn't parse your variable names so just made them up.
I set baseline volume whenever time = 0 and then do the change every other time.
RETAIN causes the value to stay until it's reset. If you have times that may not be 0 or missing baseline then you may need to modify the query.
I'd like to set all values in an array to 1 if some sort of condition is met, and perform a calculation if the condition isn't met. I'm using a do loop at the moment which is very slow.
I was wondering if there was a faster way.
data test2;
set test1;
array blah_{*} blah1-blah100;
array a_{*} a1-a100;
array b_{*} b1-b100;
do i=1 to 100;
blah_{i}=a_{i}/b_{i};
if b1=0 then blah_{i}=1;
end;
run;
I feel like the if statement is inefficient as I am setting the value 1 cell at a time. Is there a better way?
There are already several good answers, but for the sake of completeness, here is an extremely silly and dangerous way of changing all the array values at once without using a loop:
data test2;
set test1;
array blah_{*} blah1-blah100 (100*1);
array a_{*} a1-a100;
array b_{*} b1-b100;
/*Make a character copy of what an array of 100 1s looks like*/
length temp $800; *Allow 8 bytes per numeric variable;
retain temp;
if _n_ = 1 then temp = peekclong(addrlong(blah1), 800);
do i=1 to 100;
blah_{i}=a_{i}/b_{i};
end;
/*Overwrite the array using the stored value from earlier*/
if b1=0 then call pokelong(temp,addrlong(blah1),800);
run;
You have 100*NOBS assignments to do. Don't see how using a DO loop over an ARRAY is any more inefficient than any other way.
But there is no need to do the calculation when you know it will not be needed.
do i=1 to 100;
if b1=0 then blah_{i}=1;
else blah_{i}=a_{i}/b_{i};
end;
This example uses a data set to "set" all values of an array without DOingOVER the array. Note that using SET in this way changes INIT-TO-MISSING for array BLAH to don't. I cannot comment on performance you will need to do your own testing.
data one;
array blah[10];
retain blah 1;
run;
proc print;
run;
data test1;
do b1=0,1,0;
output;
end;
run;
data test2;
set test1;
array blah[10];
array a[10];
array b[10];
if b1 eq 0 then set one nobs=nobs point=nobs;
else do i = 1 to dim(blah);
blah[i] = i;
end;
run;
proc print;
run;
This is not a response to the original question, but as a response to the discussion on the efficiency between using loops vs set to set the values for multiple variables
Here is a simple experiment that I ran:
%let size = 100; /* Controls size of dataset */
%let iter = 1; /* Just to emulate different number of records in the base dataset */
data static;
array aa{&size} aa1 - aa&size (&size * 1);
run;
data inp;
do ii = 1 to &iter;
x = ranuni(234234);
output;
end;
run;
data eg1;
set inp;
array aa{&size} aa1 - aa&size;
set static nobs=nobs point=nobs;
run;
data eg2;
set inp;
array aa{&size} aa1 - aa&size;
do ii = 1 to &size;
aa(ii) = 1;
end;
run;
What I see when I run this with various values of &iter and &size is as follows:
As &size increases for a &iter value of 1, assignment method is faster than the SET.
However for a given &size, as iter increases (i.e. the number of times the set statement / loop is called), the speed of the SET approach increases while the assignment method starts to decrease at a certain point at which they cross. I think this is because the transfer from physical disk to buffer happens just once (since static is a relatively small dataset) whereas the assignment loop cost is fixed.
For this use case, where the fixed dataset used to set values will be smaller, I admit that SET will be faster especially when the logic needs to execute on multiple records on the input and the number of variables that needs to be assigned are relatively few. This however will not be the case if the dataset cannot be cached in memory between two records in which case the additional overhead of having to read it into the buffer can slow it down.
I think this test isolates the statements of interest.
SUMMARY:
SET+create init array 0.40 sec. + 0.03 sec,
DO OVER array 11.64 sec.
NOTE: Additional host information:
X64_SRV12 WIN 6.2.9200 Server
NOTE: SAS initialization used:
real time 4.70 seconds
cpu time 0.07 seconds
1 options fullstimer=1;
2 %let d=1e4; /*array size*/
3 %let s=1e5; /*reps (obs)*/
4 data one;
5 array blah[%sysevalf(&d,integer)];
6 retain blah 1;
7 run;
NOTE: The data set WORK.ONE has 1 observations and 10000 variables.
NOTE: DATA statement used (Total process time):
real time 0.03 seconds
user cpu time 0.03 seconds
system cpu time 0.00 seconds
memory 7788.90k
OS Memory 15232.00k
Timestamp 08/17/2019 06:57:48 AM
Step Count 1 Switch Count 0
8
9 sasfile one open;
NOTE: The file WORK.ONE.DATA has been opened by the SASFILE statement.
10 data _null_;
11 array blah[%sysevalf(&d,integer)];
12 do _n_ = 1 to &s;
13 set one nobs=nobs point=nobs;
14 end;
15 stop;
16 run;
NOTE: DATA statement used (Total process time):
real time 0.40 seconds
user cpu time 0.40 seconds
system cpu time 0.00 seconds
memory 7615.31k
OS Memory 16980.00k
Timestamp 08/17/2019 06:57:48 AM
Step Count 2 Switch Count 0
2 The SAS System 06:57 Saturday, August 17, 2019
17 sasfile one close;
NOTE: The file WORK.ONE.DATA has been closed by the SASFILE statement.
18
19 data _null_;
20 array blah[%sysevalf(&d,integer)];
21 do _n_ = 1 to &s;
22 do i=1 to dim(blah); blah[i]=1; end;
23 end;
24 stop;
25 run;
NOTE: DATA statement used (Total process time):
real time 11.64 seconds
user cpu time 11.64 seconds
system cpu time 0.00 seconds
memory 3540.65k
OS Memory 11084.00k
Timestamp 08/17/2019 06:58:00 AM
Step Count 3 Switch Count 0
NOTE: SAS Institute Inc., SAS Campus Drive, Cary, NC USA 27513-2414
NOTE: The SAS System used:
real time 16.78 seconds
user cpu time 12.10 seconds
system cpu time 0.04 seconds
memory 15840.62k
OS Memory 16980.00k
Timestamp 08/17/2019 06:58:00 AM
Step Count 3 Switch Count 16
Some more interesting tests results based on data null 's original test. I added the following test also:
%macro loop;
data _null_;
array blah[%sysevalf(&d,integer)] blah1 - blah&d;
do _n_ = 1 to &s;
%do i = 1 %to &d;
blah&i = 1;
%end;
end;
stop;
run;
%mend;
%loop;
d s SET Method (real/cpu) %Loop (real/cpu) array based(real/cpu)
100 1e5 0.03/0.01 0.00/0.00 0.07/0.07
100 1e8 11.16/9.51 4.78/4.78 1:22.38/1:21.81
500 1e5 0.03/0.04 0.02/0.01 Did not measure
500 1e8 16.53/15.18 32.17/31.62 Did not measure
1000 1e5 0.03/0.03 0.04/0.03 0.74/0.70
1000 1e8 20.24/18.65 42.58/42.46 Did not measure
So with array based assignments, it is not the assignment that is the big culprit itself. Since arrays use a memory map to map the original memory locations, it appears that the memory location lookup for a given subscript is what really impacts performance. A direct assignment avoids this and significantly improves performance.
So if your array size is in the lower 100s, then direct assignment may not be a bad way to go. SET becomes effective when the array sizes go beyond a few hundreds.
I am studying SAS on my own. I have no one to refer to so I just wanted to check if my code is correct.
In a fixed term deposit of 25 years calculate the total amount at the end of
term with initial amount of $5,00,000 and annual interest rate of 7 % */
1) Compounded Annually
2) Compounded Monthly.Show the amount at monthly level
My Code:
data deposit;
amount = 500000;
rate = 0.07;
do year = 1 to 25;
amount + earned;
earned + (amount*0.07);
principal = amount + earned;
output;
end;
run;
For the second question compounded monthly
data deposit1;
rate = 0.006;
amount1 = 500000;
do year = 1 to 25;
do month = 1 to 12;
earned1 + (earned1 + amount1)*0.006;
amount1 + earned1;
output;
end;
end;
run;
Pasting the Screenshots of Solution 1 and Solution 2
I am confused because when I compound annually and monthly both have different results at the end of a particular year.
Please suggest if anything is wrong in my code. Thank you for your time and attention.
It looks like you are double-counting your earned1 variable in the monthly compounding code.
earned1 + (earned1 + amount1)*0.006;
amount1 + earned1;
Should be:
earned1 = amount1*0.07**(1/12);
amount1 + earned1;
Note also you will not want to round the interest rate.
I am new to SAS and I was wondering how to cure the variable not found problem in creating a binomial distribution?
DATA additional (KEEP=X);
DO REPEAT = 1 TO 1000;
CALL STREAMINIT(1234);
DO I=1 TO 1000;
X=RAND("BINOMIAL",0.6,10); /*NUMBER OF WINS IN TEN TOSSES*/
END;
IF X GE 5 THEN WINNER + 1;
ELSE LOSER + 1;
OUTPUT;
END;
RUN;
PROC PRINT DATA=additional;
VAR WINNER LOSER;
RUN;
I am creating a binomial random variable which if x is great than 5 then counts one for the winner, if less than 5 then counts one for the loser, the question is asking to found how many time are winners and how many times are losers. I kept on getting variable not found error. Am i doing something wrong with generating the binomial distribution.
/further editing/ this is the problem I am given.
You are given $10. Let the variable money = 10.
You play a game 10 times. The probability that you win a game is 0.4,
and the probability that you lose a game is 0.6.
If you win a game, you win $1. If you lose a game, you lose $1. So if
you win the first game, money becomes 11. But if you lose the first
game, money becomes 9.
After you have played the game 10 times, money is the amount that you
go home with. If you end up with at least $10, call yourself a winner.
Otherwise, call yourself a loser. Define the variable result as winner
or loser.
(a) Write a data step to generate random numbers and simulate your
result 1000 times. So that I can easily check your outputs, use
1234 as your seed for the random number generator. (You do not
need to show me the 1000 results.)
(b) Write a proc step to show how many times you are a winner, and
how many times you are a loser.
Not fully understand that you want to do with simulation. From your codes, you just keep 1000 records, which are all kept at last loop because of your first loop end position; call streaminit should be first line; you only keep X, you could not get winner and loser variable.
I guess maybe you could try this.
DATA additional;
CALL STREAMINIT(1234);
DO REPEAT= 1 TO 1000; *numbers of sample;
DO I=1 TO 100; *size of sample;
X=RAND("BINOMIAL",0.6,10); /*NUMBER OF WINS IN TEN TOSSES*/
IF X GE 5 THEN results='WINNER';
ELSE results='LOSER';
OUTPUT;
END;
END;
RUN;
proc freq data=additional;
by repeat;
table results;
run;
Edit: It seems that you want to know final results, you could get it from above code by changing results as numeric variable. Here is modified codes, if win is +1, lose is -1.
DATA additional;
CALL STREAMINIT(1234);
DO REPEAT= 1 TO 100; *numbers of sample;
DO I=1 TO 10; *size of sample;
X=RAND("BINOMIAL",0.6,10); /*NUMBER OF WINS IN TEN TOSSES*/
IF X GE 5 THEN results+1;
ELSE results=results-1;
OUTPUT;
END;
results=0;
END;
RUN;
proc freq data=additional;
by repeat;
table results;
run;
I have a SAS issue that I know is probably fairly straightforward for SAS users who are familiar with array programming, but I am new to this aspect.
My dataset looks like this:
Data have;
Input group $ size price;
Datalines;
A 24 5
A 28 10
A 30 14
A 32 16
B 26 10
B 28 12
B 32 13
C 10 100
C 11 130
C 12 140
;
Run;
What I want to do is determine the rate at which price changes for the first two items in the family and apply that rate to every other member in the family.
So, I’ll end up with something that looks like this (for A only…):
Data want;
Input group $ size price newprice;
Datalines;
A 24 5 5
A 28 10 10
A 30 14 12.5
A 32 16 15
;
Run;
The technique you'll need to learn is either retain or diff/lag. Both methods would work here.
The following illustrates one way to solve this, but would need additional work by you to deal with things like size not changing (meaning a 0 denominator) and other potential exceptions.
Basically, we use retain to cause a value to persist across records, and use that in the calculations.
data want;
set have;
by group;
retain lastprice rateprice lastsize;
if first.group then do;
counter=0;
call missing(of lastprice rateprice lastsize); *clear these out;
end;
counter+1; *Increment the counter;
if counter=2 then do;
rateprice=(price-lastprice)/(size-lastsize); *Calculate the rate over 2;
end;
if counter le 2 then newprice=price; *For the first two just move price into newprice;
else if counter>2 then newprice=lastprice+(size-lastsize)*rateprice; *Else set it to the change;
output;
lastprice=newprice; *save the price and size in the retained vars;
lastsize=size;
run;
Here a different approach that is obviously longer than Joe's, but could be generalized to other similar situations where the calculation is different or depends on more values.
Add a sequence number to your data set:
data have2;
set have;
by group;
if first.group the seq = 0;
seq + 1;
run;
Use proc reg to calculate the intercept and slope for the first two rows of each group, outputting the estimates with outest:
proc reg data=have2 outest=est;
by group;
model price = size;
where seq le 2;
run;
Join the original table to the parameter estimates and calculate the predicted values:
proc sql;
create table want as
select
h.*,
e.intercept + h.size * e.size as newprice
from
have h
left join est e
on h.group = e.group
order by
group,
size
;
quit;