SAS

GTLでバタフライチャートを作図する

バタフライチャート(またはバタフライプロット)とは横棒グラフがグラフ中心から左右へ延びるグラフのことです。男女別の人口ピラミッドなどで利用されています。

このグラフを作る場合は作図データをマイナスに反転させるなどの前処理を実施している事例もありますが、GTLであればそのような
データの前処理なしで作図することができます。

(2021/05/03 更新 作図方法を追加しました。)

(2023/09/27 更新 SAS Plotterでバタフライチャートを作成できるようになりました。)

作図方法1

今回は東京都の新型コロナウィルス陽性者数を男女別および年齢別にバラフライチャートで図示します。

プロットエリアが2つあるのでlayout latticeを使用します。

左側のプロットエリアはX軸が反転している(右から左へ値が大きくなる)ため、左側のみX軸を反転させます。
棒グラフはbarchartステートメントを使用し、orientオプションで水平方向に指定します。
statオプションを使用すると事前にfreqプロシジャなどで人数を集計しなくても直接作図できます。

データ前処理

まず下記のリンクからcsvデータをダウンロードし、フォーマットの割り当てを実施します。そして男女毎に年齢区分変数を新規作成します。

proc import datafile=< csv file > dbms=CSV out=raw; 
   GETNAMES=no; 
RUN; 

proc format; 
   value sexf 1="男性"
              2="女性"; 

   value agef 1="10代" 
              2="20代" 
              3="30代" 
              4="40代" 
              5="50代" 
              6="60代" 
              7="70代" 
              8="80代" 
              9="90代" 
             10="100歳以上" 
             99="不明" ; 
run; 

*フォーマット割り当て 年齢区分作成; 
data dat1; 
format date yymmdd10. sex sexf. agegrp agef.; 
set raw; 
date=VAR5; 
month=month(VAR5); 
if VAR10="男性" then sex=1; 
if VAR10="女性" then sex=2; 
select (VAR9) ; 
   when("10代") agegrp=1; 
   when("10歳") agegrp=1; 
   when("20代") agegrp=2; 
   when("30代") agegrp=3; 
   when("40代") agegrp=4; 
   when("50代") agegrp=5; 
   when("60代") agegrp=6; 
   when("70代") agegrp=7; 
   when("80代") agegrp=8; 
   when("90代") agegrp=9; 
   when("100") agegrp=10; 
     otherwise agegrp=99; 
end; 
run; 

*男女別に年齢区分を作成; 
data graph; 
merge dat1(where=(sex=1)) dat1(where=(sex=2) rename=(agegrp=agegrp2)); 
keep agegrp agegrp2; run;

作図テンプレート

proc template; 
define statgraph butterfly; 
begingraph; 
entrytitle "東京都_新型コロナウイルス陽性患者(2020/11/28)" / pad=5; 

layout lattice / columns=2 columnweights=(0.52 0.48) ; 

*左側のプロットエリアにはY軸のtickvalueを表示させるため少しだけ横幅を広げる; 
*左側(男性); 

   cell; 
     cellheader ; 
        entry "男性" / textattrs=(size=10) pad=5; 
     endcellheader; 

     layout overlay / 
       xaxisopts=(display=(ticks tickvalues) 
          linearopts=(
             tickvaluesequence=(start=0 end=6000 increment=2000) viewmax=7000) 
          reverse=true 
          griddisplay=on
       ) 
       yaxisopts=(display=none displaysecondary=(ticks tickvalues) reverse=true); 

       barchart category=agegrp / 
          stat=freq 
          orient=horizontal 
          fillattrs=graphdata1; 
      endlayout; 
   endcell; 

*左側(女性); 
   cell; 
      cellheader; 
         entry "女性" / textattrs=(size=10) pad=5; 
      endcellheader; 

      layout overlay / 
         xaxisopts=(display=(ticks tickvalues) 
            linearopts=(tickvaluesequence=(start=0 end=6000 increment=2000) viewmax=7000)
            griddisplay=on ) 
         yaxisopts=(display=(ticks) reverse=true); 

         barchart category=agegrp2 / 
            stat=freq 
            orient=horizontal 
            fillattrs=graphdata2; 
      endlayout; 
   endcell; 
*X軸ラベル; 
      sidebar / align=bottom; 
          entry "陽性者人数" / 
             textattrs=(size=10) pad=5; 
          endsidebar; 
      endlayout; 
   endgraph; 
end; 
run; 

proc sgrender data=graph template=butterfly; 
run;
実行結果

全年齢で男性のほうが陽性者数が多いようですね。

なおY軸の目盛りラベルは通常左側に表示されますが、yaxisoptsオプションのdisplaysecondaryオプションを使用すれば右側に目盛ラベルを表示できます。
X軸もxaxisoptsオプションに同様の設定をすると上側に目盛を表示させることができます。

作図方法2

ほう一つの方法はあらかじめfreqプロシジャなどで性別ごとに集計し、barchartparmステートメントで作図する方法です。集計済みのデータを使用するケースが多いと思うので、方法2のほうが推奨です。

男性の集計結果はeval関数とifn関数の併用でマイナスの値に変換します。これでデータステップでマイナスに反転させる必要はなくなります。

これだと軸の目盛りがマイナスになってしまうので、tickvalueformatを利用して表示される値を修正します。

軸目盛のフォーマットを指定しないといけませんが、layout latticeを使うよりもテンプレートがシンプルになる点は良いですね。

前処理

必要なフォーマットと集計を行います。軸目盛のフォーマットはpictureステートメントを利用して先頭に付与されるマイナス符号を非表示し、4桁の数字のみを表示させるようにします。
なおpictureステートメントで先頭に文字を表示させたい場合はprefixオプションを利用します。

proc format; 
value sexf 1="男性" 
           2="女性"; 
value agef 1="10代" 
           2="20代" 
           3="30代" 
           4="40代" 
           5="50代" 
           6="60代" 
           7="70代" 
           8="80代" 
           9="90代" 
          10="100歳以上" 
          99="不明" ; 

/* 軸フォーマット */ 
picture axis low-high="0000"; 
run; 

*フォーマット割り当て 年齢区分作成; 
data dat1; 
format date yymmdd10. sex sexf. agegrp agef.; 
set raw; 
date=VAR5; 
month=month(VAR5); 
if VAR10="男性" then sex=1; 
if VAR10="女性" then sex=2; 
select (VAR9) ; 
   when("10代") agegrp=1; 
   when("10歳") agegrp=1; 
   when("20代") agegrp=2; 
   when("30代") agegrp=3; 
   when("40代") agegrp=4; 
   when("50代") agegrp=5; 
   when("60代") agegrp=6; 
   when("70代") agegrp=7; 
   when("80代") agegrp=8; 
   when("90代") agegrp=9; 
   when("100") agegrp=10; 
   otherwise agegrp=99; 
end; 
run; 

*集計; 
proc freq data=dat1 noprint; 
tables agegrp*sex / out=graph2; 
where sex^=.; 
run;

テンプレート;

軸にマイナス表示されないようにしているだけなので、実際の軸の値はマイナスになっています。そのため軸の表示範囲の指定はマイナスの値から指定する必要があります。

proc template; 
define statgraph butterfly2; 
begingraph; 
entrytitle "東京都_新型コロナウイルス陽性患者(2020/11/28)" / pad=5; 
layout overlay / 
  yaxisopts=(label="年代" reverse=true) 
  xaxisopts=(label="陽性者人数" 
     griddisplay=on 
     linearopts=(viewmin=-8000 viewmax=8000 tickvalueformat=axis. 
        tickvaluesequence=(start=-8000 end=8000 increment=2000)
     )
  ); 

/*sex=1は集計値をマイナスへ反転させる*/ 

      barchartparm category=agegrp response=eval(ifn(sex=1, -count, count))/ 
         group=sex 
         orient=horizontal 
         name="bar"; 

      discretelegend "bar"; 
  endlayout; 
endgraph; 
end; 
run; 

proc sgrender data=graph2 template=butterfly2; 
run;

データセットの数値をいじらなくてもGTL上で作図に必要な数値変換を実施してくれるので、これは便利かも。

SAS Plotterのマクロを使用する

SASグラフパッケージ「SAS Plotter」でバタフライチャートを作成できるようになりました。使用方法は以下の記事を参照してください。

https://picolabs.jp/SASPlotter_Mirrored_histogram