数据清洗DATA CLEARING之infile选项之二PAD MISSOVER TRUNCOVER

这里简单的列举下其他option的功能,可以根据需要同时用多个option。
下面再来看另外读一个简单的数据。
data numbers;
infile datalines ;
input testnum 5.;
datalines;
1
22
333
4444
55555

;
run;
 
大家知道infile是常常用来读外部文件的。现有同一.txt数据文件,在硬盘中位置:F:\saschm\samplefileforB\out_data2a.txt。
1
22
333
4444
55555
 
用下面代码读取时,发现失败的。
filename outdata1 ‘F:\saschm\samplefileforB\out_data2b.txt’;
data numbers;
infile outdata1 ;
input testnum 5.;
run;
 
得到结果为下面,说明读入是不符合要求的。
Obs    testnum
 1         22
 2       4444
 3      55555

为什么会发生这种现象,首先说明一个问题SAS读入外部文件和读入放到editor里面的数据是有区别的。
但是如果外部数据如下,在硬盘中位置:F:\saschm\samplefileforB\out_data2b.txt,【注意后面用空格的,每行字符大于或等于5都可以的。】
1   
22  
333 
4444
55555
 
用下面代码,就可以符合要求的结果。
filename outdata1 ‘F:\saschm\samplefileforB\out_data2b.txt’;
data numbers;
infile outdata1 ;
input testnum 5.;
run;
 
在实际中数据量巨大时,对于out_data2a.txt这样的外部数据,手动添加空格的方法显然不是明智的选择。当然SAS本身也提供了一个有用的option:pad 来解决这种问题,PAD选项就是这样专门用来垫当空格的。
 
filename outdata1 ‘F:\saschm\samplefileforB\out_data2a.txt’;
data numbers;
infile outdata1 pad;
input testnum 5.;
run;
 
当然还有一种解决方法,就是设置option:truncover。
filename outdata1 ‘F:\saschm\samplefileforB\out_data2a.txt’;
data numbers;
infile outdata1 truncover;
input testnum 5.;
run;
 
可能SAS对外部数据的行数(即回车键)没有辨别能力,导致读入内部和外部源数据时出现差异,当然这些问题SAS也会给出了相应的解决方案的,那就是设置option:truncover。关于选项truncover,下面将举例来说明。
 
 
简单的情况下,我们很愉快的用下面的代码读入完整的记录数据。
data ex;
infile datalines ;
input x1 x2 x3 x4;
datalines;
9 1 3811 3812
9 2 3816 3846
1 3 3819 3820

;
run;
 
实际情况下,记录的数据不是那么完整,或多或少的有些缺失值,如下下面这样有缺失值的。
9 1 3811 3812
9 2 3816    
1 3 3819 3820
用上面的代码读入,显然读入的数据达不到要求,我们可以手动的添加‘.’符号是可以解决这个问题的,但是假如这种缺失值现象非常严重,大量的手动添加是不可能的,而且会出现认为的错误。SAS提供了一个有用的option:missover,当变量没有对应的赋值,相当于可以帮你自动添加点符号,即赋缺失值(一般都给后面没有赋值的变量)。
data ex;
infile datalines missover;
input x1 x2 x3 x4;
datalines;
9 1 3811 3812
9 2 3816
1 3 3819 3820

;
run;
 
你会发现假如用option:truncover也可以读到符合要求的数据。
data ex;
infile datalines truncover ;
input x1 x2 x3 x4;
datalines;
9 1 3811 3812
9 2 3816
1 3 3819 3820
;
run;
 
truncover和missover的功能有相似的地方,就是两个选项都赋值给余下的INPUT语法给定的变量为缺失值。区别在于当读入的字段长度比该字段对应的变量定义的长度短时,INPUT语句不能读入全部的字段,MISSOVER选项将会设置该变量为缺失值,而TRUNCOVER则可以改进这个问题。【From SAS/HELP】下面的代码就说明了这个问题。
代码1:
filename outdata1 ‘F:\saschm\samplefileforB\out_data2a.txt’;
data numbers;
infile outdata1 truncover;
input testnum 5.;
run;
代码2:
filename outdata1 ‘F:\saschm\samplefileforB\out_data2a.txt’;
data numbers;
infile outdata1 misscover;
input testnum 5.;
run;
 
有truncover的代码就能很恰当的读入源数据,而missover只有当字段长度达到input规定的要求时,才能读入完整的值,否则赋缺失值。 
 
小结:好像在这里也看到了,解决同一种问题有多种方法的例子,异途同归,她们之间的差异就在于实现的途径上,^_^。
 
欢迎补充,欢迎拍砖!
 
ps:原来已经有人对这三个options作为对比了。
FROM:SUGI26.
TITLE: MISSOVER, TRUNCOVER, and PAD, OH MY!! or Making Sense of the INFILE and INPUT Statements.
 
其中他列了个表,从另外一个应用的角度来对比这几个options的差异,很好:
 
FLOWOVER :The default. Causes the INPUT statement to jump to the next record if it doesn’t find values for all variables.
MISSOVER :Sets all empty vars to missing when reading a short line. However, it can also skip values.
STOPOVER :Stops the DATA step when it reads a short line.
TRUNCOVER: Forces the INPUT statement to stop reading when it gets to the end of a short line.This option will not skip information.
SCANOVER: Causes the INPUT statement to search the data lines for a character string specified in the INPUT.
PAD: Pads short lines with blanks to the length of the LRECL= option.
 
 

数据清洗DATA CLEARING之infile选项之一 DSD DLM

SAS做其数据分析起来感觉很爽,但是实际中总会遇到一些格式花样很丰富的源数据,怎么把它读入到SAS内部作为逻辑库里面的一个数据集呢?不会要手动一个一个的使用健盘上的ctrl+v,ctrl+c,backplace,delete的快速调整吧。数据少的时候还可以做做,一旦数据量巨大的时候,恐怕找个机器人也会感觉费电的。其实SAS提供了很多灵活的接口,快速的解决这些dirty的数据,让他们规规距距的转化为SAS自定义的格式。
 
我们很喜欢读入这样的源数据:
 M        50      68        155
 F        23      60        101
 M        65      72        220
 F        35      65        133
 M        15      71        166
 
代码如下:
  data demographics;
     input Gender $ Age Height Weight;
  datalines;
 M        50      68        155
 F        23      60        101
 M        65      72        220
 F        35      65        133
 M        15      71        166
  ;
run;

但是这种以间隔符号“ ,”为分隔符的源数据,也很常见:

 M,50, 68,155
 F,23, 60,101
 M,65, 72,220
 F,35, 65,133
 M,15, 71,166

这时就需要用到infile及其option来读入这样的数据。
 data demographics;
  infile datalines dlm=’,’; /*     infile datalines dsd; /*也可以用这个替代*/ */
  input Gender $ Age Height Weight;
  datalines;
 M,50, 68,155
 F,23, 60,101
 M,65, 72,220
 F,35, 65,133
 M,15, 71,166
  ;
run;
 
下面这种的这种格式也是会遇到的:
  "M",50,68,155
  "F",23,60,101
  "M",65,72,220
  "F",35,65,133
  "M",15,71,166
 
如果继续用option:dlm=’,’的话,得到的数据集里的变量Gender的值有引号。可是我不需要引号,这时只用option:dsd就可以解决这个问题,如下:
  data demographics;
     infile datalines dsd; 
     input Gender  $ Age Height Weight;
  datalines;
  "M",50,68,155
  "F",23,60,101
  "M",65,72,220
  "F",35,65,133
  "M",15,71,166
  ;
run;
 
当然有时候你需要引号,那就继续使用option:dlm=’,’就可以了。当然下面使用option:dsd也可以的,需要在变量Gender后面加上一个’~’符号就可以了,如下:
  data demographics;
     infile datalines dsd; 
     input Gender ~  $ Age Height Weight;
  datalines;
  "M",50,68,155
  "F",23,60,101
  "M",65,72,220
  "F",35,65,133
  "M",15,71,166
  ;
run;
 
sas似乎会提供了多个方法途径来解决同一个问题,看起来很罗嗦。于是下面我们来看看这样的一个源数据。
 
  "M,12",50,68,155
  "F,11",23,60,101
  "M,22",65,72,220
  "F,21",35,65,133
  "M,31",15,71,166
 
如果仍然要求变量个数不变,dlm=’,’似乎不能很好的解决这个问题,它会机械的把括号里面的逗号折开,导致读入失败。如果用option:dsd就不会出现这种情况,代码如下:
 
  data demographics;
     infile datalines dsd; 
     input Gender ~ $ Age Height Weight; /*如果不需要保留双引号,可去掉修饰符~*/
  datalines;
  "M,12",50,68,155
  "F,11",23,60,101
  "M,22",65,72,220
  "F,21",35,65,133
  "M,31",15,71,166
  ;
run;
 
这个例子说明SAS不会很罗嗦的用多个途径来解决一个问题,在进行其他编程时,也有这种感受。也许可以通过多种方法来达到同一个目的,但是不表示这些方法之间没有差异,每种方法有其根源,在实现方式,思想和执行性能等各方面都会表现出差异。有时候会是极微小的差异,这里我们没有必要去吹毛求疵的去辨别这些差异,并以此为乐。寻找一种合适的解决问题编程途径是根本目的,因此了解和认识sas提供的各种方法的基本功能是最主要的,本例中,在sas/help中DSD的tips写得很清楚它会在存储数据前会去掉引号,如果想继续保留,可以加上格式修饰符"~"。
 
除此之外,option:DSD会把两个连续的","分隔符之间认为是缺失值,而option:dlm=’,’则把他们当作一个’,’来对待。你可以尝试运行一下下面的代码:
  data demographics;
/*     infile datalines dsd; */
  infile datalines dlm=’,’;
     input Gender ~$ Age Height Weight;
  datalines;
  "M,12",50,68,155
  "F,11",23,60,101
  "M,22",65,72,220
  "F,21",35,65,133
  "M,31",15,71,166
  ;
run;
 
小结:1,infile的选项:dlm=’ list-of-delimiting-characters”,引号里面还可以是其他很多类型的分隔符号(除了空格),还可以结合infile的其他选项来完成dirty源数据的读取。选项DSD是针对有逗号分隔符号的,功能很强大。为什么逗号会有这么好的待遇呢?需要一个专门的option来“伺候”它?^_^
         2,SAS有看似罗嗦烦杂的各种介绍和tips,这都是为了应对实际中的数据丰富多样化的需要产生的。
 
 
补充:SAS9.2中出现了新的选项:DLMSTR= ,有关描述和例子如下:
 
String Delimiter
 1, "Top 10" SASware Ballot Item
 2,Difficult to parse multi-character delimiters
 3,INFILE DLMSTR=
 4,Works like DLM=
data _null_;
infile mydata dlmstr=’–‘ dsd;
input x y name $ addr:$16.;
put x= y= name= addr=;
datalines;
1.25–2.25–"Name"–"12 Main St"
;
x=1.25 y=2.25 name=Name addr=12 Main St
 
由于我暂时还没有SAS9.2系统,所以还不能测试,仅供参考。

两个数据集的操作-根据一个关键词主表连接从表的操作

现在有两个表,一个主表,一个从表,要求根据一个关键词将主表中的变量值加到从表中,从而得到一个合并的表。
下面是一个简单的代码示例:
data test1;
input id nowtime yymmdd10.;
format nowtime yymmdd10.;
cards;
100  20071201
200  20071101
;
run;
data test2;
input id histtime yymmdd10.;
format histtime yymmdd10.;
cards;
100  20020501
100  20030601
100  20040701
200  20030701
200  20040901
200  20051001
200  20061101
;
run;
 
目标数据集为:
  id       nowtime         histtime
100    2007-12-01    2002-05-01
100    2007-12-01    2003-06-01
100    2007-12-01    2004-07-01
200    2007-11-01    2003-07-01
200    2007-11-01    2004-09-01
200    2007-11-01    2005-10-01
200    2007-11-01    2006-11-01

实现这个要求的代码,目前我只想出来三种:
1,使用经典date步的merge方法:
data exx;
merge test1 test2;
by id;
run;
proc print;
run;
 
2,使用高级sql查询语句outer joins之left join:
proc sql;
create table test as
       select coalesce(test1.id, test2.id) label=’id’,
              nowtime,
              histtime
              from test1 left join test2
              on test1.id=test2.id;
quit;
 
3,使用sas9.0,9.1的DATA步增加的新功能:HASH对象方法
data  merged;
               length  id  8;
               length  nowtime 8;
               format nowtime yymmdd10.;  
               if _N_ = 1 then do ;
                    declare Hash ht (dataset:"test1");
                    ht.defineKey("id");
                    ht.defineData("nowtime");
                    ht.defineDone();
     call missing(id,nowtime);
               end;
          
              /* Load key and query hash table*/
            set test2;
               if  ht.find() =0 then output;
run;
这三种方法都可以实现。
但是那种会更好呢?我觉得三种都不错,熟悉那个就写那个,能解决问题就行,或者工作要求那个风格就用那个风格。
然而,实际中会遇到从表非常巨大的情况,这时三种方法的优劣就显现出来,那就是完成同样的要求代码执行时间不同(前提:代码成功且稳定可靠)。
既然其他方面的问题暂时步考虑,那下面就比较一下这三个方法的实现执行所需要的时间吧。
于是构造了下面两个数据集,其中第二个用循环得到一个巨大的数据集。
data master;
input key $ val1;
cards;
aaa 000
bbb 333
ccc 444
ddd 555
;
data trans;
      do val2= 0 to 26*300000-1;
      key=compress(byte(97+mod(val2,26))||byte(97+mod(val2,26))||byte(97+mod(val2,26)));
   output;
      end;
run;
/*感谢Ahuige提供Ascii代码转化思路!http://sasor.feoh.net/modules.php?name=Forums&file=viewtopic&t=1882&highlight=ASCII&sid=c32b5326ec07d4dd0197ab06939845c4*/
 
下面用上面提到的三种方法来进行这个操作,并比较这些方法所需要花费的时间。代码如下:
CODE1:
 
proc sql noprint;
  create table meraed as
      select coalesce(master.key, trans.key) label=’key’,
          val1,
    val2
             from master left join trans
        on master.key=trans.key;
     quit;
 

CODE2:
data  merged;
               length  key  $ 13;
               length  val1  8;
               if _N_ = 1 then do ;
                    declare Hash ht (dataset:" master");
                    ht.defineKey("key");
                    ht.defineData("val1");
                    ht.defineDone();
     call missing(key,val1);  /* avoid uninitialized variable notes */         
                    end;
          
              /* Load key and query hash table*/
            set trans;
               if  ht.find() =0;
run;
 
CODE3:
 
proc sort data=master out=master;
by key;
run;
proc sort data=trans out=trans;
by key;
run;
data meraed(where=(val1 ne .));
merge master trans;
by key;
run;
 
RESULTS:
 
A, 方法merge with by
      sort 花费时间:
           实际时间         2:01.90
           CPU 时间         10.84 秒
      merge花费时间:
          实际时间         9.90 秒
          CPU 时间         4.04 秒
(运行过程中,提示我C盘空间不够啊,说明中间临时数据集需要占不少资源 ^_^)
 
B, sql方法花费时间:
当事先排序时:
 sort过程花费时间:
           实际时间         2:01.90
           CPU 时间         10.84 秒
  sql 实际时间         7.34 秒
      CPU 时间         1.23 秒
 
当事先未排序时:
     实际时间          1:46.48
     CPU 时间         11.29 秒
(看来如果事前未排序,直接sql花费的总时间更短)
 
C, hash方法花费时间:
      实际时间          8.42 秒
      CPU 时间         3.60 秒
(貌似排不排序影响不大,不过时间波动很大(实际时间在范围5.81-10.46),可能系统太多程序同时进行)
 
小结:1,如果排好序,三种方法所花时间差不多。
         2,如果没有排好序,hash时间最小,其次为sql方法,时间最多为merge with by。
         3,merge with by要求必须先排好序,sql方法没有要求,如果数据集先已排好序,速度非常快,但是所用总时间大于直接用sql方法。hash方法也不要求排序,且排序是否不影响速度。
 
我的系统配置:台式机,XP系统;SAS9.13版;AMD双核3600+,内存1.5G,硬盘IDE接口。(当然还同时开了word,pdf,qq,note,xdict,winamp,iexpore,SAS9.13等程序)。
 
上面的数据具有一定的参考意义。
欢迎大家参与测试,并留下测试时间。
测试注意merge必须要事先排好序,而sql和hash方法则不需要,但好序后sql会更快。
 
欢迎大家扔砖!^_^
并且欢迎高手点评!^_^
 

通过代码使用SAS

 
作为一个工具,具有方便使用是其重要的特性之一。SAS的使用需要复杂的编程,给人一种和脱离普通用户的感觉,好像只有高端用户才能用。其实,作为一个优秀的数据处理系统,代码是SAS系统的精髓,通过代码调用各个模块才能将SAS的性能发挥出来。
 
这就是为什么SAS不断的升级,虽然不断的有可视化模块添加到SAS系统中,仍保留那羞涩难懂的代码实现方式,并且在SAS系统不断的升级中,代码部分也在升级,不断的添加新的语言特性,来改善其代码的易读性和高效性。
不管SAS系统怎么复杂,SAS的语法多么浩瀚,SAS始终是一个工具。我们需要掌握我们需要用来解决问题的一部分就可以了,因此学会怎么使用SAS才是学习SAS的硬道理。代码作为驱动SAS系统各功能模块的重要利器,是深入使用SAS的基础。
 
所以本blog着重于通过代码来使用SAS,代码是用户和SAS系统的接口,决定了本blog的风格是代码及代码的讨论占主要地位
 
欢迎拍砖!  ^_^