是否可以多次重复数据步骤(就像你可能在%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)
。这样的事情有可能吗?
谢谢!
答案 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步骤的宏通常会慢得多,因为每个步骤都有一些开销。