在SAS中,我有一个表,其中包含两个名为parent和child的列(变量)。我想把这个表改为两种不同的安排。
该表的一个例子是:
Obs number | parent | child
----------------------------
1 | D | B
2 | J | Q
3 | D | S
4 | K | J
5 | T | U
6 | B | T
构建此表的规则是:
在每一行(观察)中,父列中的值是子列中值的父级。 例如,在第1行和第2行中,我们发现D是B的父母,B是D的孩子; J是Q'父母和Q是J的孩子。
Obs number | parent | child
----------------------------
1 | D | B
2 | J | Q
一位家长可以有几个孩子。例如,在第1行和第3行中,我们发现B和S都是D的孩子。
Obs number | parent | child
----------------------------
1 | D | B
...
3 | D | S
一个孩子只能有一个父母。例如,B的父母是D,B不应该是D以外的任何父母的子女。不允许以下形式:
Obs number | parent | child
----------------------------
1 | D | B
...
n | C | B
孩子可以拥有自己的孩子,父母可以拥有自己的孩子。例如,在第1行和第6行中,我们发现B是D的孩子并且它有自己的孩子T:
Obs number | parent | child
----------------------------
1 | D | B
...
6 | B | T
在第2行和第4行中,我们发现J是Q的父级,它有自己的父级K:
Obs number | parent | child
----------------------------
2 | J | Q
...
4 | K | J
孩子不应该是自己父母的父母,父母不应该是自己孩子的孩子。例如,B是来自D的孩子,它不能再是D的父母。不允许使用以下表格:
Obs number | parent | child
----------------------------
1 | D | B
...
n | B | D
以上是构建原始表所需的所有规则。然后我想把表变成两种不同的安排。
首先,我想对这个表进行排序,使得父和子的相关行聚在一起,形成从最老的父级到最小的子级的链接。通过这个,我的意思是从左边的原始表到右边的变换表的转换,如下所示:
Original table: Transformed table:
Obs number | parent | child Obs number | parent | child
---------------------------- ----------------------------
1 | D | B 1 | D | S
2 | J | Q 2 | D | B
3 | D | S TO: 3 | B | T
4 | K | J 4 | T | U
5 | T | U 5 | K | J
6 | B | T 6 | J | Q
其次,我想将原始表转换为新表,以使新表的每一行都包含从最老的父级到最小的子级的列。我的意思是下面的转变:
Original table: Transformed table:
Obs number | parent | child Obs number | var1 | var2 | var3 | var4
---------------------------- --------------------------------------
1 | D | B 1 | D | B | T | U
2 | J | Q 2 | K | J | Q | .
3 | D | S TO: 3 | D | S | . | .
4 | K | J
5 | T | U
6 | B | T
以下是设置原始测试表的SAS代码:
data original_table;
input parent $ child $;
datalines;
D B
J Q
D S
K J
T U
B T
;
有人问过我尝试过的最小尝试。所以这就是。在我发布这里几个小时之后,我确实找到了进行第二次转换的方法。以下是代码:
%macro TransformTable(lib=, dt=, output=);
/* rename the variable in the input table */
proc sql noprint;
select cats(name,"=",cats(upcase("var"),substr("12", varnum, 1))) into :newvars separated by ' '
from sashelp.vcolumn
where libname = upcase("&lib.") and memname=upcase("&dt.") and
1 <= varnum <= 2;
quit;
data work._original_table;
set &lib..&dt.;
rename &newvars.;
run;
/* select values that are not child of other values (pure parent table) */
proc sql noprint;
create table _combine1
as select * from
_original_table
where VAR1 not in (select VAR2 from _original_table);
quit;
/* join the pure parent table and its update table to the original table multiple times */
/* move all child and child's child into one line */
%let nonmissing = 1;
%let i = 1;
%do %while(&nonmissing. > 0);
%let newfile_num = %eval(&i. + 1);
%let lastcol_num = %eval(&i. + 2);
proc sql noprint;
create table _combine&newfile_num.
as select a.*, b.VAR2 as VAR&lastcol_num.
from _combine&i. a
left join
_original_table b
on a.VAR&newfile_num. = b.VAR1;
quit;
proc sql noprint;
select count(*) into :nonmissing
from _combine&newfile_num.(where=(^missing(VAR&lastcol_num.)));
quit;
%put &nonmissing.;
%let i = %eval(&i. + 1);
%end;
/* remove the last empty column */
data &output. (drop = VAR&lastcol_num.);
set _combine&newfile_num.;
run;
%mend TransformTable;
%TransformTable(lib=work, dt=original_table, output=Result_for_2ndTrans)
但它还远远不够好。
首先我使用循环而不是递归,因为我不知道如何在SAS或SQL中进行递归。
其次,要停止循环,我需要每次检查输出文件。它消耗额外的资源,而不像递归中的基本情况那样漂亮。
最终输出还将包含一个提取空列,需要在此宏的末尾进行额外处理。如果您可以使用它并提出更好的解决方案,请告诉我。
对于第一次转型,到目前为止我还没有想到解决这个问题。
答案 0 :(得分:2)
您可以使用hash table
和hash iterator
解决此问题。
我希望这能产生正确的结果。你需要检查一下。
proc sort data=original_table;
by parent child;
run;
data _null_;
length path $32767 longest 8;
if _N_ = 1 then do;
declare hash h();
h.defineKey('path');
h.defineData('path','longest');
h.defineDone();
call missing(path, longest);
declare hiter hi('h');
end;
set original_table end=last;
put "PARENT: " parent " CHILD: " child;
longest = 1;
path = parent;
h.ref();
put "ADD NEW SOURCE: " path=;
rc = hi.first();
do while(rc = 0);
if substr(path, lengthn(path), 1) = parent then do;
longest = 0;
h.replace();
longest = 1;
path = cats(path, child);
h.ref();
put "ADD CHILD: " path=;
end;
rc = hi.next();
end;
rc = hi.first();
do while(rc = 0);
if substr(path, 1, 1) = child then do;
longest = 0;
h.replace();
longest = 1;
path = cats(parent, path);
h.ref();
put "ADD PARENT: " path=;
end;
rc = hi.next();
end;
if last;
h.output(dataset: 'result(where=(longest = 1))');
run;
data result;
set result;
len = lengthn(path);
run;
proc sort data=result;
by descending len path;
run;
答案 1 :(得分:1)
您想要做的是递归处理数据集。在宏处理器之外,SAS并不是真的可以做到这一点。您必须使用goto
之类的东西来构建递归函数。结果代码非常混乱。哈希表将有助于跟踪已使用的父/子对。
此解决方案有效,但可以进一步优化:
data transformed_table;
length var1 - var10 $1;
if _n_ = 1 then do;
declare hash h(dataset:"original_table ");
declare hiter hi("h");
h.definekey("parent", "child");
h.definedone();
call missing(ref);
end;
set original_table end = eof;
array var [*] var1 - var10;
var1 = parent; ref = 2;
var[ref] = child;
start:
check = 0;
do i = 1 to (n);
set original_table (rename=(parent=p1 child=c1) obs=1 ) point=i nobs=n;
rc = hi.first();
do while (rc = 0);
if h.check(key:p1, key:c1) eq 0 and var[ref] = p1 then do;
ref + 1;
var[ref] = c1;
check = 1;
end;
rc = hi.next();
if check = 1 then do;
h.remove(key:var[ref - 1], key:var[ref]);
goto start;
end;
end;
end;
keep var1 - var10 ;
if eof then h.output(dataset: 'id');
run;
proc sort data = transformed_table; by var1 var2; run;
proc sort data = id; by parent child; run;
data transformed_table;
merge transformed_table
id (in = a rename=(parent=var1 child=var2))
;
by var1 var2;
if a;
run;