数据清洗DATA CLEARING之infile选项之三 SCANOVER

关于infile的选项scanover,有一个最简单的例子。
data x;
infile datalines  scanover truncover;
input @’abc’  var1 var2;
datalines;
134 234 123 145
321 abc 241 456
548 890 abc 345
296 456 123 127
;

该代码可以读取特征标签字符abc后面的字段,根据需要和missover,truncover结合使用。
 
SAS/help里面有个讲SCANOVER的例子,这个看起来还比较实用些。为了好演示,我稍作修改:
data ex;
infile datalines  scanover truncover;
   input @’phone:’ phone $32.;
   datalines;
    Jenny’s Phone Book
    Jim Johanson phone: 619-555-9340
       Jim wants a scarf for the holidays.
    Jane Jovalley phone: (213) 555-4820
       Jane started growing cabbage in her garden.
       Her dog’s name is Juniper.
    J.R. Hauptman phone: (49)12 34-56 78-90
       J.R. is my brother.
;
run;
这个代码可以从一堆数据找出所要的字段,上面就是找出电话号码,这个例子比较简单。当然用同样的方法可以从数据堆中得到例如网站地址,email地址,电话号码等等感兴趣的字段了(当然要满足一定的规律,如前面都有特征标签字段)。
 
如果我想把某一页面里面的titles保存为一个数据集,怎么办?
源数据获得方法:1,上sasor网站,点击“搜索"
                         2, 输入某个id,回车,得到一个查询结果网页
                        3,点浏览器工具栏”查看“-”源文件(C)“,得到一个文本文档,另存为源数据:dataforscanover.txt,这里面保存着该页面所有的Titles,就用它来test吧。
 
先上代码吧,用代码来说明问题:
filename outdata ‘F:\saschm\samplefileforB\dataforscanover.txt’;
data ex(drop=xx);
 infile outdata  scanover LRECL=1152;
   input @’class="topictitle">’ xx $64. @’class="topictitle">’ title $64. ;
    title=scan(title,1,"<");
run;
用print语句看看结果吧:
 
Obs    title
  1    讨论何种方式最有效地得到数据表的观测数?
  2    SAS中文论坛北京聚会通知(最新)
  3    求助:Metlab
  4    这种数据如何按这个要求读入
  5    SAS中文论坛&amp;SASOR北京网友9月聚会纪要
  6    SAS Question (3)
  7    [求助]SAS 数据转化 帮忙
  8    SAS Question 2
  9    SASOR期刊即将推出
 10    用SAS9.1.3的人多吗?
 11    SASOR第一份期刊已经完成
 12    请教:为什么SAS公司把1582年作为可以视为日期的“第一年”呢?
 13    又一个棘手的SAS数据集转换问题
 14    数据集转换问题
 15    Data Summary,questioned by max_ fromSAS中文
 16    哪儿兄弟指点一下:SAS如何做抽样分析?比如从1亿条记录集中抽取
 
上面的结果暂时让人很满意,与网页的搜索结果很一致。
 
但是在test过程中遇到了不少的问题,
1,首先实际的一行数据有很多比如这次保存的数据一行长度达到了1045,显然默认的256是不够的,那么需要通过option:LRECL=logical-record-length,来指定吧单位是byte(SAS9.13中最大值为1048576bytes,即是1M)。这个值如果小于一样的字段bytes,那么后面的字段将会都被截断,不会读取。如果设定太大,会出现占用大量资源的情况(如果有机会在后面探讨一下),所以需要选个合适大小的lercl。【TIPS:那怎么来设定lrecl的大小?最简单的方法是根据log里面的提示来设定,选用一个合适保险的,具体方法就是设成足够大,然后它提示最长记录是多少,然后设定的值只要比它大即可。】
2,选择一个特征标签字段,这个有规律字段就在我们感兴趣字段的前面,一般都会用的。这里我们碰巧遇到了class="topictitle">字段,就用这个,不过出现了一个问题,因为还有一个字段也是以它为特征标签字段的,那就一起读吧,于是有了变量xx,当然后来把这个不感兴趣的字段drop掉就可以了。得到的title变量里面的字段后面会跟着符号”>*****“一样的多个字段,用一个scan函数【函数也是SAS系统非常重要的工具,后面会浓墨描述,^_^】剪掉,于是就得到干干净净的title字段。
 
小结:一般情况下,我们从SAS/HELP文件,或者一些SAS教程中往往能看到一些简单专门讲单个语句,或者option,或者函数等单元功能部分。在实际应用中往往会全面整体去考虑,会运用到SAS的多个方面的知识点,因此SAS的编程语言认为是一个学习门槛很高的语言。学习过程需要花费很长的时间,是一个长期积累的过程。
 

数据清洗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的风格是代码及代码的讨论占主要地位
 
欢迎拍砖!  ^_^