2014年5月28日水曜日

色々な例数を簡単に出す2



色々な例数を簡単に出す」のデータステップ編です。



サンプルデータ
data DT1;
  label  FOOD     = "購入食品"
          PRICE    = "価格"
          EATFLG = "食べたフラグ"
  ;
  length FOOD $20.;
input  FOOD$  PRICE  EATFLG;
cards;
ジュース  150  1
カップ麺  100  .
ジュース  100  1
ジュース  200  .
カップ麺  200  .
;



求めたい結果
 購入数  ジュース購入数  カップ麺購入数  
  5  3    2



データステップで各例数を計算
data DT2;
   set DT1 end=EOF;           /* ① */

   C1 + (FOOD^="") ;          /* ② */
   C2 + (FOOD="ジュース") ;
   C3 + (FOOD="カップ麺") ;

   if  EOF;                         /* ③ */
   keep C:;
run;



解説
前に紹介した「SUMステートメント入門」と「1行プログラミング6:TRUE/FALSEを計算に利用する。」の合わせ技です。

  • ① まず「end = 一時変数名」と書くと、最終行の一時変数に1が入ります。
  • ② 条件式によって返される「1.true」「0.false」をSUMステートメントで合計していきます。
  • ③ 最終的な合計がでる最終行のみを残します。






応用
グループ毎に色々な例数や合計を出すこともできる。

たとえば、サンプルデータで以下のように「食品毎の例数や合計」を出したいとする。
 食品  購入数  合計金額   食べた食品の合計金額 
 カップ麺 2   300 0
 ジュース 3   450 250


データステップでグループ毎の例数や合計を出す。
proc sort data=DT1; by FOOD; run;

data DT3;
   set DT1;
   by FOOD;
   if first.FOOD then call missing(C1,C2,C3);   /* ① */

   C1 + (FOOD^="");                                 /* ② */
   C2 + PRICE;
   C3 + (EATFLG=1)*PRICE;

   if last.FOOD;                                        /* ③ */
   keep FOOD C:;
run;



解説
  • ① グループの各先頭行の時、計算結果を入れる変数を初期化する(call missingでNULLにする)
  • ② SUMステートメントで合計していきます。
  • ③ グループ毎の最終行のみを残します。


「C3 + (EATFLG=1)*PRICE;」のところの仕組みを補足しておくと、 (EATFLG=1) の条件が
  • 「0.false」なら、C3 + 0*PRICEとなり、
  • 「1.true」なら、C3 + 1*PRICEとなり、
「true/false」によって合計に含めるかどうかのトリガーにしています。



📝注意点

今回のテクニックの中で使用している「END=オプション」「FIRST.BY変数」「LAST.BY変数」は「サブセット化IF」と一緒に使用すると正しく動かなくなりやすいです。



2014年5月26日月曜日

SUMステートメント入門




質問を受けたことがあったので、まとめたいと思います。
どいうものかは説明よりプログラムを見てしまったほうが早いかも。


*** サンプルデータ作成 ;
data DT1;
    A=.   ;  output;
    A=10 ;  output;
    A=20 ;  output;
run;

 A 
  . 
 10
 20 

*** SUMステートメントを使う ;
data DT2;
    set DT1;
    NO + 1;
    A2 + A;
run;

 A   NO   A2 
  .    1     0 
 10   2  10
 20    3   30 



解説

変数名 + 足す値 ;

このイコールを省略した書き方がSUMステートメントです。
変数の値を次レコードにも保持してくれて、どんどん足し合わせていくことができます。


注意点として、データステップ内でSUMステートメント用の変数を新たに用意して、その変数にSUMステートメントを行ってください。
仕組み上、データセットに既に存在している変数だとSUMステートメントがうまく動作しません。



サンプルでは以下の動きをしています。
・「NO+1」は、変数NOに対して上から順に1を足していってます。
・「A2+A」は、変数A2に対して上から順に変数Aの値を足していってます。



ちなみに、RETAINを使って以下のように書いたときと同等です。

data DT2;
    retain NO A2 0;
    set DT1;
    NO = sum(NO,1);
    A2 = sum(A2,A);
run;



2014年5月23日金曜日

1行プログラミング6:TRUE/FALSEを計算に利用する。




たとえばですが、
ある薬を投与してから、とあるイベントが発生するまでの日数を求めたいとします。

ロジックは

日数 = イベント発生日 - 投与開始日 + 1

ただし投与前に起きたイベントは、「イベント発生日 - 投与開始日」 とする。
(投与前日のイベントだったら、+1すると日数=0となって変なので。。)



これをプログラムで書くと、通常は以下のような感じですが、、

DAY = EVENT - START +1;
if  EVENT < START  then  DAY = EVENT - START ;



真偽値(True/False)を使うと1行でスッキリ書けちゃいます。

DAY = EVENT-START + (EVENT >= START) ;



解説

式の中の(EVENT >= START)がポイントで、この条件が成り立つと「1:True」、成り立たないと「0:False」が返されるのを利用して、1を足すかどうかを判断させてます。



2014年5月20日火曜日

行削除の落とし穴




行(オブザベーション)を削除する方法はいくつかありますが、その方法によっては注意が必要です。



サンプルデータ
data DT1 DT2 DT3;
   do A=10 to 30 by 10;
       output;
   end;
run;

データセットDT1~DT3

 A 
  1  10 
  2  20 
  3  30 



まずは色々な方法で行を削除してみる。
* ①DELETEステートメント ;
data DT1;
   set DT1;
   if A=10 then delete;
run;

* ②MODIFY,REMOVEステートメント ;
data DT2;
   modify DT2;
   if A=10 then remove;
run;

* ③SQLのDELETE ;
proc sql;
   delete from DT3 where A=10;
quit;


データセット
DT1

 A 
  1 20 
  2 30
DT2

 A 
  2 20 
  3 30
DT3

 A 
  2 20 
  3 30


出来たデータセットを開いてみると上記のような感じです。いっけん同じに見えるけど、注目してほしいのはデータセットの左側に表示される行番号。
DT2とDT3は「2」から始まってますね。

1行目が削除されても行番号が削除前と変わっていないのです。
このように使用できる行番号が歯抜け状態になってしまうことでちょっとした問題を起こす場合があります。




問題を起こすケース


行削除後の行数取得。
data _null_;
   set DT2 nobs=NOBS;
   put "行数= " NOBS;
   stop;
run;

ログ
行数= 3

NOBS=を使って行数を取得しログに出力しています。
本来は「行数= 2」となるはずですよね。


POINT=を使った行の読み込み。
data DT4;
   do X=1 to NOBS;
      set DT2 nobs=NOBS point=X;
      output;
   end;
   stop;
run;

データセットDT4

 A 
  1 . 
  2 20 
  3 30 

POINT=で読み込む行番号を変数Xの値によって指定してます。
結果を見ると、1行目が欠測値になってズレちゃってますね。



対策

もしSQLのDELETEやREMOVEステートメントで行を削除してたら、、、以下のようにSETし直せばOK。

data DT2;
   set DT2;
run;


data _null_;
   set DT2 nobs=NOBS;
   put "行数= " NOBS;
   stop;
run;

ログ
行数= 2



2014年5月16日金曜日

SQLプロシジャ入門6:データセットを作成する【CREATE TABLE】


まだあとちょっと続きます。
しかし今回までの基礎を身につければかなり役に立つと思います。

今回はSQLの結果をデータセットに出力する方法です。



サンプルデータ
data DT1;
input A B;
cards;
1 10
1 15
2 20
2 10
;
 A 
B
  1  
  10   
  1
  15  
  2  
  20   
  2  
  10   




結果をデータセットに出力する。[CREATE TABLE]
proc sql;
   create table  DT2  as
   select *
   from DT1;
quit;



 A 
B
  1  
  10   
  1
  15  
  2  
  20   
  2  
  10   


解説
「CREATE TABLE 出力データセット名 AS」 で結果をデータセットに出力します。





SQLプロシジャ入門記事一覧
1.変数を選択する【SELECT】
2.レコードを並べ替える【ORDER BY】
6.データセットを作成する【CREATE TABLE】

2014年5月13日火曜日

CONTENTS,DELETEプロシジャでは「ライブラリ名._ALL_」という書き方が出来る。



CONTENTSとDELETEプロシジャではDATA = ライブラリ名._ALL_という書き方があるのをご存知でしょうか。
どういったものか、例をみていきましょう。



使用するダミーデータを作成

data DT1 DT2 DT3;
  A=1;
run;



「ライブラリ名._ALL_」と指定してみる

*** 1. CONTENTSプロシジャの例 ;
proc contents data=WORK._ALL_ out=OUTC  noprint;
run;

・指定したライブラリにある全てのデータセットとVIEWのコンテンツ情報を取得して、データセットに出力することが可能。



*** 2. DELETEプロシジャの例 ;
proc delete  data=WORK._ALL_;
run;

・指定したライブラリにある全データセットを削除することが可能。





2014年5月7日水曜日

SQLプロシジャ入門5:集計後にレコードを抽出する【HAVING】



5回目はHAVINGです。
WHEREと同じでレコードを抽出する機能なんだけど、抽出するタイミングが異なります。




サンプルデータ
data EXAM;
input NAME$ SUBJECT$ SCORE FLG;
cards;
一郎 国語 20 .
次郎 国語 50 .
三郎 国語 100 1
花子 国語 20 .
一郎 算数 80 .
次郎 算数 30 .
三郎 算数 10 1
花子 算数 99 .
;

NAME
SUBJECT
SCORE
FLG
 一郎  
 国語   
 20  
  .  
 次郎  
 国語   
 50
  .  
 三郎
 国語   
 100  
  1  
 花子  
 国語   
 20  
  .  
 一郎  
 算数   
 80  
  .  
 次郎  
 算数   
 30  
  .  
 三郎
 算数   
 10  
  1  
 花子
 算数   
 99  
  .  

上の例は、各生徒の科目別テストの点数を表してます。
ただし、カンニングした生徒(FLG=1)がいる。




1. 点数が50点未満のレコードを抽出する【HAVING】
proc sql;
   select  *
   from  EXAM
   having  SCORE < 50 ;
quit;


結果ビューア
 NAME 
SUBJECT
SCORE
FLG
 一郎  
 国語   
 20  
  .  
 花子  
 国語   
 20  
  .  
 次郎  
 算数   
 30  
  .  
 三郎
 算数   
 10  
  1  


解説
・HAVINGで抽出条件を指定。
・この例ではWHEREと同じに見えてしまうけど、違いは以下の通り。

  WHERE ・・・ FROMで指定したデータに対する抽出条件
  HAVING ・・・ 集計後のレコードへの抽出条件

具体例は次の例で。



2. 平均が50点未満の科目を調べたい(ただしカンニングした点数は含めない)
【WHERE、GROUP BY、HAVING】
proc sql;
   select     SUBJECT,  avg(SCORE) as AVG_SCORE
   from        EXAM
   where      FLG = .
   group by  SUBJECT
   having     AVG_SCORE < 50 ;
quit;

結果ビューア
SUBJECT
AVG_SCORE
 国語   
 30  


解説
・SELECTにあるAVG関数は平均を求める集計関数。
・具体的な各句の役割は以下の通り。

 ①FROMとWHEREで、データEXAMからカンニングしてないレコードを抽出
 ②SELECTとGROUP BYにより、科目毎の平均点を計算
 ③HAVINGを使って、科目毎の平均点が50点未満のレコードを抽出


このようにグループ化して集計を行ったデータに対して抽出が行えます。




SQLプロシジャ入門記事一覧
1.変数を選択する【SELECT】
2.レコードを並べ替える【ORDER BY】
4.グループ毎に集計する【GROUP BY】
5.集計後にレコードを抽出する【HAVING】
6.データセットを作成する【CREATE TABLE】
7.レコードを追加する【INSERT】
8.レコードを削除する【DELETE】