line statement using a macro variable - macros

I want to what does the .(dot) meant in the following sas line statement: (line &ls.*"_";)
I know the ls is a macro variable but what does the dot mean?
option pageno=1 nodate center;
%let ls=68;
%let ps=20;
proc report data=class2 LS=&ls PS=&ps SPLIT="/" center headline headskip nowd spacing=5 out=outdata1;
column sex age name height weight notdone;
define sex / order order=internal descending width=6 LEFT noprint;
define age / order order=internal width=3 spacing=0 "age" right;
define name / display width=8 left "name" flow;
define height / sum width=11 right "height";
define weight / sum width=11 right "weight";
define notdone / sum format= notdone. width=15 left "status";
computer before;
nd=notdone.sum;
endcomp;
compute before _page_/left;
line "gender group: " sex $gender.;
line &ls.*"_";
line ' ';
endcomp;

The period delimits the end of a macro variable name. Often, this isn't necessary as SAS will recognize the end of a macro variable name as soon as is sees a character that is not valid in a SAS name (e.g. space, semicolon). Most importantly, the period allows you to tell SAS the end of the macro variable name if it's in the middle of a string.
%let mv=var;
%put &mv.3;
returns var3 to the log, whereas &mv3 would fail to resolve without there being a macro variable named mv3 defined.
Also, realize that the delimiting period is not contained in the resolved code. e.g:
%let lib=sashelp;
data cars;
set &lib..cars;
run;
The set statement after resolving the macro variable is
set sashelp.cars;

Related

Progress 4GL for each and select * from cust

I often do the following progress 4GL code
output to /OUTText.txt.
def var dRow as char.
dRow = "cmpid|CustNum|Cur".
put unformatted dRow skip.
for each Cust no-lock:
dRow = subst("&1|&2|&3", Cust.CmpId, Cust.CustNum, Cust.Curr).
put unformatted dRow skip.
end.
output close.
in order to mimic
select * from cust (in MS SQL)
my question is is there a way to make this block of code, even closely resemblance "select *" using 4GL. Such that I don't have to type each column name and it will print all values in all columns. my thinking is. something like this.
output to /OUTText.txt.
def var dRow as char.
dRow = "cmpid|CustNum|Cur".
put unformatted dRow skip.
for each Cust no-lock:
if row = 1 then do:
for each Column in Cust:
**'PRINT THE COLUMN HEADER**
end.
end.
else do:
**'PRINT EACH CELL**
end.
end.
output close.
If there is such thing. then I don't have to keep explicit column name in dRow.
You can do what you're after if you first output all field labels (or names) and then use EXPORT to output the table content.
To change to field name instead of label: change :LABEL below to :NAME
For instance:
DEFINE VARIABLE i AS INTEGER NO-UNDO.
OUTPUT TO c:\temp\somefile.txt.
DO i = 1 TO BUFFER Customer:NUM-FIELDS.
PUT QUOTER(BUFFER Customer:BUFFER-FIELD(i):LABEL).
IF i < BUFFER Customer:NUM-FIELDS THEN
PUT UNFORMATTED ";".
ELSE IF i = BUFFER Customer:NUM-FIELDS THEN
PUT SKIP.
END.
FOR EACH Customer NO-LOCK:
EXPORT DELIMITER ";" Customer.
END.
OUTPUT CLOSE.
You could put the header part in a separate program to call dynamically every time you want to do something similar:
DEFINE STREAM str.
OUTPUT STREAM str TO c:\temp\somefile.txt.
RUN putHeaders.p(INPUT BUFFER Customer:HANDLE, INPUT ";", INPUT STREAM str:HANDLE).
FOR EACH Customer NO-LOCK:
EXPORT STREAM str DELIMITER ";" Customer.
END.
OUTPUT STREAM str CLOSE.
putHeaders.p
============
DEFINE INPUT PARAMETER phBufferHandle AS HANDLE NO-UNDO.
DEFINE INPUT PARAMETER pcDelimiter AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER phStreamHandle AS HANDLE NO-UNDO.
DEFINE VARIABLE i AS INTEGER NO-UNDO.
DO i = 1 TO phBufferHandle:NUM-FIELDS.
PUT STREAM-HANDLE phStreamHandle UNFORMATTED QUOTER(phBufferHandle:BUFFER-FIELD(i):LABEL).
IF i < phBufferHandle:NUM-FIELDS THEN
PUT STREAM-HANDLE phStreamHandle UNFORMATTED pcDelimiter.
ELSE IF i = phBufferHandle:NUM-FIELDS THEN
PUT STREAM-HANDLE phStreamHandle SKIP.
END.
output to "somefile".
for each customer no-lock:
display customer.
end.
I wouldn't generally mention this as the embedded SQL-89 within the 4GL is the highway to hell (that dialect of SQL only works for the most basic and trivial of purposes and really shouldn't be used at all in production code), but as it happens:
output to "somefile".
select * from customer.
does just happen to work to the spec of the original question (although, like the DISPLAY solution, it also does not support a delimiter...)

SAS - Fill in null values for numeric variables but not dates

I use the following to fill in null values for numeric variables with 0, but this does it for date variables as well. How can I fill in null values only for non-date numeric variables?
data mydataset;
set mydataset;
array myarray _numeric_;
do over myarray;
if myarray=. then myarray=0;
end;
run;
There is an excellent post on SAS Communities about determining if a variable is a date or not. It's available here. Your question isn't trivial, as you shouldn't forget about user-defined formats which can also behave like date formats (proc format). Let's suppose that all your date variables are of one format.
data test;
format x y z datetime8.;
x = .;
y = .;
z = .;
run;
%macro get_vars_format(lib, tab, fmt);
proc sql noprint;
select name into :names separated by ' '
from sashelp.vcolumn
where libname = "&lib." and
memname = "&tab." and
format eq "&fmt.";
quit;
%put Names: &names.;
data work.test;
set &lib..&tab.;
%let i = 1;
%let name = %scan(&names., &i., %str( ));
%do %while(&name. ne );
if &name. eq . then &name = 0;
output;
%let i = %eval(&i + 1);
%let name = %scan(&names., &i., %str( ));
%end;
run;
%mend get_vars_format;
%get_vars_format(lib=WORK, tab=TEST, fmt=DATETIME8.);
This macro takes three arguments:'
library name,
data set name,
and desired format name.
I am saving all the variables names into a macro-variable (they are separated by a space sign) and in a next step, I am iterating this in a loop (note i and name macro-variables). For every row, if a value of any variable with a given format is equal to . then replace it with 0. Note that if a variable has a date format, 0 will be treated as a Jan 01, 1960, as that's the first day in SAS date value convention.

SAS length of the value of the macro variable exceeds the maximum length

Hi I am trying to call a macro for each row in the data set using the code below
proc sql;
select cats('%run_procreg(name=',name,',month=',month,')') into :macrocalllist
separated by ' ' from dataset_a;
quit;
&macrocalllist;
I am getting the 'variable maximum length' error:
SAS length of the value of the macro variable MACROCALLLIST (65540)
exceeds the maximum length (65534). The value has been
truncated to 65534 characters.
because of the number of rows in the data set. Could you suggest a work-around?
Thank you,
CALL EXECUTE is one option. It allows you to generate a series of macro calls using data from a dataset, without storing the macro invocations in a macro variable.
For example:
%macro testprint(data=,obs=);
proc print data=&data (obs=&obs);
run;
%mend testprint;
data _null_;
input datasetname $13. obs;
call execute('%nrstr(%testprint(data='||datasetname
||',obs='||put(obs,1.)
||'))'
);
cards;
sashelp.shoes 3
sashelp.class 5
;
And the log will show:
NOTE: CALL EXECUTE generated line.
131 ;
1 + %testprint(data=sashelp.shoes,obs=3)
NOTE: There were 3 observations read from the data set SASHELP.SHOES.
2 + %testprint(data=sashelp.class,obs=5)
NOTE: There were 5 observations read from the data set SASHELP.CLASS.

Removing Unwanted commas from a csv

I'm writing a program in Progress, OpenEdge, ABL, and whatever else it's known as.
I have a CSV file that is delimited by commas. However, there is a "gift message" field, and users enter messages with "commas", so now my program will see additional entries because of those bad commas.
The CSV fields are not in double qoutes so I CAN NOT just use my main method with is
/** this next block of code will remove all unwanted commas from the data. **/
if v-line-cnt > 1 then /** we won't run this against the headers. Otherwise thhey will get deleted **/
assign
v-data = replace(v-data,'","',"\t") /** Here is a special technique to replace the comma delim wiht a tab **/
v-data = replace(v-data,','," ") /** now that we removed the comma delim above, we can remove all nuisance commas **/
v-data = replace(v-data,"\t",'","'). /** all nuisance commas are gone, we turn the tabs back to commas. **/
Any advice?
edit:
From Progress, I cal call Linux commands. So I should be able to execute C++/PHP/Shell etc all from my Progress Program. I look forward to advice, until then I shall look into using external scripts.
You are not providing quite enough data for a perfect answer but given what you say I think the IMPORT statement should handle this automatically.
In my example here commaimport.csv is a comma-separated csv-file with quotes around text fields. Integers, logical variables etc have no quotes. The last field contains a comma in one line:
commaimport.csv
=======================
"Id1", 123, NO, "This is a message"
"Id2", 124, YES, "This is a another message, with a comma"
"Id3", 323, NO, "This is a another message without a comma"
To import this file I define a temp-table matching the file layout and use the IMPORT statement with comma as delimiter:
DEFINE TEMP-TABLE ttImport NO-UNDO
FIELD field1 AS CHARACTER FORMAT "xxx"
FIELD field2 AS INTEGER FORMAT "zz9"
FIELD field3 AS LOGICAL
FIELD field4 AS CHARACTER FORMAT "x(50)".
INPUT FROM VALUE("c:\temp\commaimport.csv").
REPEAT :
CREATE ttImport.
IMPORT DELIMITER "," ttImport.
END.
INPUT CLOSE.
FOR EACH ttImport:
DISPLAY ttImport.
END.
You don't have to import into a temp-table. You could import into variables instead.
DEFINE VARIABLE c AS CHARACTER NO-UNDO FORMAT "xxx".
DEFINE VARIABLE i AS INTEGER NO-UNDO FORMAT "zz9".
DEFINE VARIABLE l AS LOGICAL NO-UNDO.
DEFINE VARIABLE d AS CHARACTER NO-UNDO FORMAT "x(50)".
INPUT FROM VALUE("c:\temp\commaimport.csv").
REPEAT :
IMPORT DELIMITER "," c i l d.
DISP c i l d.
END.
INPUT CLOSE.
This will render basically the same output:
You don't show what your data file looks like. But if the problematic field is the last one, and there are no quotes, then your best bet is probably to read it using INPUT UNFORMATTED to get it a line at a time, and then split the line into fields using ENTRY(). That way you can treat everything after the nth comma as a single field no matter how many commas the line has.
For example, say your input file has three columns like this:
boris,14.23,12 the avenue
mark,32.10,flat 1, the grange
percy,1.00,Bleak house, Dartmouth
... so that column three is an address which might contain a comma and is not enclosed in quotes so that IMPORT DELIMITER can't help you.
Something like this would work in that case:
/* ...skipping a lot of definitions here ... */
input from "datafile.csv".
repeat:
import unformatted v-line.
create tt-thing.
assign tt-thing.name = entry(1, v-line, ',')
tt-thing.price = entry(2, v-line, ',')
tt-thing.address = entry(3, v-line, ',').
do v=i = 4 to num-entries(v-line, ','):
tt-thing.address = tt-thing.address
+ ','
+ entry(v-i, v-line, ',').
end.
end.
input close.

matlab saving a cellarray

I have a script which does not fully work:
inputfield=input('Which field would you like to see: ','s')
if isfield(package, inputfield)
fprintf('The value of the %s field is: %c\n',inputfield,...
eval(['package.' inputfield]))
else
fprintf('Error: %s is not valid field\n', inputfield)
end
First I define a structure in matlab and then i use the script on the structure:
package=struct('item_no',123,'cost',19.99,'price',39.95,'code','g')
package =
item_no: 123
cost: 19.9900
price: 39.9500
code: 'g'
structurevalue
Which field would you like to see: cost
inputfield =
cost
The value of the cost field is: 1.999000e+001
structurevalue
Which field would you like to see: item_no
inputfield =
item_no
The value of the item_no field is: {
why cant it read value for item_no?
Try:
fprintf('The value of the %s field is: %s\n',inputfield,...
num2str(package.(inputfield)))
There were two issues with your version.
You were passing both numbers and strings into the %c field in your fprintf string. When a decimal goes in, it is interpreted as a number and displayed in full precision, which is why 19.99 got displayed as 1.999000e+001. But when an integer goes in, it gets interpreted as a character, which is why 123 got displayed as '{' (ASCII character 123). Use num2str to convert numbers to strings for display. Also, use %s for a string of any length, rather than %c for a character.
In general, it's not a good idea to use eval unless you have to. In this case, it's more convenient to use inputfield as a dynamic field name of package.