ラベル ステートメント: SET の投稿を表示しています。 すべての投稿を表示
ラベル ステートメント: SET の投稿を表示しています。 すべての投稿を表示

2020年5月5日火曜日

【SETステートメント】データセットを縦結合する






DATAステップの「SETステートメント」で複数のデータセットを縦に結合することができます。





構文

DATA     出力データセット;
    SET    データセット1   データセット2 ・・・;
RUN;

  • 「SET」に結合したいデータセットを列挙
  • 「DATA」に結合後に作成するデータセット名を指定




Sample data

data DT1;
input X Y:$2. Z;
cards;
1 aa 10
2 bb 20
;
data DT2;
input X Y:$1.;
cards;
3 c
4 d
;
XYZ
1aa10
2bb20
XY
3c
4d




例1. SETに1つだけデータセットを指定した場合

data OUT1;
    set DT1;
run;
XYZ
1aa10
2bb20

  • SETに指定されたデータセットと同じ中身のデータセットが作成されます。



例2. SETに複数のデータセットを指定した場合

data OUT2;
    set DT1 DT2;
run;
XYZ
1aa10
2bb20
3c.
4d.

  • SETに指定されたデータセットの順に縦結合されます。
  • SETに指定されたデータセット間で
    • 共通する変数名同士を縦に結合し
    • 片方にしかない変数も残してくれます。



例3. 失敗例

data OUT3;
    set DT2 DT1;
run;
XYZ
3c.
4d.
1a10
2b20

ログ
WARNING: 入力データセットによって、変数Yに複数の長さが指定されています。
 データの切り捨ての原因になります。


  • なんかログに WARNING が出ちゃったし、
  • 結合元の DT1 の1行目の Y が「aa」だったのが、結合後「a」になっちゃってる
  • 結合元の DT1 の2行目の Y が「bb」だったのが、結合後「b」になっちゃってる 


これは以下の重要な性質によるものです
  • SETに指定された最初のデータセットの LENGTH が適用される


今回の例では、変数Y のLENGTHは
  • DT2・・・「1」
  • DT1・・・「2」


そして、SETに指定された最初のデータセットは「DT2」なので、結合後の 変数Y のLENGTHは「1」に設定されてしまい、文字切れが起きたというわけですね。



解決策
data OUT4;
    length Y $2.;
    set DT2 DT1;
run;
XYZ
3c.
4d.
1aa10
2bb20

  • SETの前に「LENGTHステートメント」でLENGTHを再設定してあげればOK



SETステートメントの落とし穴


SASに慣れてきたかたでも、わりと陥りやすい落とし穴。
以下も参考まで(わりと上級者向けの内容ではあるかもです・・)
SETステートメントの落とし穴



2016年9月21日水曜日

SETやMERGEをした時、オブザベーション毎に結合元のデータセットがどれなのか判別する方法 【IN=オプション】



SETやMERGEで使えるIN=オプションについて解説をしたいと思います。



構文

SET   データセット1  ( IN = 一時変数1 )
          データセット2  ( IN = 一時変数2 )

          ・・・
;


・ データセット1 から持ってきたオブザベーションの場合「1」、それ以外「0」が 一時変数1 に入る
・ データセット2 から持ってきたオブザベーションの場合「1」、それ以外「0」が 一時変数2 に入る

・ 一時変数名は適当な名前でOK。またこの一時変数は出力データセットには残らない。







サンプルデータ作成
data DT1;
   length A 8. B $10.;
   A=1; B="aa"; output;
   A=2; B="bb"; output;
run;
  A  
  B  
  1 aa
  2 bb

data DT2;
   length A 8. C $10.;
   A=2; C="cc"; output;

   A=3; C="dd"; output;
run;
  A  
  C  
  2 cc
  3 dd


例① SETステートメントでの例
data OUT1;
  set  DT1 (in=FLG1)   DT2 (in=FLG2) ;

  /* 別の変数に格納してデータセットに残す */
  X = FLG1;
  Y = FLG2;
run;
  A  
  B  
  C  
  X  
  Y  
  1 aa  1  0
  2 bb
  1  0
  2 cc  0  1
  3 dd  0  1



例② MERGEステートメントでの例
data OUT2;
  merge  DT1 (in=FLG1)   DT2 (in=FLG2) ;
  by A;

  /* 別の変数に格納してデータセットに残す */
  X = FLG1;
  Y = FLG2;
run;
  A  
  B  
  C  
  X  
  Y  
  1 aa  1  0
  2 bb cc  1  1
  3 dd  0  1




実践例

上の例②で、DT1とDT2の両方に存在するBY値のオブザベーションを特定したい場合、以下の条件を書けばいい事が分かりますね。

if FLG1=1 and FLG2=1 then COMMON_FLG=1;


また、DT1になくて、DT2にのみ存在するBY値のオブザベーションを特定したい場合、以下の条件を書けばいけますね。

if FLG1=0 and FLG2=1 then DT2ONLY_FLG=1;


2015年1月21日水曜日

データセットを縦結合する際、変数名が同じでlengthが異なってたら、最大のlengthに合わせる便利技


長いタイトルですが、SQLのいいとこ取りした便利技の紹介です。


Sample Data

data DT1;
length  A $2.  B $1.;
input A$ B$ ;
cards;
01 a
02 b
02 b
;
run;

data DT2;
length  A $1.  B $2.;
input A$ B$;
cards;
3 cc
4 dd
;
run;

DT1
 B 
  01 
   a    
  02  
   b  
  02
   b  

DT2
 A  
B
   3   
  cc   
   4
  dd  


サンプルデータをまずSETで結合してみます。
data OUT1;
   set DT1 DT2;
run;

ログ
WARNING: 入力データセットによって、変数Bに複数の長さが指定されました。データの切り捨てが発生します。

 B 
  01 
   a   
  02  
   b  
  02
   b  
  3
   c  
  4
   d  

結果を見ると、変数Bの値が、一部文字切れしちゃってますね。


これは、
「set DT1 DT2」 で最初に指定したデータセットDT1にある変数のlengthが、結合後のデータセットに割り当てられるからです。


つまり、変数Bのlengthが、DT1は「$1」、DT2は「$2」と異なっていますが、
上記青字の性質によって、結合時に「$1」が割り当てられます。

なので、DT2から持ってきた変数Bは、length「$1」に合わせて文字を切ってしまいます。


解決法

正攻法として、SETする時にlengthを設定しなおせばokですが、、、

data OUT2;
   length A B $3.;
   set DT1 DT2;
run;



SQLプロシジャ入門14:データセットを縦結合する【UNION】」で紹介した「OUTER UNION CORR」を使えば、
なんと結合するデータセットの中で最大のlengthを勝手に設定してくれます。

proc sql;
   create table OUT3 as
   select * from DT1
      outer union corr
   select * from DT2;
quit;

 B 
  01 
   a   
  02  
   b  
  02
   b  
  3
   cc  
  4
   dd  



ただし、SETステートメントと異なる動きあり。


まず、以下のプログラムと結果を見てみましょう。
*** Sample Data ;
data DT1;
   A = .;
run;

data DT2;
   format A yymmdd10. ;
   A = '01jan2015'd ;
run;

DT1
      A        
         .  

DT2
 A  
 2015-01-01 


*** SETステートメントによる結合 ;
data OUT1;
   set DT1 DT2;
run;

 A 
          .
  2015-01-01 


*** 「OUTER UNION CORR」による結合 ;
proc sql;
   create table OUT2 as
   select * from DT1
      outer union corr
   select * from DT2;
quit;

       A        
           .
      20089 

2つの結合結果を比較してみると、2行目の値が異なってますね。

これは「SETステートメント」の方は、format 「yymmdd10」 が割り当てられていて、
「OUTER UNION CORR」の方は、formatが割り当てられてないというだけで、
値自体は同じものです。


リファレンスから探せなくて確かではないですが、結合したデータセットの format, informat は、
  • 「SETステートメント」: 最初のデータセットDT1にformat, informatが割り当てられてなければ、次のデータセットDT2のformat, informatを割り当てている模様。
  • 「OUTER UNION CORR」: 最初のデータセットDT1のものを割り当てている模様。

他にも差異があるかもしれません。
利用する際はいろいろテストしたり、期待通りの結果になるかご確認ください。


この違いを気にしないシチュエーションであれば、超便利なテクニックですね。

2014年8月13日水曜日

RETAIN vs SETステートメント



SETステートメントの落とし穴として紹介した「値の保持」「変数の初期化」の性質を利用したテクニックを紹介したいと思います。



サンプルデータ

data DT1;
   NO="001";  DAY=1;  POINT="100pt"; output;
   NO="001";  DAY=2;  POINT=""; output;
   NO="001";  DAY=3;  POINT=""; output;
   NO="001";  DAY=4;  POINT="150pt"; output;
   NO="002";  DAY=1;  POINT=""; output;
   NO="002";  DAY=2;  POINT="200pt"; output;
   NO="002";  DAY=3;  POINT=""; output;
   NO="002";  DAY=4;  POINT=""; output;
run;

proc sort data=DT1; by NO DAY; run;

 DT1
 NO  
 DAY 
 POINT 
  001
  1  
 100pt 
  001
  2  
 . 
  001
  3  
 .  
  001
  4  
 150pt
  002
  1  
 . 
  002
  2  
 200pt   
  002
  3  
 .   
  002
  4  
 .   


サンプルデータの説明です。
あるドーナツ屋さんがポイントカードを導入したとします。
顧客NO毎にポイントを管理していて、来店してない日はポイントをNULLにしています。

ここで、来店してない日を、前回来店時のポイントで埋めたいとします。

 NO  
 DAY 
 POINT2 
  001
  1  
 100pt 
  001
  2  
 100pt
  001
  3  
 100pt
  001
  4  
 150pt
  002
  1  
 . 
  002
  2  
 200pt   
  002
  3  
 200pt 
  002
  4  
 200pt

プログラム例

*** 1. RETAINを利用 ;
data DT2;
   length  POINT2 $8.;
   retain  POINT2;
   set  DT1;
   by  NO ;
   if  first.NO then POINT2="";
   if  POINT ^="" then POINT2 = POINT;
run;

*** 2. SETの性質を利用 ;
data DT3;
  set  DT1  DT1(obs=0 rename=(POINT=POINT2));
  by  NO ;
  if  POINT ^="" then POINT2 = POINT;
run;

RETAINについては、ここでは詳細を割愛します。
詳細解説は「RETAINステートメント徹底入門」をご覧ください。


そして、今回紹介したいSETの性質を利用した方法。
(まず一番先頭のリンクの内容を理解しておく必要があります。)

ポイントは上のプログラムで赤字にした箇所。
①「obs=0」は0行読み込むという意味、、つまり1行も読み込まない。
②「rename=」でPOINTをPOINT2にRENAMEしておく。

つまりここでやりたい事は、レコードは読み込まず、PDV上にPOINT2という変数の枠を作っておく事です。

③次に、上のプログラムの青字で示したDT1はPOINT2という変数を持っていません。
つまりこのデータセットからレコードを読み込むとき、PDV上のPOINT2は値の保持機能が働きます。



以下PDV処理の一部




















説明が難しいところなので、分かりづらいかもしれないけど、どうでしょうか。
他にも応用がきくので、いろいろ試してみると面白いです。


📝注意点

今回のテクニックの中で使用している「FIRST.BY変数」は「サブセット化IF」と一緒に使用すると正しく動かなくなる事があります。
(解説記事:「サブセット化IFでありがちな落とし穴」)



2014年8月7日木曜日

SETステートメントの落とし穴2「変数の初期化」


SETステートメントの落とし穴「値の保持」の続きです。


サンプルデータ

data DT1;
   A="003";  B=1;  C=.;  output;
run;

data DT2;
   A="001";  B=1;  output;
   A="001";  B=.;  output;
   A="002";  B=.;  output;
   A="003";  B=.;  output;
;
run;

proc sort data=DT1; by A; run;
proc sort data=DT2; by A; run;

 DT1DT2
 C  
 003 
  1  
  .  
 A  
 B  
 001 
  1  
 001
  . 
 002
  .
 003
  .


問題
サンプルデータより以下のプログラムを実行すると、①②どちらのデータが出来るでしょうか?

data DT3;
   set DT1 DT2;
   by A;
   if  B=1 then C=111;
run;

 ①

  A  
C
  1  
  001 
  1   
 111 
  2 
  001
  . 
 111
  3 
  002
  . 
  . 
  4 
  003
  1
 111
  5
  003
  .
  .

  A  
C
  1  
  001 
  1   
 111 
  2 
  001
  . 
 111
  3 
  002
  . 
 111
  4 
  003
  1
 111
  5
  003
  .
 111

正解は①です!


ポイントは「BY値が変わると、PDV内の変数値が初期化される」という事です。

①の3行目はBY変数Aが「001」から「002」に変わってます。そのためPDV内の変数値が初期化され変数CがNULLになったというわけです。

4行目と5行目のBY値はともに「003」ですが、4行目で読み込んでるデータセットはDT1で、5行目はDT2から読み込んでいます。
これは前回説明しましたが、読み込むデータセットが変わった場合もPDV内の変数値が初期化されるので、5行目のCはNULLになります。


一言でまとめると。。「別のデータセットか別のBY値になったら初期化される」という事です。


2回に分けて落とし穴というタイトルでまとめましたが、
実はこの性質を理解すれば、色々面白いことが出来ちゃうので、また別の機会に紹介したいと思います。

2014年8月5日火曜日

SETステートメントの落とし穴1「値の保持」


今回はわかりやすいように2回くらいに分けて紹介したいと思います。

サンプルデータ

data DT1;
   A="001"; B=1; C=.; output;
   A="002"; B=.; C=.; output;
   A="003"; B=1; C=.; output;
run;

data DT2;
   A="004"; B=.; output;
   A="005"; B=1; output;
   A="006"; B=.; output;
run;

proc sort data=DT1; by A; run;
proc sort data=DT2; by A; run;

 DT1DT2
 C  
 001 
  1  
  .  
 002
  . 
  .  
 003
  1 
  .  
 A  
 B  
 004 
  .  
 005
  1 
 006
  .


問題
サンプルデータより以下のプログラムを実行すると、①②どちらのデータが出来るでしょうか?

data DT3;
   set DT1 DT2;
   if  B=1 then C=111;
run;

 ①
  A  
C
  001 
  1   
 111 
  002
  . 
  .   
  003
  1 
 111 
  004
  .
  .
  005
  1
 111
  006
  .
  .
  A  
C
  001 
  1   
 111 
  002
  . 
  .   
  003
  1 
 111
  004
  .
  .
  005
  1
 111
  006
  .
 111

正解は②です!


解説

はじめに、内部処理の説明は経験に基づくもので独自解釈になってる可能性もあるので、あしからず。。

内部ではまず、
プログラムデータベクトル(PDV)という入れ物に、データを1行ずつ放り込んで処理してから、結果のデータセットDT3に出力しています。

以下、処理の流れ。

























赤字にした 6) と 10) が重要なポイントです。

・読み込むデータセットが変わった場合、PDV内の変数値が初期化される。
・PDV内の変数が、読み込むデータセットにない場合、前の値が保持される。



もし①のような結果が欲しい場合は、以下のようにDT1,DT2双方にない新たな変数名に変えてあげればok。
data DT3;
   set DT1 DT2 ;
   if  B=1 then D=111;
run;

(新たな変数は、次の行を読みにいく度に毎回初期化される性質を持ってるので上記であげた「値の保持」という影響をうけません。)


次回に続く。。