データをざっと見る話

プログラムをつくる際、ステップのたびにデータセットの中を確認するのはさすがに大変だけど、ちょいちょい覗いてチェックしながら書いた方が、結果的にミスなく速く終わるっていうのはよくある話です。

で、そういう話からデータをざっと見るのに、どんなことされてますか?って聞かれました。

はい、残念ながら特にこれといった凄技は使ってないです。

例えば僕の場合、補助的なお手軽チェック法としては
上位10カテゴリをmaxlevels=10ですくって全変数見る以下のようなコード

proc freq data=sashelp.cars order=freq;
 tables _all_ / maxlevels=10;
run;



























gsubmit "proc freq data=%8b.'%32b'N  order=freq;tables _all_ / missing maxlevels=10;run;"

と改造して、SASの右クリックカスタムメニューに入れているので、気になるデータセットを右クリックすればすぐにfreqの結果を見れるようにしてます。
(やり方は以下の記事参照)

gsubmitでSASメニューをカスタマイズしたおす話
http://sas-tumesas.blogspot.jp/2015/12/gsubmitsas.html

_all_で指定してるから、いちいち変数指定する必要や漏れもなく手軽。ID番号や登録日もどざっとでちゃうけど、10個なんで、スクロールですぐすっ飛ばせる。

最後はもちろんmaxlevelつけずに見た方がいいけど、途中確認は簡易で高速な方が、小まめにやる気が起きやすいかも。

あとはクロス表でチェックしたい際に
listオプションつけた方が目視チェックしやすいっていう話は、よく見ますよね
9.4からはcrosslistオプションが追加されました。listの場合は合計行の情報が落ちるわけですがcrosslistだと合計に関してもlist化されます。全体集計に意味がある場合はこっちの方がいいかなぁ

何もつけない場合も含めて3種の出力を比べてみる

title"通常";
proc freq data=sashelp.class;
tables sex*age;
run;

title"list";
proc freq data=sashelp.class;
tables sex*age/list;
run;

title"crosslist";
proc freq data=sashelp.class;
tables sex*age/crosslist;
run;






























あと個人的に好きなのが、クロスでチェックしたい際に、リストと合わせて
ods graphics on;
proc freq data=sashelp.cars order=freq;
 tables ORIGIN*TYPE/ plot=mosaic;
run;
ods graphics off;



とfreqのついでにモザイクプロットがplot=mosaicとするだけで出て来るのでつけてます。見た目が素敵で和むっていうのが主な使用理由ですが、リストでは気づきにくいけどプロットすると意外に気づくこともあるものです。

数値変数についてはやっぱり

ods graphics on;
ods select BasicMeasures plots;
proc univariate data=sashelp.class plot;
var _numeric_; 
run;
ods graphics off;






























って感じかなぁ。
要約統計量だけ見て、すぐに状況把握できるといいんでしょうけど、僕はグラフ欲しい派です。
ヒストグラムと箱髭は、考えた人偉いなぁって思います。
やっぱり、ID番号とか、見なくていいものもでちゃうし、グラフ書くと時間かかっちゃうのがネックですけどね

ああ、あと、実際見たことあるのは、変数をビン化するhpbinプロシジャ(9.4)をデータチェックに使っている人がいましたね。ヒストグラム描けばって思うけど、データ量が凄くてできるだけ高速に省エネでっていう状況ならありですかもね。デフォルトだと
データ範囲(最大値-最小値)をビン数で割った数の区切りでビン化されます

proc hpbin data=sashelp.class numbin=10;
 input _numeric_;
run;



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;


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