SAS实现分组内排名

在做查询计算的时候分组以及分组内排名是很常见的操作,使用SQL的话就是group by和开窗函数。sas的话是支持SQL语法,但是不支持开窗函数。 如果用纯sas代码也是可以实现,但是如果是先用过sql或者其他编程语言,对于sas还是有些难理解。sas的数据集(data)是按行选循环,所以很多操作前都需要进行一下排序。按这个逻辑就好理解了。

我这里以学生成绩这样简单的数据演示一下,如下为表结构

姓名(name) 课程(course) 分数(score)
张三 英语 92
李四 语文 88

测试数据放在student_course_score.csv文件中

sas代码实现

首先读取csv数据到sas数据集中

proc import datafile='student_course_score.csv'
    out=student_course_score
    dbms=csv
    replace;
    getnames=yes;
run;

分组计算

使用纯sas代码实现,大概逻辑就是分组中的第一行到最后一行score加起来。

proc sort data=student_course_score;
    by name;
run;

data total_score;
    set student_course_score;
    by name;
    retain total_score 0;
    if first.name then total_score = 0;
    total_score + score;
    if last.name then output;
    drop course score;
run;

使用PROC SQL实现,和我们用MySQL或者Postgres等关系型数据库语法差不多,group by直接用sum方法计算即可。

proc sql;
    create table total_score as
    select name, sum(score) as total_score
    from student_course_score
    group by name;
run;

分组排名

同样的逻辑,就是先排序,然后通过组内前后顺序依次赋予名次

proc sort data=student_course_score;
    by course descending score;
run;

data course_rank;
    set student_course_score;
    by course;
    if first.course then ranking = 1;  /* 将第一个观察值的排名设为 1 */
    else ranking + 1; /* 对于同一分组内的后续观察值,排名递增 */
    retain ranking;  /* 保持变量在观测之间的持久性 */
run;

有了排名,那么类似于取每科前三名(分组取top)的筛选也方便了

data course_top3;
    set course_rank;
    if ranking <= 3;
run;

附:SQL实现

Postgres以及MySQl8.0都支持开窗函数,在进行分组排名或者分组取前几这样的计算就简单多了

SELECT
    *, row_number() over ( 
        PARTITION BY course ORDER BY score DESC
    )  AS ranking
FROM student_course_score;

最后

最后还是得diss一下SAS,真是服了,我用DATALINES;直接写入数据到数据集的话,中文后面乱码(我看默认长度是8),如果指定长度($20.这样)又会无视分隔符导致数据错乱,不得已还是写到文件中用sas再读取到数据集。