2018年5月10日木曜日

データセット内の各文字変数について、実データの最大長(LENGTH)を求める




タイトルのような質問をよく頂きます。
製薬業界だと、CDISC対応で文字変数のLENGTHを実データの最大長に合わせたい時に必要になりますね。



求め方は、すでにデータステップ100万回のSASYAMAさんが記事を載せています。
データセット内のすべての文字変数に対して、それぞれに入っているテキストの長さの最大値を取得するハッシュオブジェクト

やりたい事とHASHオブジェクトがマッチしていて素晴らしいプログラムです。私も他の解法を考えてみたくなりました。





サンプルデータ作成

data DT1;
input ONE (TWO THREE FOUR)(:$10.);
cards;
1 aaa cc .
2 bbbb ddd .
3 . ee .
;
  ONE 
  TWO 
 THREE 
 FOUR 
  1   aaa  cc   
  2   bbbb  ddd   
  3 
  ee  

今回はこのDT1が、実データの最大長を取得したい対象のデータセットとします。




※ 本題に入る前に
今回紹介するプログラムでは文字の長さを「LENGTH関数」使って調べてますが注意が必要です(以下参照)

何が言いたいかというと、変数が欠損値の場合、LENGTH関数は「1」を返します。
もし「0」と返してほしいなら、プログラム中の「LENGTH関数」を「LENGTHN関数」に変える必要があります。





方法① 配列とPROC SUMMARYのコンボ

まずは平凡なプログラムから。


※諸注意
以下プログラム中で「_VARNAM」「_VARLEN」「_i」という変数を作っていますが、もし対象のデータセットにもこの3つと同じ名前の変数がある場合、処理がうまくいきません。
その場合はプログラム中の「_VARNAM」「_VARLEN」「_i」を別の変数名(対象のデータセットにはない変数名)に変えてください。


data OUT1;
  set DT1;

  * 文字変数を配列に格納 ;
  array ar(*) _character_;

  * 変数名とlengthを出力 ;
  length _VARNAM $32. _VARLEN 8.;

  do _i = 1 to dim(ar);
    _VARNAM = vname(ar(_i));
    _VARLEN = length(ar(_i));
    output;
  end;

  keep _VARNAM _VARLEN;
run;

* 変数毎の最大lengthを求める ;
proc summary data=OUT1 nway;
  class  _VARNAM;
  output  max(_VARLEN)=_VARLEN  out=OUT2(keep=_VARNAM _VARLEN);
run;

 _VARNAM 
 _VARLEN  
  FOUR    1
  THREE  3
  TWO   4




方法② TRANSPOSEとSQLのコンボ

内部処理的には無駄が多いものの、プログラムとしては単純かつ楽に書けるため私はよくこの方法使ってます。


※諸注意
以下プログラムでは「_TRANUM」という変数を作っていますが、もし対象のデータセットにもこれと同じ名前の変数がある場合、処理がうまくいきません。
その場合はプログラム中の「_TRANUM」を別の変数名(対象のデータセットにはない変数名)に変えてください。

* 後のtranspose用にobs毎の連番を持たせる ;
data OUT1;
  set DT1;
  _TRANUM = _N_;
run;

* 全文字変数を縦に転置 ;
proc transpose data=OUT1 out=OUT2 name=_VAR prefix=COL;
  var       _character_;
  format  _character_;
  by   _TRANUM;
run;

* 変数毎の最大lengthを求める ;
proc sql;
  create table OUT3 as
  select _VAR, max(length(COL1)) as _LEN
  from OUT2
  group by 1;
quit;

 _VAR 
 _LEN  
  FOUR    1
  THREE  3
  TWO   4




方法③ LUAプロシジャ(SAS9.4メンテナンスリリース3から追加になったプロシジャ)

個人的に書いてて一番楽しかったです。
LUAの知識は初心者レベルなので、これが最適解なのか不明、、もっといい書き方やこういう書き方も出来るってのがあったら教えてほしいです。

proc lua;
 submit;

    -- データセットをopen
    local dsid = sas.open("work.dt1")
    local tb = {}

    -- 文字変数名をテーブルに格納
    for var in sas.vars(dsid) do
        if var.type == "C" then
            tb[#tb+1] = {col=var.name, len=0}
        end
    end

    -- OBSを読み込んでいって最大lengthを求める
    while sas.next(dsid) do
        for i in pairs(tb) do
            tb[i].len = sas.max( sas.length(sas.get_value(dsid,tb[i].col)), tb[i].len )
        end
    end

    -- データセットを閉じて&テーブルの中身をデータセットに出力
    sas.close(dsid)
    sas.write_ds(tb,"work.out1")

 endsubmit;
run;


 LEN 
COL
  4   TWO 
  3  THREE 
  1   FOUR



0 件のコメント:

コメントを投稿