数字と文字をまとめて,見せかけ上,型混合ARRAY配列っぽく処理してしまう

 いいのか悪いのかわからないのですが,SASのARRAYは文字型変数と数値型変数をまぜて作れないので,少し不便なことがあります.
まず 処理したい変数をくくって,そこから文字変数はこう処理,数値変数はこうってしたいことってあると思うんですよね.ってことで,それっぽいマクロを作りました

%macro x_array(list=,

                       ch_code=,

                       num_code=,

                       array_no=

                       );

 %let list=%sysfunc(upcase(%sysfunc(compbl(&list))));

 %let qlist  = %sysfunc( tranwrd( %str("&list") , %str( ) , %str(",") ) );

 %let itemnum = %sysfunc( count( &list, %str ( ) ));

_banpei_num=.;

_banpei_char="";

array arch&array_no. _character_;

array arnum&array_no. _numeric_;

do over arch;

 if upcase(vname(arch&array_no.)) in (&qlist) then do;

  arch&array_no.=arch&array_no.;

    &ch_code;

 end;

end;

do over arnum&array_no.;

 if upcase(vname(arnum&array_no.)) in (&qlist) then do;

  arnum&array_no.=arnum&array_no.;

&num_code;

 end;

end;

drop _banpei_num _banpei_char;

%mend;


例えば以下のようなテストデータ
data test;
length C1 $20. N1 8. C2 $20. N2 8. C3 $20. N3 8. Z1-Z3 $20.;
C1="A";
N1=1;
C2="B";
N2=2;
C3="C";
N3=3;
Z1="1";
Z2="2";
Z3="3";
output;
run;










C1-C3に文字でABC
N1-3は数値で1-3入れて
Z1ーZ3には文字で1-3入れてます

ここで適当に

ここで,

① C1,C3,N1で 型混合配列1として定義し,配列内の文字型要素を---に数字型要素を1000倍

② C2,N2で 型混合配列2として定義し,配列内の文字型要素を3倍繰り返しに数字型要素を正負反転処理

③ Z1 Z2 Z3を型混合配列3として定義し,文字で入ってるけど,すべて数値に変えて,合計値をZSUMとして求める

とかって 処理をしたい時にさっきのマクロで

data out;

set test;

%x_array(list=C1 C3 N1

              ,ch_code=%str(arch='---')

              ,num_code=%str(arnum = arnum*1000 )

             );


%x_array(list=C2  N2

  ,array_no=1

              ,ch_code=%str(arch1=compress(repeat(arch1,2)))

              ,num_code=%str(arnum1 = arnum1*-1 )

             );


array NZ NZ1-NZ99;

%x_array(list=Z1 Z2 Z3

  ,array_no=2

              ,ch_code=%str(

NZ=input(arch2,best.);

)

             );

ZSUM=sum(of NZ:);

put ZSUM=;

drop NZ:;

run;

こんな風に書けば







まあ,できちゃうよと.


別途,C: みたいにコロンモディファイアでリスト指定したいとか
N1-N3とかで連番指定 C1--Z1みたいな位置していで型混合配列にしたい場合

事前に変数リストを作るマクロも作りました


%macro varlist_modi(ds=, modi=,listno=1);
ods output Position=Position;
proc contents data=&ds varnum ;
run;
ods output close;
proc transpose data=Position out=_Position;
 var num;
 id Variable;
run;
options dkricond=nowarn;
data _Position;
set _Position(drop=_label_ _name_);
run;
data _null;
set _Position(drop=_label_ _name_);
length _namelist $1000.;
array ar &modi.;
do over ar;
 _namelist=catx(' ',_namelist,upcase(vname(ar)));
end;
call symputx("namelist&listno.",_namelist,'G');
run;
%put NOTE: [namelist&listno.] created: &&namelist&listno.;
options dkricond=error;
%mend;


%varlist_modi(listno=1,ds=test,modi=C:);
とすれば
&namelist1 というマクロ変数の中に C1 C2 C3という データに基づいて生成された
リストができるので,それを先のマクロで指定してくださいってことですね

ただ,良し悪しかなと思うのは
SASは2つしか型がなくて,それはSASの根幹の部分なので,それを見せかけ上マスクしてしまうのは,作業的には便利なんですが,プログラム的にはどうなのかなと思わなくもないですね.
明示的に数値は数値の処理,文字は文字の処理って書いたほうが綺麗な気もします


YAML形式のファイルをSASから出力する

医薬の話,Analysis Results StandardってCDISCの標準の一つにYAMLっていうファイル形式がめっちゃ出てくる.

しかしSASにはProc JSONのようにYAMLファイルを簡単に作ってくれる仕組みがない.構造としてYAMLはJSONの上位互換のようなので,JSONで出してYAMLに変換してもいいけど,まあ普通にSASからダイレクトに作れた方がハッピーでしょうということで,簡単に作ってみた.

要はproc JSONは proc streamにwriteステートメントやexportステートメントといった部品が組み込まれたようなものなので,proc streamに,複数のデータセットをソースとすることができ,その内容を指定構造 として流し込めるパーツマクロがあれば,似たようなものができると考えた

だいたいこういうのやろうって時は,類似のアイデアが過去のPharmaSUGかPhuseかglobal forumにあがってるものだけど,今回は珍しく全くなかった気がする

まだ開発途中で,今はとりあえず,ネスト構造どうやって実装してやろうかなって思ったりしてる

ただ単純なものなら 多分できたっぽいので公開.たぶん現在進行形で似たようなことやらされて困ってる人が業界にいるんじゃないかなぁとか思って一助になれば幸い

とりあえず,任意データセットから,マッピング・シーケンス・マッピングのシーケンスを任意の階層(インデント)で,だせるようにしました.データセットからの絞りもいれたので1obsでマッピングすればスカラーにもなります(スカラーなら手でうてばいいけど,動的にだしたければ可能よってことで)


一応,サンプルでつくったYAMLが正しく認識されて,JSONならこうなるよっていうのは変換サイト(https://codebeautify.org/yaml-to-json-xml-csv)で試して見ました














以下 マクロと 実行サンプル.まだARSの各種公開されてるサンプルほどの構造を全部楽につくれるほどではないけど,ネストの問題さえどうにかすれば,まーSASから 比較的直観的に設計できるんじゃないかなぁとか


/*======================

macroname: ymlexport

author: sasyama

version:0.1 2024-05-27


説明: proc JSONにおけるexportステートメントのノリで使ってください

varlist=に半スペで列記した変数にたいして、index=で指定した階層(1につき半スペ2に変換))さげて出力

cat=で指定できる構造の種類は以下の3つ


[1] mapping

  Name: アルフレッド

  Age: 14


[2] sequence

  - アルフレッド

  - 14


[3] mappingsequence

  - Name: アルフレッド

    Age: 14


さらに構造の下に、マッピングやシーケンスを持った、深い構造の実装については現在検討中


wh=には%nrbquote()を配置

マクロ変数のスコープは未整備。グローバルで、MVAR_i_j MVARNAME_i_j (iはobs分 jは変数分できる) yobs nw__  

----------------------------------

Copyright (c) [2024] [sasyama]

This software is released under the MIT License.

http://opensource.org/licenses/mit-license.php

========================*/

%let nw=__ NEWLINE;

%macro ymlexport(ds=,wh= ,cat=mapping,varlist=,indent=1);

%local yobs;

%let varlist=%sysfunc(compbl(&varlist));

%let varnum = %sysfunc( count( &varlist, %str ( ) ))+1;

%do i = 1 %to &varnum+1;

 %let var&i =  %sysfunc(scan( &varlist,&i, %str ( ) ));

 %put &&var&i;

%end;

%macro dloop;

  %do i =1 %to &varnum;

  call symput(cats("mvarname&i._",_N_),ksubstr(cat(repeat(" ",&indent * 2),vname(&&var&i)),2));

  call symputx(cats("mvar&i._",_N_),&&var&i);

  call symput(cats("mvarseq&i._",_N_),ksubstr(cat(repeat(" ",&indent * 2),"- ",&&var&i),2));

  %if &i=1 %then %do;

call symput(cats("mvarname_mapseq&i._",_N_),ksubstr(cat(repeat(" ",&indent * 2),"- ",vname(&&var&i)),2));

  %end;

  %else %do;

call symput(cats("mvarname_mapseq&i._",_N_),ksubstr(cat(repeat(" ",&indent * 2 + 2),vname(&&var&i)),2));

  %end;

  %end;

%mend;

%let _rc = %sysfunc(dosubl(%str(

data tmp;

 set &ds;

 where &wh;

run;

data _NULL_;

  if 0 then set tmp nobs=NOBS;

  call symputx("yobs", NOBS);

  stop;

run;

data _null_;

 set tmp;

 %dloop;

run;

)));

%do h=1 %to &yobs.;

 %do i = 1 %to &varnum;

   %if %index(%lowcase(&cat),mapping) and %index(%lowcase(&cat),sequence)  %then %do;

&&mvarname_mapseq&i._&h: &&mvar&i._&h &nw.;

  %end;

  %else %if %index(%lowcase(&cat),mapping) %then %do;

&&mvarname&i._&h: &&mvar&i._&h &nw.;

  %end;

  %else %if %index(%lowcase(&cat),sequence) %then %do;

&&mvarseq&i._&h &nw.;

  %end;

 %end;

%end;

%mend;

/*マクロ終わり*/


/*実行例*/

filename sample "フォルダパス\sample.yaml";

proc stream outfile=sample resetdelim="__";

begin

#マッピングの例 &nw.;

sashelp.class: &nw.;

%ymlexport(ds=sashelp.class

             ,wh=%nrbquote(name="アルフレッド")

             ,cat=mapping

             ,varlist=name age sex

             ,indent=1)

--- &nw.;

#シーケンスの例 &nw.;

sashelp.class: &nw.;

%ymlexport(ds=sashelp.class

             ,wh=%nrbquote(name="アルフレッド")

             ,cat=sequence

             ,varlist=name age sex

             ,indent=1)

--- &nw.;

#マッピングをシーケンスにする例 &nw.;

sashelp.class: &nw.;

%ymlexport(ds=sashelp.class

             ,wh=%nrbquote(name in ("アルフレッド","アリス"))

             ,cat=mappingsequence

             ,varlist=name age sex

             ,indent=1)

;;;;

run;


SASユーザー総会2024など

新しいネタを何もやってないのかというと,そういうわけではないのですが,どうにもブログの更新がすぐに滞ってしまいますね..
まさかの2024年初記事.そして宣伝

SASユーザー総会2024








https://sas-user2024.ywstat.jp/

昨年,会場キャパシティがギリギリになってしまったので,反省を踏まえて,会場を変えて大きくしました

なので演題大募集中です
幅広い層のユーザーの交流を活性化することを目指しており,チュートリアル的な発表,入門者・初心者向けの演題も大歓迎していますので,できる限り気兼ねなく発表していただければと思ってます.

他の宣伝

大阪SAS勉強会
https://sites.google.com/view/osakasasbenkyokai/%E3%83%9B%E3%83%BC%E3%83%A0
大阪でゆるくやってる勉強会です

Rで実践!美しいTable & Figureを作ろう
https://jea33-preseminar3.netlify.app/
こちらのSAS部分の資料を作らせていただいたり
https://jea33-preseminar3.netlify.app/sas_stata

CDISC 2024 Japan Interchange

https://www.cdisc.org/events/interchange/2024-japan-interchange/program
こっそり発表予定.SASデータセットからDataset-JSONを作る際,読み込む際のメタデータ部分のやりくりはSAS拡張属性使うといいんじゃないですか的な提案

他にもSAS芸人枠でちょいちょい色んなところに顔ださせてもらったり

散布図行列 というか複合パネルのグラフ

 SASユーザー総会2023で「SASによる散布図行列の実装」っていう素晴らしい発表があって
https://sas-user2023.ywstat.jp/download.html?n=36&key=ensdaeeadcsr

それが呼び水となって
SASブログの方で
「SASで散布図行列を作図する -layout latticeの仕様について-」
https://superman-jp.hatenablog.com/entry/SAS-GTL-scatterplotmatrix



「新しい散布図行列の作成法Ⅰ」
https://sasonediver.blog.fc2.com/blog-entry-531.html

があがって,なんなの,今はプロットマトリクス作るのがブームなの笑
ってことで,ちょっと,ついでにオマケ情報で 違う作り方を
SAS Oneブログさんの方の「新しい散布図行列の作成法Ⅰ」とおんなじで,とりまSGで作ってそれを貼り合わせてもいいんじゃない?って時に Python呼び込んでも全然ありと思うのですが,私はRWIが楽と思ってます
ほんと楽で…

もうプロットの部分は皆さんがやってるので,特に言いたいことはないので,合成法だけ
















👆これをつかって3*3のプロットマトリクスにしてみますね

options printerpath=png nodate nonumber  papersize=('15cm' ,'15cm')  ;

ods printer file='XXX\TEST.png' nogtitle dpi=500;

title;

data _NULL_;

dcl odsout ob();

  ob.layout_gridded( columns:3, rows:3 );

  ob.region();

  ob.image( file: "XXX\PharmaSUG.png" );

  ob.region();

  ob.image( file: "XXX\PharmaSUG.png" );

  ob.region();

  ob.image( file: "XXX\PharmaSUG.png" );

  ob.region();

  ob.image( file: "XXX\PharmaSUG.png" );

  ob.region();

  ob.image( file: "XXX\PharmaSUG.png" );

  ob.region();

  ob.image( file: "XXX\PharmaSUG.png" );

  ob.region();

  ob.image( file: "XXX\PharmaSUG.png" );

  ob.region();

  ob.image( file: "XXX\PharmaSUG.png" );

  ob.region();

  ob.image( file: "XXX\PharmaSUG.png" );

run;

ods printer close;




















ob.layout_gridded( columns:3, rows:3 ); 
👆ここでマトリクス構造を指定したら
あとは

  ob.region();

  ob.image( file: "XXX\PharmaSUG.png" );

で順番に画像ファイルを指定するだけ

チョー簡単

Dataset-JSONとかの話②

読み込みとかだと こんな感じか? まだまだ これから検証してかないとですが 


%macro imp_json(inpath=XXXX,ds=);

filename js "&inpath\&ds..json" encoding="utf-8";

libname in json fileref=js ;

proc copy in = in out = work ;

run ;

libname in clear ;

filename js clear;


data _null_;

set Itemgroupdata_ig_&ds.;

call symputx("name",name);

call symputx("label",label);

run;

 


data _null_;

set Ig_&ds._items end=eof;

 call symputx( cats("rename",ordinal_items), cats("element",ordinal_items)||"="|| name );

 call symputx( cats("label",ordinal_items), cats("element",ordinal_items)||"='"|| cats(label,"'") );


 if ^missing(displayFormat) then do;

  call symputx( cats("format",ordinal_items), cats("element",ordinal_items)||" "|| cats(displayFormat,"") );

 end;

 else do;

  call symputx( cats("format",ordinal_items),"");

 end;


 if eof then call symputx("last_ordinal_items",ordinal_items);

run;

options mprint;


%macro create;

data &name(label="&label" drop=ordinal_IG_&ds ordinal_itemData ITEMGROUPDATASEQ);

 set Ig_&ds._itemdata;

 rename

 %do i = 1 %to &last_ordinal_items;

  &&rename&i

 %end;

 ;

  label

 %do i = 1 %to &last_ordinal_items;

  &&label&i

 %end;

 ;

 format

 %do i = 1 %to &last_ordinal_items;

  &&format&i

 %end;

 ;



 ;

 run;

 %mend create;


 %create;


%mend imp_json; 


Dataset-JSONとかの話

医薬業界で,主に臨床試験データの標準形式のCDISCに絡んだ話. xptに代わる次世代のデータフォーマットとして提案されてる中にJSON形式のファイルもあって Dataset-JSONとかって提案されてます
https://www.cdisc.org/dataset-json
👆これのspecificationのところで 例示がされてます

例えばSASでどう作るかって話で,

見る感じ,定義情報をいろいろ持たせるので,そこはdefineから持ってくるなり,他の与え方をするなりがいるんですが,それ以前に SASでJSONファイルって作れるのって人が多いと思うので,そこだけ

data DM (label="Demographics");

attrib

STUDYID label="Study Identifier " length= $5000.

DOMAIN label="Domain Abbreviation " length= $200.

USUBJID label="Unique Subject Identifier " length= $200.

AGE label="Age " length= 8.

;

STUDYID="MyStudy";USUBJID="001";DOMAIN="DM";AGE=56;output;

STUDYID="MyStudy";USUBJID="002";DOMAIN="DM";AGE=26;output;

run;


data DM_1;

attrib ITEMGROUPDATASEQ label="Record identifier" length=8.;

 set DM;

 ITEMGROUPDATASEQ=_N_;

 run;

 proc sort data=DM_1;

  by STUDYID USUBJID;

run;

ods noresults;

ods output Position=Position;

ods output Sortedby=Sortedby;

proc contents data=DM_1 varnum ;

run;


data dataset_item;

length OID name label type displayFormat $200.;

set Position;

call missing(of displayFormat);

if Variable = "ITEMGROUPDATASEQ" then OID=Variable;

else OID = cats("IT.",Variable);

name=Variable;

if ^missing(displayFormat) then do;

displayFormat=format;

end;

if Type in ("数値") then type="integer";

else type="string";

keep OID name label type displayFormat;

run;


ここまでは適当な下準備なので,いいとして.
今回いいたいのは Proc JSONをつかって
writeステートメントで構造や値を直に作成でき
exportで任意のデータセットの内容を変換してJSONファイルの中に含めれるよって点.
以下は,とりあえず試してみただけで,精査してないのと items作る部分は,本来データセットの情報を取り出してつくるのではなく,定義情報から付与すべきのように思うのでそのまま使ったりしないでくださいね.あくまでproc JSONを紹介したいだけなので

proc json out = "XXXXX\dm.json" pretty;

 write open object ;


   write values "creationDateTime"  "2023-03-22T11:53:27";

   write values  "datasetJSONVersion"  "1.0.0";

   write values  "fileOID"  "www.sponsor.org.project123.final";

   write values  "asOfDateTime"  "2023-02-15T10:23:15";

   write values  "originator"  "Sponsor SASYAMA";

   write values  "sourceSystem"  "SAS 9.4";

   write values  "sourceSystemVersion"  "M7";



write values "clinicalData";

write open object;


  write values "studyOID"  "xxx";

  write values  "metaDataVersionOID"  "xxx";

  write values  "metaDataRef"  "https://metadata.location.org/api.link";


  write values "itemGroupData";

  write open object;


write values "IG.DM";

     write open object;

write values "records" 2 ;

write values "name" "DM" ;

write values "label" "Demographics" ;


write values "items" ;/* attribute array */

write open array;

     export dataset_item /   nosastags;

  write close;


write values "itemData" ;/* record array */

write open array;

      export DM_1 / nokeys  nosastags;

  write close;

write close;


  write close;

  write close;

 write close;

run ;






SASユーザー総会2023









 https://sas-user2023.ywstat.jp/


基本,ただの宣伝みたいな記事は出さないのがポリシーなのですが
ちょっと今回はお許しを.

今年もSASユーザー総会開催します.

そして光栄なことに 私は世話人にしていただきまして 運営にも携わらせていただきます.
何卒宜しくお願い致します