数字と文字をまとめて,見せかけ上,型混合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芸人枠でちょいちょい色んなところに顔ださせてもらったり