2023年12月1日金曜日

各種クオーテーションマークを含む文字列を持つマクロ変数を展開する話

 仕様を私がきちんと理解しているわけではないですが、クオーテーションマークを含んだ文字列を持つマクロ変数を展開するにはsymget関数を使うと便利ですよ、との紹介です。確かに展開は出来ているのですがどうしてこうなっているのかはわかっていないのであまり深く掘り下げないでください。

事の起こりは会社でよくできる後輩君から掲題のことを聞かれました。私のところでは紆余曲折あってマクロ変数を無事展開できたのですが、どうも後輩君のところではうまく展開できずにいました。手元ではできるのになんでだろうな…と思っていると、実際の展開箇所でsymget関数を使って展開するのと、マクロ変数を""で挟んで展開しているという違いに行きつきました。私はsymget関数を使っていましたが、正直この関数はcall symputxで作ったマクロ変数を同じdata stepで展開できる関数くらいにしか思っておらず、その他のマクロ変数の展開ではマクロ変数を""で挟むのもsymget関数を使うのも同じ挙動を示すと思っていました。ところが違うんですねえ…どうやら。

仮に以下のようなシングル、ダブルクオーテーションを両方含んだ文字列を持つマクロ変数hogeを準備して、データステップで展開するとします。

%let hoge = I'm a "sas" user' ;

このマクロ変数をダブルクオーテーションで挟んで展開すると、案の定errorが出ます。今の場合ではerrorが出るのでデータステップが完了していません。

  data aa ;

      hage = "&hoge" ;

NOTE: マクロ変数"HOGE"によって生成されたライン

1      "I'm a "sas" user ;'

       -----------

       49      388

               202

NOTE 49-169: 引用符で囲まれた文字列の後の識別子の意味は、将来のSASリリースで変わる可能性があります。

             引用符で囲まれた文字列と識別子の間にスペースを挿入することをお勧めします。

ERROR 388-185: 算術演算子を指定してください。

ERROR 202-322: オプションまたはパラメータを認識できません。無視します。

2238  run ;

NOTE: 以下の箇所で文字値を数値に変換しました。(行:カラム)

      1:2    1:13

NOTE: エラーが発生したため、このステップの処理を中止しました。

WARNING: データセットWORK.AAは未完成です。このステップは、0オブザベーション、2変数で停止しました。

WARNING: このステップを中止したため、データセットWORK.AAを置き換えていません。

NOTE: DATA ステートメント処理(合計処理時間):

      処理時間           0.03 秒

      CPU時間            0.03 秒


ところがマクロ変数hogeの展開をsymget関数で行うとerrorが出ず実行されます。

  data aa ;

      piyo = symget("hoge") ;

  /*    hage = "&hoge" ;*/

  run ;

NOTE: データセットWORK.AAは1オブザベーション、1変数です。

NOTE: DATA ステートメント処理(合計処理時間):

      処理時間           0.03 秒

      CPU時間            0.01 秒


出来上がったデータセットAAを見ると、不思議と格納されています。これなんでなんでしょうね。私は意識せずsymgetをなんでか使っていたので特に意識することなくここまで来てしまったのですが…






2023年10月24日火曜日

複合パネルのグラフの作り方についての紹介

 先日のsasユーザー総会で散布図行列の作り方なる素晴らしい発表があってからというもの、あちこちで散布図行列の作り方や一ページに複数枚のグラフを貼った、複合パネルのグラフの作り方が紹介されている。これだけ色々なところで取り上げられているなら便乗しないともったいない。

例えばかの有名なブログではRWIで複合パネルのグラフを作る方法について紹介されている
https://sas-tumesas.blogspot.com/2023/10/blog-post.html

普通に考えればGTLでやればいいというのは全くその通りなのだが、sgplotで書いたグラフ画像をproc reportを使って一ページに複数枚配置する方法について紹介する。需要?多分無いよ

グラフについては各々sgplotで出力してもらうとして、今回は以下のようなpngを3*3で配置してみようと思う。どこにでもあるpngですね。PharmaSUG 2023 SDEは大阪で開催予定です。ぜひご参加下さい。

この画像を3*3にproc reportで並べたものをrtf出力すると以下のようなイメージに。もうちょっと縦に伸ばすとぴったりはいるが、どうせ脚注やら何かしらを入れるので余白のままにしている。そこまでの調整がめんどくさかったわけではないんです本当です信じてください。


以下が出力プログラム。データセットに出力したいpng名を指定した変数を作って、proc reportでそれぞれを呼び出して並べています。あくまでpingを並べているだけなので相関係数などの文字列だけ出したいときは、それだけの画像を良い感じに準備すればok。散布図行列ではないので複数グラフ共通のラベルなどは出せないのであしからず。

%let path = piyopiyo ;

data V_GRAPH ;

    L1 = "&path.\PharmaSUG.png" ; R1 = "&path.\PharmaSUG.png" ; output ;

    L1 = "&path.\PharmaSUG.png" ; R1 = "&path.\PharmaSUG.png" ; output ;

    L1 = "&path.\PharmaSUG.png" ; R1 = "&path.\PharmaSUG.png" ; output ;   

run ;


title1 ;

title2 ;


options orientation=portrait papersize=A4;

ods rtf file = "&path.\multi.rtf" nogtitle nogfootnote ;

      proc report data=V_GRAPH nowindows style(report)=[rules = none frame = void] ;

         column L1 R1 ;

        /*- style --*/

        define L1   / style={cellwidth=9.5 cm cellheight = 6.5 cm } "" ;

        define R1   / style={cellwidth=9.5 cm cellheight = 6.5 cm} "" ;

        compute L1 ;

            call define(_col_ , "style" , "style=[preimage='"||L1||"' just = center]") ;

            L1 = "" ;

        endcomp ;

        compute R1 ;

            call define(_col_ , "style" , "style=[preimage='"||R1||"' just = center]");

            R1 = "" ;

        endcomp ;

        footnote1 j=l "test" ;       

      run;

ods rtf close;





2023年8月24日木曜日

正方形のplotを描画する話

 たまに散布図を出力するときにplotを正方形にしてください、と依頼を受けることがあります。気持ちはわかるがどうすんねんという話ですが、ちょうどsgplotステートメントにaspectオプションがあり、これに 1を指定するとplotが正方形になります。aspectオプションは0-1の幅で指定できます。

詳細はsasのヘルプページのASPECT=positive-number欄に記載があります。

specifies the aspect ratio of the plot’s wall area. The ratio is expressed as a positive decimal fraction representing wall-height divided by wall-width. For example, 0.75 is a 3/4 aspect ratio, and 1.0 is a square aspect ratio.

Small numbers, such as 0.01, produce a short, wide rectangular area. Larger numbers yield a taller, narrower rectangular area.

https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/grstatproc/p073bl97jzadkmn15lhq58yiy2uh.htm#p00zvhaib2skg9n10khojgxdigzh



以下プログラム

data FDAT ;

    call streaminit(1234) ;

    do i = 1 to 10 ;

        AVAL = rand('uniform') *100 ;

        output ;

    end ;  

run ;

%let _out = %sysfunc(getoption(work)) ;

ods listing gpath = "&_out" ;

ods graphics on / reset= all noborder imagename = "test" imagefmt= png ;

proc sgplot data = FDAT nowall noautolegend aspect = 1 ;

    scatter x = I y = AVAL ;

run ;

2023年6月6日火曜日

散布図のマークをハイフンにする話

 sgplotでは散布図のシンボルをcircleなりsquareなりの指定で色々な形を出力することができます。具体的にはscatterステートメントのmarkerattrsです。例えばmarkerattrs = (symbol = circle)とすると散布図のシンボルが白丸になります。ここでmarkerattrs = (symbol = circle color = black)としてやると黒い白抜きの丸ですね。その他にどんな指定ができるのかは以下のsashelpに記載があります。
https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/grstatproc/p0i3rles1y5mvsn1hrq3i2271rmi.htm

それで終われば話が早いのですが、このsashelpに記載のないシンボル例えば「-を出したいときの紹介です。
markerchar
オプションを使うと任意の文字列をシンボルに出来るのですが、なんとこのオプションは「specifies a variable whose values replace the marker symbols in the plot. 」とあるので、MARKERCHAR=変数名と指定しないといけません。要はシンボルの文字列を格納した変数を別途sgplotに読み込ませるデータに作らないといけません。以下のフォーラムでもデータセットにシンボル用の変数を作成する旨が解決策として示されています。ちょうど「-」をMEANSYMBOLという変数に格納しているようです。
https://communities.sas.com/t5/Graphics-Programming/PROC-SGPLOT-SCATTER-PLOT-and-quot-quot-as-SYMBOL/td-p/133064

もちろんこれでも問題ないのですが、私としてはシンボル用の文字列をデータセットに持たせるのはあまり好みではありません。そこで別の手段としてsymbolcharステートメントを使います。scatter ... / markerattrs = (symbol = sym1)とします。sym1の文字列はなんでもいいです。次にsymbolcharステートメントを使ってsymbolchar name = sym1 char = "002D"x ; とします。symbolcharのnameにはmarkerattrsで指定したものと同じ文字列を指定してください。今回で言うとsym1です。char = "002D"xの部分でユニコードでの「-」を指定しています。symbolcharのcharではユニコードが通るのがミソですね。今回は-ですが違うユニコードを指定すれば当然違う文字列をシンボルに出来ます。あまり色んな文字をシンボルにしたくはないのですがたまにはやむを得ないということで…

--指定の例--

scatter x = hoge y = piyo
  / markerattrs = (symbol = sym1)

symbolchar name = sym1 char = "002D"x ;

2023年4月1日土曜日

sasからExcelを読み込む際に、全ての変数を文字列として扱う話

親の顔くらい業務中に見た話ですが、excelからsasにデータを読み込む際に全てのデータを文字として扱いたいときの話です。

要はdbms="xlsx"を指定してExcelを読み込んだ後に、数値変数をデータステップで文字変数に変換するしかない(多分)という内容です。それで終わると寂しいのでsasexcelを読み込む方法を以下に紹介します。

まず読み込むexcelは下の物です。変数b11行目にxxxという文字があり、変数cには1行目に文字があります。それ以外は数字です。

sasexcelエンジン(仮称)を使って読み込む場合

libname でもproc import でも良いのですが、"excel"を指定して読み込む方法がまずあります。libname hoge excel "piyo.xlsx" ; なり、proc import datafile = "piyo.xlsx" out = V_OUT dbms = excel ; です。

どちらも普通にexcelを読み込んでくれますが、同じ変数に数値と文字が混在している場合がややこしくなります。mixedオプションをyesにすると上から8行目までの値で変数属性が決まるので、Excel上で8行目までは数字、9行目に文字が入っている変数はsasでは数字変数となり、9行目の文字列は欠測扱いになります。

上記excelを読み込むと以下のようになります。変数bの文字が欠測になり、変数cは文字変数扱いです。


文字値の意図しない欠測を回避するためには「変数属性の決定をデータの8行目までではなく、データの最後まで読み込んでから行う」ことが必要になりますが、この変更はsasでは指定できずレジストリを変更しないといけません。sasdocumentに以下の記載があります。「The options are located in a key of the Microsoft Windows registry.」じゃねーんだよ

 https://go.documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/acpcref/n0msy4hy1so0ren1acm90iijxn8j.htm

Interaction

The 'TypeGuessRows' entry in your registry settings can affect the behavior of the MIXED= option.
The options are located in a key of the Microsoft Windows registry.

なおmixednoにすると全部数字変数として扱われるので、文字と数値の混在する変数の文字値は全部欠測になります。この場合だと変数b11行目と変数c1行目が欠測です。

意図しない欠測が出てくるのは困りものですがいったん見なかったことにして、表題の「excel上の変数を全て文字値にして取り込む」にはproc importではdbdsoptsを指定すればokです。例えば以下のように指定すると、変数が3つとも200文字の文字変数として扱われます。ただし上述した「9行目以降の欠測になった文字列」は欠測のまま文字化されています。惜しい。

proc import datafile = "hoge.xlsx" out = hogeim dbms = excel replace  ;
    sheet = "sheet1" ;
    getnames = yes ;
    mixed = yes ;
    dbdsopts = "DBTYPE=(a = 'CHAR(200)' b = 'CHAR(200)' c = 'CHAR(200)')" ;
    scantext = yes ;
run ;

libnameではdbsastypeオプションをworkに取り込む際のデータステップに記載すると同じように全て文字変数として取り込めますが、同じように欠測になった文字値はそのまま欠測です。

libname hoge excel "hoge\Book1.xlsx" mixed = yes ;
data hogex ;
    set hoge."Sheet1$"n (dbsastype=(a = char200 b = char200 c = char200) ) ;
run ;

もう一つexcelを読み込む方法として、xlsxエンジンを使う方法(仮称)があります。個人的にはこっちを推しています。

先と同じようにlibname hoge xlsx "piyo.xlsx" なり proc import datafile = "piyo.xlsx" out = hogeimsx dbms = xlsx ;なりを指定してExcelを読み込みます。

どちらの場合でもこうすると以下の画像の通り8列目以降の文字も格納され、該当の変数も文字値として扱われています。ただし全て数字格納された変数は数値変数になります。また数値変数も含めて、全ての変数を文字値として扱うためのdbdsoptsdbsastypeオプションは使えません。このオプションはdbms=excel専用です。

ちょっと読み込むexcelを変えています。C変数11行目の長めの文字列も文字切れせずに格納されていますね。

dbdsoptsオプションが使えないので全ての変数を文字値とするには、読み込んだ後にデータステップで加工するしかないと思います。私はlibname を使用した場合はworkに格納する際のデータステップで数字変数を文字に変換しています。文字変数に変換したい変数が少ないと良いのですが、数が増えると変換漏れが出たりするので注意が必要です。importでもオプションが使えないので同じようにデータステップでの加工が必要です。

 libname hoge xlsx "hoge.xlsx" ;

data hoge2 ;
    set hoge.Sheet1 ;
    length V_A $100 ;

    V_A = cats(A) ;
    drop A ;
    rename V_A = A ;
run ;

この方法には大きな問題があって、libnameなりimportなりでexcelを読み込んだ後にデータステップで加工しているので、何かしらの問題、それこそ文字変数が意図せず欠測として扱われている等、が起きている場合に修正ができません。excel指定でexcelを読み込むよりは不具合なくデータは読み込めていますがやはり確認は必要です。

今後xlsxエンジン(仮称)libnameなりimportなりを使ってexcelを読み込む時点でのオプションで、excel内の全ての変数を文字値として指定できる方法が追加されることを祈っています。ddeはあまり使いたくないので

2022年10月1日土曜日

ズンドコを判定する関数を作った

 先日アップした記事でズンドコキヨシをデータステップで作成した。その時は関数化まではしていなかったが、やはり元ネタに則り関数化までせねばならないと使命感に駆られてしまった。このクソ忙しい時に何をやっているのだろうか。実は私は暇だったのかもしれない。本当か?

お題が「関数化」なのであえてマクロではなくfcmpで関数を定義している。fcmp自体触ったことが無かったので随分と苦戦した。特にfcmpでのarrayはデータステップでのarrayとそのまま同じではないことを初めて知った。まだ二つの違いを理解できていないので難儀なもんである。sasも奥が深い。その違いによるものかは分からないが、乱数で0をズン、1をドコに割り当てたかったが既に数字として定義されているのでarrayの中で文字変数を作れなかったため、やむなく0/1のまま処理をしている。00001がズンズンズンズンドコにあたるので、その場合にキヨシが発生したとみなしている。

ややうまくいかないところがあったが、何はともあれfcmpを使った関数化には成功したので思う存分ズンドコキヨシを判定することができる。多分動くからリリースしようぜとは昔の偉い人もよく言ったものである。関数の出力はズンドコした回数だけにしているのでlogに過度に出力されることもなく、プログラムにこっそり混ぜていてもログが大惨事になることは無い。さあletsズンドコ。この記事が弊社にバレないことを祈る。

/*warning回避用*/

options cmplib = work.piyo ;


proc fcmp outlib = work.hoge.func ;

    function kiyoshi() $40 ;


        __n = 0 ;

        do until (cats(of temp1 - temp5) = "00001") ;


            __n + 1 ;

            /*任意の範囲の乱数を発生させるにはint((上限-下限+1)*乱数+下限)*/

            /*今回は0か1の2値が欲しいので上限=1,下限=0*/

            /*01の乱数をズンとドコに割り当てたかったがうまくいかないので01のままにしている*/

            array ch[5] temp1 - temp5 ;

            do i = 1 to dim(ch) ;

                ch[i] = int( 2*rand("uniform") ) ;

            end ;   


        end ;


        length V_RET $40 ;

        V_RET = cats(__n , "回ズンドコしました") ;


        return(V_RET) ;


    endsub ;

run ;



options cmplib = work.hoge ;

data _null_ ;

    HOGE = kiyoshi() ;

    putlog HOGE ;

run ;


/*実行例*/


880  options cmplib = work.hoge ;

881  data _null_ ;

882      HOGE = kiyoshi() ;

883      putlog HOGE ;

884  run ;


19回ズンドコしました

NOTE: DATAステートメント処理(合計処理時間):

      処理時間           0.04 秒

      CPU時間            0.04 秒


2022年9月1日木曜日

sasで幾何平均を出すときの注意事項

幾何平均は掛け算して根を計算するので値に0や負の数が含まれていると計算できないです.sasのプロシジャで求めるときに値に0が含まれていると,プロシジャによって結果が異なることを紹介します.

幾何平均を出すプロシジャのunivariate,surveymeans,ttestでそれぞれ幾何平均を求めます.使うのは0を含んだ3obsのデータです.私としては0を含むと幾何平均は0になるので,そもそも計算しないことが正しいと思いますが,sasだと0で出すか計算しないか0を除いて計算するかの3つなので,どのように数字を扱いたいかを事前に確認するのがよいと思います.
確認してないですが負の値が含まれていると欠損で値が返ってくるのですかね

/*使うデータ*/
data hoge ;
    a = 5 ; output ;    a = 0 ; output ;  a = 4 ; output ;
run;

/*  univariは0を含む幾何平均を0で出力する  */
proc univariate data=hoge noprint;
 var a;
 output out=out1 geomean=geomean;
run;

NOTE: データセットWORK.OUT1は1オブザベーション、1変数です。
NOTE: PROCEDURE UNIVARIATE処理(合計処理時間):
処理時間 0.00 秒
CPU時間 0.01 秒

data _null_ ;
    set out1 ;
    putlog geomean= ;
run ;

geomean=0
NOTE: データセットWORK.OUT1から1オブザベーションを読み込みました。
NOTE: DATAステートメント処理(合計処理時間):
処理時間 0.00 秒
CPU時間 0.00 秒

/*  surveymeansは0を含むと幾何平均を計算しない  */
proc surveymeans data=hoge geomean ;
  var a ;
  ods output GeometricMeans=out2;
run;

ERROR: A variable must be positive when geometric mean is requested.
NOTE: エラーが発生したため、このステップの処理を中止しました。
NOTE: PROCEDURE SURVEYMEANS処理(合計処理時間):
処理時間 0.00 秒
CPU時間 0.00 秒
WARNING: 出力'GeometricMeans'は作成されていません。出力オブジェクト名、ラベル、
パスが正しく記述されているかを確認してください。また、
要求した出力オブジェクトを作成するために、適切なプロシジャオプションが使われ
ているかも確認してください。たとえば、
NOPRINTオプションが使われていないことを確認してください。

/* ttestは0を含む幾何平均は,0を除いた値で計算してwarningを出す */
proc ttest data=hoge dist=lognormal;
  var a;
  ods output Statistics=out3;
run;

WARNING: 1 observations with invalid response values have been deleted. The response was less than or equal to zero for the
lognormal distribution.
NOTE: データセットWORK.OUT3は1オブザベーション、12変数です。
NOTE: PROCEDURE TTEST処理(合計処理時間):
処理時間 0.31 秒
CPU時間 0.17 秒
data _null_ ;
    set out3 ;
    putlog geommean= ;
run ;

GeomMean=4.4721
NOTE: データセットWORK.OUT3から1オブザベーションを読み込みました。
NOTE: DATAステートメント処理(合計処理時間):
処理時間 0.00 秒
CPU時間 0.00 秒

長々と書きましたが要はログを読んでくれ