【问题标题】:Sas Macro to semi-efficiently manipulate dataSas 宏半有效地操作数据
【发布时间】:2015-03-04 22:38:50
【问题描述】:

目标:从 Have 表 + Help 表到 Want 表。当前的实现(如下)很慢。我相信这是如何不使用 SAS 宏的一个很好的例子,但我很好奇是否...... 1. 宏观方法可以被挽救/变得足够快以使其可行 (例如 proc append 应该加快堆叠数据集的动作,但我看不到任何性能提升。) 2. 所有备选方案的外观。

我已经编写了一个非宏观解决方案,我将在下面发布以进行比较。

Data: 
data have ; 
input name $ term $; 
cards;
Joe   2000 
Joe   2002
Joe   2008 
Sally 2001
Sally 2003
; run; 

proc print ; run; 

data help ; 
input terms $ ; 
cards; 
2000
2001
2002
2003
2004
2005
2006
2007
2008
; run; 

proc print ; run; 

data want ; 
input name $ term $ status $; 
cards;
Joe   2000  here
Joe   2001  gone
Joe   2002  here
Joe   2003  gone
Joe   2004  gone
Joe   2005  gone
Joe   2006  gone
Joe   2007  gone
Joe   2008  here
Sally 2001  here
Sally 2002  gone
Sally 2003  here
; run; 

proc print data=have ; run; 

我可以为每个人编写一个小宏来让我到达那里:

%MACRO RET(NAME); 
proc sql ; 
create table studtermlist as 
select distinct term 
from have 
where NAME = "&NAME"
; 
SELECT Max(TERM) INTO :MAXTERM 
FROM HAVE
WHERE NAME = "&NAME"
; 
SELECT MIN(TERM) INTO :MINTERM 
FROM HAVE
WHERE NAME = "&NAME"
; 
CREATE TABLE TERMLIST AS 
SELECT TERMS  
FROM HELP 
WHERE TERMS BETWEEN "&MINTERM." and "&MAXTERM."
ORDER BY TERMS 
;
CREATE TABLE HEREGONE_&Name AS 
SELECT 
A.terms , 
"&Name" as Name,
CASE 
WHEN TERMS EQ TERM THEN  'Here'
when term is null THEN 'Gone'
end as status
from termlist a left join studtermlist b 
 on a.terms eq b.term 
; 
quit; 
%MEND RET ; 


%RET(Joe);
%RET(Sally);

proc print data=HEREGONE_Joe; run; 
proc print data=HEREGONE_Sally; run; 

但它并不完整。如果我遍历 for(大概有很多名字)......

*******need procedure for all names - grab info on have ; 
proc sql noprint; 
select distinct name into :namelist separated by ' '
from have
; quit;

%let n=&sqlobs ; 


%MACRO RETYA ; 
OPTIONS NONOTEs ; 
%do i = 1 %to &n ; 
 %let currentvalue = %scan(&namelist,&i); 
 %put &currentvalue ; 
 %put &i ; 
%RET(&currentvalue);
%IF &i = 1 %then %do ; 
data base; set HEREGONE_&currentvalue; run; 
                 %end; 
%IF &i gt 1 %then %do ; 
proc sql ; create table base as 
select * from base
union 
select * from HEREGONE_&currentvalue
;
drop table HEREGONE_&currentvalue;
quit;
                 %end; 
%end ; 
OPTIONS NOTES; 
%MEND; 

%RETYA ; 

proc sort data=base ; by name terms; run; 
proc print data=base; run; 

所以现在我想要,但是有 6,000 个名字,需要 20 多分钟。

【问题讨论】:

  • HELP 数据集中的 TERMS 实际上是数字吗?它们是否像示例中那样连续?
  • 不使用宏怎么办?
  • 条款是故意的。实际术语可以包含多个零 (201000) 并存储为字符。
  • 嗯...我的第一个解决方案假设相反。稍后我会再看一遍,但这取决于您的条款是如何存储的。
  • 条款是故意的。实际术语可以包含多个零 (201000) 并存储为字符。它们是连续的……尽管规模不寻常:200000、200010、200015、20020、200050、200060……但这可以通过使用“帮助”表来处理。感谢您的回复,我期待有时间仔细检查它们。

标签: sas sas-macro


【解决方案1】:

让我们尝试替代解决方案。对于每个名称,通过 proc SQL 数据步骤查找最小/最大术语。然后使用数据步骤创建时间段表并将其与原始表合并。

*Sample data;
data have ; 
input name $ term ; 
cards;
Joe   2000 
Joe   2002
Joe   2008 
Sally 2001
Sally 2003
; run; 

*find min/max of each name;
proc sql;
create table terms as
select name, min(term) as term_min, max(term) as term_max
from have
group by name
order by name;
quit;

*Create table with the time periods for each name;
data empty;
set terms;
do term=term_min to term_max;
output;
end;
drop term_min term_max;
run;

*Create final table by merging the original table with table previously generated;
proc sql;
create table want as
select a.name, a.term, case when missing(b.term) then 'Gone'
                        else 'Here' end as status
from empty a
left join have b
on a.name=b.name
and a.term=b.term
order by a.name, a.term;
quit;

编辑:现在查看您的宏解决方案,部分问题是您扫描表的次数过多。

  • 第一个表,studenttermlist不是必须的,最后一个join可以 而是被过滤。
  • 两个宏变量,最小/最大项可以是 一次计算
  • 避免使用较小的临时术语列表并使用 where 子句来过滤结果
  • 使用 Call Execute 调用您的宏而不是另一个宏循环
  • 而不是循环附加 数据,利用命名约定并使用单个数据 追加所有输出的步骤。

    %MACRO RET(NAME); 
    proc sql noprint; 
    
    SELECT MIN(TERM), Max(TERM) INTO :MINTERM,  :MAXTERM
    FROM HAVE
    WHERE NAME = "&NAME"
    ; 
    
    
    CREATE TABLE _HG_&Name AS 
    SELECT 
    A.terms , 
    "&Name" as Name,
    CASE 
    WHEN TERMS EQ TERM THEN  'Here'
    when term is null THEN 'Gone'
    end as status
    from help a 
    left join have b 
     on a.terms eq b.term 
     and b.name="&name"
     where a.terms between "&minterm" and "&maxterm";
    ; 
    quit; 
    %MEND RET ; 
    
    
    *call macro;
    proc sort data=have;
    by name term;
    run;
    
    data _null_;
        set have;
        by name;
        if first.name then do;
        str=catt('%ret(', name, ');');
        call execute(str);
        end;
    run;
    
    
    *append results;
    data all;
        set _hg:;
    run;
    

【讨论】:

    【解决方案2】:

    您实际上可以在单个嵌套 SQL 查询中执行此操作。这会很混乱,很难阅读。

    我将把它分成三个部分。

    首先,获取不同的名称;

    proc sql noprint;
    create table names as
    select distinct name from have;
    quit;
    

    第二,笛卡尔产品名称和术语,以获得所有组合。

    proc sql noprint;
    create table temp as
    select a.name, b.terms as term
    from names as a,
         help as b;
    quit;
    

    三、左连接查找匹配项

    proc sql noprint;
    create table want as
    select a.name,
           a.term,
           case
              when missing(b.term) then "gone"
              else "here"
           end as Status
    from temp as a
    left join
         have as b
    on a.name=b.name
    and a.term=b.term;
    quit;
    

    最后,删除临时表以节省空间;

    proc datasets lib=work nolist;
    delete temp;
    run;
    quit;
    

    正如 Reeza 所示,还有其他方法可以做到这一点。正如我上面所说,您可以将所有这些合并到一个 SQL 连接中并获得您想要的结果。根据计算机内存和数据大小,应该没问题(并且可能会更快,因为所有内容都在内存中)。

    【讨论】:

      【解决方案3】:
      proc sql;
      create table want as
      select c.name, c.terms, a.term, 
             ( case when missing(a.term) then "Gone"
                 else "Here" end ) as status
      from (select distinct a.name, b.terms
            from have a, help b) c
      left join have a
      on c.terms = a.term and c.name = a.name
      order by c.name, c.terms, a.term
      ;
      

      【讨论】:

        【解决方案4】:

        我将提供类似的答案,以便稍后进行比较。

        proc sql ; 
        create table studtermlist as 
        select distinct term,name 
        from have 
        ; 
        create table MAXMINTERM as 
        SELECT Max(TERM) as MAXTERM, Min(TERM) as MINTERM, name  
        FROM HAVE
        GROUP BY name
        ; 
        CREATE TABLE TERMLIST AS 
        SELECT TERMS,name  
        FROM HELP a,MAXMINTERM b 
        WHERE TERMS BETWEEN MINTERM and MAXTERM
        ORDER BY name,TERMS 
        ;
        CREATE TABLE HEREGONE AS 
        SELECT 
        a.terms , 
        a.Name  ,
        CASE 
        WHEN TERMS EQ TERM THEN  'Here'
        when term is null THEN 'Gone'
        end as status
        from termlist a left join studtermlist b 
         on a.terms eq b.term
         and a.name eq b.name 
        order by name, terms
        ; 
        quit; 
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-11-27
          • 1970-01-01
          • 2022-08-07
          • 1970-01-01
          • 1970-01-01
          • 2017-06-08
          • 1970-01-01
          相关资源
          最近更新 更多