>100 Views
January 27, 26
スライド概要
SAS言語を中心として,解析業務担当者・プログラマなのコミュニティを活性化したいです
ARD(Analysis Result Data)を SASで表にしてみた ~{tfrmt}パッケージに触発されて~ マルホ株式会社 データサイエンス部 統計解析グループ 山野辺浩己 第12回大阪SAS勉強会 2026/1/28
免責事項 本発表における意見や見解は発表者個人に属するものであり、所属する組織・ 企業を代表するものではありません。 本資料は発表者の認識に基づいた経験の情報共有のみを目的としており、その 正確性や完全性を保証するものではありません。 発表者の勉強不足による誤解、 また誤記誤謬が含まれている可能性があります。本資料に含まれる情報に基づ いてなされた判断により発生した如何なる損害・トラブルについても、発表者 は一切の責任を負いかねます。
agenda 現状と課題 ARD(Analysis Result Data)と取り巻く環境 プログラム実装 今後の展望
agenda 現状と課題 ARD(Analysis Result Data)と取り巻く環境 プログラム実装 今後の展望
背景と課題 現状のSASによる帳票作成の課題 「TFL作成」の属人化と複雑化 解析用データセット(ADaM)作成までは標準化されている。 しかし、最終的な帳票(Table)作成は Proc Report 内での複雑なComputeブロック や、データステップでの事前の文字結合に依存しがち。 レイアウト変更への弱さ 「小数点以下の桁数を変えたい」「行の順番を変えたい」だけでプログラムの大幅 な改修が必要になる。 QC(品質管理)の難しさ 出力されたPDF/RTFを目視確認する必要があり、自動化の妨げになっている。
といっても・・・ 試験データ(SDTM)や解析データ(ADaM)、解析結果メタデータ(ARM)のように解 析結果の標準化は浸透していない 何が必要か? CTDやpublication、追加解析、当局照会事項時 では、同じ解析でも人の手で体裁が修正される こともある 解析結果(Analysis Result)において、 値(Data)と体裁(Table)の分離 →ARDを経由すれば解決 Analysis Results Standard Workshop (2023 US INTERCHANGE) https://www.cdisc.org/sites/default/files/2023-10/ars_workshop_us_interchange_2023-10-18.pdf
agenda 現状と課題 ARD(Analysis Result Data)と取り巻く環境 プログラム実装 今後の展望
Analysis Result Data (ARD) とは? ARDの定義と特徴 CDISCのAnalysis Results Standard(ARS)の中で記載 「解析結果を保持する構造化データ」 統計量(平均値、標準偏差、p値など)を1レコード=1値の形式で保持。 レイアウト情報(フォント、インデント、改ページ)を含まない。 機械可読性が高く、二次利用(グラフ作成、他の帳票への転用)が容易。 従来の解析の流れ vs ARDを介した解析の流れ 従来: ADaM → Summary+Proc Report → 帳票 (PDF/RTF) [解析ロジック]と[帳票レイアウト定義]が1つの概念。 ARD: ADaM → ARD →Table化プログラム→ 帳票 [解析ロジック]と[帳票レイアウト定義]の分離。 ARD: 解析結果のみを保持する中間データセット。 帳票作成はARDをソースデータとする「フォーマット工程」として分離。 https://www.cdisc.org/
Rパッケージ {tfrmt}
{tfrmt} (Table Format) とは
ARDに表示フォーマット情報を与えて、テーブル化するRパッケージ
GSK社などが開発に関わるRパッケージ。
COSA(CDISC Open Source Alliance)に認証済み
コンセプト: "Separation of Data and Display"(データと表示の分離)
ARD (Analysis Result Data):
帳票に必要な数値情報を「構造化されたデータ」として保持。
レイアウト情報はJSONやメタデータとして別途定義。
Rでは{cards}, {cardx}等が作成に対応
利点:
数値の計算と、見た目の整形を完全に切り離せる。
https://gsk-biostatistics.github.io/tfrmt/
つまり ARD https://insightsengineering.github.io/cards/latest-tag/ https://www.cdisc.org/ https://gsk-biostatistics.github.io/tfrmt/articles/examples.html
ARDにおける課題 ARSで定義されているのは、ラベルと結果、またGroupingや結果値データの縦 積みの考え方で、細部の定義や実装例は記載されていない。 そのため、Null値などが定義されておらず、ARSのみでARDが業界横断的に標準 化されたとは言えない状況 Rにおいてはgtsummaryで作成したARDと、cardsで作成したARDで細かい値の 取り扱いが異なるため、同一の解析に対するARDの内容が異なるという課題 https://wiki.cdisc.org/pages/viewpage.action?pageId=222298985
agenda 現状と課題 ARD(Analysis Result Data)と取り巻く環境 プログラム実装 今後の展望
プログラム実行 library(cards) library(dplyr) データ # 1. Big N (全体N) df_big_n <- ADSL |> group_by(ARM) |> ard_total_n() # 2. 年齢 df_summary <- ADSL |> group_by(ARM) |> ard_continuous(variables = AGE) # 3. 性別 df_categorical <- ADSL |> group_by(ARM) |> ard_categorical(variables = SEX) # データを結合 df_combined <- bind_ard(df_big_n, df_summary, df_categorical) (上記を元に一部SAS用に加工したもの)
プログラム実行 {cards}でARDを作成した例 sort情報やNullの情報が 値として保持されていない Parameterの指定は{cards}と{tfrmt} でも差異があり、 {tfrmt}ではshuffle_card関数を準備 し、big Nをやラベル階層を成形し てからTable化する流れを作ってい る
プログラム実行 {tfrmt}は縦型のデータを、 Paramterで出力形式を当てるだけで 出力が可能 tfmrt Efficacy https://gsk-biostatistics.github.io/tfrmt/articles/examples.html
プログラム実行 {tfrmt}は縦型のデータを、 Paramterで出力形式を当てるだけで 出力が可能とする仕様に合わせる “|”→改行 “{method}”→値に置き換え ARDから出力前データセットを作成 RTF出力は汎用マクロで機械的出力
agenda 現状と課題 ARD(Analysis Result Data)と取り巻く環境 プログラム実装 今後の展望
今後の展望と課題 Cardsの事例では、Order情報、出力形式情報が値として格納されずに、 functionやListの形式でデータフレームに格納されている。 R内でも、パッケージによってARDの形式が異なる。 Rではメガファーマによる社内標準がOpen source化の傾向。 ARD+帳票化において、Rの発展・進化に対し、SASは言語及びデータセット構 造の違いから独自に発展させる必要がある。 SASでARD作成とメタデータ管 理を行うパッケージもある (いずれも森岡 裕さん)
附録
********************;
** import ;
********************;
%macro imp_csv(loc=, fil=, out=);
%****************;
%** setting
;
%****************;
%** Setting path;
%let l_CSVLOC =&loc.;
%let l_CSVNAM1=&fil.;
%let l_CSVOUT1=&out.;
%****************;
%** processing;
%****************;
%** import1;
filename csv_file "&l_CSVLOC.\&l_CSVNAM1.";
data __temp1;
infile csv_file dlm="," dsd truncover lrecl=1000 firstobs=1;
length var1-var50 $200.;
input var1-var50;
run;
filename csv_file;
data _null_;
set __temp1(obs=1);
length varnam $600.;
array a_var var1-var50;
do over a_var;
varnam=catx(" ", varnam, a_var);
end;
call symputx("l_varnam",varnam,"L" );
run;
%put &=l_varnam.;
data __temp2;
length &l_varnam. $200.;
set __temp1(firstobs=2);
array a_var var1-var50;
array a_out &l_varnam.:;
do over a_out;
a_out=a_var;
end;
keep &l_varnam.;
run;
%** output;
data &l_CSVOUT1.;
set __temp2
indsname=indsname;
in=upcase(scan(indsname,2,"."));
run;
%** delete temporary dataset;
proc del ete library=work data=__temp1-__temp2;
run;
%mend imp_csv;
%imp_csv(loc=C :\Users\3901\Desktop\phuse_jp_test-main,
fil=test_output.csv, out=dt1);
******* ************ *;
** correct;
******* ************ *;
data dt2;
set dt 1;
** 元データの修正;
if context eq "cat egorical" and st at _name eq "n" then st at _name="sn";
if context eq "cat egorical" and st at _name eq "N" t hen st at _name="BN";
if context eq "total_n" and st at _name eq "N" t hen st at _name="BN";
if context eq "cont inuous" and st at _name eq "N" t hen st at _name="sn";
if fmt_fun eq "FUNCTION_DATA" then fmt_fun="1";
** Param;
if lag(var iable)
ne variab le
then _n_variable+1;
** ARM;
if lag(var iable) ne variab le then _n_group1_level=0;
if lag(group1_level) ne group1_level then _n_group1_level+1;
** Cate;
if lag(var iable)
ne variable
t hen _n_variable_level=0;
if lag(group1_level) ne group1_level then _n_variable_level=0;
if lag(variable_level) ne variable_level then _n_var iable_level+1;
** stat ;
if lag(var iable)
ne variable
t hen _n_stat _name=0;
if lag(group1_level) ne group1_level then _n_stat _name=0;
if lag(variable_level) ne variable_level then _n_stat _name=0;
_n_stat _name+1;
run;
******* ************ *;
** Display Value;
******* ************ *;
proc sort data=dt 2;
by _n_variable variable cont ext _n_variable_level variable_level _n_stat _name;
run;
proc transpose dat a=d t2 out =dt3_1 prefix=st at _val;
by _n_variable variable cont ext _n_variable_level variable_level _n_stat _name;
id _n_group1_level;
var stat;
run;
proc transpose dat a=d t2 out =dt3_2 prefix=st at _label;
by _n_variable variable cont ext _n_variable_level variable_level _n_stat _name;
id _n_group1_level;
var st at _label;
run;
proc transpose dat a=d t2 out =dt3_3 prefix=st at _name;
by _n_variable variable cont ext _n_variable_level variable_level _n_stat _name;
id _n_group1_level;
var st at _name ;
run;
proc transpose dat a=d t2 out =dt3_4 prefix=fmt_fun;
by _n_variable variable cont ext _n_variable_level variable_level _n_stat _name;
id _n_group1_level;
var fmt_fun;
run;
data dt4;
merge dt 3_1-dt3_4
;
by _n_variable variable cont ext _n_variable_level variable_level _n_stat _name;
run;
data dt5;
set dt 4;
by _n_variable variable cont ext _n_variable_level variable_level _n_stat _name;
length
out3-out10 $200.;
array a_stat _val st at _val:;
array a_stat _label st at _label:;
array a_stat _name st at _name:;
array a_fmt_fun fmt_fun:;
array a_out
out3-out10;
do over a_stat _val;
if ^missing(a_stat _val) then do;
rnd=inp ut(cats("1e-",a_fmt _fun), best .);
fmt=cats("16.",a_fmt _fun, " -L");
a_out=putn(round(input(a_stat _val,best.),rnd), fmt);
end;
end;
run;
proc transpose dat a=d t5 out =dt6;
by _n_variable variable cont ext _n_variable_level variable_level;
id stat_name1;
idlabel st at _label1;
var out :;
run;
********************;
** D isplay C olumn;
********************;
** big N;
data _null_;
length var lbl $200.;
set dt6;
where CONTEXT eq "total_n" ;
tfrmt="N={bn}";
cnt=count(tfrmt,"{" );
var=tfrmt;
lbl=tfrmt;
do i=1 to cnt;
_tfrmt=substr(tfrmt,index(tfrmt,"{" ));
method=scan(kscan(compress(_tfrmt,"()| "),i,"{" ),1,"}");
_var=vvaluex(method);
_lbl=vl abelx(method);
if ^missing(_var) then do;
outx=tranwrd(var,cats("{" ,scan(method,1,"}")," }" ),strip(_var));
call symputx(cats("g_",_name_),outx,"G");
end;
end;
run;
** continuous;
data dt7_cont;
length var lbl $200.;
set dt6;
where CONTEXT eq "continuous" ;
tfrmt="{sn}|{mean} ({sd})|{min},{max}";
cnt=count(tfrmt,"{" );
var=tfrmt;
lbl=tfrmt;
do i=1 to cnt;
method=scan(kscan(compress(tfrmt,"()| "),i,"{" ),1,"}");
_var=vvaluex(method);
_lbl=vl abelx(method);
var=tranwrd(var,cats("{" ,scan(method,1,"}")," }" ),strip(_var));
lbl=tranwrd(lbl,cats("{" ,scan(method,1,"}")," }" ),strip(_lbl));
end;
do k=1 to count(tfrmt,"|")+1;
_n_variable_level=k;
out1=cats(VARIABLE);
out2=scan(lbl,k,"|");
outx=scan(var,k,"|");
output;
end;
run;
** categorical;
data dt7_cate;
length var lbl $200.;
set dt6;
where CONTEXT eq "categorical" ;
tfrmt="{sn} ({p})";
cnt=count(tfrmt,"{" );
var=tfrmt;
lbl=tfrmt;
do i=1 to cnt;
method=scan(kscan(compress(tfrmt,"()| "),i,"{" ),1,"}");
_var=vvaluex(method);
_lbl=vl abelx(method);
var=tranwrd(var,cats("{" ,scan(method,1,"}")," }" ),strip(_var));
lbl=tranwrd(lbl,cats("{" ,scan(method,1,"}")," }" ),strip(_lbl));
end;
do k=1 to count(tfrmt,"|")+1;
out1=cats(VARIABLE);
**out2=scan(lbl,k,"|");
out2=cats(variable_level);
outx=scan(var,k,"|");
output;
end;
run;
data dt8;
format _n_variable variable context _n_variable_level variable_level out1 out2;
set dt7_cont dt7_cate;
run;
proc sort data=dt8;
by _n_variable variable context _n_variable_level variable_level out1 out2;
run;
proc transpose data=dt8 out=dt9;
by _n_variable variable context _n_variable_level variable_level out1 out2;
id _name_;
var outx;
run;
********************;
** Table Header;
********************;
data dt10;
set dt9;
tes="aa";
label out1="";
label out2="";
label out3="&g_out3.";
label out4="&g_out4.";
label out5="&g_out5.";
keep out1-out5;
run;
** SASPAC;
filename packages "C:\temp\SAS_PACKAGES\packages";
%include packages(SPFinit);
%installPackage(rtfCreator,mirror=3)
%loadPackage(rtfCreator)
%rtfCreator(DS=dt10
,COLNUM =5
,VARLST =out1 out2 out3 out4 out5
,JUSTLST =Left Left Center Center Center
,WIDTHLST=300 150 150 150 150
,TBLHEAD=%str(Table Head)
,TBLFOOT=%str(Table Foot)
);
dm "vt &SYSLAST." vt;
参考 Cards https://insightsengineering.github.io/cards/latest-tag/index.html tfrmt https://gsk-biostatistics.github.io/tfrmt/index.html Analysis Results Standard https://www.cdisc.org/standards/foundational/analysis-results-standard/analysis-results-standard-v1-0 PharmaForest https://github.com/PharmaForest/
以上 連絡先: [email protected]