非商用目的で 無料で使えるSAS OnDemand for Academicsについて,始め方がわからないという方は,あるいはインストール版のSAS9.4について,インストールの仕方にとまどうって方が多いということで,利用手順をスライドにしてアップしてくれた方がいるので紹介させてくださいませ
https://www.docswell.com/s/NorihiroSuzuki/KNLD4E-SAS_ODA_9.4_installguide#p1
非商用目的で 無料で使えるSAS OnDemand for Academicsについて,始め方がわからないという方は,あるいはインストール版のSAS9.4について,インストールの仕方にとまどうって方が多いということで,利用手順をスライドにしてアップしてくれた方がいるので紹介させてくださいませ
https://www.docswell.com/s/NorihiroSuzuki/KNLD4E-SAS_ODA_9.4_installguide#p1
1982年~2024年までのSASユーザー総会論文集について,公開場所が分散していたりしていましたので世話人会のほうで一つ所にアーカイブしました
SASユーザー総会論文集アーカイブ
これは高橋 行雄 先生が2014年に当時の世話人会で行った,電子化・公開の取り組みを引き継いだものになります
目録のページには,全演題と,SAS社で公開されている発表スライドへのリンクをつけていってますが,確認しながら手作業でつけていっているので,まだ2000~2016年ぐらいまでです
また目録データはエクセルファイルでダウンロード可能です.
OCR由来なことと,名寄せ処理はおこなっておりませんが,簡単な分析はできると思います
エクセル取り込んでもらって
proc sgplot data=sugi;
where author_cat="筆頭筆者";
vbar year;
xaxis values=(1982 to 2024) valueattrs=(size=7);
run;
とかで 演題数の推移など
REGプロシジャにはCLASSステートメントがないため,カテゴリ変数をモデルに含める場合,事前に数値型でダミー変数を用意しないといけなかったり X*Yといった交互作用の記述をmodelステートメント内にかけないことから,例えば
data class1;
set sashelp.class;
sexn = choosen(whichc(sex,"男子","女子"),0,1);
age_height=age*height;;
run;
proc reg data=class1;
model weight = age height sexn age_height;
run;
quit;
などとして
proc hpreg data=sashelp.class;
class sex;
model weight = age height sex age*height;
run;
quit;
やっぱり モールス信号って業務で良く使いますよね.
プログラマ派遣先にいったら,全ての通信機器を取り上げられ,鉄格子窓のある部屋に閉じ込められて,SASの入ったPCだけ与えられて労働させられたみたいなあるあるの状況の際に
電気の消灯点灯タイミングで外部に助けを求めることができるかどうかが大事です
いちいち調べるの面倒なので,変換関数作ってみました
proc fcmp outlib=work.functions.common;
function text2morse (text $) $ ;
length key1 $1 text _rvalue rvalue $2000 ;
_text=upcase(compress(text));
declare dictionary d1 ;
d1["A"] = "・-";
d1["B"] = "-・・・";
d1["C"] = "-・-・";
d1["D"] = "-・・";
d1["E"] = "・";
d1["F"] = "・・-・";
d1["G"] = "--・";
d1["H"] = "・・・・";
d1["I"] = "・・";
d1["J"] = "・---";
d1["K"] = "-・-";
d1["L"] = "・-・・";
d1["M"] = "--";
d1["N"] = "-・";
d1["O"] = "---";
d1["P"] = "・--・";
d1["Q"] = "--・-";
d1["R"] = "・-・";
d1["S"] = "・・・";
d1["T"] = "-";
d1["U"] = "・・-";
d1["V"] = "・・・-";
d1["W"] = "・--";
d1["X"] = "-・・-";
d1["Y"] = "-・--";
d1["Z"] = "--・・";
do i = 1 to length(_text);
_key1=char(_text,i);
_rvalue= d1[_key1];
rvalue=catx(" ",rvalue,_rvalue);
end;
put _text= rvalue=;
return(rvalue);
endsub;
run;
data wk1;
SUBJID="AAA";AETERM="事象1"; AESTDY=1;AEENDY=5;output;
SUBJID="AAA";AETERM="事象2"; AESTDY=3;AEENDY=8;output;
SUBJID="BBB";AETERM="事象1"; AESTDY=2;AEENDY=30;output;
SUBJID="BBB";AETERM="事象2"; AESTDY=-2;AEENDY=3;output;
SUBJID="BBB";AETERM="事象3"; AESTDY=4;AEENDY=.;output;
SUBJID="CCC";AETERM="事象1"; AESTDY=15;AEENDY=20;output;
run;
proc sql noprint;
select min(min(AESTDY,1)) into:min trimmed from wk1;
select max(max(AESTDY,AEENDY)) into:max trimmed from wk1;
quit;
%put &=min;
%put &=max;
%let range =%eval( %sysfunc(range(&min,&max))+ (0<&min));
%put &=range;
data wk2;
set wk1;
array ar{&range.} $1.;
if &min < 1 then do;
st = AESTDY - &min + (AESTDY<1);
if ^missing(AEENDY) then en = AEENDY - &min + (AEENDY<1);
end;
else do;
st=AESTDY;
en=AEENDY;
end;
do i = 1 to ⦥
if st <=i <=en then ar{i}="Y";
else if st=i and missing(en) then ar{i}="Y";
else if st<=i and missing(en) then ar{i}="O";
end;
run;
%macro hed();
%do i = &min %to &max;
if &i ne 0 then ob.format_cell(data: "&i", style_attr: "background=blue color=white");
%end;
%mend;
%macro row();
%do i = 1 %to %eval(&range);
if ar&i="Y" then ob.format_cell(data: ar&i, style_attr: "background=red color=red");
else if ar&i="O" then ob.format_cell(data: ar&i, style_attr: "background=pink color=pink");
else ob.format_cell(data: ar&i, style_attr: "background=white color=white");
%end;
%mend;
ods excel options( sheet_name= "GANT" );
data _NULL_;
set wk2 end=EOF;
if _N_=1 then do;
dcl odsout ob();
ob.table_start();
*** header ;
ob.head_start();
ob.row_start();
ob.format_cell(data: "ID", style_attr: "background=darkblue color=white");
ob.format_cell(data: "Event", style_attr: "background=darkblue color=white");
ob.format_cell(data: "STDY", style_attr: "background=darkblue color=white");
ob.format_cell(data: "ENDY", style_attr: "background=darkblue color=white");
%hed();
ob.row_end();
ob.head_end();
end;
*** report ;
ob.row_start();
ob.format_cell(data: SUBJID);
ob.format_cell(data: AETERM);
ob.format_cell(data: AESTDY);
ob.format_cell(data: AEENDY);
%row();
ob.row_end();
if EOF then ob.table_end();
run;
ods excel close;
Proc OdstableはProc Reportでできる機能をほぼすべて実装可能なうえに,構文がよりシンプルであり,個人的には完全な上位互換だと思っています
ただ,RTFを作成する際の大きな弱点として,Proc ReportのbreakがOdstableにはなく任意の位置での改ページができないことがありました.
2018年にSASユーザー総会で「PROC ODSTABLEを用いた帳票作成」という論文が発表されていますが,そちらでもその点書かれていました.
8年間色々,チャレンジしてきましたが,最近また向き合う機会があり,一応実装できたので共有.
data output;
set sashelp.class;
if mod(_N_,3)=0 then break ="Y";
keep Name Age break;
run;
ods rtf ;
proc odstable data=output ;
column Name Age break ;
cellstyle break="Y" as data{PRETEXT="(*ESC*)R'\pagebb' " };
define Name; end;
define Age; end;
define break; print=no; end;
run;
ods rtf close;
なんとたったのこれだけで
まあ,医薬なら有害事象の持続とか,薬剤の継続使用を特定のカテゴリでまとめて
切れ間なく続いていた期間をまとめたレコードを合成するってことはあるのですが
これが結構SASだとやりにくい.SQLでもやれるんだけど,レコード一時的にかさむから
環境のパワーないとちょっと辛い時がある
なので結局,ハッシュオブジェクトを使ってしまう
多分,SASのFREQに癖があるせいだと思うのですが,割合の95%信頼区間のSASでの処理がどうも苦手という相談を受けましたので
少しまとめました.
まず,基本的には 1症例1レコードで,対象となるイベントが起きたか起きてないかがY/Nや1/0で変数でもっているケースを考えます
オブザベーション数=解析対象集団の例数=分母 の状況です
data wk1;
SUBJID="001";FL1="Y";FL2="N";FL3="Y";;FL4="N";output;
SUBJID="002";FL1="N";FL2="N";FL3="Y";;FL4="N";output;
SUBJID="003";FL1="Y";FL2="Y";FL3="Y";;FL4="N";output;
SUBJID="004";FL1="N";FL2="N";FL3="Y";;FL4="N";output;
SUBJID="005";FL1="Y";FL2="N";FL3="Y";;FL4="N";output;
run;
オススメなのは,まずCLASSDATA集計してしまうことです.なぜかというと,YN両方の水準がそろっていないとFREQは計算ができないからです
なので
data clds;
if 0 then set wk1(keep=FL1 FL2 FL3 FL4);
do val ="Y","N";
FL1=val;
FL2=val;
FL3=val;
FL4=val;
output;
end;
stop;
drop val;
run;
そしてこれを使って集計します
proc summary data=wk1 classdata=clds;
class FL1 FL2 FL3 FL4;
ways 1;
output out=out1;
run;
👆
各変数 N,Yとそのカウント数が_FREQ_に入ります.summaryでways 1に絞ったのは,1水準ごとの結果しか必要ないからです
そして FREQプロシジャで
ods output BinomialCLs=out2;
proc freq data=out1;
table FL1 FL2 FL3 FL4/binomial(exact level="Y");
weight _FREQ_/zeros;
run;
とします
ここで binominalの中のexactはClopperpeasonと書いてもOK.大事なのはlevel=で見たい水準を必ず指定すること.そうじゃないと今回のNのように逆側の割合をとってしまうことがあるので.
そしてzerosオプションをつけないと0の場合の計算ができずにERRORになるので注意.
ods output BinomialCLs=out2;
により結果は
ods output BinomialCLs=out2;
proc freq data=out1;
table FL1 FL2 FL3 FL4/binomial(exact level="Y");
weight _FREQ_/zeros;
output out=a binomial;
run;
とした場合,
でるっちゃでるんですが,tableステートメントの複数指定には対応していないので,最後のもの(FL4)しかでていません.
なので,対象変数が1つの場合のみですね.そうでなければ ods outputがいいと思ってます
あとは適当に成形して
data result1;
length out1 - out3 $200.;
set out2;
out1 = strip(scan(Table,2,":"));
out2 = put( round(proportion * 100,0.1), 8.1 -L);
out3_1 = put( round(LowerCL * 100,0.1),8.1 -L);
out3_2 = put( round(UpperCL * 100,0.1), 8.1 -L);
out3 = cats("[",catx(", ",out3_1,out3_2),"]");
keep out1 out2 out3;
run;
みたいな.
で,次はADAEみたいに,全症例のYNではなくて,発生したイベントのみが積みあがってるデータについて考えてみます
data wk2;
length SUBJID AETERM $20. ;
SUBJID="001";AETERM="頭痛";output;
SUBJID="001";AETERM="腹痛";output;
SUBJID="002";AETERM="倦怠感";output;
SUBJID="002";AETERM="腹痛";output;
SUBJID="002";AETERM="頭痛";output;
SUBJID="003";AETERM="倦怠感";output;
SUBJID="003";AETERM="咳嗽";output;
SUBJID="003";AETERM="頭痛";output;
SUBJID="003";AETERM="腹痛";output;
SUBJID="004";AETERM="頭痛";output;
SUBJID="005";AETERM="頭痛";output;
run;
この場合,解析対象集団の全症例にレコードが起きてるわけではないので,別途,解析対象集団の例数(分母になるN)
を求めておき,マクロ変数等に入れておきます
/*解析対象集団のNが5の場合*/
%let Big_N=5;
で,集計したい軸で症例内の重複を除去して
proc sort data=wk2 out=_wk2 nodupkey;
by SUBJID AETERM;
run;
それを集計
proc summary data=_wk2 nway;
class AETERM;
output out=out3;
run;
基本,Clopper-Pearsonの信頼区間の計算式はF分布使うのですが,F分布から変形して求められるベータ分布使うと計算式がグッと短くなるので
そっち使うのが一般的と思います
data result2;
set out3;
if 0 < &Big_N then per = round(divide(_FREQ_ , &Big_N) * 100,0.1);
if per=0 then CI_LOW=0;
if per=100 then CI_HIGH=100;
if per ne 0 then CI_LOW=round((1-betainv(.975,(&Big_N - _FREQ_+1),_FREQ_))*100, 0.1);
if per ne 100 then CI_HIGH=round((1-betainv(.025,(&Big_N - _FREQ_),_FREQ_+1))*100,0.1);
drop _TYPE_;
run;
F分布で計算すると
data result3;
set out3;
if 0 < &Big_N then per = round(divide(_FREQ_ , &Big_N) * 100,0.1);
if per=0 then CI_LOW=0;
if per=100 then CI_HIGH=100;
if per ne 0 then CI_LOW = round( (1+ (&Big_n - _FREQ_+1) / (_FREQ_ *finv(0.025, 2*_FREQ_, 2*(&Big_n-_FREQ_+1))))**-1*100,0.1);
if per ne 100 then CI_HIGH = round((1 + (&Big_n - _FREQ_) / (( _FREQ_ +1)*finv(0.975, 2*(_FREQ_ + 1), 2*(&Big_n - _FREQ_))))**-1*100,0.1);
drop _TYPE_;
run;
まずSASの公式に乗ってる例から
y = 1 / √x
でy=20となるxの値を求めてみます
グラフにすると
UpSet-Plotというプロットをよくみかけます.アイテムの組み合わせ集計を,1個での集計,2個のアイテムセットでの集計,3個でのアイテムセットの集計...... n個のアイテムセットの集計 って,バスケット分析のような集計をしたときにそれをグラフ化します.
星とり表の部分がアイテムセットを示して,上のヒストグラムはそのカウント数です.
ここで,たとえば以下のグラフはある仮想の疾患に罹患した際に,患者に発現した症状のプロットです.Aさんが発熱と倦怠感の症状があったい場合,「発熱&倦怠感」のアイテムセットとして集計されます
では全体として,「咳嗽」を少なくとも1症状として持つ人数は??となった場合は横に延びてるヒストグラムがそれにあたるという仕組みです
SASコードは以下です(冒頭のグラフ描いたやつです).一応,データが変わっても,ID , item, itemnameをあわせてもらえれば可変で動くとは思いますが,多少調整いるかもです.
もうちょっと,洗練されてどこかで出そうと思いますが,第一ドラフトということで.
barchartparamで上側のヒストグラム,scatterとhighlowで星取表部分,横のヒストグラムはhighlowで描いてます.
/*テストデータ*/
data test;
call streaminit(123);
do ID = 1 to 100;
iend=rand("integer",1,3);
do i = 1 to iend;
item = rand("integer",1,5);
itemname = choosec(item,"鼻汁","倦怠感","咽頭痛","咳嗽","発熱");
output;
end;
end;
drop i iend;
run;
/*アイテム数*/
proc sql noprint;
select count(distinct item) into: item_n trimmed from test;
quit;
%put &=item_n;
/*アイテム*/
proc sort data=test(keep=item itemname) out=itemlist nodupkey;
by item;
run;
/*アイテムformat*/
data itemformat;
set itemlist;
FMTNAME="itemformat";
START=item;
LABEL=itemname;
run;
proc format lib=work cntlin=itemformat;
run;
/*個人内での重複除去*/
proc sort data=test out=test1 nodupkey;
by ID item;
run;
/*アイテム単純集計*/
proc summary data=test1 nway ;
class item itemname;
output out=_single_count(drop=_TYPE_ rename=(_FREQ_=single_count) );
run;
data single_count;
set _single_count;
single_start=0;
run;
/*個人内での組み合わせ生成のため転置*/
proc transpose data=test1 out=out1 prefix=item_;
by ID;
id item;
var item;
run;
/*組み合わせ生成*/
data out2;
length comb $200.;
set out1;
array ar item_:;
do over ar;
if ^missing(ar) then comb=catx("/",comb,ar);
end;
count=1;
run;
/*組み合わせ集計*/
proc summary data=out2 nway missing;
class comb: item_:;
output out=_comb_count(drop=_TYPE_ rename=(_FREQ_=comb_count) );
run;
proc sort data=_comb_count;
by descending comb_count;
run;
/*組み合わせ数と最大カウント*/
proc sql noprint;
select count(*) into:comb_n from comb_count;
select max(comb_count) into:comb_max from comb_count;
quit;
/*星取り表のつなぎ線用に最小・最大値*/
data comb_count;
set _comb_count;
array dummy_{&item_n};
do i = 1 to &item_n;
dummy_{i}=i;
end;
min = min(of item_:);
max = max(of item_:);
x = _N_;
drop i;
run;
/*グラフ用データ完成*/
data graph_data;
set comb_count single_count;
run;
/*星取表*/
%macro scatterplot();
%do i = 1 %to &item_n;
scatterplot x=x y=item_&i /yaxis=y2 markerattrs=(size=12 symbol=circlefilled);
scatterplot x=x y=dummy_&i /yaxis=y2 datatransparency=0.9 markerattrs=(size=12 symbol=circlefilled);
%end;
%mend;
proc template ;
define statgraph upset;
begingraph ;
/*2*2レイアウト*/
layout lattice/ rowdatarange = union
columnweights = (0.25 0.75)
rowweights = (0.6 0.4)
columns = 2 rows = 2
pad=(top=0 bottom=0 left=0 right=0)
;
/*余白 */
layout overlay / pad=(top=0 bottom=0 left=0 right=0)
outerpad=(top=0 bottom=0 left=0 right=0)
xaxisopts = (display =none);
endlayout;
/*ヒストグラム(barchartparm)*/
layout overlay /pad=(top=0 bottom=0 left=0 right=0)
outerpad=(top=0 bottom=0 left=0 right=0)
walldisplay=none
xaxisopts = (display =none linearopts = (viewmin=1 viewmax=&item_n tickvaluesequence = (start=1 end=&item_n increment=1)))
yaxisopts = (display= (line tickvalues) linearopts = (tickvaluesequence = (start=0 end=&comb_max. increment=5))) ;
barchartparm category=x response=comb_count/datalabel=comb_count;
endlayout;
/*横ヒストグラム(highlowplot)*/
layout overlay /pad=(top=0 bottom=0 left=0 right=0)
outerpad=(top=0 bottom=0 left=0 right=0)
walldisplay=none
xaxisopts = (display= none reverse=true)
yaxisopts = (display =none linearopts = (viewmin=1 viewmax=&item_n tickvaluesequence = (start=1 end=&item_n increment=1)))
y2axisopts = (display =(tickvalues) tickvalueattrs=(size=9) linearopts = (viewmin=1 viewmax=&item_n tickvaluesequence = (start=1 end=&item_n increment=1)));
highlowplot y=item high=single_count low=single_start
/yaxis=y2 group=item type=bar barwidth=0.5 fillattrs=(color=VLIGB) outlineattrs=(color=VLIGB)
highlabel=single_count labelattrs=(color=black)
;
endlayout;
/*星取表(scatterplot とhighlowplot)*/
layout overlay /pad=(top=0 bottom=0 left=0 right=0)
outerpad=(top=0 bottom=0 left=0 right=0)
xaxisopts = (display =none linearopts = (tickvaluesequence = (start=1 end=&comb_n. increment=1)))
yaxisopts = (display= (line ) linearopts = (viewmin=1 viewmax=&item_n ))
y2axisopts = (display=none linearopts = (viewmin=1 viewmax=&item_n ))
walldisplay=none;
%scatterplot();
highlowplot x=x low=min high=max/yaxis=y2;
endlayout;
endlayout;
endgraph;
end;
run;
* グラフ作成実行 ;
proc sgrender data=graph_data template=upset;
format item itemformat.;
run;
proc fcmp;
array mat1[3,3] 2 2 3 2 4 2 3 2 6;
array mat2[3,3] / nosym;
call chol(mat1, mat2, 0);
rc = write_array("MAT1", mat1 ) ;
rc = write_array("MAT2", mat2 ) ;
run;
proc print data=MAT1;
run;
proc print data=MAT2;
run;
proc fcmp;
array mat1[2,2] (2 1 3 4);
call det(mat1, result);
put result=;
quit;
result = 5
proc fcmp;
array mat1[3,2] (3,5,0,9,1,2);
array trans_mat1[2,3] /nosym;
/*call transposeでmat1の転置行列をtrans_mat1に格納 */
call transpose(mat1,trans_mat1);
rc = write_array("MAT1", mat1 ) ;
rc = write_array("TRANS_MAT1", trans_mat1 ) ;
quit;
proc print data=MAT1;
run;
proc print data=TRANS_MAT1;
run;
proc fcmp;
array mat1[3,3] (3,5,0,9,1,2,8,7,9);
array inv_mat1[3,3] /nosym;
array result[3,3] /nosym;
/*call invでmat1の逆行列をinv_mat1に格納 */
call inv(mat1,inv_mat1);
/*mat1と逆行列の行列積をresultに格納*/
call mult(mat1, inv_mat1, result);
rc = write_array("MAT1", mat1 ) ;
rc = write_array("INV_MAT1", inv_mat1 ) ;
rc = write_array("RESULT", result ) ;
quit;
proc print data=MAT1;
run;
proc print data=INV_MAT1;
run;
proc print data=RESULT;
run;