2016年5月2日月曜日

日付から年や月などを取り出す




日付値から 「年,月日」 を取り出す

data DT1;
  format A yymmdd10.;
  A = '05jan2014'd;
  A1 = year(A);      * 年 ;
  A2 = month(A);   * 月 ;
  A3 = day(A);        * 日 ;
run;

 A
A1
  A2   
  A3   
 2014-01-05  
  2014  
    1 
    5



日時値から 「年秒」 を取り出す


日時値から年・月・日を取り出すには、DATEPART関数で日付値に変換してからYEAR関数等を使用します

当然ながら、時・分・秒を取り出す場合は、日時のままでok (DATEPART関数は不要)

data DT2;
  format A e8601dt19.;
  A = '05jan2014 10:20:8'dt;
  A1 = year( datepart(A) );      * 年 ;
  A2 = month( datepart(A) );   * 月 ;
  A3 = day( datepart(A) );       * 日 ;
  A4 = hour(A);                      * 時 ;
  A5 = minute(A);                  * 分 ;
  A6 = second(A);                  * 秒 ;
run;

A
A1
 A2 
 A3 
 A4 
 A5  
 A6 
 2014-01-05T10:20:08 
  2014 
   1 
   5
  10 
  20
   8



1つ気をつけた方が良いのは、日時にミリ秒を含む場合。

注意:ミリ秒を含む場合

data DT3;
  format A e8601dt23.3;
  A = '05jan2014 10:20:8.721'dt;
  A1 = second(A);
run;

 A
A1
 2014-01-05T10:20:08.721 
  8.72099995・・・以下省略  

秒にミリ秒を含める場合は、「秒+(ミリ秒÷1000)」って感じになると思いますが、
SECOND関数で取得したミリ秒部分(小数点以下)に誤差?が出てしまうのでご注意ください。


2016年4月28日木曜日

SPDE (Scalable Performance Data Engine) の触りと、暗黙ソートについて



SPDエンジンについて、まずはリファレンスを参照ください。

https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/engspde/p0aw8yr81ntdapn1xw6lpn20o1lg.htm


上記リファレンスの説明をかいつまむと、


 ・ high-performance data delivery用に設計
 ・ データを合理化されたファイル形式に編成
 ・ 並列処理
 ・ 非常に大きなデータセットの処理を高速化。


って感じで、分かったような分からないような感じですが、とにかく使ってみましょう。
触った感じ、Base SASエンジンで使えるオプションが一部SPDエンジンで使えなかったり、逆にSPDエンジン専用のオプションが用意されてたりします。

(たとえばfirstobs=オプションはSPDエンジンに無いっぽい。)



とにかく使ってみる。

libname sp spde "任意のパス";

data sp.DT1;
input A B;
cards;
2 11
1 22
1 33
2 44
;
run;

作成されたファイル


SAS上で見るとデータセットDT1がひとつ作られたように見えますが、エクスプローラで見ると上記のようにファイルが2つ出来ています。

これらはコンポーネントファイルといって、定義部分とデータ部分に分かれてファイルが作られている事を意味します。
さらにインデックスを定義していればインデックス用のコンポーネントファイルも作られます。



何気に便利な暗黙ソート

普通BYステートメントを含む処理は、事前にSORTプロシジャで並べ替えとく必要がありますが、SPDエンジンの場合はそれが不要なんです!

BYSORT= LIBNAMEステートメントオプション
https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/engspde/p1oeyuoie0h44vn11fxanawndqwz.htm



libname sp spde "任意のパス";

data sp.DT1;
input A B;
cards;
2 11
1 22
1 33
2 44
;
run;

proc means data=sp.DT1;
  var B;
  by A;
run;

proc transpose data=sp.DT1 out=sp.OUT1;
  var B;
  by A;
run;

変数Aの順に並んでないのに、「by A;」を含むMEANSやTRANSPOSEが動いちゃいます。

おまけにユーティリティファイルスペースを使用して並び替えられるので、DT1は上書きされず元の並び順のままです。


ちなみに、データステップやプロシジャの出力先にご注意下さい。
(例えば、上の例のTRANSPOSEで「OUT=」にBase SASエンジンのライブラリを出力先にした場合、Base SASエンジンのデータセットとして出力されます。
その辺の挙動もご留意下さい。



以上、触りなのでこの辺までとします。
実際に利用する場合は、細かい設定や注意点などがあるので、まずはリファレンスをご確認下さい。


2016年4月26日火曜日

プログラムスリム化計画 【COALESCE関数を活用する】


覚えたら絶対役に立つCOALESCE関数を紹介。


構文


  COALESCE関数( 引数1 , 引数2 ・・・ )


この関数は引数のうち最初に欠損値以外で登場する値を返してくれる。
引数が全て数値の場合「COALESCE」、全て文字の場合「COALESCEC」と使い分ける必要がある。


それではどんな時使えるのか、簡単な例を示します。

サンプルデータ

data DT1;
length YN 8. A B C $10.;
input YN A$ B$ C$;
cards;
0 . aa bb
1 . . cc
. . . .
;
run;

  YN   
  A   
  B   
  C  
   0      aa    bb  
   1         cc  
   .           


例①

サンプルデータに、有無変数YN 「0:なし、1:あり」 があります。
この変数が欠損値だったら 「99:不明」 と置き換えた変数YN2を作りたいとします。

  YN   
  YN2   
   0    0
   1    1
   .   99


通常以下のように書きますが、、
data OUT1;
   set DT1;
   if YN^=. then YN2 = YN;
   else YN2 = 99;
   keep YN YN2;
run;


COALESCE関数を使えば、一発です。
data OUT2;
   set DT1;
   YN2 = coalesce( YN, 99 );
   keep YN YN2;
run;





例②

以下のロジックで変数Xを導出したいとします。

・ Aに値があれば X =  A
・ 上記以外で、Bに値があれば X = B
・ 上記以外で、Cに値があれば X = C
・ 上記以外(すべて欠損値)なら X = "XXX"

  A  
  B   
  C   
  X  
     aa    bb    aa  
        cc    cc  
         XXX 

通常以下のような書き方になりますが、、
data OUT3;
   set DT1;
   length X $10.;
   if A^="" then X = A;
   else if B^="" then X = B;
   else if C^="" then X = C;
   else X = "XXX";
   keep A B C X;
run;


これも一発で書けます。
data OUT4;
   set DT1;
   length X $10.;
   X = coalescec( A, B, C, "XXX" );
   keep A B C X;
run;



他にも以下のような使い方など、工夫次第で色々な使い方ができる超便利な関数です。
変数指定は「V100-V1」のように逆にもできる。


2016年4月22日金曜日

プログラムスリム化計画 【LEFT、TRIM関数を使わない】



* Sample data ;
data DT1;
   length A B $10.;
   A = "aa";
   B = "b b";
run;

上のデータから以下の変数を作りたい。
  •  文字変数 A・B を結合した変数X
  •  結合時、各変数の両脇の半角スペースを除去する。


昔は以下のようにLEFT/TRIM関数を使用していました。
  
* 昔の書き方 ;
data OUT1;
   length X $20.;
   set DT1;
   X = trim(left(A)) || trim(left(B));
run;
 X   
 A   
B  
 aab b  aa  b b  


この昔から定石として書かれているプログラムは、以下のようにスリム化できます。

* プログラムのスリム化 ;
data OUT2;
   length X $20.;
   set DT1;
   X = cats( A, B );
run;


CATS関数やその他の半角スペース処理関連のテクニックは以下記事で解説しています。

2016年4月21日木曜日

INオペレータは 「X in (A B C)」 のようにカンマが無くてもOK


以下をご覧ください。

data DT1;
   set SASHELP.CLASS;
   where AGE in (13, 14, 15);
run;

「AGEが13, 14, 15のいずれか」 という条件になります。
この書き方は以下のようにカンマを入れなくても動きます。

data DT1;
   set SASHELP.CLASS;
   where AGE in (13 14 15);
run;



カンマを入れない方がプログラムがスッキリして見える気がします。
あと役にたつ例として、単純な例ですが以下のようなマクロがあったとします。

%macro TEST( IN );

   proc means data=SASHELP.CLASS;
       var AGE;
       where AGE in (&IN);
   run;

%mend;


「where AGE in (&IN)」 で INの中身をマクロ変数として指定するようにしています。
「where AGE in (13,14,15)」と展開したい場合、以下のように%STR関数を使ってカンマをクオートする必要がありますが、、
%TEST(%str(13,14,15));



カンマを省略すれば以下のような単純な書き方で済みます。
%TEST(13 14 15);



必要に応じて使い分けてみましょう。

2016年4月20日水曜日

ODSグラフ(SG系グラフ)保存時の落とし穴




いままで、SGPLOTで作ったグラフをpngファイルとして保存する際、以下のように書いてました。
使用してたバージョンはSAS9.2です。


9.2 (実行結果は環境・設定によって異なる)
 ods graphics  /  reset=index  imagename="SAMPLE"  imagefmt=png ;
 ods listing gpath="C:\TEST";

      proc sgplot data=SASHELP.CLASS;
         scatter x=HEIGHT y=WEIGHT;
      run;

 ods listing close;
 ods graphics  /  reset=all;
 ods listing;


ログ
NOTE: C:\TEST\SAMPLE.png に書き込まれたイメージ出力を一覧表示します。
NOTE: データセット SASHELP.CLASS から 19 オブザベーションを読み込みました。




で、最近9.3で同様のプログラムを実行しました。(「imagefmt」はオプション名が「outputfmt」に変わったのでそこを変更してるのと、最後の「ods listing;」は削ってます)

9.3  (実行結果は環境・設定によって異なる)
 ods graphics  /  reset=index  imagename="SAMPLE"  outputfmt=png ;
 ods listing gpath="C:\TEST";

      proc sgplot data=SASHELP.CLASS;
         scatter x=HEIGHT y=WEIGHT;
      run;

 ods listing close;
 ods graphics  /  reset=all;


ログ
NOTE: C:\TEST\SAMPLE1.pngに書き込まれたイメージ出力を一覧表示します。
NOTE: データセットSASHELP.CLASSから19オブザベーションを読み込みました。



それぞれのログを見ると、保存されたファイル名が異なってますよね。
9.2 ・・・ SAMPLE.png
9.3 ・・・ SAMPLE1.png

とある設定によって、上記のファイル名にならない方もいるはずで、そこがまた厄介。
ファイルの取り違いを起こしそうで、ちょっと危険です。



原因、の前に前提知識

画像ファイルが作成されるたびに「SAMPLE.png、SAMPLE1.png、SAMPLE2.png、SAMPLE3.png・・・」とファイル名の末尾が+1される仕組みになっていて、同じプログラムを複数回実行すると、+1ずつカウントが増えていってしまいます。
なので「ods graphics / reset=index」で前回実行時のカウントをリセットしていますが、9.3では何故か+1カウントされたような名前「SAMPLE1.png」になってるのが不思議ですよね。
しかも、複数回実行しても、毎回「SAMPLE1.png」というファイル名になって、「SAMPLE2.png、SAMPLE3.png・・・」とカウントが増えていくわけでもないのがまた不思議。



原因

9.3からSASのアウトプットのデフォルトがHTMLに変わったことが影響してます。

  • HTMLに表示されるグラフは、裏でグラフの画像ファイルが作成されています。
  • 今回の例ではまずHTMLに表示するグラフの画像ファイル「SAMPLE.png」が作成されます。
  • 次に「ods listing gpath="C:\TEST"」と書いてるので、ods listing出力先でもグラフの画像ファイルが作成されます。これは2個目の画像ファイルなので、+1カウントが進んで「SAMPLE1.png」となってしまいますよね。
  • HTML以外の一部ODS出力先も、裏で画像ファイルを作ってしまうものがあって、それらODS出力先が開いたままだと同様に保存する画像ファイル名の末尾がずれてしまいます。



解決策

先に開いているODS出力をCLOSEすればOK。

今回の例ではHTMLが出力先としてアクティブになっていたので、
先頭に「ods html close;」、末尾に「ods html;」を入れることで「SAMPLE.png」というファイル名にする事が出来ました。

2016年4月19日火曜日

プロシジャでもATTRIBやLABELなどのステートメントが使える。



以下をご覧ください。
data OUT1;
   label AGE="ねんれい";
   set SASHELP.CLASS;
run;

proc sort data=OUT1;
   by AGE;
run;

データステップでLABELを変更してから、SORTプロシジャで並び替えを行っています。
昔自分が書いたプログラムを見てたら、こんな感じで2ステップ使って書いてました。


これらの処理は1ステップで書けます。
proc sort data=sashelp.class out=OUT1;
   by AGE;
   label AGE="ねんれい";
run;

プロシジャにもATTRIB, LABEL, FORMATステートメントが使えます。
(プロシジャによっては使えない可能性もあります)


ただし一部挙動が異なる。

・ プロシジャ内でLENGTHステートメントは使えません。
書いても 「NOTE: LENGTH属性は変更できないため、無視されます。」 ってログに出て無視されます。

・ データステップの一番先頭にATTRIB等の変数属性を定義するステートメントを記述すると、記述した順に変数が並びますが、プロシジャ内で記述しても変数の順番は変わりません。