SAS - 重复数据步骤以求解值

时间:2015-04-03 02:48:32

标签: sas

是否可以多次重复数据步骤(就像你可能在%do-%while循环中),其中重复次数取决于数据步骤的结果?

我有一个带有数字变量A的数据集。我计算一个新变量result = min(1,A)。我希望结果的平均值等于一个目标,我可以通过将变量A缩放一个常数k来实现。这就解决了k,其中target = average(min(1,A * k)) - 其中k和target是常量,A是列表。

这是我到目前为止所做的:

filename f0 'C:\Data\numbers.csv';
filename f1 'C:\Data\target.csv';

data myDataSet;
    infile f0 dsd dlm=',' missover firstobs=2;
    input A;
    init_A = A; /* store the initial value of A */
run;

/* read in the target value (1 observation) */
data targets;
    infile f1 dsd dlm=',' missover firstobs=2;
    input target;
    K = 1; * initialise the constant K;
run;

%macro iteration; /* I need to repeat this macro a number of times */
    data myDataSet;
        retain key 1;
        set myDataSet;
        set targets point=key;

        A = INIT_A * K; /* update the value of A /*
        result = min(1, A);
    run;

    /* calculate average result */
    proc sql;
        create table estimate as 
        select avg(result) as estimate0
        from myDataSet;
    quit;

    /* compare estimate0 to target and update K */
    data targets;
        set targets;
        set estimate;

        K = K * (target / estimate0);
    run;
%mend iteration;

我可以通过运行%iteration几次来获得所需的答案,但理想情况下我希望运行迭代直到(target - estimate0 < 0.01)。这样的事情有可能吗?

谢谢!

2 个答案:

答案 0 :(得分:1)

前几天我遇到了类似的问题。以下方法是我使用的方法,您需要将循环结构从for循环更改为do while循环(或任何适合您的目的):

首先对表进行初始扫描,以确定循环终止条件并获取表中的行数:

data read_once;
  set sashelp.class end=eof;

  if eof then do;
    call symput('number_of_obs', cats(_n_) );    
    call symput('number_of_times_to_loop', cats(3) );
  end;
run;

确保结果符合预期:

%put &=number_of_obs;
%put &=number_of_times_to_loop;

多次遍历源表:

data final;

  do loop=1 to &number_of_times_to_loop;
    do row=1 to &number_of_obs;
      set sashelp.class point=row;
      output;
    end;
  end;

  stop; * REQUIRED BECAUSE WE ARE USING POINT=;

run;

答案 1 :(得分:0)

两部分答案。

首先,你可以做你说的话。如果你想要一个迭代宏的工作,有用的代码示例,有一些代码可以像在线一样工作。例如,David Izrael的开创性Rakinge macro,它通过迭代一个相对简单的过程(基本上是proc freqs)来执行加权过程。这非常类似于您正在做的事情。在此过程中,它在各种终止标准的datastep中查找,并输出一个宏变量,即满足的标准总数(因为每个分层变量需要单独满足终止标准)。然后检查%if是否符合该标准,如果符合则终止。

这个核心是两件事。首先,除非你喜欢无限循环,否则你应该有一个固定的最大迭代次数。这个数字应该大于您应该需要的最大合理数字,通常大约为两倍。其次,您需要收敛标准,以便您可以终止循环。

例如:

data have;
  x=5;
run;

%macro reduce(data=, var=, amount=, target=, iter=20);
   data want;
     set have;
   run;
   %let calc=.;
   %let _i=0;
   %do %until (&calc.=&target. or &_i.=&iter.);
     %let _i = %eval(&_i.+1);
      data want;
        set want;
        &var. = &var. - &amount.;
        call symputx('calc',&var.);
      run;
   %end;
   %if &calc.=&target. %then %do;
     %put &var. reduced to &target. in &_i. iterations.; 
   %end;
   %else %do;
     %put &var. not reduced to &target. in &iter. iterations.  Try a larger number.;
   %end;
%mend reduce;

%reduce(data=have,var=x,amount=1,target=0);

这是一个非常简单的例子,但它具有所有相同的元素。我更喜欢自己使用do-until和increment,但你也可以做相反的事情(如%rakinge那样)。可悲的是,宏语言并不像数据步骤语言那样允许进行操作。哦,好吧。


其次,您通常可以在单个数据步骤中执行此类操作。即使在旧版本(9.2等)中,您也可以在单个数据步骤中完成上面提到的所有操作,尽管它看起来有点笨拙。在9.3+,特别是9.4中,有一些方法可以在数据步骤中运行proc sql并使用RUN_MACRO或DOSUBL和/或FCMP语言在不等待其他数据步骤的情况下返回结果。即使是简单的事情,例如:

data have;
  initial_a=0.3;
  a=0.3;
  target=0.5;
  output;
  initial_a=0.6;
  a=0.6;
  output;
  initial_a=0.8;
  a=0.8;
  output;
run;

data want;
  k=1;
  do iter=1 to 20 until (abs(target-estimate0) < 0.001);
    do _n_ = 1 to nobs;
      if _n_=1 then result_tot=0;
      set have nobs=nobs point=_n_;
      a=initial_a*k;
      result=min(1,a);
      result_tot+result;
    end;
    estimate0 = result_tot/nobs;
    k = k * (target/estimate0);
  end;
  output;
  stop;
run;

在一个数据步骤中完成所有操作。我有点作弊,因为我正在编写自己的数据步迭代器,但这在这类事情中相当常见,而且速度非常快。迭代多个数据步骤和proc sql步骤的宏通常会慢得多,因为每个步骤都有一些开销。