データセットにパスワードをかける話とPWENCODEプロシジャで、コード内に直接パスワードを記載しなくてもよくする話

data Q1(pw=SASYAMA) ;
 X=1;
run;

とすると、データセットQ1に「SASYAMA」というパスワードを書けることができます。

このデータセットを更新・削除・その他操作しようとすると

data A0;
 set Q1;
run;







で、ここでSASYAMAって打てば、処理が実行されます。


で、いちいち手で入力しなくても

data A1;
 set Q1(pw=SASYAMA);
run;

とすればOKです。

でも、人にあげるプログラムで、その人にパスワードに「SASYAMA」って値を使っているって
しられたくない場合、つまり、ようするにコードの中に直接パスワードの文字を記述したくない
場合にどうするかという話です。

まず、あげるプログラム以外のエディタで
proc pwencode in="SASYAMA";
run;

と、パスワードをin=で指定して実行します。

するとログに











謎の文字列が出現します。

実は、これが暗号化された「SASYAMA」です。

なので

人に渡すプログラムは

data A2;
 set Q1(pw="{sas002}BCED1140421B96950FEE34A400661C44");
run;

とすればSASの内部で、実行する際に{sas002}BCED1140421B96950FEE34A400661C44が
復号されて「SASYAMA」として処理されます。
ログ等、いくら探しても、実行者はパスワードを知ることはできません。





SASからクリップボードにデータを出力する

コピーやカットしたした時に、一時的に保存される領域がクリップボードです。
ペーストしたら、そこから張り付けられるわけですね。

そのクリップボードにSASからデータをぶち込むことができます。

filename clip clipbrd;

data _null_;
file clip;
put '新手一生';
run;


として、ペーストすると

putした値がでてくるはずです。

エクセルなどに強引に値を渡す場合、
SASから値をクリップボードにつっこんでエクセルの方でペーストで展開するなんてことも
できます。


頑健なプログラムのための「受け」のテクニック_(REPEMPTY=NO)で空データセットでの上書きをガードする

頑健なプログラム書ける人っています。
シンプルかつ、イレギュラーな値が入っても処理が揺らがない、或いは
直すべき場所がすぐわかるような、しっかりしたコードは憧れます。

将棋で言うなら「受け」の技術って感じですね。


そんな受けのテクニックの一つです。

今、

data A;
 X=1;
run;

data B;
run;

というデータセットがあったとします。

データセット「B」は変数オブザベーションを持たない
全くのnullデータ、クズセットです。

本来はきちんとしたデータセットのはずなのに
何か手違いがあってこうなってしまったと思ってください。

そこで以下のコードを実行すると

data A;
 set B;
run;

当然「A」の中身もなくなります。
仮に「A」が大事なデータセットだとして、
setする「B」が変数とオブザベーションをもった普通のデータセットの時のみ
処理したければ、どうしますか?

マクロ等で、データセットの中身を調べてから分岐してもいいですが、
この場合、もっと、簡単に防御できます。

data A;
 X=1;
run;

data B;
run;


data A(REPEMPTY=NO);
 set B;
run;

とデータセットオプション(REPEMPTY=NO)とすると








「WARNING: REPEMPTY=NO and the replacement file is empty.ため、データセット WORK.A を置き換えていません。」

というログに出て、Aの上書きが拒絶されます。
Aの中身はそのままです。

以下のような場合もどうようです。

proc sort data=A out=A(REPEMPTY=NO);
 where X>5;
 by X;
run;

この書き方も結構使えます。読み込まれたデータがないなどで
解析出力がないは上書きしないみたいなことが実現できます。


まあ、僕はSASも将棋も受けは下手なので、あまり詳しくないですが。

データセットオプションの適応順の推測

自分がしょっちゅう間違えるので、自分のために整理します。

さて

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

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


以下の①から④までのプログラムを実行したとき
エラー・ワーニングがでずに、きちんとYにリネームされた
データセットが作成されるのはどれでしょうか??

/*①*/
proc sort data=Q1 out=A1(rename=(X=Y) where=(X>5));
 by X;
run;

/*②*/
proc sort data=Q1 out=A2(rename=(X=Y) where=(Y>5));
 by X;
run;

/*③*/
proc sort data=Q1 out=A3(rename=(X=Y) keep=X);
 by X;
run;

/*④*/
proc sort data=Q1 out=A4(rename=(X=Y) keep=Y);
 by X;
run;


なんか、SAS BASEの資格試験にでてきそうな、いやらしい問題ですね



まず
【①の結果】








データセットは作成されません


【②の結果】








【③の結果】









【④の結果】









WARNINGだけですが、データセットは作成されてません


というわけで

×


×

でした。


推理するに、データセットオプション内でのステートメントについて
keep → rename → where
の順で適応されているということでしょうかね。

たぶん、きちんと資料読めば、どっかに書いてあるんでしょうか、とりあえずここまで






データセット名を変更する。SCL関数のrenameで、変更前後の名前リストを参照して一括変更する方法

データセットの名前を変えます。

data A0;
 X=1;
run;
data A1;
 X=1;
run;
data A2;
 X=1;
run;
data A3;
 X=1;
run;


とりあえず、データセットのよろずごとはproc datasetsにお任せって感じで、以下のようにかけます。

proc datasets;
 change A0=Z;
quit;

でも個人的にproc datasetsあんまり好きじゃないです。なんか文法が特殊で覚えにくいです。
changeステートメントなんて、ぱっとでてきません。

僕はもっぱら

data _null_;
 rc=rename('A1','B1','data');
run;

rename関数使います。
rename関数のいいところは、たとえば

data list;
OLD='A2';NEW='C2';output;
OLD='A3';NEW='C3';output;
run;

のように、現在のデータセット名と、リネーム後のデータセット名をリスト化したものを
データセットとして作成しておけば後は

data _null_;
 set list;
 rc=rename(OLD,NEW,'data');
run;


って感じで、百個だろうが二百個だろうが、一瞬でリネームできます。

dictionaryテーブルなどから現在のデータセット名の情報をとってきて、
一定のルールにしたがって(先頭にアンダースコア付けるとか、末尾に連番とか)の
処理を仕掛けたい時に重宝します。

まあ、データが小さければ、その都度新規にデータセット作ってもいいと思いますが。

あ、ちなみに、rename関数はデータセット以外でも、ファイル名やフォルダ名変えたり、色々できます。

あと、たまに質問いただきますが rc=はreturn codeの略でリターンコードです。
処理がうまくいけば「0」が入ります。なので、0じゃなかったらputで「うまくいきませんでした」的な
メッセージを出すようにすれば、どの処理が失敗したかを判別できます。

今 _null_で作っていますが、これを適当なデータセット名にして、listのoldの値を、存在しないデータセット何して、実行した後、作成されたデータセットのrcの値を見てもらえばわかりやすいと思います。

SCL関数とよばれるやつらは、みんなこういう形態をとります。

ハッシュオブジェクト扱う時のrc=とか、インデックスとかでやりくりするときの
IORC=(Input Output Return Codeの略)とかと、全く同じようなものです。








自動SQL変数? userで、SAS実行ユーザーの情報をマクロ変数を使わずにゲットする話

自動マクロ変数ってあります。

ユーザーが値を入れなくても、勝手に決まった名前で作成されているマクロ変数です。

%put _automatic_;

って書いて実行すればログに溢れてきます。
なにせ自動で作成されているので、利用すると労力削減になるケースが多くて万歳です。


例えば、データセットを作成するプログラムがあったとして、
そのデータセットを誰が作ったかをデータセットに変数の値として持たせたいとします。

data A;
 X=2;
run;

自動マクロ変数&SYSUSERIDを使えば、SASセッションを開始した
ユーザー(PCに設定されているユーザー名)が簡単に取得できます。

data B;
set A;
 X=X*2;
 NAME="&SYSUSERID";
run;





これで、いちいちログ見なくても
SASYAMAがデータセットBを作成したってことが一目瞭然です。
(データセットに作成者全レコードに持たせるなんてマジ無駄ですが)

で、これを同じ様にSQLで書くと

proc sql;

create table C as
select X*2 as X
       ,user as NAME
from A;

quit;

とかけます。

この「user」っていうのが、別にマクロ変数でもないのに、固有の変数名で
&SYSUSERIDと同じ値を返します。


でも当然


proc sql;

create table D as
select X*2 as X
       ,"&SYSUSERID"as NAME
from A;

quit;

も通るので、あんまりお勧めできる書き方ではないかも
(ぱっと見、知っている人じゃないと意味が取れないし)

意地でもマクロ変数をコードから排除してやるって方はどうぞ。


ちなみに僕は必然性がない限り極力マクロは使わない派です。
マクロを使うべきところに使っているのは当然いいですし、マクロなしで
SASプログラミングは成立しないと思っています。

しかし、他人様のSASコードを見ていると、SQLで簡単に処理できたり
class や by ステートメントやtransposeプロシジャをうまく使えば

簡単なコードと最小ステップでまわせるような処理を
わざわざ細切れのマクロにして、グルグル回していたりして、
それはSASの良さを殺してんじゃないかしら、、と思ったりして。


かなり偉そうになりましたのでこの辺まで。

多分、複雑なマクロ組めないのでひがんでいるんですね










inオプションは1データセット1回しか使えないっぽい話

今、以下の二つのデータセットがあって

data Q1;
X=1;
run;





data Q2;
X=2;
run;






上の2つから下のデータセットを作る必要がありました








これはまず、Q1のオブザベーションを2倍にコピーして、
最初の値にだけ5を加える。
それをQ2に対しても行う。
両方を縦に結合して、Q1由来の場合はYの値を1、Q2由来の場合はYを2としたことで
作成できます。

僕はまず、ぱっと
data A0;
 set Q1(in=in1 in=ina)
     Q1(in=ina)
     Q2(in=in1 in=inb)
     Q2(in=inb)
;

if in1 then X=X+5;
if ina then Y=1;
if inb then Y=2;

run;

というコードが浮かびました。ただ、書いた瞬間に、あ~これは通らなさそうだなぁという感触が
ありました。

案の定、結果は









となりました。つまりin=in1が効いてないわけです。
ということはすなわち、in=は複数指定できないというわけです。

一応(in=in1 ina) とか(in=in1,ina)とか、記述法を思いつく限り試してみましたが、やっぱり無理でした。
in=が複数指定できるとコードの自由度爆発だから、いつかできるようにならないかな、、、。


まあ、結局、このケースの場合はinで複数指定できない分
indsname=オプションで代用することで実現できましたが、データセット名がカギになる
ケースしか使えんし、イマイチです。

data A1;
 set Q1(in=in1)
     Q1
     Q2(in=in1)
     Q2
indsname=NAME;
;

if in1 then X=X+5;
if NAME='WORK.Q1' then Y=1;
if NAME='WORK.Q2' then Y=2;

run;





DO LOOPとcontinueステートメントで逆に考えるんだ 

例えば1から10までの数字で、奇数のものだけをオブザベーションにもったデータセット









を作れと言われたら、第一感で浮かぶのは

data A1;
do X=1 to 10;
  if mod(X,2)=1 then output;
end;
run;


こんな感じのコードではないでしょうか?
ループをまわして、条件に合った時のみ処理するというのは、自然な発想だと思います。


でも敢えて逆に考えてみます。

余りが1の時に処理するのではなく、余りが0の時に処理しないというように考えてみます。

すると

data A2;
do X=1 to 10;
  if mod(X,2)=0 then continue;
  output;
end;
run;

こうなります。

結果は同じです。

この発想の方が、プログラムが自然にかける時もあるので、覚えておいて損はないはずです。


ちなみに、continueとleaveは同違うのかというと、continueがスルーしてループを継続するのに対して、leaveはその時点で、ループを打ち切ります。

なので

data A3;
do X=1 to 10;
  if mod(X,2)=0 then leave;
  output;
end;
run;

すると





ってなっちゃいます。


処理前後のデータセットの更新内容を確認するマクロ

昔、自分が書いたコードを整理していて、見つけたのですが、以下のマクロが結構面白かったです。

%macro updatecomp(base,update,key);
 proc datasets;
   modify &base(genmax=2);
 run;
 data &base;
  update &base &update;
  by &key;
 run;
 proc compare base=&base comp=&base(gennum=-1);
 run;
%mend;

マスターとなるデータを、更新用のデータでどんどん更新していく処理をしていた時に、SAS初心者で、怖かったため、実行の都度、どこがどのように更新されたかをアウトプットウインドウで確認したくて書いたみたいです。
全く覚えてなかったですが、初心者にしてはなかなか渋いコード書くなぁと感心しました。

genmaxとgennumについては

「世代管理機能でデータセットの更新履歴(更新前後のDS)を残す_GENMAX=オプション」
http://sas-tumesas.blogspot.jp/2013/11/dsgenmax.html

で紹介しています。

使い方としては

例えば

data Q1;
do X=1 to 30;
 Y='A';output;
end;
run;

data Q2;
 do X=1,3,15;
  Y='B';output;
 end;
run;

のような二つのデータセットがあって、
Xをキーとして、Q1のデータをQ2で更新したい場合、

%updatecomp(Q1,Q2,X)

とすると、Q1が更新され、アウトプットには








と、更新処理の前後の差異がでてきます。









文字操作関数の戻り値がデフォルトで$200になるルールは必ずしも成立しないから気をつけろという話

いつも以上にマニアックな話ですが、最近発見したことを書きます。
場合によっては予期せぬ結果を生む可能性のある仕様についてです。

ログにあるメッセージをだしたいので、まず msglevelをi にしておきます。
options msglevel=i;


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

data Q1;
X='A';
run;


そこで

data A1;
set Q1;
Y=repeat(X,100);
Z=repeat('A',100);
run;

と書いて実行します。

YもZもやっていることは同じで、文字値'A'を100回繰り返して
「AAAAAAAAA・・・・・・」といった値を入れています。

Y,Zについて、lengthを指定していないため



いつものようにデフォルト$200ルールが適用されます。














さて、ここまではそれでいいのですが、次に

proc sql noprint;
create table A2 as
 select repeat(X,100) as Y
        ,repeat('A',100) as Z
 from Q1;
quit;

を実行するとどうなるでしょうか?

僕はてっきり、先のコードと全く同じ結果になると思っていましたが実際は















変数X由来のYについては長さは200ですが
定数から直に作ったZの長さは200になってません!データステップなら
なっていたのにです!
ちなみにログには何もでません。


SQLで、多数の抽出結果をunionで縦に繋ぎまくる処理を書いている際、
文字切れが生じていて初めて発見しました。


9.2から、lengthの違う変数をSETで縦結合した際、「WARNING」がでるようになりましたが
(ちなみにoptions varlencheck=nowarnででなくもできまし)、

SQLプロシジャ内のunionだと文字切れが生じていてもおかまいなしなので
ゆめゆめ気をつけましょう。

文字切れは怖い













_DATA_の話

ちょっとしたお遊び、クイズです。

以下のプログラムを実行すると、どんな結果になるでしょうか?


data _DATA_ _DATA_ _DATA_;
 X=1;
run;


正解は


















正解は「Data1」「Data2」「Data3」が同じ中身で作成されるでした。



さらにもう一問、

data _DATA_ _DATA_ _DATA_;
 X=1;
run;

と同じプログラムを実行すると今度はどんな結果になるでしょうか?







正解















中身はまた同じなので省略しますが「Data4」「Data5」「Data6」ができます。


さらにもう一問最終問題!


proc delete data=_all_;
run;

とWORKのデータセットを全て削除し











その上でまた

data _DATA_ _DATA_ _DATA_;
 X=1;
run;

と実行するとどうなるでしょうか?





正解は













「Data7」「Data8」「Data9」ができます。




ちなみに

data ;
 X=1;
run;

とdataの後を省略した場合、デフォルトで_DATA_が指定されます。
「どうてもいい話 データセット名をかかずに実行」で触れていました。
http://sas-tumesas.blogspot.jp/2013/10/blog-post_1261.html


数値(文字)変数にのみ一括で特定の値を割り当てる話

直接ご質問いただいた内容へのご回答になります。

データセット中の文字型変数の値はそのままで、数値型変数のみ全て1に置き換える処理を書きたいのですが、指定するデータセットに含まれる数値変数の数や名前は可変で、全てが文字型変数であることも考えられる状況です。エラーにならない方法を教えてください。
また、欠損値にする方法もあれば併せて教えてください。

といった内容でした。

多分、他にも方法ありそうなんですが、僕がぱっと思いついたのは

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






といったデータセットがあったとして

data A1;
 set Q1;
 array AR _numeric_;
 do over AR;
  AR=1;
 end;
run;






でいかがでしょうか?数値変数のX Zだけ値が変わってます。

「非明示添字配列と do over LOOPの利用」
http://sas-tumesas.blogspot.jp/search/label/do%20over

で紹介した方法を応用しています。

_numeric_(省略形は、_num_)はデータセット中の全ての数値変数、
_character_(省略形は、_char_)は文字変数、
_all_は文字通り全てです。
ちなみに、仮に全てが文字型変数で、数値型がなかったとしてもエラーにはなりません
「WARNING: 要素なしで配列を定義します。」はでますが。



次に、欠損値ということなら、配列組むまでもなく

data A2;
set Q1;
dummy=.;
call missing (of _numeric_);
drop dummy;
run;

でいけます。

dummyをかましているには、この場合は、数値変数が全くないと、引数不足でcall missingが
エラーになっちゃうからです。








SQL optimizerで実行アルゴリズムを強制する

SASにはSQL optimizerという概念というか機能みたいなものがあります。

以下は僕の独自解釈なので、詳しくはリファレンス等でご確認ください。

SASはproc sqlで指定したSQL文を実行してくれるのですが、一口にSQLを実行すると言っても
内部的に処理するアルゴリズムはたくさんあって、その中からSASが、
このデータセットに対してこのSQL文なら、こういう風にやれば最適なパフォーマンスが出るぜ!と判断した
方法が採用されます。

データセットに適切なインデックスが設定されている場合にjoinなどを使うと、インデックスが利用されますが
これはSASのSQL optimizerがインデックスマージが一番効率的だと判断するからです。

ただ、その判断が必ずしも正確無比とは限りません。たまに最適ではない方法を採用してしまうことも、理屈的には
ありえるわけです。

なので、書いたSQL文の実行が、やけに遅いなと感じたら、実行法をSQL optimizerに判定させるのではなく強制的に
指定してやることで、ごくごくごく稀にパフォーマンスがよくなることがあります
(ほとんどの場合、SQLが遅いのはそいつのSQL文の書き方が悪いだからで、SQL optimizerの判断ミスは少なく
ケチをつけるよりSQL文を見直すかデータステップで頑張った方がいいかも)

以下、実例です。
magic=オプションで決まった数字を足すだけです。

ログに採用された方法がでるはずです。
103はでないのですが確かハッシュ法?だった気がします。


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

data Q2;
X=2;Z=3;output;
X=3;Z=4;output;
run;


proc sql magic=101 noprint;
create table A1 as
 select Q1.X,Y,Z
 from Q1 left outer join Q2
 on Q1.X=Q2.X;
quit;

proc sql magic=102 noprint;
create table A1 as
 select Q1.X,Y,Z
 from Q1 left outer join Q2
 on Q1.X=Q2.X;
quit;

proc sql magic=103 noprint;
create table A1 as
 select Q1.X,Y,Z
 from Q1 left outer join Q2
 on Q1.X=Q2.X;
quit;

以上です。


ODS tagsets.excelxpでエクセルの関数式を仕込む

odsでエクセル(xml)ファイルを作成する際、以下のように書くことで
作成されるシートにエクセル関数を仕込むことができます。

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


ods tagsets.excelxp file='D:\TEST.xls';
proc print data=Q1 noobs;
 var X Y;
 var X / style={tagattr="formula:sum(RC[-1]:RC[-2])"};
run;
ods tagsets.Excelxp close;









上の例だと、そんなもんデータステップで計算してからだせよって感じで終わりですが
例えば、VLOOKUP関数で他のエクセルからデータ持ってくるようにするとか、シチュエーションに
よってはエクセルの特性を活かすことができます。



ODS CSVとODS CSVALLと ODS tagsets.CSVBYLINEの違い

CSV、ファイル形式としてあんまり好きじゃないですが、やっぱり、CSVでデータ送ったり送られたりは避けがたい時があります。

SASからCSVを作る方法はたくさんあって、fileステートメントとputでもいいし、EXPORTプロシジャでもいいし、%DS2CSVっていうデフォルトで用意されているマクロを使ってもできます。

で、一口にCSVといってもクォート文字で包んだり包まなかったり、変数を1行目にだしたりださなかったり、区切り文字をカンマ以外にしたり(そしたらCSVとは言わないか)とか色々あって、また改行コードが入ってたり、欠損があった場合とかして、読み込むのも吐き出すのも結構奥深い世界です。詳しくないです。

うまく読み込めなかったり、吐き出しが変なところで改行されて崩れたりして、イーってなることは日常茶飯事です。単純な作業のはずなのにうまくいかないのが余計むかつきます。

今回紹介するのはODSで作っちゃうパターンの話です。

ところが、一口にODSでCSVといっても、実はいくつか選択の幅があります。その話です。

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

ods csv file="D:\SAMPLE1.csv";
ods csvall file="D:\SAMPLE2.csv"  ;
ods tagsets.csvbyline file="D:\SAMPLE3.csv"  ;
proc print data=A noobs;
run;
ods csv close;
ods csvall close;
ods tagsets.csvbyline  close;

上のコードは、printプロシジャの出力を3種類の形式でCSVファイル化しています。

以下、実際作成されるCSVの中身です

【ODS CSV】










【ODS CSVALL】












【ODS tagsets.csvbyline】











tagsets.csvbylineの特徴がわかりにくいので

ods csv file="D:\ods_CSV.csv"  ;
ods csvall file="D:\ods_CSVALL.csv"  ;
ods tagsets.csvbyline file="D:\TAGSETS_CSVBYLINE.csv"  ;
title "プロシジャのタイトル";
   proc print  data=A NOOBS;
   by X;
   run;
ods csv close;
ods csvall close;
ods tagsets.csvbyline  close;

とタイトルとbyつけてみます。


【ODS CSV】












【ODS CSVALL】

















【ODS tagsets.csvbyline】














って感じです。

まあ、実際、そんな変則的な出力を敢えてCSV形式でださなければいけないシチュエーションって
なんだろうって気がしますが。



PUTN(PUTC)関数の話

多分、基本的な関数として知っている人が多いかもしれませんが
あまり紹介されている書籍が多くない気がするのでPUTN(PUTC)関数の紹介です。
(確か以前、SAS Technical NewsのQAで見た気がしますが)

proc format;
 value UMU 1='有'
           2='無'
          ;
 value SEX 1='男'
           2='女'
          ;
run;

上記のようなフォーマットがあったとします。

そして

data Q1;
X=1;FMT='SEX';output;
X=2;FMT='UMU';output;
X=1;FMT='UMU';output;
X=2;FMT='SEX';output;
run;








このようなデータセットがあり、変数Xの値に対して
どのようなフォーマットをあてるかが、変数の中に値(フォーマット名)で
格納されているとします。

そうした場合以下のようにPUTN関数に、文字化したい変数と、対応するフォーマット名の格納された変数を指定します

data A1;
 set Q1;
 Y=putn(X,FMT);
run;







で、これの文字フォーマット版がPUTC関数です。

逆に指定したインフォーマットで値を読み込みたい場合は
INPUTN INPUTCを使います。