TRANSPOSE→CATX関数のコンボで縦持ちのデータを1行1変数に指定文字で連結してまとめてみる

CATX関数の使い勝手がよいという話題です。

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

data Q1;
length ID $10.;
ID='AAA';NO=2;ITEM='ろ';output;
ID='AAA';NO=1;ITEM='い';output;
ID='AAA';NO=3;ITEM='';  output;
ID='AAA';NO=4;ITEM='は';output;
ID='BBB';NO=1;ITEM='A'; output;
ID='BBB';NO=5;ITEM='B'; output;
run;



これをIDごとに、出現するITEMの内容を、NO順に「、」で連結して
ALLITEMという変数にいれるという問題があったとします。
求めたい結果は以下です。


なんか面倒そうだと感じますが、楽勝です。

まず、IDとNOでソートした後、IDをbyで固定してtransposeします。
このときIDステートメント(すみません、変数とまぎらわしいですね)でNOを指定します。


proc sort data=Q1;
 by ID;
run;

proc transpose data=Q1 out=Q2(drop=_NAME_) prefix=ITEM_;
 var ITEM;
 by ID;
 id NO;
run;

すると



のデータセットが得られます。
後はこれを順番に「、」で繋ぎたいのですが、nullの場合は飛ばしたいわけです。
空白が間にはいるのも嫌なので、う~んどうしよう、IFいっぱい書かなきゃいけないかと思いきや

data A;
 set Q2;
  ALLITEM=catx('、',of ITEM_:);
 keep ID ALLITEM;

run;

これで、終わりです。
catx関数は指定した区切り文字で、null以外の値を、前後の空白を除去した上で連結してくれるという、凄く気のきいた関数です。
もちろん
  ALLITEM=catx('、',ITEM_1,ITEM_2,ITEM_3,ITEM_4,ITEM_5);
とかいていいのですが、そうすると元データが増えてNO「6」ができたら
コードを修正する必要がでてしまいます。

TRANSPOSEプロシジャのprefix=は接頭語を指定できるので(suffix=は接尾語)
ここをうまく使えば、NOがどんな数字だろうと、転置後の変数は「ITEM_」から始まります。
そこで of とコロンモディファイアで、配列を使うまでもなくターゲットの変数を全て捕まえることが
できます。

CAT やCATSなど、微妙に違うことをしてくれる関数もあるので調べてみるといいと思います





SAS書籍紹介①_統計を知らない人のためのSAS入門

SASを学ぶ上で僕がお世話になった本を紹介していきます。
基本的に統計解析よりも他の部分、データステップやマクロ、SQL、レポート作成やグラフ作成に重きを置いている本を重視します。

今回はまったくSASをやったことない人が最初の1冊にするのにおすすめな本です。

①「統計を知らない人のためのSAS入門
  • 大橋 渉 (著) 
  • 出版社: オーム社 
  • 発売日: 2010/6/25

この本でSASに初めてきちんと触れることができ、この本に沿ってデータステップの基本を覚えました。
データのインポート、ソート、マージ、必要最低限の事柄に絞って、そこを優しくきちんと教えてくれます。
また、どうしてSASアレルギーの人ができてしまうのかをきちんと分析しており、そうならないように
教育者の観点で本を構成していることがとても素晴らしいと思います。
とにかく語り口調がコミカルで読みやすく、最初の1冊にお勧めです。

なお2012/11/21に

統計を知らない人のためのSAS入門 -Ver.9.3対応版-

がでました。いくつか9.2との差異についての記述とPOWERプロシジャについての章などが追加されています。





SQLで他のデータセットと共通、非共通のデータをとる(差集合 積集合) EXCEPT  INTERSECT

SASは1obs読み込んで処理してアウトプットするのが基本で、よくも悪くもデータの格納されている順番(ソート)に縛られているところがあります。
対してSQLは集合論ベースの言語で原則的にデータの格納位置(アドレス)に縛られない書き方ができます。

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

data Q_1;
input X $ Y $ Z $;
cards;
A C D
D B C
C A B
B B A
C A C
A C A
B A C
;
run;



data Q_2;
input X $ Y $ Z $;
cards;
D C D
D B C
C A B
B F A
C A C
C C A
F A C
;
run;


今Q_1について、Q_2に同じ内容のオブザベーションのあるレコードのみを抽出したいとします。
その場合、

proc sql;
 create table A_1 as
 select *
  from Q_1

 intersect corr all

 select *
   from Q_2
;
quit;






二つの抽出文をintersectで繋ぐことで共通のデータを残すことができます。

これをSASで書くなら
MergeのQ_2にin=をつけてQ_2からデータが読み込まれたらフラグがたつようにして
X Y Zを全部BYに指定して、in=が1になるデータだけを残す感じです。

この場合においては僕はSQLの方が、やりたいことの本質にあった、見通しのよい
コードになっていると思います。

また同様に、Q_1からQ_2と共通でない、かぶってないデータを欲しい場合は

proc sql;
 create table A_2 as
 select *
  from Q_1

 except corr all

 select *
  from Q_2
;
quit;


のようにexceptで繋ぎます。

allは重複を勝手にけさない、corrは処理の時にきちんと変数名をみてやってください的な
役割です。


何かやりたいことがあるとき、データステップでやるかSQLでやるか迷った場合は
やりたいことをイメージ化してみるのがいいと思います。

思い浮かんだのがフロー図であれば、データステップ
ベン図ならSQLって感じで。







TRANSPOSEプロシジャのCOPYステートメントを使って、総当たりの対戦表を作ってみる

たとえば
 
data Q;
do TEAM='A','B','C','D','E';
 output;
end;
run;
 
 
のようなデータがあったとします。
 
そこに、
 
proc transpose data=Q out=Q1(drop=_NAME_) prefix=TEAM_;
 var TEAM;
 copy TEAM;
 id TEAM;
run;
とすると何がおきるでしょうか?
 
transposeプロシジャのcopyステートメントは
あまり活躍の機会の少ないステートメントです。
 
こいつに指定された変数は、元のデータセットのデータを保持します。
しかし、BYとは違って、ただそのままであるだけです。
 
つまり結果は
 
 
となります。
 
面白いですが、このような形の結果を求めることが少ないです。
 
今回はチームAからチームEまでが総当たりで対戦をする場合の
星取り表を作るプログラムとして使ってみました。
 
上の結果に少し手を加えて
 
 data ANS;
  set Q1(in=ina where=(TEAM_A^=''))
      Q1(in=inb);
  if ina then TEAM='';
  if inb then call missing(of TEAM_:);
  label TEAM='対戦表';
run;
以下が完成です。
 
 
 
 
社内イベント等で対戦表をつくることがあれば
あえて、SASでやってやりましょう。
きっとEXCELよりもめんどくさいうえに
誰も褒めてはくれないでしょう。
 
 
 
 
 

マウスポイントで情報を表示するHTMLのグラフをGPLOTとSGPLOT両方で作ってみる

出力先をhtmlにしてグラフを作成する時、旧来のgplotプロシジャでは
goptions device=activex;
として ActiveXの機能を使うことによって、グラフの各プロットにユーザーがマウスカーソル
をあてたときにそのデータの内容を表示する動的なグラフを作ることができました。

たとえば以下のような適当なデータを作ります。

data DS1;
call streaminit(777);
do ID=1 to 3;
 do TIME=0 to 5;
  VAL=int(rand('uniform')*100);
  output;
 end;
end;
run;



















IDが被験者番号でTIMEが測定ポイント、VALが検査値です。

これに対して

ods listing close;

symbol1 i=j v=circle;
goptions device=activex;
ods html file="D:\gplotのグラフ.html";
proc gplot data=DS1;
plot VAL*TIME=ID;
run;
quit;

ods html close;

として実行すると
ブラウザが立ち上がり、セキュリティの設定によりますが
「このコンテンツを許可しますか?」的なメッセージがでて、OKと押すと


こんな感じで、今一番高い値の丸印にマウスをあてると、そのデータがID「2」のTIME「4」の
VAL「90」のデータであることがわかります。


はずれ値をとるために散布図等を書くことがあると思いますが、そういったときにはずれ値があるかの確認と、あった時にそれがどのデータなのか特定できるので大変重宝します。


さて、SG系グラフで同じようにするには、少し書き方を変えます。
imagemap=onというものを入れます。

ods graphics on/imagemap=on;
ods html body="gplotのグラフ2.html";
proc sgplot data=DS1;
series x=TIME y=VAL/group=id markers;
run;
ods html close;
実行すると



となります。















SASデータセットを削除する方法

データセットを削除します。オブザベーションを消すのではなくそのものを消す方法です。

data DS1;
 X=1;
run;

今、WORKにDS1というデータセットがあるとします。


【方法1】
proc datasets lib=work;
 delete DS1;
run;
quit;

DATASETSプロシジャのdeleteステートメントでデータセットを指定します。


proc datasets lib=work kill;
 run;
quit;

ライブラリの中を全部消したい場合はkillです。

proc datasets lib=work;
 save DS1;
 run;
quit;

save だとそのデータセットだけ残します。

【方法2】
proc delete data=DS1;
run;

どなたかがブログか論文で紹介されていたと思うのですがdeleteプロシジャというのも
使えます。

【方法3】
proc sql;
 drop table DS1;
quit;

sqlプロシジャのdrop tableでも消せます。どうしてもSASでdropがでてくると変数を落とす
イメージが強いですが。

【方法4】
DATA _Null_;
length DSFILE $20.;
 rc = filename(DSFILE,pathname("WORK")||"\"||"DS1"||".sas7bdat");
 rc = fdelete(DSFILE);
run;

これ好きです。粗暴なコードで素敵です。実戦で使うのは微妙ですが。
ようはSASデータセットそのものをファイルとして
削除してしまおうという発想です。pathname関数でライブラリの実体パスを取得できます。
それにデータセット名と拡張子を繋げてfdeleteで削除します。
消してもエクスプローラーが更新されていないと残像のようにデータセットが表示されるのですが
実際ダブルクリックすると「すでにこのデータセットは存在しません」みたいなかっこいい
メッセージがでて、なくなります。
自分が死んでいることに気付いていない幽霊のようで神秘的です。




詰めSAS7回目_n番目に大きい値を取得する

最小、最大をとる処理に比べて、n番目に大きい(小さい)値を取得する処理を書くことは少ないと思います。
そういった処理を書けといわれると、とりあえず、対象となる降順(昇順)sortして、次のデータステップにbyをつけて、連番を付与したのち、その連番がnであるオブザベーションをとるという手順が最初に思い浮かぶ方も多いと思います。
それでまったく間違いではないのですが、ここではそれ以外の方法を紹介します。

【問題】
data Q_1;
input X;
cards;
1
5
6
3
2
4
;
run;








上記のデータセットから2番目に大きいXの値(5)を取得して

上記のようなデータセットを作ります。

【解法1】
proc rank data=Q_1 out=A_1(where=(RANK=2)) descending;
 var X;
 ranks RANK;
run;

rankプロシジャは指定した変数の値を比較して順位をつけてくれるプロシジャです。
ranksステートメントを指定しないとvarで指定した変数が順位データに上書きされるので
元の値と順位両方をだしたい場合は指定します。
descendingをつけないと昇順に順位付けします。

【解法2】
ods output extremeobs=A_2(keep=HIGH HIGHOBS rename=(HIGH=X HIGHOBS=RANK) where=(RANK=2));
proc univariate data=Q_1;
var X;
run;

univariateプロシジャでも同じようなことができます。
univariateを実行するとアウトプットに

極値という項目がでて、最小最大ともにデフォルトでTOP5のデータがでるので
これをODSでとってしまえばよいのです。
n番目に値以外にunivariateで出すものがある場合はRANKプロシジャなんかを使うより
UNIVARIATEついでにこっちのやり方でやればいいですね。

【解法3】
proc transpose data=Q_1 out=Q_1_ prefix=VAL_;
run;
data A_3;
 set Q_1_;
 RANK=2;
  X=LARGEST(RANK,of VAL_:);
 keep RANK X;
run;

わかりやすいように転置でtransposeプロシジャを使用して1ステップ使っており、2ステップになっています。(transposeプロシジャを使わない転置は別頁参照)

ようはSASのLARGEST関数を紹介したかったのです。これはlargest(n,x,y,z....)でn番目に大きい値をx,y,z・・・からとる変数です。小さい値はSMALLEST関数です。


SQLでもとれますが少しめんどうなので割愛します







%put に_all_,_automatic_,_user_などを指定してみる

%put ステートメントはログ等にマクロ変数を展開して出力するステートメントです。マクロ変数に値を代入するプログラムを書いたあと、ちゃんと想定した値が入っているか確認するときなどにも使います。
ただ一気にマクロ変数を大量作成するプログラムの場合、いちいち%putでマクロ変数名を指定するのはめんどうです。

そこで%put _user_;とすると、ユーザーが作成したマクロ変数とそこに入っている値が全部ざっとでてきます。

また%put _automatic_;とするとユーザーが作成していないマクロ変数、ようするに自動マクロ変数が全部出てきます。SASはバージョンによって、自動マクロ変数の種類の豊富さが結構違うので、あとマクロ変数ここで使えたっけ?という場合や、現在日時が格納されている自動マクロ変数ってなんだっけって時に結構重宝します。

%put _all_で上の二つを合わせた全部がでます。

またユーザー定義マクロ変数のなかでも
%put _global_ または%put _local_でグローバル変数またはローカル変数だけだせるので、
マクロ変数のスコープでわけわからんくなったときは一度全部だして値を確認してみるといいかもしれません。

メッセージレベルをiにしてMergeのミスを防ぐ

通常、SASのログに現れるメッセージと言えば「ERROR」「WARNING」「NOTE」の3種類ですが、
options msglevel=i;という風にmsglevelオプションでiを指定するとログに「INFO」というメッセージがでるようになります。

このINFOというカテゴリで表示されるようになるメッセージには、結構役に立つものが多いのですが今回はMergeステートメントによって変数が上書きされた時にでるINFOメッセージについて紹介したいと思います。

たとえば臨床試験のデータで
サイクル2,3,4の検査値データが入ったデータセットとサイクル1のデータセットがあり
この2つのデータセットから、サイクル1の検査値より低い値をとったサイクルを特定するプログラムを考えてみます。

【LB】






【LB_1】




proc sort data=LB;
 by USUBJID;
run;

proc sort data=LB_1;
 by USUBJID;
run;

data OUTPUT;
 merge LB(in=ina)
          LB_1(rename=(AVAL=BASE));
      by USUBJID;
      if ina;
      if AVAL<BASE;
run;

とつらつら書いて実行すると、もれなく大悪手です




一見正解風ですが、抽出されたデータのVISITが「サイクル1」です。
あれ?サイクル1より値の低いサイクルを特定したいのに出てきたのが「サイクル1」とはこれいかにです。

もうお気づきかと思いますが、LB_1でLBのVISITを上書いてしまっているので、
本来だしたい「サイクル2」が「サイクル1」になっちゃったわけです。
LB_1のVISITをdropすれば解決です。

初歩的なミスではありますが、結構やってしまいがちで見つけにくい類のエラーです。
特にデータマネージメントにおける論理チェック(エディットチェック・コンピューターチェック)の
プログラムを書くときは、似た様なマージ文を山ほど書くので疲れてくると僕はよくやってしまいます。

そこで以下のオプションを先頭に打ってから、同じプログラムを実行してみます。
options msglevel=i;









このように
INFO: 変数VISIT(データセット WORK.LB)はデータセット WORK.LB_1によって上書きされます。
というメッセージが出て、変数の上書きが起きたことがわかります。


さて、変数の上書き以外にINFOが役に立つのは、ソートプロシジャの実行アルゴリズムを教えてくれることです。ODBCでoracle等のRDBにLibnameをかけて、そのライブラリ内でソートを使うと
そのRDBのソートアルゴリズムでソートされるのですが、欠損値の取り扱いなどが通常のSASソートと違う場合があり、返された結果の意味がわからないことがありますが、そういった場合にINFOが通常のSASソートではない方法でソートされたことがわかるので解釈と対処がしやすくなります。


詰めSAS6回目_由来するデータセットの情報で処理をする

以下の2つのデータセットを、変数名を共通のものにリネームしつつ縦結合し、なおかつ結合後のデータセットで、もともとどちらのデータセットにあったオブザベーションか判別できるように情報を付与せよといった問題になります。

data Q_1;
 A=1;B=2;output;
 A=3;B=4;output;
run;



data Q_2;
 C=5;D=6;output;
 C=7;D=8;output;
run;






上記の2つのデータセットから、下のデータセットを作ります。








なお、1ステップで行うのが最短で、新規データセットを作成する問題なので
appendプロシジャの採用は今回はおそらく悪手です。


【解法1】
data A_1;
 set Q_1(in=in1 rename=(A=X B=Y))
      Q_2(in=in2 rename=(C=X D=Y))
;
 if in1 then BASE='Q_1';
 if in2 then BASE='Q_2';
run;

inデータセットオプションはmergeステートメントと使って、外部結合(片方のオブザベーションを全て残す)ような処理の際によく使われますが、本来、そういった限定的な使用法ではなく、データセットの帰属にフラグをたてれる優秀なオプションなのでより柔軟に使用されるべきだと思います。


【解法2】
proc sql noprint;
 create table A_2 as
  select A as X,B as Y,'Q_1' as BASE
   from Q_1
    union all
  select C,D,'Q_2' as BASE
   from Q_2;
quit;

この方法で面白いのは、変数名をX,Yに変える処理をQ_1に対してしか行っていないのに後段の
Q_2の変数C、DもX,Yになって縦結合される点です。
これは unionにcorrをつけていないことに起因します。corrをつけると縦結合する際に変数名が同じ物同士をつないでくれますが、つけれなれば、どんな変数名であっても1つ目の変数は1つめの変数と結合されます。定義情報は一番最初のテーブルのものが強制されます。通常はそんなおっかない性質は扱いづらいので unionにはallとcorrをつけます。allを抜くと勝手に重複データを消してきます。
SASのSetステートメントの挙動と同じなのはunion corrで繋いだ場合ということになります。




=================================
おまけ in=は優秀
=================================
たとえば以下のようなデータセットがあったとします。

data GROUPDATA;
 input GROUP $10. SEX $;
 cards;
グループA 男性
グループA 男性
グループA 男性
グループA 女性
グループA 男性
グループA 女性
グループA 男性
グループA 男性
グループA 男性
グループA 女性
グループA 男性
グループB 男性
グループB 女性
グループB 女性
グループB 男性
グループB 女性
グループB 男性
グループB 女性
グループB 男性
グループB 女性
;
run;
























ここでAグループ、Bグループ、全体で見たときの男女の人数をカウントせよという問題があった
とします。










そういった時setステートメントで同じデータセットを縦につないで
一旦オブザベーション数が2倍になってデータが重複したデータセットをつくるコードを書きます。
ただし2つめのデータセットにデータセットオプションでin=を指定し、後にifでin=でたてた変数が
1の場合にグループ情報をダミー変数に置き換えます。
あとはグループ集計すればいいのです。
言葉で説明しにくいので実際にやってみてください、SASっぽいなぁと思わせる処理です。

data GROUPDATA_1;
 set GROUPDATA
      GROUPDATA(in=in1);
   if in1=1 then GROUP='全体';
 run;

proc freq data=GROUPDATA_1 noprint;
  tables GROUP*SEX/out=OUTPUT(drop=percent);
run;





SASデータセットを開いた時にラベル名ではなく変数名を表示するのをデフォルト設定にする


SASのエクスプローラーからデータセットをダブルクリックし、開いた際(データビュー)、変数にラベルが設定されている場合、ラベル名が表示されると思います。




ただし、コードを書くときは変数名を書くので、ラベルで表示されると面倒な時があります。
その度に「表示」→「列の名前」にチェックをつけて変数名をみるというのがおっくうでした。
 
 ところが、最近「列の名前」を表示するのをデフォルト設定にする方法を教えてもらいました。
かなり面倒な手順ですが、Win7 でSAS9.2なら以下の通りです。
「表示」→「コンテンツのみ」

















「ツール」→「オプション」→「エクスプローラ」

 
「メンバー」タブのTABLEをクリックし「編集」ボタンをクリック
 















 「開く」をクリックして「編集」をして

アクションコマンドですでに入っているコードの後ろに半角開けて
colheading=name
といれます。
そして保存します。

これで終わりです。

 
 
以後、デフォルト設定は変数名を表示になります。
 
これについて、僕はSASを初めてから今日まで3年くらいずっと悩んでましたが
僕だけでしょうか。
すっとしました。
 
 
 
 
 

SASの画面のレイアウトを自分好みにして、その設定を残す

他人のSASの画面をのぞくと、エディタの設定や画面のレイアウトが結構千差万別で、楽しいのです。

僕は以下のように、とにかくエディタをでかくします。ログウインドウは縦長にするのが好きです。
ログが細長いウインドウを滝のように流れていく様を目で追うのが好きですし、メッセージを見落としにくいので好きです。
アウトプットは、必要なときに大きくすればいいので、基本プログラムをガリガリ書くときは下に
ひっこめておきます。




さて、気のすむようにレイアウトをいじっても、一度SASを閉じて、開くと元に戻ってしまったんじゃ
面倒です。

そこで、以下のように設定を保存できます。














SAS9.2の画面ですが「ツール」→「オプション」→「プリファレンス」をクリックして












「終了時の設定を保存する」にチェックをいれて「OK」です。

これで、次からSASを閉じる際のウインドウの配置が記録され
今度開いたときにその配置で開きます。

なんか微妙にずれている気がする時もありますが、、。



詰めSAS5回目_データの重複を取り除く


データの重複を取り除く方法を考えます。
今回は全変数の値に対して重複をみて、ユニークな値のみ残すようにします。

data Q1;
input X Y Z $;
cards;
1 2 A
3 5 C
7 4 B
1 2 A
3 5 C
7 5 B
1 2 A
1 2 A
9 6 D
9 6 D
3 5 C
;
run;


のデータから




を作ります。


【解法1】
proc sort data=Q1 out=A1 noduprec;
 by X;
run;

byステートメントがないとsortプロシジャが動かせないので入れているだけで
指定はどの変数でもいいです。noduprecは全変数で同一の値を持つオブザベーションが
ある場合にまとめてくれます。

proc sort data=Q1 out=A1_ nodupkey;
 by X Y Z;
run;

のようにnodupkeyを使って全変数をbyステートメントに指定しても当然OKです。

【解法2】
proc freq data=Q1 noprint;
 tables X*Y*Z/out=A2(drop=COUNT PERCENT);
run;

freqプロシジャで組み合わせの頻度集計をしておいて、頻度と割合をdropすることで
必然的にユニークな値のみが残ります。

この方法のいいところは、今回は違いますが、たとえば、重複しているレコードの数が
2レコードの組み合わせのみ残せといったような、わけのわからない条件がついた場合に
頻度データを持っているので次のように対応できるというところです。

proc freq data=Q1 noprint;
 tables X*Y*Z/out=A2_(where=(COUNT=2));
run;

【解法3】
proc sql noprint;
 create table A3 as
  select distinct X,Y,Z
   from Q1
;
quit;

【解法4】
proc sql noprint;
 create table A4 as
  select X,Y,Z
   from Q1
  union
  select X,Y,Z
   from Q1
;
quit;

【解法5】
proc sql noprint;
 create table A5(drop=DUMMY) as
  select X,Y,Z,max(X) as DUMMY
   from Q1
   group by X,Y,Z
;
quit;

解法3-5はSQLです。書き方はほかにもいくらでもあるので、省略します。
普通に解法3で充分なのですが、個人的に面白いのは、冗長な処理ですが解法4です。
unionに allを指定しないと、結合後のデータから重複が取り除かれるという性質を利用して
自分に自分をくっつけて、同じものを消します。ぷよぷよみたいですね。


SAS®グローバル認定プログラム_SASの資格の話

SASの認定資格の話です。受験を考えられている方の一助になれば幸いです。
試験の位置づけや、詳細についてはSAS社のHPでご確認ください。受験方法や、受験できる試験の種類が結構増えたり、変わったりするので、最新の情報を参照いただければと思います。

いわゆるIT系のベンダー認定資格で、全国各地にあるテストセンターで、自分の好きな時間、好きな場所を予約して受験します。パソコンがあてがわれるので、そこでPC画面上にでる問題に解答していき、時間が切れるか、完了ボタンを押せば画面が切り替わってすぐに合否がわかります。
合格していれば、その後しばらくしてから米国SAS社からメールがきて、認定証のPDFと、合格者専用アカウントページの連絡がきます。
不合格だとその後1カ月は受験できませんが、それを過ぎればリトライ可能です。
合格すると米国SAS社のWebページに登録され、検索可能になります。ちなみに各国でどんな人がどんな資格を持っているのかすべてみることができるので楽しいです。
インドと中国でのSAS資格者の増加がすさまじく、将来にやや不安を感じます。

基本的に転職時の履歴書や、職務経歴書に資格として書け
本当にSASプログラミングできますよという証明に使います。
ほかに資格を得ることでの特権的なものはありません。
会社(多分海外)によっては、資格に対して給料上乗せとかもあるらしいですが、
身の回りでの実例は今のとこ知りません。

また、SAS絡みの部署であれば、世間的に存在ぐらいは認知されていますが
それほど古くからある資格ではないので(日本語で試験受けられるようになったのがSAS9からので)、会社によっては知らないかもしれません。

私は医薬系のSASプログラマーで、以下の3つの資格を持っています。
2013/9現在、6つの資格が日本でリリースされていて、うち3つはビジネスアナリティクス系のもので
私はさっぱりわからないです。











①SAS Base Programmer
目安として1年程度の経験者が受験となっていて
テスト範囲としては、
==================================================
生データファイルのインポートとエクスポート
データの加工と変換
SASデータセットの組み合わせ
SASプロシジャを使用した基本的な詳細レポートと要約レポートの作成
データエラー、構文エラー、論理エラーの識別と修正
===================================================
となっています。

要は基本的なデータステップなので、公式のテキストを読んで、しっかり勉強すれば
3か月くらいで受けてもいけると思います。
公式のテキストは日本語版がでています。
ただ、かなり業務経験のある方でも公式のテキストはあった方がいいかなぁと思います。
なぜかというと、テストの得点の比重として、プレーンのテキストデータを
inputステートメントで読み込む際の様々な指定の仕方が結構高頻度で出題されたり
proc printにつける様々なステートメントやオプションが聞かれたりします。

実業務で、平のテキストファイル、それも一筋縄でいかないような区切られ方をしているものを
読み込む処理をガンガン書かれている方や、proc printで出力物を作成されている方なら
問題ないと思うのですが、もしかしたらそういう方はそれほど多くないのではないでしょうか。

基本的な事柄であっても、使わないオプションやステートメントはどうしても知らないことが多いので
念のため公式テキストは購入されてた方がいいかと思います。

ちなみに僕は何も勉強せずに、SASを始めて半年で受験し、2点足りずに不合格し、
そこからテキストで2カ月勉強して余裕で合格しました。

ちなみにe-learningとして模擬問題50問にアクセスするアカウントを、
なかなかのお値段で売っていますので
念には念を入れたい方や、本番に弱い方は買われてもいいかと思います。
受験料が18900円とやっぱり高く、落ちると金銭的に嫌だったので僕は買いましたが
そのおかげで合格できたという感じはありませんでした。

模擬問題はSASテクニカルニュースの後ろにも、たまにちょぴっとついてます。


②SAS Advanced Programmer
確か目安は3年の経験だと、何かで見た覚えがあります。
ただ、これは結構甘くない試験です。
まず、2013/9現在、テキストは英語版しかありません。

出題範囲は以下です。
===================================================
高度なDATAステップ・プログラミングと効率化のテクニックを使用した問題解決
SAS SQLコードの記述と解釈
SASマクロ機能の作成と利用
 ===================================================
SQLとマクロに関しては、大した問題はでませんが、
あまり実務では使わないかなといったオプションや書き方がでるので
テキストがあるにこしたことはないです。
でもマクロもSQLも普通にかきますよという方には、多分なくても大丈夫です。
公式テキストは英語とはいえ、SQLとマクロについては、言葉がわかんなくても
例示コードを追ってけば、大筋を外すことはないとおもいます。
SQLとマクロを得点源にすべきです。

なぜかというと「高度なDATAステップ・プログラミングと効率化のテクニックを使用した
問題解決」分野が、なかなかにマニアックだからです。
たとえばCPUやハードディスクのスペックを意識して、処理効率をあげるために
バッファやページサイズをいじったりする問題がでてきます。
普段からビックデータと格闘されて、そのあたりを気にしてコード書かれている方で
あればもしかしたら楽勝なのかもしれませんが、メモリ消費なんて考えたこともありません系の
プログラマーにとっては、ほぼ知らないことなのできっついです。
しかも英語で、コンピューターのハードに関することをあれこれ書いてくるので
コードだけおっていても、これが何のメリットのある処理なのか本文を読まないとわかりません。

僕は1年半の経験で、テキストで3カ月勉強して、模擬問題(これもe-learningアカウントが売ってます)もやって受験し、なんとか一発で受かりました。
ただSQLとマクロの分野で90%以上正解したおかげで合格したといった感じで、
高度なDATAステップ・プログラミングについては5-6割程度しか正解していなかったと思います。

③SAS Clinical Trials Programmer
薬の臨床開発に特化した内容で、(米国FDAでの申請を主とした)規制要件やCDISC、統計解析の
ことも出題されますが、正直いって、医薬系でDM/STATとして半年以上SAS使われている方で
あれば、僕は何の用意もせずに受験しても大丈夫じゃなかなぁと思います。
統計解析といってもt検定を説明している以下の4つ分のうち、正しいのはどれかといったような問題で選択肢が、もう明らかにこれしかないでしょといった感じなので、会社の新入社員研修レベルの統計解析の知識でも大丈夫かなと思います。
テキストはなく、参考文献がいくつか挙げられていますが、業務経験がある方であれば
手持ちの知識と経験でとけるはずです。
僕はAdvanceの3カ月後に受けましたが、Advanceとの難易度の落差に拍子抜けしました。


以上は個人的な感想なので、正しいものとして受け取らないでください。
ただ、自分が受験するときに、あまりにも受験した人の声が少なくて、なんだか不安だったので
こういったことを書きました。

詳細に覚えているわけではありませんが、より詳しい話をお聞きになりたい方は
ご連絡いただければと思います。








 

詰めSAS4回目_直積(デカルト積、単純結合)を作成する

直積(デカルト積、単純結合)はデータセット同士の全オブザベーションの組み合わせのようなもので3obsのデータセットと4obsのデータセットの直積をとると12obsになります。
実践で使用する機会がそれほど多いとは思いませんが、たまにあったり、また直積を作った方がスムーズに以後の処理ができることもあります。

問題は
data Q1;
input A B;
cards;
1 2
3 4
5 6
;
run;
【データセットQ1】




data Q2;
input C $ D $;
cards;
A B
C D
E F
;
run;
【データセットQ2】






の二つから











のようなデータセットを作ることです。





【解法1】
data A1;
set Q1;
 do i=1 to Q2OBS;
  set Q2 nobs=Q2OBS point=i;
  output;
 end;
run;

まずはデータステップで詰みです。nobs=オプションはデータセットの総obs数をとれるのですが
それをsetステートメントの前に使用できるというのは意外です。それをループ終点にして
point=でダイレクトアクセスする度に明示的にOutputすることで、Q1のSetによって、Q1が1obs読まれる度にi=1からQ2の最後のobsまでOutputされ、それがQ1のSetが全obs完了するまで行われるので結果として直積が作成されます。2つのデータセットのサイズによって変わりますが
基本的にはこれが最も効率の良い作成法だと思われます。コードもコンパクトにまとまってます。


【解法2】
proc sql noprint;
 create table A2 as
  select *
  from Q1,Q2;
quit;

直積はSQLの基本ともいえるので(単純結合というぐらいですし)、SQLで詰ませます。
cross join等の方が綺麗な書き方なのかもしれませんが、とりあえず一番単純な書き方で実現です。SQLを使う場合の注意点は、ログにnoteとしてデカルト積が生成されたことを知らせるメッセージが必ずでるので、ログの内容に厳格に注意を払う仕事では留意する必要があります。

【解法3】
data A3;
 if _N_=0 then do;
  set Q1 Q2;
 end;
 if _N_=1 then do;
  declare hash hq(dataset:'Q2');
  declare hiter hi('hq');
   hq.definekey('C','D');
   hq.definedata('C','D');
   hq.definedone();
 end;
  set Q1;
  rc=hi.first();
   if rc=0 then output;
  do while(rc=0);
   rc=hi.next();
   if rc=0 then output;
  end;
 drop rc;
run;

ハッシュ反復子でやってみました。ハッシュはメモリにデータを展開するので、作成が高速化するか
と思ってやったのですが、コードが悪いのか、いくつかテストした条件ではデータステップやSQLの
1.5~2倍遅い結果しかだせていません。効率悪いはコードは長くて意味不明だわでいいとこなしな
感じですが、恐らくQ1がかなり巨大でQ2もそこそこの大きさのデータセットにして、オプションで
ハッシュの割当サイズを適度にすれば、速くなったりする気がします。いや、そもそも直積にハッシュはあまり意味がないのか。すみません、勉強中です。



詰めSAS3回目_最初にnullでない変数の値を取得する

変数を順番にみていって、null以外が初めて出現する際の値を取得する。また全ての変数がnullである場合、特定の値を代入する。
詰めSAS3回目は少し変わった処理になります。

問題は以下のデータセット

data Q3;
length A B C D $2.;
A='';B='ろ';C='';D='に';output;
A='い';B='ろ';C='は';D='に';output;
A='';B='';C='は';D='に';output;
A='';B='';C='';D='に';output;
A='';B='';C='';D='';output;
run;









について、A→B→C→Dとみていき、nullでない最初の値、すべてnullの場合は「へ」と返し
目的局面図









のようなデータセットを作成します。



【解法1】
data A1;
length E $2.;
 set Q3;
  E=coalescec(A,B,C,D,'へ');
 keep E;
run;

第一感として、配列とdo loopで処理する方法があると思うのですが
最短最善はcoalesce関数(今回は対象が文字変数なのでcoalescec)だと思っています。
引数のうち、指定順にnullでない最初のものを返します。
もともとSQLで使われていた関数のSASへの輸入だと思いますが、こいつは非常に優秀な
関数で、横方向への順次走査のような処理を関数でできてしまいます。定数をつかえば
最終的にnullの場合に定数を与えることができます。
たとえば臨床試験で、CRFにチェックボックスがいくつかあるもので、
よく、一つでもチェックがあれば何かのフラグをたてるといった処理がありますが
これをつかえば、ifステートメントを書き連ねる必要がなくなります。
ちなみにcoalesceはコアレスと発音します。
僕はコールエッセ関数とずっと呼んでいて恥をかきました。コアレスなんて読めます?

【解法2】
data A2;
 set Q3;
  array AR{4} $ A B C D;
   do i=1 to 4;
    if AR{i}^='' then do;
    E=AR{i};
  leave;
   end;
  end;
  if E='' then E='へ';
 keep E;
run;

配列ならこんな感じでしょうか?実は最近までleaveステートメントの存在を知りませんでした。
配列を抜けるときに使うのですね。




詰めSAS2回目_複数の変数の値で転置する

SAS9.2からtransposeプロシジャのidステートメントに2個以上の変数を指定可能になりました。
あまり取り上げられるところを見たことがないのですが、これはかなり革命的なことだと思っています。いずれ取り上げたいですが、meansプロシジャのclassdataステートメントと合わせれば、医薬系で臨床試験の有害事象を群別Grade集計するのなどが、かなり楽でスマートにできるようになりました。

ほとんど答えを先に言ってしまいましたが、詰めSAS_2問目です。


data Q1;
input X Y Z;
cards;
1 4 3
2 9 8
7 6 5
;
run;







このデータセットについてX、Yの値によってZの値を転置して以下のデータセットを作ります。

目的局面図は以下です。






【解法1】
proc transpose data=Q2 out=A1(drop=_NAME_);
 var Z;
 id X Y;
run;

これだけで詰んでます。idステートメントの複数変数指定はもっと注目されるべきSASのバージョンアップポイントだと思います。

【解法2】
data A2;
 set Q1(where=(X=1 and Y=4) rename=(Z=_14));
 set Q1(where=(X=2 and Y=9) rename=(Z=_29));
 set Q1(where=(X=7 and Y=6) rename=(Z=_76));
 drop X Y;
run;

Setの連続掛けは個人的に、とてもとても好きな方法です。指定したデータセットの中で最小のobs数に統一されることと、同名の変数が上書きされる性質は扱いにくい一方で、mergeステートメントとは違った魅力があると思います。


詰めSAS1回目_全変数全obsで最大の値をとる

詰めSASの1回目のテーマは、データセット中の最大の値を1変数1obsのデータセットに格納する最善手を考えたいと思います。1手?詰めです。

まず、問いの内容は

data Q1;
input X Y Z;
cards;
1 4 3
2 9 8
7 6 5
;
run;







のデータセットから最大の値、この場合、Yの2obs目の9という値を
変数Mのみの新しいデータセットに格納するという目的です。

目的局面図は





です。


以下、解法です。

【解法1】
data A1;
 set Q1 end=eof;
 retain M;
 if max(X,Y,Z)>M then M=max(X,Y,Z);
 if eof;
 keep M;
run;

最大の値をだす場合に、第一感で思い浮かぶのはproc univariateやmeans等の
利用かもしれませんが、その場合は変数ごとの最大をだした後にデータステップで
その中からの最大をだすことになり2ステップになってしまうと思います。

なので、データステップ1回でやっつけたいのですが、SASのmax関数は横(行)方向の最大値
をとる関数なので、そこで出した値を次のobsに持ち込まないといけません。
なのでretainを使って、持ち越し、endオプションで最終obsだけ残します。

【解法2】
proc sql noprint;
 create table A2 as
  select max(M) as M
   from 
  (select X as M from Q1
    union
   select Y as M from Q1
    union
   select Z as M from Q1)
;
quit;

SQL内でmax関数は1変数内を縦にみて最大値を返すので、じゃあ全部の変数を縦に
つないでからかけてやればいいんじゃないかと思って書いてみると
思ったより頭悪い感じのコードになりました。また、サブクエリを使うと、あまり一手といえなくなって
しまいますが。これならサブクエリ内でX、Y、Zで最大をとってからCASE文で比較して最大を
とる方がスマートだったかも。

【解法3】
data A3;
 obs1=1;
 obs2=2;
 obs3=3;
  set Q1(rename=(X=X1 Y=Y1 Z=Z1)) point=obs1;
  set Q1(rename=(X=X2 Y=Y2 Z=Z2)) point=obs2;
  set Q1(rename=(X=X3 Y=Y3 Z=Z3)) point=obs3;
   M=max(of X1--Z3);
  keep M;
  output;
 stop;
 run;

これは悪ふざけです。横に最大を返すなら、全部横にしてから、かければ
それで決着という方向で書きました。

1回目はここまで。


====================================================
後日、コメントをいただいて追記
====================================================
SQLでの記述について、突っ込みをいれていただきました。
以下のコードで詰みですね!スマート!!
思いつきもしませんでした!

標準SQLのmax関数は引数が1つなのですが、複数の引数を指定した場合
SASはSQLプロシジャ内でもSASのMAX関数と認識するそうです。
奥が深い、、

proc sql;
create table A4 as
select max(max(X,Y,Z)) as M
from Q1;
quit;