2016年3月14日月曜日

SCAN関数で、右からn個目に区切られた文字を取得する。




SCAN関数の使い方含め、落とし穴もあるので、まずは以下リンク記事をご参照下さい。
SCAN関数の落とし穴



そして本題。
SCAN関数の第2引数に「負の値」を指定すると、右からn個目に区切られた文字を取得するように変更できます。

data DT1;
   length A B $20.;
   A = "ABC,DEF,GHI";
   B = scan( A , -1, "," );
run;

  A
  B  
  ABC,DEF,GHI     GHI  


第2引数に 「-1」 と指定しているので、区切った文字の右から1個目の文字 「GHI」 を取得しています。

日本語に対応した 「KSCAN関数」 も同様に負の値を指定することが出来ます。

2016年3月11日金曜日

SQLプロシジャの 「NATURAL JOIN」 で3つ以上のデータセットを結合すると意図した結果にならない。


タイトル通りのご指摘をいただきました。

 「NATURAL JOIN」 については以前紹介しています。
SQLプロシジャで結合条件を省略する方法。


というわけで試してみます。以下2つの例は3つのデータセットを「NATURAL JOIN」で結合しているプログラムです。
。。。FEEDBACKオプションでログにプログラムを展開してみると。。


例①
proc sql feedback;
   create table OUT1 as
   select * from
   (select name from SASHELP.CLASS a where AGE=11)
      natural full outer join
   (select name from SASHELP.CLASS b where AGE=12)
      natural full outer join
   (select name from SASHELP.CLASS c where AGE=13)
;
quit;

ログ (環境・バージョンによって挙動が異なるかもしれません)

  select COALESCE(C.Name, A.Name) as Name
    from ( select A.Name
             from SASHELP.CLASS A
            where A.Age = 11
         ) full outer join
         ( select B.Name
             from SASHELP.CLASS B
            where B.Age = 12
         ) on B.Name = A.Name full outer join
         ( select C.Name
             from SASHELP.CLASS C
            where C.Age = 13
         ) on C.Name = A.Name;


例②
data DT1 DT2 DT3;
   set SASHELP.CLASS;
run;

proc sql feedback;
   create table OUT1 as
   select * from
   (select name from DT1 a where AGE=11)
      natural full outer join
   (select name from DT2 b where AGE=12)
      natural full outer join
   (select name from DT3 c where AGE=13)
;
quit;


ログ(環境・バージョンによって挙動が異なるかもしれません)

  select COALESCE(C.Name, A.Name, B.Name) as Name
    from ( select A.Name
             from WORK.DT1 A
            where A.Age = 11
         ) full outer join
         ( select B.Name
             from WORK.DT2 B
            where B.Age = 12
         ) on B.Name = A.Name full outer join
         ( select C.Name
             from WORK.DT3 C
            where C.Age = 13
         ) on (C.Name = A.Name) and (C.Name = B.Name);



2つの例は同じようなプログラムなのに、展開されるプログラムが異なっています。
(青字と赤字の部分)

マニュアルみても言及してないし、仕様なのかバグなのか不明です。何かご存知の方いたら教えてください。

てことで、「NATURAL JOIN」 で 3つ以上のデータセットを結合しない方がよさそうです。


2016年3月10日木曜日

SELECT-WHEN入門 【条件分岐処理】



SELECT-WHEN は変数の値によって処理を分岐するような場合に使います。



書き方1

data DT1;
  set SASHELP.CLASS;
  select (AGE);
       when (11)       X=1;     * ① ;
       when (12)       X=2;     * ② ;
       when (13)       X=3;     * ③ ;
       when (14,15)  X=4;     * ④ ;
       otherwise       X=99;   * ⑤ ;
  end;
run;


・AGEの値によって変数Xにいれる値を分岐しています。
・①~④の順に見ていって「AGEの値と一致する ”最初のWHENステートメント”」のみ実行される。
・AGEの値がどれにも一致しなかった場合⑤のOTHERWISEが実行される。

※ AGEがどのWHENステートメントにも一致しない場合、OTHERWISEがないとエラーになる。



書き方2

data DT1;
  set SASHELP.CLASS;
  select;
       when (AGE=11 and WEIGHT>100)   X=1;
       when (AGE=12 and WEIGHT>100)   X=2;
       otherwise                                            X=99;
  end;
run;

こちらも「条件に該当する ”最初のWHENステートメント”」のみ実行される。



応用

data DT1;
  set SASHELP.CLASS;
  select;
      when (AGE=11) do;
          X1=1;
          X2=1;
      end;
      otherwise;
  end;
run;

・DO~ENDで囲めば、処理を複数書ける。
・「OTHERWISE ;」のように処理文を省略すると「OTHERWISEの場合、なにも処理しないで」という指定になる。



2016年3月4日金曜日

【%DATATYP】 データステップ外でマクロ変数値のデータタイプを判定する




構文

  %DATATYP( 判定したい値 )


  • 値が半角数値なら 「NUMERIC」、文字なら 「CHAR」 が返される
  • 「+1」「-1」「1e10」「1d10」みたいな表現も「NUMERIC」と判定される
  • 全角等のマルチバイト文字に対応してるかマニュアルに記載がなかったので、マルチバイト文字は入れないほうが無難かも



  %let VAR1 = 10.5;
  %put %datatyp(&VAR1);

ログ
NUMERIC



色々な値で試してみました。

%put %DATATYP(100);    /* → NUMERIC  */
%put %DATATYP(1.0);     /* → NUMERIC  */
%put %DATATYP(+1.0);   /* → NUMERIC  */
%put %DATATYP(-1.0);   /* → NUMERIC  */
%put %DATATYP(1e-1);  /* → NUMERIC  */
%put %DATATYP(1d-1);  /* → NUMERIC  */
%put %DATATYP(1+);     /* → CHAR         */
%put %DATATYP(abc);   /* → CHAR         */
%put %DATATYP();         /* → CHAR         */

2016年3月3日木曜日

箱ひげ図をかく 【GTL編】




GTLの詳細については、以下リンクをご覧ください。



BOXPLOTステートメントの構文


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


  • デフォルトでカテゴリ(X軸)は離散軸になりますが、ユーザー側で線形軸等に変えると、挙動が変わってしまいます。詳細はリファレンスを要確認。



オプション

  設定内容  指定   詳細設定
  箱ひげ図を横に寝かす 

  ORIENT =  HORIZONTAL  

    
  箱の幅

  BOXWIDTH  =  0~1

  
  箱の書式

  FILLATTRS  =  (詳細設定) 

  COLOR = 色

  ひげの書式

  WHISKERATTRS  =  (詳細設定) 

  COLOR = 色

  中央値の書式

  MEDIANATTRS  =  (詳細設定)

  COLOR = 色

  平均値の書式 

  MEANATTRS  =  (詳細設定)
  
  COLOR   = 色
  SYMBOL = 表示マーク  

  外れ値の書式 

  OUTLIERATTRS  =  (詳細設定)   

  SYMBOL = 表示マーク 
  

…etc ( 色々できるのでリファレンスを確認してみてください )



注意
  • オプションの組み合わせで挙動が変わる可能性あり。
  • デフォルトで出力される箱ひげ図のタイプ(箱、ひげ、外れ値等の定義)はリファレンスを要確認



1. 簡単な例

proc template ;
  define statgraph MYGRAPH;
      begingraph ;
         entrytitle "お魚の体重分布";
         layout overlay  ;
              boxplot x=SPECIES y=WEIGHT;
         endlayout;
       endgraph;
  end;
run;

proc sgrender data=SASHELP.FISH template=MYGRAPH;
run;



2.カスタマイズ

proc template ;
  define statgraph MYGRAPH;
      begingraph ;
         entrytitle "お魚の体重分布";
         layout overlay  ;
              boxplot x=SPECIES y=WEIGHT /
                            orient = horizontal
                            meanattrs = (color=black symbol=plus)
                            medianattrs = (color=black)
                            whiskerattrs = (color=black)
                            fillattrs = (color=white)
                            outlierattrs = (symbol=circlefilled)
                            boxwidth = 0.5
              ;
         endlayout;
       endgraph;
  end;
run;

proc sgrender data=SASHELP.FISH template=MYGRAPH;
run;




2016年3月2日水曜日

テキストファイル(CSVなど)の読み込みと注意点 【IMPORTプロシジャ編】



  • 可変長テキストファイルの読み込みに限定した説明になります。
他の形式で今回のプログラムを流用すると、重要なオプションが効かず、文字切れや変数属性の設定がうまくいかない等の可能性あり。

  • とにかく、想定外の結果になりやすいので、出来たデータセットは要確認です。
例えば、聞いた話ですが、テキストファイルに「クォーテーション」「改行コード」「見えない改行コード」が含まれている際に、正しく読み込めない事があったようです。




構文

 PROC IMPORT
   OUT                         =   出力データセット
   DATAFILE                =   "読込テキストファイル"   /* パスが長いと実行失敗します */
   DBMS                      =    CSV | TAB | DLM             /* ファイル種類 */
   REPLACE                     /* 既存のデータセットを置き換える */
   ;
   DELIMITER             =   "区切り文字";    /* DBMS=DLMの場合に指定 */
   GETNAMES            =   YES | NO ;        /* 1行目を変数名として読み込むか */
   DATAROW              =   行番号 ;             /* 何行目から読み込むか */
   GUESSINGROWS  =   行番号 | MAX;   /* 何行目までのデータで変数属性を判定するか */
 RUN ;


DBMS=オプション
  指定  設定内容
  CSV    カンマ区切り 
  TAB    タブ区切り
  DLM   自分で 「DELIMITER=オプション」 に区切り文字を指定 



📚 注意点


① GUESSINGROWS=オプション ※重要!

SASはテキストファイルの先頭20行を読み込んで各変数の属性(データ型やlength)を決める。

  • 20行目まで数値で、21行目に文字が入ってきた場合
        ⇒ 数値型になるので、文字は読み込めず欠損値になる。

  • 20行目まで文字の最大長さが100バイトで、21行目に200バイトの文字が入ってきた場合
        ⇒length=$100となるので、101バイト以降の文字は切り捨てられる。


てことで、変数属性を判定する行数をGUESSINGROWSで設定しなおします。
ちなみに、SAS9.3 から 「GUESSINGROWS = MAX」 という指定が可能(MAX = SASが読込める最大行数)



② 制限(環境・バージョンによって異なる可能性あり)

 項目 内容
 SASが読込める最大行数   32767行【SAS9.2】、2147483647行【SAS9.3, 9.4】 
 1行につき読込めるバイト数   区切り文字も含め32767バイト



③ REPLACEオプション

世代管理されたデータセット(GENMAX=オプション)をREPLACEすると、そのデータセットの既存の世代データがすべて削除されるようなので注意。



④ GETNAMES=オプション

「YES」で1行目を変数名として読み込んだ場合、変数名として成立しない空白・特殊文字などが含まれていると、SASが変数名として成立する名前に変換してくれます。ただ、システムオプション「VALIDVARNAME」の設定によって、変換される変数名が変わるので注意。



最後に、気づいた範囲で「うまく読み込めない例」を紹介。



うまく読み込めない例(環境・バージョンで動作が異なるかも)


C:\test\test.txt
X,Y,Z
001, pen,2016/01/01
002,apple,2016/01/02


/* PROC IMPORT */
proc import out=OUT1
    datafile="C:\test\test.txt"
    dbms=dlm  replace
    ;
    delimiter=",";
    getnames=yes;
    datarow=2;
    guessingrows=max;
run;


/* 出力データの変数属性を確認 */
proc contents data=OUT1;
run;


① 変数X: 0落ち問題

数値変数として読み込まれて、しかも結果が読み込み元の値と異なっている(先頭の0が取れた: 001 → 1 )
読み込み元の値をダブルクォーテーションで囲ったら0落ちしませんでしたが( "001" → 001 )、データステップによるINFILE/INPUTでの読み込みに切り替えた方が良いかもしれませんね。



② 変数Y: 先頭の半角スペース落ち問題

読み込み元の値は「 pen」と先頭に半角スペースを含んでいますが、結果は先頭の半角スペースが取れた。
PROC IMPORTに限らず、SASの色々な機能で先頭の半角スペース取れがち。



③ 変数Y: FORMATとINFORMATが付与される問題

変数属性を見ると、FORMAT(出力形式)とINFORMAT(入力形式)が付与されています。
これは以下のような場合に、勘違いを起こしやすいです。

data OUT2;
    length Y $20.;
    set OUT1;
    Y = "aaaaaaab";
run;

SET前のLENGTHステートメントでYの長さを増やしてから、Y = "aaaaaaab" としたのに、結果は「aaaaaaa」となった(末尾の "b" が落ちた?)
実はちゃんと "aaaaaaab" が格納されていますが、FORMAT「$7」が付与されているため、見た目7バイト分しか見えていないだけ(この勘違い、よく聞きます)


FORMAT/INFORMATは以下で外せます(外す処理はSET後に記述しないと動作しないので注意)

data OUT3;
    length Y $20.;
    set OUT1;
    format Y;
    informat Y;
    Y = "aaaaaaab";
run;

全変数いっぺんにFORMATを外したいところですが、変数ZはFORMAT「YYMMDD10」が付与されているので、外したくないものを避ける必要あり。


2016年3月1日火曜日

1つのデータステップで、複数のデータセットを作成する方法。




以下を実行すると、、

data  DT1 DT2 DT3;
    set  SASHELP.CLASS;
run;

データセットSASHELP.CLASSをセットしたデータセットDT1, DT2, DT3をいっぺんに作ることが出来ます。



また、以下のように書くことも出来ます。

data  DT1 DT2 DT3;
     set  SASHELP.CLASS;
     if  AGE=13 then  output DT1;
     if  AGE=15 then  output DT2;
     output DT3;
run;

・ AGE=13のオブザベーションをDT1に出力
・ AGE=15のオブザベーションをDT2に出力
・ 条件なしでオブザベーションをDT3に出力


注意:データステップ中にOUTPUTステートメントとMODIFYステートメントを併用していない前提となります無限ループになったり挙動が変わることがあるので・・)



注意点


たとえば、以下の赤文字の処理、変数FLAGがDT1とDT2に作成されますが、、

data DT1 DT2;
    set SASHELP.CLASS;
    if AGE=13 then output DT1;
    if AGE=15 then output DT2;
    if AGE=13 then FLAG=1;     ***  × この書き方はダメ ******;
run;

DT1には「AGE=13」のオブザベーションがありますが、「FLAG=1」が反映されません。

暗黙のOUTPUTステートメント」 を見ていただくと原因が分かります。



というわけで、OUTPUTの前に移動してやります。

data DT1 DT2 DT3;
   set SASHELP.CLASS;
   if AGE=13 then FLAG=1;
   if AGE=13 then output DT1;
   if AGE=15 then output DT2;
run;