2018年3月30日金曜日

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




以下をご覧ください。

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

  X  
 abc 

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


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

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

  X  
  Y  
 abc   4


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

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



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




なので、

  Y = index( X, " " );


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

  Y = X || "def";


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


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

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




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


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


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

   where X = "abc  ";



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

   where X = "abc";


2018年3月27日火曜日

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




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

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


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




日付値を作る

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

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




時間値を作る

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

data DT2;
    format x y z time.;
    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 datetime.;
    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=オプション」を使うと、日付・時刻・日時データに対して、好きな書式への読み替え・変換をすることが出来ます。






構文

 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;
   format X mydatetm10.;
   X = '14mar2018:21:5:14'dt;
run;

 X 
  2018_03_14   


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

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

このディレクティブの組み合わせによって書式を定義しています。
以下、個人的に利用頻度の高いディレクティブを一覧にしました。



ディレクティブ(一部)

 ディレクティブ 設定内容 対応する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 


%I時(12時間表記)。
%0Iとすると時間が1桁の時に先頭に0が埋められる
time,
datetime

%I←大文字のアイです
%M分。
%0Mとすると分が1桁の時に先頭に0が埋められる
time,
datetime


%p午前(AM) or 午後(PM)time,
datetime
SASの言語設定によって結果が変わる
(記事下方の注意事項を参照)
%S秒。
%0Sとすると秒が1桁の時に先頭に0が埋められる
time,
datetime


・・・など


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


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




注意事項


注意①
PROC FORMAT入門の記事で何回も言ってる気がしますが、「DEFAULT=オプション」をつけないと文字切れしちゃうケースがあるのでご注意を!
(理由は以下で紹介している落とし穴と同じ。)

INFORMATの落とし穴【デフォルト長】


注意②
SASの言語設定によっては、結果が変わるディレクティブがあります。
以下をご覧ください。

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

* フォーマットを変数に割り当てる ;
data TEST;
   format X mytime10.;
   X = '21:05't;
run;

結果
SASの環境が「日本語」の場合
 X 
           午後09:05  

SASの環境が「英語」の場合
 X 
             PM09:05  


ディレクティブ「%p」はSASの言語設定によって結果が変わります。

日本語設定の場合は「午前 or 午後」で表示され、英語設定の場合は「AM or PM」で表示されます。


英語設定にあわせたい場合は、以下のように「LANGUAGE=ENGLISH」と指定します。

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

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





PROC FORMAT入門 : 記事一覧


2018年3月16日金曜日

INPUTステートメントで、先頭スペースを保持して読み込む




質問頂いたので、こちらにも展開しておきます。
以下をご覧ください。


data TEST;
   input  X  Y:$3. ;
cards;
1 a
2  b
3   c
;
  X  
  Y  
 1  a 
 2  b 
 3  c


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

  X  
  Y  
 1  a 
 2   b 
 3    c



先頭スペースを保持するには、以下のように工夫が必要です。

data TEST;
   input  @X
             @Y  $char3. ;
cards;
1 a
2  b
3   c
;
  X  
  Y  
 1 
 a 
 2 
  b 
 3 
   c


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

・「@読み込み開始位置」で変数毎に読み込みを開始する位置を指定し、
・インフォーマット「$CHAR」を使って先頭のスペースを切り捨てずに読み込んでいます。


あと実際質問もらった方に聞かれたことなので、一応こちらでも補足説明しておきます。
$CHARは文字変数として読み込むときに使います。
上の例の変数Xは数値変数として読み込みたいので$CHARの指定はしていません。

2018年3月13日火曜日

SASでトランプを作る



SGPLOTでトランプ作ってみました。

以下のような感じで1枚ずつ、マーク4種類 × 数字13枚 = 52枚のトランプがグラフとして出力されます。


出力結果の一部を切り貼りした寄せ集め



プログラム


※SAS9.4で動作します。また将来のバージョンで多少内部設定が変わったりすると、結果が綺麗に出力されないとかあるかもしれません。

ハッキリいってプログラムとしては汚いです。とりあえず作ったって感じです。
あとジョーカーを含めるとプログラムが複雑になっちゃうので、やめました。やるとしたらsymbolimageステートメントとか使って好きな画像をトランプの絵柄にしてジョーカーにする感じになります。


/*** トランプの数字とマークの表示位置をデータ化 ***/
data CARD;
    input SYMBOL:$7. NUMBER:$5. X Y;
    TOP_X     = 1.5;
    TOP_Y1    = 14;
    TOP_Y2    = 12.5;
    BOTTOM_X  = 3.5;
    BOTTOM_Y1 = 0;
    BOTTOM_Y2 = 1.5;
cards;
symbol1 1  2.5  7
symbol1 2  2.5  12
symbol2 2  2.5  2
symbol1 3  2.5  12
symbol2 3  2.5  2
symbol1 3  2.5  7
symbol1 4  2    12
symbol2 4  2    2
symbol1 4  3    12
symbol2 4  3    2
symbol1 5  2    12
symbol2 5  2    2
symbol1 5  3    12
symbol2 5  3    2
symbol1 5  2.5  7
symbol1 6  2    12
symbol2 6  2    2
symbol1 6  3    12
symbol2 6  3    2
symbol1 6  2    7
symbol1 6  3    7
symbol1 7  2    12
symbol2 7  2    2
symbol1 7  3    12
symbol2 7  3    2
symbol1 7  2    7
symbol1 7  3    7
symbol1 7  2.5  9.5
symbol1 8  2    12
symbol2 8  2    2
symbol1 8  3    12
symbol2 8  3    2
symbol1 8  2    7
symbol2 8  2.5  4.5
symbol1 8  3    7
symbol1 8  2.5  9.5
symbol1 9  2    12
symbol2 9  2    2
symbol1 9  2.5  6.999
symbol1 9  2    8.666
symbol2 9  2    5.333
symbol1 9  3    8.666
symbol2 9  3    5.333
symbol1 9  3    12
symbol2 9  3    2
symbol1 10 2    12
symbol2 10 2    2
symbol1 10 2.5  10.333
symbol2 10 2.5  3.666
symbol1 10 2    8.666
symbol2 10 2    5.333
symbol1 10 3    8.666
symbol2 10 3    5.333
symbol1 10 3    12
symbol2 10 3    2
symbol1 J  2.5  10
symbol1 J  3    10
symbol1 J  2.8  7.5
symbol1 J  2.8  5
symbol1 J  2.4  3.5
symbol1 J  1.9  4.5
symbol1 Q  2    10.5
symbol1 Q  2.5  11
symbol1 Q  3    10.5
symbol1 Q  3.2  8.5
symbol1 Q  3.2  6.5
symbol1 Q  3.05 3.85
symbol1 Q  2.5  4
symbol1 Q  2    4.5
symbol1 Q  1.8  6.5
symbol1 Q  1.8  8.5
symbol1 Q  2.75 5.8
symbol1 K  2    10.5
symbol1 K  2    8
symbol1 K  2    5.5
symbol1 K  2    3
symbol1 K  3    10.5
symbol1 K  2.8  8
symbol1 K  2.6  5.3
symbol1 K  3    3
;


/*** マーク別に1~Kまでのトランプを作成 ***/
%macro trump( unicode=, color= );


/* Attribute Map */
data MYMAP;
     ID="myid";
     VALUE="symbol1"; MARKERCOLOR="&color"; MARKERSYMBOL="symbol1"; output;
     VALUE="symbol2"; MARKERCOLOR="&color"; MARKERSYMBOL="symbol2"; output;
run;


/* Plot */
proc sgplot data=CARD noautolegend dattrmap=MYMAP;
     by NUMBER notsorted;

     xaxis min=-1 max=5   display=none;
     yaxis min=-1 max=15  display=none;

     /* トランプの形に線を引く */
     refline 1.2 3.8  / axis=x;
     refline -1 15 / axis=y;

     /* トランプのマークをUnicodeで取得 */
     symbolchar name=symbol1  char=&unicode;
     symbolchar name=symbol2  char=&unicode / rotate=180;

     /* トランプの左上の数字とマークをPlot */
     text    x=TOP_X    y=TOP_Y1    text=number  / textattrs=(size=0.6cm color=&color) ;
     scatter x=TOP_X    y=TOP_Y2                 / markerattrs=(symbol=symbol1 size=1.3cm color=&color);

     /* トランプの右下の数字とマークをPlot */
     text    x=BOTTOM_X y=BOTTOM_Y1 text=number  / textattrs=(size=0.6cm color=&color)  rotate=180 ;
     scatter x=BOTTOM_X y=BOTTOM_Y2              / markerattrs=(symbol=symbol2 size=1.3cm color=&color) ;

     /* トランプの真ん中のマークをPlot */
     scatter x=X        y=Y                      / attrid=myid  markerattrs=(size=2.1cm) group=symbol;

run;

%mend;

%trump( unicode='2660'x, color=black )  /* Spade */
%trump( unicode='2663'x, color=black )  /* Club  */
%trump( unicode='2665'x, color=red   )  /* Heart */
%trump( unicode='2666'x, color=red   )  /* Dia   */




2018年3月8日木曜日

【SYMBOLGENオプション】展開したマクロ変数の値をログに表示する



「なんのオプションか分からないけど、みんな使ってるから自分も使っとくか」みたいなパターンが多いのがこの「symbolgenオプション」





   options symbolgen;


って書くと、プログラム実行時に展開されたマクロ変数の値がログに表示されます。



例①
%let x=123;
%put &x;


ログ
SYMBOLGEN:  マクロ変数 X を 123 に展開します。
123



例②
%macro test( ds=, out=, by=);

    proc sort data=&ds out=&out;
       by &by;
    run;

%mend;

%test( ds=sashelp.class, out=work.out1, by=name );


ログ
SYMBOLGEN: マクロ変数 DS を sashelp.class に展開します。
SYMBOLGEN: マクロ変数 OUT を work.out1 に展開します。SYMBOLGEN: マクロ変数 BY を name に展開します。




元に戻すには


   options nosymbolgen;



2018年3月5日月曜日

PROC FORMATの「PICTUREステートメント」で気をつけたい内部の動き



PROC FORMATのPICTUREステートメントはちゃんと内部挙動を理解しないと痛い目を見ます。
動きが独特なので、しっかり理解できてる人はSASマイスターとして認定してもいいくらいです。


※今回の内部挙動に関する説明は私の推測も入ってるのであしからず。。間違いがあったらご指摘ください。




PICTUREステートメントの動き

以下のフォーマットを例に内部の動きを説明していきます。

proc format;
   picture TEST1_
       low-high = '009.00%';
run;


① まず、フォーマットをあてる変数に10のn乗を掛けます。(n = 定義した書式の小数点以下桁数)

今回定義した書式は '009.00%' です。小数点以下桁数は2桁で定義されてますよね。
なのでフォーマットをあてる変数に10の2乗を掛けます。


1.23 → 123
3.456 → 345.6
(小数点以下2桁部分を整数部分にシフトさせている。)


②結果から小数部分を切り捨てます。
123 → 123
345.6 → 345


③この値を、定義した書式に右からあてはめていきます。

'009.00%' の数字セレクタ部分に右から数値をあてはめると、
123 → 001.23% → 1.23%
345 → 003.45% → 3.45%





「ROUND」「MULTIPLIER」オプションの動き


以下のフォーマットを例に内部の動きを説明していきます。

proc format;
   picture TEST2_ (round)
      low-high = '009.00%' (multiplier=10) ;
run;


①まず、フォーマットをあてる変数には10のn乗を掛けずに、multiplier=オプションに設定した数を掛けます。


今回は「multiplier=10」なので
1.23 → 12.3
3.456 → 34.56


②roundオプションを指定している場合、小数部分を切り捨てではなく四捨五入します。

12.3 → 12
34.56 → 35


③この値を、定義した書式に右からあてはめていきます。

'009.00%' の数字セレクタ部分に右から数値をあてはめると、
12 → 000.12% → 0.12%
35 → 000.35% → 0.35%



実践


例えば、以下の変数Xには何かの割合が格納されているとします。

data DT1;
   input X;
   cards;
0.123
1
0.456
0.6789
;
run;

X
 0.123 
 1 
 0.456 
 0.6789 


このXを「0.123 → '12.3%'」 のように百分率にした上で%をつけたいとします。
今回解説した内容を踏まえると以下のようになるかと思います。

proc format;
   picture TEST3_ (round)
      low - high = "009.0%" (multiplier=1000);
run;

data DT2;
   set DT1;
   length PCT $30.;
   PCT = put(x,TEST3_.);
run;

X 
 PCT 
 0.123
 12.3% 
 1 
 100.0%
 0.456
 45.6%
 0.6789 
 67.9%



厄介なケース


内部で10のn乗とか計算してることによって、小数点誤差が発生する可能性があります。

「小数点誤差?」って人は以下参照
http://sas-tumesas.blogspot.jp/2014/03/blog-post_14.html(データステップ100万回)


小数点誤差が問題になる例
proc format;
    picture TEST4_
       low-high = '009.000000000' ;
run;

data a;
    format i test4_.;
    i=4.8;
run;

i
 4.799999999 

「4.8」が「4.7999…」となってしまいます。


以下のようにROUNDオプションで四捨五入しとけば一応は「4.8」と表示されますが、、

proc format;
    picture TEST4_ (round)
       low-high = '009.000000000';
run;

data a;
    format i test4_.;
    i=4.8;
run;

i 
 4.800000000 

以下の最後の方で説明している通り、ROUNDしとけばOKというわけでもないのでご注意下さい。
http://sas-boubi.blogspot.jp/2016/03/put-floating-point-error.html


2018年3月1日木曜日

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



PICTUREステートメントで小数を扱う時に使用する「ROUNDオプション」の説明です。





* PICTUREフォーマットの定義 ;
proc format;
   picture TEST1_
      low - high = '009.00%'
   ;
   picture TEST2_ (round)
      low - high = '009.00%'
   ;
run;


・「ROUNDオプション」をつけると四捨五入してくれます(今回は書式を '009.00%' と定義したので、小数点以下第3位を四捨五入し、第2位まで表示するようにしています)

・「ROUNDオプション」をつけないと切り捨てになります(今回は書式を '009.00%' と定義したので、小数点以下第3位を切り捨てし、第2位まで表示するようにしています)





実際に適当なデータを作って、FORMAT変換後の結果を見てみましょう。

* Sample Data ;
data DT1;
   input X;
   cards;
12
12.34
12.345
;

* Format変換してみる ;
data DT2;
   set DT1;
   length Y1 Y2 $20.;
   Y1 = put(X,TEST1_.);
   Y2 = put(X,TEST2_.);
run;

 X  Y1  Y2 
12 
 12.00%  12.00% 
12.34 
 12.34%  12.34%
 12.345 
 12.34% 12.35%


ここまでそんなに難しくないですよね。
でも実は内部ではちょっと複雑な動きをしています。

とくにPICTUREステートメントで小数を扱う場合に、ちゃんと内部の動きを理解してないと想定外な結果になり痛い目を見ます(以下リンク参照)

(http://sas-boubi.blogspot.jp/2018/03/proc-formatpicture.html)






PROC FORMAT入門 : 記事一覧