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;


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