I was hoping to write a macro using IML that would be able to extract the column names of the dataset to use as names later.
Some pseudocode:
proc iml;
read all dataset into matrix_a [colname = varnames];
(...)
names = varnames;
create new_data_set [rownames = names];
quit;
Is this possible?
Sure is.
data test;
array x[10];
do i=1 to 10;
do j=1 to 10;
x[j] = (i-1)*10 + j;
end;
output;
end;
drop i j;
run;
proc iml;
use test;
read all var _num_ into test[colname=names];
close test;
test = test`;
names = names`;
create test_t from test[rowname=names];
append from test[rowname=names];
close test_t;
quit;
Related
I have many datasets for each month with the same name, changing just the end with specific month so for instance my datasets that i am calling with this code:
TEMPCAAD.LIFT_&NOME_MODELO._&VERSAO_MODELO._'!! put(cur_month,yymmn6.));
are called "TEMPCAAD.LIFT_MODEL_V1_202021", "TEMPCAAD.LIFT_MODEL_V1_202022" and so on...
I am trying to append all datasets but some of them doesn't exist, so when i run the following code I get the error
Dataset "TEMPCAAD.LIFT_MODEL_V1_202022" does not exist.
%let currentmonth = &anomes_scores;
%let previousyearmonth = &anomes_x12;
data _null_;
length string $1000;
cur_month = input("&previousyearmonth.01",yymmdd8.);
do until (cur_month > input("¤tmonth.01",yymmdd8.));
string = catx(' ',trim(string),'TEMPCAAD.LIFT_&NOME_MODELO._&VERSAO_MODELO._'!! put(cur_month,yymmn6.));
cur_month = intnx('month',cur_month,1,'b');
end;
call symput('mydatasets',trim(string));
%put &mydatasets;
run;
data WORK.LIFTS_U6M;
set &mydatasets.;
run;
How can I append only existing datasets?
Instead of looping on every file to see whether it exist or not, why don't you just extract all the dataset names from dictionary.tables?
libname TEMPCAAD "/home/kermit/TEMPCAAD";
data tempcaad.lift_model_v1_202110 tempcaad.lift_model_v1_202111 tempcaad.lift_model_v1_202112;
id = 1;
output tempcaad.lift_model_v1_202110;
id = 2;
output tempcaad.lift_model_v1_202111;
id = 3;
output tempcaad.lift_model_v1_202112;
run;
%let nome_modelo = MODEL;
%let versao_modelo = V1;
proc sql;
select strip("TEMPCAAD."||memname) into :dataset separated by " "
from dictionary.tables
where libname="TEMPCAAD" and memname like "LIFT_&NOME_MODELO._&VERSAO_MODELO.%";
quit;
data want;
set &dataset.;
run;
You can easily tweak the where statement to only extract the data that you wish to append. Just remember to put double quotes if you specify a macro-variable in it.
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;
I want to achieve the same output but instead of harcoding each of the array-element use something like var1 - var10 but that would jump by 10 like decades.
data work.test(keep= statename pop_diff:);
set sashelp.us_data(keep=STATENAME POPULATION:);
array population_array {*} POPULATION_1910 -- POPULATION_2010;
dimp = dim(population_array);
/* here and below something like:
array pop_diff_amount {10} pop_diff_amount_1920 -- pop_diff_amount_2010;*/
array pop_diff_amount {10} pop_diff_amount_1920 pop_diff_amount_1930
pop_diff_amount_1940 pop_diff_amount_1950
pop_diff_amount_1960 pop_diff_amount_1970
pop_diff_amount_1980 pop_diff_amount_1990
pop_diff_amount_2000 pop_diff_amount_2010;
array pop_diff_prcnt {10} pop_diff_prcnt_1920 pop_diff_prcnt_1930
pop_diff_prcnt_1940 pop_diff_prcnt_1950
pop_diff_prcnt_1960 pop_diff_prcnt_1970
pop_diff_prcnt_1980 pop_diff_prcnt_1990
pop_diff_prcnt_2000 pop_diff_prcnt_2010;
do i=1 to dim(population_array) - 1;
pop_diff_amount{i} = population_array{i+1} - population_array{i};
pop_diff_prcnt{i} = (population_array{i+1} / population_array{i} -1) * 100;
end;
RUN;
I am still beginner in it therefore I am not sure is this possible or easy to achieve.
Thanks!
Not automatic but not all that difficult either. First create a data set of the names then transpose and use an unexecuted set to bring in the names and then define arrays. Note how arrays are define using [*] and name: as you did with population_array.
data names;
do type = 'Amount','Prcnt';
do year=1920 to 2010 by 10;
length _name_ $32;
_name_ = catx('_','pop_diff',type,year);
output;
end;
end;
run;
proc print;
run;
proc transpose data=names out=pop_diff(drop=_name_);
var;
run;
proc contents varnum;
run;
data pop;
set sashelp.us_data(keep=STATENAME POPULATION:);
array population_array {*} POPULATION_1910 -- POPULATION_2010;
if 0 then set pop_diff;
array pop_diff_amount[*] pop_diff_amount:;
array pop_diff_prcnt[*] pop_diff_prcnt:;
do i=1 to dim(population_array) - 1;
pop_diff_amount{i} = population_array{i+1} - population_array{i};
pop_diff_prcnt{i} = (population_array{i+1} / population_array{i} -1) * 100;
end;
run;
proc print data=pop;
run;
SAS is automatically going to increment the array elements by 1. Here is an alternative solution that creates the variables using one extra step to create a set of macro variables that hold the desired variable names. Since you are basing them off of the variable POPULATION_<year>, we will simply grab the years from those variable names, create the variable names for the arrays that we want, and store them into a few macro variables.
proc sql noprint;
select cats('pop_diff_amount_', scan(name, -1, '_') )
, cats('pop_diff_prcnt_', scan(name, -1, '_') )
into :pop_diff_amount_vars separated by ' '
, :pop_diff_prcnt_vars separated by ' '
from dictionary.columns
where libname = 'SASHELP'
AND memname = 'US_DATA'
AND upcase(name) LIKE 'POPULATION_%'
;
quit;
data work.test(keep= statename pop_diff:);
set sashelp.us_data(keep=STATENAME POPULATION:);
array population_array {*} POPULATION_1910 -- POPULATION_2010;
dimp = dim(population_array);
array pop_diff_amount {*} &pop_diff_amount_vars.;
array pop_diff_prcnt {*} &pop_diff_prcnt_vars.;
do i=1 to dim(population_array) - 1;
pop_diff_amount{i} = population_array{i+1} - population_array{i};
pop_diff_prcnt{i} = (population_array{i+1} / population_array{i} -1) * 100;
end;
RUN;
Getting the data out of the meta data (create variable year) would make coding life easier.
proc transpose data=sashelp.us_data out=us_pop(rename=(col1=Population));
by statename;
var population_:;
run;
data us_pop;
set us_pop;
by statename;
year = input(scan(_name_,-1,'_'),4.);
pop_diff_amount=dif(population);
pop_diff_prcnt =(population/lag(population))-1;
format pop_diff_prcnt percent10.2;
if first.statename then call missing(of pop_diff_amount pop_diff_prcnt);
drop _:;
run;
proc print data=us_pop(obs=10);
run;
Suppose I have these data read into SAS:
I would like to list each unique name and the number of months it appeared in the data above to give a data set like this:
I have looked into PROC FREQ, but I think I need to do this in a DATA step, because I would like to be able to create other variables within the new data set and otherwise be able to manipulate the new data.
Data step:
proc sort data=have;
by name month;
run;
data want;
set have;
by name month;
m=month(lag(month));
if first.id then months=1;
else if month(date)^=m then months+1;
if last.id then output;
keep name months;
run;
Pro Sql:
proc sql;
select distinct name,count(distinct(month(month))) as months from have group by name;
quit;
While it's possible to do this in a data step, you wouldn't; you'd use proc freq or similar. Almost every PROC can give you an output dataset (rather than just print to the screen).
PROC FREQ data=sashelp.class;
tables age/out=age_counts noprint;
run;
Then you can use this output dataset (age_counts) as a SET input to another data step to perform your further calculations.
You can also use proc sql to group the variable and count how many are in that group. It might be faster than proc freq depending on how large your data is.
proc sql noprint;
create table counts as
select AGE, count(*) as AGE_CT from sashelp.class
group by AGE;
quit;
If you want to do it in a data step, you can use a Hash Object to hold the counted values:
data have;
do i=1 to 100;
do V = 'a', 'b', 'c';
output;
end;
end;
run;
data _null_;
set have end=last;
if _n_ = 1 then do;
declare hash cnt();
rc = cnt.definekey('v');
rc = cnt.definedata('v','v_cnt');
rc = cnt.definedone();
call missing(v_cnt);
end;
rc = cnt.find();
if rc then do;
v_cnt = 1;
cnt.add();
end;
else do;
v_cnt = v_cnt + 1;
cnt.replace();
end;
if last then
rc = cnt.output(dataset: "want");
run;
This is very efficient as it is a single loop over the data. The WANT data set contains the key and count values.
I am trying to create a simple dataset of only 1 column. The values in the column will be "Name1","Name2","Name3",etc... to "Name15". This is the code I have tried:
data names;
drop i;
length Name $15;
do i=1 to 15;
Name=cats("Name",i);
end;
run;
But all this does is print my final name, Name15. I know this is a simple fix but for some reason I just can't figure it out. Any help would be appreciated.
In your case, you need to OUTPUT explicitly.
data names;
drop i;
length Name $15;
do i=1 to 15;
Name=cats("Name",i);
OUTPUT;
end;
run;
Alternatively
data name;
retain Name1-Name15 1;
stop;
run;
proc transpose name=name data=name out=name;
run;
data names;
drop i;
length Name $15;
do i=1 to 15;
Name=cats("Name",i);
output;
end;
run;
hmmmmm ... same answer as Haikuo Bian :-(