2014年8月29日金曜日

NLDATE関数で、日付値を好きな書式で文字変換する。


以下の2つとセットの記事です。
NLTIME関数で、時間値を好きな書式で文字変換する。
NLDATM関数で、日時値を好きな書式で文字変換する。


NLDATE関数を使うと、
日付値に対して、好みの書式で文字変換できます。

構文
 NLDATE( 対象変数, 'ディスクリプタによる変換方法を指定' )

ディスクリプタ
 %Y  …  4桁の年
 %y  …  2桁の年
 %m  …  月
 %d  …  日

・ディスクリプタは大文字・小文字の違いに注意して下さい。
(例えば「%Y」と「%y」)

・第2引数はシングルクォーテーションで囲む(ダブルクォーテーションはNG)


data DT1;
  length A1 - A3 $30.;
  A = '05jan2014'd;
  A1 = nldate(A, '%Y年%m月%d日');  /* 2014年01月05日 */
  A2 = nldate(A, '%y年%m月%d日');  /* 14年01月05日 */
  A3 = nldate(A, '%y年%m月頃');      /* 14年01月頃 */
run;



上の例では「2014年01月05日」のように月と日の頭に0がつきます。
この0を取りたい場合、以下のように「%」の後に「#」を入れてやります。


data DT2;
  length A4 $30.;
  set DT1;
  A4 = nldate(A, '%Y年%#m月%#d日');  /* 2014年1月5日 */
run;



2014年8月27日水曜日

YYMMDD出力形式でスラッシュなどの区切り文字を設定する。




フォーマットでYYMMDDの末尾に以下のアルファベットを入れると、対応する区切り文字を設定できる。

S … "/"  スラッシュ
P … "."   ピリオド
B … " "  半角スペース
C … ":"   コロン
N … ""   区切りなし

data DT1;
  length A1-A6 $10.;
  A = "05jan2001"d;

  A1 = put(A, yymmdd10.);    ** 2001-01-05 ;
  A2 = put(A, yymmdds10.);  ** 2001/01/05 ;
  A3 = put(A, yymmddp10.);  ** 2001.01.05 ;
  A4 = put(A, yymmddb10.);  ** 2001 01 05 ;
  A5 = put(A, yymmddc10.);  ** 2001:01:05 ;
  A6 = put(A, yymmddn8.);    ** 20010105 ;
run;



他の区切り文字を設定したい場合は、
データステップ100万回「proc formatのpictureステートメントの話③
で紹介されてる方法を使って、思い通りの出力形式を作ることも出来ます。


2014年8月25日月曜日

ライブラリをいっぺんに取り消す方法



1つまたはすべてのライブラリを取り消す方法を紹介します。



① LIBNAMEステートメント


ライブラリを取り消す構文
LIBNAME ライブラリ参照名 CLEAR;

    すべてのライブラリを取り消す構文
    LIBNAME _ALL_ CLEAR;


      * 適当にライブラリを割り当て ;
      libname test1 "適当なパス1";
      libname test2 "適当なパス2";

      * ライブラリ「TEST2」のみ取り消す ;
      libname test2 clear;




      ② LIBNAME関数


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


      ライブラリを取り消す構文
      リターンコードを格納する変数 = LIBNAME( "ライブラリ参照名" );

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


        * 適当にライブラリを割り当て ;
        libname test1 "適当なパス1";
        libname test2 "適当なパス2";

        * ライブラリ「TEST1」「TEST2」を取り消す ;
        data _null_;
                length msg $2000.;

                rc = libname( "test1" );
                if rc^=0 then do;
                   msg=sysmsg();
                   put msg;
                end;

                rc = libname( "test2" );
                if rc^=0 then do;
                   msg=sysmsg();
                   put msg;
                end;
        run;



        以下のようにDOループなどを使ってドカッと取り消したり、色々工夫の余地があります。

        * 適当にライブラリを割り当て ;
        libname test1 "適当なパス1";
        libname test2 "適当なパス2";
        libname test3 "適当なパス3";

        * ライブラリ「TEST1」~「TEST3」を取り消し ;
        data _null_;
              length msg $2000.;
              do i = 1 to 3;
                   rc = libname( cats( "test", i ) );
                   if rc^=0 then do;
                       msg=sysmsg();
                       put msg;
                   end;
              end;
        run;

        上の例を簡単に説明しておくと、
        • DOループで変数 i の値を1~3まで変化させながら、
        • CATS関数で "test" という文字と変数 i の値を結合します。
        • その結合した文字が表すライブラリ参照をLIBNAME関数で取り消しています。


        2014年8月20日水曜日

        VERIFY(KVERIFY)関数の紹介




        あまり日の目をみることがない「VERIFY関数」にスポットライトを当ててみたいと思います。



        VERIFY関数

         VERIFY( 変数, "検索文字" )

        • 変数に対して、検索文字を含まない最初の位置(何バイト目か)を取得する。
        • 日本語などのマルチバイト文字には未対応です。



        *** サンプルデータ ;
        data DT1;
           length V1 $10.;
           V1 = "000A"; output;
           V1 = "00AB"; output;
           V1 = "ABCD"; output;
           V1 = "A000"; output;
        run;

        *** VERIFY関数を使う ;
        data DT2;
           set DT1;
           V2 = verify( V1, "ABC" );
        run;

         V1  V2  
          000A    1 
          00AB   1 
          ABCD    4 
          A000   2 

        ここではV1に、「A」「B」「C」の文字を含まない最初の位置を返しています。
        検索文字は「ABC」という文字を検索するのではなく、「A」「B」「C」と1バイトずつ検索していることに注意しましょう。



        実践例

        では、実際にVERIFYがよく使われるシチュエーション。
        サンプルデータに対して、先頭の0を取った値が欲しいとします。

         V1  V2  
          000A   A  
          00AB  AB 
          ABCD   ABCD 
          A000  A000 


        これを実現するには、正規表現を使うか、VERIFY関数がスマートなプログラムになるかなと思います。

        data DT3;
          set DT1;

          length V2 V3 $10.;

          * 正規表現を使う例 ;
          V2 = prxchange("s/0*(.*)/$1/", 1, V1);

          * VERIFY関数を使う例 ;
          _VER = verify(V1, "0");
          if  _VER >=1  then V3 = substr(V1, _VER);
        run;


        簡単な説明となりますが、

        正規表現の例では、PRXCHANGEという正規表現を使って置換処理をする関数で、先頭の0を抜いた文字に置き換えてます。

        VERIFYを使う例では、まず0以外の文字が最初に出現する位置を取得して、SUBSTR関数でその位置からの文字を抜き出しています。



        KVERIVY関数

        こちらは日本語などのマルチバイト文字に対応しています。

         KVERIFY( 変数, "検索文字" )

        • 変数に対して、検索文字を含まない最初の位置(何文字目か)を取得する。
        • VERIFY関数が「何バイト目か」を取得するのに対し、KVERIFY関数は「何文字目か」を取得する事に注意。


        *** サンプルデータ ;
        data DT3;
           length V1 $20.;
           V1 = "あいうえお"; output;
           V1 = "あおいえい"; output;
        run;

        *** KVERIFY関数を使う ;
        data DT4;
           set DT3;
           V2 = kverify( V1, "あお" );
        run;

         V1  V2  
          あいうえお    2 
          あおいえい   3

        V1に、「あ」「お」の文字を含まない最初の位置を返しています。
        検索文字は「あお」という文字を検索するのではなく、「あ」「お」と1文字ずつ検索していることに注意しましょう。



        わたし自身、VERIFY関数の面白い使い方をまだ探し中なので、こんな使い方できるよっていうのがあれば、教えてほしいです。


        2014年8月18日月曜日

        *** コメントステートメントの落とし穴 ***;



        以下のプログラムを実行してみます。

        %macro MAC;
           *  %put  あいうえお ;
        %mend;

        %MAC;

        ログ
        あいうえお


        %put あいうえお ;」の部分を実行したくないので、「*」を先頭に入れてコメントにしています。
        ですが、ログを見てみると「あいうえお」と出力されてしまいます。


        実はマクロ内で、「*  コメント  ;」という形式でコメントにしても、マクロステートメントやマクロ変数は、マクロ機能により処理されてしまうようです。


        これを回避したい場合は、「/*  コメント  */」の書式で書けばok

        /*  %put  あいうえお ; */



        ちなみに、この書き方にも注意点があるようなので、詳細は以下記事を要チェック。



        2014年8月13日水曜日

        RETAIN vs SETステートメント



        SETステートメントの落とし穴として紹介した「値の保持」「変数の初期化」の性質を利用したテクニックを紹介したいと思います。



        サンプルデータ

        data DT1;
           NO="001";  DAY=1;  POINT="100pt"; output;
           NO="001";  DAY=2;  POINT=""; output;
           NO="001";  DAY=3;  POINT=""; output;
           NO="001";  DAY=4;  POINT="150pt"; output;
           NO="002";  DAY=1;  POINT=""; output;
           NO="002";  DAY=2;  POINT="200pt"; output;
           NO="002";  DAY=3;  POINT=""; output;
           NO="002";  DAY=4;  POINT=""; output;
        run;

        proc sort data=DT1; by NO DAY; run;

         DT1
         NO  
         DAY 
         POINT 
          001
          1  
         100pt 
          001
          2  
         . 
          001
          3  
         .  
          001
          4  
         150pt
          002
          1  
         . 
          002
          2  
         200pt   
          002
          3  
         .   
          002
          4  
         .   


        サンプルデータの説明です。
        あるドーナツ屋さんがポイントカードを導入したとします。
        顧客NO毎にポイントを管理していて、来店してない日はポイントをNULLにしています。

        ここで、来店してない日を、前回来店時のポイントで埋めたいとします。

         NO  
         DAY 
         POINT2 
          001
          1  
         100pt 
          001
          2  
         100pt
          001
          3  
         100pt
          001
          4  
         150pt
          002
          1  
         . 
          002
          2  
         200pt   
          002
          3  
         200pt 
          002
          4  
         200pt

        プログラム例

        *** 1. RETAINを利用 ;
        data DT2;
           length  POINT2 $8.;
           retain  POINT2;
           set  DT1;
           by  NO ;
           if  first.NO then POINT2="";
           if  POINT ^="" then POINT2 = POINT;
        run;

        *** 2. SETの性質を利用 ;
        data DT3;
          set  DT1  DT1(obs=0 rename=(POINT=POINT2));
          by  NO ;
          if  POINT ^="" then POINT2 = POINT;
        run;

        RETAINについては、ここでは詳細を割愛します。
        詳細解説は「RETAINステートメント徹底入門」をご覧ください。


        そして、今回紹介したいSETの性質を利用した方法。
        (まず一番先頭のリンクの内容を理解しておく必要があります。)

        ポイントは上のプログラムで赤字にした箇所。
        ①「obs=0」は0行読み込むという意味、、つまり1行も読み込まない。
        ②「rename=」でPOINTをPOINT2にRENAMEしておく。

        つまりここでやりたい事は、レコードは読み込まず、PDV上にPOINT2という変数の枠を作っておく事です。

        ③次に、上のプログラムの青字で示したDT1はPOINT2という変数を持っていません。
        つまりこのデータセットからレコードを読み込むとき、PDV上のPOINT2は値の保持機能が働きます。



        以下PDV処理の一部




















        説明が難しいところなので、分かりづらいかもしれないけど、どうでしょうか。
        他にも応用がきくので、いろいろ試してみると面白いです。


        📝注意点

        今回のテクニックの中で使用している「FIRST.BY変数」は「サブセット化IF」と一緒に使用すると正しく動かなくなりやすいです。
        (解説記事:「サブセット化IFでありがちな落とし穴」)



        2014年8月12日火曜日

        %PUTステートメントの小技「&=マクロ変数名」


        SAS9.3からの機能で、小技というまでもないちょこっとした技です。

         %let  TEST = abcdef ;
         %put  &TEST ;

        ログ
        abcdef

        上記は、%PUTステートメントで、マクロ変数TESTの値をログに出力しています。


        ここで、「%PUT &=マクロ変数名;」というふうに書くと、
        ログには「マクロ変数名=値」という感じで出力してくれます。

         %put  &=TEST ;

        ログ
        TEST=abcdef


        2014年8月7日木曜日

        SETステートメントの落とし穴2「変数の初期化」


        SETステートメントの落とし穴「値の保持」の続きです。


        サンプルデータ

        data DT1;
           A="003";  B=1;  C=.;  output;
        run;

        data DT2;
           A="001";  B=1;  output;
           A="001";  B=.;  output;
           A="002";  B=.;  output;
           A="003";  B=.;  output;
        ;
        run;

        proc sort data=DT1; by A; run;
        proc sort data=DT2; by A; run;

         DT1DT2
         C  
         003 
          1  
          .  
         A  
         B  
         001 
          1  
         001
          . 
         002
          .
         003
          .


        問題
        サンプルデータより以下のプログラムを実行すると、①②どちらのデータが出来るでしょうか?

        data DT3;
           set DT1 DT2;
           by A;
           if  B=1 then C=111;
        run;

         ①

          A  
        C
          1  
          001 
          1   
         111 
          2 
          001
          . 
         111
          3 
          002
          . 
          . 
          4 
          003
          1
         111
          5
          003
          .
          .

          A  
        C
          1  
          001 
          1   
         111 
          2 
          001
          . 
         111
          3 
          002
          . 
         111
          4 
          003
          1
         111
          5
          003
          .
         111

        正解は①です!


        ポイントは「BY値が変わると、PDV内の変数値が初期化される」という事です。

        ①の3行目はBY変数Aが「001」から「002」に変わってます。そのためPDV内の変数値が初期化され変数CがNULLになったというわけです。

        4行目と5行目のBY値はともに「003」ですが、4行目で読み込んでるデータセットはDT1で、5行目はDT2から読み込んでいます。
        これは前回説明しましたが、読み込むデータセットが変わった場合もPDV内の変数値が初期化されるので、5行目のCはNULLになります。


        一言でまとめると。。「別のデータセットか別のBY値になったら初期化される」という事です。


        2回に分けて落とし穴というタイトルでまとめましたが、
        実はこの性質を理解すれば、色々面白いことが出来ちゃうので、また別の機会に紹介したいと思います。

        2014年8月5日火曜日

        SETステートメントの落とし穴1「値の保持」


        今回はわかりやすいように2回くらいに分けて紹介したいと思います。

        サンプルデータ

        data DT1;
           A="001"; B=1; C=.; output;
           A="002"; B=.; C=.; output;
           A="003"; B=1; C=.; output;
        run;

        data DT2;
           A="004"; B=.; output;
           A="005"; B=1; output;
           A="006"; B=.; output;
        run;

        proc sort data=DT1; by A; run;
        proc sort data=DT2; by A; run;

         DT1DT2
         C  
         001 
          1  
          .  
         002
          . 
          .  
         003
          1 
          .  
         A  
         B  
         004 
          .  
         005
          1 
         006
          .


        問題
        サンプルデータより以下のプログラムを実行すると、①②どちらのデータが出来るでしょうか?

        data DT3;
           set DT1 DT2;
           if  B=1 then C=111;
        run;

         ①
          A  
        C
          001 
          1   
         111 
          002
          . 
          .   
          003
          1 
         111 
          004
          .
          .
          005
          1
         111
          006
          .
          .
          A  
        C
          001 
          1   
         111 
          002
          . 
          .   
          003
          1 
         111
          004
          .
          .
          005
          1
         111
          006
          .
         111

        正解は②です!


        解説

        はじめに、内部処理の説明は経験に基づくもので独自解釈になってる可能性もあるので、あしからず。。

        内部ではまず、
        プログラムデータベクトル(PDV)という入れ物に、データを1行ずつ放り込んで処理してから、結果のデータセットDT3に出力しています。

        以下、処理の流れ。

























        赤字にした 6) と 10) が重要なポイントです。

        ・読み込むデータセットが変わった場合、PDV内の変数値が初期化される。
        ・PDV内の変数が、読み込むデータセットにない場合、前の値が保持される。



        もし①のような結果が欲しい場合は、以下のようにDT1,DT2双方にない新たな変数名に変えてあげればok。
        data DT3;
           set DT1 DT2 ;
           if  B=1 then D=111;
        run;

        (新たな変数は、次の行を読みにいく度に毎回初期化される性質を持ってるので上記であげた「値の保持」という影響をうけません。)


        次回に続く。。