2014年11月28日金曜日

LIBNAMEを使わずに永久データセットを取り扱う小技



LIBMAMEを使わずに、永久データセットを扱う方法があります。


以下は パス「C:\TEST」 の中にDT1というデータセットがあったとして、それをWORKにSETする例です。

libname MYLIB "C:\TEST";

data DT1;
   set MYLIB.DT1;
run;

これが普通の書き方ですが、
実は以下のように、フルパスを指定することも出来ちゃいます。

data DT1;
   set "C:\TEST\DT1";
run;


一時的な作業であれば素早く書けて便利です。
がっつりプログラムを書く場合は、libnameでパスを通す基本的な書き方のほうが、プログラムとしてはキレイなのでそっちを使うほうがいいかと思います。



2014年11月25日火曜日

WHEREステートメントのLIKE演算子でエスケープ文字を設定する。



まずWHEREステートメントのLIKE演算子について、

データステップ100万回「whereステートメントでのみ使用できる演算子_contains,between,like
で分かり易い説明があるのでご覧ください。



そして、今回紹介するエスケープ文字の設定について。

Sample data
data DT1;
   VAR = '100%'; output;
   VAR = '100'; output;
run;

VAR
  100% 
  100


例えば上のデータから、末尾に「%」という文字が含まれるやつを抽出したい場合、

data DT2;
   set DT1;
   where VAR like '%#%' escape '#';
run;

VAR
  100% 

というように書けます。

escape '#'」で、「#」という文字をエスケープ文字として設定し、
この「#」の次に書かれた「%」をワイルドカードではなく、単なる文字としての「%」に変えています。


エスケープ文字は、「$」とか「\」とか自分で適当に設定できます。

ちなみに、
本題とそれるけど例えば「where VAR like "%abc"」みたいに、検索文字列をダブルクォーテーションで囲むと、検索文字列中の「%abc」がマクロとして解釈されてしまう。
マクロとして解釈されたくない場合は、検索文字列をシングルクォーテーションで囲むこと。



2014年11月21日金曜日

ディレクトリ内のファイル名とフォルダ名を取得する(DOPEN関数編)



たとえば、「C:\TEST」からファイル名とフォルダ名を取得したいとします。



filename DIR1 "C:\TEST";

data DT1 ;
   length  VAR  $400 ;

   *** ディレクトリを開く ;
   did = dopen("DIR1") ;

   *** ファイル・フォルダ名を変数に格納 ;
   do i = 1 to dnum( did ) ;
       VAR = dread( did , i ) ;
       output ;
   end;

   rc = dclose( did ) ;
run;

 VAR  
  aaa 
  bbb.txt 
  ccc.xlsx  



解説

① DOPEN関数でディレクトリを開きます。

構文 … DOPEN( "開きたいディレクトリが割り当てられたファイル参照名" )

戻り値として開いたディレクトリの識別番号が返されるので、その番号を変数didに入れてます。



② DNUM関数でディレクトリ内のメンバ数を取得できます。

構文 … DNUM( ディレクトリの識別番号 )



③ DREAD関数でディレクトリ内のメンバ名を取得できます。

構文 … DREAD( ディレクトリの識別番号 , メンバの通し番号 )

つまりファイル・フォルダの数だけDOループをまわして、ディレクトリ内の全ファイル・フォルダ名を取得しています。



④ DCLOSE関数でディレクトリを閉じます。

構文 … DCLOSE( ディレクトリの識別番号 )




2014年11月19日水曜日

SQLプロシジャ入門12:データセットを横結合する【LEFT,RIGHT 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  




①LEFT JOIN
proc sql;
   create table  DT3 as
   select    DT1.A , B , C
   from      DT1  left join  DT2  on  DT1.A = DT2.A ;
quit;


データセットDT3
  A  
 B 
  C  
  1
  AA 
 .
  2
  BB 
 10


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


解説
・FROMで結合する時、左側に書いたデータセットDT1のレコードだけ残す。
・DT1とDT2で同じ変数名Aを持ってるので、どっちのAを使うのか明確にするため、「DT1.A」とか「DT2.A」と書いてあげる必要がある。




②RIGHT JOIN
proc sql;
   create table  DT4 as
   select    DT2.A , B , C
   from      DT1  right join  DT2  on  DT1.A = DT2.A ;
quit;


データセットDT4
  A  
 B 
  C  
  2
  BB 
 10
  3
   
 20


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


解説
・FROMで結合する時、右側に書いたデータセットDT2のレコードだけ残す。




12.データセットを横結合する【LEFT,RIGHT JOIN】


2014年11月17日月曜日

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


RWIで複数の表を並べる方法を紹介します。
まずは以下のプログラムを実行して結果を見てみましょう。

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

data _NULL_;
   dcl odsout ob();

   * グリッドを設定する ;
   ob.layout_gridded( columns:2, rows:2 );

       * 1個目の表を配置 ;
       ob.region();
       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();
       ob.table_start();
          ob.row_start();
               ob.format_cell(data:"cc");
               ob.format_cell(data:"dd");
          ob.row_end();
       ob.table_end();

       * 3個目の表を配置 ;
       ob.region();
       ob.table_start();
          ob.row_start();
               ob.format_cell(data:"ee");
               ob.format_cell(data:"ff");
          ob.row_end();
       ob.table_end();

   ob.layout_end();
 run;

結果



解説


まずは、表を配置する列数と行数を設定。
  layout_gridded( columns:列数 , rows:行数  )


そしたら、表を配置する前に以下のメソッドを書くだけです。
  region()


最後にレイアウト設定を終了させます。
  layout_end()



例えば列2×行2のレイアウトを作って表を配置するイメージはこんな感じ。














regionメソッドを書く度、左から右、次の行の左から右に、、、と表をどんどん配置していきます。


ちなみに。。

長くなるので紹介は出来ませんでしたが、imageメソッドというのがあり、表だけじゃなくグラフなどのイメージも配置できちゃうので、色々遊べそうです。

2014年11月14日金曜日

引用符で囲んだ文字が長すぎる場合のWARNINGを出さないようにする方法


まずは以下のプログラムを実行してみてください。


data DT1;
A = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab";
run;

ログ
WARNING: 引用符で囲んだ文字列が 262 文字を超えているか、または引用符が足りません。


WARNINGが出てしまいました。
クオーテーションで囲った文字が長すぎる場合、「長すぎるよ、まさかクオーテーションで閉じるの忘れてない?」みたいなSASの親切心でWARNINGをだしてくれます。

このメッセージを表示しないようにするにはNOQUOTELENMAXオプションを設定します。
「NOQUOTELENMAX」でメッセージを抑制し、
「QUOTELENMAX」でメッセージを表示するように戻します。



options noquotelenmax;

data DT1;
A = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab";
run;

options quotelenmax;


上記のように、このオプションは使ったらすぐに設定を戻すようにしましょう。
本当にクオーテーションの閉じ忘れがあった時、気づきにくくなるので。

2014年11月13日木曜日

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




SQLプロシジャで横結合する方法を紹介していきます。
まずは、2つのデータセットを横結合して、指定した結合条件に合致するレコードのみを残す方法。




サンプルデータ
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  



方法1
proc sql;
   create table  DT3 as
   select    DT1.A , B , C
   from      DT1, DT2
   where    DT1.A = DT2.A ;
quit;

データセットDT3
  A  
 B 
  C  
  2
  BB 
 10

基本構文
 from  データセット1 , データセット2
 where  結合条件


解説
・WHEREによって、変数Aの値が共通するレコードだけを残すようにしてます。
・DT1とDT2で同じ変数名Aを持ってるので、どっちのAを使うのか明確にするため、「DT1.A」とか「DT2.A」と書いてあげる必要がある。




方法2
proc sql;
   create table  DT4 as
   select    DT1.A , B ,  C
   from      DT1  inner join  DT2  on  DT1.A = DT2.A;
quit;


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


解説
結果は方法1と同様。




11.データセットを横結合する【INNER JOIN】
13.データセットを横結合する【FULL JOIN】
14.データセットを縦結合する【UNION】
15.NULLの扱いに関する注意点


2014年11月12日水曜日

外部ファイルやフォルダが存在するか確認する方法




ファイルやフォルダの存在確認をするには、FILEEXIST関数を使うと便利です。

ファイルやフォルダに対してアクセス制限など、なんらかの制限をかけている場合、
FILEEXIST関数がどのような挙動をするのかは未確認です。
そのようなケースが想定される場合は、リファレンスの確認や動作確認を行ってください。



構文
リターンコードを格納する変数 = FILEEXIST( "確認したいフォルダまたはファイルのフルパス" );

  • 存在する場合は「1」、存在しない場合は「0」がリターンコードとして返されます。
  • 存在するか、しないかなどの情報はログに表示してくれません。
    • その代わり、存在しなかった場合の警告や処理のエラーをSYSMSG関数で取得できる。


data _null_;
        length msg $2000.;

        rc = fileexist("D:\myfolder\test.txt");
        if rc^=1 then do;
           msg=sysmsg();
           put msg;
        end;   
run;



2014年11月9日日曜日

%SYSFUNC関数の小技


ちょっとした小技を紹介。
(%SYSFUNCって何って方は「データステップ外で関数を使えるようにする「%SYSFUNC」その1」を参照)

たとえば、DATE関数を使って、ログに日付を表示したいとします。
そこで以下のように書いたとします。

%put   %sysfunc(date());

 ログ
 19782


ログには「19782」と日付値が表示されてしまうので、yymmdd10のFORMATをあてる必要があります。

%put   %sysfunc(putn( %sysfunc(date()) , yymmdd10));

 ログ
 2014-02-28


今度は正しく出ました。
けど、実はもっとスマートに書くことができて、、
%SYSFUNC関数の第2引数に、あてたいFORMATを指定することが出来ます

%put %sysfunc(date(), yymmdd10);

 ログ
 2014-02-28


この隠れ第2引数の存在はあまり知られてないと思います。

2014年11月6日木曜日

RWIでカレンダーを作ってみる。


遊び回です。
RWIで、カレンダー作ってみました。

style_attrで細かい書式設定をしてるため、ごちゃごちゃして見えますが、そこを見なければプログラムとしてはシンプルな構造になってます。


*** 年月を指定 ;
%let YEAR = 2014;
%let MONTH = 11;


*** RWIでカレンダーを作る ;
data _NULL_;

  * 月の開始日と終了日を取得 ;
  STDT = mdy(&MONTH, 1, &YEAR);
  ENDT = intnx("month", STDT, 0, "end");

  * カレンダー作成開始 ;
  dcl odsout ob();
  ob.table_start();

     * 月を出力 ;
     ob.head_start();
     ob.row_start();
          ob.format_cell( data: put(STDT,monname20.), column_span:7,
                                 style_attr: "background=midnightblue  color=white
                                                  fontsize=10  fontweight=bold  just=left");
     ob.row_end();

     * 曜日を出力 ;
     ob.row_start();
          do i = 1 to 7;
              ob.format_cell( data: char("SMTWTFS",i) ,
                                    style_attr: "background=skyblue color=white fontsize=5") ;
          end;
     ob.row_end();
     ob.head_end();

      * 日付を出力 ;
     ob.row_start();
          do i = STDT to ENDT;

              * 1日が日曜以外であれば空白を埋める ;
              if i=STDT then do i2=1 to weekday(STDT)-1;
                  ob.format_cell();
              end;

              * 日付を出力 ;
              ob.format_cell( data:day( i ) ,  style_attr:"height=2cm width=2cm fontsize=5;");

              * 土曜日であれば折り返す ;
              if weekday( i )=7 and i^=ENDT then do;
                   ob.row_end();
                   ob.row_start();
              end;

          end;
     ob.row_end();

  ob.table_end();
run;

結果




補足

・大体以下で紹介した事をもとにしてます。
レポート作成インターフェイス(RWI)入門1
レポート作成インターフェイス(RWI)入門2
レポート作成インターフェイス(RWI)入門3


・SAS9.4より前のバージョンでは、以下を変える必要あり。

 1) format_cellメソッドの「style_attr」を「overrides」に置き換える。
 2) SAS9.2ではプログラム先頭に「ods html file="出力パス\ファイル名.html";」、最後に「ods html close;」と書く。
 3) 出力する年月を変えたい場合は、プログラム先頭の以下の箇所を変えてあげる。

*** 年月を指定 ;
%let YEAR = 2014;
%let MONTH = 11;


・PDFなどに出力して印刷したい場合は、プログラムの先頭と最後に以下の文を挿入すればok。
 先頭: ods pdf file="結果を保存するパスを指定\SAS_Calendar.pdf";
 最後: ods pdf close;


2014年11月4日火曜日

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


今回はRWIで、セル結合する方法を紹介。

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


横結合する構文 :  format_cell( column_span: 結合する数 )

data _NULL_;

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

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

          * 2行目を出力 ;
          ob.row_start();
              ob.format_cell(data:"cc", column_span:2);
          ob.row_end();

    ob.table_end();
run;

結果


縦結合する構文 :  format_cell( row_span: 結合する数 )

data _NULL_;

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

          * 1行目を出力 ;
           ob.row_start();
                ob.format_cell(data:"aa");
                ob.format_cell(data:"bb", row_span:2);
           ob.row_end();

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

    ob.table_end();
run;

結果



実際の使用例

*** サンプルデータ ;
data DT1;
    A="001"; B="AA"; output;
    A="001"; B="BB"; output;
    A="002"; B="CC"; output;
    A="002"; B="DD"; output;
    A="002"; B="EE"; output;
run;

  A  
B
  001 
  AA   
  001
  BB
  002
  CC
  002 
  DD  
  002
  EE

上のサンプルから、RWIを使って以下のように出力してみます。
(Aの値が同じものをセル結合させる)









*** ① Aの値が同じもの同士の数を求める ;
proc sort data=DT1;
    by A;
run;

proc sql;
    create table DT2 as
    select A, count(*) as _COUNT
    from DT1
    group by A;
quit;

  A  
 _COUNT 
 001 
  2
 002
  3


*** ② RWIでセル結合した表を作る ;
data _NULL_;

    merge DT1 DT2 end=EOF;
    by A;

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

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

        * 項目名を出力 ;
        ob.head_start();
        ob.row_start();
            ob.format_cell(data:"A");
            ob.format_cell(data:"B");
        ob.row_end();
        ob.head_end();
    end;

    *** データの中身を出力 ;
    ob.row_start();
        if  first.A then  ob.format_cell( data:A, row_span: _COUNT);
        ob.format_cell(data:B);
    ob.row_end();

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


ざっくり解説
① まずAの値が同じもの同士の数を求める。
方法はDATAステップでも何でもいいけど、とりあえずSQLで求めてます。

② 次にRWIで表を作ります。ポイントは以下。

if  first.A then  ob.format_cell( data:A, row_span: _COUNT);

BY変数Aの値が同じもの同士の先頭行で、format_cellメソッドを実行。
そこで先にSQLで求めた変数_COUNTをセル結合する数として指定してます。


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