~ チルダ記号の使い道

先日、知り合いの方に、マニアックな話が多いのに、チルダの使い方とか中々でてこないねぇと
言われました。

チルダ? ~ ←これのことですね。 X~=Yで ノットイコールとして使えるけど、僕はX^=Yって書くから、使いません。チルダってなんか他に独自の使い道あったけ?

あ~、思い出しました!

CSV読み込むときに、コーテーションごと値として読み込む奴だ!!

はいはい、実戦で1回も使ったことないから、すっかり忘れてました!!

例えば

data A0;
infile cards dsd;
input X $ Y $;
cards;
'A','B'
;
run;

とすると、結果は





ですね。

この場合、通常infileでcardsじゃなくて、csvの外部ファイルを指定するわけですが、csvは値の内容を区切るため(区切り文字そのものを値にできるようにするため)にシングルコーテーションやダブルコーテーションを括ることがあります。

dsdオプションを付けると、読み込む際にその余計なコーテーションの包装を除去して値のみをとってくれます(普通、その部分はいらないので)


ところが

data A1;
infile cards dsd;
input X~ $ Y $;
cards;
'A','B'
;
run;

のように包装紙ごとデータにしたい場合は その変数の後ろにチルダをつけます。
すると






ってわけです。


いつか、使うことが来る日まで覚えておこうっと



データセットラベルの話

知っている方からすると何を今さらなのですが、案外知られてないのではないかと思う話題です。

SASでラベルというと、labelステートメントで定義する変数ラベルのことが思い浮かぶと思うのですが、実は、変数だけではなくデータセットそのものにもラベルを付けることができます。

data Q1(label='20140831作成サンプルデータ');
 X=1;
run;

のように、データセットオプションの中にlabel=と指定することで、データセットにラベルがつきます。

このラベルは







のように、データセットをダブルクリックした際のウインドウ上部に表示されたり、
プロパティでみると





















「説明」の箇所に表示されます。

その他、プロシジャ出力の種類によっては、さりげなく出現します。

一度設定したデータセットラベルは

proc datasets nolist;
 modify Q1(label='20140831修正サンプルデータ');
run;
quit;

のようにして修正できます。


さて、それで一体、このデータセットラベルは何に使うかというと、
実際見たことがあるのが

data Q2(label='20130102作成');
run;

data Q3(label='20110302作成');
run;

data Q4(label='20130405作成');
run;

のように、永久データセットとして何かしらのデータを管理する場合、その説明として
作成日や作成者やバージョンや、その他説明を記載するという使い方です
(上記のコードは、サンプルなのでWORKにつくっていますが)

データステップのsashelp.vtable(SQLプロシジャで参照する場合はdictionary.table)
には、認識されている全てのデータセットの情報が入っているので

例えば後々、2013年に作成したデータセットの一覧が欲しいって際に

data LIST;
 set sashelp.vtable;
 where substr(memlabel,1,4)='2013';
 keep memname memlabel;
run;






みたいに、すぐに把握できたり、これとcall executeとかマクロを組み合わせて
特定のデータセット群を対象に一括処理をするみたいな発想が実現できます。

まあ、知っていて損はないデータセットラベルの話でした。






SASの一貫性制約(IC:integrity constraints)の話

Base SASにはデータセット内容の修正・変更を管理する技術が多く実装されていますが
日本での知名度がとても低く、利用例の報告が少ない気がします。

ただ、SAS認定プロフェッショナルのAdvancedの試験を公式テキストで学習された方は、そこに記載があるので知っている人が多いかもしれません。

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

・監査証跡(Audit Trail)
 記事「SASのAudit Trail(監査証跡)機能について」
 http://sas-tumesas.blogspot.jp/2014/06/sasaudit-trail.html


などに触れたことがありますが、今回は
一貫性制約(IC:integrity constraints)について紹介します。

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

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







そしてこのQ1にこれから、データをどんどん追加していくとします。

しかしQ1というデータセットには、その内容について以下のルールがあるとします。

ルール1:Xの値はユニークで、非欠損値であること
ルール2:Yの値は非欠損値であること
ルール3:Yの値は3以下であること
ルール4:Xの値が「G」かつYの値が「1」であってはいけない

上記のルールに外れるデータは、データセット内に存在してはいけないとします。

そういった際に、データの追加・変更の都度、条件式を書いてチェックしていては大変です。
そこで、データセットそのものに、データの一貫性を保つ制約を設定しておくことで、自動的に
ルールに外れるものをはじく仕組みを作成できます。

具体的にはdatasetsプロシジャを使って

proc datasets nolist;
 modify Q1;
 ic create rule1=primary key(X)
           message='Xがユニークでないか、欠測値だから許容できません!';
 ic create rule2=not null(Y)
           message='Yは欠損値であってはならぬ!';
 ic create rule3=check(where=(Y<=3))
           message='Yは3以下!';
 ic create rule4=check(where=(not(X='G' and Y=1)))
           message='XがGかつYが1の組み合わせは不可!';
run;
quit;

と書きます。

ic create 任意のルール名=ルールで一貫性制約を作成します。
message=はルールによってデータが弾かれた場合に、ログに表示されるメッセージです。

ルールの記述法はSASのヘルプ等で確認してください。


そして

data Q2;
X='A';Y=1;output;
X='D';Y=.;output;
X='F';Y=4;output;
X='G';Y=1;output;
X='H';Y=2;output;
run;









というデータセットを先ほどのQ1に追加してみます。

proc append base=Q1 data=Q2;
run;



すると
ログに

















「WARNING: Xがユニークでないか、欠測値だから許容できません!  データセット WORK.Q1
への追加/更新に失敗しました。データ値が一貫性制約 rule1 に適合しません。 1 オブザベーションが拒否されました。
WARNING: XがGかつYが1の組み合わせは不可!  データセット WORK.Q1
への追加/更新に失敗しました。データ値が一貫性制約 rule4 に適合しません。 1 オブザベーションが拒否されました。
WARNING: Yは3以下!  データセット WORK.Q1 への追加/更新に失敗しました。データ値が一貫性制約 rule3に適合しません。 1 オブザベーションが拒否されました。
WARNING: Yは欠損値であってはならぬ!  データセット WORK.Q1 への追加/更新に失敗しました。データ値が一貫性制約rule2 に適合しません。 1 オブザベーションが拒否されました。


とでて、

Q1に追加されたのは1オブザベーションのみで、後は全てルールにひっかかって
追加されなかったいうことがわかります。











永久データセットに対して、データの追加や変更を繰り返しながら、保守していくような状況の
場合、これを設定しておくと、一貫性が保たれるので便利です。




フリーテキストから日付を取り出す② 複数出現した分だけオブザベーションに起こすパターン

昔、アルバイトしていた時に、シフト表組んでたことがあったのですが、バイトメンバーがそれぞれ出勤できない日をメールで送ってきてそれをスケジュール帳に書き起こして、とても面倒でした。

データセットにすると以下のような感じです。

data Q1;
length NAME $10. COMMENT $100.;
NAME='Aさん';COMMENT='2014/01/02と2014/01/03は休みたいです';output;
NAME='Bさん';COMMENT='無理な日、2014/01/01,2014/01/03,2014/01/04';output;
NAME='Cさん';COMMENT='予定があるので2014/01/07はでれません';output;
run;






さてこれを











のような形にするにはどうしましょうか?

前回の例では、1データに1回しか日付が含まれていないという強力な前提条件がありましたが
今回は違います。
データを受取るまで、その人によって無理な日が何日あるかもわかりません。

さて、そういったパターン出現数が可変、不明である場合に全てのパターンを抽出したい時は
call prxnextルーチンとsubstrtのコンボが有効であったりします。

先にコードから

data A1;
set Q1;
   ID=prxparse("/\d{4}\/\d{1,2}\/\d{1,2}/");
   START=1;
   STOP=length(COMMENT);
   call prxnext(ID, START,STOP,COMMENT,POSITION,LENGTH);
      do while (POSITION>0);
         YASUMI=substr(COMMENT,POSITION,LENGTH);
   output;
         call prxnext(ID, START,STOP,COMMENT,POSITION,LENGTH);
      end;
run;

結果は






です。

変数NAME YASUMIだけをkeepすれば目的とするデータセットと同じになります。

call prxnextルーチンは第一引数にprxparse関数で定義した正規パターン、第2引数はパターンマッチングを開始する位置を指定しておくと、マッチする部分があったらその終了位置に更新されます(正確にいうとPOSITION + MAX(1,LENGTH)の値)。第3引数はパターンマッチする終点位置、第4引数に対象とする変数、第5引数はパターンマッチでマッチした場合の開始位置が入ります、そして第6引数にマッチした部分の長さが入ります。

do while (POSITION>0);としているのは、call prxnextルーチンの特性として、順次、次にマッチする部分を情報を返してくれるということと、マッチする箇所がある限りPOSITIONは数字を返すということから成立するループです。

YASUMI=substr(COMMENT,POSITION,LENGTH);

は一致する開始する位置から、一致した部分の長さ分を抜き出せばいいでしょっていうシンプルな発想ですね。







フリーテキストから日付部分のみを取り出す

正規表現を詳細に解説していると、とんでもない量になるので、使用頻度が高そうな実用例を少しだけ紹介します。

フリーテキスト入力で収集されたデータから、日付とってくれない?ちなみに日付以外にコメントも一緒に入っているから、いい感じに抜き取ってね。

みたいなことってたまにあります。

気軽にいってくれるけど、結構困りますよね。


今回は、比較的楽なパターンを考えてみます。

つまり日付はコメント内で1回しか出現せず、その形式はyyyy/mm/ddで統一して入力されているとします。
また13月35日など、存在しない日付は入らないのでチェックの必要なしとします。

つまり

data Q1;
X='今日は2014/01/02です';output;
X='2014/3/2晴れ後曇り';output;
X='2014/07/24';output;
X='誕生日は2014/7/02';output;
run;











のようなデータセットから日付部分を抽出します。

data A1;
 set Q1;
 Y= prxchange("s/.*(\d{4}\/\d{1,2}\/\d{1,2}).*/$1/",1,X);
run;

で、結果は






です。

prxchange関数は、引数で指定した正規表現にマッチする箇所を、引数で指定した文字列に置換した結果を返します。

"s/.*(\d{4}\/\d{1,2}\/\d{1,2}).*/$1/" ですが、

s/は置換を意味する決まり文句です
.*(\d{4}\/\d{1,2}\/\d{1,2}).* の部分ですが、.は任意の文字、*は出現回数が0~∞を示します。
括弧はキャプチャといって、あとでその部分を$で指定することができます。
\dは0-9の数字 { }は出現回数で、{4}だと4回 {1,2}だと1回から2回の間となります
\/の\はエスケープ文字で、/事態に意味があるので、それを消して、スラッシュを単なる文字としてのスラッシュとして使うための指定です。

つまり.*(\d{4}\/\d{1,2}\/\d{1,2}).*は

何かしらの文章(なくてもよい)の次に、数字四ケタがきて、スラッシュがきて、1ケタか2ケタの数字がきて、スラッシュがきて、1ケタか2ケタの数字がきて、そのあと何かしらの文章(なくてもよい)が続くパターンということになります。

つまり日付が含まれるテキスト全体が対象になるわけです。

次に$1の部分、これは(\d{4}\/\d{1,2}\/\d{1,2})、のキャプチャに対応しています。キャプチャが複数存在する場合、つまり()がたくさんでてくる場合は出現順に連番で、$1 $2などと指定できます。

要するにここでは、日付を含むテキスト全体を、日付の部分だけで置換することで、結果的に日付のみを得ようぜってことです。

1は、今回1回しか日付がでてこない仮定なので1でいいです。

詳しくは、きちんと体系だてて勉強した方がいいです






EXCELを媒介にして、漢字をカタカナに無理やり直す方法

日本語は、やはり母国語なので言語としては愛着があるのですが、ことプログラムで処理するとなると、これほど厄介な言語って他にあんのかな?って気になってしまいます。

名寄せ処理する上で大問題なのが、日本語は、漢字・ひらがな・カタカナ(+数字や記号)が混在しているということです。

最近、受けた質問ですが、例えば以下の2つのデータセットがあったとします。

data Q1;
length TERM $10.;
 TERM='豆腐';output;
 TERM='アズキ';output;
 TERM='だいず';output;
run;







data Q2;
length TERM $10.;
 TERM='大豆';output;
 TERM='とうふ';output;
 TERM='小豆';output;
run;







これで、この2つのデータセットを同じ言葉同士でマージしてと言われたらどうしますか?

人の目であれば、「豆腐」と「とうふ」が同じ言葉であることは一目了然なのですが、これを機械に
わかってもらうのはとても大変です。

たとえば、これが漢字を含まない、ひらがなとカタカナだけのデータであれば
kpropcase関数で、いずれかに統一させてマージができます。

kpropcaseについては色々な方が説明されているので、そちらを見て下さい

【SAS忘備録】
http://sas-boubi.blogspot.jp/2013/12/blog-post_16.html

【SAS Utility】
http://sasutility.blogspot.jp/2010/07/blog-post.html

【SAS社Q&A】
http://www.sas.com/offices/asiapacific/japan/service/technical/faq/list/body/ba268.html


ところが、漢字の変換になると、SASだけでは(僕の知る限り)変換処理する機能がありません
(多分ですが、、、。もしあったなら教えてください)

なので何かしら外部アプリケーションの力を借りるしかないはずです。

SASの中でCやJAVAなんかのコードを利用できるらしいので、そっちを使うか或いは、
proc httpなどでweb上とやりくりができるので、yahooが提供している「かな漢字変換API」
http://developer.yahoo.co.jp/webapi/jlp/jim/v1/conversion.html
などにデータを送って、変換されたものを受取るということもできるはずです。


しかし、いずれの方法をとるにせよ、SASとは別の知識が要求されるので、他の言語ができないと結構ハードルは高くなります。


そこで、まだちょっとは馴染みのあるEXCELさんに奴隷のように働いて貰いましょう。
EXCEL VBAも別の言語ですが、まぁまだ簡単でしょう。

ちなみに、これから紹介する方法は、もはやプログラミングではない!って感じの乱暴なものですのであしからず。


まず、適当な名前でマクロ付のexcelファイル(xlsm)を作成します。
ここではCドライブ直下に、BOOK1.xslmという名前でつくっています。

そして標準モジュールに

Function getphonetic(a)
getphonetic = Application.getphonetic(a)
End Function












と書いて、ユーザー定義関数を定義します。

このApplication.getphoneticは変換候補の一番目にくるカタカナを取得します。
なので、先に断っておきますが、必ずしも正しい読み仮名であるとは限りません。人名・地名や
読み方のたくさんある言葉、一般的ではない言葉の場合、思いどおりにいかないこともあります。

さて、関数が定義できれば

次にSheet1でいいので、1列目2列目を全選択して、名前を定義します。ここでは「AREA」としています。












さて、もう何をしようとしているか気付かれたと思いますが、ここで2列目の全セルに
「=GETPHONETIC(RC[-1])」という関数を入れます。








これによって1列目のセルに何か文字をいれたら、2列目でカタカナ変換された値がでてくる
作業シートができました。


あとはもうlibnameの世界ですね。

一気に全コードを載せます。

libname EX "C:\Book1.xlsm" header=no scantext=no;

data EX.'AREA'n;
 set Q1;
 modify EX.'AREA'n;
 F1=TERM;
run;

data _Q1;
length TERM1 KANA $10.;
 set EX.'AREA'n;
 where F1^='';
 TERM1=F1;
 KANA=F2;
keep TERM1 KANA;
run;

data EX.'AREA'n;
 set Q2;
 modify EX.'AREA'n;
 F1=TERM;
run;

data _Q2;
length TERM2 KANA $10.;
 set EX.'AREA'n;
 where F1^='';
 TERM2=F1;
 KANA=F2;
keep TERM2 KANA;
run;

proc sort data=_Q1;
 by KANA;
run;

proc sort data=_Q2;
 by KANA;
run;

data A1;
informat TERM1 TERM2; 
 merge _Q1 _Q2;
 by KANA;
run;


で結果のA1の中身は







というわけです。


ちなみに途中の_Q1の中身は








_Q2は







です。


さて、この方法はお手軽で、excelさえあればできますが、問題もあります。

まず、こういった名寄せ処理は扱うデータ数が巨大な場合が多いですが、この方法だと
excelシートの行の限界数までしかできませんし、変換のためだけに無駄にシートに読み書きして
いるので、もう馬鹿みたいに遅いです。重すぎて止まっちゃうかも。

また、普通にタイピングしていて変換して1番にくる文字を選ぶだけなので、データの文脈おかまいなしです。変換結果を目でチェックしたほうが無難です。

さて、悪口ばかり言いましたが、大した量でなくて、ちょっとくらいミスがあってもいいような名寄せならこれで充分実用に耐えうります。

来年の論文の一部にしようかな。










options mfile mprint; SASマクロによって実際に実行されたコードを、独立した1つのSASファイルとして保存する方法。つまりマクロのデバックや、SASでSASプログラムを作る時に役立つテクニックの話

他人の書いたSASマクロのデバックや修正が大好き!って方はいらっしゃいますか?恐らく、なかなかそんな人はいないはずです。

マクロのデバック法はたくさんあります。 mprint mlogic symbolgen 等等のオプションを使って、ログなんかにマクロやマクロ変数の中身をログに出したりすることが一般的だと思います。

ただ僕は、実際に展開された、平のSASコードを入手して、実際に動かしながら、挙動を確認していくのが一番わかりやすいです。

記憶力と想像力がしょぼいので、ログの情報をもとに、頭の中だけで展開されたコードを再現するのが苦手なんです。

例えば以下の一連のプログラムを見て下さい。
(例なので、極めて簡単なマクロです)

%let M=A;

%macro funclist(pre);
 data DS_&pre;
  set sashelp.vfunc;
  where first(fncname)="&pre";
 run;
%mend;

%funclist(&M)


これは、Aから始まるSAS関数を抽出するプログラムです。

%funclist(&M)

によって、実際に実行されるコードは


data DS_A;
set sashelp.vfunc;
where first(fncname)="A";
run;

なわけです。

この実際に実行されるコードを一つのSASプログラムとしてファイル保存したい場合、次のようにかきます。

filename mprint "C:\code.sas";
options mfile mprint;

%let M=A;

%macro funclist(pre);
 data DS_&pre;
  set sashelp.vfunc;
  where first(fncname)="&pre";
 run;
%mend;

%funclist(&M)


で実行すると、
Cドライブ直下に「code.sas」というファイルができて、これを開くと









って感じで、見事マクロの展開形が独立したSASファイルになりました。


これは、デバックだけではなく、SASでSASプログラムを動的に作るような処理にも使えます。
putなんかでテキストに打ち込んで作成する方法と違って、
実際にマクロが実行されなきゃ、展開作成されませんけどね。



proc treeでトーナメント表とか作ってみる

最近、割と役に立つ真面目なことばかり書いていた気がしたので、たまには無益なものを紹介します。

proc treeはproc clusterなんかの結果をデータセットにして読み込ませることで、クラスター分析の結果をデンドログラム(樹上図)で表現するのに使ったりするですが、当然、このブログではそんな真面目なことには使いません。

会社で将棋部の部長をやっていた時があって、その時に、トーナメント表をSASで作れないかな~
とか考えてて、treeプロシジャに目を付けました。

例えばAさん Bさん Cさん Dさんがいて、1回戦を A vs  B、C vs Dとして、その勝者同士で決勝戦を行うトーナメント表を作る場合は、以下のようにします。

data Q1;
length _NAME_  _PARENT_ $5.;
_NAME_='Aさん';_PARENT_='CL3';_HEIGHT_=0;output;
_NAME_='Bさん';_PARENT_='CL3';_HEIGHT_=0;output;
_NAME_='Cさん';_PARENT_='CL2';_HEIGHT_=0;output;
_NAME_='Dさん';_PARENT_='CL2';_HEIGHT_=0;output;
_NAME_='CL3';_PARENT_='CL1';_HEIGHT_=1;output;
_NAME_='CL2';_PARENT_='CL1';_HEIGHT_=1;output;
_NAME_='CL1';_PARENT_='CL0';_HEIGHT_=2;output;
_NAME_='CL0';_PARENT_='';_HEIGHT_=2.5;output;
run;

_NAME_はクラスター名、_PARENT_はそのクラスターの親(統合後)となるクラスターの名前で、HEIGHTは高さだと思ってください。HEIGHT=0は一番下層のクラスターです。
つまり、AさんとBさんを統合したものの名前がCL3で、CさんとDさんを統合したものの名前がCL2で、CL3とCL2を統合したものがCL1で、CL0は最後に優勝者の部分の線を盛り上げるために設定しています。

これをproc treeに流し込むと

proc tree data=Q1; 
 title '優勝'; 
run;

結果は























って感じです。


で、実はこれはもっと発展させることができます。

例えば、ランダムな要素を入れつつ、
部員同士の過去の対戦成績から、その部員の棋力をレーティング得点という評価値で表現し、
それに従って、できる限り力の近いものとあたる、或いは強い人が固まらないようなトーナメントを
自動生成させようと考えていました。樹形図は別に左右対称である必要はないので、極端に成績のよい人や前回優勝者をシード選手にすることも表現できます。

そういった一大マクロを作ってやろうと思ったのですが、そもそもクラブの出席率が非常に悪く、
これはトーナメント作っても消化できんなと思ったので断念しました。

会社に活気あるクラブがあって、トーナメント表作られている方は、ぜひSASで、メンバーと過去成績から、適切なトーナメント表を自動生成するマクロを作って、ぜひ公開してください。






欠損値の置き換え_call stdizeバージョン

SAS忘備録で、データの標準化を行うSTDIZEプロシジャを利用して、欠損値を一気に指定の値に置換する方法が紹介されてました。
http://sas-boubi.blogspot.jp/2014/03/blog-post_20.html

これを読んだ時、あ、じゃあ同じことが call STDIZEルーチンでもできるんじゃない!
一回プロシジャステップ挟まなくても、データステップ中でできれば、そっちの方がいいな!と思って、挑戦したのですが、STDIZEプロシジャのreponlyに対応する記述法がわかんねぇ。

ヘルプにもそれらしきものは載ってないし、無理なの?と思って、放置していました。

最近、SAS Support Commnity(https://communities.sas.com/community/support-communities)をふらふらしてたら、いくつかヒントらしきものを見つけました。

それに従ってやってみたのが、以下のコードです。

まずはサンプルデータ

data DT1;
input A B C;
cards;
1 . 2
3 4 .
. . 5
run;







そして処理コード

data DT2;
  set DT1;
  call stdize('missing=',0,'none',of _all_);
run;

結果







おぉ!できたできた!


meansプロシジャのfreqステートメントの話

ある6人の生徒がテストを受けた結果、90点が2人、80点が3人、50点が1人でした。
さて、6人のテストの点数の、平均と標準偏差を求めよ。

って問題をproc meansで解けって言われたら、流し込むデータはどのように作りますか?

まあ、何も考えずに

data Q0;
X=90;output;
X=90;output;
X=80;output;
X=80;output;
X=80;output;
X=50;output;
run;

proc means data=Q0 noprint;
 var X;
 output out=A0;
run;

でも当然OKなのですが、freqステートメントの利用を想定すると、流し込むデータはこのようにできます。

data Q1;
X=90;Y=2;output;
X=80;Y=3;output;
X=50;Y=1;output;
run;







Xに値、Yはその値のオブザベーション数を入れます。

つまりX=90;Y=2;はX=90が2オブザベーションあるよということを示します。

そしてプロシジャは

proc means data=Q1 noprint;
 var X;
 freq Y;
 output out=A1;
run;

とすれば、結果は








です。 

冒頭
先述のコードと同じになります。

freqで指定した変数の値分、オブザベーションを読み込んだとみなして、計算してくれるわけです。
オブザベーション数なので整数である必要があります。小数を指定しても切り捨てられます。
また1未満の数や0を入れた場合、その値は0オブザベーション、つまり無いものとして扱われます。


意外と知られていないステートメントの気がしますが、考えれば使いどころは結構あります。



proc formatのpictureステートメントの話③

さて前回までは、0とか9とかを使って数字を好きに加工してきましたが、今回は日付フォーマットの話です。

SASを始めた人が最初につまづきやすいのが、SASは数字と文字しか型がない?日付は?
日付も数字型?フォーマットで表現?は?みたいなとこです。実際わかりにくいですもんね。

data Q1;
 X=19875;
 format X yymmdds10.;
run;

と、Xに19875という数字を割り当ててますが、こいつに日付フォーマットをあてると




まあ、こうなって日付になるわけです。起点日から何日目かを数値にすることで日付を表現してるわけですね。
EXCELなんかでも日付いれてから、表示形式を標準とかにしたら変な数字になったりしますよね、
あれと同じです(EXCELとは起点日が違うので同じシリアル値にはなりませんが)


さて、そのように癖のある日付フォーマットの概念なので、0とか9とかじゃうまく表現できません。
のでdatatypeオプションを使って、日付専用の部品を使っていじります。


proc format;
 picture date_1_ (default = 30)
 low-high = '%Yねん%mがつ%dにち' (datatype=date);

 picture date_2_ (default = 30)
 low-high = '%Y年が始まって%j日目' (datatype=date);

 picture date_3_ (default = 30)
 low-high = '%y年の第%U週' (datatype=date);

run;

3種類作ってみました。

それぞれputした結果は

data A1;
 set Q1;
 X_1=put(X,date_1_. -L);
 X_2=put(X,date_2_. -L);
 X_3=put(X,date_3_. -L);
run;



です。

%Yとか%mとかが、決まったキーワードで部品なんです。
例えば%Yは年を4ケタで表示の意味で、%mは月を数字で表示・・・って感じです。
大文字と小文字で別の意味になるので要注意!

ちなみにキーワードはたくさんあって紹介しきれないのでSASのヘルプで確認してください

http://support.sas.com/documentation/cdl/en/proc/61895/HTML/default/viewer.htm#a002473467.htm






proc formatのpictureステートメントの話②

例えば、よくあるのが、変化率とかのデータをレポートにした際に

1.51
0.2
-5.5
0

みたいな風にして出すと

「いや~、それでもいいんだけど、できればこういう風に見栄え良くしてくんない?簡単でしょ?」
といわれて

+1.51%
+0.20%
-5.50%
0.00%

っていう見本を提示されたりします。

やれとおっしゃるならやりましょう。

さて、こういう時に if 値>0 の時に、cats関数で先頭に+、お尻に%つけて、間の数字の桁数を、なんて分岐処理かいて、文字列操作関数と戯れていたら結構、面倒です。

とはいえ、ばっちり使えるフォーマットが、デフォルトでSASにあるのかをいちいち調べるのも面倒です。

じゃあパパっと作っちゃえという話です。

data Q1;
X=-5.52;output;
X=3.1;output;
X=0;output;
X=0.11;output;
X=-60;output;
X=100;output;
run;










というデータセットがあった場合、まず以下のようにフォーマットを作ります

proc format;
 picture FX(default=9)
 low-<0='009.99%'(prefix='-')
 0='009.99%'
 0<-high='009.99%'(prefix='+');
run;


pictureで作るフォーマットパターンは先頭に文字定性値を普通にはおけないので
prefix=で指定します。その場合、フォーマットの長さを定義しておかないと先頭がきれますので
(default=9)と最大9文字表示せっていにしてます。
0で表現しているところは、ないなら出すなってことで、9で表示しているとこはなかったらゼロパティングして出せという意味です。


で、それを使って普通にput関数で

data A1;
 set Q1;
 Y=put(X,FX. -L);
run;











結果OKです。
-Lは左詰めにしてくれるオプションです。いちいちleft関数とか空白除去とかしなくていいんです。



さて、この手の話でよくよく例にだされるのが、数字列を電話番号っぽく表示する話です。

data Q2;
X=1234567891;
run;

proc format;
 picture TELL(default=14)
 low-high='99)-9999-9999'(prefix='(');
run;

data A2;
 set Q2;
 format X TELL.;
run;





ってな感じです。

こんな風に、文字列操作関数でかなり加工が必要そうな処理も、簡単にかけちゃうのが
pictureステートメントの凄いところです。

なんかイメージ的には正規表現書くのに似てますよね。



proc formatのpictureステートメントの話①

う~ん、proc formatのpictureステートメントについて、、はっきり言って僕もそんなに得意じゃないので、ずっと意識的に避けてきました。そのうち mastuさんやSASNamiさんが、記事だしそうだから、それ見て勉強しよ~と思っていました。

ただ、やっぱりそういう後ろ向きな姿勢は良くないですね。苦手克服のために紹介します。
そのため、もしかしたら、間違いがあるかもしれないので、指摘してください。

pictureステートメントは、かなり自由度の高い、出力形式を創造するステートメントです。
おなじproc formatでも、valueステートメントは、この値(範囲)ならこの文字を表示みたいに、決め打ちチックですが、pictureは違います。

いいから、とっとと例をだせ!という声が聞こえてきそうなので、そうします。

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









例えば、値が15までは入りうる数字なので、そのまま表示するが、15を超えたら異常値というシチュエーションの場合、次のようにフォーマットを定義できます。


proc format; 
 picture FT_1_
 1-15 ='99'
 other= '異常値じゃない?';
run;


で、これを実際当ててみると

data A1;
 set Q1;
 format X FT_1_.;
run;









はい、注目すべきは一ケタの数字はゼロパティング(0がついて二ケタ表示)されていることですね。
pictureステートメントで'99'とすると2ゼロパティングして2ケタ数字でデータの値を表示するということになります。'999'とすれば当然「001」のようになります。

いちいちvalue 1='01' 2='02' 3='03'・・・なんてことをしなくていいわけです。

ゼロパティングなんていらねーよ!という場合は次のようにします。

proc format; 
 picture FT_2_
 1-15 ='00'
 other= '異常値じゃない?';
run;

で当ててみると

data A2;
 set Q1;
 format X FT_2_.;
run;











はい、15以下の値は元のまま表示されます。


さて、このままだと15を超える値は全部「異常値じゃない?」となってしまい、フォーマットを解除しないとデータの値がわかりません。

ので、以下のようにフォーマットを作ります。

proc format; 
 picture FT_3_
 1-15 ='00'
 other= '00は異常値じゃない?';
run;

で、当ててみると














00は異常値じゃない?としたことによって、00の部分に実際の
データの値が入ります。凄くないですか?

でも、まだまだこんなもんじゃないです。

とりあえず今回はここまで。




文字型(数値型)変数が全て欠損だった場合のフラグ立て

あるデータセットに含まれる文字型変数が全て欠損値だった場合に0,1フラグの1を立てるにはどうしたらいいですかという質問をいただきました。

多分、やり方は多くあると思いますが、僕が一番に思いついたものを書きます。
ひねりのない方法です。
ついでに今回は数値型、文字型両方についてやってみます。

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

data Q1;
A=1;B='い';C=3;D='A';output;
A=.;B='ろ';C=.;D='B';output;
A=2;B='';C=4;D='';output;
A=.;B='';C=.;D='';output;
run;










数字変数が全てnullの場合はFL1に、文字変数が全てnullの場合はFL2に0,1を入れるとするならば

data A1;
set Q1;
 FL1=missing(sum(of _numeric_));
 FL2=missing(cats(of _character_));
run;










OK。

missing関数は指定した引数がnullなら1,違うなら0を返す関数です。
ただし、引数は1つしか指定できないので、少しだけ工夫します。

数字なら、全部足したものがnullなら、すなわち全数値変数がnullということで、
文字なら、空白を含めずに連結したものがnullであればすなわち全文字変数がnullということです。


ちなみにcmiss関数という型を問わずに、しかも引数に複数変数の指定が可能で、欠損値の数をカウントしてくれる便利関数があるので、それと配列を組み合わせれば同様のことが可能です。

SAS忘備録に関連する記事があるので、これを見ればすぐに応用できます。
「欠損値のチェックを簡単に行う。」
http://sas-boubi.blogspot.jp/search/label/%E9%96%A2%E6%95%B0%3A%20CMISS