SAS put variable format in title - sas

I have a variable called agegroup which has many categories, eg: 1="0-5" 2="6-10" etc.
If I create a macro to print data by agegroup and wanted to have age group format in the title, how can I do this?
%macro ageprint(agegrp=1);
proc print data=age;
title "Age group &agegrp";
run;
%mend;
if I run this macro I would like the title be printed as "Age group 0-5" instead of "age group 1".
Anyone has hints?
Thanks!

You can use the #byval(age) option as well, it takes on the formatted value in the title:
proc format ;
value grpformat
0 - 12 = '0 - 12'
12 - 14 = '12 - 14'
other = 'other'
;
run;
proc sort data=sashelp.class out=class; by age; run;
proc print data=class;
by age;
format age grpformat.;
title "Age Group #byval(age)";
run;
You can still use the same method if you need a macro to control where the output was going for example:
%macro ageprint(agegrp=1);
proc print data=age;
by agegrp;
title "Age group #byval(agegrp)";
run;
%mend;

Something like this?
proc format ;
value grpformat
0 - 5 = '0 - 5'
6 - 10 = '6 - 10'
other = 'other'
;
run;
%macro ageprint(agegrp);
proc print data=age;
where agegrp=&agegrp;
title "Age group %sysfunc(putn(&agegrp, grpformat.))";
run;
%mend;
%ageprint(agegrp=2);
%ageprint(agegrp=8);

Related

Using upcase function in SAS programming?

Very new to SAS programming 0.0
I am trying to change the title "Listing of Data Set Health" to all uppercase and what I am doing isn't working. PLS HELP.
proc format;
value $Gender
'M'='Male'
'F'='Female'
other= 'Unknown'; * Handle Missing Values;
run;
data health;
infile '/folders/myfolders/health.txt' pad;
input #1 Subj $3.
#4 Gender $1.
#5 (Age HR) (2.)
#9 (SBP DBP Chol) (3.);
if Chol gt 200 then do;
Stoke_Risk = 'High';
LDL_Group = 'Bad';
end;
if Age le 21 then Age_Group = 1;
else if Age le 59 then Age_Group = 2;
else if Age ge 60 then Age_Group = 3;
format Gender $Gender.; *this line could be under data or proc
print;
Current_Year = year(today()); *current year based on today and year function;
Short_Gender = lowcase(Gender); *lower case function for string;
ABP = mean(SBP, DBP); *mean of blood pressure;
run;
title "Listing of Data Set Health";
proc print data=health;
ID Subj;
run;
The title statement is a global statement that is used in open code. If you would like it to always be upper-case, you will want to type your title directly in upper-case:
title "LISTING OF DATA SET HEALTH";
If you want to be able to have it always be in upper-case no matter what you type, you will need to delve into the SAS Macro Facility and macro functions. This is a more advanced aspect of SAS that you will get into later.
The %upcase() macro function can be used in open code to convert any text to upper-case.
title "%upcase(listing of data set health)";
Note that this function differs from upcase(), which you will use in the data step. Functions starting with % are special macro functions.
You can explicitly change it to uppercase in the title statement:
title "LISTING OF DATA SET HEALTH";
If you want to change the title dynamically, you could write a macro like:
%let title = "Listing of Data Set Health";
title "%upcase(&title.)";

how to vertically sum a range of dynamic variables in sas?

I have a dataset in SAS in which the months would be dynamically updated each month. I need to calculate the sum vertically each month and paste the sum below, as shown in the image.
Proc means/ proc summary and proc print are not doing the trick for me.
I was given the following code before:
`%let month = month name;
%put &month.;
data new_totals;
set Final_&month. end=end;
&month._sum + &month._final;
/*feb_sum + &month._final;*/
output;
if end then do;
measure = 'Total';
&month._final = &month._sum;
/*Feb_final = feb_sum;*/
output;
end;
drop &month._sum;
run; `
The problem is this has all the months hardcoded, which i don't want. I am not too familiar with loops or arrays, so need a solution for this, please.
enter image description here
It may be better to use a reporting procedure such as PRINT or REPORT to produce the desired output.
data have;
length group $20;
do group = 'A', 'B', 'C';
array month_totals jan2020 jan2019 feb2020 feb2019 mar2019 apr2019 may2019 jun2019 jul2019 aug2019 sep2019 oct2019 oct2019 nov2019 dec2019;
do over month_totals;
month_totals = 10 + floor(rand('uniform', 60));
end;
output;
end;
run;
ods excel file='data_with_total_row.xlsx';
proc print noobs data=have;
var group ;
sum jan2020--dec2019;
run;
proc report data=have;
columns group jan2020--dec2019;
define group / width=20;
rbreak after / summarize;
compute after;
group = 'Total';
endcomp;
run;
ods excel close;
Data structure
The data sets you are working with are 'difficult' because the date aspect of the data is actually in the metadata, i.e. the column name. An even better approach, in SAS, is too have a categorical data with columns
group (categorical role)
month (categorical role)
total (continuous role)
Such data can be easily filtered with a where clause, and reporting procedures such as REPORT and TABULATE can use the month variable in a class statement.
Example:
data have;
length group $20;
do group = 'A', 'B', 'C';
do _n_ = 0 by 1 until (month >= '01feb2020'd);
month = intnx('month', '01jan2018'd, _n_);
total = 10 + floor(rand('uniform', 60));
output;
end;
end;
format month monyy5.;
run;
proc tabulate data=have;
class group month;
var total;
table
group all='Total'
,
month='' * total='' * sum=''*f=comma9.
;
where intck('month', month, '01feb2020'd) between 0 and 13;
run;
proc report data=have;
column group (month,total);
define group / group;
define month / '' across order=data ;
define total / '' ;
where intck('month', month, '01feb2020'd) between 0 and 13;
run;
Here is a basic way. Borrowed sample data from Richard.
data have;
length group $20;
do group = 'A', 'B';
array months jan2020 jan2019 feb2020 feb2019 mar2019 apr2019 may2019 jun2019 jul2019 aug2019 sep2019 oct2019 oct2019 nov2019 dec2019;
do over months;
months = 10 + floor(rand('uniform', 60, 1));
end;
output;
end;
run;
proc summary data=have;
var _numeric_;
output out=temp(drop=_:) sum=;
run;
data want;
set have temp (in=t);
if t then group='Total';
run;

SAS macros multiple statements

I start with some example. I have "schools" variable like "harvard and oxford", "only harvard" etc. I want to find in "schools" variable only these cases which contain only one school name ("place" variable). I have "place" variable in dataset bbb which I put in listplace ("harvard!oxford!wse), "opinion" variable which I put in listopinion ("8!8!9) and "country" variable which I put in listcountry ("usa!uk!france"). I want to make macro which find in "schools" variable only these cases which contains two school names (zm1). Then, I want to add to them appropriate item from listopinion and listplace.
"Place" is school name, "opinion" is average rate and "country" is origin of this rate.
That's my first dataset:
place opinion country
- "harvard" 8 "usa"
- "oxford" 8 "uk"
- "wse" 9 "france"
That's my second dataset:
schools
- harvard best
- only harvard should be chosen
- we dont't have any values
- cake
That's what I want to have as results of my code (only two observation, not four as it is in "schools" variable):
schools opinion country
- "harvard very special" 8 "usa"
- "only harvard should be chosen" 8 "usa"
How can I make three %do until loops with %let statements?
data aaa;
input schools;
datalines;
"harvard and oxford"
"only harvard should be chosen"
"cheese, cake, pizza"
"we dont't have any values"
;
run;
data bbb;
input place opinion country;
datalines;
"harvard" 8 "usa"
"oxford" 8 "uk"
"wse" 9 "france"
;
run;
data ccc;
set
proc sql noprint;
select opinion
into :listopinion separated by "!"
from aaa
;
run;
quit;
proc sql noprint;
select country
into :listcountry separated by "!"
from aaa
;
run;
quit;
proc sql noprint;
select place
into :listplace separated by "!"
from aaa
;
run;
quit;
%let i_1 =1;
%let i_2 =1;
%let i_2 =1;
%do %until (%qscan(&listplace,&i_1,'!') = %str());
%do %until (%qscan(&listopinion,&i_2,'!') = %str());
%do %until (%qscan(&listcountry,&i_3,'!') = %str());
%let zm1=%scan(&listplace,&i_1,'!');
find(school,"&zm1")=1 or
%let zm2=%scan(&listopinion,&i_2,'!')
%let zm3=%scan(&lista3,&i_3,'!');
%let i_3=%eval(&i_3+1);
%end;
%let i_2=%eval(&i_2+1);
%end;
%let i_1=%eval(&i_1+1);
%end;
run;
It sounds like for your test of school you want to test if two or more of the names in the list exist in the value of school.
So if your list of schools to find looks like:
%let list=harvard|oxford|wse;
then you want to generate something like:
if sum( 0<findw(school,"harvard",,'i')
, 0<findw(school,"oxford",,'i')
, 0<findw(school,"wse",,'i')
) >= 2 then do;
...
end;
So you might use something like this:
if sum(
%let sep=;
%do i=1 %to %sysfunc(countw(&list,|));
&sep 0<findw(school,"%qscan(&list,&i,|)",,'i')
%let sep=,;
%end;
) >= 2 then do;
...
end;
I have no idea what you want to do with the other two lists. I think you want to use them to generate some SAS code where I have left ....

Checking whether the DS has variable value if the variable has missing values then drop the column

am passing a DS in macro parameter with var= if its corresponding variable has same value but the variables has all missing values then drop it.
DATA details;
INPUT id name $ dept $ salary;
datalines;
01 John . 10000
02 Mary . 20000
03 Priya . 30000
05 Ram . 25000
;
DATA newdetails;
INPUT var_name $ var_core $;
DATALINES;
id perm
name perm
dept perm
salary req
;
%macro core_check(inds=,newds=,var_core_val= );
proc sql noprint;
select var_name
into :varname separated by ' '
from &newds
where var_core="&var_core_val.";
quit;
%let nvar=&sqlobs;
%put &varname;
%do i=1 %to &nvar;
%let var&i=%scan(&varname,&i);
proc sql;
select count(*)
into :nobs
from &inds where &&var&i is not missing ;
%put this = &nobs;
quit;
%end;
%mend;
%core_check(inds=work.details,newds=work.newdetails,var_core_val=perm);
Here is one way to check for empty variables using the NLEVELS output of PROC FREQ. Note that the ODS table might not create the NMissLevels or NNonMissLevels variables based on the results.
So for your problem we could make a macro that takes as its input the name of the dataset to check, the dataset to create and the dataset with the metadata about which variables are optional. First get the list of variables to check. Then check them using PROC FREQ. Then use a data step to generate a macro variable with the list of empty variables.
%macro drop_optional(inds=,newds=,metadata= );
%local varlist n emptyvars ;
proc sql noprint;
select var_name
into :varlist separated by ' '
from &metadata
where var_core='perm'
;
quit;
%let n=&sqlobs;
%if (&n) %then %do;
ods output nlevels=nlevels;
proc freq nlevels data=&inds ;
tables &varlist / noprint ;
run;
data nlevels;
length TableVar $32 NLevels NMissLevels NNonMissLevels 8;
set nlevels end=eof;
nmisslevels+0;
nnonmisslevels=nlevels-nmisslevels;
length emptyvars $32767;
retain emptyvars;
if nnonmisslevels=0 then emptyvars=catx(' ',emptyvars,tablevar);
if eof then call symputx('emptyvars',emptyvars);
run;
%end;
data &newds;
set &inds (drop=&emptyvars);
run;
%mend drop_optional;
So let's use your sample data.
data details;
input id name $ dept $ salary;
datalines;
01 John . 10000
02 Mary . 20000
03 Priya . 30000
05 Ram . 25000
;
data metadata;
input var_name $ var_core $;
DATALINES;
id perm
name perm
dept perm
salary req
;
And call the macro.
%drop_optional(inds=details,newds=details_new,metadata=metadata);

Convert string with spaces to valid table name

I want to create a series of tables using SAS macro language, but the strings I am trying to pass through have spaces in them. Any ideas on what to add to make them valid table names?
%macro has_spaces(string);
proc sql;
create table &string. as
select
*
from my_table
;
quit;
%mend;
%has_spaces(has 2 spaces);
Thanks.
Another option is translate:
%macro has_spaces(string);
proc sql;
create table %sysfunc(translate(&string.,_,%str( ))) as
select *
from my_table
;
quit;
%mend;
You could do something like this as this will catch pretty much anything that isnt valid for a SAS table name and replace it with an underscore. We use a similar approach when creating file names based on customer names that contain all kinds of weird symbols and spaces etc... :
Macro Version:
%macro clean_tablename(iField=);
%local clean_variable;
%let clean_variable = %sysfunc(compress(&iField,,kns));
%let clean_variable = %sysfunc(compbl(&clean_variable));
%let clean_variable = %sysfunc(translate(&clean_variable,'_',' '));
&clean_variable
%mend;
Test Case 1:
%let x = "kjJDHF f'ke''''j d (kdj-328) *#& J#ld!!!";
%put %clean_variable(iField=&x);
Result:
kjJDHF_fkej_d_kdj328_Jld
Your test case:
%macro has_spaces(string);
proc sql;
create table %clean_variable(iField=&string) as
select *
from sashelp.class
;
quit;
%mend;
%has_spaces(has 2 spaces);
Result:
NOTE: Table WORK.HAS_2_SPACES created, with 19 rows and 5 columns.
FCMP Version:
proc fcmp outlib=work.funcs.funcs;
function to_valid_sas_name(iField $) $32;
length clean_variable $32;
clean_variable = compress(iField,'-','kns');
clean_variable = compbl(clean_variable);
clean_variable = translate(cats(clean_variable),'_',' ');
clean_variable = lowcase(clean_variable);
return (clean_variable);
endsub;
run;
Example FCMP Usage:
data x;
length invalid_name valid_name $100;
invalid_name = "kjJDHF f'ke''''j d (kdj-328) *#& J#ld!!!";
valid_name = to_valid_sas_name(invalid_name);
put _all_;
run;
Result:
invalid_name=kjJDHF f'ke''''j d (kdj-328) *#& J#ld!!! valid_name=kjjdhf_fkej_d_kdj-328_jld
Please note that there are limits to what you can name a table in SAS. Ie. it must start with an underscore or character, and must be no more than 32 chars long. You can add additional logic to do that if needed...
Compress out the spaces - one method is to use the datastep compress() function within a %SYSFUNC, e.g.
%macro has_spaces(string);
proc sql;
create table %SYSFUNC(compress(&string)) as
select
*
from my_table
;
quit;
%mend;
%has_spaces(has 2 spaces);
Just put the table name in quotes followed by an 'n' eg if your table name is "Table one"
then pass this as the argument "Table one"n.