2015年2月24日火曜日

PROC CONTENTSのSHORTオプションはアイディア次第で役に立ちそう。




CONTENTSプロシジャは、データセットの変数定義を出力できますが、、

*** サンプルデータ作成 ;
data DT1;
   retain A C B D  1;
run;

  A  
C
  B  
 D 
  1
  1  
  1  
  1  


*** 変数名を出力 ;
proc contents data=DT1  short  varnum ;
run;




  • SHORT」で出力が簡易的な「変数名のリスト」になります。
  • VARNUM」または「ORDER=VARNUM」で、データセットに格納されてる順 (上の例では「A C B D」の順) で変数名のリストが出力されます。


注意
  • 「SHORT」で出力する「変数名のリスト」の文字が長すぎると、文字が切り捨てられてログにWARNINGが出るらしいです。詳細はリファレンスを参照下さい。
  • 話それるけど、PROC CONTENTSの「OUT=オプション」で変数定義をデータセットに出力できますが、「SHORT」「VARNUM」「ORDER=」といったオプションの機能は「OUT=」で出力するデータセットには効力がないのでご注意を。





このオプション使えそうです。たとえば、、

*** 全変数名を(データセットに格納されてる順で)1つのマクロ変数に格納する ;
ods output PositionShort = OUT1;
    proc contents data=DT1  short  varnum;
    run;
ods output close;

data _NULL_ ;
    set OUT1 ;
    call symputx("VARS", VARIABLES);
run;

%put &VARS;

ログ
A C B D




あとデータステップ100万回のSASYAMAさんから、以下のように全変数名をアルファベット順で1つのマクロ変数に格納したい場合に役に立ったとコメントいただきました。なるほど~。
(すみません、色々いじってたら肝心のSASYAMAさんのコメント消してしまっていました。。)

*** 全変数名を(アルファベット順で)1つのマクロ変数に格納する ;
ods output VariablesShort = OUT2;
    proc contents data=DT1  short  order=casecollate;
    run;
ods output close;

data _NULL_ ;
    set OUT2;
    call symputx("VARS2", VARIABLES);
run;

%put &VARS2;

ログ
A B C D


  • 「ORDER=CASECOLLATE」でアルファベット順にしています。
  • また、「VARNUM」とか「ORDER=」のオプション設定値によって、「ODS OUTPUT」の出力オブジェクトの名前が「PositionShort」だったり「VariablesShort」だったり変わるみたいなのでご注意を。



2015年2月20日金曜日

「SAS Studio」で、エディタやログの位置をカスタマイズする。



今回紹介する機能や画面構成などは、環境やバージョンによって変更される可能性があります。

まずデフォルトの画面がこんな感じです↓




















ここでコード(エディタ)の位置を変えたいとします。
まず、「コード」タブを選択して、コードをアクティブにします。







そうしたら、「コード」タブをクリックして下の方までそのままドラッグすると、以下のように色が変わるので、


























マウスを離すと、コード画面を分離することが出来ます。





















同じようにログや結果の画面も分離できます。





















右端にドラッグして横並びにすることも出来ます。



2015年2月16日月曜日

Hashオブジェクトを使おう。



複数のデータセットをこねくり回して、1個のデータセットを作りあげたい時、
ハッシュオブジェクトを使うと、1回のデータステップですべての処理が済んでしまうため、プログラムを書く時間も可読性も大幅に改善できることがあります。

データステップ100万回のSASYAMAさんの得意技で、「ハッシュオブジェクトの世界①」をはじめとして、沢山記事をあげられてるので、かなり参考になります。



では私自身が実際に使ってる簡単な例を紹介したいと思います。

*** サンプルデータ作成 ;
data DT1;
input NO$ SEX$ WEI;
cards;
001 F 50.5
002 M 68.8
;

data DT2;
input NO$ FOOD:$20.;
cards;
001 サンドイッチ
001 みかん
002 おにぎり
002 サンドイッチ
;

DT1
NO
 SEX 
WEI 
  001  
  F  
  50.5  
  002  
  M  
  68.8  

DT2
 NO 
FOOD
  001  
  サンドイッチ   
  001
  みかん  
  002
  おにぎり
  002
  サンドイッチ

DT1は、患者(NO)ごとの性別(SEX)や体重(WEI)などの基本情報が格納されたデータセット、
DT2は、患者(NO)が食べた朝食(FOOD)を表したデータセットだとします。


ここで例えば、DT2にみかんを食べた患者がいたら、DT1に性別・体重と並んでみかんを食べた事を表すフラグ変数を追加した新たなデータセットを作りたいとします。

data OUT1;
    set DT1;

    *** ハッシュオブジェクトを定義 ;
    if _N_=1 then do;
         dcl hash hs( dataset:"DT2(where=(FOOD='みかん'))", multidata:"y" );
         hs.definekey( "NO" );
         hs.definedone();
    end;

    *** みかんフラグを立てる ;
    if hs.check()=0 then FLG=1;

run;

NO
SEX
WEI
 FLG 
  001  
  F  
  50.5  
    1  
  002  
  M  
  68.8  
    . 


仕組みについては、上の方に貼ったSASYAMAさんの記事リンクを見た方が分かり易いかも。

一応、ざっくり解説

if _N_=1 then do;
   dcl hash hs( dataset:"DT2(where=(FOOD='みかん'))", multidata:"y" );
   hs.definekey( "NO" );
   hs.definedone();
end;

まずオブザベーションを読み込むたびに実行する必要がない箇所、
つまり最初の1回実行するだけでいい箇所を「if _N_=1 then do;」と「end;」で囲んであげる。


   dcl hash hs( dataset:"DT2(where=(FOOD='みかん'))", multidata:"y" );

DT2からFOOD='みかん'のデータを抽出してから、


   hs.definekey( "NO" );
   hs.definedone());

変数NOをkey変数としてハッシュオブジェクトに入れてます。
ここまでで、まずはFOOD='みかん'のNOをハッシュオブジェクトに入れることが出来ました。


ちなみに「dcl hash hs~」のところで「multidata:"y"」と書いてる部分は今回は書かなくても問題ないけど説明のために入れました。
ハッシュオブジェクトはデフォルトの動作として、Keyが重複しているレコードがある場合、重複Keyの中から最初のレコードのみハッシュオブジェクトに保持します。「multidata:"y"」で重複Keyのすべてのレコードを保持します。



if hs.check()=0 then FLG=1;

CHECKメソッドで、DT1のNOと同じ値がハッシュオブジェクト内のNOに存在するかチェックを行います。
存在してれば戻り値として0が返されるので、みかんフラグを立てます。


ちょっと応用。

今度は、以下3つのフラグを立ててみましょう。

・ サンドイッチを食べた患者
・ おにぎりを食べた患者
・ みかんを食べた患者

data OUT2;
    set DT1;

    *** ハッシュオブジェクトを定義 ;
    if _N_=1 then do;
         length FOOD $20.;
         call missing( FOOD );
         dcl hash hs( dataset:"DT2", multidata:"y" );
         hs.definekey( "NO", "FOOD" );
         hs.definedone();
    end;

    *** 各フラグを立てる ;
    if hs.check( key:NO, key:"サンドイッチ" )=0 then FLG1=1;
    if hs.check( key:NO, key:"おにぎり" )=0      then FLG2=1;
    if hs.check( key:NO, key:"みかん" )=0        then FLG3=1;

    drop FOOD;
run;


NO
SEX
WEI
 FLG1 
 FLG2 
 FLG3 
  001  
  F  
  50.5  
    1  
    .  
    1  
  002  
  M  
  68.8  
    1
    1 
    . 


ざっくり解説。

dcl hash hs( dataset:"DT2", multidata:"y" );
hs.definekey( "NO","FOOD" );
hs.definedone());

DT2のNOとFOODをkey変数としてハッシュオブジェクトに入れて、


if hs.check( key:NO, key:"サンドイッチ")=0 then FLG1=1;

CHECKメソッドで、検索をかける値を指定して、うまくヒットしたらフラグを立てています。
(DEFINEKEYメソッドに指定したkeyの順に検索値を指定する)


1つ注意することは、以下のメソッドで、

hs.definekey( "NO","FOOD" );

ハッシュオブジェクトに入れるNOとFOODは、データステップ内にも同じ名前の変数NOとFOODが存在している必要があります。(存在してないとエラーがでる)

NOとFOODのうち、NOはDT1に存在してるのでokですが、FOODはありません。なので、

length FOOD $20.
call missing( FOOD );

こんな感じでまずはデータステップ内の変数としてFOODを定義してあげます。
ここがちょっと独特な仕様ですね。


📝話がそれますが1つ注意

記事の中で使用している「_N_」は「サブセット化IF」と一緒に使用すると正しく動かなくなりやすいです。




便利さは使い込んでみてよく分かるので、もっともっと普及してほしいです。
私もSASYAMAさんに続き、ハッシュオブジェクトを推していきたいです。


2015年2月9日月曜日

EXCELを読み込む方法と落とし穴 【LIBNAME EXCEL編】



Windows環境での機能で、SAS/ACCESSのライセンスも必要ですが、LIBNAMEでEXCELを読み込めます。
えぐい落とし穴もあるので最後までご覧ください。


以下のEXCELファイルを読み込みたいとします。

C:\test.xlsx
















データ部分のセル書式は「A列: 標準」「B列: 標準」「C列: 日付」「D列: 時刻」としています。







*** 正しく読み込めないので注意 ******;
* libnameで、EXCELファイルを参照する ;
libname TEST excel "C:\test.xlsx"
                header = yes
                dbmax_text = 8000
                scan_timetype = yes
                mixed = yes ;

* SET ;
data OUT1;
   set TEST."Sheet1$"n;
run;

* EXCELファイルを解放 ;
libname TEST clear;

OUT1
 
  B  
 C 
 D 
   1
  1
                 .
           .
   2
  1
 10JAN2012 
 10:20:00
   3
  1
                 .
           .
   4
  1
                 .
           .
   5
  
                 .
           .
   6
  
 20OCT2012
           .
   7
  1 
                 .
           .
   8
  1 
                 .
           .
   9
      
                 .
           .

黄色で示した部分は「aaa」という文字が読み込まれるはずだけど、欠損値になってしまう事に注目。



解説

libname TEST excel "C:\test.xlsx"
                header = yes
                dbmax_text = 8000
                scan_timetype = yes
                mixed = yes ;

LIBNAMEでEXCELファイルを参照出来るようにしています。
以下が構文(オプションは一部抜粋)

libname  参照名  excel  "EXCELファイルのフルパス"
   header             = yes | no  … 読み込み範囲の最初の行を変数名として読み込むか 
   dbmax_text      = 数値      … 文字変数の最大LENGTHを指定
   scan_timetype  = yes | no  … 項目のデータ型が時刻かどうかスキャンして取り込むか
   mixed              = yes | no  … 1つの項目に数値と文字が混在してたら、文字変数として取り込むか
;

data OUT1;
   set TEST."Sheet1$"n;
run;

EXCELの中身を、「参照名."EXCELシート名$"n」という感じで参照できます。


libname TEST clear;

最後にEXCELファイルを解放。




落とし穴(環境により動作は異なる)


実は、LIBNAME EXCELでの読み込みでは、EXCELのデータ部分の先頭8行だけを調べて、データ型などの属性が決定されているようです。
(レジストリの設定とか、環境によっては異なる可能性あり)

この「先頭8行ルール」により以下①~③の読み込み失敗例が考えられます。



① mixed=yesのオプションをつけると、数値と文字が混在してる項目を、文字変数にしてくれるのですが、、、

「先頭8行ルール」により、先頭8行に数値しかない場合、数値変数と判断されます。
そこで、もし9行目以降に文字があると、そこが取り込めず、欠損値になってしまいます。



黄色セルが、欠損値になってしまうやつ。











② 「先頭8行ルール」により、以下のように先頭8行が全て255バイト以内の文字で、9行目以降に256バイト以上の文字がある場合、256バイト以降の文字が切れたり、「?」とか変な文字になる。




黄色セルが、変になるやつ。















③ 「先頭8行ルール」により、以下のように先頭8行の書式が「標準」や「数値」で数値のみが入力されていると数値変数と判断されて、9行目以降に書式が「時刻」や「日付」で時刻(または日付)が入力されていると、9行目以降の当該データはEXCELのシリアル値で読み込まれてしまいます。



「10:20」の場合は「0.430555・・・」というシリアル値で読み込まれる。










ここまでが「先頭8行ルール」による落とし穴で、他にも以下のような落とし穴がある。

④ これもかなり特定の条件ですが、以下のように数値と256バイト以上の文字のみで構成されてるデータで、書式が「標準」や「数値」になってる場合、数値変数と判断されて、文字はすべて欠損値になってしまう。



黄色セルが、欠損値になってしまうやつ。










⑤ オプションの設定によって挙動が変わりますが、今回紹介したプログラムの設定のまま以下のEXCEL (データ部分の書式は「A列: 日付」「B列: 時刻」「C列: ユーザー定義[yyyy/m/d h:mm]」) を読み込むと、、









変数Cが日付値として読み込まれてしまいました。
  B  
 C 
 01JAN2012 
 12:28:00 
 01FEB2012
 02JAN2012
 13:00:00
 02FEB2012 



一応、以下の青文字で示した「SASDATEFMT=」というオプションに、対応させたいフォーマットを指定すると日時として読み込めるようです。
libname TEST excel "C:\test.xlsx"
                header = yes
                dbmax_text = 8000
                scan_timetype = yes
                mixed = yes ;

data OUT1;

   set TEST."Sheet1$"n (sasdatefmt=(C=datetime19.));
run;



⑥ データステップ等でLIBNAME EXCELのファイルにアクセスする際、WHEREステートメントは使わない方が良さそう。
というのも、変数に欠損値があるとWHEREステートメントでうまくオブザベーションが抽出できない場合がありました。SAS社のリファレンスからはこの辺の説明が見つけられなかったので、環境によるものかも不明です。

data OUT1;
   set TEST."Sheet1$"n;
   where B^=1;   /* ← NG */
run;




以上、わたしが気づいた範囲のえぐい落とし穴でした。

実は根本的な解決法が無いのが現状です。
一応自分なりに、こうするしかないかなぁー。。という方法はあるので、記事としてまとめられたら紹介したいと思います。

追記
いろいろ調べた結果、次回紹介予定の方法で正しく動く確証がもてなかったので、他にいい方法がないか、引き続き調査したいと思います。

この方法を使う際は、動作環境によってうまく動かないことがあったり、EXCELの仕様など不確定要素があったりなので、想定通りに読み込めているかを確認した方が良さそうです。



2015年2月5日木曜日

TRANSPOSEで転置するときラベルを付与する方法。



まずは例を見ていきましょう、

*** Sample Data ;
data DT1;
input NO:$1. MONTH:$3. YEN;
cards;
A JAN 5000
A FEB 500
A MAR 1000
B JAN 2000
B MAR 1500
;

NOMONTHYEN
AJAN5000
AFEB500
AMAR1000
BJAN2000
BMAR1500

サンプルは、各預金者(NO)に対する、月別(MONTH)の預金額(YEN)だとします。


ここで、以下のイメージで各預金者(NO)ごとに預金額を転置して「ついでに変数ラベルも付与したい」とします。

変数ラベルJanuaryFebruaryMarch
変数名NOJANFEBMAR

A50005001000

B20001500



この場合、TRANSPOSEで、ID, IDLABELステートメントを組み合わせれば、一発解決です。

*** ラベル用の変数を用意 ***************;
data DT2;
     length  LAB $20.;
     set DT1;
     if MONTH = "JAN" then LAB="January";
     if MONTH = "FEB" then LAB="February";
     if MONTH = "MAR" then LAB="March";
run;

LABNOMONTHYEN
JanuaryAJAN5000
FebruaryAFEB500
MarchAMAR1000
JanuaryBJAN2000
MarchBMAR1500


*** 転置 *****************************;
proc transpose  data=DT2  out=OUT1 (drop=_NAME_);

    *** 預金者毎に、預金額を転置 ;
     var  YEN;
     by   NO;

    *** 変数MONTH の値を、転置後の変数名に使う ;
     id  MONTH;
     format  MONTH;

    *** 変数LABの値を、転置後の変数ラベルに使う ;
     idlabel  LAB;

run;

変数ラベルJanuaryFebruaryMarch
変数名NOJANFEBMAR

A50005001000

B20001500


補足として、IDLABELを使う場合は、以下2点に気を付けましょう。
  • IDステートメントも同時に指定する必要がある。
  • IDステートメントとIDLABELステートメントの変数の値は、1対1になってる方が望ましい。
(以下では、MONTH="JAN"の時に、LAB="January" だったり "JAN" だったりと、1対2の関係になってしまってるので、望ましくない。)

LABNOMONTHYEN
JanuaryAJAN5000
FebruaryAFEB500
MarchAMAR1000
JANBJAN2000
MarchBMAR1500



2015年2月3日火曜日

UNIVARIATEプロシジャの結果をデータセットに出力する方法のひとつ「OUTTABLE=」




UNIVARIATEプロシジャは、1変数に対する様々な統計量を求めることが出来ます。

結果をデータセットに出力する方法として、以下の方法が用意されています。
 ・OUTPUTステートメント
 ・ODS OUTPUT
 ・OUTTABLE=

この中で、OUTTABLE=で出力する方法は便利だけどあまり知られてないので、紹介したいと思います。


* 適当なサンプル ;
data DT1;
input A B C;
cards;
10 10 10
20 30 10
30 20 20
;

* UNIVARIATEプロシジャ ;
* (記事の下に注意点を記載してるので参照下さい) ;

proc univariate data=DT1 outtable=OUT1;
   var A B C;
run;


・・・





統計量として、40個くらいの変数が出来ます。

基本統計量や検定などの結果が、分析変数毎に1行で出力されるので、
他の出力方法と比較して、その後の加工がしやすいです。


注意点

「OUTTABLE=」による出力と「OUTPUTステートメント」による出力で、以下の差異を見つけました。
  • 「OUTTABLE=」でデータセットに出力される変数「_NOBS_」は分析変数が非欠損値のオブザベーション数
  • 「OUTPUT OUT=OUT2 NOBS=OBS」のように書いたときの「NOBS=」は全オブザベーション数(分析変数が欠損値のオブザベーションも含む)