Double Looping in SAS - sas

This problem might be trivial but I got stuck.
My problem is I've to go to each folder for a dataset and transpose the data.
I wrote the following code and it works fine.
OPTIONS MPRINT MLOGIC SYMBOLGEN;
%LET LOC=E:\folder;
%macro test1(k,l);
libname libary "&loc.\&k\&l.";
data dataset_&l.;
set libary.dataset_original;
run;
proc transpose data=dataset_&l. out=dataset_&l._T;
run;
%mend;
%test1(var_1,var'_1);
%test1(var_2,var'_2);
%test1(var_3,var'_3);
The issue with this code is it's not dynamic in terms of folder structure. E.g. if there's another 4 extra folders, I've to write "%test1(var_3,var'_3);"4 times.
So I tried writing the following code to make it more dynamic. But unfortunately it's not working. Can anybody please tell me where I'm making the mistake.
OPTIONS MPRINT MLOGIC SYMBOLGEN;
%LET LOC=E:\folder;
%let k=var_1 var_2 var_3;
%let l=var'_1 var'_2 var'_3;
%macro words(string);
%local count word;
%let count=1;
/* The third argument of the %QSCAN function specifies the delimiter */
%let word=%qscan(&string,&count,%str( ));
%do %while(&word ne);
%let count=%eval(&count+1);
%let word=%qscan(&string,&count,%str( ));
%end;
%eval(&count-1)
%mend words;
%macro test1(k,l);
libname libary "&loc.\&k\&l.";
data dataset_&l.;
set libary.dataset_original;
run;
proc transpose data=dataset_&l. out=dataset_&l._T;
run;
%mend;
%macro test();
%do i=1 %to %words(&k.);
%do j=1 %to %words(&l.);
%let var=%scan(&k.,&i.,str());
%let var1=%scan(&l.,&j.,str());
%test1(&var.,&var1.);
%end;
%end;
%mend;
%test();
Thanks!

Try this:
/* Set your base directory */
%let base = E:\Folder;
/* Pipe output from dir */
filename flist pipe "dir /s /b /a:-h &base";
/* Read files from pipe */
data files;
length file dir $ 200 name $ 50 ext $ 10;
infile flist;
input #1 file $ &;
/* File extension */
ext = scan(file, -1, ".");
/* File name */
name = scan(scan(file, -1, "\"), 1, ".");
/* Directory */
rfile = reverse(file);
dir = reverse(substr(rfile, index(rfile, "\") + 1));
/* Select only SAS datasets */
if upcase(ext) = "SAS7BDAT" then output;
drop rfile;
run;
/* Define a macro to process each file */
%macro trans_file(dir, name);
libname d "&dir";
proc transpose data = d.&name out = d.&name._t;
run;
libname d clear;
%mend trans_file;
/* Run on all files */
data _null_;
set files;
call execute(cats('%trans_file(', dir, ",", name, ");"));
run;
This gets the file list by submitting the Windows command dir. It gets all files in the specified directory and its subdirectories.
This approach then uses a simple macro that defines a data library, reads a dataset from the library, writes a transposed dataset to it, then clears it. The macro is called for each file in the list using call execute.

Related

running sas program for multiple parameters in loop

Very new to sas, need to perform export procedure for many datasets called data1, data2, data3 ... data10.
Is there a way to operationalize this? I've tried the following without success
LIBNAME input '/home/.../input';
LIBNAME output '/home/.../output';
%macro anynumber(number);
proc export data=input.data&number file="/home/.../output/data&number..dta" dbms=dta replace;
run;
%mend;
do i = 1 to 10;
%anynumber(i)
end;
run;
CALL EXECUTE is the recommended option but a macro loop is also a good option here.
You could loop it within the same macro as well. All options are illustrated below.
Usually when I see this type of coding though, my first thought is that someone isn't familiar with BY group processing.
data demo;
do i=1 to 10;
*make sure this string matches macro call;
str = catt('%anynumber(', i, ');');
*write records to log to check;
put str;
call execute(str);
end;
run;
Another option is a macro loop itself.
%macro loop_export(numLoops=10);
%do i=1 %to &numLoops;
%anywhere(&i);
%end;
%mend;
%loop_export(numLoops=10);
Putting them together:
%macro anynumber(number);
%do i=1 %to &number;
proc export data=input.data&number file="/home/.../output/data&i..dta" dbms=dta
replace;
run;
%end;
%mend;
*will export all 10;
%anyNumber(10);

Listing files UNIX SAS

i found a code that list all directories and sub directories from a path.
but it brings only the directory and the name of the file.
could you guys please help me how to bring the owner and the file size?
%macro list_files(dir);
%local filrf rc did memcnt name i;
%let rc=%sysfunc(filename(filrf,&dir));
%let did=%sysfunc(dopen(&filrf));
%if &did eq 0 %then %do;
%put Directory &dir cannot be open or does not exist;
%return;
%end;
%do i = 1 %to %sysfunc(dnum(&did));
%let name=%qsysfunc(dread(&did,&i));
%if %index(%qscan(&name,-1,'/'),.) gt 0 %then %do;
data _tmp;
length dir $512 name $100;
dir=symget("dir");
name=symget("name");
run;
proc append base=want data=_tmp;
run;quit;
%end;
%else %if %qscan(&name,2,.) = %then %do;
%list_files(&dir/&name)
%end;
%end;
%let rc=%sysfunc(dclose(&did));
%let rc=%sysfunc(filename(filrf));
%mend list_files;
DOPTNAME is your friend here.
Read SAS documentation for "Example 1: Using DOPTNAME to Retrieve Directory Attribute Information"
This example opens the directory with the fileref MYDIR, retrieves all system-dependent directory information items, writes them to the SAS log, and closes the directory:
%let rc=%sysfunc(filename(filrf, physical-name));
%let did=%sysfunc(dopen(&filrf));
%let infocnt=%sysfunc(doptnum(&did));
%do j=1 %to &infocnt;
%let opt=%sysfunc(doptname(&did, &j));
%put Directory information=&opt;
%end;
%let rc=%sysfunc(dclose(&did));
%macro test;
%let filrf=mydir;
%let rc=%sysfunc(filename(filrf, physical-name));
%let did=%sysfunc(dopen(&filrf));
%let infocnt=%sysfunc(doptnum(&did));
%do j=1 %to &infocnt;
%let opt=%sysfunc(doptname(&did, &j));
%put Directory information=&opt;
%end;
%let rc=%sysfunc(dclose(&did));
%mend test;
%test
Use the finfo() function. You can do this all in a single data step.
Documentation
/* Macro variable to store the directory. Do not keep ending slash. */
%let directory = /my/directory;
filename mydir "&directory";
data file_list;
length directory
filetype $15.
filename $1000.
owner $100.
size 8.
;
directory = "&directory/";
/* Open the directory */
did = dopen("mydir");
/* If the directory exists, loop through all files in the directory */
if(did) then do;
do i = 1 to dnum(did);
/* Get the filename */
filename = dread(did, i);
/* Create a filename variable and create a file ID to read its attributes */
rc = filename('infile', cats(directory,filename));
fid = fopen('infile');
owner = finfo(fid, 'Owner Name');
size = finfo(fid, 'File Size (bytes)');
/* Flag if it's a directory or file */
if(missing(size)) then filetype = 'Directory';
else filetype = 'File';
/* Close the file */
rc = fclose(fid);
output;
end;
end;
/* Close the directory */
rc = close(did);
keep directory filename owner size filetype;
run;

How to print two sets of variable series next to each other in SAS?

I have a SAS dataset where I keep 50 diagnoses codes and 50 diagnoses descriptions.
It looks something like this:
data diags;
set diag_list;
keep claim_id diagcode1-diagcode50 diagdesc1-diagdesc50;
run;
I need to print all of the variables but I need diagnosis description right next to corresponding diagnosis code. Something like this:
proc print data=diags;
var claim_id diagcode1 diagdesc1 diagcode2 diagdesc2 diagcode3 diagdesc3; *(and so on all the way to 50);
run;
Is there a way to do this (possibly using arrays) without having to type it all up?
Here's one approach then, using Macros. If you have other variables make sure to include them BEFORE the %loop_names(n=50) portion in the VAR statement.
*generate fake data to test/run solution;
data demo;
array diag(50);
array diagdesc(50);
do claim_id=1 to 100;
do i=1 to 50;
diag(i)=rand('normal');
diagdesc(i)=rand('uniform');
end;
output;
end;
run;
%macro loop_names(n=);
%do i=1 %to &n;
diag&i diagdesc&i.
%end;
%mend;
proc print data=demo;
var claim_ID %loop_names(n=20);
run;
Here is some example SAS code that uses actual ICD 10 CM codes and their descriptions and #Reeza proc print:
%* Copy government provided Medicare code data zip file to local computer;
filename cms_cm url 'https://www.cms.gov/Medicare/Coding/ICD10/Downloads/2020-ICD-10-CM-Codes.zip' recfm=s;
filename zip_cm "%sysfunc(pathname(work))/2020-ICD-10-CM-Codes.zip" lrecl=200000000 recfm=n ;
%let rc = %sysfunc(fcopy(cms_cm, zip_cm));
%put %sysfunc(sysmsg());
%* Define fileref to the zip file member that contains ICD 10 CM codes and descriptions;
filename cm_codes zip "%sysfunc(pathname(zip_cm))" member="2020 Code Descriptions/icd10cm_codes_2020.txt";
%* input the codes and descriptions, there are 72,184 of them;
%* I cheated and looked at the data (more than once) in order
%* to determine the variable sizes needed;
data icd10cm_2020;
infile cm_codes lrecl=250 truncover;
attrib
code length=$7
desc length=$230
;
input
code 1-7 desc 9-230;
;
run;
* simulate claims sample data with mostly upto 8 diagnoses, and
* at least one claim with 50 diagnoses;
data have;
call streaminit(123);
do claim_id = 1 to 10;
array codes(50) $7 code1-code50;
array descs(50) $230 desc1-desc50;
call missing(of code:, of desc:);
if mod(claim_id, 10) = 0
then top = 50;
else top = rand('uniform', 8);
do _n_ = 1 to top;
p = ceil(rand('uniform', n)); %* pick a random diagnosis code, 1 of 72,184;
set icd10cm_2020 nobs=n point=p; %* read the data for that random code;
codes(_n_) = code;
descs(_n_) = desc;
end;
output;
end;
stop;
drop top;
run;
%macro loop_names(n=);
%do i=1 %to &n;
code&i desc&i.
%end;
%mend;
ods _all_ close;
ods html;
proc print data=have;
var claim_id %loop_names(n=50);
run;

Query Metatdata to get a list of all objects

I am new to the metadata concept and SAS DI. I need to query the foundation repository metadata and list out all the objects from it. Can someone please suggest how to achieve that?
Thanks
Update - I wrote a macro lately that will download all objects from a metadata repository, so does exactly what you need.
/* compile macros*/
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc;
/* run code */
%mm_tree()
Full source code below.
* use a temporary fileref to hold the response;
filename response temp;
/* get list of libraries */
proc metadata in=
'<GetMetadataObjects><Reposid>$METAREPOSITORY</Reposid>
<Type>Tree</Type><Objects/><NS>SAS</NS>
<Flags>384</Flags>
<XMLSelect search="*[#TreeType=&apos;BIP Folder&apos;]"/>
<Options/></GetMetadataObjects>'
out=response;
run;
/*
data _null_;
infile response;
input;
put _infile_;
run;
*/
/* create an XML map to read the response */
filename sxlemap temp;
data _null_;
file sxlemap;
put '<SXLEMAP version="1.2" name="SASObjects"><TABLE name="SASObjects">';
put "<TABLE-PATH syntax='XPath'>/GetMetadataObjects/Objects/Tree</TABLE-PATH>";
put '<COLUMN name="pathuri">';
put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/Tree/#Id</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>64</LENGTH>";
put '</COLUMN><COLUMN name="name">';
put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/Tree/#Name</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>256</LENGTH>";
put '</COLUMN></TABLE></SXLEMAP>';
run;
libname _XML_ xml xmlfileref=response xmlmap=sxlemap;
data &outds;
length metauri pathuri $64 name $256 path $1024
publictype MetadataUpdated MetadataCreated $32;
set _XML_.SASObjects;
keep metauri name publictype MetadataUpdated MetadataCreated path;
length parenturi pname $128 ;
call missing(parenturi,pname);
path=cats('/',name);
/* get parents */
tmpuri=pathuri;
do while (metadata_getnasn(tmpuri,"ParentTree",1,parenturi)>0);
rc=metadata_getattr(parenturi,"Name",pname);
path=cats('/',pname,path);
tmpuri=parenturi;
end;
if path=:"&root";
%if "&types"="ALL" or ("&types" ne "ALL" and "&types" ne "Folder") %then %do;
n=1;
do while (metadata_getnasn(pathuri,"Members",n,metauri)>0);
n+1;
call missing(name,publictype,MetadataUpdated,MetadataCreated);
rc=metadata_getattr(metauri,"Name", name);
rc=metadata_getattr(metauri,"MetadataUpdated", MetadataUpdated);
rc=metadata_getattr(metauri,"MetadataCreated", MetadataCreated);
rc=metadata_getattr(metauri,"PublicType", PublicType);
%if "&types" ne "ALL" %then %do;
if publictype in (%mf_getquotedstr(&types)) then output;
%end;
%else output; ;
end;
%end;
rc=metadata_resolve(pathuri,pname,tmpuri);
metauri=cats('OMSOBJ:',pname,'\',pathuri);
rc=metadata_getattr(metauri,"Name", name);
rc=metadata_getattr(pathuri,"MetadataUpdated", MetadataUpdated);
rc=metadata_getattr(pathuri,"MetadataCreated", MetadataCreated);
rc=metadata_getattr(pathuri,"PublicType", PublicType);
path=substr(path,1,length(path)-length(name)-1);
if publictype ne '' then output;
run;
proc sort;
by path;
run;

How to scan a numeric variable

I have a table like this:
Lista_ID 1 4 7 10 ...
in total there are 100 numbers.
I want to call each one of these numbers to a macro i created. I was trying to use 'scan' but read that it's just for character variables.
the error when i runned the following code was
there's the code:
proc sql;
select ID INTO: LISTA_ID SEPARATED BY '*' from
WORK.AMOSTRA;
run;
PROC SQL;
SELECT COUNT(*) INTO: NR SEPARATED BY '*' FROM
WORK.AMOSTRA;
RUN;
%MACRO CICLO_teste();
%LET LIM_MSISDN = %EVAL(NR);
%LET I = %EVAL(1);
%DO %WHILE (&I<= &LIM_MSISDN);
%LET REF = %SCAN(LISTA_ID,&I,,'*');
DATA WORK.UP&REF;
SET WORK.BASE&REF;
FORMAT PERC_ACUM 9.3;
IF FIRST.ID_CLIENTE THEN PERC_ACUM=0;
PERC_ACUM+PERC;
RUN;
%LET I = %EVAL(&I+1);
%END;
%MEND;
%CICLO_TESTE;
the error was that:
VARIABLE PERC IS UNITIALIZED and
VARIABLE FIRST.ID_CLIENTE IS UNITIALIZED.
What I want is to run this macro for each one of the Id's in the List I showed before, and that are referenced in work.base&ref and work.up&ref.
How can I do it? What I'm doing wrong?
thanks!
Here's the CALL EXECUTE version.
%MACRO CICLO_teste(REF);
DATA WORK.UP&REF;
SET WORK.BASE&REF;
BY ID_CLIENTE;
FORMAT PERC_ACUM 9.3;
IF FIRST.ID_CLIENTE THEN PERC_ACUM=0;
PERC_ACUM+PERC;
RUN;
%CICLO_TESTE;
DATA _NULL_;
SET amostra;
*CREATE YOUR MACRO CALL;
STR = CATT('%CLIO_TESTE(', ID, ')');
CALL EXECUTE(STR);
RUN;
First you should note that SAS macro variable resolve is intrinsically a "text-based" copy-paste action. That is, all the user-defined macro variables are texts. Therefore, %eval is unnecessary in this case.
Other miscellaneous corrections include:
Check the %scan() function for correct usage. The first argument should be a text string WITHOUT QUOTES.
run is redundant in proc sql since each sql statement is run as soon as they are sent. Use quit; to exit proc sql.
A semicolon is not required for macro call (causes unexpected problems sometimes).
use %do %to for loops
The code below should work.
data work.amostra;
input id;
cards;
1
4
7
10
;
run;
proc sql noprint;
select id into :lista_id separated by ' ' from work.amostra;
select count(*) into :nr separated by ' ' from work.amostra;
quit;
* check;
%put lista_id=&lista_id nr=&nr;
%macro ciclo_teste();
%local ref;
%do i = 1 %to &nr;
%let ref = %scan(&lista_id, &i);
%*check;
%put ref = &ref;
/* your task below */
/* data work.up&ref;*/
/* set work.base&ref;*/
/* format perc_acum 9.3;*/
/* if first.id_cliente then perc_acum=0;*/
/* perc_acum + perc;*/
/* run; */
%end;
%mend;
%ciclo_teste()
tested on SAS 9.4 win7 x64
Edited:
In fact I would recommend doing this to avoid scanning a long string which is inefficient.
%macro tester();
/* get the number of obs (a more efficient way) */
%local NN;
proc sql noprint;
select nobs into :NN
from dictionary.tables
where upcase(libname) = 'WORK'
and upcase(memname) = 'AMOSTRA';
quit;
/* assign &ref by random access */
%do i = 1 %to &NN;
data _null_;
a = &i;
set work.amostra point=a;
call symputx('ref',id,'L');
stop;
run;
%*check;
%put ref = &ref;
/* your task below */
%end;
%mend;
%tester()
Please let me know if you have further questions.
Wow that seems like a lot of work. Why not just do the following:
data work.amostra;
input id;
cards;
1
4
7
10
;
run;
%macro test001;
proc sql noprint;
select count(*) into: cnt
from amostra;
quit;
%let cnt = &cnt;
proc sql noprint;
select id into: x1 - :x&cnt
from amostra;
quit;
%do i = 1 %to &cnt;
%let x&i = &&x&i;
%put &&x&i;
%end;
%mend test001;
%test001;
now in variables &x1 - &&x&cnt you have your values and you can process them however you like.
In general if your list is small enough (macro variables are limited to 64K characters) then you are better off passing the list in a single delimited macro variable instead of multiple macro variables.Remember that PROC SQL will automatically set the count into the macro variable SQLOBS so there is no need to run the query twice. Or you can use %sysfunc(countw()) to count the number of entries in your delimited list.
proc sql noprint ;
select id into :idlist separated by '|' from .... ;
%let nr=&sqlobs;
quit;
...
%do i=1 %to &nr ;
%let id=%scan(&idlist,&i,|);
data up&id ;
...
%end;
If you do generate multiple macro variables there is no need to set the upper bound in advance as SAS will only create the number of macro variables it needs based on the number of observations returned by the query.
select id into :idval1 - from ... ;
%let nr=&sqlobs;
If you are using an older version of SAS the you need set an upper bound on the macro variable range.
select id into :idval1 - :idval99999 from ... ;