2013年11月27日水曜日

年齢計算の落とし穴。



SASで年齢を計算する方法を紹介します。



※閏年生まれの人の加齢のタイミングをいつにするかによって、書き方が異なります。



以下、各年齢の出し方と結果の違い。

** サンプルデータ ;
data AGE;
 input BIRTH EVENT ;
 informat BIRTH EVENT yymmdd10.;
 format BIRTH EVENT yymmdd10.;
cards;
2000/03/02 2001/03/01
2000/03/02 2001/03/02
2000/03/02 2001/03/03
1992/02/29 1996/02/28
1992/02/29 1997/02/28
;

** 【年齢計算】 2月29日生まれの人の加齢タイミング別 ;
data AGE2;
 set AGE;

 /*- ① -------------------------------------
  閏年 の場合 → 2月29日 に加齢
  平年 の場合 → 3月1日 に加齢
 -----------------------------------------*/
 AGE1 = int( (put(EVENT,yymmddn8.)-put(BIRTH,yymmddn8.))/10000 ) ;


 /*- ② -------------------------------------
  閏年・平年ともに → 2月28日 に加齢
 -----------------------------------------*/
 AGE2 = int(yrdif(BIRTH, EVENT, 'AGE'));


 /*- ③ -------------------------------------
  閏年 の場合 → 2月29日 に加齢
  平年 の場合 → 2月28日 に加齢
 -----------------------------------------*/
 AGE3 = int( (intck('month', BIRTH,EVENT) -
         ( day(EVENT) < min(day(BIRTH),day(intnx('month',EVENT,1)-1)) )
        )/12);
run;





結果データセット「AGE2」









解説

①結果自体は、SASテクニカルサポート「年齢の計算方法」にあるやつと同じになる。
他のプログラミング言語で見かけた方法で、
ポイントは、2つの日付を、日付としてではなく8ケタの数字として見て計算してるところ。

ちなみに同じ結果を返す他の計算式として、以下2つの方法などもあり、どれもうまい。
 AGE4 = intck('YEAR',BIRTH,EVENT)-(put(BIRTH,mmddyy4.)>put(EVENT,mmddyy4.)) ;
 AGE5 = int((intck('MONTH',BIRTH,EVENT)-(day(EVENT)<day(BIRTH)))/12);


②「YRDIF関数」の「AGE」オプションを使ったやり方。
「AGE」オプションは人の年齢を計算できるオプションで、SAS9.3から追加された。


③海外のSASユーザー会で発表された、
「Calculating Age in One Line of Code」(Wei Wang, Highmark Blue Cross Blue Shield, Pittsburgh, PA) にあるやり方。
式は複雑だけど、うまい具合に計算してくれる。


まとめ

「うるう年」の取扱いは、各自異なると思うので、その辺の仕様をはっきりさせ最適な方法を使用するようにしましょう。


2013年11月20日水曜日

文字の先頭をゼロ埋めして桁数を揃える。



他のプログラム言語とかでもよく出てくる「ゼロパディング」といわれる問題。


例えば5桁に揃えたい場合は、
 「123」→「00123」
 「AB」 →「000AB」
みたいな感じに加工する方法。



ゼロ埋め対象が数値変数の場合

data DT1;
   length V1 $5.;
   V0 = 123;
   V1 = put(V0, z5.); 
run;

「Zw.dフォーマット」というのが用意されてて「z桁数」と指定するだけでok。




ゼロ埋め対象が文字変数の場合

data DT2;
   length V0 $3. V1-V3 $5.;
   V0 = "ABC";
   V1 = reverse(substr(reverse(cats("00000",V0)),1,5));  * 方法① ; 
   V2 = cats(repeat("0",4-lengthn(V0)),V0);                    * 方法② ; 
   V3 = tranwrd(put(V0, 5.-R),' ','0');                             * 方法③ ; 
run;


だいたい上記3つのやり方が思い浮かぶ。

方法①
ごり押し感あるやり方。
「cats関数」で「00000ABC」という文字にする。
「reverse関数」で「CBA00000」と逆にしてから、
「substr関数」で先頭5文字を抽出「CBA00」
また「reverse関数」でもとの順に戻せば完成。
非効率だけどきらいじゃない。

追記
コメントで上記と同じ原理、かつもっといい方法を教えてもらいました。

  V1 = put(put('00000'||V0 ,$revers8.),$revers5.);

put関数にも文字を逆さまにする「$revers」というformatがある。
うまいのが「$revers5」で、5文字に切りつつ、もとの順に戻してるところ。




方法②
変数「V0」から不足してる文字の長さ分だけ「repeat関数」を使ってゼロを埋めてる。


方法③
個人的には使ってる関数も少なくて一番スマートだと思う。
数値変数の場合でも適用可。

ポイントは「put関数」で5文字の長さにして「-R」を使って文字を右詰して「  ABC」としてるところ
その後、左側に出来た半角スペースを「tranwrd関数」でゼロに置換えてる。

「put関数」の「-R」については PUT関数の小技 を参照。



、、コメント欄で、色々なアイディアを頂いているので、そちらも是非見てください。


2013年11月15日金曜日

有害事象の発現例数と件数を簡単に出す。


安全性の解析で出てくる有害事象の集計。
例数と件数をなるべく簡単に少ないステップで出したい。
SQLなら簡単にできてしまう。


* サンプルデータ ;
data AE;
  length SUBJID $3. AETERM $20. AESTDT 8.;
  input SUBJID$ AETERM$ AESTDT;
  informat AESTDT yymmdd10.;
  format AESTDT yymmdds10.;
cards;
001 RBC上昇 2012/01/02
001 WBC減少 2212/02/05
002 RBC上昇 2012/01/05
002 WBC減少 2012/01/05
003 WBC減少 2012/02/10
;
run;












このデータから以下のように有害事象の発現例数・件数を出したい。
(症例内で同じ事象が複数回発現してる場合の扱いとかは、今回の例では特に考えてないです)





* SQLで発現例数と件数を1STEPで出す ;
proc sql;
  create table AE_N as
  select count( distinct SUBJID ) as N1,
             count( SUBJID ) as N2
  from AE;
quit;


N1が例数、N2が件数を求めた結果。



簡単に解説
  • SQLのCOUNT関数: 引数が非欠損値のレコード数を求める
  • DISTINCT: 関数の中に指定すると、引数の値から重複を除いたレコードを計算対象とする

今回の例だと「count( distinct SUBJID )」で、SUBJIDの値から重複を除いた非欠損値のレコード数を求めています(=例数が求まる)

2013年11月14日木曜日

「%IF条件」で「IN」を使う。




%IF  &マクロ変数名  IN  ( 値1, 値2 )


みたいな書き方は出来ない。。。と思ったら実はできる。



以下のプログラムは、マクロ変数 「MVAR1」 に 「1」 か 「2」 が設定されていたら、
ログにメッセージを表示するサンプル。

options minoperator mindelimiter=',' ;

%let MVAR1=1;

%macro TEST;
      %if  &MVAR1 in (1,2)  %then %put マクロ変数 MVAR1には 1 か 2 が設定されています;
%mend;

%TEST;

  • minoperator」 でINが使えるようになります
    • 「IN」の代わりに「#」でもOK( %if &MVAR1 # (1,2) %then ... )
  • mindelimiter」 には値を区切る時の区切り文字(シングルバイト1文字)を指定
    • % & ' " ( ) ;」といったマクロ等で意味を持ちそうな区切り文字は設定できません


今回の例では条件値を「,」で区切っています。
%IF条件でのINを利用不可に戻したいときは

options nominoperator;

delimiterの設定を戻すオプションはヘルプとかみても見つからず。



📝注意



続き・・・
「%IF条件」で「IN」を使う。(その2)


2013年11月8日金曜日

PUT関数の小技「文字寄せ」


以下のプログラムを実行すると、、

data DT1;
  length VAR1 $20.;
  VAR1 = put(1, 20.);
run;


「         1」みたいな感じで先頭に余計な半角スペースが入ってしまう。
左詰めにするため「left関数」を使う以外に、

 VAR1 = put(1, 20. -L);

と書くこともできてしまう。

ちなみに

 「-L」が左詰め
 「-C」が中央
 「-R」が右詰め

って意味になる。



使うときの注意

① VAR1 = put(1, 8.2 -L);            * OK ;
② VAR1 = put(1, 8.2 -L) || "mg";  * NG ;


上記②の場合、
まず「put(1, 8.2 -L)」で左詰めした分、文字値の右側に半角スペースが移動します。
イメージ:「    1.00」→「1.00    」

この文字と「mg」を結合すると「1.00    mg」といった感じで、不要な半角スペースが混入してしまう。

あくまでもleft関数のような動きをしていることに注意!

SUBSTR関数の小技




以下のデータセットがあったとして、、

data DT1;
    length VAR1 $20.;
    VAR1="abcde";
run;

変数「VAR1」の2~3バイト目を「XX」に置換したいとする。
だしたい結果は「aXXde」



「translate」とか「tranwrd」を思い浮かべるけど、以下みたいにも書ける。

data DT2;
    set DT1;
    substr(VAR1,2,2)="XX";
run;


substr関数の意外に知られてない書き方だと思う。ただし、
  • 2バイトを3バイトの文字に置換、みたいな異なる長さへの置換は出来ない。
    • 失敗例1: substr(VAR1,2,2)="XXX" → "aXXde"
    • 失敗例2: substr(VAR1,2,2)="X" → "aX de"
  • シングルバイト専用の関数のため、日本語などのマルチバイトは未対応。
  • 変数を上書きする(今回の例ではVAR1の変数値を置換した結果がVAR1に格納される)のでご注意下さい。