2022年7月31日 星期日

「SAS繪圖」散佈圖盒形圖疊圖實作,用教科工具書等級的繪圖思路告訴你。

這其實是版大實體教學課其中一個教案,對象為醫務管理相關科系的實習生,這個教案出自版大統計工具書Proc sgplot vbox statement段落其中一個教學範例(摘錄約8成內容)。



以下為最終完成圖 (圖由SAS直出,未經小畫家作弊修改)

案例數據來自SAS內建練習用資料庫sashelp, 如果有一個連續變項(例如急診等候時間),一個類別變項(例如LOW-18歲年齡層, 19-65歲年齡層, 65-HIGH歲年齡層),擁有這2個變項的檔案一旦進入SAS,只要30秒就能變出上述的成品圖。30秒完成資料排列、描述性統計和繪圖。
拿版大撰寫的程式碼去套自已的數據,修改程式碼關鍵字後按跑,圖就出來了,這就是這堂課的授課目的。不必知道什麼是SAS,也不必知道語法的意義,只要知道該改哪裡。
至於SAS軟體,SAS有推出ODA (OnDemand for Academics)版SAS Studio,大家可自行前往下載(這部份版大在書中有詳細說明) SAS OnDemand for Academics  (須先申請SAS帳號)。一旦完成申請與設定程序,即可在網頁瀏覽器上以帳號密碼登入操作,不用再安裝軟體(意即任何一台可連網的電腦就能使用)。


面版似SAS Enterprise Guide的簡化版。

本案練習數據來自sashelp項下的heart資料集。
執行proc contents data=sashelp.heart; run; 
產出下表。
heart這個資料集擁有的變項如上,版大透過黃色變項將資料集內資料筆數縮小(原資料集筆數過多),語法如下:

data heart2;
set sashelp.heart;
if deathcause = "Coronary Heart Disease" and ageatstart <=45;
if cholesterol=.  or weight_status="."  or weight_status="" then delete;
keep  cholesterol weight_status;
run;
只保留致死病因為Coronary Heart Disease且得病年齡<=45yrs
黃色變項有遺漏的全刪,只保留cholesterol和weight_status變項。
前者為膽固醇表現量(連續變項),後者為體重狀態(其值為"體重過輕","體重正常","體重過重")
最後得到一個heart2資料集。

data a;   /*******自已準備一個像a內容的資料集, 請使用同樣的變項名稱*******/
set heart2;
rename cholesterol=var1  weight_status=group;
run;
變數名稱轉換後(cholesterol改成Var1,weight_status改成group),產生含有191筆資料的a資料集,如下:


如果讀者數據想呈現成品圖外貌,請準備一個excel檔,檔名為a,內有兩個變項。
連續變項名稱改為var1, 類別變項名稱改為group (請照指示改,到了後面就會知道)。

上傳SAS後,一個最粗糙簡單(無法再簡單了,再簡下去根本跑不出來)的盒形圖語法如下:
proc sgplot data=a;
vbox  var1  ;
run;
用proc sgplot跑a這個資料集,vbox表示要畫「站起來」的盒形圖(有站起來的當然就會有躺平的hbox), 至於要站多高,端看放在Y軸的變項來決定,這裡的Y軸就是指var1,這樣就完成了,對照以下圖形就能理解。

proc sgplot data=a;
vbox  var1/ category=group;
run;
因為我們有一個類別變項把這個檔案內含的資料筆數分成數群,這個變項名稱為group,放入語法後(如上),就能跑出如下圖形, 對照以下圖形就能理解。
vbox ................... /................;    / 符號左邊供放必跑的參數(稱為必指定的Option,彼此間位置可互換), / 符號右邊供放選擇性的參數(稱為選擇性指定的Option,彼此間位置也可互換),所以category=要放在 /符號右邊而不是左邊,因為category=不放也可以跑出box plot出來。

而像畫折線圖的series, 最基本的語法為
series x=var1 y=var2 / .......   /符號左邊 x= y=必放,不然折線圖完全跑不出來。
series和vbox都同屬處於中間的Statement等級,上面有Procedure(簡稱Proc)等級,下面有Option等級, Proc sgplot有著為數眾多的Statement


另外,讀者可能會問放group但怎跑出weight_status?
那是變項(欄)名稱和標籤顯示問題。下圖顯示SAS Foundation畫面:


下圖顯示SAS Studio畫面:

實際上他們是同一個變項。


過來加粗盒外形線條。
proc sgplot data=a;
vbox  var1/ category=group   nofill  lineattrs=(thickness=2.8) ;
run;
thickness值愈大線條愈粗,lineattrs用於調整線格式, lineattrs=(thickness=2.8  color=red patten=1 )分別調整線的粗細,顏色和線條類型,nofill要求不填滿。如同前面所描述,他們可互換位置(例如也可寫成   / nofill   category=group   lineattrs=(thickness=2.8) ;        )。  

過來改盒內線(中位數)和盒身符號(即平均值)。
proc sgplot data=a;
vbox  var1/ category=group   nofill  lineattrs=(thickness=2.8)
                        meanattrs=( symbol=starfilled  size=14  color=red) medianattrs=thickness=2.8  color=black)   ;
run;
粉紅色字控制代表平均值的符號格式,黃色字控制代表中位數的盒內線格式。

過來改上下兩根線的格式。
proc sgplot data=a;
vbox  var1/ category=group   nofill  lineattrs=(thickness=2.8)
                        meanattrs=( symbol=starfilled  size=14  color=red)  medianattrs=(thickness=2.8  
                        color=black)   whiskerattrs=(thickness=2.8 color=black)  nooutliers;
run;  
成果如下,整個盒子外觀一致了。到了這邊,就知道可以改哪裡了。至於符號圖的symbol=, 色碼的color=, 讀者可參考版大另篇文章
至於nooutliers,若盒形圖有出現極端值,就要求SAS隱藏不畫。


至於極端值定義,SAS沒有明確說明。在此文件中,可以找到SAS給這個term的作用說明為
然後再對照同一文件內的盒形圖說與上圖左上角那三個偏離的資料點,可以推測 nooutliers影響的是下圖 Faroutlier區的資料點(即4.5倍盒身長之外)。


若要調整盒寬,可以加入如下粉紅色的term。其值介於0-1之間,值愈大盒身就愈寬。
proc sgplot data=a;
vbox  var1/ category=group  boxwidth=0.8    nofill  lineattrs=(thickness=2.8)
                        meanattrs=( symbol=starfilled  size=14  color=red)  medianattrs=(thickness=2.8  
                        color=black)   whiskerattrs=(thickness=2.8 color=black) ;
run;  


過來改y軸。
控制y軸的statement為yaxis 座標名稱可用label="" 指定,讀者可指定一個與var1(即原變項名)不同的名稱。 labelattrs=(size=14 color=black family="微軟正黑體") 左列表示將y軸名稱設定為黑色字,字體大小為14,字體為微軟正黑體(注意:ODA版SAS Studio不支援中文字體修改,只能使用預設字體),下面粉紅內容,只設定字體大小。而valueattrs= 調整目標為y軸值格式,values=調整目標則為y軸值。

proc sgplot data=a;
vbox  var1/ category=group  boxwidth=0.8    nofill  lineattrs=(thickness=2.8)
                        meanattrs=( symbol=starfilled  size=14  color=red)  medianattrs=(thickness=2.8  
                        color=black)   whiskerattrs=(thickness=2.8 color=black) ;
yaxis label="var1"  labelattrs=(size=14) valueattrs=(size=12) values=(150 to 600 by 50);
run;  
結果如下: 150 to 600 by 50 其作用應該很明顯了, by 50就是以50的間距逐漸往上加。
若把它改成  values=(150 175 200 225 250 275 300 325 350 375 475 525 575);
就變成如下:
看到了嗎? y軸呈現了不同的資料點距,針對資料點分佈密集區,y值間距可以小一點。反之,則設定較大跨距的y值。
過來改x軸。
proc sgplot data=a;
vbox  var1/ category=group  boxwidth=0.8    nofill  lineattrs=(thickness=2.8)
                        meanattrs=( symbol=starfilled  size=14  color=red)  medianattrs=(thickness=2.8  
                        color=black)   whiskerattrs=(thickness=2.8 color=black) ;
yaxis label="var1"  labelattrs=(size=14) valueattrs=(size=12) values=(150 175 200 225 250 275 300 
          325 350 375 475 525 575);
xaxis label="Group"  labelattrs=(size=14) valueattrs=(size=12);
run;  
上圖的效果就如同y軸的設定,沒什麼好解釋。

版大想移除那3根短線和x軸名稱。

proc sgplot data=a;
vbox  var1/ category=group  boxwidth=0.8    nofill  lineattrs=(thickness=2.8)
                        meanattrs=( symbol=starfilled  size=14  color=red)  medianattrs=(thickness=2.8  
                        color=black)   whiskerattrs=(thickness=2.8 color=black) ;
yaxis label="var1"  labelattrs=(size=14) valueattrs=(size=12) values=(150 175 200 225 250 275 300 
          325 350 375 475 525 575);
xaxis display=(noticks nolabel)  valueattrs=(size=12);
run;  
那3根短線叫ticks, noticks, 再來一個nolabel (都不想顯示軸名稱了,label="Group"  labelattrs=(size=14) 理所當然可以拿掉),結果如下:
這時讀者會問:怎麼連x軸名稱都拿掉了。
財團法人中X發展中心有個輔導老師在品管圈教育訓練課程有講到圖的評判標準。老師說統計圖軸名稱不能缺,不然會打缺失。如果這圖是要參與品管圈競賽,作者會附上軸名稱(附歸附,但心裡不認同)。在這個案子,這三類名稱依序為體重正常、體重過重和體重過輕,已明示這個軸就是在說明體重分類,X軸名稱已沒有留的價值。

作者授課時喜歡用教科書內容支持我的作法和想法(都有憑有據,就是要有所本和國際期刊論文Reviewer打筆仗才會贏),這裡也不例外。以下這本書為管制圖神人LLOYD P. PROVOST 的著作。


版大應該不用再說明了(黃色內容言簡意賅),有神人支持我的作法(我和神人的看法一致,我也是神人)。這本書近8成內容我都讀過了(讀懂並把有用的知識和作法摘錄出來,剩下2成內容都是閒話家常的廢話)。我敢說全國醫療院所有在用管制圖的行政人員,沒人像我讀這麼徹底的,關於管制圖的英文教科書,我總共研究了3本 ,就上面這本寫的最好最全面(可以考慮撰寫這本巨著的翻譯書)。我正在為最後一個里程(個人專業著作)而努力,另外二個已達到(專利+國際期刊論文),目前剩第三個,一旦完成專業著作(書在圖書館藏中能搜尋到,在讀墨電子書平台能搜尋到),在全國醫療院所行政人員裡,同時擁有這3項能力經驗的人,我敢說不到0.5% 。
ps: 專利文件報告書是專利公司寫的,這事必須聲明。這東西不是這領域的人真的寫不出來。

再補上第2本支持我做法的著作:

這本書的作者,也是R軟體ggplot2套件的貢獻者,相信有在用R繪製統計圖的人都不陌生。
書的第266, 267頁這麼寫:




最後,作者認為此圖應修改成如下態樣(小弟我極為同意作者的觀點)。



回到主題~~
目前的圖,類別左至右依序為Normal > Overweight > Underweight,接下來作者把順序調成Underweight > Normal >Overweight ,這樣較完美。
我們先看看資料順序。
由上至下很明顯是過重 > 正常 >過輕。
經查SAS文件,發現以下段落。

所以塞了新語法進入,如下:
proc sgplot data=a;
vbox  var1/ category=group  boxwidth=0.8    nofill  lineattrs=(thickness=2.8)
                        meanattrs=( symbol=starfilled  size=14  color=red)  medianattrs=(thickness=2.8  
                        color=black)   whiskerattrs=(thickness=2.8 color=black) ;
yaxis label="var1"  labelattrs=(size=14) valueattrs=(size=12) values=(150 175 200 225 250 275 300 
          325 350 375 475 525 575);
xaxis display=(noticks nolabel)  valueattrs=(size=12) discreteorder=data;
run;  
效果如下:

哇,這樣也可以 (過重 >正常>過輕),有順。所以這樣也可。
如果想呈現反過來的順序,要在a資料集另建一個變項 ,Underweight=1, Normal=2, Overweight=3, 再sorting這個變項。這時,過輕的就會排在前,過重的就會排在後,一樣的discreteorder=data語句,這個排序結果就會出現。另外,也可在Excel上排好順序(只要管住前三個觀察值排序即可)後再進來SAS。

在vbox後加上 displaystats=( std  median mean n)會有神奇的效果,如下:

統計值竟然塞進了xy軸區內(而且我沒有事先算好給它)。
proc sgplot data=a;
vbox  var1/ category=group  boxwidth=0.8    nofill  lineattrs=(thickness=2.8)
                        meanattrs=( symbol=starfilled  size=14  color=red)  medianattrs=(thickness=2.8  
                        color=black)   whiskerattrs=(thickness=2.8 color=black)  
                     displaystats=( std  median mean n);
yaxis label="var1"  labelattrs=(size=14) valueattrs=(size=12) values=(150 175 200 225 250 275 300 
          325 350 375 475 525 575);
xaxis display=(noticks nolabel)  valueattrs=(size=12) discreteorder=data;
run;  
SAS以「迅雷不及掩耳」速度塞進各類別的描述性統計值。它還可以塞其它的統計值,詳如同樣的文件但這樣的作法無法改內容格式,所以請刪掉上述粉紅色內容,作者教你另闢蹊徑

上面不是有準備一個a的資料集嗎?此時請接續跑以下語法,會產出c資料集。
proc means data=a noprint nway;
class group;
var var1;
output out=b (drop= _freq_  _type_)  n(var1)=n mean(var1)=mean_var1 median(var1)=median_var1   std(var1)=std_var1;
run;
proc sql;
create table c as
select a.var1, a.group, b.n, b.mean_var1, b.median_var1, b.std_var1  from a left join b on a.group=b.group;
quit;
data c;
set c;
format mean_var1 median_var1  std_var1  9.2  ;
run;
內容如下:
看到了嗎?以group分類為依據,算出每一群組的n, mean, median, std,接下來請插入以下粉紅色內容。

proc sgplot data=c;   /*現在改拿c資料集畫圖了嘿*/
vbox  var1/ category=group  boxwidth=0.8    nofill  lineattrs=(thickness=2.8)
                        meanattrs=( symbol=starfilled  size=14  color=red)  medianattrs=(thickness=2.8  
                        color=black)   whiskerattrs=(thickness=2.8 color=black) ;
yaxis label="var1"  labelattrs=(size=14) valueattrs=(size=12) values=(150 175 200 225 250 275 300 
          325 350 375 475 525 575);
xaxis display=(noticks nolabel)  valueattrs=(size=12) discreteorder=data;
xaxistable n /   label="人數" labelattrs=(size=12 color=blue)  valueattrs=(size=12)  stat=mean  location=inside nostatlabel; 
xaxistable median_var1  /  label="中位數" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean  location=inside nostatlabel;
xaxistable mean_var1  / label="平均數" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean   location=inside nostatlabel; 
xaxistable std_var1  / label="標準差" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean   location=inside nostatlabel; 
run;  

效果如下:
然而,順序變了!(可惡)
如果不在意順序,以下這段可以跳過。

在上面的橘底區語法後面接以下語法:
data d;
set c;
if group="Underweight" then sort=1;
else if group="Normal" then sort=2;
else sort=3;
run;
Proc sort;by sort;run;
依group內容值決定sort變項內容,然後sort變項再小至大排。
d資料集如下:




然後再跑一次以下語法  
proc sgplot data=d;   /*要改成d資料集了*/
vbox  var1/ category=group  boxwidth=0.8    nofill  lineattrs=(thickness=2.8)
                        meanattrs=( symbol=starfilled  size=14  color=red)  medianattrs=(thickness=2.8  
                        color=black)   whiskerattrs=(thickness=2.8 color=black)  ;
yaxis label="var1"  labelattrs=(size=14) valueattrs=(size=12) values=(150 175 200 225 250 275 300 
          325 350 375 475 525 575);
xaxis display=(noticks nolabel)  valueattrs=(size=12) discreteorder=data;
xaxistable n /   label="人數" labelattrs=(size=12 color=blue)  valueattrs=(size=12)  stat=mean  location=inside nostatlabel; 
xaxistable median_var1  /  label="中位數" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean  location=inside nostatlabel;
xaxistable mean_var1  / label="平均數" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean   location=inside nostatlabel; 
xaxistable std_var1  / label="標準差" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean   location=inside nostatlabel; 
run;  
想要的效果就回來了。

 有xaxistable當然就會有yaxistable,前者沿x軸「排列到右邊」,軸名稱在y軸上;而後者就是沿著y軸「排列到上面」,軸名稱在x軸上。關於該statement,讀者可自行閱讀此文件(此教案不細講此statement用法)。

若讀者不想讓它出現,可以在語句前掛個*字號。
例如SAS本來會執行
xaxistable std_var1  / label="標準差" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean   location=inside nostatlabel; 
一旦在前面加一個*
*xaxistable std_var1  / label="標準差" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean   location=inside nostatlabel; 
此xaxistable statement會被SAS略過。
故  *....;  作用同  /*......*/ ,SAS都不會讀取。不成文規定是前者常用於某段程式的開開關關;後者則用於注釋說明。

臨床檢驗的知識告訴我們,Cholesterol超過200就算超標。所以接下來,我們把資料落點區分成LOW-200和200-HIGH。這個要求,可靠Band statement達成。我們用2個band分別畫出LOW-200和200-HIGH

band  x=group  lower=0 upper=200  / fillattrs=(color=cxe4f8c9) legendlabel="Var1值表現安全區"  name="a"; 
x=group,表示這條band的寬(x軸)由最左邊長到最右邊,而其高由y=0長到y=200,fillattrs是指定用什麼「顏料」塗滿band, 可以是顏色,也可以是各式線條。legendlabel= 為圖說設定之用(稍後說明),name= 則為此圖示強制命名,可選擇性地要求是否顯示於圖說中(稍後說明)。
第二條band語法如下:
band  x=group  lower=200 upper=100000  / fillattrs=(color=cxf5aba8) legendlabel="Var1值表現危險區" ;
語法和結果圖如下:

proc sgplot data=d  ;
band  x=group  lower=0 upper=200  / fillattrs=(color=cxe4f8c9) legendlabel="Var1值表現安全區" name="a"; 
band  x=group  lower=200 upper=100000  / fillattrs=(color=cxf5aba8)  legendlabel="Var1值表現危險區"  name="b"; 
vbox  var1 / category=group  boxwidth=0.8  nofill  lineattrs=(thickness=2.8)  meanattrs=(                           symbol=starfilled  size=14  color=red)  medianattrs=(thickness=2.8  color=black)  
                     whiskerattrs=(thickness=2.8 color=black)   outlierattrs=(size=12) ;
yaxis label="var1"  labelattrs=(size=14) valueattrs=(size=12) values=(150 175 200 225 250 275 300 
                     325 350 375 475 525 575);
xaxis label="Group"  display=(noticks nolabel) labelattrs=(size=14) 
                     valueattrs=(size=12) discreteorder=data;
xaxistable n /   label="人數" labelattrs=(size=12 color=blue)  valueattrs=(size=12)  stat=mean  
                       location=inside nostatlabel; 
xaxistable median_var1  /  label="中位數" labelattrs=(size=12 color=blue)  valueattrs=(size=12) 
                                            stat=mean  location=inside nostatlabel;
xaxistable mean_var1  / label="平均數" labelattrs=(size=12 color=blue)  valueattrs=(size=12) 
                                       stat=mean   location=inside nostatlabel; 
xaxistable std_var1  / label="標準差" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean  
                                    location=inside nostatlabel; 
run;

這時就知道legendlabel= 的功能了,而上圖圖說顯示黑線為Var1,可把它拿掉。
keylegend  "a"  "b" / valueattrs=(size=14) autoitemsize  ;
透過上述語法,只加入 "a" 和 "b", 這時就不會在圖說中出現那條黑線。如果也想讓那條黑線存在(事實上,它不該出現在這),只要在 vbox statement後面加入name="c",然後再於上述keylegend statement中加放"c" ,那條黑線就會存在,如下:

proc sgplot data=d  ;
band  x=group  lower=0 upper=200  / fillattrs=(color=cxe4f8c9) legendlabel="Var1值表現安全區"  name="a"
band  x=group  lower=200 upper=100000  / fillattrs=(color=cxf5aba8) legendlabel="Var1值表現危險區"  name="b"
vbox  var1 / category=group  boxwidth=0.8 nooutliers  nofill  lineattrs=(thickness=2.8)  meanattrs=( 
                    symbol=starfilled  size=14  color=red)   medianattrs=(thickness=2.8  color=black)  
                   whiskerattrs=(thickness=2.8 color=black)   outlierattrs=(size=12)   name="c";
yaxis label="var1"  labelattrs=(size=14) valueattrs=(size=12) values=(150 175 200 225 250 275 300 325 350 375 475 525 575);
xaxis label="Group"  display=(noticks nolabel) labelattrs=(size=14) valueattrs=(size=12) discreteorder=data;
xaxistable n /   label="人數" labelattrs=(size=12 color=blue)  valueattrs=(size=12)  stat=mean  location=inside nostatlabel; 
xaxistable median_var1  /  label="中位數" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean  location=inside nostatlabel;
xaxistable mean_var1  / label="平均數" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean   location=inside nostatlabel; 
xaxistable std_var1  / label="標準差" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean   location=inside nostatlabel; 
keylegend  "a"  "b" / valueattrs=(size=14) autoitemsize  ;
run;



過來,把散布圖的球擺入,散布圖的statement為scatter,如下:
scatter x=group   y=var1 / jitter transparency =0.1 filledoutlinedmarkers markerattrs=(symbol=circlefilled size=14) markerfillattrs=graphdata1 dataskin=sheen;

transparency =0.1控制球的透明度,jitter控制球排列方式,
其餘則控制球的外觀,這是我測試過覺得最好看的視覺效果(當然,每個人的美感有所不同)。
若想調整顏色甚至其它格式,請自行搜尋scatter statement。(版大的書會有詳盡說明)。


proc sgplot data=d  ;
band  x=group  lower=0 upper=200  / fillattrs=(color=cxe4f8c9) legendlabel="Var1值表現安全區"  name="a"; 
band  x=group  lower=200 upper=100000  / fillattrs=(color=cxf5aba8) legendlabel="Var1值表現危險區"  name="b"; 
scatter x=group   y=var1 / jitter transparency =0.1 filledoutlinedmarkers markerattrs=(symbol=circlefilled size=14) markerfillattrs=graphdata1 dataskin=sheen;
vbox  var1 / category=group  boxwidth=0.8 nooutliers  nofill  lineattrs=(thickness=2.8)  meanattrs=( 
                    symbol=starfilled  size=14  color=red)   medianattrs=(thickness=2.8  color=black)  
                   whiskerattrs=(thickness=2.8 color=black)   outlierattrs=(size=12)  ;
yaxis label="var1"  labelattrs=(size=14) valueattrs=(size=12) values=(150 175 200 225 250 275 300 325 350 375 475 525 575);
xaxis label="Group"  display=(noticks nolabel) labelattrs=(size=14) valueattrs=(size=12) discreteorder=data;
xaxistable n /   label="人數" labelattrs=(size=12 color=blue)  valueattrs=(size=12)  stat=mean  location=inside nostatlabel; 
xaxistable median_var1  /  label="中位數" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean  location=inside nostatlabel;
xaxistable mean_var1  / label="平均數" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean   location=inside nostatlabel; 
xaxistable std_var1  / label="標準差" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean   location=inside nostatlabel; 
keylegend  "a"  "b" / valueattrs=(size=14) autoitemsize  ;
run;
這時讀者可發現statement由上至下依序為band、scatter、vbox。 SAS讀取程式由上至下,所以先畫band,再畫散佈圖scatter,再畫盒形圖vbox,所以band在最下面,vbox在最上面,他們分處在不同的圖層,呈現順序剛好與Photoshop圖層相反。

在Photoshop中,左邊(圖中)顯示綠上黑下,粉紅矩形在中間層,而右邊圖層面板也顯示這個順序。只要在圖層面板中更改(拖動)彼此間順序,圖中元素層序也會跟著改變。所以在上述語法中,若將band statement留到最後再寫,就只會看到band。在band的後面,藏著我們看不到的scatter和vbox,除非調整band的透明度,但版大不建議這樣做,這結果會產生「朦朧美」,這不是這樣的圖該呈現的「特效」。


上面兩張黑白圖為以下教科書的說明案例-顯示統計圖由數個圖層層層疊出。
在繪圖流程中,讀者一定要有圖層的思維。

若要加圖標,請下title statement
h值(也可用全銜字 height)控制字大小,若使用SAS Studio,只能改變英文字體無法改變中文字體。欲改變中文字體,得靠商業版SAS。例如: font="微軟正黑體"    family="微軟正黑體"等,這方面參數設定在版大書中有詳盡說明,含控制粗黑體,斜體等格式。
此title一旦寫下去,會套用到後續產生的所有統計圖。故此標題若只套用此圖,尾巴用title; 清掉內容。

title   height=1.8 "我是標題" ;    
proc sgplot data=d  ;
band  x=group  lower=0 upper=200  / fillattrs=(color=cxe4f8c9) legendlabel="Var1值表現安全區"  name="a"; 
band  x=group  lower=200 upper=100000  / fillattrs=(color=cxf5aba8) legendlabel="Var1值表現危險區"  name="b"; 
scatter x=group   y=var1 / jitter transparency =0.1 filledoutlinedmarkers 
               markerattrs=(symbol=circlefilled size=14) markerfillattrs=graphdata1 dataskin=sheen;
vbox  var1 / category=group  boxwidth=0.8 nooutliers  nofill  lineattrs=(thickness=2.8)  meanattrs=( 
                    symbol=starfilled  size=14  color=red)   medianattrs=(thickness=2.8  color=black)  
                   whiskerattrs=(thickness=2.8 color=black)   outlierattrs=(size=12)  ;
yaxis label="var1"  labelattrs=(size=14) valueattrs=(size=12) values=(150 175 200 225 250 275 300 325 350 375 475 525 575);
xaxis label="Group"  display=(noticks nolabel) labelattrs=(size=14) valueattrs=(size=12) discreteorder=data;
xaxistable n /   label="人數" labelattrs=(size=12 color=blue)  valueattrs=(size=12)  stat=mean  location=inside nostatlabel; 
xaxistable median_var1  /  label="中位數" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean  location=inside nostatlabel;
xaxistable mean_var1  / label="平均數" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean   location=inside nostatlabel; 
xaxistable std_var1  / label="標準差" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean   location=inside nostatlabel; 
keylegend  "a"  "b" / valueattrs=(size=14) autoitemsize  ;
run; title;


接下來我們來改統計圖的長寬。用ods graphics 把proc....run 區間夾起來。圖寬設1000畫素,圖高設800畫素。

ods graphics / imagefmt=png  width=1000px  height=800px  ;  
......(主語法區)
ods graphics off;

title   height=1.8 "我是標題" ;    
ods graphics / width=1000px  height=800px  ; 
proc sgplot data=d  ;
band  x=group  lower=0 upper=200  / fillattrs=(color=cxe4f8c9) legendlabel="Var1值表現安全區"  name="a"; 
band  x=group  lower=200 upper=100000  / fillattrs=(color=cxf5aba8) legendlabel="Var1值表現危險區"  name="b"; 
scatter x=group   y=var1 / jitter transparency =0.1 filledoutlinedmarkers 
               markerattrs=(symbol=circlefilled size=14) markerfillattrs=graphdata1 dataskin=sheen;
vbox  var1 / category=group  boxwidth=0.8 nooutliers  nofill  lineattrs=(thickness=2.8)  meanattrs=( 
                    symbol=starfilled  size=14  color=red)   medianattrs=(thickness=2.8  color=black)  
                   whiskerattrs=(thickness=2.8 color=black)   outlierattrs=(size=12)  ;
yaxis label="var1"  labelattrs=(size=14) valueattrs=(size=12) values=(150 175 200 225 250 275 300 325 350 375 475 525 575);
xaxis label="Group"  display=(noticks nolabel) labelattrs=(size=14) valueattrs=(size=12) discreteorder=data;
xaxistable n /   label="人數" labelattrs=(size=12 color=blue)  valueattrs=(size=12)  stat=mean  location=inside nostatlabel; 
xaxistable median_var1  /  label="中位數" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean  location=inside nostatlabel;
xaxistable mean_var1  / label="平均數" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean   location=inside nostatlabel; 
xaxistable std_var1  / label="標準差" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean   location=inside nostatlabel; 
keylegend  "a"  "b" / valueattrs=(size=14) autoitemsize  ;
run; 
ods graphics off;  
title;

產生的統計圖就會變大。圖略。
若是商業版,還可以改輸出圖格式.png, .jpg, 甚至.tif (dpi隨你設 200, 300都沒問題),也可為圖檔設定檔名,輸出時指定存放資料夾(有時一個巨集會跑出數百張圖,再依圖內的資訊類別分門別類放至指定資料夾)。這些在版大的書中都有詳盡說明。

========摘要========

自已準備一個連續變項,名稱叫var1,一個類別變項,名稱叫group。
檔案匯入SAS Studio後,資料集名檔改為a 
然後跑以下語法,就可以產出高水準的統計圖,只須數秒時間。

proc means data=a noprint nway;
class group;
var var1;
output out=b (drop= _freq_  _type_)  n(var1)=n mean(var1)=mean_var1 median(var1)=median_var1   std(var1)=std_var1;
run;
proc sql;
create table c as
select a.var1, a.group, b.n, b.mean_var1, b.median_var1, b.std_var1  from a left join b on a.group=b.group;
quit;
data c;
set c;
format mean_var1 median_var1  std_var1  9.2  ;
run;
data d;
set c;
if group="Underweight" then sort=1;
else if group="Normal" then sort=2;
else sort=3;
run;
Proc sort;by sort;run;

title   height=1.8 "我是標題" ;    
ods graphics / width=1000px  height=800px  ; 
proc sgplot data=d  ;
band  x=group  lower=0 upper=200  / fillattrs=(color=cxe4f8c9) legendlabel="Var1值表現安全區"  name="a"; 
band  x=group  lower=200 upper=100000  / fillattrs=(color=cxf5aba8) legendlabel="Var1值表現危險區"  name="b"; 
scatter x=group   y=var1 / jitter transparency =0.1 filledoutlinedmarkers 
               markerattrs=(symbol=circlefilled size=14) markerfillattrs=graphdata1 dataskin=sheen;
vbox  var1 / category=group  boxwidth=0.8 nooutliers  nofill  lineattrs=(thickness=2.8)  meanattrs=( 
                    symbol=starfilled  size=14  color=red)   medianattrs=(thickness=2.8  color=black)  
                   whiskerattrs=(thickness=2.8 color=black)   outlierattrs=(size=12)  ;
yaxis label="var1"  labelattrs=(size=14) valueattrs=(size=12) values=(150 175 200 225 250 275 300 325 350 375 475 525 575);
xaxis label="Group"  display=(noticks nolabel) labelattrs=(size=14) valueattrs=(size=12) discreteorder=data;
xaxistable n /   label="人數" labelattrs=(size=12 color=blue)  valueattrs=(size=12)  stat=mean  location=inside nostatlabel; 
xaxistable median_var1  /  label="中位數" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean  location=inside nostatlabel;
xaxistable mean_var1  / label="平均數" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean   location=inside nostatlabel; 
xaxistable std_var1  / label="標準差" labelattrs=(size=12 color=blue)  valueattrs=(size=12) stat=mean   location=inside nostatlabel; 
keylegend  "a"  "b" / valueattrs=(size=14) autoitemsize  ;
run; 
ods graphics off;  
title;

通常資料集內容很多,有時只跑部份,就可善用 where這個功能。
例如在run;上面安插一個 where deathcause="Cerebral Vascular Disease" and sex="Male"; 就只針對這一群人畫圖。條件再改一下,再按跑,另一張條件不同的圖又出來了。效率高且保證每次產出的統計圖都有一致的格式。

後記1:SAS Studio也有提供點按式畫圖方式,但能調的效果有限,且也無法做出疊圖效果,如下:

後記2::關於盒形圖,有一支proc直接對應boxplot, 就是 Proc boxplot。寫法就跟Proc freq跑交叉表的用法很像。有興趣者可跑看看以下語法
data Turbine;
   informat Day date7.;
   format Day date5.;
   label KWatts='Average Power Output';
   input Day @;
   do i=1 to 10;
      input KWatts @;
      output;
      end;
   drop i;
   datalines;
05JUL94 3196 3507 4050 3215 3583 3617 3789 3180 3505 3454
05JUL94 3417 3199 3613 3384 3475 3316 3556 3607 3364 3721
06JUL94 3390 3562 3413 3193 3635 3179 3348 3199 3413 3562
06JUL94 3428 3320 3745 3426 3849 3256 3841 3575 3752 3347
07JUL94 3478 3465 3445 3383 3684 3304 3398 3578 3348 3369
07JUL94 3670 3614 3307 3595 3448 3304 3385 3499 3781 3711
08JUL94 3448 3045 3446 3620 3466 3533 3590 3070 3499 3457
08JUL94 3411 3350 3417 3629 3400 3381 3309 3608 3438 3567
11JUL94 3568 2968 3514 3465 3175 3358 3460 3851 3845 2983
11JUL94 3410 3274 3590 3527 3509 3284 3457 3729 3916 3633
12JUL94 3153 3408 3741 3203 3047 3580 3571 3579 3602 3335
12JUL94 3494 3662 3586 3628 3881 3443 3456 3593 3827 3573
13JUL94 3594 3711 3369 3341 3611 3496 3554 3400 3295 3002
13JUL94 3495 3368 3726 3738 3250 3632 3415 3591 3787 3478
14JUL94 3482 3546 3196 3379 3559 3235 3549 3445 3413 3859
14JUL94 3330 3465 3994 3362 3309 3781 3211 3550 3637 3626
15JUL94 3152 3269 3431 3438 3575 3476 3115 3146 3731 3171
15JUL94 3206 3140 3562 3592 3722 3421 3471 3621 3361 3370
18JUL94 3421 3381 4040 3467 3475 3285 3619 3325 3317 3472
18JUL94 3296 3501 3366 3492 3367 3619 3550 3263 3355 3510
;
ods graphics off;
title 'Box Plot for Power Output';
proc boxplot data=Turbine;
   plot KWatts*Day;
   inset min mean max stddev /
      header = 'Overall Statistics'
      pos    = tm;
   insetgroup min max /
      header = 'Extremes by Day';
run;

沒有留言: