subpad関数はもう少し使われてもよさそうだけどもって話

substr関数を使って、特定の文字数で変数を分割したいなってときがあったとします。
3文字ごとに3つの変数にわけようと思って、以下のコードを書いたとします。

data b;
set a;
x1=substr(x,1,3);
x2=substr(x,4,3);
x3=substr(x,7,3);
run;

ところが流し込むデータを受け取ってみると、こんな感じでした。

data a;
x="1234";output;
run;

先のコードをデータセットaに対して実行すると





となり、結果は正しいのですが






ログにノートがでます。
xのlengthが4のため、4文字目から3文字とれと言われても
はみ出てるやんっていうメッセージです。
同様の理由で7文字目はそもそもないのでメッセージがでます。

まあ、理由も明確に説明できるので
いいちゃいいかもしれませんが、理想としてはログにこういうのは極力だしたくないし
_ERROR_=1たつのはやはり健全ではない。

この場合においては、subpad関数が有効で、はみ出た分は自動的に空白で埋めて
ログには何もださない。SASによる説明に少し補足すると
「必要に応じて空白埋め込みを使用し、(第3引数で)指定した長さの部分文字列を返します。」

書き方はsubstrと同じで以下になる。

data c;
set a;
x1=subpad(x,1,3);
x2=subpad(x,4,3);
x3=subpad(x,7,3);
run;

ただし、要注意なのがsubstr変数が作るx1 x2 x3のlengthは
もとの変数xのlengthが引き継がれますが
subpad関数で作成される変数は文字変数戻りのデフォである
$200.になるのでそこは要注意です。

substrなら、性質上、元の変数のlengthを引き継いでおけば生成される変数で内部の値が
それを超えることはありえないので文字切れはないですが、subpadの場合、
 x1=subpad(x,1,300);などとした場合201-300文字は切れることになります。

ただ、その場合は親切設計で



とわかりやすくですので、ログを全く見ないアンポンタン以外は、まず気づけるはず。

割といい関数だけど、存在をよく忘れてしまう






小ネタ:%putに_readonly_と_writable_が追加されてるなぁ

本来、以前の記事といっしょに挙げようと思っていて忘れていた話。

9.4M3でreadonlyが追加されたことを受けて、%putの指定で、
読み取り専用のマクロ変数を出力する_readonly_と、逆に
上書き可能なマクロ変数を出力する_writable_が追加されている。

_global_ _local_ _user_と_all_ _automatic_も復習しておくと

%global A;
%global B;
%global  /readonly C=1 ;

%macro m1;
%local A;
%local D;
%local /readonly E=1 ;

%put *===_global_===*;
%put _global_;

%put *===_local_===*;
%put _local_;

%put *===_user_===*;
%put _user_;

%put *★===_readonly_===*;
%put _readonly_;

%put *★===_writable_===*;
%put _writable_;

/*===_all_はたくさん出るから割愛===*/
/*%put _all_;*/

/*===_automatic_はたくさん出るから割愛===*/
/*%put _automatic_;*/

%mend;

%m1

結果は

*===_global_===*
GLOBAL A
GLOBAL B
GLOBAL C 1
*===_local_===*
M1 A
M1 D
M1 E 1
*===_user_===*
M1 A
M1 D
M1 E 1
GLOBAL A
GLOBAL B
GLOBAL C 1
*★===_readonly_===*
M1 E 1
GLOBAL C 1
*★===_writable_===*
M1 A
M1 D
GLOBAL A
GLOBAL B



みたいな感じ

小ネタ:なぜこの男らしいコードが通らないのか

人のコード見てて、すごい面白かったのが
以下のコードです。

options nodsnferr;
data all;
set wk1-wk9999999999;
run;

なるほどね。この人は set wk: ;の書き方を知らなかったわけです。
しかしnodsnferrを調べて頑張った。勢いを感じるコードですね

ちなみにnodsnferrについては

http://sas-tumesas.blogspot.jp/2014/03/error-workxxdata-0.html

を参照してください。

その発想は面白いのですが、残念ながらこのコードは実行すると失敗します。

なぜ失敗するでしょうか?また具体的にどう直せばコードとして成立するでしょうか?

正解をいうとですね、
一度実行してみて、エラーメッセージをみるとはっきりします

ERROR: 数字付きデータセットリスト(WORK.wk1-WORK.wk9999999999)の数字が大きすぎます。最大値は2147483647です。

ね。そういうわけなんですよ。

一般的な言語の整数型の範囲は -2,147,483,648 ~ 2,147,483,647( ± 2**31 )ですがその値が、データセットの接尾数値検索の上限値になってるんですね。
(2の31乗なのは2進法で32bitのうち、符号に使う1を引いた数ですね)

なので
set wk1-wk2147483647;とすれば、コードとしては成立します。
しかし注意!!!!!! コロンと違って、ハイフンでつないだ場合、SASがデータセットを順に見ていくのにそれなりに時間かかります。億超えなんて指定した場合は、洒落にならんぐらい時間かかるか、場合によっては落ちます。

なんにでも限界値というものがあるという教訓ですね。

以前このブログで警鐘をならした
「西暦2万1年問題」に通じるものがありますね。
ちなみにこの問題はまだ未解決です。SAS社には早急に対応してほしいですね。
残された時間は、あと1万8千年もないですからね

西暦2万1年問題
http://sas-tumesas.blogspot.jp/2014/11/1.html

if 0 then setにunion corrしたviewを突っ込むことで、2つのデータセットのLengthを大きい方に自動的に寄せることができるんじゃないかというアイデア

データステップの、誰もやらない、絶対役に立たないマニアックな書き方を紹介して
一部の特殊な嗜好を持った悲しい人達が、仕事中に見てニヤニヤできればいいなぁっていう思いでやってる部分が大きいのである意味、ブログの主旨にあった記事かも。

今日、仕事中に、setステートメントの前に割り当てステートメントで、setで指定しているデータセット中に含まれる変数を指定するとlengthはどうなるかという話で恥を書く。

ようするに以下のコードを実行した際にデータセットがどうなるかって話。

data XXXX;
 NAME="A";
 set sashelp.class;
run;

まあ、実際やってみて貰えればわかるんですけど、僕はちょっと勘違いしてたんですよ。
そしたら、えぇ、こんなのBASEレベルの基本知識っすよとドヤ顔される。
誰とはいいませんけど僕の席の後ろの席に

要するに、割り当てだろうが、setだろうが、先に入った値、変数でlengthが決まるという基本知識ですよ、ええ、SASの基本ですね、はいはい。うっかりしてましたってば。

例えば以下の2つのデータセットがあったとして

data X;
length A $1. B $5.;
A="1";B="1234";C=1;
run;





data Y;
length B $1. A $5.;
B="1";A="1234";D=2;
run;






Xを先にsetすると

data OUT1;
set X Y;
run;







よくみると、Aの2obs目が一文字目で切れてる。
確認してみると










データセットXの変数AのlengthがデータセットY由来にも適用されちゃってるわけですね。

逆に今度はYを先にsetすると


data OUT2;
set Y X;
run;







Bが切れるわけですよ。

どうすればいいのか?

いや、素直にlengthステートメントで指定すればいいんですよ。
切り捨てが起きうる状況の場合、必ずログに






こういうのがでるんで、そしたらlengthを調べて、十分な値にしてやればいいんです。

はい、でもそれだと面白くもなんともないんで、lengthステートメントを使わない方法を考えてみます。
たとえば変数が凄い多くあって、lengthが仕様で定義されてない場合とか面倒でしょ。

これも、指摘してきた人に指摘されたことですが、SQLのunionを使うと、大きい方にlengthを寄せてくれます。
つまり

proc sql;
create table OUT3 as
select * from X
union all corr
select * from Y
;
quit;

とすれば













おぉ!!
しかし、Xにしかない変数C、Yにしかない変数Dが落ちてやがる。

じゃあ、corrとればいいんでない?と思っちゃうかもしれないけど、変数情報が完全に同一でない
状況でcorrをつけないと、まあ、ひどいことになるから、興味のある人はやってみてください。

/*---------追記-----------------*/
matsuさんに突っ込まれました。「outer union corr」使えばいいやんと。
あ~、しまった、その通りです。ボケてましたよ…。駄目だなぁ。
というわけで、以下あんまり意味のない記事になってしまいましたが、
連結後の処理をSQLじゃなくて、データステップでやりたい場合?になるのかなぁ。
/*-----------------------------*/


じゃあどうするかということで、例えば2ステップですが、こんなんどうでしょう?

proc sql;
create view vw1 as
select * from X
union corr
select * from Y
;
quit;

data OUT4;
if 0 then set vw1;
set X Y;
run;
















おぉ!ばっちし!!
多分そんなに遅くないし、どんなデータが来てもコードを変えずに実行できるし、なかなか面白いんじゃないですか?

多分、他にもやりようあるので、是非考えてみてください。

以上。

Proc Reportとかのスタイルの値はフォーマットで返すようにしといてもいけるよって話

なんかしらアウトプット出す際に
値に応じて、レポートの色を変えたり、罫線の種類変えたり、太字にしたいってことありますよね。
例えばsashelp.classのデータセットで
変数WEIGTHが80以下の場合、フォントカラーを「緑」、80以上100未満の場合、「青」、
100以上の場合「赤」にしろって言われたらどうします?

ちなみに余談ですが、SASHELP.CLASSの単位はポンドなんですね。
デフォルトで変数ラベルをださない設定にしていたので、
当初、極度に肥満の子達のデータだと思ってました。
身長がインチの時点で気づけよって話ですが。

さらにさらに余談ですが9.4のM3からデータセット開いて、「表示」タブで
「列の名前」か「列のラベル」をチェックで選択した内容が、ずっと記憶されるようになりましたよ。

いずれ以下の記事とかは、若い人が読んだ時に、このじじい何書いてんだ?って思われるんだろうなぁ。

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

はい、本筋に戻します。
たとえばproc reportで、以下のように書けます。

proc report data=SASHELP.CLASS nowd;
   column  NAME WEIGHT;
   define  NAME   /display;
   define  WEIGHT/ display;
    compute WEIGHT;
       if .<WEIGHT< 80        then call define( _COL_, "style", "style=[color=green]" );
       if 80<=WEIGHT< 100 then call define( _COL_, "style", "style=[color=blue]" );
       if 100<=WEIGHT         then call define( _COL_, "style", "style=[color=red]" );
    endcomp;
quit;



























が、しかし、style指定の内容について、実はフォーマットを指定することができるんですね。すると、値にフォーマットが掛かった結果の値が、スタイルの引数になるわけです。

つまり

proc format;
 value wight_f
  low-<80="green"
  80-<100="blue"
  100-high="red"
;
run;

proc report data=SASHELP.CLASS nowd;
   column  NAME WEIGHT;
   define  NAME   /display;
   define  WEIGHT/ style=[color= wight_f.];
quit;

で、結果は同じとなるわけです。

だもんで、この程度であれば
proc printなんかでもいいわけです。

proc print data=sashelp.class;
 var name;
 var weight / style={foreground=wight_f.};
run;


色に限らず、なんでもできるので、カテゴリ幅がよくよく変わる動的スタイルの場合
フォーマットで管理した方が楽かもしれんですね。





俺のWORK、マクロ変数、マクロ、オプション、フォーマットを全てお前に託す。お前が引き継ぐんだ!PRESENVプロシジャ~!!

バトルもので能力の継承って格好いいですね。

さて、それはちょっと置いておいて。


SASプログラムAをバッチ実行

EXCELに戻る アンド 何らかの作業

SASプログラムBをバッチ実行

みたいな際にAを実行したときのオプションやマクロ変数、WORKの中身をBに引き継ぎたいという場合、どうするか?
AとBは別セッションで、連続しないとします。

まあ、色々方法はあると思いますが、9.4からのpresenvプロシジャを使えば

グローバルステートメントの定義内容、つまり
optionやgopition、filenameやtitleなどの定義
および、マクロ変数、マクロ、WORKのデータセットを復元するプログラムを作成することができます。

復元プログラムをincludeしてから、新しいプログラムを実行することで、あたかも連続したセッションで実行しているような環境の引継ぎができるわけですね

もともとSAS Enterprise Guide のグリッド?で使いやすくするために開発されたみたいだけど
普通に便利

例えば、以下のような適当なプログラムを実行
data a;
 x=1;
run;
options msglevel=i;
proc format;
 value fm 1="A";
run;
%macro test;
XXXXX
%mend test;
%let YYYYY=AAA;
%let YYYYY=ZZZZ;
title "タイトル";

そして以下のコードを実行して、

libname lib1 "任意のフォルダ。ここにデータセットやカタログが保存される";
options presenv;
proc presenv
permdir=lib1
sascode="任意のパス\hukugen.sas";
run; 

作成されたhukugen.sasの中身をみると

【以下が生成されたプログラム】

 OPTIONS msglevel=i;
 TITLE "タイトル";
 LIBNAME LIB1 "任意のフォルダ。ここにデータセットやカタログが保存される";
 OPTIONS presenv;
%let saved_options=options %sysfunc(getoption(source))
 %sysfunc(getoption(notes)) %sysfunc(getoption(obs,keyword))
 %sysfunc(getoption(firstobs,keyword));
options obs=max firstobs=1;
options nosource nonotes;
%macro dummy_macro_for_presenv; %mend;
data _null_; rc=filename('presenvf',' ','temp'); run;
proc printto log=presenvf new; run;
proc sql;
create table _data_ as select distinct memname
        from dictionary.catalogs
        where libname='LIB1' and memtype='CATALOG';
     quit;
data _null_; set;
     call execute(
      cats('proc catalog force c=LIB1.',memname,
      '; copy out=work.',memname,'; quit;')
      );
     run;
proc delete; run;
proc copy in=LIB1 out=work mt=data; quit;
proc printto; run;
data _null_; rc=filename('presenvf'); run;
data _null_;
     call symput('YYYYY','ZZZZ' );
     call symput('AFDSID','0' );
     call symput('AFDSNAME',TRIMN(' '));
     call symput('AFLIB',TRIMN(' '));
     call symput('AFSTR1',TRIMN(' '));
     call symput('AFSTR2',TRIMN(' '));
     call symput('FSPBDV',TRIMN(' '));
run;
options _last_=WORK.A;
&saved_options;

一回SASを閉じた後、これを実行すると、閉じる前と全く同じ状況になっていることがわかる。

俺の屍をこえてゆけ みたいな。ゲームやったことないけど




時限爆弾_wakeup関数

SASのtwitterをフォローすると、過去のFAQやテクニカルニュースをランダム?にツイートしてくれる。結構古いTipsが多いので、最近のSAS使いにとっては逆に勉強になる。
ちなみに中の人は、いい人で、たまにユーザー総会の端っこで、本売らされたりしてる。

最近それで知ったのが
https://www.sas.com/offices/asiapacific/japan/service/technical/faq/list/body/pc072.html


call sleepルーチンの前身として、sleep関数があるというのは知っていたけど
wakeup関数は知らなかったなぁ。

どうも最新のリファレンスには載っていない
でも使える。(on demandでは無理みたい)

日時を指定してその関数にかけると、WAKEUPウインドウというのがチカチカして
アイドリング状態になって。何時何分に起きて動きますよと教えてくれる。

wakeupの時間がくると、そのコード以下が実行される。
戻り値にはアイドリングしていた時間が返るが、通常いらないので_null_を指定する

時間を決めた処理はバッチでやることが多いと思うので、一体どういうときに使うんだろう?
実行開始時間がSASプログラム内で動的に決まるようなケース?
それってなんだろう??

まあ、簡単に書けるので、大して重要でもないSASコードの実行押すためだけに待機っていう悲しいシチュエーションがあるなら、wakeup関数さっと書いて実行して帰っちゃえばいいのかもしんないですね。


data _null_;
   slept_time=wakeup('24JAN2017:23:00:00'dt);

   /*以下は2017年1月24日23時00分以降に実行される*/
run;

data b;
x=1;
run;