Macro variable, %eval and character operand in sas - sas

I have a problem with sas macro and macro variable. When I use it, I get information: 'A character operand was found in the %eval function or %if condition were numeric.
I have something like distribution (d1-d5) and I want to get similar variables but shifted about diff (data before diff are equal 0). Below example table - of course I need to do something for much bigger table.
Example_table
Name d1 d2 d3 d4 d5 diff
A 0.2 0.2 0.1 0.2 0.3 1
B 0.3 0.1 0.4 0.3 0 2
C 0.1 0.2 0 0.4 0.3 2
Table I want to get: (new_table)
Name n1 n2 n3 n4 n5 diff
A 0 0.2 0.2 0.1 0.2 1
B 0 0 0.3 0.1 0.4 2
C 0 0 0.1 0.2 0 2
Data example_table;
Name = A B C;
d1 = 0.2 0.3 0.1;
d2 = 0.2 0.1 0.2;
d3 = 0.1 0.4 0;
d4 = 0.2 0.3 0.4;
d5 = 0.3 0 0.3;
diff = 1 2 2;
run;
%macro distr ();
%local i;
%do i = 1 %to 5;
if &i. <= diff then n&i. = 0;
else n&i. = d%eval(&i. - diff);
/* I cant compute this eval, it looks like diff is character variable..., but it doesn't */
%end;
%mend;
Data new_table;
Set example_table;
%distr();
run;

The macro processor knows nothing about the values of your dataset variables.
You are trying to subtract the letters diff from the value of the macro variable i. That cannot work.
You will want to use SAS code to do your data manipulation, not macro code. For example by using arrays.
data example_table;
input Name d1-d5 diff ;
cards;
A 0.2 0.2 0.1 0.2 0.3 1
B 0.3 0.1 0.4 0.3 0 2
C 0.1 0.2 0 0.4 0.3 2
;
data want;
set example_table;
array d d1-d5;
array n n1-n5;
do index=1 to dim(n);
if 1 <= index-diff <= dim(d) then n[index]=d[index-diff];
else n[index]=0;
end;
drop index d1-d5;
run;
Results:
Obs Name diff n1 n2 n3 n4 n5
1 A 1 0 0.2 0.2 0.1 0.2
2 B 2 0 0.0 0.3 0.1 0.4
3 C 2 0 0.0 0.1 0.2 0.0

You're mixing up SAS and Macro language here, specifically:
%eval(&i. - diff)
%eval is a macro function, meaning it applies to the text of the code. diff is a SAS data step variable, meaning it has some value - but %eval only operates on the text itself. So %eval is trying to take &i (a number) and subtract from it the letters diff (not a number).
Fortunately it's pretty easy - &i is available to the SAS datastep, as a number. You can use an array to resolve the problem! First declare the array, then...
else n&i. = d[&i].;
Of course, you don't need to use the macro language at all here.
data new_table;
set example_table;
array d[5] d1-d5; *technically d1-d5 is unneeded here as those are the default names;
array n[5] n1-n5; *also n1-n5 unneeded, but it is more clear;
do i = 1 to dim(d);
if i <= diff then n[i] = 0;
else n[i] = d[i];
end;
run;

Related

SAS assign group and accumulate

I have a dataset which have columns Event and Time. I need to create columns Group and Cumulative. What I need to measure is the duration of the Event 'Event1_Stop' until an 'Event1_Start' appears. Last group should sum the time meaning that the stop is ongoing and no start for the event has entered.
My data sample is:
data have;
length Event $15;
input Event $ Time;
datalines;
Event3_Start 0.2
Event2_Start 0.4
Event2_Stop 0.2
Event1_Stop 0.2
Event3_Start 0
Event4_Start 0.5
Event3_Stop 0.2
Event1_Start 0
Event4_Stop 0
Event4_Stop 0
Event1_Stop 0.3
Event3_Start 0.3
Event1_Start 0
Event3_Start 0.4
Event3_Stop 0
Event1_Stop 0.2
Event3_Start 0.2
Event2_Start 0.4
run;
The result dataset that I need to obtain is:
data have;
length Event $15;
input Event $ Time Group Cumulative;
datalines;
Event3_Start 0.2 0 0
Event2_Start 0.4 0 0
Event2_Stop 0.2 0 0
Event1_Stop 0.2 1 0.9
Event3_Start 0 1 0
Event4_Start 0.5 1 0
Event3_Stop 0.2 1 0
Event1_Start 0 0 0
Event4_Stop 0 0 0
Event4_Stop 0 0 0
Event1_Stop 0.3 2 0.6
Event3_Start 0.3 2 0
Event1_Start 0 0 0
Event3_Start 0.4 0 0
Event3_Stop 0 0 0
Event1_Stop 0.2 3 0.8
Event3_Start 0.2 3 0
Event2_Start 0.4 3 0
run;
Thanks for your suggestions.
Regards.
Thanks to #mkeintz on SAS forum for the solution:
data stop_to_start (keep=group cumulative);
set have end=end_of_have;
group+(event='Event1_Stop');
if event='Event1_Stop' then cumulative=0;
cumulative+time;
if end_of_have or event='Event1_Start' ;
run;
data want;
set have;
if _n_=1 or event='Event1_Start' then group=0;
cumulative=0;
if event='Event1_Stop' then set stop_to_start;
run;

How to calculate cumulative product in SAS?

I need to create a variable that takes the product of the values of all prior values and including the one in the current obs.
data temp;
input time cond_prob;
datalines;
1 1
2 0.2
3 0.3
4 0.4
5 0.6
;
run;
Final data should be:
1 1
2 0.2 (1*0.2)
3 0.06 (0.2* 0.3)
4 0.024 (0.06 * 0.4
5 0.0144 (0.024 *0.6)
This seems like a simple code but I can't get it to work. I can do cumulative sums but cumulative product is not working when using the same logic.
Use the RETAIN functionality.
For the first record I set it to a value of 1 because anything multiplied by 1 will stay the same.
data want;
set temp;
retain cum_product 1;
cum_product = cond_prob * cum_product;
run;

SAS DO Loop seems to skip records

In writing a very simple DATA step to start a new project, I ran across some strange behavior.
The only difference between set1 and set2 is the use of the variable lagscore in the equation in set1 vs. dummy in the equation in set2.
set1 produces output that appears to indicate that including lagscore causes the score and lagscore variables to be undefined in half of the iterations.
Note that I was careful to NOT call lag() more than once and I include the call in set2 just to make sure that the lag() function call is not the source of the problem.
I appreciate any explanations. I've been away from SAS for quite awhile and I sense that I am missing something fundamental in how the processing occurs.
(Sorry for the difficult to read output. I could not figure out how to paste it and retain spacing)
data set1;
obs=1;
score=500;
a_dist = -5.0;
b_dist = 0.1;
dummy = 0;
output;
do obs = 2 to 10;
lagscore = lag(score);
score = lagscore + 1 /(b_dist * lagscore + a_dist);
output;
end;
run;
data set2;
obs=1;
score=500;
a_dist = -5.0;
b_dist = 0.1;
dummy = 0;
output;
do obs = 2 to 10;
lagscore = lag(score);
/* score = lagscore + 1 /(b_dist * lagscore + a_dist);*/
score = dummy + 1 /(b_dist * dummy + a_dist);
output;
end;
run;`
Set1 results
obs score a_dist b_dist dummy lagscore
1 500 -5 0.1 0 .
2 . -5 0.1 0 .
3 500.02 -5 0.1 0 500
4 . -5 0.1 0 .
5 500.04 -5 0.1 0 500.02
6 . -5 0.1 0 .
7 500.06 -5 0.1 0 500.04
8 . -5 0.1 0 .
9 500.08 -5 0.1 0 500.06
10 . -5 0.1 0 .
Set2 results
obs score a_dist b_dist dummy lagscore
1 500 -5 0.1 0 .
2 -0.2 -5 0.1 0 .
3 -0.2 -5 0.1 0 500
4 -0.2 -5 0.1 0 -0.2
5 -0.2 -5 0.1 0 -0.2
6 -0.2 -5 0.1 0 -0.2
7 -0.2 -5 0.1 0 -0.2
8 -0.2 -5 0.1 0 -0.2
9 -0.2 -5 0.1 0 -0.2
10 -0.2 -5 0.1 0 -0.2
The key point is that when you call the lag() function it returns a value from a queue that is initialized with missing values. The default is a queue with one item in it.
In your code:
score=500 ;
*...;
do obs = 2 to 10;
lagscore = lag(score);
score = lagscore + 1 /(b_dist * lagscore + a_dist);
output;
end;
The first iteration of the loop (obs=2), LAGSCORE will be assigned a missing value because the queue is initialized with a missing value. The value 500 will be stored in the queue. SCORE will be assigned a missing value because LAGSCORE is missing, and therefore the expression lagscore + 1 /(b_dist * lagscore + a_dist) will return missing.
The second iteration of the loop (obs=3), LAGSCORE will be assigned the value 500 (read from the queue), and the value of SCORE (a missing value) is written to the queue. Score is then assigned the value 500.2 from the expression lagscore + 1 /(b_dist * lagscore + a_dist).
The third iteration of the loop (obs=4), LAGSCORE will be assigned a missing value (read from the queue) and the value 500.2 is written to the queue.
And that pattern repeats.
If I understand your intent, you don't actually need the LAG function for this sort of data creation. You can just use a DO loop with an output statement in it, and update the value of SCORE after you output each record. Something like:
data set1 ;
score = 500 ;
a_dist = -5.0 ;
b_dist = 0.1 ;
do obs = 1 to 10 ;
output ;
score = score + (1 /(b_dist * score + a_dist)) ;
end ;
run ;
Returns:
score a_dist b_dist obs
500.000 -5 0.1 1
500.022 -5 0.1 2
500.044 -5 0.1 3
500.067 -5 0.1 4
500.089 -5 0.1 5
500.111 -5 0.1 6
500.133 -5 0.1 7
500.156 -5 0.1 8
500.178 -5 0.1 9
500.200 -5 0.1 10

SAS_Stan.Dev for each minute

I want to ask a quick question. I think I can explain better by using simple sample.
So, I have the following data:
Time Value
13:45 0.2
13:45 0.4
13:45 0.3
13:46 0.1
13:46 0.2
13:46 0.3
13:46 0.5
13:46 0.4
I want to add one more column. The value in this column should be the standard deviation for each minute. So, I want to get the following data:
Time Value St.D
13:45 0.2 0.1 (it is the standard deviation of 0.2,0.4 and 0.3 - so st.dev for 13:45)
13:45 0.4 0.1
13:45 0.3 0.1
13:46 0.1 0.1528 (it is the standard deviation of 0.1,0.2,0.3,0.5 and 0.6 - so st.dev for 13:46)
13:46 0.2 0.1528
13:46 0.3 0.1528
13:46 0.5 0.1528
13:46 0.6 0.1528
Many thanks in advance for your helps.
Prepare data:
data a;
time ="13:45";
value=0.2;
output;
time ="13:45";
value=0.4;
output;
time ="13:45";
value=0.3;
output;
time ="13:46";
value=0.1;
output;
time ="13:46";
value=0.2;
output;
time ="13:46";
value=0.3;
output;
time ="13:46";
value=0.5;
output;
time ="13:46";
value=0.6;
output;
run;
Calculate stddev:
proc summary data=a stddev nonobs noprint nway;
by time;
var value;
output out=b(drop=_type_ _freq_) stddev()=;
run;
proc sql noprint;
CREATE TABLE res AS
SELECT a.*
,b.value as stddev
FROM a
LEFT JOIN b
ON a.time=b.time
;
quit;
However the stddev of 13:46 differs from your expected. Moreover you have a little typo in you example data for 13:46 ([0.1,0.2,0.3,0.4,0.5],[0.1,0.2,0.3,0.5,0.6]).

get round numbers between 0 to 1

I have UISlider that produce numbers between 0 to 1,
0.0590829
0.0643739
..
I want to get the rounded number between them, like:
0.1
0.2
0.3
...
1.0
found this (in c):
float x = arc4random() % 11 * 0.1;
but its not working on swift
var x = arc4random() % 11 * 0.1;
//error: binary operator '*' cannot be applied to operands of type 'UInt32' and 'Double'
Thanks
Multiply by 10 to get values between 0.0 and 10.0
round to remove the decimal
divide by 10
Example:
let values = [0, 0.0643739, 0.590829, 0.72273, 1]
for value in values {
print("\(value) -> \(round(value * 10) / 10)")
}
// 0.0 -> 0.0
// 0.0643739 -> 0.1
// 0.590829 -> 0.6
// 0.72273 -> 0.7
// 1.0 -> 1.0