2014年12月26日金曜日

今年も1年有難うございました&締めのPROC EXPLODE


今年もあとちょっとですね。
ブログを見てくださった方や、コメントくださった方、皆さん本当に有難うございました!

まだ今年も更新するかもしれませんが、来年もよろしくお願いします!

とりあえず、一度使ってみたかったEXPLODEプロシジャで締めたいと思います。

filename ft15f001 temp;

proc explode;
parmcards;
1 HAVE A GREAT
2 NEW YEAR!!
;
run;


。。。SAS Studioだと、結果がでないので、
ログ・アウトプットを外部ファイルに出力する。で紹介したPRINTTOプロシジャで、出力結果をテキストファイルとして保存し、SAS Studio上でそのテキストファイルを開くと、見れると思います。

2014年12月24日水曜日

「SAS Studio」でインデントなどを自動設定する便利機能



今回紹介する機能や画面構成などは、環境やバージョンによって変更される可能性があります。


   まず、適当にプログラムを書いて、、、



「コードのフォーマット」を押すと、




インデントが入りました!


インデントに限らず、プログラムの可読性を高めるようなコードの自動フォーマットを行ってくれるようです。


2014年12月19日金曜日

EXCELを読み込む方法と落とし穴 【DDE編】




Windows環境でDDEを使ってEXCELファイルを読み込む方法。
Unicode版だと日本語が文字化けして、うまく読み込めないようなのでご注意下さい。

ちなみに、色々な落とし穴があるので最後までご覧ください。




以下のEXCELファイルを読み込みたいとします。



C:\test.xlsx





EXCELファイルをDDEで読み込む例

* EXCELを起動 ;
options  noxwait noxsync;
%sysexec  "C:\TEST.xlsx";


* 5秒待機 ;
data _NULL_;
  rc = sleep(5,1);
run;


* EXCELデータを読み込む ;
filename EXC dde "Excel|[test.xlsx]Sheet1!C1:C4";

data OUT1;
  attrib
      A length=8.
      B length=$20.
      C length=8.   informat=yymmdd10.  format=yymmdds10.
      D length=8.   informat=time5.         format=time5.
  ;
  /* filenameで読み込む行のみを設定した場合、firstobsオプションは不要 */
  infile EXC notab dlm="09"x dsd missover lrecl=50000 firstobs=2;
  input A B C D;
run;

* ファイル参照を解放 ;
filename EXC clear;


OUT1
 
B 
 C 
 D 
   1
 1
                 .
           .
   2
 1
 2012/01/10 
 10:20 
   3
 1
                 .
           .
   4
 1
                 .
           .
   5
  
                 .
           .
   6
  
 2012/10/20
           .
   7
 1 
                 .
           .
   8
 1 
                 .
           .
   9
 aaa 
                 .
           .




解説

options noxwait noxsync;
%sysexec "C:\TEST.xlsx";

・OSのコマンドの力を借りて、EXCELファイルを開きます。
%sysexec "開きたいファイルのフルパス";

・コマンド発行時にSASが一時停止しないよう、noxwait noxsyncオプションを設定しておく。


data _NULL_;
  rc = sleep(5,1);
run;

・EXCELが開くまで後続のSASプログラムが実行されないよう「 sleep(秒数, 1) 」でSASを5秒スリープさせる。重いファイルで起動が遅い場合は、秒数を長く設定しましょう。

(この場合、sleep入れなくてもEXCELが開くまでSASプログラムの実行止めてくれるんでしたっけ?書かなくていい場合と、書かなきゃいけない場合があって、よく分からないのでひとまずsleep入れておきます)


filename EXC dde "Excel|[test.xlsx]Sheet1!C1:C4";

・読み込むEXCELの範囲をファイル参照名で定義しておく。
filename ファイル参照名  dde "Excel|[ファイル名]シート名!読み込むセル範囲"; 

今回は1~4列目を読み込む範囲に設定してます。


data OUT1;
  attrib
      A length=8.
      B length=$20.
      C length=8.   informat=yymmdd10.  format=yymmdds10.
      D length=8.   informat=time5.         format=time5.
  ;
  /* filenameで読み込む行のみを設定した場合、firstobsオプションは不要 */
  infile EXC notab dlm="09"x dsd missover lrecl=50000 firstobs=2;
  input A B C D;
run;

・attribで、読み込む変数の属性を定義しておき、infileとinputで、EXCELデータを読み込んでいきます。

「 lrecl=50000 」で、1行に読み込む長さを50000バイトに増やしてます。
この長さが足りないと、途中で文字が切れてしまう事があるので。。
ここは読み込むデータの長さに応じて、設定して下さい。

・また、今回はEXCELの1~4列目を読み込む範囲に設定しましたが、読み込みを開始したい行は、2行目からなので、「 firstobs=2 」としてます。

「 filename EXC dde "Excel|[test.xlsx]Sheet1!R2C1:R10C4"; 」というように、filenameで読み込む行も設定してる場合、firstobs は不要です。




落とし穴


① EXCELのセル内改行には注意。

たとえば1セルに
「aaa
  aaa」
と入力して、SASデータセット化して開いてみると、「aaaaaa」と入ってるように見えます。
改行きえた?と思うかもしれないけど、実は目に見えない改行が入ってます。
この見た目と中身が異なるデータを持つのは、後々面倒を起こしそうです。

対策として、EXCEL上でセル内改行を全て削除しておくか、
SASYAMAさんが紹介している 「目に見えない改行コードが邪魔をしてくる話」を参考に、SAS上で改行コードを削除する方法があります。
(データに日本語等のマルチバイト文字を含む場合、マルチバイト非対応のCOMPRESS関数ではなく、KCOMPRESS関数を利用して下さい)



② EXCELに表示されてる”見た目”をそのまま読み込んでしまうという罠。

どういう事かというと、

EXCELで長い文字を入力して、セルの書式を「文字列」に設定すると、、


以下の通り、「########…」となってしまいます。(EXCELのバージョンとかによっては、この問題は起こらない)












EXCELの仕様っぽいですが、この状態で今回のプログラムを実行すると、SASデータセットにも「#######…」という文字が入ってしまいます。
対策としては、該当セルの書式を「標準」にしてあげれば、直ります。

また、EXCEL上でフィルタや非表示の設定をしている状態でプログラムを実行すると、非表示になってる部分は読み込んでくれません。


③ 想定した通りの行を読み込めているか確認した方が良さそう。

例えば、今回の例では読み込む範囲を「Sheet1!C1:C4」として、EXCELの1~4列の範囲にあるデータを読み込むよう指定していますが、このような指定方法では、どこの行まで読み込むかを明示的に指定していないため、データが入っている範囲外の空白行を読み込んでしまうことがあった。

その他、EXCELの仕様など、不確定要素があるので、最低限、最初と最後の行が想定通りに読み込めているか確認した方が良さそうです。


2014年12月17日水曜日

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


RWIで画像ファイルを配置するメソッドを紹介。

  IMAGE( file: "画像ファイルのフルパス)



簡単な例ですが、スマホで撮った写真を配置してみます。
(以降、HTMLへの出力が有効になっている前提)

data _NULL_;

  dcl odsout ob();
  ob.image( file: "画像ファイルのパス\flower.jpg" );

run;


結果



実務では、SASで作ったグラフを、画像ファイルとして保存しておき、それを配置するってことも出来ます。


ちなみに、写真の花は堀切菖蒲園というところで撮ったやつです。
花びらがクシャッと垂れた感じがかわいいですね。



2014年12月15日月曜日

日付値と時間値から、日時値を作る小技。





DHMS関数で日時値を簡単に作ることが出来ます。



まずは 日付値、時、分、秒 から日時値を作る方法。


構文
DHMS日付値,  ,  ,   )



data DT1;
   format DT yymmdd10. H M S 8. DTM e8601dt19.;

   * 日付値 ;
   DT  = input("2014/01/10", yymmdd10.);

   * 時 ;
   H   = 20;

   * 分 ;
   M   = 10;

   * 秒 ;
   S   = 00;

   * 日時値を作成 ;
   DTM = dhms(DT, H, M, S);
run;


 DT 
S
DTM 
  2014-01-10  
  20  
  10  
  0  
  2014-01-10T20:10:00  



マニュアルにも載ってる小技ですが
日付値と時間値から、日時値を作る事も出来ます。


構文
DHMS日付値,  0,  0,  時間値 )




data DT1;
   format DT yymmdd10.  TM time5.  DTM e8601dt19.;

   * 日付値 ;
   DT  = input("2014/01/10", yymmdd10.);

   * 時間値 ;
   TM  = input("20:10", time5.);

   * 日時値を作成 ;
   DTM = dhms(DT, 0, 0, TM);
run;


 DT 
TM 
DTM 
  2014-01-10  
  20:10  
  2014-01-10T20:10:00  


2014年12月12日金曜日

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


RWIで改ページを設定するメソッド。

  PAGE()



プログラムと結果を見てみましょう。
(以降、HTMLへの出力が有効になっている前提)

data _NULL_;
  dcl odsout ob();

     *** 1ページ目 ;
     ob.table_start();
         ob.row_start();
            ob.format_cell( data:"aa" );
         ob.row_end();
     ob.table_end();

     ob.page();

     *** 2ページ目 ;
     ob.table_start();
         ob.row_start();
            ob.format_cell( data:"bb" );
         ob.row_end();
     ob.table_end();
run;

結果



レポートを作るうえで大事なメソッドです。

2014年12月10日水曜日

ATTRIBとKEEPを使った基本コンボ


大した内容じゃないですが、基本コンボを紹介。
まずATTRIBステートメントの性質から。

LABEL, LENGTH などの変数属性を、まとめて定義できるステートメント。
データステップの一番先頭に書くと、ATTRIBに指定した順番で変数が並んでくれる。



よくデータセット作成のシメとして、きちんと変数属性を定義する目的で使ってます。
例を見てみましょう。

*** 適当なサンプルデータ作成 ;
data DT1;
  retain A B C D E F 1 G "aa" H '20:00't  ;
run;

 C 
 D  
 E  
  1  
  1  
  1  
  1  
  1  
  1  
  aa  
 72000  


*** 要る変数だけ属性定義してKEEPする例 ;
data OUT1;
    attrib
       A label="あ" length=8.
       B label="い" length=8.
       C label="う" length=8.
       G label="え" length=$10.
       H label="お" length=8.   format=time5.
    ;
    set DT1;

    keep A B C G H;
run;

 C 
  1  
  1  
  1  
  aa  
 20:00 



解説
ATTRIBで変数属性を定義し、これら変数をKEEPで残しています。

ここで、ひとつ無駄がある。
KEEPする前の状態として、変数はATTRIBで指定した順番に並び、ATTRIBで指定されてないものはそのうしろに並びます。
(順番・・・A B C G H  D E F)

なので、「変数名をいっきに指定する方法 [まとめ]」のテクニックを利用してKEEPの部分を

keep A -- H ;


と省略して書いた方が楽です。

ちなみにKEEPあるあるで今回の話しに限ったことではないですが、後から保持する変数が追加になったとき、KEEPにその変数を書き忘れがちっていう、SASプログラマーあるあるにご留意を。



今回の例では変数の数が少ないので、必要性を感じづらいかもしれないですが、
実務では沢山の変数を扱うことになるので、その便利さがわかると思います。

2014年12月8日月曜日

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



RWIで、複数の表を好きな位置に配置する方法を紹介。

ods html close;
ods pdf file="出力するファイルのパス\test.pdf";

data _NULL_;
  dcl odsout ob();

  * レイアウト設定開始 ;
  ob.layout_absolute();

     * 1個目の表を配置 ;
     ob.region( x:'1cm', y:'1cm' );
     ob.table_start();
         ob.row_start();
            ob.format_cell(data:"aa");
            ob.format_cell(data:"bb");
         ob.row_end();
     ob.table_end();

     * 2個目の表を配置 ;
     ob.region( x:'5cm', y:'2cm' );
     ob.table_start();
         ob.row_start();
            ob.format_cell(data:"cc");
           ob.format_cell(data:"dd");
         ob.row_end();
     ob.table_end();

  ob.layout_end();
run;

ods pdf close;
ods html;


結果




解説


今回使うlayout_absoluteメソッドは、マニュアルにPRINTER出力のみ対応してるよと書いてあります。
なので、HTML出力を閉じておき、PDFファイルに出力します。

  ods html close;
  ods pdf file="出力するPDFファイルのフルパス";


好きな位置に配置するためのレイアウトを開始。

  layout_absolute()


表を配置する前にregionメソッドで領域の出力位置を設定。
ただ、いまいち分からないのが、例えば位置を5cmと設定して、出力されたpdfを倍率100%にして見ても5cmのところに配置されてるわけでもなさそう?。。どういう基準で配置されてるのかよく分からないです。

  region( x : 'xの位置' ,  y : 'yの位置' )


レイアウト設定を終了させます。

  layout_end()


PDF出力を閉じて出力完了。あと最後にデフォルトの出力先をHTMLにしてます。

  ods pdf close;
  ods html;


イメージとしては、スクラップブックのように、いろんな表やグラフを好きな位置にペタペタ貼る感じです。


また別の記事で、応用例(遊び)を紹介したいと思います。



RWI入門記事一覧
1.基本構文
2.データセットの中身を出力する
4.複数の表を並べる
5.複数の表を好きな位置に配置する

2014年12月5日金曜日

FREQプロシジャのLISTオプションで、変数値同士の組み合わせを見易くする。




データの組み合わせなどを確認する際に役立つ、ちょこっとしたオプションを紹介。

*** Sample Data ;
data DT1;
input CODE$ NAME$;
cards;
001 AAA
004 DDD
003 BBB
001 AAA
004 DDD
002 CCC
002 CCC
;

 CODE 
NAME
  001  
  AAA  
  004
  DDD  
  003
  BBB  
  001
  AAA  
  004
  DDD  
  002
  CCC  
  002
  CCC  


たとえば、上のサンプルで「CODEとNAMEの値の組み合わせを調べたい」ってなったとします。


そこでまずはFREQを使ってみます。

proc freq data=DT1;
   tables CODE * NAME / missing;
   format _all_;
run;




これでもいいけど、もうちょっと変数値同士の組み合わせを見やすくしたいので、、

proc freq data=DT1;
   tables CODE * NAME / missing list;
   format _all_;
run;


「LISTオプション」を追加するとリスト形式になって、確認しやすくなりました。


ちなみに今回はFREQプロシジャに以下を設定してますが、この辺は状況に合わせて設定してください。
  • 変数に欠損値があった場合、欠損値も水準に含めるため「missingオプション」を設定(組み合わせを見たい時って、欠損値も含めたいことが多いので)
  • formatをあてた値の組み合わせではなく、変数値の組み合わせを見たい場合「format _all_」で全変数にあてられたformatを解除。


📝 1つ気をつけたいのが、、

例えば、以下の例

data DT2;
   X="   AAA"; output;
   X="BBB"; output;
run;

proc freq data=DT2;
   tables X / missing list;
   format _all_;
run;



データセットDT2の1行目の変数Xの値は「   AAA」って感じで先頭に半角スペースが含まれていますが、
FREQの出力結果を見ると、この半角スペースが削除されて表示されていますよね。

プロシジャの結果がHTML形式等で表示される際、先頭の半角スペースを除く仕様みたいです。

こんな感じで、格納値の通りに表示されない場合があるので、
データクリーニングとか、ちゃんと格納値を確認したい場合は、今回の機能だけじゃなくて、FREQをかける前の元データを目視確認したり、改行・文字化け・余計なスペース等のおかしなデータを検出するチェックプログラムを作ったり、別途確認が必要です。



てことで、あんまり使わなそうな機能でも、覚えておくと今回のようにちょっとした事で役立つという話でした。
SASに無駄な機能なしです。



2014年12月4日木曜日

日時値から、日付値または時間値を抽出する



わりとこの質問を沢山もらうので、紹介したいと思います。


日時値に対して、DATEPART関数で日付値を抽出し、TIMEPART関数で時間値を抽出します。

data DT1;
   format DT yymmdd10. TM time5.;

   * 適当な日時値を作成 ;
   DTM  = input("2014/07/01 10:20:00", ymddttm.);

   * 日付値を抽出 ;
   DT   = datepart( DTM ) ;

   * 時間値を抽出 ;
   TM   = timepart( DTM ) ;

   keep DT TM;
run;


 DT 
TM 
  2014-07-01  
  10:20  


2014年12月2日火曜日

SQLプロシジャ入門13:データセットを横結合する【FULL JOIN】




横結合時に両方のデータセットの全てのレコードを残す方法を紹介。




サンプルデータ
data DT1;
  A=1; B="AA"; output;
  A=2; B="BB"; output;
run;

data DT2;
  A=2; C=10; output;
  A=3; C=20; output;
run;

DT1
 A 
B
  1  
  AA   
  2
  BB  

DT2
 A 
C
  2  
  10   
  3
  20  





FULL JOIN
proc sql;
   create table  DT3 as
   select    coalesce( DT1.A, DT2.A ) as A ,
                B ,
                C
   from      DT1  full join  DT2  on  DT1.A = DT2.A ;
quit;

  A  
 B 
  C  
  1
  AA 
 .
  2
  BB 
 10
  3
   
 20


基本構文
  from  データセット1  full join  データセット2  on  結合条件


解説
結合時、2つのデータセットの全てのレコードを残します。
 (結合条件に合うレコードだけでなく、片方のデータセットにしかないレコードも残す)


② 結合する2つのデータセットで同じ変数名を持っている場合、「データセット名.変数名」と書きます。
(今回の例では、DT1とDT2で同じ変数名Aを持っているので、どっちのAを使うのか明確にするため、「DT1.A」とか「DT2.A」と書いています)


③ SELECTでcoalesceという関数を使ってます。
この関数は、指定した引数の値を順番に見ていって、最初の非欠損値の値を返します。

つまりSELECTの「coalesce( DT1.A, DT2.A )」は、
・DT1にしか存在しないレコードだったら、DT1.Aの値を持ってくる
・DT2にしか存在しないレコードだったら、DT2.Aの値を持ってくる

という事をやっています。






13.データセットを横結合する【FULL JOIN】

2014年12月1日月曜日

変数名などの定義情報を比較する方法



以下に、似てるようで似てない2つのデータセットがあります。

data DT1;
  length A $20. B C 8.;
  A="xxx";
  B=1;
  C=2;
run;

 A  
  B   
  C  
 xxx 
  1
  2

data DT2;
  length A C $10. D 8.;
  A="yyy";
  C="zzz";
  D=3;
run;

  A  
  C   
  D   
 yyy
 zzz
  3



この2つの変数定義を比較したい場合、以下の例のようにCOMPAREプロシジャを使うと楽です。

注意:
COMPAREプロシジャには変数の順番を比較する機能はないようです (例えば片方のデータセットでは変数A, B, C の順、もう片方では変数A, C, B の順で定義されてても、定義不一致として検出してくれません)


proc compare  base=DT1(obs=0)  compare=DT2(obs=0) listvar;
run ;


結果の一部




解説


  • listvar を指定すると、一方にしかない変数名をリストアップしてくれます。
  • それと、今回比較するのは変数定義だけで、変数値の比較結果は必要ありません。なので、(obs=0) をつけてオブザベーションを読み込まないようにします。
  • ログに「同等なオブザベーションが見つかりません。」みたいなWARNINGが出てしまうけど、オブザベーションを読み込んでないから出るメッセージです。



もし厳密にWARNINGを出したくない場合は、以下のようにかけばok

proc compare  base=DT1  compare=DT2  listvar  novalues ;
run ;



novaluesで変数値の比較結果を出力しないようにします。
(といっても裏では変数値の比較をしてるので、重たいデータセットだと実行がモタつくかも)