divide関数で0除算によるログが汚れるのを防ぐ

ゼロ除算、つまり、値を0で割り算するのは少なくともプログラムの世界では有名なタブーです。
数学的な議論、説明付けはゼロ除算で検索していただくとして、

まあ他の言語はさておき、SASは0で割ったとして、いきなり強制終了したり。無限ループに陥ったりはしないので、普段そこまで強く意識しないことが多いのではないでしょうか?

以下のデータセットがあるとします

data Q1;
X=2;Y=1;output;
X=1;Y=0;output;
X=0;Y=0;output;
X=-1;Y=0;output;
run;








これに対して

data E1;
 set Q1;
  Z=X/Y;
run;

とするとログは












とまあ、うるさい感じになってしまいます。
結果は省略しますが、2obs以降のZは全て欠損値です。


data A0;
 set Q1;
 if Y^=0 then Z=X/Y;
run;

とすれば、回避ですが、その他にdivide関数をというやつを使えます。

data A1;
 set Q1;
 Z=divide(X,Y);
run;












となってログは綺麗です。

で肝心の中身をみると








え、I ? M ? なにそれ??

SASのdivide関数のヘルプみていただければいいんですが
これはspecial missing values、特殊欠損値で
Iは無限大インフィニティのI、Mはマイナス無限大を示します。
こうみえて数値型です。

正の値を0でわると無限大、負の値だとマイナス無限大になってるよということを
教えてくれます。

data A1_;
 set A1;
 where Z=.I;
run;

たとえば、このようにすれば、0除算が発生し、なおかつ正の値/0のみを選択的に抽出できます。
どういった時にそういった状況になるかはしりません。

data A1;
 set Q1;
  Z=coalesce(divide(X,Y),.);
run;

欠損値に値を充てるcoalesce関数に欠損値を指定するという珍妙なコードをかけば
みなれたいつもの「.」欠損値に戻せます。










詰めSAS9回目:Fizz_Buzz問題をSASで解く

Fizz_Buzz問題という言葉を聞いたことあるでしょうか?

これは、プログラミング言語を問わずに、その人が本当にその言語の基本的素養を持っているかを簡易に判別する有名なテスト法です。

問題は以下の通りです。

===========================================================
1から100までの数を出力するプログラムを書け。
ただし3の倍数のときは数の代わりに「Fizz」と出力、
5の倍数のときは「Buzz」と出力、
3と5両方の倍数の場合には「FizzBuzz」と出力すること
===========================================================

これを実現するコードを2分以内に作成できれば、OKというものです。
3の倍数だけアホになるようなことをプログラムでしてみろということです。

いろんな言語、たとえばCやJavaやRubyやPerlやVBAやRやら、いろんな言語で
解答が有志によって公開されているんですが、ざっと探したところ
SASが見つからないのです(たぶん誰かすでに絶対公開してるとは思いますが)。

まあFortranによる解答はすぐ見つかって、SASはFortranの影響を強く受けていて
コードも似た感じなので、誰も敢えてやらないのかもしれませんが、、

Rですぐ見つかるのにSASですぐ見つからないのは癪なので、ここで勝手にけりをつけます。

data _NULL_;
file print;
do X=1 to 100;
 if mod(X,15)=0 then put 'FizzBuzz';
 else if mod(X,3)=0 then put 'Fizz';
 else if mod(X,5)=0 then put 'Buzz';
 else put X;
end;
run;

これでどうでしょうか??























結果は多分あっていると思うのですが、自信はありません。
最善手かどうかもわかりません。

他に解答があれば是非ご意見をください




SAS Support Communitiesの紹介と、日本でもSASの情報交流を活発化させたいなという話

まず海外の話ですが、SAS Support Communitiesというコミュニティサイトがあります。

https://communities.sas.com/community/support-communities

ここでは、SASユーザーが様々な情報を発信・共有しています。
何かわからないことや、いろんなやり方を知りたい、最善手を探求したいと思ったら
ここでアカウントを登録して、スレッドをたてれば、すぐにいろんな人が思い思いのコードを
貼り付けて返してくれます。

また、定期的にメールが届いて、現在進行中の最新のスレッドについて教えてくれます。

英語が得意な方、いや、得意でなくてもプログラマはコードで会話できるので是非活用してみると
よいと思います。


やっぱりアメリカのSAS環境はいいなぁと思います。本もいっぱいでているし、論文もいっぱいWebにアップされているし、コミュニティも多いし、ブログ書いている人も多いし。

まあ、英語勉強すればいいんですけど、、最近更新されていなくて、ブログをうつされたようですが「日本のSASプログラマに救済を」というサブタイトルでブログをつけていらした方がいっらっしゃって、そのサブタイトルをみた時に、あぁそうだなぁと思いました。

やっぱり、例えば、いきなり初心者に海外サイトや英語のSAS本みて、SAS覚えろってのはちょっと酷ですし、ちょっとしたやり取りや、情報交換はわいわい日本語で気軽にできた方が楽しいですよ。


で、本題は、相互リンクしていただけるSASのサイト、またはブログを募集していますという話です。
今日から始めたばかりのブログで結構です。
内容が間違っていても偏っていても、初心者向けでも、マニア向けでも、分野も金融経済社会科学システム医薬なんでもよくて、
とにかく、日本のSASのWeb情報をつなぎたいなぁとか思っています。

日本全体としてプログラマの層を広く厚くしていかないと、中国とかインドに全部もってかれちゃいそうで、なんとなくそれは嫌だなぁと思います。

PC版画面でみれば画面の右にリンクがでているはず?

宜しくお願いします。

BYグループごとにある変数が最大の値を持つレコードのみ残す、ただし同値で複数存在する場合は複数レコードを残す処理_RANKプロシジャは使える子という話

ある変数が最大のオブザベーションのみ残す処理と聞くと、まず思いつくのはソートして、LAST.でオブザベーションを残す方法かなと思います。

ただし、次のようなケースならどうでしょうか。

いま、以下のデータセットがあるとします。

data Q1;
X=1;Y=1;Z='い';output;
X=1;Y=2;Z='ろ';output;
X=1;Y=3;Z='は';output;
X=1;Y=3;Z='に';output;
X=2;Y=3;Z='い';output;
X=2;Y=4;Z='ろ';output;
X=3;Y=.;Z='い';output;
X=4;Y=5;Z='い';output;
X=4;Y=5;Z='ろ';output;
X=4;Y=5;Z='は';output;
run;
proc sort data=Q1;
 by X;
run;














このデータセットで、例えばXでグループ化して、その中で最大のYを持つオブザベーションを残す
とします。例えばX=2のグループであれば、Y=4のオブザベーションだけ残したいわけです。
ただし、X=1のグループではY=3が最大なのですが、Y=3のオブザベーションは2つあります。
その場合は2オブザベーション残したいとします。

その場合の処理を考えてみます。

まあ、まずはXの値ごとのYの最大値をmeansだのunivariateなんだので出して、それと元データをマージしてくっついたやつだけ残すとか、SQLでサブクエリにすればいいとか、まあ色々方法はあると思いますが、

こういったケースで個人的に一番いけてると思っているのはrankプロシジャです。

proc rank data=Q1 out=A1(where=(RANK in (.,1))) ties=low descending;
var Y;
ranks RANK;
by X;
run;

これで結果は










はい詰んだ!

もし欠損値を認めない場合は(where=(RANK =1))としてください。

ranksステートメントをつけないと、Yの中身が順位番号データに上書きされるから要注意!
ties=lowは同じ値がたくさんあった時に、順位が一番小さくなる値を採用するというもので
同着1位が三人いたら、三人とも一等賞という指定です、ここをhighにすると
同着1位が三人いたら、三人とも三等賞という指定です。
他には例えば、平均にもできますし、他のオプションと組み合わせると色々面白いので
ヘルプみてください

descendingは降順に順につけてます。


RANKSプロシジャって結構色々応用できて、楽しいですよ。
古くからあるのに使えねぇプロシジャだと言った人は猛省してください。













LIBNAMEとVBAで、EXCELとSASをつないで対話的アプリケーションにする

EXCELにはEXCEL VBAという便利な言語がくっついているので、これを使ってEXCELからSASを実行することができます。

またSASのLIBNAME EXCEL機能を使えば、EXCELの値を自由に読み込み&書き込みできます。

この二つを組み合わせれば、みんな大好きエクセルさんをインターフェースにして、そこに
ユーザーが入力した値をSASに渡して、処理を行い、処理結果をエクセルに返すことができます。


その簡単な例を紹介します。
今、「D:\FACE.xlsm」というマクロつきのエクセルブックがあったとします。
黄色の部分にそれぞれの計算結果が入れたいとします(黄色セルは表示形式:数値)












そして、「D:\計算.sas」というSASファイルがあり、
その中身は以下のコードだとします。

libname EXLIB "D:\FACE.xlsm" header=no scan_text=no;

data Q1;
 set EXLIB."Sheet1$D8:D8"n ;
 rename F1=X;
run;

data Q2;
 set EXLIB."Sheet1$F7:F7"n ;
 rename F1=Y;
run;

data Q3;
 set EXLIB."Sheet1$F9:F9"n ;
 rename F1=Z;
run;

data A1;
 set Q1;
 set Q2;
  A=X+Y;output;

  A=.;output;

 set Q1;
 set Q3;
  A=X+Z;output;
run;

data EXLIB."Sheet1$H7:H9"n ;
 set A1;
 modify EXLIB."Sheet1$H7:H9"n ;
  F1=A;
run;


何をしているかというとエクセル数字の入っている3セルを読み込んで
計算結果を黄色のセルに出力しているわけです。


で肝心のEXCELからSASを起動する方法ですが
「計算ボタン」に以下のコードを結び付けています。

Sub ボタン1_Click()
Dim sasobj As Object
Set sasobj = CreateObject("SAS.application")
sasobj.Visible = False
sasobj.Submit ("%inc 'D:\計算.sas';")
sasobj.Submit ("ENDSAS;")
End Sub

これだけです。
実行ログファイルが欲しい場合はprinttoプロシジャ等をいれましょう。


以上の準備が整えば、あとはエクセル上のボタンをクリックすれば











と一瞬で計算結果がエクセルに返ってきます。
endsasをいれているので、SASも勝手にとじます。


この一連の流れは結構使えると思います。


あと、これは勝手なお願いですが、LIBNAME EXCELは便利で、DDEにはない長所をいっぱい
持っているんですが、いまいちまだ解らない部分や、誰も試していない部分が多くて
DDEのように枯れた知識になってない感じがあります。

新しく発見された部分があれば、どんどん共有していけたらと思います。








SAS書籍紹介⑨_SASプログラミング

書籍案内⑧のSASハンドブックと同じ著者の方々で、
内容についても、割とそのまま記載されている部分が多いです。
ただ、ハンドブックの初級者向けと思われる部分がなくなり、
代わりにより深く加筆修正されています。

特に個人的に思ったのが、SQLの部分により踏み込んだなというところと
SG系でもSGRENDER、つまりproc templateでGTL書くあたりの記述が加筆されたなという
感じです。

SASハンドブックと両方買いそろえる必要は、、ないかなぁといった個人的感想です。

『書名』SASプログラミング
宮岡 悦良 (著), 吉澤 敦子 (著) 
出版社: 共立出版 (2013/9/25)


SAS書籍紹介⑧_SASハンドブック

データステップ、マクロ、SQL、SG系グラフ、G系グラフ、IMLについて
全体的にまとまった本で、どなたかがブログか何かのレビューで「SASの幕の内弁当」と評していたのですが
確かに、だいたいそんなイメージです。

ods graphicsで作成されるグラフやSG系プロシジャ、ないしはIMLについて、
日本語で説明されているテキストが極端に少ないので貴重だと思います。

ただ、結構、コードが複雑な部分があって、中級者でもぱっと見結果が予測できないコードがあったり
runステートメント省略してたり、インデントが(僕から見て)変わっていたり、若干癖があるなと感じました。
全くの初心者に入門書として進めるのであれば、「統計解析ソフトSAS(工学社)」か
「SAS® 認定プロフェッショナルのための Base Programming for SAS® 9 完全ガイド」の方がお勧めで
この本は、中級者の手元に一冊といった感じでしょうか、個人的感想ですが。

『書名』SASハンドブック 
宮岡 悦良 (著), 吉澤 敦子 (著) 
出版社: 共立出版 (2011/7/9)



SAS書籍紹介⑦_Carpenter's Guide to Innovative SAS Techniques

Carpenter(大工)というだけあって、表紙は大工道具のイラストになっていて、
やっぱ外人は洒落てるなぁと感心します。

どういう本かというと、このブログみたいな本です。というと偉そうになってしまうのですが
要は普段よくぶつかる処理に対して、様々なやり方をひたすら紹介するテクニックツール集です。
だから大工なんですね。

結構マニアックで、SASプログラムでメールを送る方法等、本で紹介されているのを見たことない分野がふんだんに書かれていて、データステップマニア必読?の書となっています。

『書名』Carpenter's Guide to Innovative SAS Techniques
Art Carpenter (著)
出版社: Sas Inst (2012/3/2)

SAS書籍紹介⑥_Simulating Data with SAS

シミュレーション処理に絞ったSAS本ってニーズ高そうなのにあんまりないなぁと
思っていたら、今年の4月にでましたSimulating Data with SAS 。
まんまなタイトルで、様々な分布に基づくシュミレーションデータの発生やそれを用いた
シュミレーション法、特に基本的な注意点や取り組み方を教えてくれる本です。
IMLを用いる場合と用いない場合で、きちんと両パターン解説してくれているので
とてもいい本だと思いました。
なんだか小学生の感想みたいになってしまいました。


『書名』Simulating Data With SAS
Rick Wicklin (著) 
出版社: SAS Institute (2013/4/25)


SAS書籍紹介⑤_Implementing CDISC Using SAS: An End-to-End Guide

医薬関連の方以外にはなじみのない話になってしまいますが、
新しい医薬品を公的機関に、データをもって申請する際に、色んな会社が
統一された規格で申請することによって、審査がスムーズになったり、データを併せて
新たな知見を得ることができたりします。

その規格パッケージとしてCDISCというものがあり、実際のファイル形式は事実上標準としてSASが使用されます。

この本ではCDISCの中で、SDTM,ADaMの説明と基本的な導出、バリデーションについて実践的にかかれています。また特徴的なのが、define.XMLというデータセットの構造等を記述するXML形式のファイルを作成するサンプルプログラム(excelでパラメータシート的なものを作成しそれをSASで読み込んでXMLファイルを作成)がついていて、その説明が詳細であることだと思います。

SAS社が行ったCDISCのセミナーでもテキストとして使用されたことがあり、しっかりとした本です。

『書名』Implementing CDISC Using SAS: An End-to-End Guide

Chris Holland (著), Jack Shostak (著)
出版社: Sas Inst (2012/11/23)




FREQ,MEANSなどで集計する時に、各グループと全体で集計できるようにデータセットを加工しておく話

基本的なテクニックなのですが、僕がSASをはじめたばかりの頃に教えてもらって、おぉ!となったことを思い出したので書きます。

以下のようなデータセットがあったとします。

data Q1;
length GROUP $10.;
GROUP='A群';VAL='い';output;
GROUP='A群';VAL='ろ';output;
GROUP='A群';VAL='は';output;
GROUP='A群';VAL='い';output;
GROUP='B群';VAL='ろ';output;
GROUP='B群';VAL='ろ';output;
GROUP='B群';VAL='い';output;
GROUP='B群';VAL='ろ';output;
run;












これを各群ごと、かつ全体で集計するやりかたが、全然わからなかったのですが












data _Q1;
 set Q1
     Q1(in=ina);
  if ina then GROUP='全体';
run;

proc freq data=_Q1 noprint;
 table GROUP*VAL/out=A1(drop=PERCENT);
run;

でいいわけです。

流し込むデータを2倍にしてから集計するという発想がなかったので凄く驚いた覚えがあります。
今思うと、この時に、SASのデータステップは面白いなとスイッチが入った気がします。

水準がデータで揃わずスカスカの集計表に立ち向かう_meansやsummaryのclassdata=とtransposeのid 複数変数を利用して

以前、meansまたはsummaryプロシジャのclassdata=オプションは集計表を作成するうえで役立つと言い詳細はまた紹介すると書きました。
(ちなみにmeansとsummaryの違いは、デフォルトでアウトプット出力するかどうかです。つまりmeansにnoprintをつければsummaryと同じで、逆にsummaryにprintをつければmeansと同じです。)

またtransposeプロシジャのIDステートメント複数づけのおかげで、集計表が作りやすくなったとも言い、実例をいつか紹介すると書きました。

遅くなりましたが、ざっくりとした流れを紹介したいと思います。

今、以下のようなデータセットがあるとします

data Q_1;
GROUP='A';SUBGROUP='X';LEVEL1=2;LEVEL2=1;LEVEL3=5;output;
GROUP='A';SUBGROUP='Z';LEVEL1=1;LEVEL2=0;LEVEL3=4;output;
GROUP='B';SUBGROUP='Z';LEVEL1=1;LEVEL2=0;LEVEL3=3;output;
run;






で、GROUPはA群とB群がいて
それぞれのグループにSUBGROUP X、Y、 Zがあるとします。
そして、3つの変数があり
LEVEL1は1-2の値をとります。
LEVEL2は0-1の値をとります。
LEVEL3は3-5の値をとります。

ところが、今まだデータが集まっていない、またはデータの収集が打ち切られた等の
理由で、たった3オブザベーションしかありません。

このわずかのデータで
















のようなEXCELの集計表テンプレートに出力する必要があるとします。
つまり、大半のセルは0になります。

どんな言語でも、あるデータをだすのは簡単ですが、ないデータをだすプログラムは難しいものです。とりあえず先にコード全部のせます。

data CLDS;
 do GROUP='A','B';
  do SUBGROUP='X','Y','Z';
   do DUMMY=0 to 20;
    output;
   end;
  end;
 end;
run;

/*================================================
マクロ名:syogi
引数-①dsname 対象データセット
   ②varname 対象変数
   ③minval 対象変数のとりうる最小のカテゴリ数値
   ④maxval 対象変数のとりうる最大のカテゴリ数値
=================================================*/
%macro syougi(dsname,varname,minval,maxval);
 proc means data=&dsname.
     classdata=CLDS(rename=(DUMMY=&varname.) where=(&minval.<=&varname.<=&maxval.))
     noprint nway exclusive;
     class GROUP SUBGROUP &varname.;
     var &varname.;
     output out=&varname._(drop=_TYPE_ _FREQ_) N=COUNT;
 run;

 proc sort;
  by &varname.;
 run;

 proc transpose data=&varname._ out=_&varname.(drop=_NAME_ rename=(&varname.=VAL))  delimiter=_;
  var COUNT;
  id GROUP SUBGROUP;
  by &varname.;
 run;
 %mend;

/*実行*/
 %syougi(Q_1,LEVEL1,1,3)
 %syougi(Q_1,LEVEL2,0,1)
 %syougi(Q_1,LEVEL3,3,5)

 data A_1;
  set _:;
 run;

/*EXCEL出力*/
 libname OUTEX "D:\集計.xlsx" header=no scan_text=no;

data OUTEX."Sheet1$D11:I18"n ;
 set A_1;
 modify OUTEX."Sheet1$D11:I18"n ;
  F1=compress(put(A_X,best.));
  F2=compress(put(A_Y,best.));
  F3=compress(put(A_Z,best.));
  F4=compress(put(B_X,best.));
  F5=compress(put(B_Y,best.));
  F6=compress(put(B_Z,best.));
run;

libname OUTEX clear;











まず、CLDSというのを作っています。
これはクラスデータセットといって、meansのクラスで指定する変数の、フルパターンをクラスデータセットに作成しておくことで、カウントであれば実際のデータの水準が足りなくても0で補完して計算してくれます。

ただ、1つの変数に対しての集計ならいいのですが、複数で、かつ採りうる値が変数ごとに違う場合、変数分それに対応するクラスデータセットをつくってたら煩雑すぎます。

なので、通常、群やサブグループなど固定のクラス変数は固定して、それ以外のカテゴリ値の部分は一端ダミーの変数名にして、大きめにデータセットを作っておきます。

以下がCLDSの中身(一部です)です。






















(一部です)


それで、マクロの中で、実際に集計する対象の変数を指定して、ダミー変数をその変数名にrenameして、かつそのカテゴリ値の採りうる値でwhereでクラスデータセットを絞っています。
以下が、マクロにLEVEL1を指定した時の、meansからoutされたデータセットです。
欠損水準を0で補って、フルセットで集計されています。





















しかしこのままだと縦持ちなのでこれをグループ、サブグループで転置します。

以下は上記のデータセットがtransposeされた後のデータセットです







delimiter=でアンダーバーを追加しています。
たとえば、A_XはグループAのサブグループXの集計結果というわけです。

それでLEVEL1-3までを集計してその結果をコロンモディファイア指定でSetしてつなぎます。
最後にLIBNAME EXCELでだしていますが、DDEでもなんでもいいです。

ざっくりとした例なので、実際に使用される場合は、適宜書き換えてください(とりあえずマクロ名変えましょう)。そのままべたっと貼っても動きません多分。

で注目点はクラスデータと、実際のデータのフォーマットやラベルなどのメタデータが同期していることが必要なのでLENGTHを合わせたり、

proc datasets nolist;
 modify Q_1;
  attrib _all_ informat= format= label='';
quit;

などで、余計なメタデータを消しておいた方がいいです。
classdata=使うといつもエラーになって、あきらめますという話を聞くのですが
大半はここが原因です。

あと最後に、若干ネタ切れ感がでてきたので、取り上げてほしい部分や、詰めSASの問題など
アイデアがある方はご連絡ください



大規模データのソートを行う際に、一時的なディスク容量オーバーの問題をUTILLOCオプションでUTILファイル作成先を逃がすことで回避する

SASでシュミレーション等の処理の際に、実行時間を短縮する方法はいろんな方がたくさん提言されていてとても深い世界ですので、ここではあまり触れません。

ただ、一般的に大規模なデータセットを作成してbyでシュミレーション回ごとに解析プロシジャを回すことが多いと思いますが、その過程で大規模なデータセットに対してソートプロシジャをかけることがあると思います。

例えば、今、WORKが割り当てられている領域のディスク容量がちょうど100GBであったとして、そこに100GBのデータセットを作成して上書きでソートプロシジャを掛けることは可能でしょうか?

答えは無理です。
SASは効率的に処理するためにUTILファイル(ユーティリティファイル)という一時ファイルを作成します。処理が終わると消えるファイルなので正確に測定していないのですが、どうもほぼ、元ファイルと同じくらいの容量になっている気がします。

なので100GBのデータセットを作成してソートプロシジャを実行する場合、一時的にですが多分およそ200GBの容量が必要になります。
この問題を解決する一番単純な方法は、容量が大きいディスクを用意して、そこで回すということです。

まあ、それで話が終わりなのですが、例えばCドライブ100GB、Dドライブ100GBの容量が空いているパーテーション構成だとすれば、わざわざパーテーションを組みなおしたり外付けハードディスクなどを用意しなくても、
WORKはCドライブ、UTILファイル作成先はDドライブにすれば回りきるわけです(実際はそこまでギリギリにすると不味いとおもいますが)

その場合、SASのコンフィングファイル「SASV9.CFG」に事前に設定を仕込む必要があります。
パスは環境や設定によってことなりますがwindowsなら
(C:\Program Files\SAS\SASFoundation\9.2\nls\ja)等に存在するのがデフォルトでしょうか?

それををwin7の場合、管理者権限で開いて
以下のようにWORKの絶対パス指定の下に「-UTILLOC "D:\"」と打って保存する

-WORK "!TEMP\SAS Temporary Files"
-UTILLOC "D:\"

これでUTILファイルの作成をDドライブに逃すことができます。


SQLでデータをランダム順に並び替える_order by ranuni() おまけで非復元無作為抽出

データを単にランダムな順番でシャッフルしたいこと、、あんまりないと思います。
データステップなら多分、乱数を作成するステップと、その乱数でソートするステップで2ステップ必要だと思います。

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

data Q1;
do X= 1 to 20;
 output;
end;
run;























その場合、
data _Q1;
 set Q1;
  SORTKEY=ranuni(2013);
run;























となり、それを以下のようにソートすると

proc sort data=_Q1 out=A1(drop=SORTKEY);
 by SORTKEY;
run;






















となり、ランダムな順番に並び変わりました。

まったく同様の処理をSQLの場合、よりスマートに表現できます。

proc sql noprint;
 create table A2 as
 select X
 from Q1
 order by ranuni(2013)
 ;
quit;

で同じ結果となります。
order byにいきなりranuni関数を指定できるところが味噌ですね。

で、まあ以下のようにoutobs=オプションを使えば、ログに、処理が途中で打ち切られたという
WARNINGがでますが、無作為抽出ができます。
10のデータを抽出する場合、

proc sql noprint outobs=10;
 create table A3 as
 select X
 from Q1
 order by ranuni(2013)
 ;
quit;


















となり、要は前の結果のうち10obs目までを出力しているだけですが、結果としてランダムサンプリングしたような結果となります。


まあ、しかしバージョン8以降はsurveyselectプロシジャを使うことで、様々な標本抽出を表現できるようになったので、よほどの理由がない限りはそちらを使った方がいいと思います。

標本10の単純な非復元無作為抽出であれば

proc surveyselect data=Q1 noprint
 method=srs
 n=10
 seed=2013
 out=A4;
run;
















のようになります。
処理が異なるため、ここでのseedの指定を前のプログラムのものと同じにしても
結果は違うので、古いコードをリファクタリングする場合は注意してください。


SQLのCASE式がどうしても苦手な場合、IFC、IFN関数で代用してみる

SQLにIF文はないのかは、よく尋ねられる話です。MySQLなど特定の製品の独自関数として定義されている場合を除いて、基本的にSQLにIFはありません。

SQLでの条件分岐はCASE式を使うのですが、普段書いているSASのIFステートメントと少し違うので、なかなか書き方がぱっとでてこず、調べたりしてしまうことが多い方もいると思います。
条件分岐が面倒だからSQLは使わないという方もいたりするので、邪道かもしれませんが、SASでのSQLの使用を普及させたい僕としては、どうしてもCASE文が嫌いならとりあえずIFC,IFN関数を使ってみればと提案しています。

戻り値が数字の場合はIFN、文字の場合はIFC関数になります。
この関数はEXCELのIF関数と同じで

IFN(条件式,真の場合の戻り値,偽の場合の戻り値)といった感じで使います。

まず通常のデータステップで説明します。

以下のデータセットがあったとします。

data Q1;
X=1;Y='い';output;
X=2;Y='ろ';output;
X=3;Y='は';output;
run;







data A1;
 set Q1;
 _X=IFN(X=1,X*10,X*20);
 _Y=IFC(Y='い','A','B');
run;

を実行すると






こうなります。

関数を入れ子にすることで、階層的な条件分岐を表現できます。

data A2;
 set Q1;
 _X=IFN(X=1,X*10,IFN(X=2,X*20,999));
 _Y=IFC(Y='い','A',IFC(Y='ろ','B','Z'));
 run;

を実行すると







となります。


これをSQLのCASE式でかくなら

 proc sql noprint;
  create table A3 as
   select X,Y
    ,case X
     when 1 then X*10
     when 2 then X*20
     else 999
     end as X_
     ,case Y
     when 'い' then 'A'
     when 'ろ' then 'B'
     else 'Z'
     end as Y_ length=200
   from Q1;
  quit;

となります。改行の仕方によりますが、やはりぱっと見、ボリュームが、もさっとしがちに感じてしまします。

そこで、IFC IFN関数を使用してみます。

 proc sql noprint;
  create table A4 as
   select X,Y
    ,IFN(X=1,X*10,IFN(X=2,X*20,999)) as _X
    ,IFC(Y='い','A',IFC(Y='ろ','B','Z')) as _Y
   from Q1;
  quit;

こざっぱりします。ただ条件分岐が多くて入れ子にすると深すぎるなら、
CASE式の方が見通しやすいと思います。

まあ好みの問題ですが、無理してでもCASE式の書き方に慣れた方が、他のシステムで
SQL書くときにも使えるので、いいとは思います。





SQLプロシジャで_N_やfirst. last.の処理を疑似的に表現する_列番号を返すmonotonicを利用して

SQLで_N_やfirst. last.を使えますか?と聞かれることがあります。

SQLは本来、データ格納の位置やソート順に縛られない言語なので
そういうのはデータステップでやってくださいなというところです。

実際、標準SQLには列番号を取得するような関数は意図的に存在しないのですが
実はSASのSQLプロシジャには、monotonic関数という独自関数があります。
こいつを使うことで、_N_やfirst. last.と結果が同じになる処理を行うことはできます。

本来のSQLの理念に反しているということで、賛否両論、批判的な人も多いですが
まあ、実際あるもんだから、知ってて損はないはずです。

以下のようなデータセットがあったとします。

data Q1;
 do X=1 to 5;
  do Y=1 to 3;
   output;
  end;
 end;
run;



















if _N_=3で絞ってデータセットを作る処理、つまり3番目に格納されているobsのみ残すのを
をSQLプロシジャでやるなら

proc sql noprint;
 create table A1 as
  select X,Y
  from Q1
  where monotonic()=3;
quit;





となります。
monotonic()がいわゆる行番号を返します。

つまり

proc sql noprint;
 create table A2 as
  select X,Y,monotonic() as N
  from Q1;
quit;

の結果は



















となります。

で、ここからは応用でfirst.Xまたはlast.Xのみを残す処理をかいてみます。
多分、すでに予想がついているかと思いますが、案の定group byとhaving及び、minまたはmax関数を使います

first.Xは

proc sql noprint;
 create table A3(drop=N) as
  select X,Y,monotonic() as N
  from Q1
  group by X
  having min(N)=N
;
quit;

で結果は









となりlast.Xは

proc sql noprint;
 create table A4(drop=N) as
  select X,Y,monotonic() as N
  from Q1
  group by X
  having max(N)=N
;
quit;









となります。

まあ、あくまで結果を同じにしているだけで、データステップと同じように動いているわけではないので注意。

【追記】
う~ん、matsu a  さんもリンク先で指摘されているので、あんまり頼らない方がいいかもしれませんね。。
http://sas-boubi.blogspot.jp/2014/02/sqlmonotonic.html



LIBNAME EXCELでSASからエクセルにデータセットを出力する際 dblabel=YESで変数名ではなくラベルがでるようにする

以前、options validvarname=any;のところでLIBNAME EXCELで列名として自由なテキスト出す際に命名規則をanyにして変数名を日本語にしたデータセットをつくれば、あとは出すだけだけど、筋が悪いやりかと、書きました。

じゃあその場合の、正しいやり方は何ですか?と質問をうけたので、それについて書きます。
あ、今回のやつを実行するにはSAS ACCESSのライセンスが必要で、かつ9.2以降です。

今、ラベルのついた以下のデータセットがあったとします。
data Q1;
X='い';Y='ろ';output;
X='は';Y='に';output;
label X='ラベルX' Y='ラベルY';
run;

存在しないパスに対してLIBNAMEを指定して、新しいエクセルファイル「AA.xlsx」を出現させ、
そのままシートA1とA2を作成するコードを実行してlibnameを解除します。
A2のほうには(dblabel=YES)を指定しています。

libname OUTEX "D:\AA.xlsx" header=yes scantext=no;

data OUTEX.A1;
 set Q1;
run;

data OUTEX.A2(dblabel=YES);
 set Q1;
run;

libname OUTEX clear;


そしてエクセルを開いて、2つのシートを比較してみます。

【シートA1】








【シートA2】







といった感じです。

状況は限定的ではあるが、巨大なデータセットと小さいデータセットを結合する場合に、call executeとproc appendを利用した極めて効率的な方法

巨大なデータセットと小さいデータセットの情報を両方使って処理をする。
処理効率をよくするにはどうするか(実行時間の短縮)??

典型的な例なのでハッシュオブジェクトの利用を思いつく方が多いかもしれません。
(私事ですがハッシュオブジェクトについては現在研究中で、 来年のユーザー総会の発表テーマにしたいと思っているので取り上げる回数が少ないかもしれませんが、軽んじているわけではないです。)

しかし、内部結合した結果のみ残すという条件と、小さい方にあるキー値がかならず大きい方に含まれているという限定的な条件が加わった場合(両方にキーが存在するobsのみ残す)においてのみ、劇的に処理効率のよい方法があります。

論文[Merging Data Eight Different Ways]や[Countdown of the Top 10 Ways to Merge Data](著者は同じ)でも紹介されている、call executeとproc appendを使った離れ業です。
論文中のDATA=dat0の部分はDATA=alldatの誤記か誤植かと思いますが、それにしても
ぶっとんだ発想なので、まねてみます。

data Q1;
 do X=1 to 1000000;
   Y=X*2;
  output;
 end;
run;




















(画像は途中まで)

という100万オブザベーションのデータセット(欲をいうと変数Xにインデックスがついてると最上ですが)と

data Q2;
 X=123;Z='い';output;
 X=333;Z='ろ';output;
 X=423;Z='は';output;
run;







という3obsという小さいデータセット(今回の方法では小さいほうが、obs数が少なければ少ない程効率がよい)があって

変数Xを使って結合するとします。
求める結果は







こんな感じです。

考え方として、100万の方に対して、3obsしか読みこみたくないわけですね。100万全部読み込んでから3obsを残す処理にすると、どうしても読み込み時間がかかるのでそれを回避したいのです。
じゃあ、whereを使って、抽出したいわけ(サブセットIfと違いwhereは読み込む前に抽出する)ですが、その条件とQ2、結合したいデータもQ2に入っちゃているわけです。

じゃあどうすんの、で、以下の回答です。

DATA _NULL_;
 set Q2;
 call execute("data Q3;
                     set Q1;
                     where X="||X||";
                      Z='"||Z||"';
                    run;
                  proc append base=A1 data=Q3 force;
                  run;");
run;


小さい方のQ2をセットしてデータステップを開始して1obs読み込むごとに読み込んだXの値で
でかい方のQ1にwhereで抽出をかけて、そこに読み込んだZを加えた上で一時的にQ3という
データセットを作り、proc appendで本来作成したい最終結果となるA1にQ3を追加しているのですね。call executeの部分が疑似的なループ処理になるので、回数が少ないほど、つまり、Q2のobs数が少ないほど良いといったのはそういう理由です。

多分コードだけでイメージをつかみにくいと思うので、実際に実行してみてください。
ログにcall executeで実行されたラインがでてくるので、それをみると何が起きたのかわかりやすいかと思います。






CALL EXECUTEで、マクロループみたいなことを平コードで実現する_条件に合致するデータセットに対して処理を掛ける例

CALL EXECUTEというと、データステップ内で発生した値をマクロの引数にして実行する時によく使う機能でマクロとセットで捉われがちですが、
本来、CALL EXECUTEの引数を必ずマクロ化した処理にしなければならないわけではありません。


例えば今、以下のようにDS1,DS2,DS3,DS4と適当なデータセットが4つあったとします。

data DS1;
 do X=1 to 10;
  output;
 end;
run;
data DS2;
 do X=10 to 20;
  output;
 end;
run;
data DS3;
 do X=20 to 30;
  output;
 end;
run;
data DS4;
 do X=30 to 40;
  output;
 end;
run;

そして、以下のLISTというデータセットがあったとします

data LIST;
 DSNAME='DS1';FLAG='Y';output;
 DSNAME='DS2';FLAG='N';output;
 DSNAME='DS3';FLAG='N';output;
 DSNAME='DS4';FLAG='Y';output;
run;







FLAG変数がYとなっているDSNAMEのデータセットに対して
要約統計量の入ったデータセットを作成したいとします。

その場合、以下のようにLISTをデータステップでsetし
ifステートメント後にcall executeで、実行したい処理を文字列として記述します。
処理の引数にしたい部分は、データステップの変数なのでコーテーションでくるまないように
気をつけます。

data _NULL_;
 set LIST;
 if FLAG='Y' then
 call execute("proc summary data="||DSNAME||";
               var X;
               output out="||DSNAME||"_RESULT;
               run;");
run;

これで実行するとDS1とDS4に対してのみproc summaryが実行され
DS1_RESULTとDS4_RESULTが作成されます。





















もし、マクロ化してからcall executeするのであれば以下の感じでしょうか。

%macro m_d(ds);
  proc summary data=&ds.;
   var X;
   output out=&ds._RESULT;
  run;
 %mend m_d;

 data _NULL_;
  set LIST;
  if FLAG='Y' then
  call execute('%m_d('||DSNAME||')');
 run;

options validvarname=anyでSASの変数命名規則を無視して、日本語の変数名とか作っちゃう話

受け取ったSASデータセットが開けない。
繰り返されるよくある話で、原因はいろいろ咲き乱れですが、
ログやエラーメッセージからだいたい原因はわかります。

フォーマットをロードできないとかでてたら、フォーマット参照の指定を忘れていたらfmtsearchで
指定。フォーマットがなぜかないならnofmterrでとりあえず開けます。
OS等、プラットフォームの違いによるものであればCPORTプロシジャでカタログ移送形式に変換して
からデータを貰って、CIMPORTで戻しましょう。


実は最近、SASデータセットを受け取って、そこからWORKにコピーしようとしたら














とメッセージがでて、移動できなかったんですね。
ダブルクリックで開こうとしても同じメッセージが出て、ログには何もでませんでした。
あれ、このパターンはなんだっけ??

メッセージの意味もよくわかりませんでした、列1が無効なSAS名??
しばらく考えて、あ、もしかしてと思って

options validvarname=any;

としてから、そのデータセットを開いてみると





となっていました。
この「列1」というのラベルじゃなくて、ガチで変数名です。
つまり、

data Q1;
列1='い';列2='ろ';列3='は';output;
run;

のようなデータセットで、なんと変数名に日本語が使われていたのです。
そんな特殊なデータセット送ってこないでよ、、。

validvarname=anyを使うと、SASの変数名に対する命名規則が適用されなくなり
日本語だろうがなんだろうが変数名にしてコードがかけるようになります。

ただ、普通はそんなことしません。他のシステムとの兼ね合い等でどうしても
命名規則に反する変数名を使う必要がある時ぐらいでしょうか?
まあ、乱暴な方法として、libnameでheader=yesの状態でエクセルにデータをアウトプットする
際に変数名を日本語にすることで、あたかもラベル出力したように見せかけたりできますが、
あんまり綺麗なことじゃないです。

options validvarname=V7;

で通常の命名規則になります。

あと、面白いのは
options validvarname=UPCASE;
ですかね。

このオプションが効いている状態で作成されたデータセットは
たとえ以下のように

data A;
x=1;
run;

小文字で変数名を書いて、実行しても
勝手に大文字に変更されます。





これはオプションを解除しても大文字のままです。

使いどころとしては最終的なデータセットの変数名が
大文字に統一されていることが必ず必要な場面であれば、
最後のステップでこのオプションをつけて作成すればいいですね。
まあ最初から宣言しててもいいですけど。

正直あまりいいオプションとは思えませんが、むしろ反面教師としてTRANSPOSEプロシジャのletの説明

transpsoeプロシジャのletオプションは、ちょっと危ないオプションというか、
絶対にWarningのでるオプションで、はっきり言って僕は絶対使わない方がいいと思うオプションです。
よくわかってない人が全部のtransposeプロシジャの使用箇所にletオプションをつけたせいで、えらいことになって、代わりに書きなおすはめになったことがあるので、個人的に恨みがあります。

どういうことかといいますと、
通常transposeでidステートメントを使用する場合、指定された変数の値に重複があると、転置ができないためエラーになります。これは当然の処置で、本来idに指定する変数は一意に値を定める手がかりにするものなので重複は論外、エラーになるのが正しいのです。

ところがletオプションは、これを強引にWARNINGに格下げして、データセットを最後まで作成します。重複をどのようにして解決するかというと、重複する変数の最後の値のみ採用するという方法で回避します。

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

data Q1;
X='A';Y=1;Z=1;output;
X='A';Y=2;Z=2;output;
X='A';Y=3;Z=4;output;
X='A';Y=3;Z=3;output;
X='A';Y=3;Z=3;output;
X='B';Y=1;Z=1;output;
X='B';Y=2;Z=3;output;
X='B';Y=2;Z=2;output;
X='B';Y=3;Z=4;output;
run;













proc transpose data=Q1 out=A1 prefix=Y_;
 var Z;
 id Y;
 by X;
run;


を実行すると









となってデータセットは作成されません。
Yの値が重複してるからです。
例えばX='A'のグループにおいてY_3変数が3つ作られてしまい、SASは同じ変数名をデータセット内
に保持できないのでエラーなのです。

ところが

proc transpose data=Q1 out=A1 prefix=Y_ let;
 var Z;
 id Y;
 by X;
run;












となってERRORからWARNINGにメッセージレベルが変わり、データセットは作成されます。

中身は






です。

いや、まあ便利といってしまえばそうなのかもしれませんが、どうせ重複した値を最後を除いて
消すのなら
transposeに流す前に、nodupkeyとかfirst.last.とかで加工して、一意になる形にしてから
通常の転置をした方がWARNINGもでないし、正道だと思うのです。

どうしても一意にできないデータセットなら、そもそも転置をかけること自体が間違っているか
データ構造に不備があるのではないかと思います。

「このオプションつけるとなんでも転置できて凄い便利ですよ~、でもなんかログが緑になるんですよね~。WARNINGでないようにするオプション知りませんか?」とか言ってたら、ちょっと、その人はヤバイというか、身内なら、全力で叩きなおした方がいいかもしれません。








TRANSPOSEプロシジャのdelimiter=を使ってみる

SAS9.2からtransposeプロシジャのidステートメントに複数変数を指定できることは何回か紹介したのですが、それに付随してdelimiter=が追加されていることを紹介し忘れていました。

今、以下のデータセットがあったとします。

data Q1;
X='A';Y='1';Z='い';output;
X='B';Y='2';Z='ろ';output;
X='C';Y='3';Z='は';output;
run;







idステートメントにXとYの2変数を指定して以下のコードで転置すると

proc transpose data=Q1 out=A1;
 var Z;
 id X Y;
run;





となります。

変数名をみるとXとYの値がそのままくっついているのがわかります。
ただ、これって例えば変数によって入っている値の長さがばらばらで、内容が似ていたりすると
どこからどこまでが何個目の変数から生成された部分か判別できないことがあります。
delimiter=はその間に接続文字を入れることができます(変数の命名規則には注意)
つまり

proc transpose data=Q1 out=A2 delimiter=_;
 var Z;
 id X Y;
run;

とすると





となります


proc transpose data=Q1 out=A3 delimiter=and;
 var Z;
 id X Y;
run;

なら





です。

TRANSPOSEプロシジャのname=を使ってみる

transposeプロシジャのname=、そういえば使ったことありませんでした。

例えば以下のデータセットがあって

data Q1;
X='A';Y='1';Z='い';output;
X='B';Y='2';Z='ろ';output;
X='C';Y='3';Z='は';output;
run;







proc transpose data=Q1 out=A1;
 var Y Z;
 id X;
run;






となるところを、name=をつけると


proc transpose data=Q1 out=A2 name=YZ;
 var Y Z;
 id X;
run;







要は_NAME_を任意の変数名にかえるオプションですか。なるほど。