SAS

SASで複数のグラフを配置する(3) GTLとODSの併用

前回に引き続き複数のグラフを格子状に配置する方法を検証をしていきます

最後に検証するのはGTLです。

前回記事

SASで複数のグラフを配置する(1) ods layout前回は評価項目ごとにグラフを出力しましたが、複数のグラフを格子状(グリッド)に配置するケースは多いと思います。 グリッド上に配置する方...
SASで複数のグラフを配置する(2) proc report前回に引き続き複数のグラフを格子状に配置する方法を検証をしていきます 今回はproc reportを検証します。業務ではそこそこ使...

eval関数でサブグループ毎にグラフを作成する

GTLではwhereステートメントが使えませんが、eval関数を使用すればwhereステートメントと同等の結果を出力できます。

例えばparamn=1のデータを折れ線グラフで図示するには以下のように書きます。

seriesplot x=eval(ifn(paramn=1, ATPT, .)), y=CHG / < options >;

eval関数は任意のSAS関数を実行することができます。ifn関数でparamn=1以外のX変数の値を欠損値に置き換えています。
欠損値になったデータは作図されませんので、paramn=1のデータのみ作図することができます。

サブグループ毎にグラフを作成してグリッド上に配置するにはdatalatticeレイアウトやdatapanelレイアウトを使うと思われるかもしれませんが、
これらは制約が多すぎて正直あまり使えない印象です。コード量が多くなってしまいますが、llatticeレイアウトを使用してグリッドごとに作図を定義したほうが汎用性は高いと思います。

データセットは普通縦積みの形式で作成されますから、eval関数を使用すればデータ構造を変えることなく作図することが可能です。

ここではparamn=1~4のサブグループ毎に評価項目の変化量とリスク集合テーブルを作成し、2*2のグリッド上に配置します。

*軸ラベル用フォーマット; 

proc format; picture tick 
                     0="9" 
                     12="99" 
                     24="99" 
                     36="99" 
                     52="99" 
                     64="99" 
                     76="99" 
                     other=" "; 
run; 

ods escapechar="^"; 
proc template; 
define statgraph figure1a_gtl; 
begingraph; 

*attribute map設定; 
discreteattrmap name="map"; 
   value "Drug XXX" / lineattrs=(color=green pattern=solid) 
                      markerattrs=(color=green symbol=trianglefilled); 
   value "Placebo" / lineattrs=(color=grey pattern=solid) 
                     markerattrs=(color=grey symbol=circlefilled); 
enddiscreteattrmap; 

discreteattrvar var=TRT1A attrmap="map" attrvar=_group; 
entryfootnote halign=right "upper: response curve, lower: No. of paticipants"; 

layout lattice / rows=2 columns=3 
                 columnweights=(0.04 0.48 0.48) 
                 columngutter=5 rowgutter=5; 
*1行目ラベル; 
   layout overlay; 
      entry "Worsening" / 
        rotate=90 pad=(bottom=10%); 
      drawarrow x1=70 y1=90 x2=70 y2=30 /
        lineattrs=(thickness=1 color=black) 
        arrowheadshape=filled 
        arrowheadscale=0.5; 
   endlayout; 
*paramn=1; 
   cell; 
      cellheader; 
         entry "iADRS" / textattrs=(size=10); 
      endcellheader; 
      layout lattice/ rows=2 rowweights=(0.9 0.1) 
                      columndatarange=union; 
         layout overlay /
            xaxisopts=(label="Week" 
                       linearopts=(viewmax=76 viewmin=0 
                       tickvaluesequence=(start=0 end=76 increment=4) tickvalueformat=tick.) 
                       ) 
            yaxisopts=(label="Least squares mean / change from baseline"
                       labelsplitchar="/" 
                       labelfitpolicy=splitalways 
                       linearopts=( viewmin=-12 viewmax=2 
                           tickvaluesequence=(start=-12 end=2 increment=2)
            )); 
            seriesplot x=eval(ifn(paramn=1,ATPT,.)) y=CHG / 
               group=_group 
               display=all 
               name="series" 
               yerrorlower=lower 
               yerrorupper=upper; 
            referenceline y=0 / 
               lineattrs=(color=black pattern=shortdash thickness=2); 
 
            discretelegend "series" / 
                across=1 
                location=inside 
                autoalign=(bottomleft topright); 
        endlayout; 

        layout overlay /walldisplay=none 
            yaxisopts=(display=none) 
            xaxisopts=(display=none 
                linearopts=(viewmax=76 viewmin=0 
                    tickvaluesequence=(start=0 end=76 increment=4) 
                    tickvalueformat=tick.)
            ); 

           axistable x=eval(ifn(paramn=1,ATPT,.)) value=atrisk / 
              class=_group; 
        endlayout; 
     endlayout;
 endcell; 

*paramn=2; 
   cell; 
      cellheader; 
         entry "CDR-SB" / textattrs=(size=10); 
      endcellheader; 

      layout lattice/ rows=2 rowweights=(0.9 0.1) 
                      columndatarange=union; 

         layout overlay /
             xaxisopts=(label="Week" 
                 linearopts=(viewmax=76 viewmin=0 
                    tickvaluesequence=(start=0 end=76 increment=4) tickvalueformat=tick.) 
                 ) 
             yaxisopts=(display=(line ticks tickvalues ) 
                 linearopts=( viewmin=-2.5 viewmax=0 
                     tickvaluesequence=(start=-2.5 end=0 increment=0.5))
             ); 

             seriesplot x=eval(ifn(paramn=2,ATPT,.)) y=CHG / 
                 group=_group 
                 display=all 
                 name="series" 
                 yerrorlower=lower 
                 yerrorupper=upper; 

             referenceline y=0 / 
                 lineattrs=(color=black pattern=shortdash thickness=2); 

             discretelegend "series" / 
                across=1 
                location=inside 
                autoalign=(bottomleft topright); 
          endlayout; 

          layout overlay /walldisplay=none 
             yaxisopts=(display=none) 
             xaxisopts=(display=none linearopts=(viewmax=76 viewmin=0 
                tickvaluesequence=(start=0 end=76 increment=4) tickvalueformat=tick.)); 

                axistable x=eval(ifn(paramn=2,ATPT,.)) value=atrisk / 
                   class=_group; 
          endlayout; 
       endlayout; 
    endcell; 

*2行目タイトル; 
   layout overlay; 
       entry "Worsening" / rotate=90 pad=(bottom=10%); 
       drawarrow x1=70 y1=90 x2=70 y2=30 /
          lineattrs=(thickness=1 color=black) 
          arrowheadshape=filled 
          arrowheadscale=0.5; 
    endlayout; 
*paramn=3; 
   cell; 
      cellheader; 
         entry "ADAS-Cog" {sub "13"} / textattrs=(size=10); 
      endcellheader; 
      layout lattice/ rows=2 rowweights=(0.9 0.1) 
                      columndatarange=union; 
         layout overlay /xaxisopts=(label="Week" 
                            linearopts=(viewmax=76 viewmin=0 
                            tickvaluesequence=(start=0 end=76 increment=4) tickvalueformat=tick.) 
                        ) 
                        yaxisopts=(label="Least squares mean / change from baseline" 
                            labelsplitchar="/" 
                            labelfitpolicy=splitalways 
                            linearopts=( viewmin=-6 viewmax=1 
                            tickvaluesequence=(start=-6 end=1 increment=1))
                        ); 
              seriesplot x=eval(ifn(paramn=3,ATPT,.)) y=CHG / 
                  group=_group 
                  display=all 
                  name="series" 
                  yerrorlower=lower 
                  yerrorupper=upper; 

              referenceline y=0 / lineattrs=(color=black pattern=shortdash thickness=2); 

              discretelegend "series" / across=1 location=inside autoalign=(bottomleft topright); 
          endlayout; 
          layout overlay /walldisplay=none 
             yaxisopts=(display=none) 
             xaxisopts=(display=none 
                linearopts=(viewmax=76 viewmin=0 
                   tickvaluesequence=(start=0 end=76 increment=4) tickvalueformat=tick.)); 

             axistable x=eval(ifn(paramn=3,ATPT,.)) value=atrisk / 
                class=_group; 
           endlayout; 
       endlayout; 
   endcell; 

*paramn=4; 
   cell; 
      cellheader; 
         entry "ADCS-iADL" / textattrs=(size=10); 
      endcellheader; 
      layout lattice/ rows=2 rowweights=(0.9 0.1) 
         columndatarange=union; 

      layout overlay /
         xaxisopts=(label="Week" linearopts=(viewmax=76 viewmin=0 
            tickvaluesequence=(start=0 end=76 increment=4) 
            tickvalueformat=tick.) ) 
         yaxisopts=(display=(line ticks tickvalues ) 
            linearopts=( viewmin=-6 viewmax=0 
            tickvaluesequence=(start=-6 end=0 increment=2)
         )); 

         seriesplot x=eval(ifn(paramn=4,ATPT,.)) y=CHG / 
            group=_group 
            display=all 
            name="series" 
            yerrorlower=lower 
            yerrorupper=upper; 
         referenceline y=0 / 
            lineattrs=(color=black pattern=shortdash thickness=2); 

         discretelegend "series" / 
            across=1 
            location=inside 
            autoalign=(bottomleft topright); 
       endlayout; 
   layout overlay /walldisplay=none 
       yaxisopts=(display=none) 
       xaxisopts=(display=none linearopts=(viewmax=76 viewmin=0 
          tickvaluesequence=(start=0 end=76 increment=4) 
          tickvalueformat=tick.)); 

       axistable x=eval(ifn(paramn=4,ATPT,.)) value=atrisk / 
          class=_group; 
       endlayout; 
     endlayout; 
  endcell; 
endlayout; 
endgraph; 
end; 
run; 

/*念のため 画像出力 */ 
ods graphics /imagename="graph_gridded" width=28cm noborder ; 
ods listing gpath="画像保存パス" ; 
options nobyline nodate orientation=landscape; 
ods rtf file="保存パス"; 
proc sgrender data=import2 template=figure1a_gtl; 
run; 
ods rtf close;


実行結果(pngファイル)
実行結果(RTFファイル)

RTFでも正常に出力できました。

コード量が多くわかりにくいのでレイアウトの構造を図示してみました。

レイアウト構造(ブロックごとに色分けして表示)

グラフエリアを2*3に分割し、1,4には矢印を、2,3,5,6にはグラフを配置します。2,3,5,6はさらに上下に分割し、上には折れ線グラフ、下はリスク集合テーブルをを配置しています。

このような表だと行と列に共通軸を設定できるのですが、layout latticeがネストしているのと縦軸の設定が各グラフでバラバラなので、軸設定は個別に指定し、表示する要素を細かく指定しています。
評価項目名はcellheaderレイアウトで配置しました。

凡例は共通ですので、globallegendレイアウト上に1つだけ配置しても良かったかもしれませんね。リスク集合テーブルはプロットエリア内に配置したほうがもっとスペースを節約できそうです。

GTLは各グラフの共通要素を配置する専用のレイアウトがあるので、表示スペースを節約できます。
複数のグラフ配置するのはやはり圧倒的にGTLが高機能です。

難易度が高い

RTF出力も問題なくできるし、各要素を細かく設定できるので論文投稿用の凝った成果物を作成したいのであればGTL一択なのですが・・・コード量が多いのでちと大変です。datalatticeが使い勝手悪く、今回のケースでは使えないためどうしても同じようなコードを何度も書くことになってしまいます。メンテナンス性は良いかといわれる微妙なところです。

datalatticeレイアウトの機能を拡張してくれると楽になるのですけど無理かな・・・

結論

3回の検証の結果、以下のような結論となりました。

全く同じ設定のグラフを単純にグリッド上に配置するのみで、PDFやHTML形式の出力で出力する場合
→proc report

各グラフの要素を細かく指定し洗練されたグラフを作成したい場合、あるいはRTFとEMF形式でないと困る場合
→GTL

RTFでの出力が一般的でしょうから、やっぱりGTL使うしかないのではないでしょうか。