I have variable x = 2001. I want to produce a string that looks like this:
"TEST0106.xls". The 01 in that string is the last two digits of 2001, and the 06 is the last two digits of 2006 (x+5).
My current code is this:
%let x = 2001;
%let sub1 = %sysfunc(mod(&x, 100));
%let sub2 = %sysfunc(mod(&x+5, 100));
%let test = TEST&sub1&sub2.xls;
%put &test;
However, this just gives me "TEST16xls" since the 0 disappears in the modulus division and I'm not sure why the period isn't there. I believe I'll have to do some approach where I convert the numbers into characters and do a sub-string. How would I go about this task?
First, you don't need to use modulo arithmetic to do a substring in macro variable land, everything is text, so just use %substr.
Second, you can give an optional argument to %sysfunc telling it how to format the result; use the z2 format to tell it what you want. I think leaving the modulo is convenient here simply because it does give you that option. Otherwise you can use %sysfunc(putn( if you want to not use modulo.
Third, you need another . since the first one ends the macro variable (technically macro variables are & to . inclusive unless they hit another character that is invalid for macro variable names first).
%let x = 2001;
%let sub1 = %substr(&x,3,2);
%let sub2 = %sysfunc(mod(&x+5, 100),z2.);
%let test = TEST&sub1.&sub2..xls;
%put &test;
or
%let sub2 = %sysfunc(putn(&sub1.+5,z2.));
Related
Why is this macro not working?
%MACRO Cerved_recap;
%let i=2;
%DO i = 2 %TO &oggi.;
%let ieri = %sysevalf(&i.-1);
data work.initial_db_cerved;
set work.initial_db_cerved;
length esito_&i. $ 10;
format esito_&i. $char20.;
%if ENCOURS_TOTAL_&i. < ENCOURS_TOTAL_&ieri. %then %do; esito_&i.='total_out';
%end;
%else %do; esito_&i.= '-';
%end;
run;
%end;
%MEND Cerved_recap;
%Cerved_recap;
Here is a technical answer, to get a real answer explain in words what you are trying to do, and perhaps show an example of the working SAS code you are trying to use the macro to generate.
Since you set I to a digit string like 2 and set IERI to a digit string like 1 the test in your %IF statement will usually be false. It is comparing a string like ENCOURS_TOTAL_2 to another string like ENCOURS_TOTAL_1 and since the beginning of those two strings are exactly the same it just like comparing the digit 2 to the digit 1. The test will only be true when I has a value that is a power of 10 (10 100 1000 etc) because then you are comparing a string that starts with the digit 1 to a string that starts with the digit 9.
Perhaps you wanted to compare the values of SAS variables with those names?
If so then use actual SAS statements instead of macro statements.
if ENCOURS_TOTAL_&i. < ENCOURS_TOTAL_&ieri. then esito_&i.='total_out';
else esito_&i.= '-';
Some context:
I have a string of digits (not ordered, but with known range 1 - 78) and I want to extract the digits to create specific variables with it, so I have
"64,2,3" => var_64 = 1; var_02 = 2; var_03 = 1; (the rest, like var_01 are all set to missing)
I basically came up with two solutions, one is using a macro DO loop and the other one a data step DO loop. The non-macro solution was to fist initialize all variables var_01 - var_78 (via a macro), then to put them into an array and then to gradually set the values of this array while looping through the string, word-by-word.
I then realized that it would be way easier to use the loop iterator as a macro variable and I came up with this MWE:
%macro fast(w,l);
do p = 1 to &l.;
%do j = 1 %to 9;
if &j. = scan(&w.,p,",") then var_0&j. = 1 ;
%end;
%do j = 10 %to 78;
if &j. = scan(&w.,p,",") then var_&j. = 1 ;
%end;
end;
%mend;
data want;
string = "2,4,64,54,1,4,7";
l = countw(string,",");
%fast(string,l);
run;
It works (no errors, no warnings, expected result) but I am unsure about mixing macro-DO-loops and non-macro-DO-loops. Could this lead to any inconsistencies or should I just stay with the non-macro solution?
Your current code is comparing numbers like 1 to strings like "1".
&j. = scan(&w.,p,",")
It will work as long as the strings can be converted into numbers, but it is not a good practice. It would be better to explicitly convert the strings into numbers.
input(scan(&w.,p,","),32.)
You can do what you want with an array. Use the number generated from the next item in the list as the index into the array.
data want;
string = "2,4,64,54,1,4,7";
array var_ var_01-var_78 ;
do index=1 to countw(string,",");
var_[input(scan(string,index,","),32.)]=1;
end;
drop index;
run;
To begin, this is for a class, i dont like this language. Its simple Do loop to print the square root of numbers. The objective is to replace the value in the do loop with macro variables. Here is my source code:
%LET Start_Value = 1;
%LET Stop_Value = 5;
DATA sqrt_table;
DO &Start_Value. TO &Stop_Value.;
Sqrt_n = SQRT(&Start_Value.);
OUTPUT;
END;
RUN;
TITLE 'Square root table from 1 to 5';
PROC PRINT DATA = sqrt_table noobs;
RUN;
TITLE;
The Log says the error is in the DO &Start_Value. "Symbol is not recognized"
I followed the the source coude given, i have decalred the macros as they should be, and i am accessing them as i read to do so. What is the issue?
Macro code in general, and in this case specifically, is just used to replace constant text. First get a working DO loop without any macro variables and then replace the parts that you want to vary with the macro variable references.
So the basic syntax for an iterative DO loop is:
do VAR=START to END;
...
end;
Where VAR is a variable name and start and end are numerical expressions.
Compare that to the pattern of your attempt and you can see that you have left off the VAR= part.
Also the assignment statement is going to assign the same value to SQRT_N on every iteration of the DO loop. Because you have essentially written.
Sqrt_n = SQRT(1);
Remember macro variables are just ways to help you generate the program that you want SAS to actually run.
If you are begginer in SAS don't mix macro lanauge wit 4GL. Here is what you need.
%LET Start_Value = 1;
%LET Stop_Value = 5;
DATA sqrt_table;
DO i = &Start_Value. TO &Stop_Value.;
Sqrt_n = SQRT(i);
OUTPUT;
END;
RUN;
I'm looking for a way to use a normal variable value as a macro variable in a data step.
For example I have macro variable &statesList_Syphilis = AAA
and another macro variable &statesList_Giardia = BBB
And in a data step I have a variable Germ wich contains 2 rows: "Syphilis" and "Giardia".
In my data step I need to find AAA when iterating over the first row when Germ="Syphilis"
and BBB when iterating over the second row, when Germ="Giardia"
an attempt would look like this
%let statesList_Syphilis = AAA;
%let statesList_Giardia = BBB;
data test;
set mytablewithgerms; * contains variable Germ ;
* use germ and store it in &germ macro variable ;
* something like %let germ = germ; or call symput ('germ',germ);
* I want to be able to do this;
xxx = "&&statesList_&germ"; * would give xxx = "AAA" or xxx = "BBB";
* or this;
&&statesList_&germ = "test"; * would give AAA = "test" or BBB = "test";
run;
I don't think this is possible, but I figured I would ask just to be sure.
Thanks!
EDIT (Following questions in the comments, I'm adding context to my specific problem, but I feel this is making things more complicated):
This was an attempt to simplify the problem.
In reality AAA and BBB are long lists of words
like
"asymptomatic_1 fulminant_1 chronic_1 chronic_1 fatalFulminant_1 hepatocellular_1 compensated_1 hepatocellular_2 decompensated_1 fatalHepatocellular_1 fatalHepatocellular_2 fatalDecompensated_1"
And I don't want to store this long string in a variable, I want to iterate each word of this string in a do loop with something like:
%do k=1 %to %sysfunc(countw(&&statesList_&germ));
%let state = %scan(&&statesList_&germ, &k);
* some other code here ;
%end;
EDIT2:
here is a more complete view of my problem:
%macro dummy();
data DALY1;
* set lengths ;
length Germ $10 Category1 $50 Category2 $50 AgeGroupDALY $10 Gender $2 value 8 stateList$999;
* make link to hash table ;
if _n_=1 then do;
*modelvalues ----------------;
declare hash h1(dataset:'modelData');
h1.definekey ('Germ', 'Category1', 'Category2', 'AgeGroupDALY', 'Gender') ;
h1.definedata('Value');
h1.definedone();
call missing(Germ, Value, Category1, Category2);
* e.g.
rc=h1.find(KEY:Germ, KEY:"ssssssssss", KEY:"ppppppppppp", KEY:AgeGroupDALY, KEY:Gender);
*states ---------------------;
declare hash h2(dataset:'states');
h2.definekey ('Germ') ;
h2.definedata('stateList');
h2.definedone();
end;
set DALY_agregate;
put "°°°°° _n_=" _n_;
DALY=0; * addition of terms ;
rc2=h2.find(KEY:Germ); * this creates the variable statesList;
put "statesList =" statesList;
* here i need statesList as a macro variable,;
%do k=1 %to %sysfunc(countw(&statesList)); *e.g. acute_1 asymptomatic_1 ...;
%let state = %scan(&statesList, &k);
put "=== &k &state";
&state = 1; * multiplication of terms ;
* more code here;
%end;
run;
%mend dummy;
%dummy;
EDIT3:
The input dataset looks like this
Germ AgeGroup1 AgeGroup2 Gender Cases Year
V_HBV 15-19 15-19 M 12 2015
V_HBV 15-19 15-19 M 8 2016
V_HBV 20-24 20-24 F 37 2011
V_HBV 20-24 20-24 F 46 2012
V_HBV 20-24 20-24 F 66 2013
The output dataset will add variables contained in the string defined by the macro variable which depends on the Germ.
e.g. for V_HBV it will create these variables: asymptomatic_1 fulminant_1 chronic_1 chronic_1 fatalFulminant_1 hepatocellular_1 compensated_1 hepatocellular_2 decompensated_1 fatalHepatocellular_1 fatalHepatocellular_2 fatalDecompensated_1
I'm not following the big picture, but one of the previous iterations of your question had some code (pseudo code) that illustrates possible confusion about how the macro language works. Consider this step:
data _null_;
germ="Syph";
call symput('germ',germ);
%let Germ=%sysfunc(cats(germ));
put "germ = &germ";
run;
%put &germ;
The log from executing that in a fresh SAS session shows:
1 data _null_;
2 germ="Syph";
3 call symput('germ',germ);
4 %let Germ=%sysfunc(cats(germ));
5 put "germ = &germ";
6 run;
germ = germ
7 %put &germ;
Syph
Now let's talk about what's happening. I'll use the line numbers from the log.
Line 2 assigns text string Syph to data step variable germ. Nothing special.
Line 3 creates a macro variable named Germ, and assigns in the value of the datastep variable germ. So it assigns it the value Syph. This CALL SYMPUT statement executes when the data step executes.
Line 4 is a macro %let statement. It creates a macro variable named Germ, and assigns it the value germ. Because this is a macro statement, it executes before any of the DATA STEP code has executed. It does not know about data step variables. Line 4 is equivalent to %let Germ=germ. To the macro language, the right hand side is just a four-character string germ. It is not the name of a data step variable. %syfunc(cats()) is doing nothing, because there is no list of items to concatenate.
Line 5 is a data step PUT statement. The macro reference &germ is resolved while the data step is compiling. At this point the macro variable germ resolves to Germ because the %LET statement has executed (the CALL SYMPUT statement has not executed yet).
Line 7 is a %PUT statement that executes after the DATA NULL step has completed (and after the CALL SYMPUT has written the value Syph to macro variable Germ).
As a general principle, it is difficult (and unusual) to have a single data step in which you are using data to create a macro variable (e.g. via call symput) and using that macro variable in the same step (i.e. referencing the macro variable). Macro references are resolved before any of the data step code executes.
Typically if your data are already in a dataset, you can get what you want with data step statements (DO loops rather than %DO loops, etc). Or alternatively you can use one DATA step to generate your macro variables, and a second DATA step can reference them.
Hope that helps.
I have a list of dataset variables in a macro variable called &allvars. I have another macro variable called &contlist that contains the variables in &allvars that are continuous. I'm try to extract the variables in &allvars which are not in &contlist, i.e. are not continuous.
This is what I have:
%let binlist = &allvars;
%let i = 1;
%do %until( %scan(&var, &i, %str( )) = );
%let v = %scan(&var, &i, %str( ));
%let j = 1;
%do %until( %scan(&contlist, &j, %str( )) = );
%let c = %scan(&contlist, &j, %str( ));
%if %upcase(&v) = %upcase(&c) %then
%let binlist = %sysfunc(tranwrd(&binlist, &v, %str()));
%let j = %eval(&j + 1);
%end;
%let i = %eval(&i + 1);
%end;
%let binlist = %sysfunc(compbl(&binlist));
However, this causes a problem when variables have similar names, like so:
%let allvars = x x1 x2 x3;
%let contlist = x x1 x2;
The desired output here would be only x3, but instead this produces 1 2 3 because of the call to TRANWRD.
My only other thought would be to output both lists to datasets, merge them, keep only the records from allvars not in contlist, then create binlist from those records. However, there has to be a simpler, more elegant solution.
Any ideas?
There isn't a good solution for your problem-as-stated. There are some messy ones (more messy than your provided one, which I consider pretty messy). Perl regular expression is probably your best bet.
Ultimately, the right answer is to figure this out from datasets. Whether that means you create a dataset with your macro variable (messy, sure), or store this in the first place in datasets and pull out the pieces you need for various purposes (the right way to do it), that's the best solution. Macro code like this is messy, error-prone, easily harmed by changes, and hard to read. Joining two datasets to each other will get you the right answer pretty quickly with a low error rate, will be obvious to even a fairly novice programmer, and be less of a headache overall.