2018年4月17日火曜日

PROC FORMAT入門7 : フォーマットカタログの保存




以下のようにフォーマットを定義したとします。

proc format;

   value TEST1_
       1 = "MALE"
       2 = "FEMALE"
   ;
   invalue TEST2_
       "M" = 1
       "F" = 2
   ;
run;


定義したフォーマットはライブラリ「WORK」に「FORMATS」という名前で格納されます。
この「WORK.FORMATS」を「SASカタログ(或いはフォーマットカタログ)」といいます。

WORKに格納されるので、通常はSAS終了とともに削除されます。




フォーマットカタログの保存


WORK以外の場所にフォーマットカタログを保存したい場合は「LIBRARY=オプション」を指定します。

PROC FORMAT  LIBRARY = ライブラリ.カタログ名;
      ~ フォーマット定義 ~
RUN;


例)
library = MYLIB.FORMATS ……「MYLIB.FORMATS」で保存
library = MYLIB.MYFMT      ……「MYLIB.MYFMT」で保存
library = MYLIB                   ……「MYLIB.FORMATS」で保存

※3つ目の例のようにカタログ名を省略すると「FORMATS」という名前で保存されます。



ここまでで、フォーマットカタログを保存する方法は分かりましたが、
WORK以外に保存したフォーマットカタログはそのままでは利用できません。利用するには以下2つの方法があります。




① ライブラリ参照「LIBRARY」を利用する


例えば、以下のプログラムで「TEST1_」というフォーマットを使用しています。

data DT1;
   format X TEST1_.;
   X = 1;
run;



まずSASは、この「TEST1_」をフォーマットカタログから探しにいきます。
デフォルトでは、「WORK.FORMATS」→「LIBRARY.FORMATS」の順に探しに行きます。



「WORK.FORMATS」の次に検索される「LIBRARY.FORMATS」はSASが決めたやつで、

LIBNAME LIBRARY  "フォーマットカタログを保存したパス";

と書けば、このパスに格納されている「FORMATS」と名の付いたカタログが検索されるようになります。




② 「OPTIONS FMTSEARCH」 を利用する


「FMTSEARCH」システムオプションでは、参照するフォーマットカタログと、参照する順番を指定できます。

OPTIONS FMTSEARCH = (  参照する順にフォーマットカタログを列挙  )


例) options fmtsearch=( TEST.MYFMT );

「WORK.FORMATS」→「LIBRARY.FORMATS」→「TEST.MYFMT」の順に検索。
(「WORK.FORMATS」と「LIBRARY.FORMATS」は強制的に最初に検索される。)


例) options fmtsearch=( TEST );

「WORK.FORMATS」→「LIBRARY.FORMATS」→「TEST.FORMATS」の順に検索。
(「TEST」というように、ライブラリ名しか指定しない場合、「ライブラリ.FORMATS」が検索される)


例) options fmtsearch=( TEST WORK );

「LIBRARY.FORMATS」→「TEST .FORMATS」→「WORK.FORMATS」の順に検索。
(「WORK.FORMATS」または「LIBRARY.FORMATS」の検索順を明示的に指定して変えることができる)



ちなみに、FMTSERACH=システムオプションは「CASサーバーでのフォーマットライブラリの検索順には適用されない」とのこと(CASは使用したことないので詳細不明。詳しくはリファレンスを参照下さい)


長い説明になりましたが、データステップ100万回のSASYAMAさんの記事もあわせて参照下さい。(やっぱSASYAMAさんの記事分かり易い、説明うまいなぁ)
http://sas-tumesas.blogspot.jp/2013/12/libraryfmtsearch.html




PROC FORMAT入門 : 記事一覧

2. INVALUEステートメント
3. 範囲の指定

2018年4月11日水曜日

WHEREやIFでの数値変数に対する範囲指定




4月からSASを始める方も多いので、入門記事的なのも書いてこうと思います。
最後に注意点も書いてるので、そちらも見ていただけたらと思います。


では早速、以下をご覧ください。

* Sample data ;
data DT1;
   do X=1, 1.1, 2, 2.1, 3;
      output;
   end;
run;

  X  
 1 
 1.1 
 2 
 2.1 
 3 


上のデータからXが「1」「2」「2.1」のいずれかのオブザベーションを抽出したいとします。
なんも考えずに書くと以下のようになりますね。

data OUT1;
  set DT1;
  where X=1 or X=2 or X=2.1;
run;
  X  
 1 
 2 
 2.1 



ですが、この書き方はちょっとメンドイです。以下のようにもっと楽な書き方があります。

data OUT1;
  set DT1;
  where X in (1, 2, 2.1);
run;
  X  
 1 
 2 
 2.1 


こんな感じで、ひとつの変数に対して「この値とこの値」または「この値からこの値まで」のオブザベーションをWHEREやIFで抽出したい場合の、範囲の書き方を以下にまとめました。




範囲の書き方

 指定内容 例 例から抽出されるX  注意事項
 ●以下

・X <= 2
・2 >= X
 2以下


 ●未満

・X < 2
・2 > X
 2未満

 ●●以上

・X >= 2
・2 <= X
 2以上

 ●

・X > 2
・2 < X
 2超

 ●以上、以下  

・X >= 1 and X <= 2
・X between 1 and 2 
 1以上、2以下

・betweenはwhereだけに対応
 (ifでは使えない)
 ●超、未満

・X > 1 and X < 2

 1超、2未満





 ●  or    or  …

・X in (1, 2, 2.1)

 1 or 2 or 2.1

 ●以上、以下
  (かつ整数)
・X in (1:2)

 1以上、2以下の整数

 昔の記事で紹介してます。
 http://sas-boubi.blogspot.jp/2014/01/in.html




注意点


注意①

今回は「DATAステップやプロシジャにおけるWHEREやIF」での「数値変数に対する範囲の書き方」に限定。
%IF等のマクロステートメントでは挙動が異なるので、リファレンスや以下の記事などをご確認下さい。


注意②

取扱注意のため、今回紹介しなかった「1 <= X <= 2」や「1 < X < 2」という書き方もありますが。。

DATAステップ、プロシジャ、マクロステートメントなどの機能毎に挙動が異なる場合があります。
特に、%IFなどのマクロステートメントでは挙動が「超絶」異なるので使用NGです。
またこれはSAS独特の書き方で、他言語では通用しません。


注意③

記事の趣旨から外れますが、初学者向けに注意点。
WHEREやIFに「AND」と「OR」の式が混在していると、「AND」→「OR」の順に式が優先される。
  • X=1 or Y=1 and Z=1 だと、「X=1」or「Y=1 and Z=1」という式のまとまりになる。
  • (X=1 or Y=1) and Z=1 のようにカッコで囲むとそこが優先されて、「X=1 or Y=1」and「Z=1」となる。

2018年4月3日火曜日

IF vs SELECT



「IFは使うけど、SELECTは全然使わない、むしろ要らないよ」っていう人がいたので、IFとSELECTを勝負させてみたいと思います。



各ステートメントについては以下で解説してます。

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




ROUND1.

以下は、変数VAR1の値によって、VAR2の値を設定しています。
data DT1;

   VAR1 = "b";

   * IFステートメントの場合 ;
   if         VAR1 = "a" then VAR2 = 1;
   else if  VAR1 = "b" then VAR2 = 2;
   else if  VAR1 = "c" then VAR2 = 3;

run;


このように、ひとつの変数の値によって処理を分岐する場合にSELECTがつかえます。

data DT2;

   VAR1 = "b";

   * SELECTステートメントの場合 ;
   select (VAR1);
      when ("a")  VAR2=1;
      when ("b")  VAR2=2;
      when ("c")  VAR2=3;
      otherwise   VAR2=.;
   end;

run;

SELECTの方がスッキリしている気がします。なのでSELECTの勝ちです。




ROUND2.

以下は、変数VAR1の1バイト目の値によって、VAR2の値を設定しています。

data DT3;

   length VAR1 VAR2 TEMPVAR $10.;
   VAR1 = "M001";

   * IFステートメントの場合 ;
   TEMPVAR = substr( VAR1,1,1);

   if         TEMPVAR = "M" then VAR2 = "MALE";
   else if  TEMPVAR = "F" then VAR2 = "FEMALE";

   drop TEMPVAR;
run;

TEMPVARという変数にVAR1の1バイト目を入れておいて、最後にこの変数をDROPしています。


このような場合も、SELECTの方がスッキリしてみえます。

data DT4;

   length VAR1 VAR2 $10.;
   VAR1 = "M001";

   * SELECTステートメントの場合 ;
   select (substr(VAR1,1,1));
      when ("M")   VAR2 = "MALE";
      when ("F")    VAR2 = "FEMALE";
      otherwise      VAR2 = "";
   end;

run;

IFの時は、「TEMPVAR = substr(VAR1,1,1);」っていう変数作って、あとでDROPしましたが、
SELECTの時は、「select (substr(VAR1,1,1));」って書いちゃえば、変数作らずに済んでDROPステートメントが不要になります。


これもSELECTの勝ち。



てことで、SELECTの完全勝利ですね。
(ちなみにハンデとしてSELECTが勝てる勝負だけ選びました)

2018年3月30日金曜日

文字値の末尾にある空白問題




以下は、長さを「5」に設定した変数Xに「abc」という文字を格納しています。

data DT1;
   length X $5.;
   X = "abc";
run;

  X  
 abc 



次に以下を実行してみます。

data DT2;
   set DT1;
   Y = index( X, " " );
run;

  X  
  Y  
 abc   4


index関数は、変数に対して「特定の文字値」が何バイト目に含まれるか返します。
(シングルバイト専用。日本語等マルチバイト文字には使用NG)


今回は、変数Xに半角スペース(空白)が何バイト目に含まれているかを求めています。
結果は「4」となり、4バイト目に半角スペースが含まれているということになります。

「abc」だから半角スペースなんてないはずですよね。



実は内部では以下のように長さ5のうち、余った末尾の2バイトには半角スペースが入っています。
     
 X 
 a 
 b 
 c 
   
   




なので、

  Y = index( X, " " );

は半角スペースが4バイト目にあると解釈したわけですね。
また以下のように文字値を結合する場合も

  length Y2 $20.;
  Y2 = X || "def";

変数Xの末尾に半角スペースが2つあるので「abc  def」という結果になってしまいます。



そこでよく、trimn関数などで末尾の半角スペースを取り除く処理を入れたりします。

  Y = index( trimn(X), " " );

  length Y2 $20.;
  Y2 = trimn(X) || "def";



ただし、比較演算では末尾の半角スペースを考慮する必要はありません。
例えば「 "abc" = "abc   " 」という比較演算の結果は「等しい」と判断されます。

「比較演算では末尾の半角スペースの有無に意味を持たせていない」ということですね。


今回のデータでも、where条件を書くとき、

   where X = "abc  ";

というように、わざわざ末尾を気にしなくても、以下で問題ないです。

   where X = "abc";


2018年3月27日火曜日

日付・時間・日時の書き方




例えば、2018/03/27という日付値を作りたい時、
以下のように書かれる方も多いと思います。(私もそうですが)

data DT1;
   format x date9.;
   x = input('2018/03/27',yymmdd10.);
run;


ですが、もっと簡単に日付値(と時間値・日時値)を作ることが出来ます。




日付値を作る

「'ddmmmyyyy'd」 という書式で日付値を作ることが出来ます。

data DT1;
    format x date9.;
    x = '27mar2018'd;
run;




時間値を作る

「'hh:mm:ss.s't」 という書式で時間値を作ることが出来ます。

data DT2;
    format x y z time11.2;
    x = '23:05't;
    y = '23:05:10't;
    z = '11:05:10pm't;
run;




日時値を作る

「'ddmmmyyyy:hh:mm:ss.s'dt」 という書式で日時値を作ることが出来ます。

data DT3;
    format x y z datetime21.2;
    x = '27mar2018:23:05'dt;
    y = '27mar2018:23:05:10'dt;
    z = '27mar2018:11:05:10pm'dt;
run;



時間値と日時値で「am/pm」が指定できるのは知らなかったなぁ、常識なのかな。
また一歩SASマスターに近づいた。


2018年3月23日金曜日

PROC FORMAT入門6 : PICTUREステートメント




PICTUREステートメントで日付・時刻・日時値に書式を設定する「DATATYPE=オプション」の説明をしていきます。



📝 前置き
  • PICTUREステートメント1」の前提知識が必須
  • 設定・環境で動きが変わりまくるので挙動確認が必須。一部注意点を最後に記載したのでそちらも要確認!




構文

 PROC FORMAT;

      PICTURE フォーマット名
             =  '書式の定義 (DATATYPE = データのタイプ)
             =  '書式の定義'  (DATATYPE = データのタイプ)
           ・・・
      ;
 RUN;


・フォーマット名の末尾は数字NG(例えば「TEST1」など)

・フォーマットを適用する変数が、日付(DATE)・時刻(TIME)・日時(DATETIME)のどれなのかを「DATATYPE=」に指定

・間違ったDATATYPEを指定しないように!
(例えば対象の変数が日付値なのに、DATATYPE=DATETIMEを指定するなど)






* フォーマット作成 ;
proc format;
   picture mydatetm (default=10)
      low - high = '%Y_%0m_%0d' (datatype=datetime)
   ;
run;

* フォーマット変換 ;
data TEST;
   length X 8. Y $10.;
   X = '14mar2018:21:5:14'dt;
   Y = put(X,mydatetm.);
run;

 Y 
 2018_03_14 


「datatype=datetime」として、日時値の変数に対するフォーマットを作成しています。

書式を定義する部分(青字箇所)に、やたら「%」が書かれてますね。
この「%なんちゃら」の部分を「ディレクティブ」と呼びます。

ディレクティブの組み合わせによって書式を定義します。



ディレクティブ(一部)


※ オプションやディレクティブの組み合わせ、SASの環境・言語設定によって、動作しなかったり動きが変わるため要挙動確認!

ディレクティブ設定内容対応するDATATYPE
%y
年(2桁)
%0yとすると年が1桁の時に先頭に0が埋められる
date,
datetime
%Y年(4桁)date,
datetime
%m
%0mとすると月が1桁の時に先頭に0が埋められる
date,
datetime
%d
%0dとすると日が1桁の時に先頭に0が埋められる
date,
datetime
%H時(24時間表記)
%0Hとすると時間が1桁の時に先頭に0が埋められる
time,
datetime 
%M
%0Mとすると分が1桁の時に先頭に0が埋められる
time,
datetime
%S秒(秒の整数部分のみ対応)
%0Sとすると秒が1桁の時に先頭に0が埋められる
time,
datetime
%%例えば「100%」と表示するには「100%%」と指定date,
time,
datetime
・・・など


例えば、書式を「%Y年%0m月%0d日」と定義したフォーマットを日付値に適用した場合、「2018年03月14日」みたいな感じで適用されます。


・ディレクティブには「%y」と「%Y」のように小文字・大文字の区別があるので注意
・他にも曜日や月名、週数などを設定するディレクティブもあります。あわせてマニュアルもご覧ください。




注意事項


注意①

以下記事の最後らへんで解説している通り、フォーマット適用後の文字の長さを考慮して「DEFAULT=オプション」も併用しないと、文字切れするケースがある。
PICTUREステートメント1

また、PICTUREフォーマットはデフォルト長などの長さに応じて、右詰めで適用されるので、
「    2018年03月14日」みたいに文字が右に詰められて左側に空白(半角スペース)が入る事があるのでそこんところもご留意下さい。



注意②

オプションやディレクティブの組み合わせ、SASの環境・言語設定など、様々な要素によって結果が変わります。
以下、SASの環境によって結果が変わる一例です。

* フォーマット作成 ;
proc format;
   picture mytime (default=8)
      low - high = '%p%I' (datatype=time)
   ;
run;

* フォーマット変換 ;
data TEST;
   length X 8. Y $10.;
   X = '21:05't;
   Y = put(X,mytime.);
run;

結果
SASの環境が「日本語」の場合
 Y 
  午後9  

SASの環境が「英語」の場合
 Y 
      PM9  


ディレクティブ「%p」と「%I」は、以下の意味になります。
・%p: timeやdatetimeの時刻に対してAM/PMを出力
・%I(大文字のアイ): timeやdatetimeの時刻に対して「時(12時間表記)」を出力


上の例ではSASの環境によって結果が変わっています。
(日本語の場合は「午前 or 午後」、英語の場合は「AM or PM」が適用された)


英語の設定にあわせたい場合は、以下のように「LANGUAGE=ENGLISH」と指定します。
(この方法でも環境とか他の要素で結果が変わるかも)

proc format;
   picture mytime (default=8)
      low - high = '%p%I' (datatype=time language=english)
   ;
run;

ちなみに、LANGUAGE=オプションは現状一部の言語にしか対応してません。
(日本語とか中国語とかには設定できない。なんでだ・・・)



注意③

欠損値に「DATATYPE=」を設定すると「ERROR」という文字が返ってきます。

proc format;
   picture mydatetm (default=10)
      . = '%Y_%0m_%0d' (datatype=datetime)
   ;
run;

data TEST;
   length X 8. Y $10.;
   X = .;
   Y = put(X,mydatetm.);
run;

 Y 
      ERROR 




PROC FORMAT入門 : 記事一覧

2. INVALUEステートメント
3. 範囲の指定

2018年3月16日金曜日

固定長のテキストファイルで、先頭スペースを保持して読み込む




以下のテキストファイルを読み込みたいとします。


C:\test\test.txt
0 .
1 a
2  b
3   c


*** 例①:失敗例 *****;
data TEST;
   length X 8. Y $3.;
   infile "C:\test\test.txt" truncover;
   input  X 1-1
             Y 3-5;
run;

  X  
  Y  
 0   
 1  a 
 2  b 
 3  c


文字変数Yには、テキストファイルの中身通り、先頭の半角スペースを保持して以下のように読み込みたかったのですが、ふつうに読み込むとこの半角スペースは消えちゃいます。

  X  
  Y  
 0   
 1  a 
 2   b 
 3    c



先頭の半角スペースを保持するには、以下のような工夫が必要です。
(後述しますが、文字欠損値の取り扱いだけ注意が必要)

*** 例② *****;
data TEST;
   length X 8. Y $3.;
   infile "C:\test\test.txt" truncover;
   input  @X
             @Y  $char3. ;
run;

  X  
  Y  
 0 
 . 
 1 
 a 
 2 
  b 
 3 
   c


INPUT  @読み込み開始位置   変数名   $CHAR読み込む文字の長さ.」という感じで書きます。

  • 「@読み込み開始位置」で変数毎に読み込みを開始する位置を指定し、
  • インフォーマット「$CHAR」を使って先頭の半角スペースを切り捨てずに読み込んでいます
  • 「$CHAR」は文字変数として読み込むときに使います。上の例の変数Xは数値変数として読み込みたいので「$CHAR」は指定していません


注意
  • 文字欠損値の取り扱いだけ注意
    • 最初の例(例①)では、テキストファイルの1行目のドット「.」は欠損値として扱われて文字変数Yに読み込まれましたが、
    • 今回の例(例②)では、$CHARを使ったことで、テキストファイルの1行目のドット「.」がそのまま「.」という文字値として文字変数Yに読み込まれました。


その他の補足

今回の例で使用している「TRUNCOVERオプション」について知りたい方は、以下記事の最後らへんで解説しています。