Recoding macro argument in SPSS - macros

I am fairly new to SPSS. I am trying to write a macro where "time" will be passed as an argument. I want to recode it into another macro variable "t2". I can do it in the following way:
DEFINE !my_macro (time = !TOKEN(1))
!LET !t2=" ".
!IF (!time >=2 & !time <2.5) !THEN !LET !t2=1. !IFEND.
!IF (!time >=2.5 & !time <3) !THEN !LET !t2=2. !IFEND.
!IF (!time >=3 & !time <3.5) !THEN !LET !t2=3. !IFEND.
...
The problem is I have so many of these intervals. Just wondering if there is any one line of code that will recode a macro variable into another macro variable?

I think a visual binning (RECODE) would work better instead of using macros. Given that your time variable is numeric.
*Create the macro for binning.
define group (!positional !cmdend).
!do !var !in (!1)
recode !var (lo thru 2.5=1) (2.5 thru 3.5=2) (3.5 thru hi=3) .
value !var 1 '2-<2.5' 2 '2.5-<3.5' 3 '>=3.5'.
exec.
!doend
!enddefine.
group time time2 time3. /*Conduct the binning.
list time time2 time3.
I think that should work provided your time variable is numeric.
You could also use the DO REPEAT method.

Related

SPSS/macro: split string into multiple variables

I am trying to split a string variable into multiple dummy coded variables. I used these sources to get an idea of how one would achieve this task in SPSS:
https://www.ibm.com/support/pages/making-multiple-string-variables-single-multiply-coded-field
https://www.spss-tutorials.com/spss-split-string-variable-into-separate-variables/
But when I try to adapt the first one to my needs or when I try to convert the second one to a macro, I fail.
In my dataset I have (multiple) variables that contain a comma seperated string that represents different combinations of selected items (as well as missing values). For each item of a specific variable I want to create a dummy variable. If the item was selected, it should be represented with a 1 in the new dummy variable. If it was not selected, that case should be represented with a 0.
Different input variables can contain different numbers of items.
For example:
ID
VAR1
VAR2
DMMY1_1
DMMY1_2
DMMY1_3
1
1, 2
8
1
1
0
2
1
1, 3
1
0
0
3
3, 1
2, 3, 1
1
0
1
4
2, 8
0
0
0
Here is what I came up with so far ...
* DEFINE DATA.
DATA LIST /ID 1 (F) VAR1 2-5 (A) VAR2 6-12 (A).
BEGIN DATA
11, 28
21 1, 3
33, 12, 3, 1
4 2, 8
END DATA.
* MACRO SYNTAX.
* DEFINE VARIABLES (in the long run these should/will be inside the macro function, but for now I will leave them outside).
NUMERIC v1 TO v3 (F1).
VECTOR v = v1 TO v3.
STRING #char (A1).
DEFINE split_var(vr = TOKENS(1)).
!DO !#pos=1 !TO char.length(!vr).
COMPUTE #char = char.substr(!vr, !#pos, 1).
!IF (!#char !NE "," !AND !#char !NE " ") !THEN
COMPUTE v(NUMBER(!#char, F1)) = 1.
!IFEND.
!DOEND.
!ENDDEFINE.
split_var vr=VAR1.
EXECUTE.
As I got more errors than I can count, it's hard to narrow down my problem. But I think the problem has something to do with the way I use the char.length() function (and I am a bit confused when to use the bang operator).
If anyone has some insights, I would really appreciate some help :)
There is a fundamental issue to understand about SPSS macro - the macro does not read or interact in any way with the data. All the macro does is manipulate text to write syntax. The syntax created will later work on the actual data when you run it.
So, for example, Your first error is using char.length(!vr) within the syntax. You are trying to get the macro to read the data, calculate the length and use, but that simply can't be done - the macro can only work with what you gave it.
Another example in your code: you calculate #char and then try to use it in the macro as !#char. So that obviously won't work. ! precedes only macro functions or arguments. #char, in your code, is neither, and it can't become one - can't read the data into the macro...
To give you a litte push forward: I understand you want the macro loop to run a different number of times for each variable, but you can't use char.length(!vr). I suggest instead have the macro loop as many times as necessary to be sure you can deal with the longest variable you'll need to work with.
And another general strategy hint - first, create syntax to deal with one specific variable and one specific delimiter. Once this works, start working on a macro, keeping in mind that the only purpose of the macro is to recreate the same working syntax, only changing the parameters of variable name and delimiter.
With my new understanding of the SPSS macro logic (thanks to #eli-k) the problem was quite easy to solve. Here is the working solution.
* DEFINE DATA.
DATA LIST /ID 1 (F) VAR1 2-5 (A) VAR2 6-12 (A).
BEGIN DATA
11, 28
21 1, 3
33, 12, 3, 1
4 2, 8
END DATA.
* DEFINE MACRO.
DEFINE #split_var(src_var = !TOKENS(1)
/dmmy_var_label = !DEFAULT(dmmy) !TOKENS(1)
/dmmy_var_lvls = !TOKENS(1))
NUMERIC !CONCAT(!dmmy_var_label,1) TO !CONCAT(!dmmy_var_label, !dmmy_var_lvls) (F1).
VECTOR #dmmy_vec = !CONCAT(!dmmy_var_label,1) TO !CONCAT(!dmmy_var_label, !dmmy_var_lvls).
STRING #char (A1).
LOOP #pos=1 TO char.length(!src_var).
COMPUTE #char = char.substr(!src_var, #pos, 1).
DO IF (#char NE "," AND #char NE " ").
COMPUTE #index = NUMBER(#char, F1).
COMPUTE #dmmy_vec(#index) = 1.
END IF.
END LOOP.
RECODE !CONCAT(!dmmy_var_label,1) TO !CONCAT(!dmmy_var_label, !dmmy_var_lvls) (SYSMIS=0) (ELSE=COPY).
EXECUTE.
!ENDDEFINE.
* CALL MACRO.
#split_var src_var=VAR2 dmmy_var_lvls=8.

Passing a varying number of macro arguments as a string in System Verilog

I have an existing code that uses some macro definitions in order to display messages from my test cases. I want to change the implementation of these macros, however, as these macros are extensively used in already existing testcases, I am looking to reimplement their functionality witout having to modify how the macros are used.
Currently the macros are difined as such:
`define My_Info $write("INFO:"); $display
`define My_Error $write("ERROR:"); $display
In the testcases, example calls of these macros include:
`My_Info("This is an info message with arguments %0d, %0d, and %0d", var1, var2, var3);
`My_Info("My_ID", $psprintf("ID : %s", var4));
`My_Error("Failed to open file: %s ", fname);
Currently $display, displays the messages in the brakets.
What I want to do is to define the macros in a way that these messages in the brakets of the macro calls could be passed as a string argument to a function (for example the function my_msg(msg) where msg is a string and my_msg is a function that formats and returns this string to the log file.
My issue is, because in the testcases the macro calls have varying number of arguments as seen in the example above, I am not sure how to define the macros in a universal way.
Currenlty my solution is to define the macros like:
`define My_Info(string=" ", var1= , var2= , var3= , var4= ) my_msg($sformat(s,var1, var2, var3, var4)
But this relies on a finite number of arguments (in this case 5).
Is there a more elegant way of doing it?
You can workaround the lack of varying numbers of macro arguments by requiring an extra set of ()'s.
module top;
`define my_error(msg) begin $error({"My ID:",$sformatf msg}); end
int a,b;
initial begin
`my_error( ("hello") )
`my_error( ("A = %0d B = %0d", a,b) )
end
endmodule

Quote-unquote idiom in Julia & concatenating Expr objects

I'd like to write a simple macro that shows the names & values of variables. In Common Lisp it would be
(defmacro dprint (&rest vars)
`(progn
,#(loop for v in vars
collect `(format t "~a: ~a~%" ',v ,v))))
In Julia I had two problems writing this:
How can I collect the generated Expr objects into a block? (In Lisp, this is done by splicing the list with ,# into progn.) The best I could come up with is to create an Expr(:block), and set its args to the list, but this is far from elegant.
I need to use both the name and the value of the variable. Interpolation inside strings and quoted expressions both use $, which complicates the issue, but even if I use string for concatenation, I can 't print the variable's name - at least :($v) does not do the same as ',v in CL...
My current macro looks like this:
macro dprint(vars...)
ex = Expr(:block)
ex.args = [:(println(string(:($v), " = ", $v))) for v in vars]
ex
end
Looking at a macroexpansion shows the problem:
julia> macroexpand(:(#dprint x y))
quote
println(string(v," = ",x))
println(string(v," = ",y))
end
I would like to get
quote
println(string(:x," = ",x))
println(string(:y," = ",y))
end
Any hints?
EDIT: Combining the answers, the solution seems to be the following:
macro dprint(vars...)
quote
$([:(println(string($(Meta.quot(v)), " = ", $v))) for v in vars]...)
end
end
... i.e., using $(Meta.quot(v)) to the effect of ',v, and $(expr...) for ,#expr. Thank you again!
the #show macro already exists for this. It is helpful to be able to implement it yourself, so later you can do other likes like make one that will show the size of an Array..
For your particular variant:
Answer is Meta.quot,
macro dprint(vars...)
ex = Expr(:block)
ex.args = [:(println($(Meta.quot(v)), " = ", $v)) for v in vars]
ex
end
See with:
julia> a=2; b=3;
julia> #dprint a
a = 2
julia> #dprint a b
a = 2
b = 3
oxinabox's answer is good, but I should mention the equivalent to ,#x is $(x...) (this is the other part of your question).
For instance, consider the macro
macro _begin(); esc(:begin); end
macro #_begin()(args...)
quote
$(args...)
end |> esc
end
and invocation
#begin x=1 y=2 x*y
which (though dubiously readable) produces the expected result 2. (The #_begin macro is not part of the example; it is required however because begin is a reserved word, so one needs a macro to access the symbol directly.)
Note
julia> macroexpand(:(#begin 1 2 3))
quote # REPL[1], line 5:
1
2
3
end
I consider this more readable, personally, than pushing to the .args array.

Variable labels in SPSS Macro

I'm new to the SPSS macro syntax and had a hard time trying to label variables based on a simple loop counter. Here's what I tried to do:
define !make_indicatorvars()
!do !i = 1 !to 10.
!let !indicvar = !concat('indexvar_value_', !i, '_ind')
compute !indicvar = 0.
if(indexvar = !i) !indicvar = 1.
variable labels !indicvar 'Indexvar has value ' + !quote(!i).
value labels !indicvar 0 "No" 1 "Yes".
!doend
!enddefine.
However, when I run this, I get the following warnings:
Warning # 207 on line ... in column ... Text: ...
A '+' was found following a text string, indicating continuation, but the next non-blank character was not a quotation mark or an apostrophe.
Warning # 4465 in column ... Text: ...
An invalid symbol appears on the VAR LABELS command where a slash was
expected. All text through the next slash will be be ignored.
Indeed the label is then only 'Indexvar has value '.
Upon using "set mprint on printback on", the following code was printed:
variable labels indexvar_value_1_ind 'Indexvar has value ' '1'
So it appears that SPSS seems to somehow remove the "+" which is supposed to concatenate the two strings, but why?
The rest of the macro worked fine, it's only the variable labels command that's causing problems.
Try:
variable labels !indicvar !quote(!concat('Indexvar has value ',!i)).
Also note:
compute !indicvar = 0.
if(indexvar = !i) !indicvar = 1.
Can be simplified as:
compute !indicvar = (indexvar = !i).
Where the right hand side of the COMPUTE equation evaluates to equal TRUE a 1 (one) is assigned else if FALSE a 0 (zero) is assigned. Using just a single compute in this way not only reduce the lines of code, it will also make the transformations more efficient/quicker to run.
You might consider the SPSSINC CREATE DUMMIES extension command. It will automatically construct a set of dummies for a variable and label them with the values or value labels. It also creates a macro that lists all the variables. There is no need to enumerate the values. It creates dummies for all the values in the data.
It appears on the Transform menu as long as the Python Essentials are installed. Here is an example using the employee data.sav file shipped with Statistics.
SPSSINC CREATE DUMMIES VARIABLE=jobcat
ROOTNAME1=job
/OPTIONS ORDER=A USEVALUELABELS=YES USEML=YES OMITFIRST=NO
MACRONAME1="!jobcat".

SAS IML use of Mattrib with Macro (symget) in a loop

In an IML proc I have several martices and several vectors with the names of columns:
proc IML;
mydata1 = {1 2 3, 2 3 4};
mydata2 = {1 2, 2 3};
names1 = {'red' 'green' 'blue'};
names2 = {'black' 'white'};
To assign column names to columns in matrices one can copypaste the mattrib statement enough times:
/* mattrib mydata1 colname=names1;*/
/* mattrib mydata2 colname=names2;*/
However, in my case the number of matrices is defined at execution, thus a do loop is needed. The following code
varNumb=2;
do idx=1 to varNumb;
call symputx ('mydataX', cat('mydata',idx));
call symputx ('namesX', cat('names',idx));
mattrib (symget('mydataX')) colname=(symget('namesX'));
end;
print (mydata1[,'red']) (mydata2[,'white']);
quit;
however produces the "Expecting a name" error on the first symget.
Similar question Loop over names in SAS-IML? offers the macro workaround with symget, what produces an error here.
What is the correct way of using mattrib with symget? Is there other way of making a variable from a string than macro?
Any help would be appreciated.
Thanks,
Alex
EDIT1
The problem is in the symget function. The &-sign resolves the name of the matrix contained in the macro variable, the symget only returns the name of the macro.
proc IML;
mydata1 = {1 2 3};
call symputx ('mydataX', 'mydata1');
mydataNew = (symget('mydataX'));
print (&mydataX);
print (symget("mydataX"));
print mydataNew;
quit;
results in
mydata1 :
1 2 3
mydata1
mydataNew :
mydata1
Any ideas?
EDIT2
Function value solves the symget problem in EDIT1
mydataNew = value(symget('mydataX'));
print (&mydataX);
print (value(symget("mydataX")));
print mydataNew;
The mattrib issue but persists.
SOLVED
Thanks Rick, you have opened my eyes to CALL EXECUTE() statement.
When you use CALL SYMPUTX, you should not use quotes for the second argument. Your statement
call symputx ('mydataX', 'mydata1');
assigns the string 'mydata1' to the macro variable.
In general, trying to use macro variables in SAS/IML loops often results in complicated code. See the article Macros and loops in the SAS/IML language for an indication of the issues caused by trying to combine a macro preprocessor with an interactive language. Because the MATTRIB statement expects a literal value for the matrix name, I recomend that you use CALL EXECUTE rather than macro substitution to execute the MATTRIB statement.
You are also having problems because a macro variable is always a scalar string, whereas the column name is a vector of strings. Use the ROWCAT function to concatenate the vector of names into a single string.
The following statements accomplish your objective without using macro variables:
/* Use CALL EXECUTE to set matrix attributes dynamically.
Requires that matrixName and varNames be defined at main scope */
start SetMattrib;
cmd = "mattrib " + matrixName + " colname={" + varNames + "};";
*print cmd; /* for debugging */
call execute(cmd);
finish;
varNumb=2;
do idx=1 to varNumb;
matrixName = cat('mydata',idx);
varNames = rowcat( value(cat('names',idx)) + " " );
run SetMattrib;
end;