2015年8月28日金曜日

文字列の置換をするTRANWRD vs TRANSTRN関数





両方とも文字列を置換する関数です。



構文

TRANWRD( 対象変数or文字列 , 置き換えたい文字列 , 置き換え後の文字列  )
TRANSTRN( 対象変数or文字列置き換えたい文字列 , 置き換え後の文字列  )







例1 ・・・ 文字列の置き換え
data OUT1;
   length A B1 B2 $10.;

   A = "aabb";
   B1 = tranwrd( A, "ab", "X" );
   B2 = transtrn( A, "ab", "X" );
run;

 A 
 B1 
 B2 
 aabb 
  aXb  
  aXb  



例2 ・・・ 文字列の削除
data OUT2;
   length A B1 B2 $10.;
   A = "abc";

   B1 = tranwrd( A, "b", "" );
   B2 = transtrn( A, "b", "" );
run;

 A 
 B1 
 B2 
 abc 
  a c  
  a c  

削除した「b」の部分に半角スペースが残ってしまいます。(仕様っぽいです)



例3 ・・・ 文字列の削除
data OUT2;
   length A B1 B2 $10.;
   A = "abc";

   B1 = tranwrd( A, "b", trimn("") );
   B2 = transtrn( A, "b", trimn("") );
run;

 A 
 B1 
 B2 
 abc 
  a c  
  ac  

TRANSTRN関数の方は、trimn("") とすると文字列を削除すると同時に半角スペースも詰められます。




注意
SAS9.4のマニュアルを見ると、TRANWRD、TRANSTRN、TRIMN関数は日本語などのマルチバイト文字も対応していますが、それより前のバージョンでマルチバイト対応しているかは未確認なので、ご注意ください。
(関連記事 : 関数の落とし穴




利用例


SAS社ホームページに「指定した文字列を削除する」というFAQがあります。

変数値から 「株式会社」 という文字列を削除したいというもので、
昔は、これだけの処理をするのも大変でしたが、今はTRANSTRN関数で一発解決です。
(しつこいようですが、SAS9.4での例になります。それより前のバージョンでは正しく動作するか未確認)


data out1;
   length NAME NAME2 $50.;
   NAME = "(abcd株式会社)";
   NAME2 = transtrn( NAME, "株式会社", trimn("") );
run;

 NAME
 NAME2 
 (abcd株式会社) 
  (abcd) 



2015年8月27日木曜日

SQLプロシジャのDISTINCTを使ったグループ毎のカウント。




SQLプロシジャのDISTINCTについては、「有害事象の発現例数と件数を簡単に出す。」で紹介済みですが、まだまだ魅力が伝わりきってないので再度紹介したいと思います。



サンプルデータ

data DT1;
input CUST_ID$ NAME$ FOOD$;
cards;
001 James BREAD
001 James CANDY
002 Alice BREAD
002 Alice CANDY
002 Alice CANDY
;

 CUST_ID 
 NAME 
 FOOD 
 001
 James
 BREAD 
 001
 James
 CANDY
 002
 Alice
 BREAD
 002
 Alice
 CANDY
 002
 Alice
 CANDY


サンプルは、顧客ID(CUST_ID)で管理されたお客さん(NAME)が購入した食べ物(FOOD)を記録したデータだとします。



やりたい事

このデータから、以下の集計をしたい。
↓↓食べ物(FOOD)毎の「購入者数(C1)」と「購入者の全購入数(C2)」

 FOOD 
 C1 
 C2 
 BREAD 
  
  2
 CANDY
  2
  3



こういった集計は、SQLのDISTINCTを使えば一発です。

proc sql;
  create table OUT1 as
  select  FOOD,
             count( distinct CUST_ID ) as C1,
             count( CUST_ID ) as C2
  from DT1
  group by FOOD;
quit;



簡単に解説

  • SQLのCOUNT関数: 引数が非欠損値のレコード数を求める
  • DISTINCT: 関数の中に指定すると、引数の値から重複を除いたレコードを計算対象とする


つまり、食べ物(FOOD)毎に以下を求めています。
  • count( distinct CUST_ID ): CUST_IDの値から重複を除いた非欠損値の数 = 購入者の人数
  • count( CUST_ID ): CUST_IDが非欠損値の数 = 購入者の全購入数


2015年8月26日水曜日

DICTIONARYテーブルで色々な定義情報を取得する




DICTIONARYテーブルには、色々な定義情報が入っています。
(この中身はSQLプロシジャでのみ参照する事ができます。)

SQL使えないよーって人も、DICTIONARYテーブルをビュー化したものが、ライブラリ「SASHELP」の中に入っています。


DICTIONARYテーブルの一覧 (個人的に利用頻度が高いもののみ抜粋)

DICTIONARY
SASHELP内のビュー
中身
COLUMNS
VCOLUMN
変数名と変数属性
MACROS
VMACRO
システムやユーザーが定義しているマクロ変数名
TABLES
VTABLE
データセット名やビュー名
TITLES
VTITLE
タイトルとフットノートの設定
OPTIONS
VOPTION
オプションの設定
INDEXES
VINDEX
インデックスの設定
 ・・・

DICTIONARYテーブルを直接みることは出来ませんが、SASHELP内のビューは開いて見る事が出来ます。



利用方法

* テストデータ ;
data DT1;
   length A B 8. C $20.;
run;

たとえば上記テストデータの変数名や変数属性を取得したいとします。



DICTIONARYテーブルから取得する
proc sql;
   create table OUT1 as
   select *
   from DICTIONARY.COLUMNS
   where LIBNAME="WORK" and MEMNAME="DT1";
quit;


・・・変数多いので省略



SASHELP内のビューから取得する
data OUT2;
   set SASHELP.VCOLUMN;
   where LIBNAME="WORK" and MEMNAME="DT1";
run;


2015年8月24日月曜日

一時配列(ARRAY _TEMPORARY_)はRETAIN機能をもってる


ARRAYステートメントにRETAIN機能を付与する小技」 の番外編。


ARRAYステートメントの変数名を指定する部分に「_TEMPORARY_」と指定すると、「一時データ要素」の配列を作ることが出来ます。

「一時データ要素」はデータステップ内でのみ存在し、終了したら消えます。


* テストデータ ;
data DT1;
input A;
cards;
1
.
.
2
.
;

 A  
 1 
 . 
 . 
 2 
 . 

* 一時配列の動きを確認してみる ;
data DT2;
   set DT1;
   array AR(1) _temporary_ ;
   if A^=. then AR(1) = A;
   A2=AR(1);
run;

 A  
 A2 
 1 
 1 
 . 
 1 
 . 
 
 2 
 2 
 . 
 2

一時データ要素はデータステップ終了とともに無くなってしまうので、適当にA2という変数を作って、そこに一時データ要素の値を入れて動きを確認しています。

A2を見ると、RETAINのように値が保持され続けてる事が確認できます。
この一時配列の性質を知っておくと結構役に立ちます。

Graph Template Language(GTL)入門:LAYOUTステートメント【OVERLAY】【OVERLAYEQUATED】


LAYOUTステートメントによって、PLOTを重ねたり並べたりといったレイアウトの設定が出来ます。

構文
PROC TEMPLATE ;
   DEFINE STATGRAPH テンプレート名;
         BEGINGRAPH ;
             LAYOUTステートメント  ;
                     PLOTステートメント;
             ENDLAYOUT;
         ENDGRAPH;
   END;
RUN;


適当なデータ作成

data DT1;
label NO="No." HEIGHT="身長(父親)" HEIGHT2="身長(子供)";
input NO HEIGHT HEIGHT2;
cards;
001 165.5 154.5
002 175.8 160.5
003 159.8 150.9
004 180.5 158.2
005 156.8 145.9
;


LAYOUT OVERLAY

もっともよく使用されるLAYOUTステートメント。
この中に、PLOTステートメントを沢山書いて、PLOTを重ね合わせる事が出来ます。

* グラフテンプレート作成 ;
proc template ;
  define statgraph MYGRAPH1;
      begingraph ;
          entrytitle "親子の身長";
          layout overlay  ;
              scatterplot x=HEIGHT y=HEIGHT2; * 散布図 ;
              regressionplot x=HEIGHT y=HEIGHT2 ; * 回帰直線 ;
          endlayout;
      endgraph;
  end;
run;

* グラフ作成実行 ;
proc sgrender data=DT1 template=MYGRAPH1;
run;

とりあえず最低限 「LAYOUT OVERLAY」 さえ覚えとけばいい感じです。


LAYOUT OVERLAYEQUATED

「LAYOUT OVERLAY」と同様、PLOTの重ね合わせが出来る上、
X軸の幅とY軸の高さなどを自動的に統一してくれます。

EQUOTETYPE=」オプションで、どんな感じで軸を統一するか設定でき、以下のように「SQUARE」を設定すると、X軸とY軸の「軸の増分」、「最小・最大値」、「幅と高さ」を統一して正方形のグラフにしてくれます。

* グラフテンプレート作成 ;
proc template ;
  define statgraph MYGRAPH2;
      begingraph ;
         entrytitle "親子の身長";
         layout overlayequated  / equatetype=square ;
              scatterplot x=HEIGHT y=HEIGHT2;
         endlayout;
       endgraph;
  end;
run;

* グラフ作成実行 ;
proc sgrender data=DT1 template=MYGRAPH2;
run;




その他

この他、「LAYOUT OVERLAY3D」というのもあり、3Dのグラフを作る事が出来ます。
詳細はリンクさせていただいてる 僕の頁「GTLによる3Dプロットあれこれ」 で解説をされています。


GTL入門記事一覧

2015年8月21日金曜日

SCAN関数の落とし穴




私自身がSCAN関数にはめられた出来事を紹介。
SCAN関数とは、、、まず例をご覧ください。




data DT0;
   length A  A1-A3  $10.;
   A="aa,bb,cc";

   A1 = scan( A, 1, "," );
   A2 = scan( A, 2, "," );
   A3 = scan( A, 3, "," );
run;

 A  
 A1  
 A2  
 A3  
 aa,bb,cc 
 aa
 bb
 cc

上の例では、「aa,bb,cc」という文字列をカンマ「,」で区切って分割しています。
つまり、文字列を、指定した1文字で区切った時のn個目の文字を返してくれるのがSCAN関数です。

勘違いしやすいのが、例えば区切る文字に「,:」と指定したら「,」と「:」の2つが区切り文字になります。
「,:」というひとつづきの文字が区切り文字になるという意味ではないです。



構文

 SCAN( 文字列または変数 , n個目 , "区切り文字" )

  • 日本語などのマルチバイト文字には対応していません。マルチバイト文字を扱う場合は、KSCAN関数というのが用意されています。




落とし穴

以下のプログラムの結果は想定通りでしょうか?

* テストデータ ;
data DT1;
input A :$10.;
cards;
aa,bb
aa
,aa
aa,,bb
;

* SCAN関数で分割 ;
data OUT1;
   set DT1;
   length A1-A3 $5.;

   A1 = scan(A, 1, ",");
   A2 = scan(A, 2, ",");
   A3 = scan(A, 3, ",");
run;

 A  
 A1  
 A2  
 A3  
 aa,bb 
 aa
 bb

 aa 
 aa

 ,aa 
 aa

 aa,,bb 
 aa
 bb


以下の結果を想定した方も多いと思います。
 A  
 A1  
 A2  
 A3  
 aa,bb 
 aa
 bb

 aa 
 aa

 ,aa 
 aa

 aa,,bb 
 aa
 bb


この現象は以下のルールによるものです。

  • 3行目の「,aa」のように一番先頭に区切り文字があると、先頭の区切り文字は無視される。
  • 4行目の「aa,,bb」のように区切り文字が連続している場合、区切り文字は1つだけと解釈される。



このルールを無効にしたい場合、SCAN関数に”M修飾子”というものをつけてやります。

* SCAN関数で分割 ;
data OUT2;
   set DT1;
   length A1-A3 $5.;

   A1 = scan(A, 1, "," , "M");
   A2 = scan(A, 2, "," , "M");
   A3 = scan(A, 3, "," , "M");
run;

 A  
 A1  
 A2  
 A3  
 aa,bb 
 aa
 bb

 aa 
 aa

 ,aa 
 aa

 aa,,bb 
 aa
 bb


できた!
ただし、KSCAN関数には”M修飾子”の機能がありません。
(SAS9.4M5からKSCANX関数というのが追加されて同様の構文で"M修飾子"がつかえます)



2015年8月18日火曜日

PROC REPORTでインデントをつける方法(ODS出力時)



今回は「ODS RTF」での例になります。
「ODS EXCEL」等、一部の出力ではインデントが設定できなかったので、ご注意下さい。

* サンプルデータ ;
data DT1;
input A$ FLG;
cards;
aaa .
aaa 1
aaa 1
bbb .
bbb 1
bbb 1
;

FLG 
  aaa  
  . 
  aaa  
  1  
  aaa 
  1  
  bbb  
  . 
  bbb  
  1  
  bbb 
  1  


上のデータで、変数Aのデータ部分にインデントをつけて、出力してみます。

ods rtf file="ここに出力ファイルのフルパスを指定";

   proc report data=DT1;
       column A ;
       define A   / display style(column)=[marginleft=1cm] "インデントのテスト";
   run;

ods rtf close;




また条件によって、インデントをつけたり、つけなかったりしたい場合、

たとえば、今回のサンプルデータでFLG=1の時だけ、変数Aにインデントを設定したいとします。

ods rtf file="ここに出力ファイルのフルパスを指定";

   proc report data=DT1;
       column FLG A ;
       define FLG / display noprint;
       define A     / display  "インデントのテスト";

       compute A;
          if FLG=1 then call define(_COL_,"style","style=[marginleft=1cm]");
       endcomp;
   run;

ods rtf close;



ポイントは COLUMN と COMPUTE

① まずCOLUMNステートメントでは、以下の順番で変数を指定する必要があります。

column  条件に使用する変数  対象変数 ;

今回の例では「column FLG A;」としています。
この順番を守らないと、ちゃんとインデントが設定されません。


② 次にCOMPUTEステートメントで、以下のように設定します。

compute 対象変数;
     if 条件 then call define(_COL_ , "style", "style=[marginleft=インデント幅]");
endcomp;


ちなみに、以下記事で解説しているよくあるミスにも注意!

全角スペースを取り除いてくれる関数達




半角スペースを取り除いてくれる関数達」の全角バージョン。

関数仕様
KLEFT
  • 先頭の半角・全角スペースを取り除いて左寄せ。
  • 左寄せした分、半角スペースが末尾に足される
  • 一部の環境では先頭のUnicodeスペースも取り除くようです
KTRIM
  • 末尾の半角・全角スペースを取り除く
  • 一部の環境では末尾のUnicodeスペースも取り除くようです
KCOMPRESS
  • 文字列中の半角・全角スペースを取り除く
KSTRIP
  • 先頭と末尾の半角スペースのみを取り除く
  • 勘違いしやすいので、ここに注意として記載しましたが、全角スペースは取り除けない


注意点

① (2023/1/22確認)
以前のリファレンスには記述されていなかったのですが、一部環境において、
KLEFTでは先頭の、KTRIMでは末尾の「U+000A (改行)」「U+000D (キャリッジリターン)」等の「Unicodeスペース」っていうのも取り除くようです。


② 以下の記事で触れていますが、末尾のスペースを取り除いても、その結果を変数に格納した時点で、結局末尾に半角スペースが入ってしまうんで、そこの取り扱いだけ注意。

2015年8月14日金曜日

DS2プロシジャ入門8:ユーザー定義パッケージ




メソッドをパッケージに入れる事で、その中に入れたメソッドはいつでもどこでも使えるようになります。



構文


パッケージの定義
PROC DS2;

     PACKAGE パッケージ名 / OVERWRITE=YES;

          METHOD メソッド1;
               ~メソッド処理~
          END;

          METHOD メソッド2;
               ~メソッド処理~
          END;
          ・・・
     ENDPACKAGE;

     RUN;
QUIT;




定義したパッケージを使えるようにする

 DCL PACKAGE パッケージ名  インスタンス参照名();


定義したパッケージを使用するには、パッケージのインスタンスってのを作って、そのインスタンスを参照するための変数(インスタンス参照名)を定義します。


パッケージで定義したメソッドを使う

 インスタンス参照名.メソッド();






*** パッケージの作成 ;
proc ds2;
   package MYPACK / overwrite=yes;
       method TEST (double VAR1) returns double;
          return VAR1+10;
       end;
   endpackage;
   run;
quit;

ログ
package mypack がデータセット work.mypack に作成されました。



数値を放り込むと、10足した値を返してくれるTESTというメソッドを作って、
これをMYPACKというパッケージに入れています。


パッケージは通常WORKに作成されるので、SASを終了すると消えてしまいますが、
package ライブラリ名.パッケージ名」 というように書けば、そのライブラリにパッケージが作成され、SASを終了しても残ります。


*** パッケージを使う ;
proc ds2;
   data _NULL_;
       dcl package MYPACK mp();
       method init();
           dcl double OUTVAR;
           OUTVAR = mp.TEST(100);
           put '******' OUTVAR '******';    
       end;
   enddata;
   run;
quit;

ログ
****** 110 ******



dcl package MYPACK mp()」 でパッケージMYPACKをmpというインスタンス参照名で使えるようにし、
mp.TEST(100)」 でパッケージで定義したTESTというメソッドを使っています。

FCMPプロシジャに似てる感じですね。




DS2プロシジャ入門記事

1: 基本構文
: 変数の宣言
3: 変数属性と配列の定義
4: データ型①
8: ユーザー定義パッケージ

2015年8月11日火曜日

Graph Template Language(GTL)入門:PLOTステートメント


作成したいプロットをPLOTステートメントにて設定します。
前回記事で紹介した基本構文の中にPLOTステートメントを入れます。

構文
PROC TEMPLATE ;
   DEFINE STATGRAPH テンプレート名;
         BEGINGRAPH ;
             LAYOUTステートメント  ;
                     PLOTステートメント;
             ENDLAYOUT;
         ENDGRAPH;
   END;
RUN;


PLOTステートメントの一覧  (種類欄に詳細構文のリンクを設定予定)
 種類構文 完成見本

 散布図 

 SCATTERPLOT  X=X軸変数   Y=Y軸変数   /  オプション  ;

 回帰直線 

 REGRESSIONPLOT  X=X軸変数   Y=Y軸変数   /  オプション ;

 時系列プロット 

 SERIESPLOT  X=X軸変数   Y=Y軸変数   /  オプション  ;

 棒グラフ 

 BARCHART  CATEGORY=カテゴリ変数   RESPONSE=応答変数   /  オプション  ;

  (STAT=オプションで棒グラフの統計量を指定可)
  ・RESPONSE=を指定しない場合は以下いずれか指定可
       STAT = FREQ, PCT, PROPORTION (デフォルトはFREQ)

  ・RESPONSE=を指定する場合は以下いずれか指定可
       STAT = SUM, MEAN (デフォルトはSUM)

  (ORIENT=オプションで棒の向きを変更可)
     ORIENT = HORIZONTAL ・・・ 横
     ORIENT = VERTICAL      ・・・ 縦


 階段状のプロット

 STEPPLOT  X=X軸変数   Y=Y軸変数   /  オプション  ;

 BOXPLOT  X=カテゴリ   Y=分析変数   /  オプション  ;


 ヒストグラム

 HISTOGRAM   対象変数   /  オプション  ;


 円グラフ

 PIECHART  CATEGORY=カテゴリ変数   /  オプション  ;

 (LAYOUT REGION /GRIDDED /LATTICE の中でのみ使用可能)


 参照線

 REFERENCELINE  X=数値  /  オプション  ;
   または
 REFERENCELINE  Y=数値  /  オプション  ;

 (他のPLOTステートメントと一緒に使用する)
 (「X=」「Y=」には、設定値を格納した変数を指定しても良い)


 直線(傾き指定

 LINEPARM  X=数値  Y=数値  SLOPE=数値  /  オプション  ;

 (他のPLOTステートメントと一緒に使用する)
 (「X=」「Y=」「SLOPE=」には、設定値を格納した変数を指定しても良い)


 ( ・・・etc  随時更新予定 )



使用例

PLOTステートメントは複数重ねて書くことが出来ます。
以下ではSCATTERPLOT(散布図)とREGRESSIONPLOT(回帰直線)を重ねてプロットしています。

* 適当なデータ作成 ;
data DT1;
label NO="No." HEIGHT="身長(父親)" HEIGHT2="身長(子供)";
input NO HEIGHT HEIGHT2;
cards;
001 165.5 154.5
002 175.8 160.5
003 159.8 150.9
004 180.5 158.2
005 156.8 145.9
;

* 散布図と回帰直線を重ねたグラフテンプレートを作成 ;
proc template ;
  define statgraph MYGRAPH;
      begingraph ;
         entrytitle "親子の身長";
         layout overlay  ;
              scatterplot x=HEIGHT y=HEIGHT2; * 散布図 ;
              regressionplot x=HEIGHT y=HEIGHT2; * 回帰直線 ;
         endlayout;
       endgraph;
  end;
run;

* グラフ作成実行 ;
proc sgrender data=DT1 template=MYGRAPH;
run;




GTL入門記事一覧

2015年8月8日土曜日

「SASユーザー総会2015」に行ってきました。



今年も見たい発表重なりまくりで、発表会場を駆け回る2日間でした。

去年に続き、ブログ「データステップ100万回」と「僕の頁」の中の人も発表されてました。お疲れ様です!


また、数年ぶりに再会する人がいたりして色々楽しかったです。

SASプログラマが集まる場とあって、毎回思わぬ出会いがあり、それだけでも行く価値はあるんじゃないでしょうか。











帰りのアンケートに答えたらSASマニア向けのロゴ入りモバイルケースが貰えました。

使うの勿体ないので、押し入れにコレクションとして大事にしまっておこうと思います。







来年の話もあり、
2016年のユーザー総会は7月下旬で神戸開催予定との事。


あと今年もそうですが、論文形式のものを提出してないと最優秀賞とか優秀賞の対象外になってしまう決まりっぽい。
なので素晴らしい発表なのにパワーポイントしか提出してなくて惜しくも賞を逃した人がいるようです。

来年発表する人は気をつけないとですね。

2015年8月4日火曜日

SASユーザー総会2015で発表の「RWIによる柔軟なレポート作成」の関連記事リンク


ポスター展示にて発表します。
「RWIでカレンダーを作る」っていうタイトルで一発芸的なのにしようと思いましたが、ちょっと肉付けしました。

しょぼい内容ですが、「あ、こんなんでいいんだ」って思って頂いて、自分もなんか発表してみようかなと思って頂ければと思います。

今回の発表とそれに関連するRWIの記事を以下にまとめました。


【RWIの解説】

 ・ レポート作成インターフェイス(RWI)入門1


【発表内容の関連記事】

SUMMARYプロシジャによる楽々転置法



サンプルデータ
data DT1;
input A$ B C$;
cards;
001 10 aa
001 20 bb
001 50 cc
002 . dd
002 100 .
002 200 ee
;






サンプルから、以下のようなデータセットを作りたいとします。
(Aの値毎に各変数値を横に転置しています。)







今回個人的にハマってるSUMMARYプロシジャで色々調べていたら、海外のSAS Global Forumで発表された方法が面白かったので、この方法で転置してみます。
http://support.sas.com/resources/papers/proceedings10/102-2010.pdf


SUMMARYプロシジャのIDGROUPを使った方法
proc sort data=DT1;
   by A;
run;

proc summary data=DT1 ;
   by A;
   output out=OUT1 (drop=_TYPE_ _FREQ_)  idgroup( out[3] (B C)= );
run;


これだけ!
IDGROUPについては以下で紹介しました。
MEANSまたはSUMMARYプロシジャで使えるIDGROUPオプションの紹介

最初、上のリンク記事の例のようにIDGROUPには min(変数) とか max(変数) を書く必要があるのかと思ったらそうでもないんですね!



汎用性をもたせるために、Aのグループ内obs数の最大値を取得して、その数だけ横に並べるようにしたプログラムが以下になります。
proc sql noprint;
   select max(_COUNT) into:_MAX trimmed from
   (select count(*) as _COUNT from DT1 group by A);
quit;

proc sort data=DT1;
   by A;
run;

proc summary data=DT1 ;
   by A;
   output out=OUT2 (drop=_TYPE_ _FREQ_)  idgroup( out[&_MAX] (B C)= );
run;


※ ただし、制限として1つの変数につき横に並べられるのは100個までとなります。
※ 「by A」を「class A」と書くと、合計行が作られてしまうので、必ず「by A」と書いてください。




2015年8月2日日曜日

RWIで迷路を作る


RWIを使って迷路を描画してみます。


迷路作成の初期値を設定

%let n    = 37;
%let init = 1234;

n    ・・・ 迷路のマスの数。(必ず奇数を設定する事)
init  ・・・ 迷路の壁と床を決めるために生成する乱数のシード値

初期値を変えるたびに異なる迷路を作ることが出来ます。


迷路の壁と床を乱数で決める

data DT1;

  *** 全マス目分の配列を定義 ;
  array AR(&n, &n) ;

  *** 最初から壁にする部分を2に設定 ;
  do y=1 to &n;
      do x=1 to &n;
         if x in (1,&n) or y in (1,&n) or mod(x*y,2)=1 then AR(x,y)=2;
         else AR(x,y)=1;
      end;
  end;

  *** 乱数を生成して壁の位置を決める ;
  *** 壁の位置 1:上、2:下、3:右、4:左 ;
  call streaminit(&init);
  do y=3 to &n-2 by 2;
  do x=3 to &n-2 by 2;

     *** 1段目は上下左右の壁の乱数を生成 ;
     if y=3 then do;
         * 左のマスが壁だったら上下右のみの壁の乱数を生成 ;
         if AR(x-1,y)=2 then  RAND = ceil(rand('uniform')*3);
         else RAND = ceil(rand('uniform')*4);
     end;

     *** 2段目以降は下左右の壁の乱数を生成 ;
     if y^=3 then do;
         * 左のマスが壁だったら下右のみの壁の乱数を生成 ;
         if AR(x-1,y)=2 then  RAND = ceil(rand('uniform')*2)+1;
         else RAND = ceil(rand('uniform')*3)+1;
     end;

     if RAND=1 then AR(x,y-1)=2; * 上 ;
     if RAND=2 then AR(x,y+1)=2; * 下 ;
     if RAND=3 then AR(x+1,y)=2; * 右 ;
     if RAND=4 then AR(x-1,y)=2; * 左 ;
  end;
  end;
run;

RWIで迷路描画

・SAS9.4より前のバージョンでは、以下の箇所を変えてみてください。
 1) format_cellメソッドの「style_attr」を「overrides」に置き換える。
 2) SAS9.2ではプログラム先頭に「ods html file="出力パス\ファイル名.html";」、最後に「ods html close;」と書く。

data _NULL_;
  length VAR1 $5.;
  set DT1;
  array AR(&n, &n);
  dcl odsout ob();
  ob.table_start();

  do y=1 to &n;
      ob.row_start();

      do x=1 to &n;

         *** 壁と床を描画 ;
          VAR1="";
          if (x=2 and y=2) or (x=&n-1 and y=&n-1) then VAR1="☆";
          ob.format_cell(data:VAR1,  style_attr: "background=" || choosec(AR(x,y),"white","black") ||
                                                                  " height=0.4cm  width=0.4cm");
      end;

      ob.row_end();
  end;

  ob.table_end();
run;