【问题标题】:SAS Looping through macro variable and processing the dataSAS通过宏变量循环并处理数据
【发布时间】:2018-03-14 18:40:55
【问题描述】:

我有一堆字符变量,需要从大型数据集中整理出来。不需要的变量都有相同或全部丢失的条目(这意味着我想在进一步处理数据之前从数据集中删除这些)。数据集非常大,因此无法手动完成,我会做很多次,所以我试图创建一个宏来完成这个。我使用以下代码创建了一个包含所有字符变量的列表宏变量(我的部分数据不同,但我使用相同类型的代码):

data test;
     input Obs ID Age;
datalines;
1 2 3
2 2 1
3 2 2
4 3 1
5 3 2
6 3 3
7 4 1
8 4 2
run;

proc contents
data = test
noprint
out = test_info(keep=name);
run;

proc sql noprint;
select name into : testvarlist separated by ' ' from test_info;
quit;

然后我的想法是仅使用数据步骤从原始数据集中删除此变量列表。现在,问题是我需要遍历每个变量,并确定该变量的观察结果是否都相同。我的想法是创建一个循环遍历所有变量的宏,并为每个变量计算条目的出现次数。由于该表的长度等于唯一条目的数量,因此我知道如果表的长度为 1,则应删除该变量。到目前为止,我的尝试是以下代码:

%macro ListScanner (org_list);
%local i next_name name_list;
%let name_list = &org_list;
%let i=1;
%do %while (%scan(&name_list, &i) ne );
    %let next_name = %scan(&name_list, &i);
    %put &next_name;    
        proc sql;
            create table char_occurrences as
            select &next_name, count(*) as numberofoccurrences
            from &name_list group by &next_name;    
            select count(*) as countrec from char_occurrences;
        quit;
    %if countrec = 1 %then %do;
        proc sql;
            delete &next_name from &org_list;
        quit;
    %end;
    %let i = %eval(&i + 1);
    %end;
%mend;

%ListScanner(org_list = &testvarlist);

虽然我遇到了语法错误,并且我的真实数据遇到了其他类型的问题,无法正确读取数据,但我一次只采取了一步。我在想我可能会使事情变得过于复杂,所以如果有人有更简单的解决方案或者可以看到可能出现的问题,我将非常感激。

【问题讨论】:

  • 不明白你想做什么,在你的宏程序中,问题是proc sql中的&name_list,&name_list被解析为变量列表,而不是数据集。
  • 您可以随时推出自己的产品,但有一些解决方案。我建议搜索 SAS 博客或 Google:“sas rick wicklin missing”。最快的方法是通过 IML,“最简单”的编码方法是 PROC FREQ 并使用 NLEVELS 变量。它还取决于什么是大文件。使用 PROC FREQ 方法应该可以处理少于 1000 万条记录,尽管它也可能取决于您的内存设置。
  • 您提到了字符变量,但您的示例是数字变量。变量是字符还是数字?

标签: loops sas


【解决方案1】:

有很多方法可以做到这一点。 但是,让我们看看您遇到的问题。

首先要遍历以空格分隔的名称列表,让 %do 循环为您增加索引变量会更容易。使用countw()函数求上界。

%do i=1 %to %sysfunc(countw(&name_list,%str( )));
  %let next_name = %scan(&name_list,&i,%str( ));
  ...
%end;

其次,您的 SQL 代码中的输入数据集在哪里?在宏定义中添加另一个参数。您想在哪里编写没有空列的数据集?所以也许是另一个参数。

%macro ListScanner (dsname , out, name_list);
%local i next_name sep drop_list ;

第三,您可以使用单个查询一次计算所有变量。只需使用count( distinct xxxx ) 而不是group by

proc sql noprint;
create table counts as 
  select 
  %let sep=;
  %do i=1 %to %sysfunc(countw(&name_list,%str( )));
    %let next_name = %scan(&name_list,&i,%str( ));
    &sep. count(distinct &next_name) as &next_name
    %let sep=,;
  %end;
  from &dsname 
;
quit;

所以这将得到一个包含一个观察值的数据集。您可以使用 PROC TRANSPOSE 将其转换为每个变量的一个观察值。

proc transpose data=counts out=counts_tall ;
  var _all_;
run;

现在您可以查询该表以查找具有 0 个非缺失值的列的名称。

proc sql noprint ;
  select _name_ into :drop_list separated by ' '
    from counts_tall
    where col1=0
  ;
quit;

现在您可以使用新的DROP_LIST 宏变量了。

data &out ;
  set &dsname ;
  drop &drop_list;
run;

所以现在剩下的就是清理自己了。

proc delete data=counts counts_tall ;
run;
%mend;

【讨论】:

  • 完美运行!非常感谢你!你能详细说明一下 sep 部分吗?为什么使用逗号而不是例如分隔空间?那么delete-step到底是做什么的呢?我对 SAS 很陌生,所以我还没有真正掌握正确的编码实践。
  • 在 SQL SELECT 语句中,您用逗号分隔变量。 SEP 宏变量只是在变量之间放置逗号的一种方式。 PROC DELETE 步骤将删除宏创建的工作表,只是为了清理。节省空间,但也可以避免在您多次调用宏或希望稍后在程序中将这些名称用于其他表时可能出现的问题。但是,如果在宏完成后对您有用,您可能希望保留一种或两种格式的计数数据集。
【解决方案2】:

就您最初提出的具体问题而言,这相当简单。假设&testvarlist 是您的宏变量,包含您感兴趣的变量,并在have 中创建一些测试数据:

%let testvarlist=x y z;

data have;
  call streaminit(7);
  do id = 1 to 1e6;
    x = floor(rand('Uniform')*10);
    y = floor(rand('Uniform')*10);
    z = floor(rand('Uniform')*10);
    if x=0 and y=4 and z=7 then call missing(of x y z);
    output;
  end;
run;


data want fordel;
  set have;
  if min(of &testvarlist.) = max(of &testvarlist.)
    and (cmiss(of &testvarlist.)=0 or missing(min(of &testvarlist.)))
  then output fordel;
  else output want;
run;

这并不是特别低效,但肯定有更好的方法来做到这一点,如 cmets 中所述。

【讨论】:

  • 最小/最大方法是否适用于字符数据?我知道这在 PROC SQL 中有效,但我认为 Base 函数需要数值。
  • @Reeza 是的,你是对的;我忘了它只适用于数字。奇怪的是 SAS 选择不为字符变量实现 MIN/MAX,但尽管如此。我会留下这个,因为它可能对有相同数字问题的人有帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多