フォーマットを使って集計_マルチラベルフォーマットで連続量を任意の範囲で区切って集計

カテゴリ集計のやり方は様々ですが、連続量を任意の範囲で区切って集計する場合
フォーマットを使うと結構便利です。MULTILABEL FORMATを使えば、値の区間が重複している場合も別途に集計できて、気持ちいいです。

今、以下のように

data Q1;
call streaminit(2014131);
 do i=1 to 1000;
  X=rand('uniform');
  output;
 end;
drop i;
run;






















0から1の間に分布する1000個の値について

 0.2未満
 0.2以上0.6未満
 0.6以上
 0.5未満
 0.5以上

のカテゴリで区切って数を数えてと言われた場合

if X<0.2 then CATE=1; などなどデータステップで集計変数を定義してからプロシジャにかけてもいいですが、面倒です。

しかも区切る範囲がかぶっている(0.1という値は0.2未満で集計され、かつ0.5未満でも集計されなければならない)ので変数を分ける必要があります。

そんな時なら、例えば

proc format; 
value FREQF (multilabel) 
 low-<0.2='0.2未満'
 0.2-<0.6='0.2以上0.6未満'
 0.6-high='0.6以上'
 low-<0.5='0.5未満'
 0.5-high='0.5以上';
quit; 

のように区切る範囲をフォーマットで定義します。 (multilabel) を打てば値が重複していても
定義できます。

その上で

proc means data=Q1 MIN MAX ; 
class X /mlf
var X;
format X FREQF.
run; 

としてやれば













のように一気に集計できます。最小最大値は確認用にだしているだけです。

formatで指定することと、classステートメントに/mlfをつけることを忘れずに。



ATTRIBステートメント

ATTRIBステートメントはごく基本的なステートメントで使用頻度も高いです。

data A1;
attrib X label='Xのラベル' format=$revers10. length=$10.
        Y label='Yのラベル' format=8.2
       ;
X='ABC';
Y=0.1;
run;







のように、いちいち labelステートメントでラベルをつけて、formatステートメントでフォーマットを定義して、lengthで、、、といったように個々のステートメントを打たずとも
一括で変数属性の定義が行えてしまいます。

僕はattribは絶対に知っていた方がよいと考えていますが、実はこのステートメント、全く紹介していない書籍が多いステートメントなのです。

たとえば、
【SASハンドブック(共立出版)】
【SASプログラミング(共立出版)】
【統計を知らない人のためのSAS入門(オーム社)】
【統計解析ソフト「SAS」(工学社)】
【SAS® 認定プロフェッショナルのためのBase Programming for SAS®9 完全ガイド】

はどれもSASの入門として読まれることが多い最近の書籍ですが索引でattribステートメントが
あるのは【統計解析ソフト「SAS」(工学社)】だけです。

書籍によって特色があるので、それはいいのですが、
SAS Base Programmingの出題範囲にattribなんで入ってないのだろう?というのは疑問です。

というか、SAS Advancedの資格試験テキスト(今のとこ英語版しかない)でも、どんなけマニアックだ!みたいな部分に大量のページを割いている割にattribステートメントは本文中に1回、あまり関係のない部分で言葉がでてくるだけって!attribなんでこんな冷遇されてんだろって感じです。

で、何が言いたいかというと、SASを勉強してきた環境、どの本で基本を覚えたかによって知っている範囲にある程度偏りがでるので、例えば
SAS Base ProgrammerやAdvanced Programmerの資格を持っている方がattribステートメントを知らなくても、ある種必然というか、しょがない面があるということです。
SASプログラミングを教えたりやスキルの統一を目指す立場にある方は最近の書籍やセミナー、資格試験等でどのような内容がでているかを押さえておいた方がいいんじゃないかなと思ったりします。

特に最近出版された書籍で独学でSASを習得した方はattribを知らない可能性が高いので、フォローしてあげるべきだと思います。

SASの入門セミナーがもう少し、参加しやすいお値段であれば、みんな取りあえずそれを受講するようになって、世の中のSASプログラマーの基本的な知識に偏りがなくなると思うのですが、、。






LISTステートメントの話

テキストファイルの読み込み(.txt)は奥が深いです。
SAS Base Programmerの資格試験でinput周り(@とか@@の使い方とか)をこれでもかって程、出題してくるので、その時はかなり一生懸命勉強しましたが、普段使わないとどうしても忘れてしまいます。

で、特に流れと関係ないのですが、LISTステートメントの話です。













今、上記のようなテキストファイルがあって、これを読み込みたいとします。

data A1;
infile 'D:\AA.txt';
input X Y $;
run;

で終わりで、なんのこっちゃないです。















ログには上記のように、3レコード読み込まれたことがでていますが、
このログからは読み込んだテキストファイルの中身がどのようなものであったかは
わかりません。
まあ、ファイル開いて、読み込んだデータセット開いて確認すればいいですが、軽くログで
確認する方法として


data A2;
infile 'D:\AA.txt';
input X Y $;
list;
run; 

このようにlistステートメントを入れてやると




















ログが詳しくなって、実際読み込むテキストファイルの各ラインがどんな感じか
わかります。末尾に4とか3とか6など元ファイルにない値がでていますが、
これはその行の末端の位置(レコード長)を示してくれています。

put _all_も似てますが、少し意味が違います。

data A3;
infile 'D:\AA.txt';
input X Y $;
put _all_;
run; 























SASのAbbreviation機能(省略形の追加)を利用してコーディングの効率を上げる

以前

「SASのキーボードマクロを使って、ショートカットキーにコードを割り当てる」
http://sas-tumesas.blogspot.jp/2013/10/sas.html

「SASの画面のレイアウトを自分好みにして、その設定を残す」
http://sas-tumesas.blogspot.jp/2013/09/sas.html

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

などで、知ってなくても大したことないけど、知っているとプログラムがはかどるかもしれないような小技をいくつか紹介しましたが、今回もそれ系の話です。


「省略形の追加」機能(Add Abbreviation)についてご存知でしょうか?
予測変換候補を自分で辞書登録するような、なんてことない機能で、僕も舐めてたんですが、これがなかなか結構使えます。

例えばunivariateプロシージャを使いたい時、proc univariateって書くわけですが、綴りが長いですよね?まあ、まだマシな方ですが、sasのプロシジャや関数には、1ワードが相当長いものもあります。(ちなみに僕が一番どうにかしてよとおもっているのは、GraphTtemplateLangageでグラフの詳細なデザインを決めるステートメントの綴りが全部ことごとく長いことです)

話を元に戻します。そういった時、例えば、uniまで打てば、まあ後はunivariateのパターンが多いななと判断して























「ツール」→「省略形の追加」をクリックして













省略キー「uni」、省略するテキスト「univariate」と打ってOKします。

そこでエディタで







uniまで打つと、予測変換でunivariateがでてきますので、ここでEnterを押すと
そのまま







となります。

で、他には例えば、いつも引数の設定とか順番を忘れてしまう自作マクロや、SAS関数があった場合に利用したりです。

たとえばSCAN関数で先に、数字もってくるのか、区切り文字指定するのか、いっつも忘れてしまう
鳥頭の僕の場合













SASのヘルプのSCAN関数のsyntaxをコピペしておけば







こうなって、使う時に迷うことがなくなります。

まあ、あんまり、環境設定を自分流にカスタマイズしまくって、それに依存すると
端末が変わった時に環境設定ファイルコピーしないと何もできない人になってしまうことも
あるかもしれないので、その辺りは気をつけてください。





いただいたコメントの回答例_インデックスをbyで指定することで事前のproc sortを回避する

前回の投稿にコメントでご質問いただきました。本当に有難うございます。コメントをいただけると元気がでます。

少し回答が長くなりそうだったので、ここでお答えします。

まずいただいたコメントから

=================================================================================
匿名2014年1月22日 23:36
いつもブログを読んで勉強させてもらってます。現在自分が所属する部署ではSAS上でsort summary を行うことが多いため、そのための簡単なマクロを設定したいと思っています。sortではなくindexを設定する方が早いのでindexを活用していますが、index設定の際のキーが複数の場合と一つの場合の両方で使えるマクロは作成するのによいアイデアはありませんか?今の自分の知識では以下のマクロが精一杯です。

%macro sum(a,b,c,x,y);
/*
a ソートキー
bサムキー
c ソート項目が単数なら1
x インプットデータ
y アウトプットデータ
*/
%if &c=1 %then %do;
proc datasets nolist;
modify &x;
index delete &a;
index create &a;
run;
%end;

%if &c^=1 %then %do;
proc datasets nolist;
modify &x;
index delete keyZZZ;
index create keyZZZ(&a);
run;
%end;

proc summary data=&x noprint;
var &b;by &a;
output out =&y
sum=&b ;
run;
%mend sum;
=================================================================================

なるほど、僕はマクロはあまり上手ではないですが、とりあえず、cのパラメータ引数が少し無駄手な感じですね。

まず、サンプルデータセットを勝手に作ります

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


で、わかりやすい感じにするためやや冗長かもですが、以下の感じでしょうか??
とりあえず、引数1個減らしました。

options msglevel=i;

%macro sum2(a,b,x,y);
/*a:ソートキー bサムキー xインプットデータ yアウトプットデータ*/

%let c=%index(&a,%str( ));

proc datasets nolist;
modify &x;
 index delete _all_;
 %if &c=0 %then %do;
  index create &a;
 %end;
 %if &c^=0 %then %do;
  index create keyZZZ=(&a);
 %end;
run;

proc summary data=&x;
 var &b;
 by &a;
 output out =&y
 sum=&b ;
run;
%mend sum2;

cで手動で1フラグをたてて、判定していた部分を、マクロ引数に半角スペースが含まれているか
どうかで判定させてみました。入っていれば複数変数が指定されているはず、と考えました。
どのインデックスがbyステートメントで採択されたかを明示するためoptions でmsglevelをiにしています。

元コードにあったdeleteは、元々設定されているインデックスを一度リセットする意図だと思うので
いっそ _all_で全殺ししました。


/*単一インデックスの実行例*/

%sum2(X,Z,Q1,A1)

/*複合インデックスの実行例*/

%sum2(X Y,Z,Q1,A1)


で、ふと思ったのが、ちょっと巨大なデータセット相手の経験が豊富ではないのでインデックスのパフォーマンスでの恩恵度合いが推定できないのですが、ソートかますと時間がかかり、それを飛ばすためだけにインデックスを使用してるということで、
summaryプロシジャでデータセットを作成するだけであれば

%macro sum3(a,b,x,y);
proc summary data=&x nway;
 var &b;
 class &a;
 output out =&y
 sum=&b ;
run;
%mend sum3;

/*実行例*/
%sum3(X Y,Z,Q1,A2)

class ステートメントで今までbyで指定していた変数を指定してやれば、事前のソートも必要なく、単数でも複数でも可能で結果は同じになります。感覚的に、結構大きなソートされていないデータセットでもそこそこ早いような気がしてますが、どうなんでしょうか。

もしかしたら、的外れな回答、或いは既に知っていることを偉そうに能書いただけかもですが、その場合は申し訳ございません。

何かご質問ございましたら、どなたでもいつでもご連絡ください。

sasyupi@gmail.com






非明示添字配列と do over LOOPの利用

ARRAYステートメントを初めて覚える時、
 
array 配列名 {配列要素数} 以下、$や長さや配列要素内容や初期値といった感じで、

とりあえず array 配列名 {配列要素数} までは鉄板と覚えてしまいがちですが
それは正確には明示添字配列の定義法であり、
実は{配列要素数}を省略して定義することができます。

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

data Q1;
X=1;Y=2;Z=3;output;
run;





XからZまでを配列として、配列要素全てに+1をする場合、
書き方はいくらでもありますが、例えば

data A0;
 set Q1;
 array AR{*} X--Z;
 do i=1 to dim(AR);
  AR{i}=AR{i}+1;
 end;
 drop i;
run;





と書いたとします。
ここでは{*}と要素数を定数にしていませんが、do to loopの終端条件で
dim関数でARの要素数を取得しているので問題ありません。

ただ、よくよく考えると、配列を定義して、その配列全部におんなじ処理を一括で
かける今回のような場合、要素番号ってあんまり役にたってません。

そこで実は以下のようなコードが成立します。

data A1;
 set Q1;
 array AR X--Z;
 do over AR;
  AR=AR+1;output;
 end;
run;

結果は同じです。
 array AR X--Z;と要素番号定義をすっとばしています。
 do i=1 to dim(AR);が do over AR;となっています。do overは全要素に対して順にループするという
 do i=1 to dim(AR);と同義になります。

ちなみに明示添字配列で定義した配列にdo overを使用すると以下のエラーメッセージです。




「ERROR: 明示添字配列に DO OVER ステートメントは使用できません。」

ただし、たとえば以下のコードのように非明示添字配列で定義しても
要素番号処理は裏でちゃんと勝手に行われているので

data A2;
 set Q1;
 array AR X--Z;
  A=AR{1};
  B=dim(AR);
run;




エラーになったりはしません。


FREQのtablesステートメントは複数指定できるという話

人が書いたコードを点検していた際の話ですが

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

みたいに適当なデータセットがあり、それに対して

proc freq data=Q1 noprint;
 tables X/out=A1;
run;
proc freq data=Q1 noprint;
 tables X*Y/out=A2;
run;
proc freq data=Q1 noprint;
 tables Z/out=A3;
run;

とfreqを3連撃していたので、それって

proc freq data=Q1 noprint;
 tables X/out=A1;
 tables X*Y/out=A2;
 tables Z/out=A3;
run;

で1手で済んで、多分、実行速度的にもこっちの方が早いんじゃないかと言ったところ
「こんな書き方ができるとは!」って感じで、とても驚かれました。

そういうことってありますよね。僕も日々、そんなことできたんだって感動ばかりです


xステートメント(xコマンド)でファイルのコピー

外部ファイルをコピーするxコマンドについてよく忘れるので自分のためにメモです。
xコマンドはSASからOSにお願いをして、指定の命令を実行してもらうような機能です。
options noxwait;をつけないとコマンドプロンプトの黒画面が残ります。


options noxwait; 

x 'copy "D:\sample\AA.xlsx" "D:\sample\BB.xlsx"';

AAというエクセルファイルをコピーしてBBというエクセルファイルを作成します。

例えばアンケート調査をする時なんかに
まず、コピー元になるひな型アンケートフォームをエクセルで作って、
必要なexcel関数式や入力規則、名前の定義を指定しておきます。

そしてそれをlibnameで読み込むプログラムを書きます。

xコマンドをうまくマクロループで利用して、調査ID分、ひな型ファイルをコピーしまくる。

アンケートをばらまく→エクセルに記入してもらってそのファイルを回収
→読み込むプログラムをマクロループで繰り返して全部いっぺんにデータセット化みたいな風にすると、入力業務が省け、読み込みエラーも少なく、楽だと思います。
エクセルファイルをアクティブにせずにアクセスできるので、実行速度的にDDEより早いはずです。

ファイルの場所や名前にマクロ変数を入れる場合、クォート周りに要注意。

x "%str(copy %"&PATH.\BASE.xlsx%" %"&PATH.\COPYOUT.xlsx%" )";






tagsets excelxpでODS出力をエクセルファイルにする時に、セルの表示形式を設定する方法

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

data Q1;
IDNO='001';X='い';Y=1;output;
IDNO='002';X='ろ';Y=2.2;output;
run;






これに対して

ods tagsets.excelxp file='D:\A.xls';
proc print data=Q1 noobs;
 var IDNO X Y;
run;
ods tagsets.excelxp close;

とすれば、純粋なエクセルではないのでファイルが開く際に
確認のメッセージが入りますがOKすれば








こんな感じのエクセルファイルができると思います。

便利なのでちょっとした出力の際によく使うのですが、
これだとセルの表示形式が「標準」になってしまうため、「001」といったテキストが
1になってしまいます。

そういった場合に次のように書くことで回避できます。

ods tagsets.excelxp file='D:\B.xls';
proc print data=Q1 noobs;
 var IDNO/style={tagattr='format:text'};
 var X;
 var Y/style={tagattr='format:0.0'};
run;
ods tagsets.excelxp close;
















/style={tagattr='format:text'}と打つことで「文字列」とすることができ、

また/style={tagattr='format:0.0'};で有効表示桁数を設定できます。



SASインタリーブ(interleave)の紹介と再帰的インタリーブで、SQL再マージ処理をデータステップで疑似的に表現する

インタリーブと聞くと、なんだっけそれ?となってしまいますが、たいしたことではなく
例えば

data Q1;
 do X=1,3,5;
  output;
 end;
run;
proc sort data=Q1;
 by X;
run;







data Q2;
 do X=2,4,6;
  output;
 end;
run;
proc sort data=Q2;
 by X;
run;






のようにソート変数の一致したデータセットが複数存在する場合

data A1;
 set Q1
     Q2;
  by X;
run;

とすると、以下のように










全てのデータセットを縦結合してからソートしなおす必要なく、ソートされた縦結合データセットが
得られるという技術のことです。

特に巨大なデータセットの場合、巨大なやつをつくってからproc sortするよりも、
小さい部品となるデータセットごとにソートしておいてインタリーブした方が、処理効率が
よいらしいです。


で今回のメインは処理効率の話ではなく
インタリーブをつかってSQL再マージと同様の自己参照処理をやってみようという話です。

SQL再マージ?という方はSAS忘備録の「SQL「再マージ」入門」を先に読んでください
http://sas-boubi.blogspot.jp/2013/12/sql_26.html


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

data Q3;
X='い';Y=1;output;
X='い';Y=5;output;
X='い';Y=9;output;
X='ろ';Y=2;output;
X='ろ';Y=3;output;
X='は';Y=2;output;
X='は';Y=4;output;
X='は';Y=6;output;
run;

proc sort data=Q3;
 by X;
run;











このデータセットに対して、もとのX,Yはそのままに、Xの値ごとのYの合計を一体化させたデータセットを作成したいとします。
つまり












上記の感じです。


SQL再マージで書くなら

proc sql noprint;
  create table  A0 as
  select  X,Y,sum(Y) as TOTAL
  from   Q3
  group by  X;
quit;

で詰みですね。

あるいはdo untilによる複数データステップの併合を利用して

data A00;

do until(last.X);
set Q3;
 by X;
 retain TOTAL;
 if first.X then TOTAL=.;
 TOTAL+Y;
end;

do until(last.X);
 set Q3;
 by X;
 output;
end;

run;

でも同じ結果になります。

【参考】do until(end変数)set end=を利用して、複数のデータステップを1つにまとめて共存させる方法
http://sas-tumesas.blogspot.jp/2013/12/do-untilendset-end1.html

ぱっと思いつきませんでしたが、call executeでもかけそうです。


脱線しました、他の方法を考えていると、きりがないです。
インタリーブで書くと、以下のようになります。

data A2;
 set Q3(in=in1)
     Q3;
  by X;
  retain TOTAL;
   if in1 then do; 
     if first.X then TOTAL = .; 
      TOTAL = sum(TOTAL,Y, 0); 
     end;
    else do;
      output; 
    end; 
run;

で詰みです。
ポイントは、setで敢えて元データを倍加重複させておいた上でインタリーブしてるところですね。
で、そのままだといけないので、in=で要約統計を出す部分と、実出力部分を分けているのですね。
タネさえ解れば、どうってことない処理ですね






SASにおけるPerl正規表現の存在を紹介するだけ

さんざん、マニアックなところを紹介しているのに、PRX系関数に一切触れないのはどうしてですか?というご質問をいただきました。

正直、痛いところを突かれたと思いました。
軽んじているわけではなく、まだ練習中で、あまり詳しくないからです。

ただ、PRX?という方もいらっしゃると思うので簡単な例だけ紹介します。

詳しくは「SAS Perl Regular Expressions」や「SAS 正規表現」で検索していただければ
よっぽど丁寧で詳細な資料がたくさん見つかるので、そちらで勉強してください。

正規表現は、乱暴にざっくりいうと文字のパターンマッチングのことです。
特にPerlというプログラミング言語に実装されている正規表現の書き方が、イケてる?ので
それに準じた機能をSASの関数にしましたよというのが、PRX関数です。


以前「whereステートメントでのみ使用できる演算子_contains,between,like」
http://sas-tumesas.blogspot.jp/search/label/like

でlike演算子を紹介しました。「 where X like '_B_D%' ;」のようにアンダースコアや%を使って、
文字のパターンを表現して、それにマッチしたものを抽出するといった機能でした。
感じとしては、これのゴージャス版です。

data Q1;
length X $20.;
X='123-xx';output;
X='12-xx';output;
X='423-XZ';output;
X='423_XZ';output;
X='000000 123-yy';output;
X='123-xy 123-xy 123-xy';output;
run;


ここでXの値に 数字3個の次にハイフンがきてその後 x か z か yの文字が2個続く
という特定のパターンが出現するかどうかを調べなければならないとします。

以下のコードと実行結果を見てください

data A1;
 set Q1;
  Y=PRXMATCH('/¥d{3}-[x-z]{2}/',X);
run;

まずPRXMATCH関数は、正規表現で指定したパターンが、対象変数のどの位置で
最初に出現するかを数字で返す関数です。0であれば、対象変数に指定したパターンは出現していないということです。

/¥d{3}-[x-z]{2}/ が正規表現の部分です。なんとなくわかるかもしれませんが¥d{3}が数字3個
-はそのままハイフン、[x-z]{2}がxかyかzが2個という部分に対応しています。

今は結果はYという変数に割り当てましたが、要はこれをIFステートメントに指定すれば
パターンでオブザベーションの抽出ができるわけです。

他にたくさん記号や書き方があるので、そこを勉強すれば、相当柔軟なマッチングができるわけです。

ちなみに

data A2;
 set Q1;
  Y=PRXMATCH('/¥d{3}-[x-z]{2}/i',X);
run;

とすると、大文字小文字の違いが不問化され

となります。


ちなみにただ、一致した位置を返すだけがSAS Perl正規表現ではありません。

以下のプログラムと結果をみてください

data A3;
set Q1;
 Y1=PRXCHANGE('s/23-/いろ/',1,X);
 Y2=PRXCHANGE('s/23-/いろ/',2,X);
 Y_1=PRXCHANGE('s/23-/いろ/',-1,X);
run;



これは変数の中に出現する、「23-」というパターンを「いろ」という文字列に置き換える処理です。
置換にはPRXCHANGE関数と正規表現の部分の/前にsをつけます。

第2引数は、見つかったパターンの何個目まで置換対象にするかという数字です。

ここを-1にすると、1個だろうが100個だろうが、全部置き換えるという処理になります。


多分、ここまでで、なんかこれって、もっと凄い柔軟で複雑なテキスト処理ができるんじゃないか?
と感じていただければ幸いです。
正規表現を極めれば、対テキストデータのプロになれます。
多分テキストマイニング等をされる方は、使う方多いんではないでしょうか?

/*追記*/
コメントにて、とても大切なテクニックについてご教示いただきました。有難うございます!













文字型に入っている数字のデータをproc sortする時に、数字とみなして並び替える

sortプロシジャはソートアルゴリズムや、ソートの順序規則を変えることができ、奥が深すぎるので、
実用的なテクニックだけ1つ紹介します。

data Q1;
length X $2;
X='1';output;
X='3';output;
X='5';output;
X='9';output;
X='11';output;
run;









これをXをby変数にしてソートすると、どうなるでしょうか?

proc sort data=Q1 out=A1;
 by X;
run;

以下のようになります









えっ!と思われた方もいるかもしれませんが、Xは文字型なので、言葉として辞書的にソートされちゃうイメージです。

なので数値的にソートしたければ、一端、数値に変換した変数を新しく割り当ててから、それでソートします。
しかし、やりたいことが単純な並び替えだけなのであれば、なんだか一手損な感じがしてしまいます。

そこで、ソートルールをいじくります。

proc sort data=Q1 out=A2
 sortseq=linguistic(numeric_collation=on);
 by X;
run;








青字の部分はかなり色々な書き方ができるので深入りしたい方は調べてみてください。






全てがnullであった時を問題とするブランクチェックについて、データ構造が縦型と横型であった場合の違い

生データをチェック・クリーニングする際に、ブランクチェックは頻度の高い処理です。

たとえば今A,B,Cという変数があって、どれか1つ以上でも値が入っていれば、問題なし。
全てが欠損値であるデータをピックアップしたいという状況があったとします。

例えばデータが

data Q1;
IDNO='001';A=.;B=.;C=10;output;
IDNO='002';A=.;B=.;C=.;output;
IDNO='003';A=5;B=.;C=.;output;
run;







こんな感じならwhereで単純に=.をandでつないでもいいし

data A1;
 set Q1;
 if coalesce(A,B,C)=.;
run;

こんな感じでもいいし、簡単です。





少し頭を使うのは、同じ意味であってもデータ構造が

data Q2;
IDNO='001';ITEM='A';VAL=.;output;
IDNO='001';ITEM='B';VAL=.;output;
IDNO='001';ITEM='C';VAL=10;output;
IDNO='002';ITEM='A';VAL=.;output;
IDNO='002';ITEM='B';VAL=.;output;
IDNO='002';ITEM='C';VAL=.;output;
IDNO='003';ITEM='A';VAL=5;output;
IDNO='003';ITEM='B';VAL=.;output;
IDNO='003';ITEM='C';VAL=.;output;
run;











こういう場合です。
1つのIDにつき項目ごとにオブザベーションが別れています。
問題にすべきはA,B,C全てが欠損である002のIDで、それを特定したいわけですが、
さて、どう書きましょうか?

まず1つとしては

proc transpose data=Q2 out=Q2_(drop=_NAME_);
 var VAL;
 by IDNO;
 id ITEM;
run;

とすれば、最初の問題のデータ構造に転置されるので、そうしてから
同じやり方で片付ける方法です。

縦に解く問題が難しければ、横にすればいいし
横に解く問題が難しければ、縦にすればいいというのはSASの定跡ですね。

まあ、でもこの場合、わざわざデータ構造を変えなくても
例えば、

proc means data=Q2 nway noprint;
class IDNO;
var VAL;
output out=A2(where=(S=.)) sum=S;
run;





このようにすれば、IDごとにAからCの合計をだしてくれるわけですが
sumによる加算結果がnullになるのはどんな時でしょうか?
それは対象全てがnullであったときのみです。その他の場合はnullを除いて足し算してくれます。
すなわちsumがnullであればA,B.Cすべてnullであることに他ならないので
上記のコードでID 002を特定できるわけです。
maxとかでももちろんいいです。


他のアプローチとして、

proc sort data=Q2;
 by IDNO VAL;
run;

data A3;
 set Q2;
 by IDNO VAL;
 if last.IDNO and VAL=.;
run;





もアリですね。

ID順、VAL順にソートして、各IDの最後のVALがnullである場合は、そこまでの全てのVALがnullであることと同義ですからね。









筋悪ですが、LINKステートメントとGOTOステートメントについて

SAS Technical News 2010 Autumn 「意外と知らない!? コロンの活用術をご紹介」は僕のお気に入りの号のひとつです。

http://www.sas.com/offices/asiapacific/japan/periodicals/technews/index.html#section=4

SASにおけるコロン「:」はトリッキーかつ結構いい働きをするやつで、これをうまく使えるかどうかで、コードの完成度に差がでると思っています。僕の中では桂馬みたいなイメージです。

で、だいたい上記の号の中でコロンの使いどころについて言及されているのですが、ちょっと勝手に感じたのが、やっぱりSAS的にはLINKステートメントやGOTOステートメントにおけるラベルコロンは紹介すべきではないと考えているのかなということでした。

LINKやGOTOステートメントは、プログラムの途中で指定箇所(ラベルといわれる)にぶっ飛ぶことができる危ない奴です。

GOTO文は筋悪というのは、多くのプログラム言語に当てはまることで、SASにとっても
上から下に流れるSASのシンプルな処理をぶち壊す、やっかいなステートメントです。

恐らく、ほぼ全てのケースにおいてGOTOやLINKを使わずに、目的の処理が果たせるコードはかけるはずなので、使用はお勧めしません。

ただ、コードを書く人も、読むであろう人も全員、熟練者であれば、相当複雑なネストを組む時に、全体をすっきりさせるために使用することはあるのかもしれません。

しかし、もしかしたら、運悪く、どこかで他人の書いたGOTO や LINKのコードに対峙することがあるかもしれません。そんな時の対抗手段として、基本的な挙動は知っておいて損はないと思います。

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

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



















これに対して以下のコードを実行すれば

data A1;
 set Q1;
 by X;
 if first.X then Z=0;
  Z+Y;
run;



















となります。


これをLINKステートメント使って表現すると

data A2;
 set Q1;
 by X;
 if first.X then link FL;
  Z+Y;
 return;

 FL:
  Z=0;
 return;
run;

となりますFLというのがジャンプ先の目印になって
そこからジャンプ先での処理が行われて、returnで戻ってきます。

ちなみにlinkの部分をgotoにすると

data A3;
 set Q1;
 by X;
 if first.X then goto FL;
  Z+Y;
 return;

 FL:
  Z=0;
 return;
run;

returnで停止するので



















僕が知っているのも、ほんとこの程度までです。


LIBNAME EXCELで「予期しないエラー~」とでる場合はファイルの属性をチェックし、access=readonlyで解決する場合がある

最近で直接いただいた質問のうち、2件同じ症状で、同じ解決法で解決した事例の紹介です。

libname EXCELでlibnameステートメントを実行した時点で以下のような



ERROR: Connect: 外部データベース ドライバ (????????) で予期しないエラーが発生しました。



ERROR: Connect: 外部データベース ドライバ (am Files\Common Files\Microsoft Shared\OFFICE12\ACECORE.DLL) で予期しないエラーが発生しました。

といったエラーメッセージがでる場合、EXCELファイルのプロパティの属性が
「読み取り専用」となっている可能性があります。






その場合libnameステートメントに

libname XX "----------" access=readonly;

と読み取り専用でライブラリ指定しないと上記のエラーになります。
エラーメッセージから意味が読み取りにくいのはいつものことですが、これも気づきにくい
エラーですね。

読み取り専用のチェックはずしてもいいなら外してもOKです。







do i=1 to ○○ until();のようにdo toループとuntilやwhileが併用できることを今まで知らなくてとても悲しかった話

とてもショックです。

data A1;
X=20;
 do i=1 to 99 until (X=40);
  X+2;
 output;
end;
run;

99回ループする。ただし途中終了条件としてX=40になれば終了する。
この処理を上記のコードでかけるのです。
こんな単純な書き方なのに気づきませんでした。ショックです。悲しいです。

以前は以下のように書いていました。

data A2;
X=20;
 do until(i=99 or X=40);
  X+2;
  i+1;
 output;
end;
run;

最近、leaveステートメントを知って、その時もショックでしたが、
以来、以下のように書けるようになりました。

data A3;
X=20;
 do i=1 to 99;
  X+2;
  output;
  if X=40 then leave;
end;
run;

SASのデータステップの知識の全てを100としたら、多分そのうち理解できている部分は
1以下だろうと思い知りました。

性根を叩き直して勉強しなおします






n個の変数からm個の変数を選択する、組み合わせのデータセットを作成する方法

「い」「ろ」「は」「に」という4つの値から2つを選ぶ場合、組合せの数は
4C2で6通りになりますが、その組み合わせを1組み1オブザベーションとしてデータセットを作る場合、たとえば以下のようにかけます。

data A1;
   array X(4) $ ('い' 'ろ' 'は' 'に');
   do i=1 to comb(dim(X),2);
      call allcomb(i, 2, of X(*));
 keep X1 X2;
      output;
   end;
run;











まずcomb関数は組合せ数を返します、dim(X)つまり4から2をとる組合せ数、つまりここが
6になります。

作成したいデータセットは6オブザベーションになるはずのなので1から6までのループをかけ

allcombルーチンをかけます、ここでの第1引数は作成された各組合せに振られた連番で
1から6すべて選択してoutputすることで全組み合わせをデータに起こせます。
2つの変数しか必要ないので第3,4要素の変数をdropします。


また例えば、全順列が欲しい場合は階乗をかえすfact関数と
allpermルーチンで、以下のようにできます。

data A2;
   array X(4) $ ('い' 'ろ' 'は' 'に');
   do i=1 to fact(dim(X));
      call allperm(i, of X(*));
 drop i;
      output;
   end;
run;






















ちなみに1000個の変数をallpermルーチンに指定したら、天文学な数字になって
永遠に処理終わらなくなんのかなと思って、よし、SASよ永久無限に働くがよい!と思って
ためしにやってみると









「ERROR: ALLPERMルーチンは20個を超える変数を並べ替えることはできませんが、
       1000個の変数が指定されました。
 ERROR: 内部エラーが関数ALLCOMBに検出されました。DATAステップは、
       EXECUTIONフェーズで終了しました。」

とSASにしっかり咎めていただきました。



ユークリッドの互除法で最大公約数をだすコードを一生懸命書いた後で、GCD関数とLCM関数を紹介して、便利になったなぁと感謝する話

言語を問わず、プログラミングのチャレンジ問題でよくでてくる「ユークリッドの互除法」を紹介します。

「ユークリッドの互除法(ユークリッドのごじょほう)は、2 つの自然数または整式の最大公約数を求める手法の一つである。2 つの自然数(または整式) a, b (a ≧ b) について、a の b による剰余を r とすると、 a と b との最大公約数は b と r との最大公約数に等しいという性質が成り立つ。この性質を利用して、 b を r で割った剰余、 除数 r をその剰余で割った剰余、と剰余を求める計算を逐次繰り返すと、剰余が 0 になった時の除数が a と b との最大公約数となる。」
(Wikipediaより転載)

最小公倍数についてはa*b/(aとbの最大公約数)でだせるので省略します。

これをSASで書くなら

data _NULL_;
 X=1029;
 Y=1071;
 call sortn(X,Y);
  MD=mod(Y,X);
  ANSWER=X;
 do while(MD^=0);
  MD_=MD;
  MD=mod(Y,MD);
  Y=MD_;
  ANSWER=Y;
 end;
 put '最大公約数は' ANSWER;
run;









上記の感じでしょうか?XとYの値を色々変えてみてください。
call sortnルーチンで横ソートしているので、大小関係を気にせず指定できます。

要は余りが0になるまで、割ってけばいいんですね。
でも実際、アルゴリズムはわかっていても、コードにおこすのは、結構大変ですよね。


ところが9.2からgcd関数で最大公約数をl㎝関数で最小公倍数を簡単にだせるようになりました。
複数変数指定も可能で、もう二度とデータステップでループ書く必要がなくなりました。

data _NULL_;
ANSWER1=gcd(1029,1071,63);
ANSWER2=lcm(8,3,5);
 put '最大公約数は' ANSWER1;
 put '最小公倍数は' ANSWER2;
run;








詰めSAS11回目:集計データセットを集計前の状態に戻す

今回の詰めSASは、超簡単なので1分以内の早解き問題です。

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

data Q1_;
X='い';output;
X='い';output;
X='い';output;
X='い';output;
X='ろ';output;
X='ろ';output;
X='ろ';output;
X='は';output;
X='は';output;
run;















これに対してFREQプロシジャをかけて、OUTでだしたデータセットが以下です。

proc freq data=Q1_ noprint order=freq;
tables X/out=Q1(drop=percent) ;
run;







data Q1;
X='い';COUNT=4;output;
X='ろ';COUNT=3;output;
X='は';COUNT=2;output;
run;



このデータセットを、最初の状態に戻すプログラムを書いてください。







【解法】

data A1;
 set Q1;
 do i=1 to COUNT;
  output;
 end;
drop i COUNT;
run;