写SAS程序时我们会用到一些带下划线的变量如_N_, _ERROR_,这些变量在某些时候能给写程序带来很大的便利,本次笔记是尝试对这些变量种类和用法的总结。

在查阅一些SAS书籍和SAS帮助中心文档后,个人认为这类变量可以分成自动变量、变量列表、语句里的临时变量以及宏里面的自动变量:

变量类型

举例

自动变量

_N_, _ERROR_, FIRST.variable, LAST.variable

变量列表

_ALL_, _CHARACTER_, _NUMERIC_

语句临时变量

MERGE语句中的IN=,SET语句中的INDSNAME,PROC MEANS/FREQ/TRANSPOSE语句中的_TYPE_, _FERQ_, _NAME_, _LABEL_

宏里面的自动变量

&SYSDATE, %SYSDAY, etc.

本次文章主要总结自动变量和变量列表,临时变量归属于SET/MERGE等各个语句、数量众多,和宏里的自动变量一并之后再总结。

自动变量 Automatic Variables

专门指代DATA步执行时系统产生的变量,在官网文档中提到了这几个:_CMD_, _ERROR_, _IORC_, _N_, _MSG_, FIRST.variable, LAST.variable

常用基础变量(N, ERROR

其中_N_, _ERROR_是里关联使用的基础变量,_N_用来表示SAS处理数据循环经过的迭代次数,_ERROR_表示DATA步中程序是否出错,默认值为0,遇到错误为1。

data test_error_n;
    input raw_value $;
    /* 尝试将字符转换为数字 */
    num_val = input(raw_value, best.);
    
    /* 1. _N_ :记录当前是第几次循环 */
    loop_count = _N_;
    
    /* 2. _ERROR_ :如果转换失败,_ERROR_ 会自动变 1 */
    if _ERROR_ = 1 then do;
        put "【警告】: 第 " _N_ " 行发生错误。";
        put "         原始数据为: " raw_value;
    end;
    else do;
        put "【成功】: 第 " _N_ " 行处理正常。";
    end;
​
    datalines;
100
ABC
200
;
run;
​
/* 得到结果 */
 【成功】: 第 1  行处理正常。
 NOTE: 函数 INPUT 的参数无效,位置: 行 74 列 15。
 【警告】: 第 2  行发生错误。
          原始数据为: ABC
 规则:    ----+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8----+----9----+----0                     
 92         ABC
 raw_value=ABC num_val=. loop_count=2 _ERROR_=1 _N_=2
 【成功】: 第 3  行处理正常。

输入的数据类型和规定的类型不一致,因此报错。

BY分组变量(FIRST.variable, LAST.variable)

你没看错,BY里常用的分组变量FIRST/LAST也是自动变量,分组之后可以对其选出第一条,对其操作

/* 1. 准备数据 */
proc sort data=sashelp.class out=class_sorted;
    by Sex;
run;
​
/* 2. 分组计数 */
data check_first_last;
    set class_sorted;
    by Sex;
    is_first = first.Sex;
    is_last  = last.Sex;
​
    if first.Sex then count = 0; 
    count + 1;
    
    if last.Sex then put "分组结束: " Sex= " 总人数=" count;
run;
​
 分组结束: Sex=F  总人数=9
 分组结束: Sex=M  总人数=10

特殊场景变量(IORC, CMD, MSG

_IORC_,用于处理数据读写异常等自动变量,当顺序读取或者原地更新的时候,发生找不到记录的现象就会停止报错,通过IORC,可以拦截这个错误,决定是否继续让程序运行。

/* 1. 准备数据:客户表(必须建立索引) */
data customers(index=(cust_id));
    input cust_id name $;
    datalines;
101 Alice
102 Bob
;
run;
​
/* 2. 准备数据:交易表(无需排序,且包含一个不存在的客户 999) */
data transactions;
    input cust_id amount;
    datalines;
101 50
999 200  /* 这是一个脏数据,客户表中没有 999 */
102 80
;
run;
​
/* 3. 使用 _IORC_ 进行容错匹配 */
data result;
    set transactions; 
    set customers key=cust_id / unique; 
    
    if _iorc_ ^= 0 then do;
        put "【警告】: 交易记录中的客户 " cust_id " 在客户表中不存在!";
        name = "Unknown";
        _error_ = 0;
    end;
run;
​
【警告】: 交易记录中的客户 999  在客户表中不存在!

至于_CMD_, _MSG_,是制作简易交互界面的功能(第一次知道SAS还能做交互界面),在SAS EG和Web端如SAS viya上无法显示窗口。

其实还有一个_NULL_,工作中也用到过,它可以不让语句里的数据集输出到硬盘且直接获得调试结果,运行速度更快,在实际工作中主要用在调试打印和生成宏变量。

data _null_;
    set sashelp.class end=last_row;
    
    /* 1. 累加计算年龄 */
    retain sum_age 0;
    sum_age + age;
    
    /* 2. 输出 */
    if last_row then do;
        put "===============================";
        put "【统计完成】 总年龄是: " sum_age;
        put "===============================";
        
        call symputx('total_age_macro', sum_age);
    end;
run;
​
%put 这里的总年龄是宏变量的值: &total_age_macro;
​
​
===============================
 【统计完成】 总年龄是: 253
===============================

变量列表(SAS Variable Lists)

其实变量列表在官方文档里是一种语法的概念,既包含了语句、也包含了一些变量合集,比如:

编号范围列表

var1-var6,指代一连串变量

名称范围列表

x--b,指代数据集中x到b的所有列

名称前缀列表

work.temp:,指代work库中所有temp开头的变量、数据集

特殊SAS名称列表

_NUMERIC_

_CHARACTER_

_ALL_

指代当前data步中:

数据集里所有数值变量;

数据集里所有字符变量;

数据集中已定义的所有变量

示例

除了变量列表范围,这里还提到了个FUNCTION: OF,可以和之前的变量列表进行连用,直接用示例说明:

data numbered_range;
    input Score1-Score4 8.; 
    
    /* 用of运算符选择变量,3个总计结果一致 */
    Total_Score1 = sum(of _NUMERIC_);
    Total_Score2 = sum(of Score1-Score4);
    Total_Score3 = sum(of Score:);
    
    datalines;
10 20 30 40
5 5 5 5
;
run;

结合这些OF、':'的写法,用好这些下划线变量,能够在很短的代码里完成一些复杂的工作:

data name_range;
    set sashelp.baseball;
    
    /* 保留cr开头的列 */
    *keep cr:;
    
    
    /* 保留name到 nhits之间的列 */
    *keep name--natbat;
    
    
    /* 找出所有字符串变量里,包含'And'的行 */
    if index(catx(' ', of _CHARACTER_),'And');
​
​
    /* 用数组的方式,把所有为空的数值型变量,填充为0 */
    array nums[*] _NUMERIC_;
    do i = 1 to dim(nums);
        if nums[i] = . then nums[i] = 0;
    end;
    
    
    /* 用数组的方式,把所有字符型变量改成大写 */
    array chars[*] _CHARACTER_;
    do j = 1 to dim(chars);
        chars[j] = upcase(chars[j]);
    end;
    
    
    /* 数据开始第一次循环时,打印全部变量及其值 */
    if _N_ = 1 then put _ALL_;
run;