2014年10月30日木曜日

SQLプロシジャ入門10:デカルト積を作る【CROSS JOIN】




デカルト積?という方も例をみるとイメージできると思います。





サンプルデータ
data DT1;
  A=1;  output;
  A=2;  output;
  A=3;  output;
run;

data DT2;
  B="AA"; output;
  B="BB"; output;
run;

データセットDT1
  A   
   1
   2
   3

データセットDT2
 B 
  AA  
  BB



方法1
proc sql;
   create table DT3 as
   select  A, B
   from   DT1 cross join  DT2 ;
quit;

データセットDT3
  A  
B
  1 
  AA   
  2
  AA
  3
  AA
  1 
  BB   
  2
  BB
  3
  BB


基本構文
 SELECT 変数1 , 変数2 ・・・
 FROM  データセット1  CROSS JOIN データセット2


解説
2つのデータセットにあるレコード組み合わせを作ってくれる。


方法2
proc sql;
   create table DT4 as
   select  A, B
   from   DT1 , DT2 ;
quit;


基本構文
 FROM  データセット1 , データセット2

解説
方法1と同じ結果になります。
カンマで区切るだけなので、書くのは楽。





2014年10月29日水曜日

UPDATEステートメントでオブザベーションをまとめる2


UPDATEステートメントでオブザベーションをまとめる」とはまた違ったテクニックを紹介。

*** サンプルデータ ;
data DT1;
   length NO PRO $20.;
   NO="001"; PRO="コタツ"; output;
   NO="001"; PRO="みかん"; output;
   NO="001"; PRO="トランプ"; output;
   NO="002"; PRO="みかん"; output;
   NO="002"; PRO="トランプ"; output;
run;
proc sort data=DT1;
   by NO;
run;

 NO  
PRO 
 001
 コタツ
 001
 みかん
 001
 トランプ
 002
 みかん
 002
 トランプ 

NO   ・・・ 顧客No
PRO ・・・ 購入商品


上のデータから、
コタツまたはみかんを買った顧客を割り出して、以下のようなデータを作りたいとします。
 顧客No.  
 コタツ買ったフラグ 
 みかん買ったフラグ 
 001
  1
  1
 002
  .
  1


*** UPDATEステートメントで、OBSをまとめる ;
data DT2;
   set DT1;
   if PRO="コタツ"   then FLG1=1;
   if PRO="みかん" then FLG2=1;
run;

data DT3;
   update DT2 (obs=0)  DT2 ;
   by NO ;
   keep NO FLG1 FLG2;
run;

 NO  
 FLG1 
 FLG2 
 001
  1
  1
 002
  .
  1


解説


①まずDT2を作るとこんな感じのデータセットができる。
 NO  
PRO 
FLG1 
FLG2 
 001
 コタツ
 1
 .
 001
 みかん
 .
 1
 001
 トランプ
 .
 .
 002
 みかん
 .
 1
 002
 トランプ 
 .
 .
FLG1, FLG2には欠損値か1が入る。


②これを以下のようにまとめたい。
 NO  
 FLG1 
 FLG2 
 001
  1
  1
 002
  .
  1


③ここで「UPDATEステートメント入門」を理解すると、
変数値を、欠損値以外の値で更新していくUPDATEステートメントの性質が利用できそうだと気付きます。

以下の構文から、、
 update マスターデータセット  トランザクションデータセット  ;
 by BY変数;
  ↓
 update マスターデータセット  DT2;
 by NO;

こんな感じでまずはデータセット名と変数名を当てはめてあげれば良さそうです。
ではマスターデータセットには何を当てはめればよいか。。

マスターデータセットはBY変数NOを持っていて、BY値が重複してたらダメというルールを満たしている必要があるので、、
update DT2(obs=0)  DT2 ;
by NO;

と書いてあげればいい。

(obs=0)でオブザベーションを読み込まない、つまりオブザベーションがなければ、少なくともBY値は重複していないので、見事ルールを満たしてる事になってくれます。
(ようはプログラムとして成立させるためのダミー)


2014年10月24日金曜日

レポート作成インターフェイス(RWI)入門2




今回はデータセットの中身をRWIで出力する方法を紹介。

(以降、HTMLへの出力が有効になっている前提)


出力するデータ
data DT1;
   V1=1; V2="AAA"; output;
   V1=2; V2="BBB"; output;
   V1=3; V2="CCC"; output;
run;

 V1  
 V2  
  1
 AAA  
  2
 BBB
  3
 CCC


RWIを使って出力

data _NULL_;

   set DT1 end=EOF;

   *** データステップの最初だけ実行する部分 ;
   if _N_=1 then do;

       * RWIを使えるようにして表作成を開始 ;
       dcl odsout ob();
       ob.table_start();

       * 項目名を出力 ;
       ob.head_start();
       ob.row_start();
            ob.format_cell(data: "V1",  style_attr: "background=blue color=white");
            ob.format_cell(data: "V2",  style_attr: "background=blue color=white");
       ob.row_end();
       ob.head_end();

   end;


   *** データの中身を出力 ;
   ob.row_start();
        ob.format_cell(data: V1);
        ob.format_cell(data: V2);
   ob.row_end();

   *** 最後に表作成を終了 ;
   if EOF then ob.table_end();
run;

結果ビューア





処理の流れ


set DT1 end=EOF;

まず、出力するデータセットDT1を読み込む。
end=EOF」で最終オブザベーションを読み込むとき一時変数EOFに「1」が入る。(この一時変数は後で使う)

 ちなみに今回は一時変数名をEOFという名前にしているけど、任意の名前で指定可能。
 

*** データステップの最初だけ実行する部分 ;
if _N_=1 then do;

    * RWIを使えるようにして表作成を開始 ;

    dcl odsout ob();
    ob.table_start();

    * 項目名を出力 ;
    ob.head_start();
    ob.row_start();

         ob.format_cell(data: "V1",  style_attr: "background=blue color=white");
         ob.format_cell(data: "V2",  style_attr: "background=blue color=white");
    ob.row_end();
    ob.head_end();

end;

オブザベーションを読み込むたびに実行する必要がない箇所、
つまり最初の1回実行するだけでいい箇所を「if _N_=1 then do;」と「end;」で囲んであげる。

今回新たに登場した「head_start」「head_endメソッド」では、囲んだ範囲をヘッダーとして扱うことが出来ます。

また、「format_cellメソッド」の「style_attr」というのは、セルのスタイル属性を設定するオプションで、今回は背景色=青、文字色=白にしてます。

セルのスタイル設定
参照名.format_cell( data: "表示するテキスト",  style_attr: "スタイル属性")

※SAS9.4より前のバージョンは「style_attr」ではなく「overrides」で動く。

使いたいスタイル属性は、SAS社の提供してるODSのリファレンスを参照ください。


*** データの中身を出力 ;
ob.row_start();
     ob.format_cell(data: V1);
     ob.format_cell(data: V2);
ob.row_end();

オブザベーション毎に変数値を出力


if EOF then ob.table_end();

一番最初に作った一時変数EOFを使って、最後に表作成を終了させる。




📝注意
記事の中で使用している「_N_」と「END=オプション」は「サブセット化IF」と一緒に使用すると正しく動かなくなりやすいです。

2014年10月22日水曜日

レポート作成インターフェイス(RWI)入門1


「Report Writing Interface」略してRWIと言われる機能がSAS9.4から追加されました。
SAS9.4より前のバージョンでも評価版として一部機能は使えます。


RWIを使うと、データステップ上で柔軟にレポートを作成することができます。
マニュアルには、出力先としてHTMLやPRINTERを選択できるよと書いてあります。


まずは簡単に、適当な表を作ってみましょう。
(以降、HTMLへの出力が有効になっている前提)


data _NULL_;

   *** ① RWIを使えるようにする **********;
   dcl  odsout ob() ;

   *** ② 表作成開始 *******************;
   ob.table_start();

      *** 1行目出力 ;
      ob.row_start() ;
          ob.format_cell( data: "1") ;
          ob.format_cell( data: "aaa") ;
      ob.row_end() ;

      *** 2行目出力 ;
      ob.row_start() ;
          ob.format_cell( data: "2") ;
          ob.format_cell( data: "bbb") ;
      ob.row_end() ;

  ob.table_end() ;

run;


結果



解説

① まず最初に「dcl odsout ob()」は「obって名前でRWIを使えるようにするよ」みたいな事をいってます。
ここはobじゃなくても好きな名前でok

② そのあとは「ob.メソッド()」で表を作る各メソッドを実行しています。
以下に各ステートメントやメソッドについてまとめました。

ステートメントとメソッドの説明
 dcl odsout 参照() 

 指定した参照名でRWIを使えるようにする 

 参照.table_start()

 表の作成開始

 参照.table_end()

 表の作成終了

 参照.row_start()

 行の作成開始

 参照.row_end() 

 行の作成終了

 参照.format_cell( data:"表示するテキスト")

 セルを作成


メソッドのざっくりしたイメージ。



























以上が、もっとも基本的な部分です。
HTMLで<table><tr><td>のようなタグを作るようなイメージに似てますね。



RWI入門記事一覧
1.基本構文
5.複数の表を好きな位置に配置する
6.改ページの設定
7.画像ファイルを配置する

2014年10月21日火曜日

SQLプロシジャ入門9:値を更新する【UPDATE】


SQLプロシジャによる変数値の更新方法を紹介。


サンプルデータ

data DT1;
   A=1; B="AA"; output;
   A=2; B="BB"; output;
   A=3; B="CC"; output;
run;

data DT2;
   A=2; B="YY"; output;
   A=3; B="ZZ"; output;
run;

データセットDT1
 A  
B
  1
  AA   
  2
  BB 
  3
  CC 

データセットDT2
 A 
B
  2  
  YY   
  3
  ZZ  



値の更新方法

方法1
proc sql;
   update  DT1
   set       B = "XX"
   where   A = 2 ;
quit;


データセットDT1
  A  
B
  1 
  AA   
  2
  XX 
  3
  CC 


基本構文
 UPDATE  対象のデータセット
 SET        更新する変数 = 格納する値
 WHERE   更新するレコードの条件


方法2
proc sql;
   update  DT1
   set       B = (select B from DT2 where DT1.A=DT2.A)
   where   exists (select * from DT2 where DT1.A=DT2.A);
quit;


データセットDT1
  A  
B
  1
  AA   
  2
  YY 
  3
  ZZ 


解説
他のデータセットを使って更新する方法。
処理内容を翻訳してみると以下のような感じになります。

 SET  B  = (DT1とDT2の変数Aがイコールになる時の、DT2の変数Bの値)
 WHERE     DT1とDT2の変数Aがイコールになるレコードを更新対象とする


間違えに注意
proc sql;
   update  DT1
   set       B = (select B from DT2 where DT1.A=DT2.A);
quit;


データセットDT1
  A  
B
  1
      
  2
  YY  
  3
  ZZ 


解説
よく間違えやすいのが、方法2でWHEREを入れ忘れてしまうケースです。
更新するレコードをWHEREで絞ってないので、DT1とDT2で変数Aがイコールになるものがない場合は欠損値が返されて、変数Bの値を欠損値として更新してしまう。



SQLプロシジャ入門記事一覧

2014年10月17日金曜日

日付値を簡単に作るMDY関数の紹介


MDY関数は「月、日、年」の順に引数を指定することで、簡単に日付値を作れます。

構文
MDY( 月, 日, 年 )


*** 例1 ;
data DT1;
  format YMD yymmdd10. ;
  YMD = mdy( 10,17, 2014 ) ;
run;


以下のように、年,月,日を分けて変数に入れてる場合も、MDY関数を使えば一発です。
*** 例2 ;
data DT2;
   format YMD yymmdd10. ;
   Y = 2014;
   M = 10;
   D = 17;
   YMD = mdy( M, D, Y ) ;
run;


覚えておくととても便利です。

2014年10月15日水曜日

全オブザベーション欠損値の変数を削除する


たとえば、以下サンプルデータで、削除したい変数はAとCです。

*** サンプルデータ ;
data DT1;
   A=.; B=1; C=""; D=1; output;
   A=.; B=1; C=""; D=2; output;
   A=.; B=.; C=""; D=3; output;
run;

 A  
 B  
 C  
 D  
  .
  1
  
  1
  .
  1
  
  2
  .
  .
  
  3

欲しい結果
 B  
 D  
  1
  1
  1
  2
  .
  3

そこで、データステップ100万回「freqプロシジャのnlevelsオプション」をヒントにプログラムを組んでみました。


*** ① 全OBS欠損値の変数を特定する ;
ods output NLevels=O_NLEVEL;

    proc freq data=DT1 nlevels;
       tables _all_ / noprint;
       format _all_;
    run;

ods output close;

*** ② 特定した変数をDROPする ;
data _NULL_;
    length  NNONMISSLEVELS 8. ;
    set  O_NLEVEL  end=EOF;
    if  _N_=1 then
          call execute( "DATA DT2; SET DT1 (DROP=" );

    if  NNONMISSLEVELS=0 then
          call execute( TABLEVAR );

    if  EOF then
          call execute( "); RUN;" );
run;


解説

①まず、FREQプロシジャのNLEVELSオプションによって下のようなOUTPUTとデータセットが出来る。

OUTPUT
変数    水準数    欠損水準    非欠損水準
----------------------------------------------
A              1           1             0
B              2           1             1
C              1           1             0
D              3           0             3

データセット
 TableVar 
 NLevels 
 NMissLevels 
 NNonMissLevels 
 A
 1
 1
  0
 B
 2
 1
  1
 C
 1
 1
  0
 D
 3
 0
  3

注目してほしいのは「非欠損水準(NNonMissLevels)」で、これが0ってことはその変数は全オブザベーションが欠損値ということですよね。


②次にプログラムを生成・実行してくれるCALL EXECUTEを使い、上で取得した非欠損水準が0の変数をDROPするプログラムを生成します。

生成されるプログラムは以下のような感じ。

DATA DT2; SET DT1 (DROP=
A
C
); RUN;


同様の処理を沢山のデータセットにやりたい場合は、このプログラムをちょっと改良してマクロ化しちゃえば楽です。

単純に全オブザベーション欠損値の変数が知りたいだけでも、FREQプロシジャ+NLEVELSの組み合わせでいけちゃうので便利です。



📝注意


・変数に format が割り当てられている場合、
formatがあてられた値に対してNLEVELSが動いてしまい、思った動きをしてくれないことがある。そのため、FREQプロシジャのとこで「format _all_」でformatを解除しておいた方が良いです。


・対象のデータセットが空(0オブザベーション)の場合、FREQプロシジャのとこでつまづいてWARNINGが出ちゃってうまく動きません。

その辺も考慮しなきゃいけない場合は、「オブザベーション数=0なら~」みたいな条件分岐する文を追記する必要があります。


・今回のテクニックの中で使用している「_N_」と「END=オプション」は「サブセット化IF」と一緒に使用すると正しく動かなくなりやすいです。
(解説記事:「サブセット化IFでありがちな落とし穴」)


2014年10月12日日曜日

SASでアニメーションを作る方法(SAS9.4以降)


SAS9.4から簡単にアニメーションを作れる面白いオプションが出来ました!

まずは適当にサンプルデータ
*** サンプルデータ ;
data DT1;
input SUBJID$ VISIT VAL;
cards;
001 1 50
001 2 200
001 3 420
002 1 150
002 2 300
002 3 480
003 1 100
003 2 360
003 3 400
;

では、適当にグラフを何個か作って、それらを繋げてGIFファイルを作ってみます。
*** GIFファイルを作る ;
options  ANIMATION=START  ANIMDURATION=0.5   PRINTERPATH=GIF ;
ods printer file="出力するパスを指定\test.gif" ;

proc sgplot data=DT1;
   scatter x=SUBJID y=VAL / markerattrs=(size=20);
   yaxis values=(0 to 500 by 100);
   where VISIT=1;
run;

proc sgplot data=DT1;
   scatter x=SUBJID y=VAL / markerattrs=(size=20);
   yaxis values=(0 to 500 by 100);
   where VISIT=2;
run;

proc sgplot data=DT1;
   scatter x=SUBJID y=VAL / markerattrs=(size=20);
   yaxis values=(0 to 500 by 100);
   where VISIT=3;
run;

ods printer close ;
options  ANIMATION=STOP ;



構文解説


options ANIMATION=START  ANIMDURATION=秒数   PRINTERPATH=GIF ;
ods printer file="作るファイルのパスとファイル名";

ANIMATION = START … アニメーションフレームの作成を開始する。
ANIMDURATION = 秒数 … 1つのフレームを表示させる時間を指定。
PRINTERPATH = GIF … 「ods printer」での出力先をGIFファイルに。

あとは、アニメーションで表示させたいものをSGPLOTなどで作ってやる。

ods printer close ;
options  ANIMATION=STOP ;

最後に、ANIMATION=STOPでアニメーションフレームの作成を終了させる。


2014年10月8日水曜日

DO i = 'A' to 'Z' ;

ちょっとした遊びです。

タイトルにあるような、アルファベット"A"から"Z"までループをまわすみたいな事って出来ないんですよね。
なので、色々工夫して実現してみたいと思います。

前置き
今回紹介するBYTE, COLLATE関数は、Windows版のSAS環境で試した結果を示しています。
環境によってASCII照合順序やEBCDIC照合順序の違いにより、今回示した結果にならないことがあります。
リファレンスや環境の確認を行ってください。




*** DO i = 'A' to 'Z'のような動作を実現 ;
data _NULL_;
   do i = 65 to 90 ;
       A = byte(i) ;
       put A ;
   end;
run;

ログ
A
B
C
D
・・・中略・・・
X
Y
Z

Windows環境にて、BYTE関数はASCII照合順序に該当する文字を返してくれます。
要は、数字と文字が対応してて、65は「A」、66は「B」、、、、90は「Z」の順であらわされる。
これをうまくDOループと組み合わせてアルファベットを順番に取得しています。



*** 'ABC・・・XYZ'という文字列を取得する ;
data _NULL_;
  A = collate(65, 90) ;
  put A;
run;

ログ
ABCDEFGHIJKLMNOPQRSTUVWXYZ


Windows環境にて、COLLATE(開始値, 終了値)で、開始値から終了値までの該当するASCII照合順序の文字を連結して返してくれます。



*** %DO i = A %to Zのような動作を実現 ;
%macro MAC;
   %do i = 65 %to 90;

       data  %sysfunc( byte(&i) ) ;
          A=1;
       run;

   %end;
%mend;
%MAC;


データステップ外でも関数を使用できるようにする%SYSFUNC関数でBYTE関数を使い、まるで「%DO i = A %to Z」と書いてるかのような動作を実現。
上の例では、AからZまでのデータセットを作成しています。


2014年10月5日日曜日

SQLプロシジャ入門8:レコードを削除する【DELETE】


SQLプロシジャによるレコードの削除方法を紹介します。



サンプルデータ

data DT1;
  A=1; output;
  A=2; output;
  A=3; output;
run;


  A  
  1
  2
  3


data DT2;
  A=2; output;
  A=3; output;
run;


  A  
  2
  3


構文1

proc sql;
  delete from DT1
  where A = 2;
quit;

DT1
 A  
  1
  3


基本構文
  DELETE  FROM 対象のデータセット
  WHERE 削除条件


構文2

proc sql;
  delete from DT1
  where A in (select A from DT2);
quit;

DT1
 A  
  1


解説
他のデータセットから選択したレコードを削除条件にすることも出来る


構文3

proc sql;
  delete from DT1;
quit;

DT1
 A  


解説
削除条件を省略すると、すべてのレコードが削除される。


ただし、SQLによる行削除には注意点あり(行削除の落とし穴を参照)



SQLプロシジャ入門記事一覧
1.変数を選択する【SELECT】
2.レコードを並べ替える【ORDER BY】
7.レコードを追加する【INSERT】
8.レコードを削除する【DELETE】
9.値を更新する【UPDATE】
10.デカルト積をつくる【CROSS JOIN】

2014年10月1日水曜日

ライブラリをいっぺんに割り当てる方法




ライブラリをいっぺんに取り消す方法とは逆に、いっぺんに割り当てる方法を紹介。


たとえば、「C:\TEST」というパスに 「A」「B」「C」というフォルダがあったとします。
これらにライブラリを割り当てたいとします。



① LIBNAMEステートメント


ライブラリを割り当てる構文
LIBNAME ライブラリ参照名 "割り当てるパス";

* ライブラリを割り当て ;
libname mylib1 "C:\TEST\A";
libname mylib2 "C:\TEST\B";
libname mylib3 "C:\TEST\C";



② LIBNAME関数


データステップ内でもLIBNAME関数でライブラリの割り当てが出来ます。


ライブラリを割り当てる構文
リターンコードを格納する変数 = LIBNAME( "ライブラリ参照名", "割り当てるパス" );

    • 割り当てが成功したか失敗したか、などの情報はログに表示してくれません。
    • その代わり、LIBNAME関数のリターンコードには以下が格納される。
      • 割り当てが成功した場合「0」
      • なんらかのERROR、WARNING、NOTEなど特記事項がある場合は「0以外」(特記事項はSYSMSGという関数で取得できる)


    * ライブラリを割り当てる ;
    data _null_;
            length msg $2000.;

            rc = libname( "mylib1", "C:\TEST\A" );
            if rc^=0 then do;
               msg=sysmsg();
               put msg;
            end;
            rc = libname( "mylib2", "C:\TEST\B" );
            if rc^=0 then do;
               msg=sysmsg();
               put msg;
            end;
            rc = libname( "mylib3", "C:\TEST\C" );
            if rc^=0 then do;
               msg=sysmsg();
               put msg;
            end;
    run;



    以下のようにDOループなどを使っていっぺんに割り当てたり、色々工夫の余地があります。

    * ライブラリを割り当てる ;
    data _null_;
          length x msg $2000.;
          do  x = "A", "B", "C";
               i + 1;
               rc = libname( cats( "mylib", i ) , cats( "C:\TEST\", x ) );
               if rc^=0 then do;
                   msg=sysmsg();
                   put msg;
               end;
          end;
    run;