Macro variable for each delimiter to import multiple delimited files in SAS - macros

I have 2 different delimited files (csv and text) having the variables below respectively. The first 3 are character variables and the rest are numeric variables:Plant, Type, Treatment, conc, uptake. the text file has 5 numeric variables and a character variable.I would like to import the two files using a macro variable for every delimiter in SAS as part of an exercise.
I have the code below to extract multiple files using macro. I would like to get your advice on how to create a macro variable for every delimiter (csv, text).
%macro one (output, Sample);
proc import out=output
datafile= "C:\Users\komal\Desktop\Sample.csv"
dbms=csv replace;
getnames=yes;
run;
%mend one;
%one (output, Sample.csv);
%one (data2, datafiletwo.txt);

You import different type of data, so you need to define type of data in dbms.
%macro one (output, Sample,type);
proc import out=&output
datafile= "C:\Users\komal\Desktop\&Sample"
dbms=&type replace;
getnames=yes;
run;
%mend one;
%one (output, Sample.csv,cvs);
%one (data2, datafiletwo.xlsx,excel);
%one (class, class.txt,tab);

Thanks Shenglin
I have tried the code below and it works perfectly.
%macro one (a, b, c);
proc import out=&a
datafile= "C:\Users\komal\Desktop\&b"
dbms=&c replace;
getnames=yes;
run;
%mend one;
%one (outcsv, Sample.csv, csv);
%one (outtab, datafiletwo.txt, tab);

Related

SAS proc import guessingrows issue

I'm trying to import csv file to SAS using proc import; I know that guessingrows argument will determine automatically the type of variable for each column for my csv file. But there is an issue with one of my CSV file which has two entire columns with blank values; those columns in my csv file should be numeric, but after running the below code, those two columns are becoming character type, is there any solutions for how to change the type of those two columns into numeric during or after importing it to SAS ?
Here below is the code that I run:
proc import datafile="filepath\datasetA.csv"
out=dataA
dbms=csv
replace;
getnames=yes;
delimiter=",";
guessingrows=100;
run;
Thank you !
Modifying #Richard's code I would do:
filename csv 'c:\tmp\abc.csv';
data _null_;
file csv;
put 'a,b,c,d';
put '1,2,,';
put '2,3,,';
put '3,4,,';
run;
proc import datafile=csv dbms=csv replace out=have;
getnames=yes;
run;
Go to the LOG window and see SAS code produced by PROC IMPORT:
data WORK.HAVE ;
%let _EFIERR_ = 0; /* set the ERROR detection macro variable */
infile CSV delimiter = ',' MISSOVER DSD lrecl=32767 firstobs=2 ;
informat a best32. ;
informat b best32. ;
informat c $1. ;
informat d $1. ;
format a best12. ;
format b best12. ;
format c $1. ;
format d $1. ;
input
a
b
c $
d $
;
if _ERROR_ then call symputx('_EFIERR_',1); /* set ERROR detection macro variable */
run;
Run this code and see that two last columns imported as characters.
Check it:
ods select Variables;
proc contents data=have nodetails;run;
Possible to modify this code and load required columns as numeric. I would not drop and add columns in SQL because this columns could have data somewhere.
Modified import code:
data WORK.HAVE ;
%let _EFIERR_ = 0; /* set the ERROR detection macro variable */
infile CSV delimiter = ',' MISSOVER DSD lrecl=32767 firstobs=2 ;
informat a best32. ;
informat b best32. ;
informat c best32;
informat d best32;
format a best12. ;
format b best12. ;
format c best12;
format d best12;
input
a
b
c
d
;
if _ERROR_ then call symputx('_EFIERR_',1); /* set ERROR detection macro variable */
run;
Check table description:
ods select Variables;
proc contents data=have nodetails;run;
You can change the column type of a column that has all missing value by dropping it and adding it back as the other type.
Example (SQL):
filename csv 'c:\temp\abc.csv';
data _null_;
file csv;
put 'a,b,c,d';
put '1,2,,';
put '2,3,,';
put '3,4,,';
run;
proc import datafile=csv dbms=csv replace out=have;
getnames=yes;
run;
proc sql;
alter table have
drop c, d
add c num, d num
;

SAS: How to reference a global macro variable to create new table or dataset?

I'm having some trouble referencing a global macro variable outside of the macro to create a new data set. The global variable was created to run a loop for creating several yearly data sets using a vector of specified years, as you can see in the code below:
%macro loopyear;
%global year;
%do year = 2004 %to 2017;
proc import datafile = "C:\Filepath\blah.txt"
dbms = dlm out = blah&year.; /*Creates a dataset for each year, e.g. blah2004, blah2005, etc.) */
delimiter = " ";
getnames = no;
run;
data blah&year.;
set blah&year.;
year = &year.;
run;
proc sql;
create table blah&year._rail as
select year, var1, var2, var3, var4
from blah&year.
where var2= "rail";
quit;
%end;
%mend loopyear;
%loopyear;
/*Merge all year datasets into one master set*/
data blah_total;
set blah&year._rail;
run;
When I try to create the master data set outside of the macro, however, I get the following error:
data blah;
set blah&year._rail;
run;
ERROR: File work.blah2018_rail.data does not exist
This is frustrating because I'm only trying to create the master set based on 2004-2017 data, as referenced in the macro variable. Can someone help me pinpoint my error -- is it in the way I defined the global variable, or am I missing a step somewhere? Any help is appreciated.
Thanks!
This is an interesting quirk of both macro and data step do-loops in SAS - the loop counter is incremented before the exit condition is checked, so after your loop has run it will be one increment past your stop value, e.g.:
%macro example;
%do i = 1 %to 3;
%put i = &i;
%end;
%put i = &i;
%mend;
%example;
Output:
i = 1
i = 2
i = 3
i = 4
For your final step you probably want the set statement to look like this:
set blah2004_rail ... blah2017_rail;
You could write a macro loop to generate the list and move the data step inside your macro, e.g.
set %do year = 2004 %to 2017; blah&year._rail %end;;
The second semi-colon is important! You need one to close the %end and one to terminate the set statement.
Change your naming structure. Have a common prefix and put the year at the end, then you can use the semi colon to short reference all the datasets at once.
%macro loopyear;
%global year;
%do year = 2004 %to 2017;
proc import datafile = "C:\Filepath\blah.txt"
dbms = dlm out = blah&year.; /*Creates a dataset for each year, e.g. blah2004, blah2005, etc.) */
delimiter = " ";
getnames = no;
run;
data blah&year.;
set blah&year.;
year = &year.;
run;
proc sql;
create table blah_rail_&year. as
select year, var1, var2, var3, var4
from blah&year.
where var2= "rail";
quit;
%end;
%mend loopyear;
%loopyear;
/*Merge all year datasets into one master set*/
data blah_total;
set blah_rail: ;
run;

Facing problem importing a csv file while using macro variable in SAS

I wrote a code in unix SAS to import multiple csv files from current folder. The macro variables are being assigned correct values but somehow the relevant files are not being imported. I am getting the following error message
ERROR: Physical file does not exist, /work/pricepromo/modeler/tolapa01/pawan/&j..csv.
ERROR: Import unsuccessful. See SAS Log for details.
Below is the code.
OPTIONS MERROR MPRINT SERROR MLOGIC SYMBOLGEN ;
X ls *.csv > list;
data name ;
infile 'list' delimiter = ',' MISSOVER DSD lrecl=32767 firstobs=1 ;
informat name_list $9. ;
format name_list $9. ;
input
name_list $
;
run;
data name2;
set name;
name_mod=translate(name_list,'','.csv');
run;
proc sql;
select name_mod into :name separated by '*' from name2;
%let count2 = &sqlobs;
quit;
%macro yy;
%do i = 1 %to &count2;
%let j = %scan(&name,&i,*);
proc import out = &j datafile='./&j..csv'
dbms=csv replace;
run;
%end;
%mend;
%yy;
Try using double quotes
datafile="./&j..csv"
not
datafile='./&j..csv'
With all those options it should have been obvious from reading the SAS log.

SAS-using macro combine dataset but SAS doesn't recognise the target name

I used following code to combine several datasets in a library with one dataset. However, according to log file, the SAS did not recognise &target..* in the macro.
The log file is shown as following:
%macro combintprice(sourcelib=,from=,going=,target=);
proc sql noprint; /*read datasets in a library*/
create table mytables as
select *
from dictionary.tables
where libname = &sourcelib
order by memname ;
select count(memname)
into:numb
from mytables;
%let numb=&numb.; /*give a number to datasets in the library*/
select memname
into :memname1-:memname&numb.
from mytables;
quit;
%do i=1 %to &numb.;
proc sql;
create table &going.&&memname&i. as
select &from.&&memname&i...*, &target..*
from &from.&&memname&i. as a left join &target. as b
on a.date=b.date;
quit;
%end;
%mend;
%combintprice(sourcelib='AXP',from=AXP.,going=WORK.,target=axp1);
It often helps to break the code down into bits when debugging this sort of thing. Let's try running this with some dummy inputs and skip the first proc sql:
%let memname1= data1;
%let memname2= data2;
%let memname3= data3;
%let numb = 3;
%macro combintprice(sourcelib=,from=,going=,target=);
%do i=1 %to &numb.;
proc sql noexec;
create table &going.&&memname&i. as
select &from.&&memname&i...*, &target..*
from &from.&&memname&i. as a left join &target. as b
on a.date=b.date;
quit;
%end;
%mend;
%combintprice(sourcelib='AXP',from=AXP.,going=WORK.,target=axp1.);
This gives the following log output:
22: LINE and COLUMN cannot be determined.
NOTE 242-205: NOSPOOL is on. Rerunning with OPTION SPOOL might allow recovery of the LINE and COLUMN where the error has occurred.
ERROR 22-322: Syntax error, expecting one of the following: a quoted string, !, !!, &, *, **, +, ',', -, /, <, <=, <>, =, >, >=, ?,
AND, AS, BETWEEN, CONTAINS, EQ, EQT, FORMAT, FROM, GE, GET, GT, GTT, IN, INFORMAT, INTO, IS, LABEL, LE, LEN, LENGTH,
LET, LIKE, LT, LTT, NE, NET, NOT, NOTIN, OR, TRANSCODE, ^, ^=, |, ||, ~, ~=.
200: LINE and COLUMN cannot be determined.
NOTE: NOSPOOL is on. Rerunning with OPTION SPOOL might allow recovery of the LINE and COLUMN where the error has occurred.
ERROR 200-322: The symbol is not recognized and will be ignored.
NOTE 137-205: Line generated by the invoked macro "COMBINTPRICE".
76 proc sql noexec; create table &going.&&memname&i. as select &from.&&memname&i...*, &target..* from &from.&&memname&i.
_
22
76 ! as a left join &target. as b on a.date=b.date; quit;
ERROR 22-322: Syntax error, expecting one of the following: a name, *.
So the problem is that your macro code is generating invalid SQL. All those error messages generated by proc sql (even with noexec set) and bits of macro variables actually get in the way here, so let's just look at what actual code generated looks like, using equivalent %put statements:
%let memname1= data1;
%let memname2= data2;
%let memname3= data3;
%let numb = 3;
%macro combintprice(sourcelib=,from=,going=,target=);
%do i=1 %to &numb.;
%put
proc sql;
%put create table &going.&&memname&i. as
select &from.&&memname&i...*, &target..*
from &from.&&memname&i. as a left join &target. as b
on a.date=b.date;
%put quit;
%end;
%mend;
%combintprice(sourcelib='AXP',from=AXP.,going=WORK.,target=axp1.);
And this is the result (with just the few semicolons omitted):
proc sql
create table WORK.data1 as select AXP.data1.*, axp1..* from AXP.data1 as a left join axp1. as b on a.date=b.date
quit
You have a few too many periods. Try fixing this so that only valid SQL is produced, and then maybe it will work as expected.

sas macro index or other?

I have 169 towns for which I want to iterate a macro. I need the output files to be saved using the town-name (rather than a town-code). I have a dataset (TOWN) with town-code and town-name. Is it possible to have a %let statement that is set to the town-name for each iteration where i=town-code?
I know that I can list out the town-names using the index function, but I'd like a way to set the index function so that it sets a %let statement to the TOWN.town-name when i=TOWN.town-code.
All the answers below seem possible. I have used the %let = %scan( ,&i) option for now. A limitation is that the town names can be more than one word, so I've substituted underscores for spaces that I correct later.
This is my macro. I output proc report to excel for each of the 169 towns. I need the excel file to be saved as the name of the town and for the header to include the name of the town. Then, in excel, I merge all 169 worksheets into a single workbook.
%MACRO BY_YEAR;
%let townname=Andover Ansonia Ashford Avon ... Woodbury Woodstock;
%do i = 1999 %to 2006;
%do j = 1 %to 169;
%let name = %scan(&townname,&j);
ods tagsets.msoffice2k file="&ASR.\Town_Annual\&i.\&name..xls" style=minimal;
proc report data=ASR nofs nowd split='/';
where YR=&i and TWNRES=&j;
column CODNUM AGENUM SEX,(dths_sum asr_sum seasr_sum);
define CODNUM / group ;
define agenum / group ;
define sex / across ;
define dths_sum / analysis ;
define asr_sum / analysis ;
define seasr_sum / analysis ;
break after CODNUM / ul;
TITLE1 "&name Resident Age-Specific Mortality Rates by Sex, &i";
TITLE2 "per 100,000 population for selected causes of death";
run;
ods html close;
%end;
%end;
%MEND;
My guess is that the reason why you want to look up the town name by town index is to repeatedly call a macro with each town name. If this is the case, then you don't even need to get involved with the town index business at all. Just call the macro with each town name. There are many ways to do this. Here is one way using call execute().
data towns;
infile cards dlm=",";
input town :$char10. ##;
cards;
My Town,Your Town,His Town,Her Town
;
run;
%macro doTown(town=);
%put Town is &town..;
%mend doTown;
/* call the macro for each town */
data _null_;
set towns;
m = catx(town, '%doTown(town=', ')');
call execute(m);
run;
/* on log
Town is My Town.
Town is Your Town.
Town is His Town.
Town is Her Town.
*/
If you do need to do a table lookup, then one way is to convert your town names into a numeric format and write a simple macro to retrieve the name, given an index value. Something like:
data towns;
infile cards dlm=",";
input town :$char10. ##;
cards;
My Town,Your Town,His Town,Her Town
;
run;
/* make a numeric format */
data townfmt;
set towns end=end;
start = _n_;
rename town = label;
retain fmtname 'townfmt' type 'n';
run;
proc format cntlin=townfmt;
run;
%macro town(index);
%trim(%sysfunc(putn(&index,townfmt)))
%mend town;
%*-- check --*;
%put %town(1),%town(2),%town(3),%town(4);
/* on log
My Town,Your Town,His Town,Her Town
*/
Or how about you just pass both the code and the name to the macro as parameters? Like this?
%MACRO DOSTUFF(CODE=, NAME=);
DO STUFF...;
PROC EXPORT DATA=XYZ OUTFILE="&NAME."; RUN;
%MEND;
DATA _NULL_;
SET TOWNS;
CALL EXECUTE("%DOSTUFF(CODE=" || STRIP(CODE) || ", NAME=" || STRIP(NAME) || ");");
RUN;