2地点間の緯度経度から直線距離でもっとも近いデータを取得する話 geodist関数

親戚の子供の宿題を手伝っていたんですが、以下の問題に頭抱えました。

「県庁所在地で、直線距離がもっとも近いのは何県と何県?
地図帳とものさしを使って考えてみよう」

うわぁ~、凄いめんどい。

「名古屋と岐阜であってるよね」って言われても、そんなのわかんない。
大阪-神戸だと思ったけど自信なし

あたりをつけて、さし当ててみるけど、結構難しいぞこれ。
わかります??

困った時のSAS On Demand
ネットさえつながればどこでも自由にSAS使えるので、ちょっとPC借りて
以下のコードをパチパチ。

data q1;
length no 8. shi $10. ido keido 8.;
input no shi ido keido;
cards;
1 札幌市 43.063968 141.347899
2 青森市 40.824623 140.740593
3 盛岡市 39.703531 141.152667
4 仙台市 38.268839 140.872103
5 秋田市 39.7186 140.102334
6 山形市 38.240437 140.363634
7 福島市 37.750299 140.467521
8 水戸市 36.341813 140.446793
9 宇都宮市 36.565725 139.883565
10 前橋市 36.391208 139.060156
11 さいたま市 35.857428 139.648933
12 千葉市 35.605058 140.123308
13 新宿区 35.689521 139.691704
14 横浜市 35.447753 139.642514
15 新潟市 37.902418 139.023221
16 富山市 36.69529 137.211338
17 金沢市 36.594682 136.625573
18 福井市 36.065219 136.221642
19 甲府市 35.664158 138.568449
20 長野市 36.651289 138.181224
21 岐阜市 35.391227 136.722291
22 静岡市 34.976978 138.383054
23 名古屋市 35.180188 136.906565
24 津市 34.730283 136.508591
25 大津市 35.004531 135.86859
26 京都市 35.021004 135.755608
27 大阪市 34.686316 135.519711
28 神戸市 34.691279 135.183025
29 奈良市 34.685333 135.832744
30 和歌山市 34.226034 135.167506
31 鳥取市 35.503869 134.237672
32 松江市 35.472297 133.050499
33 岡山市 34.661772 133.934675
34 広島市 34.39656 132.459622
35 山口市 34.186121 131.4705
36 徳島市 34.06577 134.559303
37 高松市 34.340149 134.043444
38 松山市 33.84166 132.765362
39 高知市 33.559705 133.53108
40 福岡市 33.606785 130.418314
41 佐賀市 33.249367 130.298822
42 長崎市 32.744839 129.873756
43 熊本市 32.789828 130.741667
44 大分市 33.238194 131.612591
45 宮崎市 31.91109 131.423855
46 鹿児島市 31.560148 130.557981
47 那覇市 26.212401 127.680932
;
run;

県庁所在地と緯度経度をネットから適当に拾ってきて
geodist関数にあてます。
geodist関数は(地点1の緯度,地点1の経度,地点2の緯度,地点2の経度)で、距離を返します(デフォルトなら単位はキロメートル、オプションでマイルにもできます)

ちなみに緯度経度には日本測地系と世界測地系があって、
微妙に違うので、できれば世界に変換して同じ測地系で比べましょう。

変換法はPHPだけど式は同じなので以下のページで紹介されているコードなんかがいいと
思います
http://d.hatena.ne.jp/nakamura001/20080501/1209660263


で、書いたコードは以下のとおり、これぐらいのオブザベーション数なら
直積作ってもよかったですけどハッシュでもわりとあっさり書けました。

data a1;
length _shi $10. _ido _keido 8.;
if _N_=1 then do;
call missing(_shi,_ido,_keido);
declare hash h1(dataset:'q1(rename=(shi=_shi ido=_ido keido=_keido))');
h1.definekey('no');
h1.definedata('_shi','_ido','_keido');
h1.definedone();
end;
set q1;
do no= 1 to 47;
rc=h1.find();
dist=geodist(ido,keido,_ido,_keido);
if dist^= 0 and (dist<mindist or mindist=.) then do;
mindist=dist;
moyori=_shi;
end;
end;
keep shi moyori mindist;
run;

proc sort data=A1;
by mindist;
run;
proc print;
run;

結果は



















































































滋賀の大津市と京都の京都市か~、確かに近いな!
10キロちょっとか

神戸にとっての最寄は大阪だけど大阪の最寄は奈良か。
へ~、そういう感じだったんですね。
勉強になりました。

詰めSAS データステップで新しく作成された変数か、元からデータセットに あった変数かを判定する方法を考えてみる。vinarray関数とか

結構、よく質問されるのが、データステップ中で新しく追加された変数か、
読み込んだデータセットに元からあった変数どうかを判別したいみたいな話です。

いちいち、データセットの仕様書つくるの面倒くさいから
先にプログラム書いちゃって、その実行ついでに新規変数がどうやって生成されているかを
取得して、仕様書を自動生成しちゃえば楽だよなぁみたいな、おバカなこと考えたことありません?
え、いや、僕はないですけどね。

ところが、SASの場合、これが結構ハードで厳しい。
手抜きするつもりが、真面目にやる1000倍くらい時間がかかったりするのもよくある話です

しかも、いまだに完全に納得できる方法が思いついてません
誰か教えてください。

とりあえず、前後のデータセットをコンペアしてその結果を読み込むという
面倒すぎる方法は除外して、1ステップ内で、本処理のついでにできる方法に限定します。

僕が考えた案を2つほど。

以下のデータセットがあります。



変数 a b c dがあります。

data Q1;
a=1;b=2;c=1;d='A';
run;

まず一つ目の方法は以下です。

data A1;
set Q1;
array an _numeric_;
array cn _character_;

/*新しい変数 e f g 作る*/
e=1;
f='b';
g=a+b;

/*チェックしてみる*/
flg_a=ifc(vinarray(a),'元','新');
flg_b=ifc(vinarray(b),'元','新');
flg_c=ifc(vinarray(c),'元','新');
flg_d=ifc(vinarray(d),'元','新');
flg_e=ifc(vinarray(e),'元','新');
flg_f=ifc(vinarray(f),'元','新');
flg_g=ifc(vinarray(g),'元','新');

put (flg_a--flg_g) (/=);

run;

判定結果をputしているので、それを見てみると











追加した3つの変数が正しく判定されているのがわかります。

なにをしているかというと、最初に数値型、文字型それぞれ全変数を
配列に定義しちゃいます。

で、その後、割り当てステートメントがはいるわけですが、
ここで新規に割り付けられた変数は、最初に定義している配列には含まれていない
わけです。

そこで、判定の箇所でvinarray関数という、引数の変数が何かしらの配列に属しているか
どうかを1 , 0で返すニッチな関数で判定します。
配列に入ってないということは、新しい変数だろってノリで。

しかしまあ、お気づきの方もいるでしょうが、これは新しく作る変数を配列に定義しているような
プログラムでは正しく判定できない限定的な方法ですね。
e f gが何の配列にもたまたま定義されていないからそれでいけてるわけです。
同じく、新規変数をarrayで生成したりしても判別不能になります。
(他にも弱点はありますがそれは後で)

次に思いついたのはハッシュオブジェクトです。

data A2;
set Q1;

/*この時点での全変数の情報を一旦ハッシュオブジェクトにいれる*/
if _N_=1 then do;
 declare hash h1();
 h1.definekey('vname');
 h1.definedone();
 array an _numeric_;
 array cn _character_;
 do over an;
  vname=vname(an);
h1.add();
 end;
 do over cn;
  vname=vname(cn);
h1.add();
 end;
 drop vname;
end;

/*新しい変数 e f g 作る*/
e=1;
f='b';
g=a+b;

/*チェックしてみる*/
flg_a=ifc(h1.check(key:vname(a)),'新','元');
flg_b=ifc(h1.check(key:vname(b)),'新','元');
flg_c=ifc(h1.check(key:vname(c)),'新','元');
flg_d=ifc(h1.check(key:vname(d)),'新','元');
flg_e=ifc(h1.check(key:vname(e)),'新','元');
flg_f=ifc(h1.check(key:vname(f)),'新','元');
flg_g=ifc(h1.check(key:vname(g)),'新','元');

put (flg_a--flg_g) (/=);

run;

結果は同じなので割愛。

最初のレコードの時点のPDVの変数名情報をハッシュオブジェクトに格納して
ステップの終わりで、checkメソッドで、存在するかどうかを判定する方法
(さっきと逆に、あれば0,なければエラーコードとして0以外の数字が戻ります)
これならば、ステップの途中で新規変数に配列つかったりしてても問題なしです。


ただ、2つの方法とも、致命的に弱点なのがあくまでステップ開始時の
PDVにある変数を、元からある変数だと考えている点です。

つまり length f $100.;といったようにlengthやformatステートメントなど
データを読み込む前に箱を定義しちゃうようなものがはいると、fは実際は元データセットに無いけど
元からある変数ってことになるんですね。
ここが僕の限界。

う~ん、どうするのが正解なんでしょう。
contentsプロシジャとかsqlとかで、ガチで大元のデータセット情報読み込むコードを
作ってステップ内でdosubl関数で回すとか?

あるいは新規データセットのPDVに影響受けずに変数名をとれるのかな?
if _N_=0の状態でvnextルーチン回したりできんのかな?

誰か教えてください

ひとつのマクロステップ内で重複しなければ何でもいい一時的な値をもつマクロ変数&sysindex。 たとえばデータステップ内でツール的に利用するマクロなんかで、配列名やハッシュオブジェクト名みたいに1データステップ内で同名存在ができないものを生成するケースなどに使えます って話

(追記-最初にあげたときマクロの内容や画像が派手に間違えてました。ごめんなさい。
同時に指摘いただいた4名の方々有難うございます。ちゃんと見てる人いるんですね、反省します..)

こないだ電車乗ってたら、ブログに長いタイトルつける奴は仕事できない奴だっていってるおじさんがいました。要点がまとめれていないからだそうです。僕もそう思います。

さて、もう言いたいことはタイトルで言ってしまったので、これ以上何もないのですが説明します。

例えば以前の記事で、SASには現状、文字型の非欠損値をカウントする変数がないって話をしました
http://sas-tumesas.blogspot.jp/2015/02/blog-post.html

それをマクロをサブルーチン的に使って解決しようと考えたとします。
以下のようなデータがあり

data Q1;
A='';B='a';C='a';D='a';E='a';output;
A='a';B='';C='a';D='';E='a';output;
A='';B='a';C='';D='a';E='';output;
A='a';B='a';C='a';D='a';E='a';output;
run;











以下のようにマクロを組んでみました。

%macro ar(varlist);
array AR $ &varlist;
CN=0;
do over AR;
 CN + ^missing(AR);
end;
drop CN;
%mend ar;


早速そのマクロを使って、変数A B Cのうち値があるものの数をカウントしてみます。

options mprint;

data A1;
set Q1;
%ar(A B C)
count_A_B_C=CN;
run;











はい、できました。
ちなみにoptions mprintをつけたのでログにはこんな感じで展開されたものが
でています


そこで調子にのって、同じステップなの中でC D Eに対してもやってみようとします

data A2;
set Q1;
%ar(A B C)
count_A_B_C=CN;
%ar(C D E)
count_C_D_E=CN;
run;

はい、エラーになりました。








これはarマクロの中でarrayステートメントで配列名「AR」を生成してるんですが
arマクロを2回以上入れると、マクロが展開されたとき、1つのデータステップの中で配列「AR」を
複数回宣言することになります。そしてそれはSASの文法上NGなので通りませんぜと。

じゃあ、どうするか。
このマクロにとって、配列は、カウント処理の際のみに必要なものであって
CNが計算できた後は意味がなく、配列の名前についてもどうでもいいわけです。

たとえば、引数の変数名を全部文字連結して配列名にするようにしてもいいですが
配列名は32文字までなので、長い変数をたくさん指定すればエラーになってしまいます。

乱数とかでもいいですが、たまたま衝突する可能性は、まあ一応ゼロではありません。

じゃあ、パラメーターひとつ増やして、使用時に毎回配列名も指定させます?
使い捨ての名前にそこまで手を煩わせたくないな~

じゃあ、どうするのか?
ここでsysindexを使います。こいつはsasを開いてから閉じるまでの
sasセッションの中で、呼び出される都度+1された連番が入るので
決して重複しないという性質をもっています

以下のようにマクロを書き直しました。

%macro ar2(varlist);
array AR&sysindex $ &varlist;
CN=0;
do over AR&sysindex;
 CN + ^missing(AR&sysindex);
end;
drop CN;
%mend ar2;

では再度実行してみます。

data A3;
set Q1;
%ar2(A B C)
count_A_B_C=CN;
%ar2(C D E)
count_C_D_E=CN;
run;

無事できました。


ログを見てみると、さっきと違ってAR5とAR6というように
勝手に配列名に連番が付与されていることで、名前の衝突が回避されていることが
わかります。

ちなみに、なぜ5から始まっているかというと

まず最初何もしていない状態が1で

最初データセットA1を作った際に一回%arを呼び出したので
ここで+1されて2になってます。

続いて、結局エラーになりましたがA2で二回呼び出しているので
+1 +1 で4になってます。

だから今回5なんですね。ユーザーが意識しようがしまいが内部的に自動カウント
されているわけです。

SASはfcmpプロシジャの機能がアレなんで、マクロを関数やサブルーチンなどステップ内での
ツール的に使いたいケースも多いんですが、そういう時に知ってると役立ちます

9.4から登場した拡張属性(Extended Attribute)はあんまり話題になんないねって話

昔、とある場所でデータセットをみていた際、変数に全部に長~いラベルがついてました
例えば、[XX_日付型_データAA由来] のようにその変数の元の参照先や、詳細な型について記載されてました。

誰がみても、その変数の詳細がわかるようにと、義務づけられてるそうです。

まあ、確かにSASって文字型と数値型しかないので、普通にプロシジャに数値型全指定でかけたら
日付もIDも集計してくれたりするし、データセットいじってる内に、この変数ってどっからきたの?みたいなこともあります。

まあ、定義書で管理すればいいんじゃないって気がしてましたし、
ラベルでそういうのを管理するのは限界があるよな~って思いました。

データセット自体にXMLのように自由に属性つけれれば管理がはかどるのにって思う方もいるのでしょう。

そんなあなたに朗報。9.4から自分で好きに属性を定義して、それをデータセットに付与することが
できるようになりました。

それが拡張属性です!

9.4で登場した新機能のひとつですが、DS2やFedSQLに輪をかけて影が薄い!!
誰も話題にしてないこと、この上なしです。

例えば以下のようなデータセットがあるとします。

data Q1;
x=1;y=19562;z=320;output;
x=2;y=19566;z=280;output;
x=3;y=19569;z=120;output;
format y yymmdds10.;
run;











これに、その変数の説明と、参照元、型の補足(日付なのか、IDとかみたいに質的なものなのかとか)を
それぞれ
detail source cate という適当な属性を作って付与してみようと思います。

以下を実行します

proc datasets nolist;                             
   modify Q1;     
      xattr add var x (detail="ユーザーID" source='登録情報' cate="ID") 
                    y (detail="XX日" source='20XX年情報' cate='日付')                    
                    z (detail="売り上げ(千円)" source='売り上げ情報' cate="量的変数")
;                    
run;                 
quit;

拡張属性はいまのとこ、proc datesetsでしか作れないみたいです。attribステートメントとかでも
作れればいいのにね。

文法は上記のとおりで、xattr add varで変数に対する拡張属性を付与できます。
ちなみにvarのところをdsにすると、データセットにも属性を定義できます。

脱線しますが、ラベルにもあまり使われていないですが、データセットラベルというのが
昔からありました。
「データセットラベルの話」
http://sas-tumesas.blogspot.jp/2014/08/blog-post_31.html


で、話を戻して、拡張属性を付与したら、そのデータセットをcontentsプロシジャで確認してみましょう。
(ods ooutputは後で使うのでおまけです)

ods output ExtendedAttributesVar=xattr_ds;
proc contents data=Q1;
run;



























このように、付与した拡張属性を参照することができました。


例えばこれを使って、拡張属性で「cate」の値が「量的変数」の変数のみを
meansにかけたいな~と思ったら、さっきods outputでデータセット化しておいたものを
使って

data _null_;
length target $1000.;
set xattr_ds end=eof;
retain target;
where ExtendedAttribute='cate' & AttributeCharValue='量的変数';
target=catx(' ',target,attributevariable);
if eof then call symputx('target',target);
run;


proc means data=Q1;
var &target;
run;

上記のようにかけるわけですね。

拡張属性、バリバリ利用してますって方がいれば、どんなことしてるか是非教えてほしいです

gsubmitでSASメニューをカスタマイズしたおす話

ちょっと身の回りで色々とあったりしてすっかり更新が滞ってしまってました。
僕自身は相変わらず元気です。
連絡いただいているのに返せていない方、ごめんなさい。少し落ち着いたので徐々に
返信させていただきます。不義理な真似してすみませんでした。

あと、掲示板の方を、amatsuさんに丸投げしているようで、本当にすみません。
いつも見てはいるんですが、ついつい甘えてしまってました。

さて、いつものことですがかなり需要のない話をします。

なんかデータセットがあったとして

data Q1;
x=1;y='A';output;
x=2;y='B';output;
run;


それをSAS上で、選択して右クリックすると以下のようなメニューがでてきます。



これ見て、「もっとマシなメニューにしろよ」って怒りを感じるのは、捻くれた僕だけですか?

しかもEXCELで表示とかいいながら、内部的に実行されているコードは以下のとおりで
filename _temp_ "XXXX.xls";
ods noresults;
ods listing close;
ods html file=_temp_ rs=none style=minimal;
   proc print data=Work.'Q1'N label noobs;
   run;
ods html close;
ods results;
ods listing;
filename _temp_;

ods htmlで無理やりだしてるだけやん。って感じですね。純粋なEXCELじゃないから
警告出てきてうざいし、そのまま人にだすのがためらわれて嫌なんです。

とはいえ、ここのメニューをどうこうしたりとかは SAS/AF みたいなアプリケーション開発用の
ライセンスないとできないんでしょって思ってました。

ところがなんとできたんです。

手順は「えぇ?」って感じなんですが、覚えれば簡単です。
(9.4は試してないのでわからないのですが9.13-9.3までは以下の手順でできるはずです)

まず、拡張エディタがアクティブな状態で
ツールバーの「表示」で「コンテンツのみ」をクリックします。何もおきませんけど必要なんです



次に「ツール」で「オプション」→「エクスプローラ」を選択します
※この「エクスプローラー」はコンテンツのみを選択した直後しか表示されない
隠し項目になります



ここで「メンバー」タブの「テーブル」を選択して、「編集」ボタンをおします




そこで「追加」をクリックします



アクションの部分に「FREQプロシージャにかける」
アクションコマンドの部分に以下のコードを書いてください。



gsubmit "proc freq data=%8b.'%32b'N;run;"

あとはOK OK OKでウインドウを閉じます。

そしたら、再度さっきのデータセットを選択して、右クリックします。

すると以下のようにメニューが追加されていることがわかります。






















そしてそれを実行してみると、



のように選択したデータセットがFREQプロシージャにかけられました。


これを応用すれば色々できます。
たとえば、さっき文句をつけたエクセル表示の場合

アクション「ちゃんとしたEXCELで表示」
アクションコマンド「
gsubmit "proc export data= %8b.'%32b'N outfile= 'c:\tmp.xlsx' dbms=excel label replace;newfile=yes;run;options noxwait noxsync;x 'c:\tmp.xlsx';"

とすれば、proc exportで出力したEXCELファイルが開きます
(そもそもproc exportの仕様自体に言いたいことは多いですが...)

で、肝心のアクションコマンドの書き方ですが
%8b.'%32b'Nで、ユーザーが選択したライブラリの選択したデータセットみたいな意味になります。
ここを普通のデータセット名前にしてしまうと、どれ選んで実行しても同じデータセット開いちゃいますからね。

で、どうもアクションコマンドは255までしか記述できないみたいです。
あとマクロを中で使いたい場合は%%testのように%を二度付けするとうまくいくようです。

このあたりに詳しい方がいれば逆に教えてください。情報なさすぎです。

ちなみに、この変の話は、大昔にもちょっと触れていたことがありましたので参考まで

〔記事〕
SASデータセットを開いた時にラベル名ではなく変数名を表示するのをデフォルト設定にする
http://sas-tumesas.blogspot.jp/2013/09/sas_20.html

また、当分、更新ペースはこんな感じか、下手したらさらに遅くなると思いますが
来年もよろしくお願いします

standardプロシジャやstdizeプロシジャで色んな標準化の話

なんかスコアデータみたいのがあって、最低スコアを0にして
最大スコアを100になるように値を変換してということがありました。

z得点だしたり、偏差値だしたり、はしょっちゅうするんで同じノリでproc standardでできるかなと
思ったけど、よく考えてみると、あれ?ってなりました。

proc standardで行ういわゆる標準化は、平均と標準偏差が一定の値になるように変換するわけで
今回みたいに無理やり最低値と最高値を決まった上限下限値にするのと全然違うよな~、平均も標準偏差も
使わないし。

もういいや~、面倒だからsummaryで各変数の最大、最小だして
変換値 =(元の値 - 最小値) / (最大値 - 最小値) * (設定上限値 - 設定下限値 ) + 設定下限値
でデータステップで片づけてしまいました。

で後で、対応するプロシジャってなんになるんだろう、多分stdizeだろうなと思って調べると
やぱりstdizeでした。

stdizeは広い意味での様々な標準化変換を行えるもので、かなり奥が深いです。

さわりだけ見てみましょう

今、以下のようなデータがあります

data q1;
x=1;y=20;z=-5;output;
x=6;y=10;z=5;output;
x=4;y=0;z=-1;output;
x=8;y=0;z=-3;output;
x=2;y=100;z=8;output;
run;











とりあえず基本統計量を見てみましょう
proc means data = q1  n mean std max min median sum maxdec=3;
run;








例えば、データを中心化したいから、平均0に寄せてと言われたら
以下のように各変数の平均を引けばいいわけです。

data q2;
set q1;
 x = x - 4.2;
 y = y - 26;
 z = z - 0.8;
run;

中身と統計量は以下になります。ちゃんと0になってますね

これをstandardプロシジャで行う場合は以下のように書きます
m=0というのが平均0にしている部分ですね

proc standard data=q1 out=a1 m=0;
 var x y z;
run;





















同じ結果になることが確認できました。

次はいわゆる偏差値です std=は標準偏差の指定です
proc standard data=q1 out=a2 m=50 std=10;
 var x y z;
run;

確認






















つづいてm=0 std=1 でz得点 標準得点ってやつですね
proc standard data=q1 out=a2 m=0 std=1;
 var x y z;
run;





















さて一方stdizeプロシジャとはなんなのかという話で、
まずは以下のように何も指定せずにまわしてみます

proc stdize data= q1 out = a3;
 var x y z;
run;




















さっきstandardプロシジャでzスコアだしたのと同じですね。
つまり何も指定しないとデフォルトで標準得点に変換するということがわかりました

実は、何も指定しなかった場合、内部的には以下のコードを実行しているのと同じになります

proc stdize data= q1 out = a3
 method=std
 add =0
 mult=1
;
 var x y z;
run;

method=std というのが標準偏差を使った標準化(変な言い方ですが)であること
add はこの場合、指定する平均
multiは指定する標準偏差になります

ですので偏差値をだすのであれば以下のようにかけます。
methodは省略可です。


proc stdize data= q1 out = a4
 method=std
 add=50
 mult=10
;
 var x y z;
run;



さて、ここまでだとstandardプロシジャと何が違うんだという話ですが
stdizeの味噌はmethod=の箇所にいろいろ指定できて、広い意味でのいろんな基準化ができるというところです

例えば冒頭の例のように0から100にデータを変換する場合

変換値 =(元の値 - 最小値) / (最大値 - 最小値) * (設定上限値 - 設定下限値 ) + 設定下限値にしたがって


data q3;
set q1;
x = ( x - 1 ) / ( 8 - 1 ) * (100 - 0) + 0;
y = ( y - 0 ) / ( 100 - 0 ) * (100 - 0) + 0;
z = ( z -(-5) ) / ( 8 - (-5) ) * (100 - 0) + 0;
run;

と書くところを、stdizeであれば以下のようにできます
mehodがrangeになっているところがポイントです

proc stdize data= q1
out = a5
        method=range
add = 0
mult = 100
;
 var x y z;
run;

結果は同じで




















最低値20 最大値100であれば

data q4;
set q1;
x = ( x - 1 ) / ( 8 - 1 )  * (100 - 20) + 20;
y = ( y - 0 ) / ( 100 - 0 ) * (100 - 20) + 20;
z = ( z -(-5) ) / ( 8 - (-5) ) * (100 - 20) + 20;
run;

を以下のように指定できます。
proc stdize data= q1
out = a6
        method=range
add = 20
mult = 80
;
 var x y z;
run;





















stdizeプロシジャの挙動は、わかると明快で
一貫した以下のルールに従って変換を行うんですね。

変換値 = add指定値 + mult指定値 ×[(元の値 - location(method毎に決まってる)/scale(method毎に決まってる]

method=stdの場合locationは平均値 scaleは標準偏差になります
method=rangeの場合locationは最小値 scaleはレンジになります

メソッドごとのlocationとscaleの設定は以下にまとまっております
http://support.sas.com/documentation/cdl/en/statug/63033/HTML/default/viewer.htm#statug_stdize_sect012.htm

methodに関わらず addのデフォルトは0 multのデフォルトは1になります。

なので、例えば中央値を使って標準化をする場合

proc stdize data= q1
out = a7
    method=median
add = 50
;
 var x y z;
run;




















最後に平均と標準偏差を使った標準化に対して
ノンパラ版として平均の代わりに中央値、標準偏差の代わりに正規化四分位範囲を使った方法が工業分野とかでよくあるらしいです。
すみません、まったくアホなんで詳しくないですが

四分位範囲はmethod=IQR で指定できるので、それに正規化のnormオプションを付けて

proc stdize data= q1
out = a8
    method=iqr
norm
;
 var x y z;
run;
















な感じですかね


ユニークなIDとか作れって言われたらの話

多分需要ないし、方法に工夫もないけど、与えらえた文字列から指定の長さで
ユニークなIDを作成するマクロです。

すでに発行済のIDが入ったデータセットを指定することで
過去に発行したものとも重複しない作成が可能です。

使用可能文字の数と、作成IDの長さの設定によって、生成できるIDの限界数が
決まります。
例えば100万の生成限界なのに90万のIDを発行したりすると実行効率遅いです。
生成限界はできるだけ余裕を持たせてください。

アルゴリズムは単純ですが
生成限界がぶっとんだ値であれば、何十万発行しようが結構速いと思います。

すでに存在するデータセットに付与して作りたい場合は
マクロをちょっと書き換えてsetをいれて、do untilのループをとっちゃえばいいです。


%macro unique_make(outds=,obs=,idlength=,seed=,moji=,ban=);
/*--------------------------------------------------------
outds=作成されるデータセット
obs =作成するオブザベーション数
idlength=作成される文字列の長さ
seed =作成のための乱数シード
moji =使用する文字列(半角英数字)
ban  =データセット指定(ここにあるidは作成されない)
----------------------------------------------------------*/
data &outds(keep = id);
length id $&idlength..;
if _N_ = 1 then do;
declare hash h1
%if %length(&ban) =0 %then %do;();%end;
%else %do;(dataset:"&ban");%end;
h1.definekey('id');
h1.definedone();
end;
x="&moji";
n=length(x);
kumi=n**&idlength;

put 'NOTE:与えられた文字の数は ' n;

put 'NOTE:生成限界は ' kumi;

call streaminit(&seed);

do until(obs=&obs);
do until(okfl=1);
do i = 1 to &idlength;
r=int(rand('uniform') * n +1);
id = cats(id , char(x,r));
if i = 5 then do;
if h1.check() ne 0 then do;
okfl=1;
h1.add();
output;
obs+1;
end;
else id='';
end;
end;
end;
end;
run;
%mend;

以下実行例です。


/*英数字で5桁のIDを10000発行*/

%unique_make(outds=A1
 ,obs=10000
 ,idlength=5
 ,seed=2345
 ,moji=abcdefghijklmnopqrstuvwxyz0123456789
);

/*先に発行したA1とかぶらないようにさらに10000発行*/

%unique_make(outds=A2
 ,obs=10000
 ,idlength=5
 ,seed=6789/*シード変えた方がよい*/
 ,moji=abcdefghijklmnopqrstuvwxyz0123456789
 ,ban=A1
);