データセット内のすべての文字変数に対して、それぞれに入っているテキストの長さの最大値を取得するハッシュオブジェクト

SAS忘備録の記事「Hashオブジェクトを使おう。」http://sas-boubi.blogspot.jp/2015/02/hash.html

を読んで感動しました。
わかりやすい。僕の記事の数兆倍わかりやすいので、是非読んでください。

そして思ったのが、やっぱり普及させたい技術についてはそのまま実務で使えたり、
使い道が容易に想像できるコードを例にしないと、僕だけが楽しいばかりで、駄目だなということです。

僕だけが楽しい悪い例の一部↓
「ハッシュオブジェクトでマジカルバナナのデータを順番通りに連結する」
http://sas-tumesas.blogspot.jp/2014/09/blog-post_16.html
「ハッシュ反復子オブジェクトとcall compcostルーチンで、マジカルチェンジのデータを順番通りに連結する」
http://sas-tumesas.blogspot.jp/2014/09/call-compcost.html

実は最近 sumメソッド周りを集中的に研究していて、割と実務的かつ面白なサンプルを生産してるのですが、それは一応ユーザー総会用にしようかと思って、ストックしているので今暫くお待ちください。いずれ全てUPします。

で、前置きが長くなりましたが、ここから本題。



さて、文字型の変数の場合、適切なlengthの設定というのは結構、頭の痛い問題です。
文字切れはやばいし、大は小を兼ねるの発想で、せいぜい$100.くらいしか入らないだろうなってとこも、データ入力に制限がない場合、長文入れる人もいて怖いから取りあえず$1000.とかにしがちじゃないでしょうか。
で、一個$1000.にすると、もうめんどいから文字変数は全部1000でいいやみたいな。

オブザベーション数が少ない、或いはそういう文字変数が少ない場合は別にかまわないです。
しかし、経験のある人も多いと思いますが、文字変数のlengthは実行時間に対して、割とダイレクトに影響します。

なので、データが固まった後でlengthをもっかい決めたいなって時、実際、入ってる値のlengthの最大値が欲しくなります。
或いは、SASデータセットを他のシステムやDBに渡す時とかも実際どの程度とればいいのかを調べることあります。

まあ、length関数と文字変数に対するループで、各変数の長さだして、それをmeansとかにかけて最大値とるとか、或いはretainで最大値更新していってeofで出すとかでいいんですけど、その場合、データセット名だけ指定すれば、常に動くマクロ、できれば処理時間も最速でっていうものを作りにくいんですよね(まあ、僕がマクロ苦手なので)。
あと、できれば、ほかのシステムにペタッと貼り付けたり、そこからコード自動生成するのに
縦積みになってた方がうれしいので敢えてハッシュ使ったという個人的事情もあります。
とりあえずハッシュ使いたいだけという趣味的な部分もありますが。

で、今

data A;
length A B C $1000.;
do i= 1 to 5;
 A=repeat('A',int(ranuni(777)*20));
 B=repeat('B',int(ranuni(777)*20));
 C=repeat('C',int(ranuni(777)*20));
output;
end;
drop i;
run;










というデータがあったとして

data _null_;
length var $100. maxlength 8.;
set A end=eof;
array cha _character_;

if _N_ = 1 then do;
call missing(var,maxlength);
declare hash h1();
h1.definekey('var');
h1.definedata('var','maxlength');
h1.definedone();
do over cha;
var = vname(cha);
maxlength = 0;
h1.add();
end;
end;

do over cha;
var = vname(cha);
if h1.find() = 0 & length(cha) > maxlength then do;
maxlength=length(cha);
h1.replace();
end;
end;

if eof then do;
h1.remove(key:'var');
h1.output(dataset:'LEN');
end;
run;


こうすれば、結果は








となります。

今 set A; としているところのデータセット名だけ変えれば常に動くはずです。

実際、自分用に使っているマクロは、その最大文字数の実際のデータと、IDとかもとるようにしていますが、
中身を理解できれば、割と簡単に自分好みにカスタマイズできるはずです。

で、中身の説明ですが
ハッシュオブジェクト定義と、そこへのデータ追加の部分

array cha _character_;

if _N_ = 1 then do;
call missing(var,maxlength);
declare hash h1();
h1.definekey('var');
h1.definedata('var','maxlength');
h1.definedone();
do over cha;
var = vname(cha);
maxlength = 0;
h1.add();
end;
end;

が割と工夫されていて、面白いですね(自画自賛)。
まずからっぽで定義してから、全文字変数の変数名をキーにしてハッシュに格納します。
現状の最大lengthは0にしておきます。

この時点での、このハッシュオブジェクトh1の中身は










となっているはずです。
(慣れるまではデバックのためハッシュオブジェクトの中身をこまめにouputメソッドでだしておくのもいいかもです。)

そのあと
do over cha;
var = vname(cha);
if h1.find() = 0 & length(cha) > maxlength then do;
maxlength=length(cha);
h1.replace();
end;
end;
は、そのまんまですね。findメソッドで、その時点で処理している変数名の現状の最大lengthをハッシュオブジェクトから
引っ張ってきて、比較して、自分の方が大きければreplaceメソッドで更新すると。

なんか今回はハッシュオブジェクトをretainステートメントみたいな感じで使ってるわけです。



最後
if eof then do;
h1.remove(key:'var');
h1.output(dataset:'LEN');
end;

は、varは元のデータセットにない、作った変数なのでremoveメソッドで消します
(※対象のデータセットにvarという名前の変数があったら、コード変えてくださいね)

h1.output(dataset:'LEN')でデータセットを出力します。

if eof then do;にしているのは最後の1回出力すればいいからですね。


以上です。

この変数名をキーにして、ターゲットになる値を更新していく方法は結構応用がききます。



2 件のコメント:

  1. プログラム活用させて頂いております。文字型の変数についてしか結果が返ってこないようですが、数字型データについても返って来るようにできないでしょうか??

    返信削除
    返信
    1. コメント有難うございます。
      数値型の長さというのは、lengthとしての8.のことではなく、文字型に変換した際の文字数ということでよろしいでしょうか?

      もし
      100なら2
      1000なら4
      1.1なら3
      -1なら2
      といったように単純に文字数でよいのであれば

      いま、文字変数のみarray _character_で定義してループさせているのに加えて
      array _numeric_を加えてやれば大丈夫です。
      文字化するのがbest.フォーマットでよければたとえば以下の感じでどうでしょうか?

      data _null_;
      length var $100. maxlength 8.;
      set A end=eof;
      array cha _character_;
      array num _numeric_;

      if _N_ = 1 then do;
      call missing(var,maxlength);
      declare hash h1();
      h1.definekey('var');
      h1.definedata('var','maxlength');
      h1.definedone();
      do over cha;
      var = vname(cha);
      maxlength = 0;
      h1.add();
      end;
      do over num;
      var = vname(num);
      maxlength = 0;
      h1.add();
      end;
      end;

      do over cha;
      var = vname(cha);
      if h1.find() = 0 & length(cha) > maxlength then do;
      maxlength=length(cha);
      h1.replace();
      end;
      end;

      do over num;
      var = vname(num);
      if h1.find() = 0 & length(put(num,best. -l)) > maxlength then do;
      maxlength=length(put(num,best. -l));
      h1.replace();
      end;
      end;

      if eof then do;
      h1.remove(key:'var');
      h1.output(dataset:'LEN');
      end;
      run;

      大きい数字が入る場合などは、best.のところを変えたり、符号抜きの桁数がほしいなど場合はlength(put(num,best. -l)の箇所を変えたりしてください。

      なにかわからない箇所があれば遠慮なくどうぞ

      削除