从程序员到数据科学家:SAS 编程基础 (06)- DATA步与PDV

来源:互联网 发布:怎么给域名续费 编辑:程序博客网 时间:2024/04/29 04:26


BASE SAS 中,DATA 语句用于开始一个数据步, 后续为若干DATA步特定的语句;SAS数据步结束于下一个 DATA 步 PROC 步开始之处,或者结束于后续显式指定的RUN语句

注意:DATA 步是SAS编程语言的基础,它可以执行在多种执行环境中。本章要讲的是传统意义上的SAS DATA步,后面我们会讲到下一代DATA步以及DATA步在SAS 云分析服务环境中执行的情况。千里之行,始于足下!我们还是从传统 DS 开始吧

DATA 语句最常见的调用方式有如下几种:

1)DATA 语句可不指定任何参数,则DATA 步将自动创建一个目标数据集 DATAn,其中 n为从1开始不断增长的唯一整数。

比如下面的SAS代码将在临时逻辑库 WORK中创建数据集 Data1,包含15列数据。再次运行该代码时将生成 Data2…依次类推。

DATA;

       Name="Leon"; Sex="M"; Age=30; Weight=83.5; Height=175

run;

注意:这种方式一般用来生成不在乎输出数据集名称的时候,但由于每次执行都会在 WORK 中生成一份新的数据,一般不建议这种用法。通常情况下会显式指定输出数据集的名字会更好一些。

对于这种临时数据,我们依然可以使用系统宏变量 &SYSLAST来跟踪,比如下面的代码用来显示上一次生成的临时数据集的名字,并从WORK中删除它。

data _null_;

    put "&SYSLAST";

run;

proc datasets nolist ;         

    delete %scan(&SYSLAST,2);

quit;

2)DATA 语句也可以指定数据集名称 _NULL_ ,表示不输出任何目标数据集,通常用于纯粹的计算逻辑处理。也经常用于调试SAS代码。

3)DATA 语句也可以指定多个输出数据集,从而实现在一个DATA步里输出各种需要的数据。比如下面的代码将同时生成两份内容相同的数据集 data1data2

DATA data1 data2;

Name="Leon"; Sex="M"; Age=30; Weight=83.5; Height=175

run;

一般情况下,我们会显式指定生成一个目标数据集名称,并且希望放在特定的目录中。SAS DATA语句在数据处理中可以展现出令人惊异的行为,比如我们希望把 sashelp.class 中两种性别的数据分成两个数据集 classFclasM,可使用下面的代码简洁地实现。

proc print data=sashelp.class;run;

DATA classF classM;

    set sashelp.class;

    if sex="F" then output classF; /*Split F to ClassF*/

    else  output classM; /*Split M to ClassM*/

run;

proc print data=classF; run;

proc print data=classM; run;

上一章的最后,我提到数据集中的观测(记录)有各种生成方式,下面我们就一起看看在SAS中如何做到。

l  利用内嵌数据行创建SAS数据集

一般情况下,如果我们要生成的数据量比较小,且不希望有独立的数据文件,我们可以直接将数据嵌在SAS代码中,内嵌数据行有几种不同的方式:

1) 最常见的情况是我们有一系列的数据行,数据项之间用空格分隔。这种情况我们可以使用datalines 语句配合input 语句直接生成。datalines语句表示下一行将包含数据行。由于数据行并非语句,所以它不需要分号。另外,除了 datalines 语句外,我们也可使用 linesdatalines 语句的别名 cards 语句,三者等价

libname mylib "c:\temp";

DATA mylib.myclass;

  input Name $ Sex $ Age Height Weight; /*$ 表示字符型变量*/

  datalines;

Alfred M 14 112.5 69

Alice F 13 84 56.5

Barbara F 13 98 65.3

Carol F 14 102.5 62.8

Henry M 14 102.5 63.5

James M 12 83 57.3

Jane F 12 84.5 59.8

Janet F 15 112.5 62.5

Jeffrey M 13 84 62.5

John M 12 99.5 59

Joyce F 11 50.5 51.3

Judy F 14 90 64.3

Louise F 12 77 56.3

Mary F 15 112 66.5

Philip M 16 150 72

Robert M 12 128 64.8

Ronald M 15 133 67

Thomas M 11 85 57.5

William M 15 112 66.5

RUN;

系统会生成如下数据集,存放在 C:\temp 目录中。与利用临时库 WORK不同,该数据集在你SAS 会话结束后依然存在,存放在 C:\temp\class.sas7bdat 文件中。

注意:生成到磁盘上的文件名依赖于操作系统的文件系统,Windows平台的FAT/NTFS不区分大小写,而Linux/Unix 则区分大小写,SAS一律使用小写字母生成数据集文件,在Unix/Linux平台上,如果我们要读取包含大写字母的文件名(即数据集名)时,需要启用系统选项VALIDMEMNAME=EXTEND,并且数据集的名字必须与磁盘上的文件名大小写完全匹配。

2) 如果数据行包含特定的分隔符,我们可以利用 infile 语句来指向特殊的文件引用 datalines,并且使用参数 delimiter=’<分隔符>’ 来指定分隔符。比如:

libname mylib "c:\temp";

DATA mylib.myclass;

  infile datalines delimiter=',';

  input Name $ Sex $ Age Height Weight;

  datalines;

Alfred,M,14,112.5,69

Alice,F,13,84,56.5

Barbara,F,13,98,65.3

Carol,F,14,102.5,62.8

Henry,M,14,102.5,63.5

James,M,12,83,57.3

Jane,F,12,84.5,59.8

Janet,F,15,112.5,62.5

Jeffrey,M,13,84,62.5

John,M,12,99.5,59

Joyce,F,11,50.5,51.3

Judy,F,14,90,64.3

Louise,F,12,77,56.3

Mary,F,15,112,66.5

Philip,M,16,150,72

Robert,M,12,128,64.8

Ronald,M,15,133,67

Thomas,M,11,85,57.5

William,M,15,112,66.5

RUN;

3) 如果数据行本身包含分号(注:由于分号是SAS语句的结束符,因此很特别),我们该如何处理呢?我们可以使用 datalines4 语句来完成。也可以使用该语句的别名为 cards4 lines4datalines4 语句必须使用4个连续的分号开始的一行来标记数据行结束。下面的例子中,字符型列 Name 包含分号,则我们必须以datalines4 来输入数据。

libname mylib "c:\temp";

DATA mylib.myclass; 

  input Name $ Sex $ Age Height Weight;

  datalines4;

Alfred; M 14 112.5 69

Alice; F 13 84 56.5

Barbara; F 13 98 65.3

Carol; F 14 102.5 62.8

Henry; M 14 102.5 63.5

James; M 12 83 57.3

Jane; F 12 84.5 59.8

Janet; F 15 112.5 62.5

Jeffrey; M 13 84 62.5

John; M 12 99.5 59

Joyce; F 11 50.5 51.3

Judy; F 14 90 64.3

Louise; F 12 77 56.3

Mary; F 15 112 66.5

Philip; M 16 150 72

Robert; M 12 128 64.8

Ronald; M 15 133 67

Thomas; M 11 85 57.5

William; M 15 112 66.5

;;;;

RUN;

4) 如果数据行本身是变长(比如字符串变量包含空格字符),也就是说数据行参差不齐,那我们如何输入呢?我们可以使用SAS提供的列指针,用来明确指定数据行中变量的读取的起止位置,从而正确读取变长的字符串。比如:

libname mylib "c:\temp";

DATA mylib.myclass;

  input Name $1-15 Sex $ Age Height Weight; 

  datalines;

Alfred Liu      M 14 112.5 69

Alice Wang      F 13 84    56.5

Barbara Deng    F 13 98    65.3

Carol Zhang     F 14 102.5 62.8

Henry Kissinger M 14 102.5 63.5

James Michalle  M 12 83    57.3

Jane Xu         F 12 84.5  59.8

Janet Quin      F 15 112.5 62.5

Jeffrey Smith   M 13 84    62.5

John Albert     M 12 99.5  59

Joyce Betty     F 11 50.5  51.3

Judy Yang       F 14 90    64.3

Louise Bernard  F 12 77    56.3

Mary Kushiner   F 15 112   66.5

Philip Pebble   M 16 150   72

Robert Chu      M 12 128   64.8

Ronald Wiese    M 15 133   67

Thomas Berryman M 11 85    57.5

William Wu      M 15 112   66.5

RUN;

5) 有的同学会问,如果我们在一个数据行上包括多个观测(记录),我们该如何读取?SAS input 语句上设计了一个特殊的参数 @@,用来告诉SAS 从数据行完整读取一个观测后,不要马上读入下一个数据行,而是继续从当前行的缓冲区中读取数据填充观测。这为我们节省代码文件的行数非常有用。比如:

    libname mylib "c:\temp";

DATA mylib.myclass;

  input Name $ Sex $ Age Height Weight @@

  datalines;

Alfred M 14 112.5 69

Alice F 13 84 56.5    Barbara F 13 98 65.3

Carol F 14 102.5 62.8 Henry M 14 102.5 63.5 James M 12 83 57.3

Jane F 12 84.5 59.8   Janet F 15 112.5 62.5 Jeffrey M 13 84 62.5

John M 12 99.5 59     Joyce F 11 50.5 51.3  Judy F 14 90 64.3

Louise F 12 77 56.3   Mary F 15 112 66.5    Philip M 16 150 72

Robert M 12 128 64.8  Ronald M 15 133 67   

Thomas M 11 85 57.5   William M 15 112 66.5

RUN;

l  基于外部文件创建SAS数据集

大部分情况下,数据来自于磁盘上的某个外部文件,而且通常不是一系列的文件。比如在C:\temp目录中有如下文本文件: myclass.txt, 我们怎么用 DATA 步来读取呢?

我们不需要 datalines语句,而是在 DATA 步内利用 infile 语句指定该外部文件(相当于我们将datalines语句下的数据行移入了外部文件)。然后再用 input语句读入。比如:

libname mylib "c:\temp";

DATA mylib.MyClass;

  infile  'c:\temp\myclass.txt';

  input Name $ Sex $ Age Height Weight;   

RUN;

系统将创建一个完整的数据集 mylib.MyClass

还有一种更加标准的做法是,我们先用 filename 语句定义一个文件引用 myfile,然后再在infile 语句中使用该文件引用。代码如下:

libname mylib "c:\temp";

filename myfile 'c:\temp\myclass.txt';

DATA mylib.MyClass;

  infile myfile  ;

  input Name $ Sex $ Age Height Weight;   

RUN;

就像前面已经提到的一样,数据行中可能包含说明文字,或者数据的表头什么的。这种情况下我们可以在 infile 语句上指定开始读取观测的行 firstobs=,同时也可以指定结束读取观测的行obs= 用来限定读入的数据量,通常结果集的总行数为 obs-firstobs+1 行。比如下面的代码读入第二行开始的10行数据。

infile myfile  delimiter=',' firstobs=2 obs=11; 

l  简单验证生成的SAS数据集

为了验证我们自己创建的数据集 MyLib.myclass 和系统 SASHELP.CLASS 数据集的差异,我们可以调用 PROC COMPARE 来比较两个数据集的异同。

proc compare base=sashelp.class compare=mylib.myclass;

run;

系统显示两个数据集基本相同,除了 sashelp.class 有数据集Label信息,Sex 列宽度为8字节外,两个数据集完全一样。

l  通过已有的SAS 数据集生成数据

很多时候我们都是操作已有的SAS数据集,来进行各种操作生成目标数据集。比如对数据集中数据的增删改查,数据集的排序、合并、分离、转置等。

1)  增加数据行:在数据集尾部增加数据行

DATA OneRow;

    Name="Leon"; Sex="M"; Age=30; Weight=83.5; Height=175

run;

data Class2;

    set sashelp.class OneRow;

run;

    也可以在数据的头部增加数据行,只需要改变 SET 语句中的数据集顺序即可。 

DATA OneRow;

     Name="Leon"; Sex="M"; Age=30; Weight=83.5; Height=175;

run;

data myclass;

    set OneRow sashelp.class;

run;

proc print data=myclass;

run;

注意:细心的读者可能会发现,输出的数据集中Name有截断错误,原因是在 SET语句时 PDV的初始结构来自于我们创建的临时数据集 OneRow,而该数据集中变量Name的长度定义不够,可以通过增加临时数据集宽度定义来修正。

DATA OneRow; 

    length Name $8;

    Name="Leon"; Sex="M"; Age=30; Weight=83.5; Height=175

run;

  如果需要在特定行处插入数据,可以使用内部计数器 _N_ 作条件实现:

DATA myclass;

    set sashelp.class;

    if _N_ = 1 then do;  /*在第一行后面插入数据*/

       output;

       Name="Leon"; Sex="M"; Age=30; Weight=83.5; Height=175

    end;

    output;

run;

option obs=3/*列出前三行*/

proc print data=myclass;

run;

2 删除数据行:删除第三行数据

data myclass;

    set sashelp.class;

    if _N_ = 3 then return;

    else output;

run;

proc summary data=myclass printrun;

当然你也可以删除满足指定条件的数据行

data myclass;

    set sashelp.class;

    if Sex = 'M' then return;

    else output;

run;

proc print data=myclass;

run;

2 修改数据行: 满足特定条件时修改变量的值,比如把第三行的Name改为 "Baby".

Data myclass;

    set sashelp.class;

    if _N_ = 3 then name="Baby";   

run;

 

options obs=3;

proc print ; run;

2)  查询特定数据行:仅输出满足特定条件的数据行

libname mylib "c:\temp";

data mylib.myclass;

    set sashelp.class;

    if name="Alfred" then output;  

run;

proc print data=mylib.myclass;run;

l  通过 PROC IMPORT PROC SQL 生成

SAS 提供PROC IMPORT  PROC EXPORT 来将数据导入/导出 SAS 运行环境,常用的数据文件格式为逗号分隔的 CSV 和微软的电子表格 EXCEL 文件,我们可以使用如下代码简单完成。

proc import

  datafile='c:\temp\class.csv' dbms=csv out=class replace;

  getnames=YES;

  mixed=NO;

run;

proc print data=class;run;

proc import

  datafile='c:\temp\class.xlsx' dbms=xlsx out=class  replace;

  getnames=YES;

  mixed=NO;

run;

注意:以上例子需要数据文件 c:\temp\class.csv  class.xlsx,你可以用 proc export 进行生成。 

PROC EXPORT  data=sashelp.class  outfile='c:\temp\class.xlsx' dbms=xlsx replace;

run;

PROC EXPORT  data=sashelp.class  outfile='c:\temp\class.csv' dbms=csv replace;

run;

注意:很多人根据帮助文档使用 dbms=EXCEL 导入 EXCEL 文件时会出现如下错误:

ERROR: Connect: 没有注册类

ERROR: Error in the LIBNAME statement.

根本的原因是 dbms=EXCEL  dbms=xlsx  SAS 里访问机制不同,前者需要安装 ACE 引擎才能工作,而默认情况下我们并没有安装它。这是一个令很多程序员困惑的技术陷阱。

PROC SQL  SAS 可以调用结构化查询语言 SQL 进行数据操作,广泛用于关系数据库管理系统的数据表和视图的增删改查,但SAS 技术更高一筹,也可以对 SAS 数据集进行标准的 SQL操作。主要功能包括:创建数据表和数据视图,对数据列作索引;查询存储在数据表和数据视图中的数据;增删改数据行和增删改数据列本身;将数据库支持的 SQL 语句到数据库管理系统中进行数据查询。另外 SAS 也支持将 SQL 查询结果置入 SAS 宏变量中,进行数据传递功能。PROC SQL 功能非常强大,下面仅列出两个简单的例子:创建/查询数据表。

1)基于已有的数据集创建新的数据集,没有使用 DATA 步。

libname mylib 'c:\temp';

proc sql;

   create table mylib.myclass as

    select Name, Sex, Age, Height, Weight format=best.

      from sashelp.class;

proc print data=mylib.myclass; run;

2)使用标准的 SQL 语言创建数据集,如果 mylib 指向某个数据库管理系统,则会在该数据库中创建对应的数据表。

libname mylib 'c:\temp';

proc sql;

    create table mylib.myclass( Name char(8), Sex char(1), Age num, Height num, Weightnum informat=best. format=best.);

    insert into mylib.myclass

        values('Leon','M',31,175,80)

        values('Jim''M',30,173,75);

    title 'Table mylib.myclass';

    select * from mylib.myclass;

proc printtorun;

DATA 步的运行机制

前面的例子让我们看到SAS在处理数据非常方便,但这依然不够,我们需要深入探索SASDATA步是怎么工作的——即我们需要深刻理解SAS DATA 步的运行机制,这是SAS编程的核心内容之一。

首先需要指出的是,SAS语言是按步进行编译执行的,所以SAS程序与大多数编译型计算机语言程序一样,总体上要经过编译和执行两个阶段。简要流程如下图所示:


编译阶段

编译阶段SAS 主要做两件事:

1)扫描DATA 步内的每一行语句,a) 执行语法检查:扫描代码片段,检查是否存在于语法错误。常见的语法错误包括关键字缺失拼写错误无效变量名称标点符缺失或者无效、以及无效的参数或选项等。b)标识每一个变量的名称,类型和长度等信息,并且判断是否需要为后续变量引用作类型转换等。

2)为程序执行创建必要的数据结构,包括输入缓冲区IB(INPUT BUFFER)程序数据向量PDVProgram Data Vector)和输出数据集描述信息 DI(Descriptor Information),其中输入缓冲区 IB只在从外部读取原始数据文件时才是创建。

a)  输入缓冲区IB:当DATA 步内执行INPUT语句是从原始数据文件(Raw Data,比如前面例子中的外部文本文件)中读取观测(记录)时,SAS会在内存中分配一块逻辑区域作为缓冲区,作为将数据放入PDV之前的临时缓冲区存在。如果是使用 SET语句来读取SAS数据集时,SAS则将数据直接拷贝到PDV中,而不需要所谓的输入缓冲区IB

b)  程序数据向量PDVDATA步每读入一行数据时,都需要在内存中分配一个逻辑区域,用于存放数据集的变量和计算变量(即计算列)信息。其数据来自于输入缓冲区IBSAS 执行语句。

另外,PDV中还包含2个仅用于系统处理阶段的临时变量,它们不会被写入目标数据集:

l  行计数器 _N_:用来对DATA 步的每次处理进行循环计数,从1开始;

l  错误标志 _ERROR_:用来标记执行过程中由于数据错误引起的错误;默认值是0,表示没有错误,否则为1,表示有一个或者多个错误。

c)  输出数据集描述符信息

SAS为每一个输出数据集创建和维护的元数据信息,包括数据集属性和变量属性。比如数据集名字,成员类型,创建日期,创建时间,观测数,变量名称,类型(字符型/数值型)等。

让我们考察如下代码的编译过程:

/*利用系统 SASHELP.CLASS数据,创建体质指数 BMI,俗称肥胖指数*/

data myclass;

  set sashelp.class;

  where age> 12 and sex='';

  BMI=(weight * 0.4535924) / ((height*2.54/100) **2);

  format BMI 4.1;

  drop weight height;

run;

对于如上代码,编译时 PDV 的变化依次如下图右侧所示。从上到下各行语句编译时会修改PDV,比如DROP 语句的作用是告诉SAS PDV中哪些变量不需要输出到输出数据集,对那些变量设置“删除”标志,以免执行时输出到目标数据集中。

编译到 RUN 语句时,DATA步编译宣告结束。SAS会为需要输出到目标数据集的那些变量,创建必要的描述符信息——包括数据集中各列的名称、类型、长度、输出格式、标签等。

运行阶段

一旦编译成功,执行阶段开始。SAS 的执行主要有如下步骤:

1)SAS 首先会从DATA语句处开始执行,如果是第一次执行,SAS会设置内部变量_N_=1_ERROR_=0,否则会对内部计数器 _N_自动加1

2)SAS会以缺失值对程序数据向量PDV)中那些由INPUT语句赋值语句创建的变量进行初始化。注意:那些以 SET, MERGE, MODIFY UPDATE语句读取的变量并不会被重置为缺失值。

3)默认情况下,SAS从外部原始数据文件中读入一条数据记录到输入缓冲区IB中,然后创建对应的PDV,或者直接从SAS数据集中读入一个观测到程序数据向量PDV中。SAS语句 INPUT SET MERGE MODIFYUPDATE都可以用来读入一条记录。

4)对当前记录,执行后续的SAS程序语句,包括赋值、计算和更新等等。 

5)当执行到 DATA 步的最后时,隐含的SAS语句 OUTPUTRETURNRESET 自动触发。SAS将当前记录作为一个观测写入到SAS数据集中。执行自动返回到DATA 步的开始处进入下一循环。如果SAS 读取外部文件结束或SAS数据集结束,则整个 DATA 步执行终止,进入后续的DATAPROC步的编译执行。

让我们考察前面代码的运行过程,其中DATA 步中的新变量(如体质指数BMI)首先也会以缺失值 . 进行初始化,随后在执行赋值语句时对表达式重新求值,赋给那些新变量;然后执行下一行语句 DATA 步的最后,SAS会将PDV中没有删除标志的非临时变量(除了 height, weight)和值写入到目标数据集中。然后控制流程再返回到 DATA 步的开始处进入下一个循环。正是SAS DATA步这种独特的隐性循环设计,为用户在进行数据处理时提供了自然的循环概念,从而让用户对具体文件的I/O读写细节不必过于关注。

SAS进入下一次循环时,临时变量 _N_ 计数器会自动加1,对于非INPUT语句读入的情况,SAS 也会保留上次读入到 PDV中的变量值,直到被新读入的观测所覆盖;对于DATA步中的新变量(如体质指数 BMI),SAS会重新使用缺失值 (.) 进行初始化。当数据被再次读入时(比如从上面的SET语句所指定的源),SAS会将源数据集中的第二个观测读入到 PDV中,并重新计算程序中的新变量 BMI。然后在DATA步的最后,使用 PDV中的值作为第二个观测(记录)写入到输出数据集中。然后控制再次回到DATA步开始,一直循环执行,直到源数据集中的所有观测都被处理完毕。

结语:本章我们学习了利用SAS创建数据集的几种灵活方法,然后介绍了SAS DATA 步运行机制的奥秘。深刻理解DATA步的编译运行机制对掌握SAS 编程至关重要,灵活使用SAS DATA步可以为分析准备任何形式的待分析数据。

下面笔者就以如何生成前20个黄金分割数列的简单SAS程序来结束本章的学习黄金分割数列即斐波那契数列,该数列中后一个数与前一个数的比例越往后越接近于黄金比例(1+√5)/2 ,此数列分布表现出极致的均衡与和谐之美;其前8个数为:1 1 2 3 5 8 13 21…

/*生成前20个黄金分割数列到数据集 WORK.FbNC */

data Fbnc;

    do n=1 to 20;  

       if n=1 or n=2 then y=1;

       else y=  y1 + y2;

        y2=y1; y1=y; 

       output;

       put n= y=; /*打印到SAS日志*/

    end;

    drop n y1 y2;

run;

proc print data=fbnc;run;

输出:

n=0 y=1

n=1 y=1

n=2 y=2

n=3 y=3

n=4 y=5

n=5 y=8

n=6 y=13

n=7 y=21

0 0
原创粉丝点击