2014年2月25日火曜日

FORMATプロシジャで正規表現を使う。



「SAS9.3」から、
FORMATプロシジャのINVALUEステートメント内で、正規表現が使えるようになりました。

ただしこの機能はシングルバイト文字でのみ使用したほうが良さそうです。
(現状、日本語などのマルチバイト文字に対応していないようなのと、こちら」で取り上げた問題により意図した結果にならない可能性があるため)
 


INVALUEはインフォーマットを作成するステートメントです。
正規表現は詳しくない、、ので詳細は割愛します。。




*** サンプルデータ作成 *****;
data DT1;
  input V1$;
cards;
ad
ca
fd
;

*** INVALUEステートメントに正規表現を使う例 *****;
* ① ***;
proc format;
  invalue MYFMT1_
    '/[abc]{2}/'  (regexp) = 1
    '/[def]{2}/'  (regexp) = 2
    other = 3
  ;
run;

* ② ***;
data DT2;
  set DT1;
  V2 = input(V1, MYFMT1_.);
run;

データセット「DT2」
V1  V2
ad   3
ca   1
fd    2




解説

①まず正規表現を含むINVALUEステートメントを定義します。
以下の両端を「/」で囲んでるやつが正規表現。また「(regexp)」と書くことで、正規表現ですよ、と宣言してます。

'/[abc]{2}/'  (regexp) = 1


上記は、「a,b,c」のいずれかの2文字で構成されてる値だったら「1と表示する」と定義してます。


②INPUT関数でインフォーマットを指定して数値変換します。



2014年2月19日水曜日

SQLでMONOTONIC関数を使用することについて。




この関数について、すこし紹介(こちら)したことがありますが、
色々調べて注意点をまとめました。




  • まずMONOTONIC関数はSASのリファレンスに載っていない、いわゆる「Undocumented SAS function

  • データステップで使う分にはあまり問題となる事はないですが、SQLプロシジャで使用する際は要注意
    どういう事かというと、、
    データステップだとレコードが並んでる順にデータ処理が行われるけど、

    SQLプロシジャだと再マージやGROUP処理など複雑な処理では、レコードが並んでいる順に処理を行っているわけでは無さそう(内部的な話なので、どのような順で処理を行うのかも分からない)

  • MONOTONIC関数は処理順に連番をふるので、SQLプロシジャで使用すると上記で説明したとおり、意図したものと違う、おかしな順番で番号がふられてしまいます。




MONOTONIC関数の結果値を見ることでSQLプロシジャでの内部処理順を探るヒントになりそうで、それはそれで面白いけど、注意点としておさえておく必要があります。


SASYAMAさんの記事で更なる注意点が見つかりました。
monotonic関数でアイタタタ(データステップ100万回)


2014年2月17日月曜日

FORMATプロシジャの小技



フォーマットの中にフォーマットを指定することができる面白い小技。



サンプルデータ作成

proc format ;
   value SEX_
     1 = "男性"
     2 = "女性"
   ;
run;

data DT1;
  format V1 SEX_. ;
  V1 = 1;  output ;
  V1 = 2;  output ;
  V1 = .;   output ;
run;




変数「V1」にフォーマット「SEX_」を割り当てています。



小技使用例


上のサンプルで例えば、、「V1」がNULLなら ”未記載” と表示したいとする。
その場合、”未記載”を含めた新たなフォーマットを作成するため、、

proc format ;
   value SEX2_
     1 = "男性"
     2 = "女性"
     .  = "未記載"
   ;
run;



と書くけど、以下のようにも書ける。

proc format ;
   value SEX2_
     . = "未記載"
     other = [SEX_.]
   ;
run;




解説と注意点

上記例では、フォーマット「SEX2_」を作成し、
NULLの場合、”未記載”と表示させ、
それ以外は、フォーマット「SEX_」の内容を適用させてる。


注意点としては、ログに以下のようなメッセージがでる。

NOTE: SEX_入出力形式が等号(=)の右側に指定されましたが、
      長さの指定がありません。 PROC FORMATは生成される入出力形式の標準の長さを40と仮定しました。
      十分な長さでない場合は、SEX_入出力形式に長さを指定し、 PROC FORMATを再実行するか、
      十分なDEFAULT=オプションを指定してください。


ざっくり言うと、
”「SEX2_」から「SEX_」を参照する時は表示する文字の長さを「40」で切るね。”
と言ってます。


今回「SEX_」は”男性”と”女性”で文字の最大長さは40以下なので、支障がないから上記メッセージは無視して構わない。

もし長さが40を超える場合や、上記のメッセージが嫌という場合は、
以下のように、フォーマット名の末尾に、十分な長さの指定を加えればよい。

proc format;
 value SEX2_
 . = "未記載"
 other = [SEX_10.]
 ;
run;



2014年2月14日金曜日

日本語の西暦・和暦を日付値に変換する。




日本語で入力された西暦・和暦を日付値に変換する方法を紹介します。



サンプルデータ作成

data DT1;
   length C1-C4 $30.;

   C1 = "2000/02/01";
   C2 = "2000年2月1日";
   C3 = "H12/02/01";
   C4 = "平成12年2月1日";
run;





  • 日本語を含む和暦は「JNENGO」というインフォーマットが用意されてるので変換が楽。
  • 日本語を含む西暦は漢字の「年」「月」「日」をKTRANSLATE関数で「/」に置換してから変換するなど工夫が必要。
 *** 「2000/02/01」 ;
  N1 = input( C1, yymmdd10. ) ;

 *** 「2000年2月1日」 ;
  N2 = input( ktranslate(C2,"/","年","/","月","","日") ,yymmdd10.) ;

 *** 「H12/02/01」 ;
  N3 = input( C3, nengo9. ) ;

 *** 「平成12年2月1日」 ;
  N4 = input( C4, jnengo32. ) ;



2014年2月13日木曜日

グループ間連番をふる方法


グループ間連番をふる方法を簡単に紹介。


サンプルデータ

data DT1;
input SUBJID$  VAL$;
cards;
001  a
001  b
005  c
005  d
005  e
010  f
010  g
;

データセット「DT1」
SUBJID  VAL
001         a
001         b
005         c
005         d
005         e
010         f
010         g

求めたい結果
「SUBJID」毎の連番をふる。

SUBJID  VAL  SEQ
001         a        1
001         b        1
005         c        2
005         d        2
005         e        2
010         f         3
010         g        3





連番をふる

*** 方法1 **********;
proc sort data=DT1; by SUBJID; run;

data DT2;
  set DT1;
  by SUBJID;

  if  first.SUBJID then SEQ+1 ;
run;

*** 方法2 **********;
proc sort data=DT1; by SUBJID; run;

data DT2;
  set DT1;
  by SUBJID;

  SEQ + first.SUBJID ;
run;

方法1もそんなに面倒な処理をしていないので、この方法でも良いけど、
方法2の方がスッキリしてるのでおすすめ。



📝注意点

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



2014年2月7日金曜日

関数の小技


関数の中に式を書くテクニック。


サンプルプログラム

data DT1;
   V1 = 1;
   V2 = "abc";
   VSUM = sum( V1=1, index(V2,"b")>0 );
run;


解説

関数の中にあるそれぞれの式には、
条件に合致する場合「1:true」、合致しない場合「0:false」が返される。

上記サンプルの場合、
・式「V1=1」の結果は「1:true
・式「index(V2,"b")>0」の結果は「1:true
・「sum(1:true + 1:true)」=「2」となる。


応用例

SUM関数での応用例としては
  ・条件に当てはまる個数を知りたい
  ・IF文を短縮させたい
といったとき役に立つ。

IF文の短縮とは、、
たとえば上記サンプルに、以下のようなルールで変数「FLG」を作りたいとする。
条件①「V1」=「1」である。
条件②「V2」に"b"という文字が含まれている。

・条件が全て当てはまらない場合 ・・・ FLG=「1」
・いずれか当てはまる場合 ・・・ FLG=「2」
・全て当てはまる場合 ・・・ FLG=「3」



通常、以下のようなIF文を書くことになるけど、、

if  not (V1=1  or  index(V2,"b")>0)    then FLG=1;
if  V1=1  or  index(V2,"b")>0       then FLG=2;
if  V1=1  and  index(V2,"b")>0    then  FLG=3;



関数の中に式を入れる方法を使うと、

VSUM = sum(V1=1, index(V2,"b")>0);

if  VSUM = 0  then  FLG=1;
if  VSUM > 0  then  FLG=2;
if  VSUM = 2  then  FLG=3;


このようにすっきりした文を書くことが出来る。


まとめ

パフォーマンスが悪くなったり、関数の中の式があまりにも長文になる場合を除いて、スッキリと見通しのいいプログラムになるのであれば積極的に使ってます。

他の関数でも、たくさん応用的な使い方が出来るので、どんなことが出来るかいろいろ試してみると面白いです。