I am a beginner SAS user with much experience in VBA and am having a hard time figuring out User defined functions in SAS.
I am having several problems using variables in User defined functions, but I think the two listed below are probably a related issue and would likely solve the rest of them.
I) How do you use a variable in a macro function from within a user defined function?
proc fcmp outlib = sasuser.funcs.trial;
function testNumbers(testvar $) $;
length testing $ 100;
lencheck = %length(testvar);
return (lencheck);
endsub;
run;
options cmplib = sasuser.funcs;
%put %sysfunc(testnumbers(short));
No matter what the input to the function is, the result is always 7, which matches the length of the input variable name "testvar" If I change the variable name, it changes the result. I've tried putting an ampersand in front of the variable name, but this doesn't work (it just makes the result 8...). I can get the function to return the input by putting in "return (testvar)" but can't figure out how to get the length function to work.
II) How do you define a variable as numeric in the context of the user defined function?
proc fcmp outlib=sasuser.funcs.trial;
function testNumbers(testvar $) $;
length testvar $ 100;
myNumber = 5
testNum = put(myNumber, 2.);
tempPath = %substr(1234567890, 3, 2)
tempPath1 = %substr(1234567890, 3, myNumber)
tempPath2 = %substr(1234567890, 3, testNum)
tempPath3 = %substr(1234567890, 3, put(myNumber, 2.))
return (tempPath);
endsub;
run;
The first tempPath works and returns "34" as expected. But tempPath1, tempPath2 and tempPath3 all return errors. The error is that Argument 3 to macro function %substr is not a number. For tempPath3 there is an additional error that a required operateor not found in the expression.
Note: I am aware that these functions do not do anything worthwhile. These are simplified as I am trying to learn the language and the possibilities. There may be other problems even with the simple code provided, and any advice on that would be appreciated.
What I was actually trying to code was a function that will allow for dynamically changing the library being used (so if a temp flag is set, everything will go into the Work directory, but if not, it will go to the final production location). If there is a better solution than a UDF for this, I'd love to hear this too.
The macro processor evaluates before the results are passed onto base SAS for processing.
Since your program uses this macro logic.
lencheck = %length(testvar);
The macro processor will calculate %length(testvar) which is 7 since that is how many characters are in the string testvar. It is the same as if wrote this statement:
lencheck = 7 ;
If you want the function to find the length of the variable TESTVAR then you need to use the LENGTH() function and not the macro function %LENGTH().
You have a similar issue with your use of the %SUBSTR() macro function instead of the function SUBSTR() in your second example.
Related
I obtain an error in SAS while creating a new field in a dataset recalling a numeric macro variable. Here there's an example.
data input;
input cutoff_1 cutoff_2;
datalines;
30 50
;
data db;
input outstanding;
datalines;
1000.34
2000.45
3000.90
5000.98
8000.02
;
data _null_;
set input;
call symput("perc1",cutoff_1);
call symput("perc2",cutoff_2);
run;
proc univariate data=db noprint;
var outstanding;
output out=test
pctlpts = &perc1. &perc2.
pctlpre = P_;
run;
data test2;
set test;
p_&perc1._round=round(P_&perc1.,1);
p_&perc2._round=round(P_&perc2.,1);
run;
From the log it seems that the macros &perc. are solved, but that it's not possible to use those results (30, 50) to name a new variable in the dataset test2. What am I missing?
When you ask normal SAS code to use a numeric value in a place where a character value is required (both arguments to CALL SYMPUT() require character values) then SAS will convert the number into a string using the BEST12. If the value does not require all 12 characters it will be right aligned. So you created macro variables with leading spaces. The leading spaces make no difference for generating pctlpts= option values as extra spaces will not matter there. But having the leading spaces mean you are generating code like:
p_ 30_round=round(P_ 30,1);
You should use the modern (probably over 20 years old) CALL SYMPUTX() function instead. That function will remove leading/trailing spaces from the second argument when creating the macro variable. It also accepts a numeric value as the second argument converting the number into a character string using a format more like BEST32 instead of BEST12.
call symputx("perc1",cutoff_1);
call symputx("perc2",cutoff_2);
The only cases where you should ever use the ancient CALL SYMPUT() function is when you actually need to create macro variables that contain leading and/or trailing spaces.
Other solutions are to remove the spaces in the function call
call symput("perc1",strip(put(cutoff_1,best32.)));
or after generating the macro variables.
%let perc1=&perc1;
One possible solution is to use call symputx and not call symput. With call symputx the variables created are globally defined.
I have procedure which splits comma separated string.
The string would be passed at runtime in ["",""] format.
I need to call procedure where string is passed on runtime.
However if i run:
begin push_data(100,'q'''||:data);end;
It doesn't remove brackets and i need to pass string as :data. And this is eactly how i need to call and get results same as above.
Is this what you're looking for?
declare
v_txt varchar2(4000) := '["Project title afor BYU heads","The values are,\n exactly up to the requirement and analysis done by the team.
Also it is difficult to,\n prepare a scenario notwithstanding the fact it is difficult. This user story is going to be slightly complex however it is up to the team","Active","Disabled","25 tonnes of fuel","www.examplesites.com/html.asp&net;","Apprehension","","","","25","Stable"]';
begin
push_data(100, substr(v_txt, 2, length(v_txt) - 1));
end;
/
I am trying to write a macro that creates a text file using parameter value. I understand all SAS parameters are passed as text, so i need to convert the text to numeric. Using INPUT for this but still getting a syntax error. Appreciate the help. Thank you.
code:
%macro test(n_var);
data _null_;
file"c:/temp/test.txt" TERMSTR=crlf;
put ;
put "(numeric variable passed = "input(&n_var,8.)")";
put ;
run;
%mend;
%test(n_var=100);
Log:
SYMBOLGEN: Macro variable N_VAR resolves to 100
NOTE: Line generated by the macro variable "N_VAR".
39 100
___
22
76
MPRINT(TEST): put "(numeric variable passed = "input(100,8.)")";
MPRINT(TEST): put ;
MPRINT(TEST): run;
ERROR 22-322: Syntax error, expecting one of the following: a name, arrayname, _ALL_, _CHARACTER_, _CHAR_, _NUMERIC_.
ERROR 76-322: Syntax error, statement will be ignored.
All SAS macro symbols (aka variables) are text. SAS macro parameters are text.
Your use case probably does not need to convert text to numeric.
Consider:
%let x = 100;
data _null_;
my_num_var = &x;
run;
The resolution of the macro variable (or perhaps better understood as 'symbol') are the letters 1 0 0, but with respect to text to be interpreted as SAS code. The data step compiler infers my_num_var is numeric from the line it sees as
my_num_var = 100;
There are some use cases where you may want to test that a macro parameter can be interpreted as a numeric value. Such use cases are probably beyond your needs at this time.
The INPUT function is one of those special DATA Step functions that is not available for use in SAS macro via the %sysfunc function. When you must 'input' a value in a 'pure' manner outside a DATA step, you will want to invoke the INPUTN or INPUTC functions via %sysfunc
%let evaluatedRepresentation = %sysfunc(inputn(&x,best8.));
The numeric evaluation of the inputn is converted to text and assigned to symbol evaluatedRepresentation.
If you are not in control of the callers to you macros in which you do ampersand evaluations the safer approach is to evaluate via SUPERQ to break code injections and other anamolies
%let evaluatedRepresentation = %sysfunc(inputn(%superq(x),best8.));
Is there a function SAS proc SQL which i can use to extract left part of the string.it is something similar to LEFT function sql server. in SQL I have left(11111111, 4) * 9 = 9999, I would like to something similar in SAS proc SQL. Any help will be appreciated.
Had an impression you want to repeat the substring instead of multiply, so I'm adding REPEAT function just for the curiosity.
proc sql;
select
INPUT(SUBSTR('11111111', 1, 4), 4.) * 9 /* if source is char */
, INPUT(SUBSTR(PUT(11111111, 16. -L), 1, 4), 4.) * 9 /* if source is number */
, REPEAT(SUBSTR(PUT(11111111, 16. -L), 1, 4), 9) /* repeat instead of multiply */
FROM SASHELP.CLASS (obs=1)
;
quit;
substr("some text",1,4) will give you "some". This function works the same way in a lot of SQL implementations.
Also, note that this is a string function, but in your example you're applying it to a number. SAS will let you do this, but in general it's wise to control you conversion between strings and numbers with put() and input() functions to keep your log clean and be sure that you're only converting where you actually intend to.
You might be looking for SUBSTRN function..
SUBSTRN(string, position <, length>)
Arguments
string specifies a character or numeric constant, variable,
or expression.
If string is numeric, then it is converted to a character value that
uses the BEST32. format. Leading and trailing blanks are removed, and
no message is sent to the SAS log.
position is an integer that specifies the position of the first
character in the substring.
length is an integer that specifies the length of the substring. If
you do not specify length, the SUBSTRN function returns the substring
that extends from the position that you specify to the end of the
string.
As others have pointed out, substr() is the function you are looking for, although I feel that a more useful answer would also 'teach you how to fish'.
A great way to find out about SAS functions is to google sas functions by category which at the time of writing this post will direct you here:
SAS Functions and CALL Routines by Category
It's worth scanning through this list at least once just to get an idea of all of the functions available.
If you're after a specific version, you may want to include the SAS version number in your search. Note that the link above is for 9.2.
If you have scanned through all the functions, and still can't find what you are looking for, then your next option may be to write your own SAS function using proc fcmp. If you ever need assistance with doing this than I suggest posting a new question.
I've just realised how useful it is to reduce the length of numeric (dummies and integers) variables, since it saves me both time and diskspace. However, I think it's convenient to use the length statement at the end of my code rather than before mentioning "set" (the latter way is how SAS bloggers and other experts mostly recommend you to use the length statement).
So, is there a difference between these two ways (see the examples below)? I can't find any differences in the output, but I'm a bit worried that I might be doing something wrong. Can you please explain what the difference is (if there is one) and why you would prefer to do it the either way.
Thanks in advance!
This is an example of how I use the length statement:
data b;
set a;
dummy = income > 10 000;
label "dummy = Income > 10 000";
length dummy 3;
run;
But here is how the experts recommend you to do it.
data b;
length dummy 3;
set a;
dummy = income > 10 000;
label "dummy = Income > 10 000";
run;
I would swear that in previous versions of SAS, you'd not be able to override the length of the variable once defined by a length statement or "inherited" from source data.
I remember some notes or warnings about "length of the variable ... was already set".
In SAS 9.3 the code:
data a;
length income dummy 8.;
income = 1234567890;
dummy = 1234567890;
output;
stop;
run;
data b;
set a;
attrib dummy length = 3 label = "dummy = Income > 10 000";
dummy = income > 10000;
length dummy 8;
length dummy 5;
run;
creates a variable dummy with length 5, without any notes.
So it seems to me the behaviour has changed. Previously, I'd say you would end up with a variable as defined by first of explicit definition or appearance in source data.
However it surely does not help readability and maintainability of code to first assign values to variables and define basic properties of variables at the very end.
Btw the correct definition of label would be: label dummy = "dummy = Income > 10 000";
Alternatively you might prefer ATTRIB statement to specify various properties of single variable in single statement.
data b;
set a (drop = dummy);
attrib dummy length = 3 label = "dummy = Income > 10 000";
dummy = income > 10000;
run;
Numeric variables may have their length changed at any time, while character variables may have it only done prior to their creation. That's because a numeric variable's length only affects the output dataset; inside the PDV, numeric variables always have 8 bytes of precision regardless of any length statements. However, character variables may not have their lengths redefined, as the PDV length associated with a character variable is not fungible after it is initially defined (in a set statement or the first length/attrib/assignment for a character variable). See the documentation on LENGTH for more details (although not as many as I'd like to see).
That said, personally I prefer formatting and lengths up front rather than at the end. Part of this is so that anyone reading the program knows going in what the ultimate formats will be; but most of it is that some lengths/attribs must be done up front: character lengths, in particular, and any variable where you need to specify the type (numeric/character) ahead of time in order to ensure you end up with the right type. If you usually put lengths at the end, you'll end up with a mixture of some at front/some at end, and as such I'd rather do all at front to be more organized.