在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中进行递归。
第二,为了停止循环,我需要每次检查输出文件。它消耗额外的资源,而且不像递归中的基本情况那么漂亮。
最终输出还将包含一个提取空列,该列需要在该宏的末尾进行额外的处理。如果你能想出一个更好的解决方案,请告诉我。
对于第一个转换,到目前为止我还没有任何想法来处理这个问题。
这里你有这个问题的非常简单的解决方案,使用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;
您要做的是对数据集进行递归处理。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;