很多的时候程序的比较并不一目了然.结果正确, 运行高效, 句法简单, 一定的通用性等等, 往往可以作为非正式的评判标准. 如果说某些标准具备主观性, 其中的前两个,正确性和效率, 往往更加客观,更加关键.
这里以前论坛上的某个问题以及给出的解决方案.因为可能牵扯较大的数据和较多的数据循环, 我这里做了简单的比较. 当然,结果和看起来或者原以为并不完全相同.
结果正确: 三个程序给定的结果完全一致
效率: 通过运行时间来决定.因为其中的运行时间的差异非常明显, 所以并没有反复运行实验.但是结果在不同的PC 和环境上也许有一定的差异.
简洁通用: 因人而异,不做深入比较.
具体问题来源.( https://bbs.pinggu.org/thread-3174189-1-1.html)
现有数据如下, 三个column (title,authors(不同的name用|隔开),number_authors)
Title Authors Number_authors
Title 1 Name A | Name B 2
Title 2 Name A | Name B | Name C 3..
大概有20000个observations,其中
1. title是unique的
2. number_authors 取值从1-200.
现在想做的是,对每一个observation生成一系列variables(5个):at_least_x_authors_repeat. X从1-5取整数值. 变量取值0或1也就是:at_least_1_authors_repeat; at_least_2_authors_repeat;at_least_3_authors_repeat;at_least_4_authors_repeat at_least_5_authors_repeat.
变量描述了在这组数据中有多少作者是重复的
方法 1- 一个DATA STEP
方法 2- 多个 SQL 过程
方法 3- 多个DATA STEP和SQL的混合.
- /*simulate data*/
- %let n =50000;
- data test;
- call streaminit(0);
- length Title $20. Authors $200.;
- array LD[26] $6. _temporary_;
- do i =1 to &n;
- title =cats('title', put(i, z10.));
- start =min(rand('poisson',2),10) +1;
- stop = max(start,min(rand('poisson',6),26)); call missing(of ld );
- do j =start to stop;
- ld[j] ='NAME '||substrn('ABCDEFGHIJKLMNOPQRSTUVWXYZ', j, 1);
- end;
- Authors =catx('|', of ld);
- Number_authors =stop -start +1;
- output;
- end;
- drop start stop i j;
- run;
- /*-----------------------------------------------------------------------------------------------*/
- %macro method1;
- proc sql noprint;
- select distinct cats(max(number_authors)) into: maxn from test;
- quit;
- data method1;
- set test;
- array t[&maxn] $10. _temporary_;
- call missing(of t);
- do i =1 to Number_authors;
- t =scan(authors, i, '|');
- end;
- k =0;
- do i =1 to nobs;
- set test(keep=authors rename=(authors=authors1)) point=i nobs=nobs;
- if _n_ ^=i then do j =1 to Number_authors;
- if not missing(t[j]) then if find(authors1, cats(t[j])) then s ++1;
- end;
- k =max(k, s);
- s =0;
- end;
- array f[5] at_least_1_authors_repeat at_least_2_authors_repeat at_least_3_authors_repeat at_least_4_authors_repeat at_least_5_authors_repeat;
- do i =1 to dim(f);
- f =0;
- if i <=k then f =1;
- end;
- drop k j s authors1;
- run;
- %mend method1;
- /*-----------------------------------------------------------------------------------------------*/
- %macro method2;
- data ex;
- length author $10;
- set test;
- do i=1 to Number_authors;
- author=strip(scan(authors,i,'|'));
- output;
- end;
- keep Title author;
- run;
- proc sql;
- create table ex1 as
- select t.title, t.author, t1.title as title1
- from ex t
- left join
- ex t1
- on t.author=t1.author
- and t.title ^=t1.title
- order by t.title,t.author,t1.title
- ;
- create table ex2 as
- select t.title,t.title1,sum(case when t.title1 is null then 0 else 1 end) as cnt
- from ex1 t group by t.title,t.title1
- order by t.title,t.title1
- ;
- create table method2 as
- select title,
- max(cnt) as max_cnt,
- case when max(cnt)>=1 then 1 else 0 end as at_least_1_authors_repeat,
- case when max(cnt)>=2 then 1 else 0 end as at_least_2_authors_repeat,
- case when max(cnt)>=3 then 1 else 0 end as at_least_3_authors_repeat,
- case when max(cnt)>=4 then 1 else 0 end as at_least_4_authors_repeat,
- case when max(cnt)>=5 then 1 else 0 end as at_least_5_authors_repeat
- from ex2
- group by title;
- quit;
- %mend method2;
- /*-----------------------------------------------------------------------------------------------*/
- %macro method3;
- proc sql noprint;
- select distinct max(number_authors) into: maxn
- from test;
- quit;
- %let maxn=&maxn; %put *&maxn*;
- data test1;
- array author(&maxn) $10;
- set test;
- comb=2**number_authors-1;
- fmt="Binary"||cats(number_authors)||".";
- do i=1 to comb;
- k=0;
- binary=reverse(putn(i,fmt));
- call missing(of author1- author&maxn);
- do j=1 to number_authors;
- if substr(binary,j,1)="1" then do;
- k+1;
- author(k)=left(scan(authors,j,"|"));
- end;
- end;
- call sortc(of author&maxn-author1);
- output;
- end;
- keep author1-author5 title;
- run;
- proc sql;
- create table test2 as
- select distinct author1,author2, author3,author4,author5,title,count(distinct title) as titlenum
- from test1
- group by author1,author2,author3,author4,author5
- order by author1,author2,author3,author4,author5;
- quit;
- data test3;
- set test2;
- by author1-author5;
- array repeat_(5);
- tmp=catx("*", of author1-author5);
- varn=count(tmp,"*")+1;
- repeat_(varn)=(titlenum>1);
- run;
- proc sql;
- create table author_repeat as
- select distinct title, max(repeat_1) as at_least_1_authors_repeat
- , max(repeat_2) as at_least_2_authors_repeat
- , max(repeat_3) as at_least_3_authors_repeat
- , max(repeat_4) as at_least_4_authors_repeat
- , max(repeat_5) as at_least_5_authors_repeat
- from test3
- group by title;
- quit;
- data method3;
- merge test author_repeat;
- by title;
- run;
- %mend method3;
运行结果
N = 1,000
1=0.89100003242492 seconds
2=8.24699997901916 seconds
3=3.28099989891052 seconds
N = 5,000
1=021.744000196457 seconds
2=499.838999986648 seconds
3=017.079999923706 seconds
N =10,000
1=0088.73000001907 seconds
2=2230.13800001144 seconds
3=0033.89900016785 second
N =20,000
1=342.348000049591 seconds2=000.000000000000 seconds(not available)
3=071.160000085831 seconds
N =50,000
1=2177.96100020408 seconds
2=0000.00000000000 seconds (not available)
3=0192.801999807357 seconds
如果想要比较结果,程序可以如下
- proc compare base =method1 compare =method2; run;
从结果来看,如果数据记录较少的时候(比如N=1,000), 三个程序的运行时间类似. 如果N =5000, 方法2 变得缓慢. 如果N =10,000, 方法2的运行时间至少20倍长.方法3的时间最短, 只有方法1 的1/2. 如果数据进一步增加至20,000, 方法2 因为时间过长,没有测试. 而方法3的效率凸现优势,只有方法1 的1 /5左右.N =50,000, 方法 3 的优势更加明显.
略做总结:
- 方法 1 是我的程序, 因为编写的思路和结构,似乎没有优化的可能性.如果数据量超过10,000, 运行比较缓慢.我猜想,如果N 超过100,000 程序几乎不可用. 我当时的思路是程序越简单越好.
- 方法 2 采用SQL过程.可以看得出, 许多人认为比较容易理解. 但是因为其中牵扯 不等式的LEFT JOIN, 如果数据很大, 这一过程非常缓慢. 在SQL 里, SAS SQL 差不多效率是最低的,虽然在较新的版本里效率似乎有所改善.也就是说 ,如果这个程序不在SAS 里运行,或许效率要好的多. 当N 超过20,000 程序几乎不可用.
- 方法 3 看起来最复杂,但是效率最好, 尤其是在数据尺寸增大的时候. 其实程序是否复杂并不是关键,因为一旦程序确立并稳定下来,你不必要时时去阅读原程序.而效率就变得尤为关键重要.
- 从本题目的要求来看,显然方法3是最好的选择,因为起运行时间比其他的良种方案要快的多.
by JingJu(my blog)