如何用SAS对数据进行行内排序?
一、行内排序需求
SAS 程序员都知道排序要用 PROC SORT 或者 PROC SQL,但是 PROC SORT 和 PROC SQL 只能将整个数据集按照某一列或者某几列排序,不能对一条记录内的多个变量进行行内排序。
那么,什么时候我们需要对数据进行行内排序呢?现在我们看几个需求:
1)信用卡评分结果中提供的拒绝理由
有些国家的法律规定,银行或者信用卡公司必须提供拒绝授予信用卡的理由,比如:是因为年龄问题还是收入问题?一种实现的方法是将信用卡模型中用到的所有特征项得分与该特征项的平均得分相减,然后将所有这些特征项的差值从小到大排序,取值最小的 k 个特征项就是最后提交给客户的拒绝理由,其中 k 的大小由银行或者评分机构自行设定。 SAS/EM 从 7.1 版本开始提供这个功能,其代码就是对行内的差值数据进行排序。
2)RFM 分析
这个案例取自 2013年 SAS 全球用户大会的一篇论文。
从上图可以看出,我们需要对每个客户在线购买的产品种类分别按照购买的频数和金额进行汇总,并按照从大到小的顺序显示。
原文:http://support.sas.com/resources/papers/proceedings13/376-2013.pdf
3)顺序抽样
个人感觉这种应用比较少,本文就不介绍了,大家有兴趣的话可以阅读:
http://www2.sas.com/proceedings/sugi29/075-29.pdf
二、样例数据和任务
假设我们现在有一张表,共有八列,数据如下所示:
Obs | col_1 | col_2 | col_3 | col_4 | col_5 | col_6 | col_7 | col_8 |
---|---|---|---|---|---|---|---|---|
1 | 6 | 5 | 3 | 1 | 8 | 7 | 2 | 4 |
现在我们需要对表内的每一行从小到大排序,结果如下所示:
Obs | col_1 | col_2 | col_3 | col_4 | col_5 | col_6 | col_7 | col_8 |
---|---|---|---|---|---|---|---|---|
1 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
样例数据通过下面的代码可以生成:
data raw;
array col(*) col_1-col_8;
input col_1-col_8;
cards;
6 5 3 1 8 7 2 4
;
run;
proc print;
run;quit;
三、实现的方式
SAS提供了多种方法帮助用户实现行内排序,我们按照从易到难的顺序逐一介绍:
1)SORTC 和 SORTN 例程
CALL SORTC (variable-1<, …, variable-n>) ;
CALL SORTN (variable-1<, …, variable-n>) ;
注:SORTC 和 SORTN 这两个例程只能按照从小到大的顺序排序。
2) LARGEST、SMALLEST 和 ORDINAL 函数
LARGEST (k, value-1-2<, value …>)
SMALLEST (k, value-1<, value-2 …>)
ORDINAL (k, value-1-2<, value …>)
方法一和方法二中用到的例程和函数,根据《SAS Functions by Examples》的作者 Dr. Ron Cody ,被 SAS 用户评选为最有用的 SAS 函数之一,请参阅他在SAS全球用户大会上的文章《A Survey of Some of the Most Useful SAS? Functions》。
3)转置加排序方法
PROC TRANSPOSE + PROC SORT,这种方法在下面的两篇文章里都有提到,有兴趣的同学请自行阅读,不难。
http://support.sas.com/resources/papers/proceedings12/117-2012.pdf
http://support.sas.com/resources/papers/proceedings13/376-2013.pdf
4)冒泡排序法
对冒泡排序法不熟悉的同学,建议先看一下 wiki 上的介绍,简单易懂,非常不错。
http://en.wikipedia.org/wiki/Bubble_sort
在SAS的官网和SAS全球用户大会上可以查到几篇包含冒泡排序法的文章,代码都不是最高效的,但是我们可以很容易结合两种不同代码的优点,实现一个相对高效的版本。
代码一:来自文章 http://support.sas.com/resources/papers/proceedings13/376-2013.pdf
data sorted(drop= sorted I temp);
set raw;
array code(*) col_1-col_8;
do until (sorted);
sorted=1;
do i = 1 to dim(code)-1;
if code(i) > code(i+1) then do;
temp=code(i+1);
code(i+1)=code(i);
code(i)=temp;
sorted=0;
end;
end;
end;
run ;
proc print;
run;quit;
代码二:来自SAS官网 http://support.sas.com/kb/24/754.html
data sorted(drop= I J temp);
set raw;
array S(*) col_1-col_8;
do I=1 to dim(S);
do J=1 to dim(S)-I;
if S(J) > S(J+1) then do;
Temp = S(J);
S(J)= S(J+1);
S(J+1) = Temp;
end;
end;
end;
run ;
proc print;
run;quit;
代码三:对前面两种代码改进之后的版本。
data sorted(drop= n sorted I temp);
set raw;
array code(*) col_1-col_8;
n = dim(code);
do until (sorted);
sorted=1;
do i = 1 to n-1;
if code(i) > code(i+1) then do;
temp=code(i+1);
code(i+1)=code(i);
code(i)=temp;
sorted=0;
end;
end;
n = n-1;
end;
run ;
proc print;
run;quit;
不同方法的比较:
代码一:第一遍循环后最后一个数据已经是最大值,在第二遍循环时可以不用再做任何比较,依此类推,第i遍循环时只需比较 1 到 (n-i)个数即可,而代码一却没有考虑这种情况。
循环共六次,比较操作共42次(6*7=42),每次循环后的排序结果:
5 3 1 6 7 2 4 8
3 1 5 6 2 4 7 8
1 3 5 2 4 6 7 8
1 3 2 4 5 6 7 8
1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8
代码二:没有考虑数据已经排好序的情况。
循环共八次,比较操作共28次(7+6+5+4+3+2+1=28),每次循环后的排序结果:
5 3 1 6 7 2 4 8
3 1 5 6 2 4 7 8
1 3 5 2 4 6 7 8
1 3 2 4 5 6 7 8
1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8
代码三:同时考虑上述两个问题。
循环共六次,比较操作共27次(7+6+5+4+3+2=27),每次循环后的排序结果:
5 3 1 6 7 2 4 8
3 1 5 6 2 4 7 8
1 3 5 2 4 6 7 8
1 3 2 4 5 6 7 8
1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8
当我们将列数增加到80列时,性能比较的结果如何呢?
方法 |
循环次数 |
比较次数 |
方法一 |
67 |
5293 |
方法二 |
80 |
3160 |
方法三 |
67 |
3082 |
测试数据和代码详见下面两篇文章:
http://blog.sina.com.cn/s/blog_8db50cf70101h23s.html
http://blog.sina.com.cn/s/blog_8db50cf70101h1va.html
5)插入排序法
http://en.wikipedia.org/wiki/Insertion_sort
代码来自文章: http://www2.sas.com/proceedings/sugi29/075-29.pdf
data sorted(drop=i j temp);
set raw;
array nums(*) col_1-col_8;
do i=2 to dim(nums);
temp = nums{i};
j=i;
do while (nums{j-1} > temp);
nums{j} = nums{j-1};
j = j-1;
if j <=1 then leave;
end;
nums{j} = temp;
end;
run ;
循环7次,交换操作16次(1+2+3+0+1+5+4=16),每次循环后的排序结果:
5 6 3 1 8 7 2 4
3 5 6 1 8 7 2 4
1 3 5 6 8 7 2 4
1 3 5 6 8 7 2 4
1 3 5 6 7 8 2 4
1 2 3 5 6 7 8 4
1 2 3 4 5 6 7 8
6)快速排序法
http://en.wikipedia.org/wiki/Quicksort
代码来自文章: http://www2.sas.com/proceedings/sugi29/075-29.pdf
data sorted(drop=stackPointer first last left right true pivot temp);
set raw;
array nums(*) col_1-col_8;
array stack{64} _temporary_;
stack{1}=1;
stack{2}=8;
stackpointer=3;
do while (stackpointer ne 1);
stackpointer=stackpointer-2;
first=stack{stackpointer};
last=stack{stackpointer+1};
do while (first < last);
pivot = nums{int((first+last)/2)};
left = first-1;
right = last+1;
true=1;
do while (true);
do until (nums{right} le pivot);
right=right-1;
end;
do until (nums{left} ge pivot);
left=left+1;
end;
if left ge right then leave;
temp = nums{left};
nums{left} = nums{right};
nums{right} = temp;
end;
if (right-first) lt (last-right) then do;
stack{stackpointer} = right+1;
stack{stackpointer+1} = last;
stackpointer=stackpointer+2;
last=right;
end;
else do;
stack{stackpointer} = first;
stack{stackpointer+1} = right;
stackpointer=stackpointer+2;
first = right+1;
end;
end;
end;
run;
三、大数据时的策略
1)SORTC 和 SORTN 这两个例程只能按照从小到大的顺序排序,而且 DS2(最新一代的 SAS 编程语言)不支持该例程的调用,所以无法实现库内或者并行计算。
2)DS2 支持函数 LARGEST、SMALLEST 和 ORDINAL。
3)转置过程步无法在数据库内运行或者采用并行模式运行,对于大数据,这种方式显然无法采用。
4)冒泡排序、插入排序和快速排序因为都是 data step 代码,且没有使用特别的函数,应该能够转换成 DS2 代码。
四、参考文献
http://support.sas.com/resources/papers/proceedings13/376-2013.pdf
http://support.sas.com/kb/24/754.html
http://www2.sas.com/proceedings/sugi29/075-29.pdf
http://www2.sas.com/proceedings/sugi29/217-29.pdf
http://support.sas.com/resources/papers/proceedings09/142-2009.pdf
http://support.sas.com/resources/papers/proceedings12/117-2012.pdf
转载请注明:数据分析 » 如何用SAS对数据进行行内排序?_sas数据排序_sas培训