2020年6月15日月曜日

sgplotでemfファイルを出力する話

何回やっても次やるときにすっかり忘れて自分のプログラムを探す羽目になるのでここをメモ代わりにします.私以外の人に需要は無いと思いますがそんなことは知らぬ

sas9.4のsgplotで出力をemfファイルで出したい時の設定です.いっつもpngとかで出しがちなので覚えてないんですよね…emfにもいいところはあるのですが.

以下プログラム

/*----- 適当なデータ -----*/
data hoge ;
    call streaminit(1234) ;
    do X = 1 to 36 by 1 ;
        Y = int(rand("uniform") * 100) ;
        output ;
    end ;
run ;

ods html close ;   *--htmlをcloseしないとwarninig出る;
ods listing gpath = "hogehoge" ;
ods graphics on / reset = all imagename="hist1" imagefmt=emf ;

proc sgplot data = hoge nowall noborder ;
    vbar X / response = Y ;

    xaxis
        values = (1 to 25 by 1)
        label="発現時期"
    ;

    yaxis
        label="発現率(%)"
        values = (0 to 100 by 20)
    ;

run ;

ods graphics off ;
ods listing close ;
ods html ;  *-- 元に戻す ;

2020年6月1日月曜日

複数の文字変数をつないだら文字切れするのを回避する話

ついに月一の更新すら怪しくなったブログはこちらです.
コロナで生活リズム変わったからね仕方ないね
 
先日私の身辺でちょっと話題になったことをテーマにします.
フリーテキストのコメントを一覧表かなんかに出力する時に,想定より長いコメントが来て途中までしか出力出来てなかったことがありました.一覧表と言えばページ数がかさむので,途中で切れてないか目視チェックは結構厳しいところがあります.安易に変数長を200byteまでにすると200を超える長さのコメントが来るとか,出力の際に()付けたりとか複数のコメントを結合させて1か所に出せとか言われるとぽろっと後ろが切れたりします.お薬の英語名と日本語名と記載名とかもそうですが…

さりとて切れてるのがあるとお叱りを受けてしまうので何とかしないといけません.出力用の変数が十分な長さを持っているかをlengthnで取得して長さを比較すれば確認できますね.
 
data _null_ ;
    /*----- 元のコメント -----*/
    A = "日本語のコメントだよ" ;
    B = "qwertyuiop";
 
    /*----- 出力用変数 -----*/
    length OUT $24 ;
    OUT = A ||  "/" ||  B ;
 
    /*----- 各変数の長さを求める -----*/
    V_A = lengthn(A) ;
    V_B = lengthn(B) ;
    V_OUT = lengthn(OUT) ;
 
    /*----- 元の変数と区切り文字の長さの合計が出力用変数より長い場合にobs番号をログに表示 -----*/
    if V_A + V_B + 1 > V_OUT then putlog "E" "RROR/" _n_ ;
run ;
 
ってタラタラ書きましたけどさあcat系の関数使ったら結合後の文字変数の長さが足りない時はwarning返るよねえ…文字の結合を||でやってんのかね…

241
242  data _null_ ;
243
244     /*----- 元のコメント -----*/
245     A = "日本語のコメントだよ" ;
246     B = "qwertyuiop";
247
248     length hoge hoge2 hoge3 $24 ;
249     hoge = cat(A, "/" , B) ;
250     hoge2 = cats(A, "/" , B) ;
251     hoge3 = catx("/" , A , B) ;
252
253  run ;
WARNING: CAT関数の呼び出しで、結果に割り当てられたバッファの長さが十分ではなかったため、すべての引数を連結して含めることができませんでした。
         正しい結果には31文字が含まれますが、実際の結果は、呼び出し環境によって、24文字に切り捨てられた、またはすべてブランクの可能性があります。
         どの引数以降が切り捨てられたかを次に示します。
NOTE: 第3引数(関数CAT('日本語のコメントだよ','/','qwertyuiop'):行 249 カラム 8)は無効です。
WARNING: CATS関数の呼び出しで、結果に割り当てられたバッファの長さが十分ではなかったため、すべての引数を連結して含めることができませんでした。
         正しい結果には31文字が含まれますが、実際の結果は、呼び出し環境によって、24文字に切り捨てられた、またはすべてブランクの可能性があります。
         どの引数以降が切り捨てられたかを次に示します。
NOTE: 第3引数(関数CATS('日本語のコメントだよ','/','qwertyuiop'):行 250 カラム 9)は無効です。
WARNING: CATX関数の呼び出しで、結果に割り当てられたバッファの長さが十分ではなかったため、すべての引数を連結して含めることができませんでした。
         正しい結果には31文字が含まれますが、実際の結果は、呼び出し環境によって、24文字に切り捨てられた、またはすべてブランクの可能性があります。
         どの引数以降が切り捨てられたかを次に示します。
NOTE: 第3引数(関数CATX('/','日本語のコメントだよ','qwertyuiop'):行 251 カラム 9)は無効です。
A=日本語のコメントだよ B=qwertyuiop hoge=  hoge2=  hoge3=  _ERROR_=1 _N_=1
NOTE: DATAステートメント処理(合計処理時間):
      処理時間           0.01 秒
      CPU時間            0.01 秒

2020年4月1日水曜日

includeで読み込んだプログラムファイルのパスとファイル名を取得する話

%includeで読み込んだプログラムのパスとファイル名を保持している自動マクロ変数を見つけたので紹介です.でもちょっと使い道が思いつかないので良い感じのがあればぜひ教えてください

変数の名前はSYSINCLUDEFILEDIR とSYSINCLUDEFILENAME です.長いので覚えられないですね.これをプログラムに仕込んでおいて,その仕込んだプログラムをincludeすると値が取得できます.平文で書いてもマクロ変数の中身は空なのでincludeしている時に値を取得している感じですかね.errorにならないのはとても良い

例えば以下の簡単なプログラムをtst.sasと名前を付けてincludeすると,ログにプログラム名とパスが表示されます.表示されるんですが...使い道思いつかないですね...ファイル名に関しては実行しているプログラム内で自分のファイル名を取れるのでそこは便利か...?

/*---------- includeしたプログラム(ファイル名はtst.sas) ----------*/
/*読み込み成功*/
%put &=SYSINCLUDEFILEDIR ;
%put &=SYSINCLUDEFILENAME ;

/*---------- include ----------*/
data _null_ ;
  rc = dlgcdir("piyopiyo") ;
run ;
%include "tst.sas" / source2;

/*---------- 実行ログ ----------*/
144  data _null_ ;
145
146  rc = dlgcdir("piyopiyo") ;
147
148  run ;
NOTE: 現在の作業ディレクトリは"piyopiyo"です。
NOTE: DATAステートメント処理(合計処理時間):
      処理時間           0.01 秒
      CPU時間            0.01 秒
171
172  %include "tst.sas" / source2 ;
NOTE: %INCLUDE (レベル1)ファイルtst.sasはファイルpiyopiyo\tst.sasです。
173 +/*読み込み成功*/
174 +%put &=SYSINCLUDEFILEDIR ;
SYSINCLUDEFILEDIR=piyopiyo        /*---------- includeしたプログラムのパス ----------*/
177 +%put &=SYSINCLUDEFILENAME ;
SYSINCLUDEFILENAME=tst.sas        /*---------- includeしたプログラムの名前 ----------*/

あと同じようなものにSYSINCLUDEFILEDEVICE とSYSINCLUDEFILEFILEREF もあります.
includeしたファイルが存在するデバイス名と,includeにfilenameステートメントで定義した文字列を指定した時に,そのfilerefを取得するものです.今回はfilenameしてないので空ですね

175 +%put &=SYSINCLUDEFILEDEVICE  ;
SYSINCLUDEFILEDEVICE=DISK       /*-------- ファイルはdiskにいる,それはそうだ. --------*/
176 +%put &=SYSINCLUDEFILEFILEREF ;
SYSINCLUDEFILEFILEREF=               /*---------- 今回は空 ----------*/

filename hogehoge "hoge.sas" ;としてから%include hogehogeを実行すると,SYSINCLUDEFILEFILEREFの値としてhogehogeが取れます.filefilerefってfileが続くのちょっとややこしい

2020年3月1日日曜日

棒グラフをsgplotで出す話

ある日質問されて答えられなかったのでここに供養します.ピンとこなかった自分が悲しい.

sgplotで棒グラフを書く時のサンプルです.vbarステートメントを使って出力するのですが,詳細は以下のプログラムの通りです.あんまり凝ったことしてなくほんとにただの紹介なのであまり目新しいことはないですが...軸ラベルが適当ですみません

変数Yの平均値を書いているのですが,オプションでmeanを指定すればOKなのと,標準偏差のヒゲをはやしているのですがこれもオプションで書けます.わざわざ出力用データセットに平均値と標準偏差を持たなくて良いのは楽ですね.確認の具合もあるので何でもかんでもオプションで出せばいいという訳ではありませんが…データに持たせた標準偏差を使ってヒゲをannotateで書かそうとする奴は敵です.

/*以下プログラム*/

data HOGE;
    call streaminit(12);
    do i = 1 to 10 ;
        Y = int(rand('uniform') *100) ;
        if 1 <= i <= 5  then GR = 1 ;
        if 6 <= i <= 10 then GR = 2 ;
        output;
    end;
run;

ods graphics / imagename="bar" imagefmt=png ;
ods listing gpath = "hogehoge" ;
ods html close ;

proc sgplot data = hoge noautolegend nowall noborder ;
    styleattrs datacolors = (gray white) ;
    vbar GR / group = GR groupdisplay = cluster
              response = Y stat = mean nozerobars   /*Yの平均値の棒を立てる*/
              barwidth = 0.5
              limits = upper limitstat = stddev          /*Yの標準偏差のひげを生やす*/
    ;
    xaxis type=DISCRETE ;                              /*不要*/
run ;

ods graphics / reset = all ;
ods html ;

2020年2月1日土曜日

現在の作業ディレクトリの変更する関数の話

dlgcdir関数を使えば現在の作業ディレクトリが変更できます.
正しく実行できると戻り値は0になります.

415  /*現在の作業ディレクトリをlogに表示*/
416  data _null_;
417    rc=dlgcdir();  /*()を空にして実行すると現在のディレクトリを表示する*/
418    put rc=;         /*正しく実行できているかの確認_別に不要ではあるが見るのには便利*/
419  run;

NOTE: 現在の作業ディレクトリは"piyopiyo"です。
rc=0    /*正しく実行できているので戻り値が0*/
NOTE: DATAステートメント処理(合計処理時間):
      処理時間           0.00 秒
      CPU時間            0.00 秒


420
421  /*現在の作業ディレクトリをhogeに変更*/
422  data _null_ ;
423  rc = dlgcdir("hoge") ;  /*()に指定したフォルダを現在のディレクトリとする*/
424  put rc= ;
425  run ;

NOTE: 現在の作業ディレクトリは"hoge"です。
rc=0
NOTE: DATAステートメント処理(合計処理時間):
      処理時間           0.00 秒
      CPU時間            0.00 秒


現在のディレクトリを変更することで,指定したフォルダ内のプログラムを%incで読み込む際にファイル名の指定だけで済むので記述が短くなって良いですね.もちろん現在のディレクトリを変更しているので,何が指定したフォルダに出力されるかを確認する必要はありますが.

例えば指定したフォルダの中のhoge.sasとhoge2.sasを実行するには%incにファイル名を書くだけでokになります.もちろん実行するプログラムの前にフォルダパスを記載すれば指定していないフォルダのプログラムも実行できますが,タラタラ前に書くのがめんどくさい時もありますし,フォルダパスをマクロ変数に格納して何かのはずみでマクロ変数の上書きが意図せず起きたりするのが結構ややこしい印象です.

%inc "hoge.sas" ;
%inc "hoge2.sas" ;

私は使ったことないですけど便利そうですよね,そのうち使うかもしれません.
 
後はファイルメニューの「プログラムを開く」で,最初に表示されるファイルの場所が現在のディレクトリになれば最高なんですが…今はsasの保存場所かなんかが最初に出るんですよね…最初と言わずとも現在のディレクトリにすぐアクセスできればokなんですが.

2020年1月8日水曜日

sas studioが9.4M6に更新されてて嬉しい話

最近アクセスできなかったsas studioですが,先日復旧したようですね.
復旧したのでとりあえずproc product_statusを実行すると,BASE SASが9.4M6に更新されているじゃないですか.これで思う存分円グラフが試せるというもんです,やったぜ.円グラフの実用性については知りませんねえ…

以下にせっかくなので実行したproc product_statusの結果を置いておきます.
毒にも薬にもなりませんが更新されててとてもうれしい.せっかくなので該当箇所を赤文字にしています.意味などないですが.

この手の更新情報ってどこかで公開されたりとかってないですかね…私が知らないだけですか?

 1          OPTIONS NONOTES NOSTIMER NOSOURCE NOSYNTAXCHECK;
 70        
 71         proc product_status ;
 72         run ;

 For Base SAS Software ...
    Custom version information: 9.4_M6
    Image version information: 9.04.01M6P110718
 For SAS/STAT ...
    Custom version information: 15.1
 For SAS/GRAPH ...
    Custom version information: 9.4_M6
 For SAS/ETS ...
    Custom version information: 15.1
 For SAS/OR ...
    Custom version information: 15.1
    Image version information: 9.04.01M6P050819
 For SAS/IML ...
    Custom version information: 15.1
 For SAS/QC ...
    Custom version information: 15.1
 For SAS/CONNECT ...
    Custom version information: 9.4_M6
 For SAS Enterprise Miner ...
    Custom version information: 15.1
 For SAS Time Series Workspace Macros ...
    Custom version information: 15.1
    Image version information: 9.04.01M5P110718
 For SAS/ACCESS to Postgres ...
    Custom version information: 9.4_M6
 For SAS Integration Technologies ...
    Custom version information: 9.4_M6
 For SAS/Secure 168-bit ...
    Custom version information: 9.41_M3
 For SAS Credit Scoring ...
    Custom version information: 15.1
 For SAS Text Miner ...
    Custom version information: 15.1
 For SAS High-Performance Forecasting ...
    Custom version information: 15.1
 For High Performance Suite ...
    Custom version information: 2.2_M7
 For SAS Forecast Server Mid-Tier ...
    Custom version information: 15.1
    Image version information: 9.04.01M5P110718
 For SAS/ACCESS Interface to PC Files ...
    Custom version information: 9.4_M6
 For SAS/ACCESS Interface to MySQL ...
    Custom version information: 9.4_M6

2019年12月1日日曜日

htmlのタグを指定して値をsasデータセットに格納する話

sasでwebスクレイピングってあんまり事例がないと思うので紹介する話です.
スクレイピングしようとするとwebページをhttpプロシジャ使ってhtmlのタグ付きのテキストに変換して,そこから必要な分だけをデータセットに取り込むのがいいかなと記事投稿時点では考えています.

問題になるのが「必要な分だけデータセットに取り込む」の部分かと.vba等のほかの言語ならhtmlのclass名なりid名なりを指定して格納されている値を取れますが,タグ付きのテキストに変換しているのでそんな器用なことができません.所詮テキストです.かと言って取得時にhtmlのタグを使えないとお話にならないので,テキストは@を使えば読み込み箇所を動かせるのを利用して値を取得します.

例えば以下のようなタグ付きのテキストを仮に取得したとします.
今回欲しいのはその中でも「c-rating__val c-rating__val--strong list-rst__rating-val」classの値である「3.49」ですね.


 <p class="c-rating c-rating--xl c-rating--val30 list-rst__rating-total cpy-total-score">
   <i class="c-rating__star list-rst__rating-star">
   </i>
      <span class="c-rating__val c-rating__val--strong list-rst__rating-val">3.49
      </span> </p>
 <p class="list-rst__rvw-count is-highlight">

「3.49」を取るために以下の段階を踏みます.コードの全体は一番下にまとめてあります.
まずdlm="</"とすることで,sasの変数に</ごとに区切りを変えて値を取得できます.こうすることで上のテキストの緑の部分の前後は以下のようにsasデータセットに格納できます.1行が1obsに相当すると思ってください.

</i> <span class="c-rating__val c-rating__val--strong list-rst__rating-val">3.49
</span>
</p> <p class="list-rst__rvw-count is-highlight">

1つの変数に</i> <span class="c-rating__val c-rating__val--strong list-rst__rating-val">3.49と格納できたので,class="c-rating__val c-rating__val--strong list-rst__rating-val">が出てきたら読み込み位置を@で1つ進めて3.49を取得できます.
今回はclassタグを指定していますが,同じことをすればidタグも同様に指定して値が取れるはずです.と言うかidタグがユニークになるのでとれるならidの方がいいですね.

/*--コードの全体像*/
data OUT_P;
 infile F_OUT dlm="</" dsd missover  lrecl=30000 firstobs=1;
 input @ 'class="c-rating__val c-rating__val--strong list-rst__rating-val">' V_VAL ;

 /*--要らないレコードを削除*/  
 if V_VAL = . then delete ;
run;




/*--取得した値をデータセットに上書き ページ数分繰り返せばok*/
proc append base= V_OUT data= OUT_P ;
run ;

proc httpだとurlを指定するのでどうしてもテキストに変換するのは1ページごとになってしまいます.読み込みを複数ページにまたがってやりたい時はproc httpでページを読み込み→テキストをsasデータに変換→欲しい値をproc appendでデータに追加の3つをloopして行えばokと思います.ただし一気にwebページに複数回アクセスすると攻撃扱いされますので,loop内に適宜sleep関数を挟むのが必須です.