SAS编程实践---宏“Mkfrm”:创建某一个域的空数据集

写在前面。

今天是个小工具宏,大佬们见笑了。如果您看完有所指正,那我会很感谢您。如果您看完有所收获,那是我的荣幸。

在做SDTM或者ADaM数据集时,我和同事一般的习惯是:

写某一个域的数据集时,比如dm、adsl,...,先根据spec文件中的说明,创建一个包含这个域中所有变量的空数据集,再去根据spec说明创建包含所需变量的数据集,最后再把做好的数据集和这个空的数据集set一下。

这样最终的数据集中变量的顺序、名称、标签、类型和长度等就与spec中说明一致了。

很多不同的方法实现,个人感觉使用proc sql还是很方便的。

但是还是要敲不少代码,如果spec写的足够规范质量很高,直接读入来创建这个空的数据集(框架)是个不错的主意。

注意,前提是spec写的足够好spec写的足够好spec写的足够好


准备步骤

目标拆解

一样的,先进行一下目标的拆解:

  • spec中可能包含的部分信息
Variable Label Type Length or Display Format
STUDYID 研究编号 C 200
USUBJID 受试者唯一编号 C 200
TRTSEQP 给药顺序 C 200
TRTSEQPN 给药顺序(数值) N 8
FASFL 是否进FAS C 200

注意,我们公司是用C和N标识变量类型,这和后面一个条件判断有关;

如果你们不是使用N/C,那后面的代码需要进行适当修改

  • 目标
STUDYID USUBJID TRTSEQP TRTSEQPN FASFL

宏编写步骤

注释

宏的作用,注意事项,参数的说明在注释中已经写清楚了,如下:


/*******************************************************************************************
Purpose: 从导入SAS的spec数据集中,或者从外部spec文件中,获取一个域对应数据集中变量的名称、标签、类型和长度的4个变量,产生这个域的空数据集;

dtin:如果已经将spec文件读入SAS会话中产生数据集,则将dtin参数赋值为该数据集名称;

      注意,该数据集的前4个变量必须依次为:名称、标签、类型和长度;

      如果是要从外部spec文件创建的情形,则不要给dtin参数赋值;

sepcfile:如果没有将spec文件读入SAS会话中,则需要使用filename语句产生spec文件路径的引用,
          例如,filename name ".../spec.xlsx";,则将name赋值给参数sepcfile;

dbms:适用于从外部spec文件创建的情形,赋值给import过程步的DBMS参数;

sheet:适用于从外部spec文件创建的情形,赋值给import过程步的SHEET参数,SHEET名称应该在给定的EXCEL中;

startrow:适用于从外部spec文件创建的情形,赋值给import过程步的datarow参数,开始读取数据的行;
          注意,默认不读取变量名,因此,直接赋值给需要读取的数据的开始行;

frm:一般也就是域名,用于这个域的空数据集名称的构建;

**************************************************************************************************/

sepc信息来源的考虑

我考虑了两种情况:

  1. 你把spec所在的路径给出了,并且使用filename语句引用给了一个标识符。好,那这个宏就按照标识符引用的路径去读你的spec,然后进行后续的步骤;

  2. 你已经把spec文件读进SAS会话创建了数据集。好的,那这个宏就使用这个数据集进行后续步骤。

所以数据的预处理使用了条件判断:

从SAS数据集中读取数据

%if ( %sysfunc(exist(&dtin. ,data )) and &dtin. ^= %str() )   %then %do;
%put WARNING: 将从SAS数据集中读取数据;
data _specdtin;
set &dtin. ;
run;

proc contents data= _specdtin out= _info noprint;
proc sort ; by varnum;
run;

proc sql   noprint;
    select   name into:col1 -:col4 from _info where varnum <= 4;
quit;

proc sql   noprint;
    select count(distinct &col1. ) ,   &col1. ,  &col2. ,  &col3.,  &col4.  into: varn, :vnam1 -:vnam999,   :vlabel1 -:vlabel999,  :vtype1 -:vtype999,  :len1 -:len999 from _specdtin;
quit;

%end;

注意为真的条件,输入数据集参数dtin你进行了赋值,并且这个数据集名称你不是乱写的,是有数据的,这个宏才能够从中读取数据并且进行一系列宏变量赋值操作。

这步骤中宏变量赋值的思路我讲一下:

  1. 使用proc contents获取给的spec数据集变量的信息,主要是前4个变量的名称,必须依次为名称、标签、类型和长度,然后赋值给了4个宏变量

  2. 然后使用proc sql将给的spec数据集中的这四个宏变量指定的变量的所有观测分别赋值给4组宏变量

从外部spec文件中读取数据

%else %do;
%put WARNING: 将从外部spec文件中读取数据;

proc import datafile= &sepcfile.  out=_specdtin  
    dbms=&dbms.  replace;
    SHEET =%sysfunc( upcase( &sheet.) );
    datarow=2;
    getnames=no;
run;

proc sql   noprint;
    select count(distinct a) ,  a , b , c, d  into: varn, :vnam1 -:vnam999,   :vlabel1 -:vlabel999,  :vtype1 -:vtype999,  :len1 -:len999 from _specdtin;
quit;
%end;

注意,不论是从已存在的数据集还是外部文件,数据集中必须包含名称、标签、类型和长度这4个个变量的信息,并且顺序也是固定

上述代码主要涉及的就是一些宏变量的赋值,我有说明的不清楚的地方的话,可以查看我这篇文章:

SAS编程实践 宏变量赋值(一文尽力涵盖)

proc sql创建空数据集

最后就是这个宏核心功能步,其实就是在proc sql中使用了循环和条件判断

    proc sql noprint;
        create table %sysfunc( upcase( &frm._frm) )  (
            %do aa = 1 %to  &varn.;
                %if  %sysfunc(upcase( &&vtype&aa. )) =  %str(C) %then

            %do;
                %let vtyp&aa. = Char(&&len&aa.);
            %end;

        %if  %sysfunc(upcase( &&vtype&aa. )) =  %str(N) %then
            %do;
                %let vtyp&aa. = Num(&&len&aa.);
            %end;

        %if &aa. ^=  &varn. %then
            %do;
                &&vnam&aa.  &&vtyp&aa. "&&vlabel&aa.",
            %end;

        %if &aa. =  &varn. %then
            %do;
                &&vnam&aa.  &&vtyp&aa. "&&vlabel&aa."
            %end;
%end;
        );
        quit;

注意,前一个条件判断,是为了把数据类型N或者C转换为sql中的NumChar;如果你们不是使用N/C标识变量类型,那么这个判断需要根据实际情况修改。

另一个条件判断,

是因为使用proc sql创建空数据集时的语句中,最后一句后面没有逗号,而前面都有,没有这个判断,会报错的。

这个宏没有核心步骤后的处理和输出步,不过还是清除了一下中间临时数据集。

proc datasets lib=work noprint;
delete _:;
run;

总结

以上就是我写这个宏的最主要代码,完整代码如下:

%macro Mkfrm(
dtin=,
sepcfile=,
dbms=,
sheet=,
startrow=,
frm=
);

/*******************************************************************************************
Purpose: 从导入SAS的spec数据集中,或者从外部spec文件中,获取一个域对应数据集中变量的名称、标签、类型和长度的4个变量,产生这个域的空数据集;

dtin:如果已经将spec文件读入SAS会话中产生数据集,则将dtin参数赋值为该数据集名称;

      注意,该数据集的前4个变量必须依次为:名称、标签、类型和长度;

      如果是要从外部spec文件创建的情形,则不要给dtin参数赋值;

sepcfile:如果没有将spec文件读入SAS会话中,则需要使用filename语句产生spec文件路径的引用,
          例如,filename name ".../spec.xlsx";,则将name赋值给参数sepcfile;

dbms:适用于从外部spec文件创建的情形,赋值给import过程步的DBMS参数;

sheet:适用于从外部spec文件创建的情形,赋值给import过程步的SHEET参数,SHEET名称应该在给定的EXCEL中;

startrow:适用于从外部spec文件创建的情形,赋值给import过程步的datarow参数,开始读取数据的行;
          注意,默认不读取变量名,因此,直接赋值给需要读取的数据的开始行;

frm:一般也就是域名,用于这个域的空数据集名称的构建;

**************************************************************************************************/
%if ( %sysfunc(exist(&dtin. ,data )) and &dtin. ^= %str() )   %then %do;
%put WARNING: 将从SAS数据集中读取数据;
data _specdtin;
set &dtin. ;
run;

proc contents data= _specdtin out= _info noprint;
proc sort ; by varnum;
run;

proc sql   noprint;
    select   name into:col1 -:col4 from _info where varnum <= 4;
quit;

proc sql   noprint;
    select count(distinct &col1. ) ,   &col1. ,  &col2. ,  &col3.,  &col4.  into: varn, :vnam1 -:vnam999,   :vlabel1 -:vlabel999,  :vtype1 -:vtype999,  :len1 -:len999 from _specdtin;
quit;

%end;
%else %do;
%put WARNING: 将从外部spec文件中读取数据;

proc import datafile= &sepcfile.  out=_specdtin  
    dbms=&dbms.  replace;
    SHEET =%sysfunc( upcase( &sheet.) );
    datarow=2;
    getnames=no;
run;

proc sql   noprint;
    select count(distinct a) ,  a , b , c, d  into: varn, :vnam1 -:vnam999,   :vlabel1 -:vlabel999,  :vtype1 -:vtype999,  :len1 -:len999 from _specdtin;
quit;

%end;

    proc sql noprint;
        create table %sysfunc( upcase( &frm._frm) )  (
            %do aa = 1 %to  &varn.;
                %if  %sysfunc(upcase( &&vtype&aa. )) =  %str(C) %then

            %do;
                %let vtyp&aa. = Char(&&len&aa.);
            %end;

        %if  %sysfunc(upcase( &&vtype&aa. )) =  %str(N) %then
            %do;
                %let vtyp&aa. = Num(&&len&aa.);
            %end;

        %if &aa. ^=  &varn. %then
            %do;
                &&vnam&aa.  &&vtyp&aa. "&&vlabel&aa.",
            %end;

        %if &aa. =  &varn. %then
            %do;
                &&vnam&aa.  &&vtyp&aa. "&&vlabel&aa."
            %end;
%end;
        );
        quit;

proc datasets lib=work noprint;
delete _:;
run;

%mend;

新手小白,疏漏在所难免,如果您看完有所指正,那我会很感谢您。如果您看完有所收获,那是我的荣幸。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容