之前写过查询数据集中的空变量的宏程序,一时没找到。于是重写一版,并以此简单介绍下宏程序的构建过程。
这篇文章从功能算法讲起,然后编程实现算法逻辑,最后进行宏程序的构建,宏程序完整代码在文章第4部分汇总。
希望这篇文章可以对读者日常SAS编程工作有所帮助。
先展示宏程序输出的效果:



更多临床试验SAS编程内容,欢迎关注:SAS茶谈。
宏程序的构思设计,从最小功能单位开始。对于查询数据集中的空变量,我们从单个数据集的单个变量的判断做起。
演示数据集使用SASHELP.Class,进行新增空变量处理。
***test dataset;
data class;
set sashelp.class;
a = "";
b = .;
run;

目前数据集中有两个完全为空的变量,宏程序的目的就是把这两个变量找出来。用什么程序语言来表示空变量,这个需要程序员自己摸索和尝试。我选用的是,变量不为空的记录数为0。听起来有些拗口,但程序实现起来比较简单。
Proc SQL和Data步都能够实现非空记录数的统计,但因为SQL的聚合函数跨记录处理相对方便,我以SQL语句进行演示,先统计变量Name不为空的记录数:
***Get number of non-missing records;
proc sql noprint;
create table result1 as
select "CLASS" as Dataset length=50, "NAME" as Var length=50, sum(not missing(name)) as Sum
from class;
quit;

依次类推,我们可以统计出每个变量不为空的记录数,然后依次将各个结果纵向拼接在一起。
SQL中纵向拼接的查询表达是outer union,默认是按两个查询表依次位置拼接的,相同变量拼接需加上关键字corresponding/corr,语法细节参考SAS官方文档:SAS Help Center: query Expression。
***Get number of non-missing records for all variables;
proc sql noprint;
create table result1 as
select "CLASS" as Dataset length=50, "NAME" as Var length=50, sum(not missing(NAME)) as Sum
from class
outer union corr
select "CLASS" as Dataset length=50, "SEX" as Var length=50, sum(not missing(SEX)) as Sum
from class
outer union corr
select "CLASS" as Dataset length=50, "AGE" as Var length=50, sum(not missing(AGE)) as Sum
from class
outer union corr
select "CLASS" as Dataset length=50, "HEIGHT" as Var length=50, sum(not missing(HEIGHT)) as Sum
from class
outer union corr
select "CLASS" as Dataset length=50, "WEIGHT" as Var length=50, sum(not missing(WEIGHT)) as Sum
from class
outer union corr
select "CLASS" as Dataset length=50, "A" as Var length=50, sum(not missing(A)) as Sum
from class
outer union corr
select "CLASS" as Dataset length=50, "B" as Var length=50, sum(not missing(B)) as Sum
from class;
quit;

再获取数据集中所有变量不为空的记录数后,我们只需要筛选记录数为0的记录就可以获取空变量的信息。为展示方便,我们可以把两个变量信息综合在一起,这个可以通过转置后横向拼接实现。
***Display result;
proc transpose data = result1 out=result2 prefix=emp_;
by dataset;
var Var;
where sum = 0;
run;
data result3;
set result2;
length Empvar $2000;
Empvar = catx(", ", of emp_:);
keep dataset empvar;
run;


这样处理看起来比较简洁,也方便后续多数据集检查空变量结果的拼接。
关于程序,多解释一点,SAS中函数批量处理变量序列时,需要在变量序列前添加of。如果不批量处理,也可以手动输入每一变量名称,例如:
empvar = catx(", ", emp_1, emp_2);
如果不知道输出变量的数目,使用特定的前缀对变量进行标记,再用函数批量处理,这个过程会变得简洁许多。
在手动编程将算法实现后,就可以着手构建宏程序。就我个人SAS编程经验来讲,宏程序的作用主要有两个:
- 单个功能的重复调用;
- 宏循环的批量处理。
关于这两个作用,读者可以与自己的宏程序编程经历对照理解,这里就不过多展开。
从以上手动编程的过程中可以看出,程序主要“重复”的地方在于各个变量不为空记录的统计。除了变量名称,拼接程序完全相同。如果我们将需要处理的变量名称保存到宏变量序列中,就可以通过宏循环依次进行调用,并通过宏循环批量构建程序。
宏循环处理的关键,在于循环次数的获取以及变量名称宏变量序列。
宏变量的生成常用有2种方法:
- Proc SQL 中的
into :语句- Data步中的
call symputx语句
SAS数据集的元数据信息保存在SAS字典中,这里我以Data步中的call symputx语句进行举例。
变量数目保存到宏变量中:
***Get the number of vairiables;
data tmp1;
set sashelp.vtable;
where libname = "WORK" and memname = "CLASS";
call symputx("nvar", strip(put(nvar,best.)));
run;
%put nvar= &nvar.;


变量名称保存到宏变量序列中:
***Get variables' name;
data tmp2;
set sashelp.vcolumn;
where libname = "WORK" and memname = "CLASS";
call symputx("var"||strip(put(varnum, best.)), strip(name));
run;
%put var1= &var1.;
%put var7= &var7.;


这里一些读者可能有这样的想法:这里完全可以使用一个Data步,从SASHELP.vcolumn数据集获取最后一条数据的varnum作为宏变量nvar的取值。类似这样的处理:
***Get variables' name and nvar;
data tmp3;
set sashelp.vcolumn end=eof;
where libname = "WORK" and memname = "CLASS";
call symputx("var"||strip(put(varnum, best.)), strip(name));
if eof then call symputx("nvar1", strip(put(varnum, best.)));
%put nvar1= &nvar1.;
run;
以当前演示数据集来看,这两种方式处理结果相同。但如果一个数据集中没有任何变量,这时候SASHELP.vcolumn中是没有记录的,而SASHELP.vtable中是有nvar=0的记录。此时后者无法抓取数据集的变量数。
如果感兴趣,读者可以用以下空数据集进行测试:
data test;
run;
宏循环需要在宏程序中进行,宏程序的构建尽可能包含可能的情形。这里根据变量数目进行分类处理。
***Temp macro;
%macro check_empty_var;
%if &nvar. = 0 %then %do;
data result;
length Dataset $50 empvar $2000;
dataset = "CLASS";
empvar = "There is no variable in the dataset Class!";
run;
%end;
%else %if &nvar. > 0 %then %do;
%if &nvar. = 1 %then %do;
proc sql noprint;
create table result1 as
select "CLASS" as domain length=50, "&var1." as Var length=50, sum(not missing(&var1.)) as Sum
from class
;
quit;
%end;
%if &nvar. > 1 %then %do;
proc sql noprint;
create table result1 as
select "CLASS" as Dataset length=50, "&var1." as Var length=50, sum(not missing(&var1.)) as Sum
from class
%do i = 2 %to &nvar.;
outer union corr
select "CLASS" as Dataset length=50, "&&var&i." as Var length=50, sum(not missing(&&var&i.)) as Sum
from class
%end;
;
quit;
%end;
*Display result;
proc transpose data = result1 out=result2 prefix=emp_;
by dataset;
var Var;
where sum = 0;
run;
data result;
set result2;
length Empvar $2000;
Empvar = catx(", ", of emp_:);
keep dataset empvar;
run;
%end;
%mend check_empty_var;
%check_empty_var;
以上宏程序运行结果如下,与手动编程结果保持一致。

宏参数一般有3类:
- 输入内容(变量/数据集)
- 输出内容(变量/数据集)
- 特定条件
这个宏程序从简,直接以Reslt数据集输出,不需要额外的筛选条件,只需设置输入数据集就好。为了省事,也不在宏里判断输入数据集的逻辑库名称、数据集名称,直接定义到宏参数中。
%macro check_empty_var(libname=WORK, memname=);
...
%mend check_empty_var;
确定宏参数后,需要在之前初步宏程序中进行内容替换,这样方便以后对不同参数对象的处理调用。
完整宏程序需综合以上内容,并尽可能考虑多种可能情况,以求宏程序运行稳定。例如,输入数据集不存在的情况;数据集没有空变量的情况。
如果输入数据集不存在,最好能在Log中输出一条Warning记录作为提醒。为避免一些程序文本检查机制的误判,War ning最好能拆开处理下。
其他处理细节就不再展开描述,汇总程序如下:
%macro check_empty_var(libname=WORK, memname=);
***Dataset exists;
%if %sysfunc(exist(&libname..&memname.)) = 1 %then %do;
***Get the number of vairiables;
data _null_;
set sashelp.vtable;
where libname = upcase("&libname.") and memname = upcase("&memname.");
call symputx("nvar", strip(put(nvar, best.)));
run;
%put nvar= &nvar.;
***Macro loop;
%if &nvar. = 0 %then %do;
data result;
length Dataset $50 Empvar $2000;
dataset = upcase("&libname..&memname.");
Empvar= "There is no variable in the dataset %sysfunc(upcase(&libname..&memname.)).";
run;
%end;
%else %if &nvar. > 0 %then %do;
**Get variables name;
data _null_;
set sashelp.vcolumn;
where libname = upcase("&libname.") and memname = upcase("&memname.");
call symputx("var"||strip(put(varnum, best.)), strip(name));
run;
%put var1= &var1.;
%if &nvar. = 1 %then %do;
proc sql noprint;
create table result1 as
select upcase("&libname..&memname.") as Dataset length=50, "&var1." as Var length=50, sum(not missing(&var1.)) as Sum
from &libname..&memname.
;
quit;
%end;
%if &nvar. > 1 %then %do;
proc sql noprint;
create table result1 as
select upcase("&libname..&memname.") as Dataset length=50, "&var1." as Var length=50, sum(not missing(&var1.)) as Sum
from &libname..&memname.
%do i = 2 %to &nvar.;
outer union corr
select upcase("&libname..&memname.") as Dataset length=50, "&&var&i." as Var length=50, sum(not missing(&&var&i.)) as Sum
from &libname..&memname.
%end;
;
quit;
%end;
*Display result;
proc transpose data = result1 out=result2 prefix=emp_;
by dataset;
var Var;
where sum = 0;
run;
data result;
set result2;
length emp_1 $50 Empvar$2000;
if not missing(emp_1) then empvar = catx(", ", of emp_:);
else do;
dataset = upcase("&libname..&memname.");
empvar = "There is no empty variable in the dataset %sysfunc(upcase(&libname..&memname.)).";
end;
keep dataset empvar;
run;
%end;
%end;
***Dataset does not exist;
%if %sysfunc(exist(&libname..&memname.)) ne 1 %then %do;
%put %sysfunc(compress(War ning)): Dataset %sysfunc(upcase(&libname..&memname.)) does not exist. ;
%end;
%mend check_empty_var;
%check_empty_var(libname=work, memname=class);
%check_empty_var(libname=work, memname=Yeteng);
如果数据集不存在,最后显示结果如下:

文章梳理了查询数据集中所有空变量宏程序构建过程,希望能够对读者有所帮助。单个数据集处理的完成,也方便后续对多个数据集批量处理。
感谢阅读, 欢迎关注:SAS茶谈!
若有疑问,欢迎评论交流!
我正在用Ruby编写一个简单的程序来检查域列表是否被占用。基本上它循环遍历列表,并使用以下函数进行检查。require'rubygems'require'whois'defcheck_domain(domain)c=Whois::Client.newc.query("google.com").available?end程序不断出错(即使我在google.com中进行硬编码),并打印以下消息。鉴于该程序非常简单,我已经没有什么想法了-有什么建议吗?/Library/Ruby/Gems/1.8/gems/whois-2.0.2/lib/whois/server/adapters/base.
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击
我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是
我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R
我知道我可以指定某些字段来使用pluck查询数据库。ids=Item.where('due_at但是我想知道,是否有一种方法可以指定我想避免从数据库查询的某些字段。某种反拔?posts=Post.where(published:true).do_not_lookup(:enormous_field) 最佳答案 Model#attribute_names应该返回列/属性数组。您可以排除其中一些并传递给pluck或select方法。像这样:posts=Post.where(published:true).select(Post.attr
有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳
几个月前,我读了一篇关于rubygem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:
我收到格式为的回复#我需要将其转换为哈希值(针对活跃商家)。目前我正在遍历变量并执行此操作:response.instance_variables.eachdo|r|my_hash.merge!(r.to_s.delete("@").intern=>response.instance_eval(r.to_s.delete("@")))end这有效,它将生成{:first="charlie",:last=>"kelly"},但它似乎有点hacky和不稳定。有更好的方法吗?编辑:我刚刚意识到我可以使用instance_variable_get作为该等式的第二部分,但这仍然是主要问题。
我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_