数据清洗之单个数据集操作-重复记录

现有一堆数据,里面有很多是重复的,要去掉?要重新保留成另外的数据集?
 
2 2 3
2 2 3
4 5 6
4 5 6
4 5 6
4 5 6
4 4 4
7 7 8
9 5 5
 
我想去掉里面重复的观察值,实现的四种方法:
1,proc语句
2,sql语句(之所以单独出来,是因为sql本来都强大,可以独顶一方)
3,data步
4,hash对象方法
 
 
第一种,PROC 两种方法:
1,SAS语句proc sort:
data ex;
input a b c;
cards;
2 2 3
2 2 3
4 5 6
4 5 6
4 5 6
4 5 6
4 4 4
4 4 5
7 7 8
9 5 5
;
run;
proc sort NODUPRECS out=ex1 DUPOUT=ex2;
by a b  ;
run;
 
不重复的保留在数据集ex1里面,重复的保留在数据集ex2里面。
这里重复有两种情况,如果指定关键词with by的重复的操作的话,那么sort的option:NODUPRECS要换成NODUPKEY,这样得出的结果是两种不同的情况。
 
2,SAS语句SUMMARY,SAS_Dream大侠做过深入的讨论,详情见:
http://www.mysas.net/forum/viewtopic.php?t=815
 
第二种,PROC  SQL,有多种途径:
还有上面SAS_Dream的。
 
 
 
第三种,DATA步内微操作,(这样操作有个前提,就是此数据集需要进行排序或者已经排好序了)。
data ex;
input a b c;
cards;
2 2 3
2 2 3
4 5 6
4 5 6
4 5 6
4 5 6
4 4 4
4 4 5
7 7 8
9 5 5
;
run;
proc sort;
by a b c ;
run;
data ex1 ex2;
   set ex;
   by a b c;
   retain n 0;
   if first.c then output ex1;
    else output ex2;
   run;
 
这只是一个DATA步而已,并没有表现出“微操作”的特性,如果我们需要的重复记录的数据集里面的记录不重复,怎么办?是否需要在对重复记录的重复记录再操作一次?
这个问题用DATA步的微操作可以一步实现:
data ex;
input a b c;
cards;
2 2 3
2 2 3
4 5 6
4 5 6
4 5 6
4 5 6
4 4 4
4 4 5
7 7 8
9 5 5
;
run;
proc sort;
by a b c ;
run;
data ex1 ex2;
   set ex;
   by a b c;
   retain n 0;
   if first.c then do;
                      n=0;
                      output ex1;
                   end;
         n+1;
               else output ex2;
   if last.c and n ge 2 then output ex2;
   run;
 
这样的结果,好像proc sort不能一步晚成。
其实这个DATA步是一个经典的DATA步,用于很多场合,非常稳定有效,让人满意。
 
 
第四种,HASH 对象方法:
 

数据清洗DATA CLEARING之infile选项之六 与INPUT控制符号联用

碰到这样的一个源数据需要读入:
000001 p1,p2,p3
000002 p2,p3
500003 p5,p6
080004 p2,p7
008005 p3,p4,p1,p2
000006 p4,p5,p6
009007 p2,p9
000408 p10
 
目标数据集为:
000001    p1
000001    p2
000001    p3
000002    p2
000002    p3
500003    p5
500003    p6
080004    p2
080004    p7
008005    p3
008005    p4
008005    p1
008005    p2
000006    p4
000006    p5
000006    p6
009007    p2
009007    p9
000408    p10
 
源数据看起来非常的有规律,需要后一个变量对应的字段扯开。这时候会有许多的小技巧方法,有如下几种思想:
1,把后面的全部当一个字段读入到一个变量,然后一个一个拆开(用函数),然后拼成。
2,用x1-xn,把后面全部读入,没有的设置成缺失(后面再T掉),然后用数组和循环的方法修正为要求的数据集格式。
3,用矩阵分组转制,然后merge。
 
上面的两种思想都可以用代码来实现目的,但是代码是非常的不简洁,并且性能和通用性也不好。
其实SAS提供了一种可以说是专门解决这类问题的方法(在SAS公司培训资料上看过的,BASE的内容)。
data Ex;
infile cards missover dlm=’,';
input Code $6. Per $ @;
do while (Per ne ”);
output;
input Per $ @;
end;
cards;
000001 p1,p2,p3
000002 p2,p3
500003 p5,p6
080004 p2,p7
008005 p3,p4,p1,p2
000006 p4,p5,p6
009007 p2,p9
000408 p10
;
run;
 
在之前,遇到一个类似例子,也是用同样的方法解决:
data ex  ;
  infile datalines missover;
   input status  family member@;
     do while (member ne .) ;
  output;
  input member @;
  end;
datalines;
9 1 3809 3810 3811 3812
9 2 3814 3815 3816
1 3 3817 3818 3819 3820
;
 
还有一个更一般的例子,这个可能是实际中遇到真实的情况。
 
001 A 头疼 轻度 感冒 重度 ALT增高 轻度
002 B 咳嗽 轻度 过敏 轻度
003 B 感冒 轻度
004 A 感染 中度
 
实现代码是:
data ex  ;
  infile datalines missover;
   input drug group  $ res $ chdu $ @;
     do while (res ne ”) ;
  output;
  input res $ chdu $ @;
  end;
datalines;
001 A 头疼 轻度 感冒 重度 ALT增高 轻度
002 B 咳嗽 轻度 过敏 轻度
003 B 感冒 轻度
004 A 感染 中度
;
run;
 
 
结合infile的选择和行读入控制符号@(input pointer),就可以很好的解决这个问题,并且是一种通用的方法。(前面有一个列控制符号#与infile选项联用的例子:数据清洗DATA CLEARING之infile选项之四 N OBS FIRSTOBS)。
 
小结:
1,SAS提供了非常灵活的数据接口,input就是其中技术之一。infile的选项和input的各种控制符号结合,使得SAS与外界的数据接口变得很灵活很强大。其实input本身读入数据过程是一个步骤非常多的一个过程,并且SAS提供了好多的方法对其各阶段进行控制。有机会偷窥一下SAS的input机制。
2,SAS9以后提供的HASH对象方法,也可以解决这个问题,以后补充。
 

数据清洗DATA CLEARING之infile选项之四 N FIRSTOBS OBS

现有源数据,我想从第四行开始读入数据,读到300行即可:
Title = CPMG Echo Peak File
Version = 1.5
Time(ms)   —   Amplitude
0.467           5369.750
1.467           3151.469
2.467           1593.875
3.467           1504.656
4.467           2836.906
5.467           1997.172
6.467           6213.953
7.467           3965.172
.                .     
485.967        81.633 
487.100        75.186 
488.600        82.873 
490.000        84.433 
496.000        69.029 
497.167        79.925 
实现代码:
data ex1;
infile datalines firstobs=4 obs=300;
input x y;
datalines;
Title = CPMG Echo Peak File
Version = 1.5
Time(ms)   —   Amplitude
0.467           5369.750
1.467           3151.469
2.467           1593.875
3.467           1504.656
4.467           2836.906
5.467           1997.172
6.467           6213.953
7.467           3965.172
.                .     
485.967        81.633 
487.100        75.186 
488.600        82.873 
490.000        84.433 
496.000        69.029 
497.167        79.925 
;
run;
一共读入了300-4+1=297行,结果里面有297条观察值。如果要想读入300条观测值的话,那么需要设定obs值为:300-1+4=303。
 
下面我们看看下面这种情况,即一条记录值需要从多行读入才能完成,这时候需要用到选项N,还有列input pointer指示符号“#”,这样才能完整读入。
data ex;
   infile datalines n=2  ;
   input name $ 1-15 #2 @3 id;
   datalines;
J. Brooks
  40974
T. R. Ansen
  4032
A. Brooks
  40974
B. R. Ansen
  4032
C. Brooks
  40974
D. R. Ansen
  4032
E. Brooks
  40974
F. R. Ansen
  4032
J. Brooks
  40974
H. R. Ansen
  4032
I. Brooks
  40974
J. R. Ansen
  4032
K. Brooks
  40974
L. R. Ansen
  4032
;
run;
 
如果碰到需要从中间读入的情况,需要结合N,FIRSTOBS,OBS来使用。
data ex;
   infile datalines n=2  firstobs=4  obs=19;
   input name $ 1-15 #2 @3 id;
   datalines;
This is a *****
and *****
haha ,over
J. Brooks
  40974
T. R. Ansen
  4032
A. Brooks
  40974
B. R. Ansen
  4032
C. Brooks
  40974
D. R. Ansen
  4032
E. Brooks
  40974
F. R. Ansen
  4032
J. Brooks
  40974
H. R. Ansen
  4032
;
run;
 
需要注意的是obs-firstobs+1必须是N的倍数才能读入完整的(obs-firstobs+1)/N条数据,否则余数部分都不会读入的。
 
INFILE的选项FIRSTOBS,OBS分别用来指定从第firstobs行读起,然后读到obs行,是读取源数据其中的某一部分数据。
N是每一次input需要读入多少行。
 
这几个options是infile非常简单的选项,由于经常性的读入一行作为一条观察值,所以有时会误以为firstobs,obs是指定观察值的条数,这样容易出现一些小小的差错。

数据清洗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的编程语言认为是一个学习门槛很高的语言。学习过程需要花费很长的时间,是一个长期积累的过程。