SAS Training 002 - 聊一聊贴标签 labeling
前几天给药品💊编盲,贴了大量的标签🏷️。今天我们就来聊聊贴标签,不同的是,我们聊聊 SAS 里的贴标签(labeling)。
直接进入主题。我们这里所说的 labeling,其实是进行格式的 label。SAS 中已经有对数字、日期等可用的格式,但是我们 programming 不满足于此,想更大可能地简化我们的工作,这时候,自定义 labeling 就呼之欲出了。
最直接的例子,现有一个分类变量,取值 1、2、3,你想依据其进行分组编码(high、mid、low),但是在分析过程中,你只想以 1、2、3 简单代替实际分组 character,怎么办?对分类变量的各 value 进行 labeling。
实际上,labeling 主要有两种:对 variable 进行 format,以及对 varaible 的取值 value 进行 format。我们一一来看。
creating variable labels
首先,准备演示数据集:
DATA auto;
INPUT make $ mpg rep78 weight foreign;
CARDS;
AMC 22 3 2930 0
AMC 17 3 3350 0
AMC 22 . 2640 0
Audi 17 5 2830 1
Audi 23 3 2070 1
BMW 25 4 2650 1
Buick 20 3 3250 0
Buick 15 4 4080 0
Buick 18 3 3670 0
Buick 26 . 2230 0
Buick 20 3 3280 0
Buick 16 3 3880 0
Buick 19 3 3400 0
Cad. 14 3 4330 0
Cad. 14 2 3900 0
Cad. 21 3 4290 0
Chev. 29 3 2110 0
Chev. 16 4 3690 0
Chev. 22 3 3180 0
Chev. 22 2 3220 0
Chev. 24 2 2750 0
Chev. 19 3 3430 0
Datsun 23 4 2370 1
Datsun 35 5 2020 1
Datsun 24 4 2280 1
Datsun 21 4 2750 1
;
RUN;
PROC CONTENTS DATA= auto;
RUN;

我们来使用 label statement 对其中的遍历 foreign、mpg 和 rep78 进行 labeling,看看结果啥样:
DATA auto2;
SET auto;
LABEL rep78= "1978 Repair Record"
mpg= "Miles Per Gallon"
foreign= "Where Car Was Made";
RUN;
PROC CONTENTS DATA= auto2;
RUN;

哎,有变化。多出一列 label。我们可以把它当作对 variable name 的一种详细描述。这里,需要注意的是:一旦我们在 data 步完成 labeling,其 label 在这个数据集后续的所有操作中是永久有效的;而作为对比,如果是在 proc 步中 labeling,那么该 label 只是在该 proc 步有效。我们来瞧瞧 data 步 labeling 后,该变量的 label 还是不是矢志不渝地存在?
PROC MEANS DATA= auto2;
RUN;

没有问题,label 依然存在。
很好理解吧,给 variable 进行 labeling,我们可以更确切地理解该变量的含义。
creating and using value labels
要对变量的各种取值进行 labeling,坦率地说,用 proc format,有两种方式都可以实现,都拿来吧你!
方式一:借助 value statement
等不及了,先看 code:
PROC FORMAT;
VALUE forgnf
0= "domestic"
1= "foreign"
;
VALUE $makef
"AMC"= "American Motors"
"Buick"= "Buick (GM)"
"Cad."= "Cadillac (GM)"
"Chev."= "Chevrolet (GM)"
"Datsun"= "Datsun (Nissan)"
;
RUN;
PROC FREQ DATA= auto2;
FORMAT foreign forgnf.
make $makef.;
TABLES foreign make;
RUN;

显而易见,foreign 的 0、1 数值取值在 display 时被字符串标签替代,make 的字符串取值被更详细的 label 取代。这里值得指出来的是,**我们注意到:**字符串变量 make 有 7 种分类取值,而在最后只有其中的 5 种被 labeling,剩余未 labeling 的取值仍然保持原 value 显示;此外,注意到 format statement 中每种 format 后都有个 . ,为啥加这个?其实是为了让 SAS 清楚地分辨变量名和 format 的名字。
以上,出现了对数值取值、字符串取值的 character labeling,我能不能对字符串取值作 numeric labeling?
上 code:
proc format;
invalue fmtgrdn
"Male"= 1
"Female"= 2;
run;
data xx1;
length sex $10;
sex= "Male"; output;
sex= "Female"; output;
run;
data xx2;
set xx1;
sexgrdn= input(sex, fmtgrdn.);
run;
当然是可以的。这里要提醒大家注意:我们使用了 invalue statement 和 input function。
方式二:借助 proc format input control data sets(cntlin)
讲方式二之前,我们先看下我们上面生成的 fmtgrdn format 的具体情况:
proc format lib= work fmtlib;
run;

我这里展示的是,我的 work library 中所有已 define 的 format,为什么全部拿出来展示?因为实际上,包含了 numeric values -> character labels,character values -> character labels 以及 character values -> numeric labels 的情况。我们通过认真理解这些 format 的内在逻辑,非常有助于我们理解第二种 labeling 的方式,即通过 SAS 数据集来生成 format。
我们要 produce 与以上逻辑类似的数据集,上 code:
data scale;
input begin: $char2. end: $char2. amount: $char4.;
datalines;
0 3 0%
4 6 3%
7 8 6%
9 10 8%
11 16 10%
;
run;
data ctrl;
length label $11;
set scale(rename= (begin= start amount= label)) end= last;
retain fmtname "PercentageFormat" type "n";
output;
if last then do;
hlo= "O";
label= "***ERROR***";
output;
end;
run;

我们借助官方文档中的这个例子来说明 PROC FORMAT input control data sets 的格式要求(如上)。
proc format library= work cntlin= ctrl;
run;
proc format lib= work fmtlib;
select percentageformat;
run;
生成的 format 如下:

值得好好理解体会。
proc format library= work;
invalue evaluation
"O"= 4
"S"= 3
"E"= 2
"C"= 1
"N"= 0
;
run;
data points;
input EmployeeId $ (Q1-Q4) (evaluation., +1);
TotalPoints= q1+q2+q3+q4;
datalines;
2355 S O O S
5889 2 . 2 2
3878 C E E E
4409 0 1 1 1
3985 3 3 3 2
0740 S E E S
2398 E E C
5162 C C C E
4421 3 2 2 2
7385 C C C N
;
run;
proc print data= points;
run;
proc report data= work.points nowd headskip split= "#";
column employeeid totalpoints totalpoints= Pctage;
define employeeid / right;
define totalpoints / "Total#Points" right;
define pctage / format= PercentageFormat12. "Percentage" left;
title "The Percentage of Salary for Calculating Bonus";
run;
这是一个非常有意思的例子,里面很值得细细琢磨。
其实,还有很多值得注意的细节,限于篇幅,我们不能在短短一篇推送里面面俱到,期待以后的推送😬
