且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

《深入解析sas:数据处理、分析优化与商业应用》一2.2 通过DATA步读取数据

更新时间:2022-08-19 15:50:52

本节书摘来自华章出版社《深入解析sas:数据处理、分析优化与商业应用》一书中的第2章,第2.2节,作者 夏坤庄 徐唯 潘红莲 林建伟,更多章节内容可以访问云栖社区“华章计算机”公众号查看

2.2 通过DATA步读取数据

DATA步是SAS编程中的重要组成部分。本节主要介绍如何使用DATA步的各种输入方式来读取不同格式的外部文件中的数据,并创建数据集。同时,还会介绍DATA步的处理过程,相信这会帮助我们更好地学习和掌握如何使用DATA步读取原始数据。

2.2.1 DATA步处理

DATA步由一组SAS语句组成。首先,由DATA语句创建并命名SAS数据集;然后SAS会编译并检查其语句的语法,如果语法正确,这些语句会被执行。在最简单的形式下,DATA步自动输出和返回,这个过程会一直循环。下面是一个典型的DATA步读取外部文件的处理流程:
1)编译DATA步中的SAS语句并检查语法。
2)创建输入缓冲区、程序数据向量PDV(Program Data Vector)和数据集描述信息。
3)从DATA语句开始执行。
4)将PDV中所有变量值(除自动变量_N_和_ERROR_)置为缺失值。
5)判断是否有数据要读入。有数据要读入,进行下一步;如果没有,关闭数据集。
6)将数据读入输入缓冲区,并赋值给PDV中的变量。
7)执行其他可执行语句。
8)将观测写入SAS数据集。
9)返回DATA步开始下一个迭代,从第3步开始。
1.?编译阶段
当提交DATA步执行时,SAS检查SAS语句的语法并编译它们,将语句自动翻译为机器代码。SAS进一步处理代码并创建输入缓冲区、PDV和数据集描述信息。
输入缓冲区是内存中的逻辑区域。当程序执行时,SAS会将原始数据文件中的每条数据记录读入该区域。
PDV也是内存中的逻辑区域,SAS在该逻辑区域中构建数据集,每次一个观测。当程序执行时,SAS从输入缓冲区读取数据值,或使用SAS语句创建数据值。SAS把数据值赋给变量,然后从PDV中将这些数据值写入为SAS数据集中的一个观测。
PDV中还包含两个自动变量_N_和_ERROR_。_N_变量计算DATA步迭代的次数,_ERROR_变量作为在执行过程中由数据引起的错误的信号,0表示没有错误,1表示有一个或多个错误。这些自动变量不会写入输出数据集中。
描述信息是关于每个SAS数据集的信息,前面介绍过。
2.?执行阶段
DATA步中的所有可执行语句在每次迭代中都会被执行一遍。如果输入文件包含原始数据,那么SAS会先读入一个观测到输入缓冲区,然后读取输入缓冲区中的数据值并将其赋给PDV中的变量。SAS还会计算由程序语句创建的数据值,并将这些值写入PDV。当程序执行到DATA步结束时,默认会执行如下操作:
1)把PDV中的当前观测写入数据集。
2)程序循环回到DATA步最开始的地方。
3)PDV中的变量重置为缺失值。注意,在RETAIN语句中指定的变量和自动变量_N_、_ERROR_不会重置为缺失值。关于RETAIN语句会在后面的章节介绍。
如果还有另一条记录要读取,那么程序会再执行一遍,SAS会构建第二个观测并继续,直到没有任何记录。这时,数据集关闭,SAS继续下一个DATA或PROC步。
下面通过示例来介绍DATA步编译和执行各条语句时输入缓冲区和PDV中的内容。该示例在之前用过的创建产品库存的DATA步基础上进行了简单的修改,新增加了变量Cost。代码如下:

libname saslib "c:\sas\data";

data saslib.inventory;
    input Product_ID $ Instock Price;
    Cost=Price*0.15;
    datalines;
P001R 12 125.00
P003T 34 40.00
P301M 23 500.00
PC02M 12 100.00
;
run;

DATA步由DATA语句开始,该DATA语句还创建命名为Inventory的数据集,并将其存储在saslib逻辑库中。
INPUT语句创建了3个变量:Product_ID、Instock和Price。
赋值语句创建了额外的变量Cost,表示库存成本为其价格(Price)的15%。
DATALINES语句标识输入数据的开始,数据值后的分号表示输入数据的结束。
RUN语句表示DATA步的结束。
下面借助每一步输入缓冲区和PDV中的数据来理解提交该DATA步后编译和执行阶段SAS的行为。
1)提交DATA步执行时,SAS首先编译DATA步。编译时,SAS创建输入缓冲区、PDV和数据集saslib.Inventory的描述信息。PDV包含在INPUT语句中的变量、赋值语句中的变量Cost,以及自动产生的变量_N_和_ERROR_。所有的变量,除了_N_和_ERROR_以外,都会初始化为缺失值。数字变量的缺失值由点(.)表示,字符变量的缺失值由空格表示。此时的输入缓冲区和PDV中的内容如图2.17所示。

《深入解析sas:数据处理、分析优化与商业应用》一2.2 通过DATA步读取数据

2)语法正确,DATA步开始执行。INPUT语句会让SAS读入第一个数据,并记录到输入缓冲区。此时的输入缓冲区和PDV中的数据如图2.18所示。

《深入解析sas:数据处理、分析优化与商业应用》一2.2 通过DATA步读取数据

3)根据INPUT语句中的指令,SAS将输入缓冲区的值赋值给PDV中的变量。此时的输入缓冲区和PDV中的数据如图2.19所示。

《深入解析sas:数据处理、分析优化与商业应用》一2.2 通过DATA步读取数据

4)SAS执行程序中的下一条赋值语句:

Cost=Price*0.15;

该赋值语句计算变量Cost的值,并将该值写入PDV。此时的输入缓冲区和PDV中的数据如图2.20所示。

《深入解析sas:数据处理、分析优化与商业应用》一2.2 通过DATA步读取数据

5)这时,DATA步的这一次迭代结束,程序自动做如下操作:
将PDV中的内容作为第一个观测写入数据集,记住,自动变量_N_和_ERROR_不会被写入。
循环到DATA步最开始处,开始下一个迭代。
释放输入缓冲区的记录。
在PDV中,将自动变量_N_加1,重置自动变量_ERROR_为0,并将其他变量设置为缺失值。
以上操作完成后,输入缓冲区和PDV中的数据如图2.21所示。

《深入解析sas:数据处理、分析优化与商业应用》一2.2 通过DATA步读取数据

6)继续执行。INPUT语句查找下一条观测。在这个例子中,存在下一条观测,INPUT语句将第二条观测读入到输入缓冲区。此时,输入缓冲区和PDV中的数据如图2.22所示。

《深入解析sas:数据处理、分析优化与商业应用》一2.2 通过DATA步读取数据

7)接下来SAS像构建上一条观测值一样在PDV中构建下一条观测值,并将PDV的内容写入数据集。SAS执行完赋值语句后,输入缓冲区和PDV中的数据如图2.23所示。

《深入解析sas:数据处理、分析优化与商业应用》一2.2 通过DATA步读取数据

8)整个过程继续,直到没有其他观测。DATA步迭代次数与读取的原始数据观测条数一样。
9)SAS关闭数据集Inventory,本DATA步执行结束,退出该DATA步。

2.2.2 读取外部文本文件中的数据(初级)

在使用DATA步读取外部文本文件中的数据时,需要给SAS提供读取原始数据所要求的特定信息,例如原始数据的位置、数据集变量和类型,以及SAS如何读数据等。SAS会根据这些信息创建数据集。
在DATA步中可以使用DATALINES直接输入数据,或通过INFILE语句指定原始数据文件。本书之前的示例代码所给出的都是通过DATALINES直接输入数据。但在实际应用中,要分析的原始数据往往存储在外部文件中。本节主要介绍使用DATA步读取外部文本数据文件中的数据。
在读取原始数据文件并创建SAS数据集之前,必须先检查原始数据文件,确定要读取的数据值在原始数据记录中的格式,并基于这些信息选择读取数据时使用的方法。SAS提供了以下3种基本输入方式:
列表输入
按列输入
格式化输入
这3种输入方式可以单独使用,也可组合使用,还可以和SAS提供的各种修饰符以及指针控制结合使用,例如与输入格式、行控制符、行指针控制和列指针控制一起使用。
使用DATA步读取外部文件中数据的基本形式如下:

DATA 数据集名称;
    INFILE 数据文件位置;
    INPUT 变量列表;
RUN;

其中:
DATA语句指定数据集名称。
INFILE语句指定原始数据的位置和名称。原始数据文件可以是在FILENAME语句中定义的文件引用形式或操作系统下的文件路径。
INPUT语句用于告诉SAS如何读取数据。
这里简单介绍如何使用FILENAME定义文件引用。在INFILE语句中可直接使用带路径的外部文件名称,也可以使用事先由FILENAME语句赋值的文件引用。使用FILENAME语句可以将SAS文件引用与外部文件或者存储位置(存储有多个外部文件)关联起来,其基本形式如下:
FILENAME 文件引用 外部文件|外部文件存储位置;
其中文件引用用于指定文件引用的名称,以字母开始,可包含字母、数字和下划线,长度不能超过8个字符。外部文件则给出了一个带完整路径名的外部数据文件的文件名,或者一组文件的存储位置。下面分别给出在Windows环境下,FILENAME语句指定单个文件和一组文件的存储位置定义,以及在INPUT语句中如何使用这两种的文件引用。
1)FILENAME语句指定到单个文件的文件引用。

filename invtfile 'c:\sas\data\inventory.dat'; 

data saslib.Inventory;
    infile invtfile;
    input Product_ID $ Instock Price;
run;

2)FILENAME语句指定到一组外部文件存储位置的文件引用。

filename extfiles 'c:\sas\data';

data saslib.Inventory;
    infile extfiles(inventory);
    input Product_ID $ Instock Price;
run;

本章后面的例子都使用后面这种方式引用外部数据文件。
1.?列表输入
列表输入(List Input)用于读取原始数据记录中每个字段由至少一个分隔符隔开,并且数据值中不包含该分隔符的原始数据。列表输入默认分隔符为空格,连续的分隔符会当成一个分隔符处理,INPUT语句中包含了简单的变量名称列表。使用列表输入的基本形式如下:

DATA 数据集;
    INFILE 文件引用;
    INPUT 变量1 <$> <变量2 <$> …>;
RUN;

其中:
数据集指定要生成的数据集。
文件引用指定要读入外部原始数据文件。
变量1、变量2等是数据集的变量,变量与变量之间用空格分隔。对于字符变量则需在变量后加字符$。INPUT语句顺序地将原始数据记录中由空格隔开的数据值读入到所列出的变量中。
(1)原始数据各个字段以空格隔开
当原始数据记录中的数据值以一个或多个空格隔开,且数据值中不包含空格时,可使用默认的列表输入方式。
文件inventory.dat(位于c:sasdata目录下)的内容如下:

P001R 12 125.00
P003T 34 40.00
P301M 23 500.00
PC02M 12 100.00

本章所有的示例中,都假定已经为逻辑库引用名saslib指定了SAS逻辑库,而且为文件引用名extfiles指定了所有外部数据文件所在的位置。
在SAS窗口中提交如下代码:

data saslib.Inventory;
    infile extfiles(inventory);
    input Product_ID $ Instock Price;
run;

proc print data=saslib.Inventory;
run;

INPUT语句会逐行顺序地读取inventory.dat中的数据值,并赋值给变量。在读取每行数据时,遇到空格就停止读入当前数据值,并从非空格处读入下一个数据值。
PRINT过程打印的数据集内容如图2.24所示。
上述的列表输入代码存在如下限制:
使用列表输入时,字符变量长度默认为8个字节。当输入数据长度超过8个字节时,从输入缓冲区读入PDV的数据值会产生截断。这个问题可以通过在INPUT语句之前使用LENGTH语句指定该变量的长度或其他方式来解决。
SAS遇到空格时会停止读入当前数据值,这样SAS就不能处理原始数据记录中的数据值包含空格的情况了。
原始数据中必须使用占位符(.)来表示该数据值为缺失值。
(2)使用LENGTH语句指定字符变量长度
指定变量长度的LENGTH语句的基本形式如下:
LENGTH 变量1 <$>长度 <变量2 <$>长度 …>;
通常在变量第一次出现时程序会根据上下文环境确定其长度。可以在INPUT语句前通过LENGTH语句明确指定变量长度。
要读入的数据文件state.dat中的原始数据记录如下,在该记录中州名全称字符长度超过了8个字节。
WA Washington
CA California
AK Alaska
AL Alabama
如果使用不带LENGTH语句的列表输入的代码如下:

data saslib.state;
    infile extfiles(state);
    input Short_Name $ State $;
run;

proc print data=saslib.state noobs;
run;

PRINT语句打印的数据集内容如图2.25所示,可以看到,第1条和第2条观测中State变量的数据值不全,都出现了截断。
可在INPUT语句前加LENGTH语句,指定State变量的长度为20个字符。修改后的SAS代码如下:

data saslib.state;
    length State $20;
    infile extfiles(state);
    input Short_Name $ State $;
run;

proc print data=saslib.state noobs;
run;

这时,PRINT打印的数据集的内容如图2.26所示。State变量中的州名完全写入数据集中了。
可能已经注意到,打印的数据集中变量的顺序发生了变化。这是因为它们的顺序由DATA步中变量出现的顺序决定,而State变量首先在LENGTH语句中出现,接着INPUT语句才会出现Short_Name。但是,这并不影响数据值的读入顺序。在列表输入方式下,读入的数据值顺序由其在INPUT语句中出现的顺序确定。
(3)使用INFILE语句的选项DLM=指定分隔符
当原始数据中数据记录的数据值未使用空格,而是使用其他分隔符时,需在INFILE语句中使用DLM=选项,告诉SAS读入数据时需要使用的分隔符。
下面将上面外部数据文件的内容稍作修改以便比较。文件inventory_dlm.dat的内容如下,数据记录中的各数据值之间由逗号(,)隔开。
P001R,12,125.00
P003T,34,40.00
P301M,23,500.00
PC02M,12,100.00
正确读取该数据文件的代码如下:

data saslib.Inventory;
    infile extfiles(inventory_dlm) dlm=',';
    input Product_ID $ Instock Price;
run;

proc print data=saslib.Inventory noobs;
run;

PRINT过程打印的数据集与上例一样,这里不再给出。
使用DLM=选项可处理原始数据记录中数据值中包含空格的情况。此外,使用DLM=选项的DATA步也可以很好地处理数据中的缺失值。如果接连有多个指定的分隔符,也会当成一个分隔符处理。但如果分隔符之间有空格,则该空格会当作缺失值读入变量并写入数据集。例如,当数据文件inventory_missing.dat的内容如下:
P001R,12,125.00
P003T,34,40.00
P301M, ,500.00
PC02M,12,100.00
提交与上例相同的SAS代码,记得将外部数据文件名字改为inventory_missing.dat。PRINT过程打印的数据集内容如图2.27所示,其中第3个观测Instock变量为默认值。
(4)使用INFILE语句的选项DSD
指定选项DSD后,如果数据值是由引号引起来的,可以将数据值中的分隔符当成是数据值的一部分读入,字符值中的引号在读入PDV时会被删除。DSD选项将默认的分隔符设置为逗号,还改变了使用列表输入时SAS处理分隔符的方式,比如,如果有两个连续的逗号,将被当作缺失值。
原始数据文件customer_dsd.dat的内容如下,其中客户的地址信息中包含了逗号,并使用双引号引起来,第一条和第三条记录中的客户名字缺失。

C001,,"14 Bridge St. San Francisco, CA"
C002,Emily Cooker,"42 Rue Marston"
C002,,"52 Rue Marston Paris"
C005,Jimmy Cruze,"Box 100 Cary, NC"

读取该外部数据文件的SAS代码如下,代码中还使用了LENGTH语句指定变量长度。

data saslib.customer;
    length Name $20 Address $40;
    infile extfiles(customer_dsd) dsd;
    input Customer_ID $ Name $ Address $;
run;

proc print data=saslib.customer noobs;
run;

PRINT过程打印的数据集内容如图2.28所示。同样,因为SAS先编译LENGTH语句,所以数据集中Name和Address变量出现在Customer_ID前面了。
选项DSD还可以和其他选项(例如DLM=和DLMSTR=)一起使用。DLM=在上面介绍过,DLMSTR=用于指定分隔数据值的字符串。如果需要,可以查看SAS帮助文档获得更多信息。
2.?按列输入
当原始数据记录中的数据值在每条记录中占据相同的列时,可使用按列输入的方式。按列输入(Column Input)可以读取固定列的数据。该读入方式的INPUT语句基本形式如下:
INPUT 变量1 <$> 开始列<-结束列> <变量2 <$>开始列<-结束列> …>;
其中:
变量名后有$表示该变量为字符型变量。
开始列-结束列指定变量在原始数据记录中所处的位置。
变量长度由为该变量指定的列数确定,可以超过8个字节。
按列输入可读入包含空格的数据值。
可以处理数据中的缺失值,不需要使用占位符。
文件customer.dat的内容如下,其中,第1~14列为产品编号,第16~26列为附属品牌,第28~29列为专卖店数,第31~35列为产品库存数。

C001                14 Bridge St.     San Francisco CA
C002 Emily Cooker   42 Rue Marston
C002                52 Rue Marston    Paris
C005 Jimmy Cruze    Box 100           Cary          NC

读入该原始文件中数据的SAS代码如下:

data saslib.customer;
    infile extfiles(customer);
    input Customer_ID $ 1-4  Name $ 6-19 Address $ 21-37 City $ 39-51 State $ 53-54 ;
run;

proc print data=saslib.customer noobs;
run;

PRINT过程打印的数据集内容如图2.29所示。
在列表输入方式下,读入的数据值由指定的列号确定,所以使用列表输入可以以任何顺序读入列,而且可跳过一些列、不读入到数据集中或重复读取数据记录中的数据值。
3.?格式化输入
上面介绍的按列输入与列表输入一样,只能读取标准的字符或数字值到数据集中。其实,SAS还可以读取特殊格式的数字数据,例如二进制数据、日期/时间(01FEB2013),或者包含逗号(1,262)、货币符号($87.3)等特殊字符的数字值。在这种情况下,就需要使用格式化输入(formatted input)了,即在INPUT语句中提供特殊的指令,以便SAS正确地读取原始数据记录中的数据值。这些特殊指令称为输入格式(Informat)。格式化输入组合了按列输入特征和读取非标准化数字或字符值的能力,保证数据值可正确地从原始数据记录中读入。
对单个变量,格式化输入的INPUT语句的基本形式如下:
INPUT 变量 <$> 输入格式;
数值型和字符型最基础的输入格式形式分别为n.和$n,例如10.和$20.,表示从输入缓冲区的当前列控制指针处分别读取10列和20列,并赋值给对应变量。对于字符变量,输入格式还指定了变量的长度,而数字型变量的长度仍然为8。
除了这些基础的格式外,SAS还提供了一些格式用于读入特殊格式的数字值,例如前面提到的读入带$符号的数字或日期时间值。同时,SAS还支持自定义格式,在本书后面的章节会介绍。
来看个示例,原始数据文件sales.dat的内容如下,该文件中原始数据记录包含字段依次为员工ID、部门、销售额和上次修改日期,其中销售额和日期都不是标准数字值,需使用对应的输入格式。

ET001 TSG $10000     01JAN2012
ED002     $12000     01FEB2012
ET004 TSG $5000      02MAR2012
EC002 CSG $23000     01APR2012
ED004 QSG            01AUG2012

读入处理该文件的SAS代码如下,其中Sales和Date变量分别使用了输入格式comma6.和date9.,Emp_ID和Dept使用的是上面介绍过的按列输入方式。

data saslib.sales;
    infile extfiles(sales);
    input Emp_ID $1-5 Dept $7-9 +1 Sales comma6. @22 Date date9.;
run;

proc print data=saslib.sales noobs;
run;

PRINT过程打印的数据集内容如图2.30所示。SAS的日期、时间以数字形式存储。所以打印的数据集中Date变量的值为数字,其实可通过输出格式(format)输出为更加易读的形式。
SAS提供了commaw.和datew.等很多格式,用于读取对应的带嵌入符号和日期的数字值,其中的w表示读入数据的总宽度,包含美元符号。输入格式将数据值中嵌入的$符号转换成了不带美元符号的数字值写入到PDV中,并最终写入数据集,如图2.31所示。
          
《深入解析sas:数据处理、分析优化与商业应用》一2.2 通过DATA步读取数据

在INPUT语句中,还使用了相对列控制符号+1和绝对列控制符号@22,分别表示将当前的输入列控制指针向前移1位和将该指针直接移动到列22。在上面的示例中,程序读入一行记录到输入缓冲区后,列控制指针的移动情况如下:
第1~5列写入Emp_ID,列控制指针在第6列。
第7~9列写入Dept,这时列控制指针在第10列。
+1将列控制指针移到第11列。
开始读入comma6.中指定的6列,即将第11~16列使用输入格式转换后写入Sales,这时列控制指针在第17列。
@22将控制指针直接移到第22列,读入date9.中指定的9列,即第22~30列,然后使用该输入格式进行转换,并写入Date。
SAS还提供了丰富的输入格式以便读入各种形式的数据值。下面给出了SAS提供的常用数字、字符、日期、时间、日期时间输入格式,如表2.1和表2.2所示。关于这些输入格式的详细使用方法,请参考SAS帮助文档学习。
《深入解析sas:数据处理、分析优化与商业应用》一2.2 通过DATA步读取数据

《深入解析sas:数据处理、分析优化与商业应用》一2.2 通过DATA步读取数据

4.?带修饰的列表输入
在学习格式化输入时,我们知道了输入格式的概念。本节将列表输入、输入格式和修饰符结合起来,结合后就成了带修饰的列表输入(modified list input),这样可以使用列表输入方式更灵活地读入数据。前面讲到列表输入时,提到列表输入的一些限制,例如所创建的变量长度为8个字节、默认(分隔符为空格时)不能读入包含嵌入空格的数据值、不能处理带特殊字符的数字值和日期等。SAS提供如下格式的修改符来消除这些限制,进而增加了列表输入的灵活性。
&修饰符(ampersand format modifier):使用列表输入时,该修改符能够读入数据值中包含一个或多个嵌入空格的字符值,并指定字符的输入格式。SAS读入数据直到遇到两个连续的空格或达到所定义的数据长度或输入行结束才停止。&修饰符解决了使用列表输入方式读取数据值中包含嵌入空格的问题,但要求该包含空格的数据值与下一个数据值之间至少间隔两个空格。
修饰符(colon format modifier):使用列表输入时,该修改符可以在变量名后指定输入格式。SAS读入数据直到遇到空列、达到所定义的数据长度(对字符型变量来说)或输入行结束才停止。:修饰符可以读取超过8个字节的字符数据和包含特殊字符的数字字符。
~修饰符(tilde forat modifier):可以读入并保持数据值中的单引号、双引号和分隔符。
接下来通过示例来学习修饰的列表输入。注意,下面的示例中没有使用~格式修改符,有兴趣的读者可参考SAS帮助文档进行学习。
原始数据文件customer2.dat的内容如下,每条记录包含联系人信息:客户ID、名字和出生日期,其中名字里面嵌入了空格,可使用&修饰符读入。注意,使用&修饰符要求名字和出生日期之间为两个空格。

C001  Willam Smith   22Oct1970
C002  Emily Cooker   01JAN1978
C002  Geroge Collin  09MAR1968
C005  Jimmy Cruze    25Jun1972

处理该数据的SAS代码如下,其中,Name变量使用了&修饰符读入带空格的名字,并指定输入格式为$20.,所以Name变量的字符长度为20个字节,而且SAS会将缓冲区中的20个字符读入Name。Birth_Date使用了:修饰符读入日期格式的数据。

data saslib.customer2;
    infile extfiles(customer2);
    input Customer_ID $ Name & $20. Birth_Date:date9.;
run;

proc print data=saslib.customer2 noobs;
run;

PRINT过程打印的数据集如图2.32所示,所有数据值都正确读入到了数据集中。
5.?命名输入
命名输入(named input)读取包含变量名、等于符号和变量值的输入数据,例如Name= Willam。命名输入的基本形式如下:

INPUT 变量1=  <$>  <变量2=  <$> …>;

使用命名输入方式时,INPUT语句从输入指针的当前位置读取输入数据。如果原始数据记录开始处不是命名形式,则可以在开始部分使用其他输入风格,接着再使用命名方式读入数据。但是,一旦INPUT语句开始使用命名输入方式,接下来的数据值则必须也是命名形式。
例如,原始数据文件named.dat内容如下,第二、三个字段为命名形式。

C001 Name=Willam Age=43
C002 Name=Emily Age=35
C002 Name=Geroge Age=45
C005 Name=Jimmy Age=41

使用命名输入读入第二、三个字段Name和Age,第一个字段Customer_ID变量使用列表输入。代码如下:

data saslib.customer;
    infile extfiles(named);
    input Customer_ID $ Name= $ Age=;
run;

proc print data=saslib.customer noobs;
run;

PRINT过程打印的SAS数据集内容如图2.33所示。
命名输入还可以和其他语句(例如LENGTH语句定义变量长度)以及其他输入方式结合,提供更多的灵活性来读取外部数据文件。
6.?混合输入
在使用INPUT语句时不限于使用一种输入方式,可以在一条INPUT语句中混合使用这些输入方式,只要可以适当地描述原始数据记录就行。在前面格式化输入的示例中已经组合了按列输入方式和格式化输入方式,这里再将3种基础输入方式组合。
原始数据文件mixedinput.dat的内容如下,其中依次包括了课程编号、课程名称、开课日期和报名人数等信息。

PM01 SAS Base Programming      22Oct2013 22
PM02 SAS Advanced Programming  01JAN2013 15 
TM01 SAS Text Mining           09MAR2013 8
SY05 SAS System Administration 25Jun2013 12

列表输入读取Course_ID和Attendee、按列输入读取Course_Name、格式化输入读取Open_Date。代码如下:

data saslib.mixedinput;
    infile extfiles(mixedinput);
    input Course_ID $
          Course_Name $ 6-30  
          @32 
          Open_Date date9. 
          Attendee;
run;

proc print data=saslib.mixedinput noobs;
run;

PRINT过程打印的数据集内容如图2.34所示。各个数据值都被正确地从数据文件读入,并写入数据集。

2.2.3 读取外部文本文件中的数据(高级)

在创建SAS数据集时,根据数据文件的格式,经常会需要使用更加高级的特征来正确有效地将原始数据文件的记录(或行)转换为数据集。例如:
对原始数据记录中的数据长度不够INPUT语句中所有变量读取的情况进行处理;
在创建SAS数据集的观测前先测试条件是否成立;
在单条数据记录中创建多个观测;
从多个原始数据记录中创建单条观测;
1.?使用INFILE语句的选项
当DATA步从外部文件中读取原始数据记录时,如果SAS在为所有变量读到数据之前就遇到了输入行的末尾,可能会有问题,尤其是当INPUT语句要求读入的变量长度超过输入缓冲区中的数据,或记录中包含缺失值又没有占位符时。默认情况下,SAS读入一行记录,当INPUT语句在当前输入行找不到所有的数据值时,会自动读入下一行数据记录,DATA步中的语句继续执行,但可能产生意外结果(即未按预期读入数据)。这时,可以在INFILE语句中指定MISSOVER、TRUNCOVER或STOPOVER选项,从而改变SAS的默认行为。
FLOWOVER:即上面描述的默认情况,INPUT语句会读入下一条记录到输入缓冲区中,给当前PDV中未赋值的变量赋值。
MISSOVER:会在DATA步的本次迭代中阻止INPUT语句读入原始数据的下一条记录,并将PDV中所有未赋值的变量保持为缺失值(PDV中变量未赋值时就为缺失值)。当原始数据记录中的最后一个或多个字段没有值,并且希望SAS将对应的变量置为缺失值时使用MISSOVER。
TRUNCOVER:也会阻止INPUT语句读入原始数据的下一个记录,但对于当前正在读入的变量,当输入缓冲区中的数据长度少于当前变量要求的长度时,则将当前输入缓冲区中的数据赋值给PDV中的该变量。所有未赋值的变量置为缺失值。
STOPOVER:DATA步的执行会停止。
下面通过一些示例来帮助理解这些选项的作用。
例2.4:选项MISSOVER。
外部数据文件missover.dat的内容如下,依次包括课程编号、课程名称、参加课程人数和讲师姓名等信息。其中第二条记录中未提供参加课程人数和讲师姓名,也没有占位符。

PM01,SAS Base Programming,22,Greg William
PM02,SAS Advanced Programming
TM01,SAS Text Mining,8,Kevin Wynne
SY05,SAS System Administration,12,Jenny Hagen

下面为使用默认的FLOWOVER选项代码:

data saslib.default;
    length Course_Name $30 Instrutor $20;
    infile extfiles(missover) dsd;
    input Course_ID $ Course_Name $ Attendee Instrutor $;
run;

proc print data=saslib.default noobs;
run;

PRINT过程打印的数据集如图2.35所示。
所生成的数据集***有3个观测。在第二个观测中,Instrutor变量值为原始数据文件中第三条记录的部分记录,Attendee为缺失值。这是因为在第二次迭代时INPUT语句读完Course_Name后,发现第二条原始数据记录已经没有值可供INPUT语句给PDV中的Attendee和Instrutor赋值了,所以SAS读入第三条原始数据记录到输入缓冲区中。这时输入缓冲区的第一个数据值“TM01”为字符值,与Attendee(数字型变量)的类型不匹配,所以PDV中Attendee为缺失值,第二个数据值“SAS Text Mining”则赋值给了PDV中的Instructor变量。接着,进行DATA步的第三次迭代,读入第四条原始数据记录。
查看日志窗口中DATA步执行的日志,如图2.36所示。其中提示信息表示“TM01”对数值型变量无效。而且“NOTE:INPUT语句到达一行的末尾,SAS已转到新的一行。”这句注释表示SAS在该次迭代中间(正常情况下,仅在迭代开始时读入数据行)从输入数据文件中读入了新行。

《深入解析sas:数据处理、分析优化与商业应用》一2.2 通过DATA步读取数据

下面在INFILE语句中加上MISSOVER选项,代码如下:

data saslib.missover;
    length Course_Name $30 Instrutor $20;
    infile extfiles(missover) dsd missover;
    input Course_ID $ Course_Name $ Attendee Instrutor $;
run;

proc print data=saslib.missover noobs;
run;

PRINT过程打印的数据集如图2.37所示。可以看到,原始数据记录如我们所预期的那样读入了数据集中。
例2.5:选项TRUNCOVER。
默认情况下(选项为FLOWOVER),当原始数据记录长度小于INPUT语句的预期时,INPUT语句自动读入下一条数据记录。当指定选项TRUNCOVER时,即使当前输入行数据的长度小于INPUT语句的预期,也会将当前输入行的数据赋值给当前处理的变量,并将其他没有赋值的变量设置为缺失值。
TRUNCOVER选项常用于处理变长的原始数据记录,可在INPUT语句中定义足够长度的变量,即使当前数据记录中的数据长度小于变量指定的长度,也可以将该记录从缓冲区读入PDV,并写入数据集,以便进一步处理。
原始数据文件comments.dat的内容如下,共3条记录,全部为文本,文本长度不确定。

Action brought Community Plant Variety Office Action brought Community Plant Variety 
    Office Parties Applicants 
Commission Regulation September 1995 concerning the second list of priority substances 
    as foreseen under Council Regulation 
Commission Regulation establishing the standard import values for determining the 
    entry price of certain fruit and vegetables Commission Regulation (EC) No

使用TRUNCOVER选项读入该文件记录。设置变量Text的输入格式为“$500.”,当原始记录中文本长度不足500个字符时,TRUNCOVER选项会将当前输入缓冲区中的所有内容写入PDV,并写入数据集。

data saslib.truncover;
    infile extfiles(comments) truncover;
    input Text $500.;
run;

proc print data=saslib.truncover noobs;
run;

PRINT语句打印的数据集内容如图2.38所示。可以看到,所有的评论信息都读入了数据集中。

《深入解析sas:数据处理、分析优化与商业应用》一2.2 通过DATA步读取数据

MISSOVER与TRUNCOVER的不同之处在于,如果当前变量没有读到要求长度的数据,MISSOVER会将当前变量的值也置为缺失值。还是以上面的示例为例,如果将TRUNCOVER换成MISSOVER,所生成的数据集中3个观测值都为缺失值。
例2.6:选项STOPOVER。
当INPUT语句在当前输入行找不到所有数据值时,如果在INFILE语句中使用选项STOPOVER,SAS会停止执行该DATA步。
使用前面用到的数据文件missover.dat,内容如下:

PM01,SAS Base Programming,22,Greg William
PM02,SAS Advanced Programming
TM01,SAS Text Mining,8,Kevin Wynne
SY05,SAS System Administration,12,Jenny Hagen

使用STOPOVER选项的代码如下:

data saslib.stopover;
    length Course_Name $30 Instrutor $20;
    infile extfiles(missover) dsd stopover;
    input Course_ID $ Course_Name $ Attendee Instrutor $;
run;

proc print data=saslib.stopover noobs;
run;

DATA步执行的日志如图2.39所示。此时,在日志窗口会给了出错误消息“INPUT语句超过记录长度”,并且SAS系统停止处理DATA步。

《深入解析sas:数据处理、分析优化与商业应用》一2.2 通过DATA步读取数据

PRINT语句打印的数据集内容如图2.40所示。可以看到,所生成的数据集中只有一个观测。因为在处理第二条原始数据记录时,输入数据缓冲区中没有供Attendee和Instructor变量读取的数据,SAS处理停止执行该DATA步了。
2.?使用选项LRECL和在INFILE中使用选项PAD
选项LRECL为系统选项指定用于读写外部文件的默认逻辑记录长度。LRECL指定逻辑记录的长度为1(字节)或1024(k字节)的倍数。例如32表示32字节、16k表示16?384字节。该选项的范围为1~32?767。在SAS 9.4中,LRECL系统选项默认值为32?767,通常不需要修改。
PAD和NOPAD选项控制SAS是否使用空格对从外部文件读入的记录进行填充,使其达到选项LRECL=指定的长度。默认设置为NOPAD。
当使用PAD选项时,SAS会自动用空格填充从外部文件中读入的记录长度。
还是以上面的comments.dat文件为例。

Action brought Community Plant Variety Office Action brought Community Plant Variety 
    Office Parties Applicants 
Commission Regulation September 1995 concerning the second list of priority substances 
    as foreseen under Council Regulation 
Commission Regulation establishing the standard import values for determining the 
    entry price of certain fruit and vegetables Commission Regulation (EC) No

下面在INFILE语句中使用PAD选项,代码如下:

data saslib.comments;
    infile extfiles(comments) pad;
    input Text $500.;
run;

proc print data=saslib.comments noobs;
run;

PRINT过程打印的数据集内容如图2.41所示。DATA步正确读入了文件中的所有评论。

《深入解析sas:数据处理、分析优化与商业应用》一2.2 通过DATA步读取数据

3.?使用INPUT语句的行保持符和行控制符
在一个DATA步中可有多个INPUT语句。默认情况下,当执行到INPUT语句时,程序会自动将下一条数据记录放入输入缓冲区,并且,每次迭代完成后SAS会返回DATA步的开始处,同时,输入缓冲区中的数据会自动清除。可以在INPUT语句中使用行保持符和行指针控制符改变这种行为模式。
(1)行保持符
如果在INPT语句的结束处指定单个@的行保持符(Line-hold Specifier),输入缓冲区的数据则会保持住,当前迭代中的下一条INPUT语句可以继续使用。SAS会维护列控制指针在输入缓冲区中的位置。但是,当程序返回DATA步开始处执行时,输入缓冲区中的数据会被释放。
在INPUT语句的结束处使用有两个@的行保持符(形式为@@)时,输入缓冲区中的数据会保持住,直到读到数据记录的结尾内容为止。这样,程序在执行INPUT语句时就不会自动将下一条数据记录读入到输入缓冲区中,同时,程序返回DATA步的开始处继续执行时,输入缓冲区的数据也不会释放。但是SAS会维持指针在当前记录中的位置,这样,程序就能够读取记录中的每个值了。
(2)行指针控制符
在INPUT语句中还可使用行指针控制符/和#n。
行指针控制符/会强制读入下一条记录到输入缓冲区,并将列指针放入该行记录的开始处。该行指针控制符常用于跳过原始数据记录中的数据值。
n会将当前指针移至多行输入缓冲区的对应行。当INPUT语句中出现行指针控制符#n时,编译程序会创建一个多行输入缓冲区,输入缓冲区的行数等于INPUT语句中出现的最大n值。这样,执行时程序可以一次读入多行数据到输入缓冲区。通过这种方式,INPUT语句可以以任意顺序读入数据值。
下面通过几个示例来讲解行保持符@和@@,以及行指针控制符/和#n的使用方式。
(3)创建一个观测前测试条件
在只需读入输入文件中的部分满足条件的数据时,可能需要先读入部分变量值,判断这些变量值是否满足某种条件。如果满足,继续读入当前缓冲区中的其他数据值。如果使用了默认INPUT语句,会在第二次执行INPUT语句时将下一条记录读入输入缓冲区,之前输入缓冲区中的数据则会被冲掉。这时可以使用行保持符@将该记录保持在输入缓冲区中,第二次执行INPUT语句时程序不会重新读入新的数据记录,而是直接使用当前输入缓冲区中的数据。
原始数据文件Sales.dat的内容如下,每条记录中包含员工ID、部门、销售额和上次修改时间等信息,现在要求仅读入部门TSG的员工并创建数据集。

ET001 TSG $10000     01JAN2012
ED002     $12000     01FEB2012
ET004 TSG $5000      02MAR2012
EC002 CSG $23000     01APR2012
ED004 QSG            01AUG2012

下面的DATA步创建仅包含部门TSG员工的数据集:
第一条INPUT语句读入输入数据的第7~9列到Dept变量中,并使用@行保持符告诉SAS将输入记录保持在缓冲区。
IF语句判断变量Dept的值是否为TSG。如果不是,程序返回DATA步的开始处进行下一个迭代;如果是,执行该次迭代的下一条语句。
第二条INPUT语句从输入缓冲区中按列读入和格式化读入其他变量值。代码如下:

data saslib.sales;
    infile extfiles(sales);
    input Dept $7-9 @;
    if Dept='TSG';
    input Emp_ID $1-5 +5 Sales comma6. @22 Date date9.;
run;

proc print data=saslib.sales noobs;
run;

PRINT过程打印的数据集的内容如图2.42所示。可以看到,数据集中只包含部门(Dept)为TSG的观测。
(4)读单条记录创建多个观测
当原始数据文件的一条记录中存在要创建的数据集的多个观测时,应在INPUT语句中使用两个行保持符@@,这样,程序执行到下一个INPUT语句,甚至下一个迭代时,都不释放输入缓冲区中的记录,INPUT语句会继续从输入缓冲区中读取数据值,直到INPUT读完缓冲区中所有的数据值为止。
数据文件inventory2.dat的内容如下,每行包含两种商品的信息。

P001R 12 125.00 P003T 34 40.00 
P301M 23 500.00 PC02M 12 100.00

这时使用@@行保持符将记录一直保持在输入缓冲区,直到缓冲区中的所有数据值都被读取。代码如下:

data saslib.Inventory;
    infile extfiles(inventory2);
    input Product_ID $ Instock Price @@;
run;

proc print data=saslib.Inventory noobs;
run;

PRINT过程打印的数据集如图2.43所示。可以看到,所有4种商品信息都读入了数据集中。
(5)从多条记录中创建一个观测
有时一个客户的联系信息,或者一种商品的属性信息会分布在多个行中,而我们只关心部分数据行的信息,这时可以使用单独的空INPUT语句或/行指针控制符来跳过不关心的行。
参考如下原始数据文件Customer3.dat的内容。这里打算将同一个联系人的多行信息读入为数据集的一条观测,但是不包含街道或邮箱信息(即每组信息的第二行)。

Greg William
14 Bridge St.
San Francisco
CA
Emily Cooker
42 Rue Marston
New york
NY
Jimmy Cruze
Box 100
Cary
NC

方法1:使用多个INPUT语句。
此方法在一个DATA步中使用多个INPUT语句,每个INPUT语句读入新行到输入缓冲区,并从输入缓冲区中读入指定变量值到PDV。当DATA步结束时,将PDV中的变量值写入数据集。代码如下:

data saslib.Customer;
    infile extfiles(Customer3) truncover;
    input Name $20.;
    input;
    input City $20.;
    input State $2.;
run;

proc print data=saslib.Customer noobs;
run;

在上述代码的执行过程中:
第一个INPUT语句读入第一行,并将其内容赋值给PDV中变量Name。
第二个INPUT语句读入第二行。
第三个INPUT语句读入第三行,并将其内容赋值给PDV中变量City。
第四个INPUT语句读入第四行,并将其内容赋值给PDV中变量State。
DATA步结束时,PDV中的变量写入数据集。同样,在下一个迭代中将第五行、第七行、第八行数据赋值给PDV中的变量Name、City和State,然后写入SAS数据集。PRINT过程打印的数据集如图2.44所示。
方法2:使用/行指针控制符。
也可以使用/行控制符,强制读入新行到输入缓冲区,并赋值给变量。代码如下:

data saslib.Customer;
    infile extfiles(customer3) truncover;
    input Name $20. / / City $20. / State $2.;
run;

proc print data=saslib.Customer noobs;
run;

在上述代码的执行过程中:
INPUT语句读入第一行,并赋值给PDV中的变量Name。
//强制依次读入两行,即第二行和第三行,第三行的值会覆盖第二行,并赋值给PDV中的变量City。
/读入第四行,赋值给PDV中的变量State。
DATA步结束时,将PDV中的变量写入数据集。同样,在下一个迭代中,将第五行、第七行、第八行的数据赋值给PDV中的变量Name、City和State,然后写入SAS数据集。PRINT过程打印的数据集如图2.45所示。
方法3:使用#n行指针控制符。
还可以使用#n行指针控制符,直接在多行的输入缓冲区中移动行指针。代码如下:

data saslib.Customer;
    infile extfiles(customer3) truncover;
    input #3 City $20. #1 Name $20. #4 State $2.;
run;

proc print data=saslib.Customer noobs;
run;

DATA步在编译时会创建一个4行的输入缓冲区,因为n的最大值为4。执行过程中:
INPUT语句一次读入4行记录到输入缓冲区。
3将行输入指针移动到输入缓冲区的第三行,将数据值读入到PDV中的变量City。
1将行输入指针移动到第一行,将数据值读入到PDV中的变量Name。
4将行输入指针移动到第四行,将数据值读入到PDV中的变量State。
DATA步结束时,PDV中的变量写入数据集。同样,在下一个迭代中,INPUT语句会将第五行至第八行数据一次读入到输入缓冲区。再从输入缓冲区的第三行、第一行和第四行中分别读入数据赋值给PDV中的变量City、Name和State,然后写入SAS数据集。
PRINT语句打印的数据集内容如图2.46所示。这里的观测数和数据值相同。
该数据集与前两个数据集的变量顺序不同,是因为在这3个示例中的变量在DATA步中出现的顺序不同。
本例为了说明使用#n行控制符可以随意在输入缓冲区的行之间移动,特意指定读取顺序为第三行、第一行和第四行。若INPUT语句按如下形式编写,则所生成数据集的变量顺序也与前两种方式一样:
input #1 Name $20. #3 City $20. #4 State $2.;
这3种方法都要求单条观测在原始数据文件中占用的行数一样。例如,在本例中都是4行,而且要读取的同类别信息必须出现在相同的相对位置上,例如在每4行记录中,第一行是客户姓名、第三行是所在城市、第四行是所在州。