SAS looped macro variables resolving incorrectly - loops

Good afternoon.
I am writing a SAS program that will loop through several sets of time-series/ observations. For each set, there is one observation per month, with roughly 450 observations/months total. For simplicity's sake, the months start at 1 and move sequentially.
Now, for each set of observations I have an additional set of variables to be employed. I am importing an auxiliary data set that contains these variables for all of the sets, and using the &&var&i. structure to assign each observation's variables a unique macro variable to be called during the execution of the main loop. So, for example, all of the variables in the first observation have a "1" concatenated onto their variable name, second observation variables have a "2," and so on. When the main loop goes through it's first iteration and calls &&var&i., it will resolve to &var1 and pull in the value assigned from the first observation in the auxiliary data set. I have tested this, and it is working fine.
Important note: each observation in the auxiliary set has a series of variables called ratio_1, ratio_2, ... up to ratio_9. After passing through the macro assignment above, they would assume macro names of ratio_11, ratio_21... for the first set, and ratio_12, ratio_22,... and so on for subsequent sets.
My problem arises when I try to insert code that is only supposed to occur at very specific time intervals within each set. Each set has a variable initial_check that determines on which month this code should begin executing. This code should then execute on each observation that occurs in 12-month increments. So, for example, set 1 might have an initial_check value of 36, meaning that the code will only execute for the observation on month 35 (see code below), with subsequent executions on months 47, 59, 71, and so on.
The first line of code is meant to determine that the code that follows only executes at the aforementioned intervals (the rem_var checks for the remainder of the difference between the current month and the initial_check, over 12 - if there is no remainder, then a multiple of 12 months has passed) :
if mon >= %eval(&&initial_check&k -1) and rem_var = 0 and mon < &&term&k. then do ;
I have run that code in isolation to check that each of its parameters is doing what it should, and it appears to be working correctly. The following code comes next:
** Iterate ratios **
if mon = %eval(&&initial_check&k. -1) then call symput('j',1) ;
else if mon = %eval(&&initial_check&k. +11) then call symput('j',2) ;
else if mon = %eval(&&initial_check&k. +23) then call symput('j',3) ;
else if mon = %eval(&&initial_check&k. +35) then call symput('j',4) ;
else if mon = %eval(&&initial_check&k. +47) then call symput('j',5) ;
else if mon = %eval(&&initial_check&k. +59) then call symput('j',6) ;
else if mon = %eval(&&initial_check&k. +71) then call symput('j',7) ;
else if mon = %eval(&&initial_check&k. +83) then call symput('j',8) ;
else if mon = %eval(&&initial_check&k. +95) then call symput('j',9) ;
end ;
Again, I have tested this using non-macro language (that is, assigning the values to regular variable j), and this also appears to be working. Unfortunately, even with the "mprint" option on, I can't see if the macro variable is being properly assigned. Following that, I have additional code that is only supposed to execute if that first condition was met.
if &&ratio_&j&k ne 0 then do ;
And HERE is the issue: I'm getting a note that macro variable j is unresolved.
This code is only supposed to execute in an instance in which &j has been defined, so I can't figure out why it is unresolved. That &&ratio_&j&k is supposed to resolve to &ratio_11 in month 35, &ratio_21 in month 47, and so on for the first loop of the broader program.
I have tried experimenting with the macro versions of the conditional logic (%IF, %THEN, %DO), but have so far failed to get the results I want.
Would anyone happen to have any insight? I'm at my wit's end. I will be following this thread, so I can add details where necessary. And thank you in advance for taking the time to read this.

We need more information. You cannot include the last two blocks of code in the same data step since the data step will use the value of the macro variable J that exists when the data step is compiled and not the one generated by the call symput() function.
Why isn't J just a data step variable?
If it is a macro variable and you want to use the value that call symput() created then you need to use symget() (or symgetn()) to retrieve it at run time. You can then use its value to generate the name of the macro variable that you actually want to reference.
if symgetn(cats('ratio_',symgetn('j'),"&k")) ne 0 then do ;

Related

SAS- setting missings to a range of columns based on the value of another variable

I currently have a dataset that includes an ID for each person, and then variables called day1 through day1826 that are binary indicators of availability of a drug on each of those days. I need to censor individuals on certain days. For example, if a person needs to be censored on day500, then I need day500 to be set to missing, as well as every day variable after that (i.e. day500 through day1826). I have a variable called time_for_censor that indicates what day to start the missings.
How can I code this in SAS?
I've tried to code it in a loop like this:
array daydummy (1826) day1-day1826;
if time_for_censor ne . then do time_for_censor=1 to 1825;
daydummy(time_for_censor)=.;
daydummy(time_for_censor + 1) =.;
end;
Just loop from the censor date to the end of the array.
array daydummy (1826) day1-day1826;
if not missing(time_for_censor) then do index=time_for_censor to 1826;
daydummy(index)=.;
end;
drop index;
You might need to change the lower bound on the do loop to time_for_censor+1 depending on the whether the values are valid on the censoring date or not.

AutoHotKey - Receive content from an array using a variables' integer as the index

I want to receive a string from an array using a variables' integer as the array index. But it is not working.
Attempt 1
; Suspended | 0 = No, 1 = Yes
global Suspended := 0
global SuspendedMsg := ["The script has been paused.","The script has been re-activated."]
Pause::
Suspend
if suspended = 0 ; If script is not suspended
{
TrayTip, Paused, SuspendedMsg[Suspended], 3
Suspended++
} else ; If it is suspended
{
TrayTip, Activated, SuspendedMsg[Suspended], 3
Suspended--
}
return
Attempt #1 will just display the string "SuspendedMsg[Suspended]" because I don't know where to set the variable indicator %. Even if I set it to SuspendedMsg[%Suspended%] it will either display [1] or [0].
Attempt 2
; Suspended | 0 = No, 1 = Yes
global Suspended := 0
global SuspendedMsg := ["The script has been paused.","The script has been re-activated."]
global SendSuspendMsg := SuspendedMsg[Suspended]
Pause::
Suspend
if suspended = 0 ; If script is not suspended
{
TrayTip, Paused, %SendSuspendMsg%, 3
Suspended++
} else ; If it is suspended
{
TrayTip, Activated, %SendSuspendMsg%, 3
Suspended--
}
return
Attempt #2 won't do as well, it doesn't even display any message. I tried fiddling arround with % inside the global SendSuspendMsg := SuspendedMsg[Suspended] variable but it won't do no good. Anyone care to help me out?
#Blauhim missed an important point, although his answer is mostly correct. First the Index in an Array when created like you did, always starts at 1, then proceeds to 2 etc, etc... So your code was flawed when you tried to use your Boolean variable to call to an index as a 0 Index does not exist (not to mention that you didn't force and Expression on that TrayTip Command).
; Set our variable to 1 why? Because we are going to use a Logical switch below.
Suspended := 1
; This was correct format and I left it, although I removed Global's as they are not needed
SuspendedMsg := ["The script has been paused.","The script has been re-activated."]
Pause::
; Suspend toggles each time it's called
Suspend
; Here we are toggling the value of our variable using !
; We started with a 1 so that it would be correctly
;Changed to a 0 for the code below.
suspended := !suspended
; Nothing changed here
if suspended = 0 ; If script is not suspended
{
; In order to pass an Array or Object or Expression to a Command you Force it
; using the a Percent Sign with a space on either side.
; Also note you were trying to use your Logical True/False 0 or 1 variable to
; itterate. This didn't work because Array's always start with an Index of 1.
; Below I've accounted for this by simply added a 1 to your suspended so it correctly
; points to the Index in our Array.
TrayTip, Paused, % SuspendedMsg[suspended + 1], 3
} else ; If it is suspended
{
TrayTip, Activated, % SuspendedMsg[suspended + 1], 3
}
return
Instead of TrayTip, Paused, SuspendedMsg[Suspended], 3 or TrayTip, Paused, SuspendedMsg[%Suspended%], 3, try
TrayTip, Paused, % SuspendedMsg[Suspended], 3
. TrayTip asks you for a
specify the message to display
which means as much as a String. So, variables names aren't handled as variables here, but as strings instead (as most of the times in commands). It would make sense to state TrayTip, Paused, %SuspendedMsg[%Suspended%]%, 3
, but you cannot nest variable's percent signs. So, we'll have to use the percent sign to force an expression:
Force an expression: An expression can be used in a parameter that does not directly support it (except OutputVar parameters) by preceding the expression with a percent sign and a space or tab. In [v1.1.21+], this prefix can be used in the InputVar parameters of all commands except the traditional IF commands (use If (expression) instead). This technique is often used to access arrays.
Concerning your second problem: I don't think Arrays can be declared like that, can they..? (but I'm not sure). Also see this short article. So I guess the problem lies within the 3rd line of your code, because the rest of it looks good to me

Each xtable produced in R-loops should have \begin{table}..\end{table} environment in Sweave

I try to write an R-function which produces xtables in a loop. Later I want to call my function in a Sweave document- but a single chunk can't support multiple tables. I would have to put each table in a single chunk and wrap it with the Latex Code \begin{table} ... \end{table}.
So I wonder, whether it's possible to somehow call Sweave/knitr from within the Loop of the R-function and add \begin{table} .. \end{table} around each xtable?
Or whether it is somehow possible to send each xtable from the loop to a chunk with \begin{table} ... \end{table} environment?
A mini-example of my function:
multiple_tables_Loop<-function(...){
(....) ##Some necessary calculations to produce a data frame
for(j in 1:m){
for(i in 1:n){
a<-data.frame(...)
table<-xtable(a)
print(table)
}
}
}
In Sweave I would call the function:
<<Hallo_Table,results='aisis'>>
multiple_tables_Loop(...)
#
I'm confused by your question. xtable does include \begin{table}/\end{table} pairs. And you can put multiple tables is a code chunk (for both Sweave and knitr .Rnw files). Could it be just that you have misspelled 'asis' in your chunk header?
Showing xtable does include \begin{table}/\end{table}:
> xtable(data.frame(x=1))
% latex table generated in R 3.1.2 by xtable 1.7-4 package
% Fri Jan 23 11:12:47 2015
\begin{table}[ht]
\centering
\begin{tabular}{rr}
\hline
& x \\
\hline
1 & 1.00 \\
\hline
\end{tabular}
\end{table}
And a simple .Rnw file of
<<results="asis">>=
library("xtable")
xtable(data.frame(x=1))
xtable(data.frame(y=1))
#
properly gives two tables.
If the misspelling isn't the problem, a complete minimally reproducible example is needed along with the version numbers of R and all the packages (output of sessionInfo())

SAS use value from one observation to overwrite different one

I have a data set with two main variables of interest now - Major and Major_Code. These should match up 1 to 1 but there are some errors I need to fix and what I've found is that for 14 Major_Code values, there are two different Majors. This is only due to a change in spelling or punctuation, like "ed." and "education". They are supposed to have the same value here but don't.
So I have a table with 7 pairs. Each pair has the same Major_Code and different a Major. How can I select one of the Major vales to use for each code? My only idea was through an if-then statement but that seems horribly inefficient.
I found the doubled values like this:
proc freq data=majorslist;
tables Major_Code/out=majorcodedups;
run;
proc print data=majorcodedups;
where COUNT > 1;
run;
So I can easily find these observations but can't extract certain values to overwrite onto another observation. I've looked into arrays, macros, sql and transpose but it's all a bit over my head right now.
Logically it would work like this:
from obs i to n, find value for variable x at obs i, output value onto variable y at obs i, go to obs(i+1) and repeat.
Assuming you have some rule for determining which MAJOR is correct for a MAJOR_CODE, you should do this:
This assumes majorslist is a dataset of every major/major_code pair whether unique or not - but only one per major/major_code pair.
proc sort data=majorslist;
by major_code major;
run;
data majorslist_unique;
set majorslist;
by major_code major;
if first.major_code and last.major_code then output;
else do;
*rule to determine whether to output it or not;
end;
run;
So, you now have the major_code/major relationship. Let's say you picked if first.major_code then output; as your rule (ie, take the major_code with the alphabetically first major value).
Now, you need to apply this to your larger dataset. There are a lot of ways to do that - merge this on is one, format is another, for starters. Format works like this:
Create a dataset with FMTNAME, START, LABEL defined. For each value of MAJOR_CODE, construct one row like that, where START is MAJOR_CODE and LABEL is MAJOR. We'll also add an extra line that says what to do with non-matches (in case you get new values of major_code).
data for_fmt;
set majorslist_unique;
fmtname='MAJORF'; *add a $ if MAJOR_CODE is a character variable;
start=major_code;
label=major;
output;
if _n_=1 then do;
hlo='o';
call missing(start);
label='NONMATCHED';
output;
end;
keep fmtname start label hlo;
run;
proc format cntlin=for_fmt;
quit;
Now you have a format, MAJORF. (or $MAJORF. if MAJOR_CODE is character), that you can use in a PUT statement.
data my_bigdata2;
set my_bigdata;
major = put(major_code,MAJORF.);
run;

Loop with two counters

Following is some C code. How do I do the same in sas?
For(i=30, j=1; i<=41, j<=12; i++, j++)
(
closure(i,j) /*calling function with input parameters I and j */
);
I basically want to do the following macro calls using a loop with two counters I and J
%closure(30,201201);
%closure(31,201202);
%closure(32,201203);
%closure(33,201204);
%closure(34,201205);
%closure(35,201206);
%closure(36,201207);
%closure(37,201208);
%closure(38,201209);
%closure(39,201210);
%closure(40,201211);
%closure(41,201212);
Please note that I do not want to use a nested loop.
Tips are appreciated.
Doing this in SAS depends on how your data is structured. It's possible to do this:
%do i = 1 to 12;
%closure(%eval(29+i),%eval(201200+i));
%end;
That's a bit odd, but it should work fine.
You could also do it in the %closure macro. Pass i and then determine the value of the other parameters inside the macro, if they always have this relationship. If they always have some relationship, but the 2012 and 18 parts are variable, then you have several options:
Define 2012 and 29 as macro variables ahead of this step, and replace them in the code with such.
%let year=2012;
%let startrec=29;
%do i = 1 to 12;
%closure(%eval(&startrec.+&i.),%eval(&year.00+&i.));
%end;
Use date functions to determine the value of j, if it is not always 01-12.
%closure(30,%sysfunc(intnx(month,'01JUN2011'd,&i.-1)))
(you may want to format the result in YYYYMM format, or you may be just as well off using the date result, depending on %closure)
Define all of these terms in a dataset, and call the macro from the dataset.
data to_call;
input i j;
datalines;
30 201201
31 201202
.... ;
run;
proc sql;
select cats('%closure(',i,',',j,')') into :calllist separated by ' ' from to_call;
quit;
&calllist.
That's a more 'SAS'sy way to do things, making the process data driven. Most commonly used when the i and j parameters are stored as data element somewhere (in a control table, for example, or derived from some other data source).
So If you want
%closure(30,201201);
%closure(31,201202);
%closure(32,201203);
%closure(33,201204);
%closure(34,201205);
%closure(35,201206);
%closure(36,201207);
%closure(37,201208);
%closure(38,201209);
%closure(39,201210);
%closure(40,201211);
%closure(41,201212);
then it would be better either you calculate the value of J and bring it to 201200 aur something near about.
Or you should start the j loop with 201201 and end it to 201212
simply go for
For(i=30, j=201201; i<=41, j<=201212; i++, j++)
(
closure(i,j)
);

Resources