割合の正確な95%信頼区間(Clopper-Pearsonの信頼区間 二項分布に基づく割合の信頼区間)

 多分,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をつかわずに

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;

 






同じですね.


FCMP.SOLVEで方程式の実数解をガウス・ニュートン法で求める

 まずSASの公式に乗ってる例から

y = 1 / √x

でy=20となるxの値を求めてみます

グラフにすると



なので,まあ だいたい値は0.0023~26あたりかなぁって感じですが

proc fcmp outlib=work.common.solve ;
/*解きたい式を定義*/
   function f1(x);
     y = 1/sqrt(x);
      return(y);
   endsub;
 
/*yを指定して、xを求めて戻すルーチン*/
  subroutine solver(y,x);
   outargs x  ;
   x=solve("f1",{.}, y , .);
  endsub ;
run;

options cmplib=work.common;
data wk1;
 call missing(x);
 call solver(20,x);
run;
proc print data=wk1;
run;




👆
と,このように,あらかじめ解きたい式をFCMPで指定し,
FCMP.SOLVE関数の第一引数に 解きたい関数名,第三引数に目標値(ここでいうところの左辺,y)を指定します,
第二引数には計算のための5つのパラメータを配列で与えるのですが,{.}とするとデフォルト設定になります
最後の引数は,第一引数にとった関数に追加引数をここで与えることができます

つぎに3次方程式

を解いてみます

グラフにすると


👆どうも実数解は3つありそうです.ぱっとみ,交点は1,2,3らへん

この場合,初期値の設定によって,収束する解が異なるので,コールルーチンをちょっと改造して,パラメータを細かく指定できるようにして実行してみます

proc fcmp outlib=work.common.solve ;
/*解きたい式を定義*/
   function f1(x);
      return(x**3 -6*x**2 + 11*x -6);
   endsub;
 
/*yを指定して、xを求めて戻すルーチン*/
  subroutine solver(y,x,initial_value,absolute_criterion,relative_criterion,maximum_iterations,solve_status);

  /*設定パラメータ */
   array opts[5] initial_value absolute_criterion relative_criterion maximum_iterations solve_status;
   outargs x ,solve_status,y ;

   x=solve("f1"
               ,opts
               , 0
               , .);
   put x= solve_status=;
  endsub ;
run;
options cmplib=work.common;
data wk1;
 call missing(of x);
 absolute_criterion=1.0e-12;
 relative_criterion=1.0e-6;
 maximum_iterations=100;
 solve_status=.;
 y=0;

 initial_value=0.001;
 call solver(20,x,initial_value,absolute_criterion,relative_criterion,maximum_iterations,solve_status);
 output;

 initial_value=1.9;
 call solver(20,x,initial_value,absolute_criterion,relative_criterion,maximum_iterations,solve_status);
 output;

 initial_value=2.9;
 call solver(20,x,initial_value,absolute_criterion,relative_criterion,maximum_iterations,solve_status);
 output;

run;
proc print data=wk1;
run;

グラフから,おそらくx=1,2,3あたりに解がありそうなので
初期値(initial_value)を,0.001,1.9, 2.9として回してみると


👆
やはり x=1,2,3が解でした.solve_status=0は収束していることを示します












UpSet plot (アップセット プロット)の実装例

 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;


IMLを使わずFCMPで行列計算⑦ コレスキー分解

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;


mat2の転置行列を作って,mat2とmat2tの行列積をつくればmat1に戻ることが確認できる
つまり,コレスキー分解は 下三角行列とその転置の積に分解する操作


 

IMLを使わずFCMPで行列計算⑥ 行列式の算出

 proc fcmp;

   array mat1[2,2] (2 1 3 4);

   call det(mat1, result);

   put result=;

quit;

result = 5



2次正方行列なので
2 * 4  - 1 * 3 = 5










IMLを使わずFCMPで行列計算⑤ 単位行列の生成

proc fcmp;

  array mat1[3,3] /nosym;

   call identity(mat1);

   rc = write_array("RESULT", mat1 ) ;

quit;








 

IMLを使わずFCMPで行列計算④ 転置行列

 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;