データ操作#

このセクションでは、3つの主要なトピックについて説明します。

  1. 横持ち(ワイド)形式から縦持ち(ロング)形式へのデータ変形

  2. 縦持ち(ロング)形式から横持ち(ワイド)形式へのデータ変形

  3. データセットの結合

データを変形するために、次の2つの方法について説明します。

  • PROC TRANSPOSE

  • 配列を使ったデータステップ

2番目の一般的な方法を理解するには、まずいくつかのSASプログラミングのキーワードと構造について学ぶ必要があります。

  • OUTPUTステートメントとRETAINステートメント

  • ループ

  • 配列

  • FIRST.変数とLAST.変数

OUTPUTステートメントとRETAINステートメント#

データステップを処理する際、SASは2つの手順に従います。

  1. データステップの開始時にSASがDATAステートメントを読み取ると、INPUTステートメントまたは割り当てステートメントによって割り当てられた変数に欠損値を設定します。(合計ステートメントによって作成された変数、またはSETステートメントやMERGEステートメントによってデータセットから値が来た場合は、変数は欠損値にリセットされません。)

  2. データステップの最後で、変数の値をプログラムデータベクトル(PDV)から作成中のデータセットに出力します。

ここでは、OUTPUTステートメントとRETAINステートメントを使ってこれらのデフォルトの動作を変更する方法を扱います。

  • OUTPUTステートメントを使うと、オブザベーションをいつ、どのデータセットに書き出すかを制御できます。

  • RETAINステートメントは、データステップで作成された変数が次のオブザベーションに渡されるように指示するので、各反復の開始時に欠損値にリセットされることがありません。

OUTPUTステートメント#

OUTPUTステートメントを使うと、デフォルトの動作を上書きして、OUTPUTステートメントが処理されたタイミングで現在のオブザベーションを出力するようにできます。OUTPUTステートメントの形式は次のようになります。

OUTPUT dataset1 dataset2 ... datasetn;

ここで、dataset1、dataset2、…、datasetnはデータセット名で、いくつでも指定できます。データセット名を指定しないでOUTPUTステートメントを使うと、SASはDATAステートメントで名前が付けられたすべてのデータセットに現在のオブザベーションを書き出します。OUTPUTステートメントで指定されているデータセット名は、すべてDATAステートメントにも指定されている必要があります。

OUTPUTステートメントは非常に強力で、次のようなことができます。

  • 複数のデータセットにオブザベーションを書き出す

  • 特定の条件に基づいてデータセットへのオブザベーションの出力を制御する

  • OUTPUTステートメントとRETAINステートメント、BY グループ処理、LAST.変数を組み合わせることで、データセットを転置する

このセクションの残りの部分では、OUTPUTステートメントを正しく使う方法を示す例を見ていきます。ここでは、ICDB研究のログデータセット(icdblog.sas7bdatはREADMEを参照)の一部を使って進めます。

libname PHC6089 "/folders/myfolders/SAS_Notes/data/";

proc print data = phc6089.icdblog (obs=5);
run;
SAS 出力

SAS システム

OBS SUBJ V_TYPE V_DATE FORM
1 210006 12 05/06/94 cmed
2 210006 12 05/06/94 diet
3 210006 12 05/06/94 med
4 210006 12 05/06/94 phytrt
5 210006 12 05/06/94 purg

ログデータセットには、次の4つの変数が含まれています。

  • subj: 被験者の識別番号

  • v_type: 診療所の来院回数で、最初の来院から何ヶ月経過したかを表す

  • v_date: 診療所の来院日

  • form: 被験者の来院時に記入されたデータフォームのコード

ログデータセットは、全国的な臨床研究で典型的に見られるデータセットの一種です。国内の複数の拠点で集められたデータが、データ調整センター(DCC)に集められます。DCCでデータフォームを追跡管理するのは大変な作業です。例えば、ICDB研究では試験期間中に68,000を超えるデータフォームが集められました。

DCCに届いたデータフォームは、データベースに記録されその後の処理が追跡されます。実際のログデータベースには、ここで扱う4つの変数以外にもデータがデータベースに入力された日付、入力者、データの検証日、検証者など多くの変数が含まれています。ここでは単純化するために、これらの4つの変数のみを扱います。

#

この例では、OUTPUTステートメントを使ってデータセットへのオブザベーションの書き出し条件を指定しています。具体的にはOUTPUTステートメントを使って、データセットicdblogの被験者識別番号が特定の条件を満たすかどうかに応じて、3つのデータセットs210006、s310032、s410010を作成しています。

libname PHC6089 "/folders/myfolders/SAS_Notes/data/";

data s210006 s310032 s410010;
  set phc6089.icdblog;
      if (subj = 210006) then output s210006;
  else if (subj = 310032) then output s310032;
  else if (subj = 410010) then output s410010;
run;

title 'The s210006 data set'; 
proc print data = s210006 (obs=5) noobs;    
run;

title 'The s310032 data set'; 
proc print data = s310032 (obs=5) noobs;
run;

title 'The s410010 data set'; 
proc print data = s410010 (obs=5) noobs;    
run;
SAS 出力

The s210006 data set

SUBJ V_TYPE V_DATE FORM
210006 12 05/06/94 cmed
210006 12 05/06/94 diet
210006 12 05/06/94 med
210006 12 05/06/94 phytrt
210006 12 05/06/94 purg

The s310032 data set

SUBJ V_TYPE V_DATE FORM
310032 24 09/19/95 backf
310032 24 09/19/95 cmed
310032 24 09/19/95 diet
310032 24 09/19/95 med
310032 24 09/19/95 medhxf

The s410010 data set

SUBJ V_TYPE V_DATE FORM
410010 6 05/12/94 cmed
410010 6 05/12/94 diet
410010 6 05/12/94 med
410010 6 05/12/94 phytrt
410010 6 05/12/94 purg

DATAステートメントには、s210006、s310032、s410010の3つのデータセット名が含まれています。これはこれらの名前のデータセットを作成するように指示しています。SETステートメントは、永久データセットstat481.icdblogからオブザベーションを読み込むようしています。次にIF-THEN-ELSEステートメントとOUTPUTステートメントが効果を発揮します。最初のIF-THENは、被験者210006のオブザベーションをs210006データセットに出力するようにしています。2番目のIF-THENは、被験者310032のオブザベーションをs310032データセットに出力するようにしています。3番目のIF-THENステートメントは、被験者410010のオブザベーションをs410010データセットに出力するようにしています。OUTPUTステートメントにデータセット名を指定していないと、エラーとなります。
PRINTプロシージャは、新しく作成された3つのデータセットを出力するようにしています。最後のPRINTプロシージャにはDATA=オプションがありません。DATAステートメントで複数のデータセット名を指定すると、最後の名前が最新に作成されたデータセットになるので、後続のプロシージャではそのデータセットが自動的に使用されるからです。したがって、最後のPRINTプロシージャはs410010データセットを自動的に出力します。
ここではIF-THEN-ELSEがOUTPUTステートメントと同時に使用されていて、DATAステートメントでデータセットオプションに WHERE=を使用したときと同じ動作になっていることに注目してください。
プログラムの実行前にデータセットicdblogが保存されていることを確認し、LIBNAMEステートメントを保存先に合わせて変更してください。

#

OUTPUTステートメントを使うと、データステップの最後でのオブザベーションの自動出力が抑制されます。したがって、データステップでOUTPUTステートメントを使う場合は、そのステップのすべての出力をOUTPUTステートメントでプログラミングする必要があります。次のプログラムは、すべてのオブザベーションの出力を指示しなかった場合の動作を示しています。

data subj210006 subj310032;
  set phc6089.icdblog;
  if (subj = 210006) then output subj210006;
run;

title 'The subj210006 data set'; 
proc print data = subj210006 noobs;    
run;
SAS 出力

The subj210006 data set

SUBJ V_TYPE V_DATE FORM
210006 12 05/06/94 cmed
210006 12 05/06/94 diet
210006 12 05/06/94 med
210006 12 05/06/94 phytrt
210006 12 05/06/94 purg
210006 12 05/06/94 qul
210006 12 05/06/94 sympts
210006 12 05/06/94 urn
210006 12 05/06/94 void
title 'The subj310032 data set';
proc print data = subj310032 noobs;   
run;
11                                                        SAS システム               2024年 6月 6日 木曜日 08時09分00秒

83         ods listing close;ods html5 (id=saspy_internal) file=_tomods1 options(bitmap_mode='inline') device=svg style=HTMLBlue;
83       ! ods graphics on / outputfmt=png;
84         
85         options notes ;
86         title 'The subj310032 data set';
87         proc print data = subj310032 noobs;
88         run;

NOTE: データセットWORK.SUBJ310032にオブザベーションがありません。
NOTE: PROCEDURE PRINT処理(合計処理時間):
      処理時間           0.00 秒
      ユーザーCPU時間    0.00 秒
      システムCPU時間    0.00 秒
      メモリ             465.78k
      OSメモリ           18336.00k
      タイムスタンプ     2024/06/06 午前08:10:19
      ステップ数                        9  スイッチ数  0
      ページフォルト回数                0
      ページリクレーム回数              70
      ページスワップ回数                0
      自発的コンテキストスイッチ回数    0
      非自発的コンテキストスイッチ回数  0
      ブロック入力操作回数              0
      ブロック出力操作回数              0
      

89         
90         
91         ods html5 (id=saspy_internal) close;ods listing;
92         

12                                                        SAS システム               2024年 6月 6日 木曜日 08時09分00秒

93         

DATAステートメントには2つのデータセット名subj210006とsubj310032が含まれているので、この2つのデータセットを作成ます。しかし、IFステートメントにはデータセットsubj210006への出力を指示するOUTPUTステートメントがあるものの、subj310032データセットへの出力を指示するOUTPUTステートメントがありません。プログラムを実行すると、データセットsubj210006には被験者210006のデータが入っているのに対し、データセットsubj310032にはオブザベーションが0例であることがわかります。

#

データステップ内でOUTPUTステートメントを使い、新しい変数を割り当てステートメントで作成する場合、その割り当てステートメントをOUTPUTステートメントの前に置く必要があります。そうしない場合、すでにオブザベーションをデータセットに書き出した後になって、新しい変数の値が計算されるので、その変数の値は欠損値になってしまいます。次のプログラムは、OUTPUTステートメントがあるときに、変数「current」と「days_vis」をデータセットに書き込もうとすると、欠損値になってしまう例を示しています。

data subj210006 subj310032 subj410010;
  set phc6089.icdblog;
      if (subj = 210006) then output subj210006;
  else if (subj = 310032) then output subj310032;
  else if (subj = 410010) then output subj410010;
  current = today();
  days_vis = current - v_date;
  format current mmddyy8.;
run;

title 'The subj310032 data set';
proc print data = subj310032 noobs;    
run;
SAS 出力

The subj310032 data set

SUBJ V_TYPE V_DATE FORM current days_vis
310032 24 09/19/95 backf . .
310032 24 09/19/95 cmed . .
310032 24 09/19/95 diet . .
310032 24 09/19/95 med . .
310032 24 09/19/95 medhxf . .
310032 24 09/19/95 phs . .
310032 24 09/19/95 phytrt . .
310032 24 09/19/95 preg . .
310032 24 09/19/95 purg . .
310032 24 09/19/95 qul . .
310032 24 09/19/95 sympts . .
310032 24 09/19/95 urn . .
310032 24 09/19/95 void . .

この例で重要なのは、「current」と「days_vis」の割り当てステートメントがIF-THEN-ELSEステートメントとOUTPUTステートメントの後に来ていることです。つまり、各オブザベーションがいずれかの出力データセットに書き出された後で、「current」と「days_vis」の値が計算されることになります。データステップで作成された変数は、反復のたびに欠損値にリセットされるので、「current」と「days_vis」の値はすべてのオブザベーションについて欠損値のままになります。
ところで、「current」に代入されているtoday()関数は今日の日付値を作成します。そのため、「days_vis」は被験者の来院日「v_date」からの日数が格納されています。しかし上で記載のように「current」と「days_vis」は欠損値となります。プログラムを開いて実行し、データセットsubj310032の「current」と「days_vis」が欠損値だけであることを確認してみてください。もしデータセットsubj210006と subj410020も同様に出力しても結果は同じになっているでしょう。

次のプログラムは、OUTPUTステートメントがある場合の正しい割り当てステートメントのコードです。

data subj210006 subj310032 subj410010;
  set phc6089.icdblog;
  current = today();
  days_vis = current - v_date;
  format current mmddyy8.;
      if (subj = 210006) then output subj210006;
  else if (subj = 310032) then output subj310032;
  else if (subj = 410010) then output subj410010;
run;

title 'The subj310032 data set'; 
proc print data = subj310032 noobs;
run;
SAS 出力

The subj310032 data set

SUBJ V_TYPE V_DATE FORM current days_vis
310032 24 09/19/95 backf 06/06/24 10488
310032 24 09/19/95 cmed 06/06/24 10488
310032 24 09/19/95 diet 06/06/24 10488
310032 24 09/19/95 med 06/06/24 10488
310032 24 09/19/95 medhxf 06/06/24 10488
310032 24 09/19/95 phs 06/06/24 10488
310032 24 09/19/95 phytrt 06/06/24 10488
310032 24 09/19/95 preg 06/06/24 10488
310032 24 09/19/95 purg 06/06/24 10488
310032 24 09/19/95 qul 06/06/24 10488
310032 24 09/19/95 sympts 06/06/24 10488
310032 24 09/19/95 urn 06/06/24 10488
310032 24 09/19/95 void 06/06/24 10488

割り当てステートメントがOUTPUTステートメントの前にあるので、変数が正しく出力データセットに書き込まれます。つまり、変数「current」にはプログラムが実行された日付が入り、変数「days_vis」には被験者の来院日からその日までの日数が入ります。 プログラムを起動して実行し、データセットsubj310032の「current」と「days_vis」が正しく書き込まれていることを確認してみてください。もしデータセットsubj210006と subj410020も同様に出力しても結果は同様になるでしょう。

#

OUTPUTステートメントをデータステップで処理した後、オブザベーションはプログラムデータベクトル(PDV)に残り、さらに処理を続けることができます。同じデータセットまたは別のデータセットに、そのオブザベーションを再び出力することもできます。次のプログラムは、一部のオブザベーションを共有する異なるデータセットを作成する方法を示しています。つまり、DATAステートメントで作成するデータセットは必ずしも相互に排他的である必要はありません。

data symptoms visitsix;
  set phc6089.icdblog;
  if form = 'sympts' then output symptoms;
  if v_type = 6 then output visitsix;
run;

title 'The symptoms data set'; 
proc print data = symptoms noobs;    
run;

title 'The visitsix data set'; 
proc print data = visitsix noobs;    
run;
SAS 出力

The symptoms data set

SUBJ V_TYPE V_DATE FORM
210006 12 05/06/94 sympts
310032 24 09/19/95 sympts
410010 6 05/12/94 sympts

The visitsix data set

SUBJ V_TYPE V_DATE FORM
410010 6 05/12/94 cmed
410010 6 05/12/94 diet
410010 6 05/12/94 med
410010 6 05/12/94 phytrt
410010 6 05/12/94 purg
410010 6 05/12/94 qul
410010 6 05/12/94 sympts
410010 6 05/12/94 urn
410010 6 05/12/94 void

データステップでデータセットsymptomsとvisitsixが作成されます。データセットsymptomsは「form」= “sympts” のオブザベーションみを含みます。一方データセットvisitsixは「which v_type」= 6 のオブザベーションのみを含みます。そのため、この2つのデータセットは相互に排他的ではありません。プログラムを開いて実行し、PRINTプロシージャの出力を確認してください。「subject」= 410010 で「form」 = “sympts”のオブザベーションはデータセットsymptomsとvisitsixの両方に含まれていることに注意してください。

RETAINステートメント#

データステップの開始時にDATAステートメントを読み取ると、INPUTステートメントまたは割り当てステートメントで割り当てられた変数には欠損値が設定されます。RETAINステートメントを使うと、このデフォルトの動作を上書きできます。つまり、RETAINステートメントを使うと、INPUTステートメントまたは割り当てステートメントで値が割り当てられた変数の値が、次の反復に進むときに欠損値にリセットされないようになり、代わりにその値を保持します。RETAINステートメントの一般的な形式は次のとおりです。

RETAIN variable1 variable2 ... variablen;

変数名は1つでも複数でも指定できます。変数名を指定しない場合、INPUTステートメントまたは割り当てステートメントで作成されたすべての変数の値を保持します。RETAINステートメント内で変数の値を初期化することもできます。例えば、次のステートメント

RETAIN var1 0 var2 3 a b c 'XYZ'

では、「var1」には0が、「var2」には3が割り当てられ、「a」、「b」、「c」には文字列’XYZ’が割り当てられます。初期値を指定しない場合、保持される変数の初期値を欠損値に設定します。

最後に、RETAINステートメントは実行可能なステートメントではないので、データステップのどこに置いても構いません。

以降の部分では、次のデータステップで作成されるデータセットgradesを使用して進めます。

data grades;
  input idno 1-2 l_name $ 5-9 gtype $ 12-13 grade 15-17;
  cards;
10  Smith  E1  78
10  Smith  E2  82
10  Smith  E3  86
10  Smith  E4  69
10  Smith  P1  97
10  Smith  F1 160
11  Simon  E1  88
11  Simon  E2  72
11  Simon  E3  86
11  Simon  E4  99
11  Simon  P1 100
11  Simon  F1 170
12  Jones  E1  98
12  Jones  E2  92
12  Jones  E3  92
12  Jones  E4  99
12  Jones  P1  99
12  Jones  F1 185
;
run;

title 'The grades data set'; 
proc print data = grades (obs=5) noobs;    
run;
SAS 出力

The grades data set

idno l_name gtype grade
10 Smith E1 78
10 Smith E2 82
10 Smith E3 86
10 Smith E4 69
10 Smith P1 97

データセットgradesは、「被験者別・成績別」データセットと呼ばれるものです。つまり、被験者ごと、成績ごとに1つのオブザベーションがあります。被験者は識別番号(idno)と名字(l_name)で特定されます。このデータセットには、6種類の成績が含まれています。100点満点の4回の試験(E1、E2、E3、E4)、100点のプロジェクト(P1)、200点の期末試験(F1)です。ここでは講師が4回の試験のうち最低の成績は参照しないことに同意したとしましょう。プログラムを開いて実行し、PRINTプロシージャの出力から、データセットgradesが正しく読み込まれていることを確認してください。

RETAINステートメントの例を見る前に、変数「FIRST.」と「LAST.」について見ていきましょう。

#

次のプログラムは、BYステートメントを使ってデータセットをソートし、データステップ内で変数「FIRST.」と「LAST.」を取得する方法を示しています。これにより、各被験者の最初と最後の成績レコードを識別できます。

proc sort data = grades out = srt_grades;
  by idno;
run;

data grades_first_last;
  set srt_grades;
  by idno;
  firstGrade = FIRST.idno;
  lastGrade = LAST.idno;
run;

proc print data = grades_first_last;
run;
SAS 出力

The grades data set

OBS idno l_name gtype grade firstGrade lastGrade
1 10 Smith E1 78 1 0
2 10 Smith E2 82 0 0
3 10 Smith E3 86 0 0
4 10 Smith E4 69 0 0
5 10 Smith P1 97 0 0
6 10 Smith F1 160 0 1
7 11 Simon E1 88 1 0
8 11 Simon E2 72 0 0
9 11 Simon E3 86 0 0
10 11 Simon E4 99 0 0
11 11 Simon P1 100 0 0
12 11 Simon F1 170 0 1
13 12 Jones E1 98 1 0
14 12 Jones E2 92 0 0
15 12 Jones E3 92 0 0
16 12 Jones E4 99 0 0
17 12 Jones P1 99 0 0
18 12 Jones F1 185 0 1

変数「idno」でBYグループ処理を行うため、データセットを「idno」でソートする必要があります。この例ではデータセットはすでに「idno」でソートされていますが、ソートが必要であることを強調するためにPROC SORTを追加しています。

SETステートメントとBYステートメントは、「idno」の値が同じオブザベーションをグループ化するよう指示します。これにより自動的にBYステートメントの変数名ごとに2つの一時変数「FIRST.変数」と「LAST.変数」を作成します。これらの変数は0または1を取ります。

  • FIRST.変数 = 1 ならばそのオブザベーションはBYグループの最初のオブザベーションです

  • FIRST.変数 = 0 ならばそのオブザベーションはBYグループの最初のオブザベーションではありません

  • LAST.変数 = 1 ならばそのオブザベーションはBYグループの最後のオブザベーションです

  • LAST.変数 = 0 ならばそのオブザベーションはBYグループの最後のオブザベーションではありません

「FIRST.変数」と「LAST.変数」の値を使って、BYグループの最初と最後のオブザベーション、したがってグループ自体を識別します。”一時”という形容詞について補足すると…SASは「FIRST.変数」と「LAST.変数」をプログラムデータベクトル(PDV)に配置するので、データステップでプログラミングが可能です。しかし、これらの変数は作成されるデータセットには追加されません。その意味で一時的なのです。

FIRST.変数とLAST.変数を出力データセットに書き込まれないので、これらの内容を見るにはいくつかの工夫が必要です。次の割り当てステートメント

    firstGrade = FIRST.idno;
    lastGrade = LAST.idno;

は一時変数「FIRST.idno」と「LAST.idno」の値を永続的な変数「firstGrade」と「lastGrade」に割り当てます。PRINTプロシージャは、「firstGrade」と「lastGrade」の値を含むデータセットを出力するので、「FIRST.変数」と「LAST.変数」の値を確認できます。

#

RETAINステートメントの最も強力な使い方の1つは、オブザベーション間で値を比較することです。次のプログラムは、RETAINステートメントを使ってオブザベーション間の値を比較し、各被験者の4回の試験のうち最低の成績を決定しています。

data exams;
  set grades (where = (gtype in ('E1', 'E2', 'E3', 'E4')));
run;
 
data lowest (rename = (lowtype = gtype));
  set exams;
  by idno;
  retain lowgrade lowtype;
  if first.idno then lowgrade = grade;
  lowgrade = min(lowgrade, grade);
  if grade = lowgrade then lowtype = gtype;
  if last.idno then output;
  drop gtype;
run;

title 'Output Dataset: LOWEST'; 
proc print data=lowest;    
run;
SAS 出力

Output Dataset: LOWEST

OBS idno l_name grade lowgrade gtype
1 10 Smith 69 69 E4
2 11 Simon 99 72 E2
3 12 Jones 99 92 E3

最初のデータステップでは、データセットgradesから試験の成績(E1、E2、E3、E4)のみを抽出してデータセットexamsを作成しています。

2番目のデータステップが本質的な部分で、理解が難しいかもしれません。このデータステップでは、データセットexamsを全体から各被験者(「by idno」)について最低の成績(「min(lowgrade, grade)」)を探します。SASは通常、変数「lowgrade」と「lowtype」のをデータステップの各反復の開始時に欠損値にリセットするので、RETAINステートメントを使ってこれらの変数の最低値を保持しています。最後のオブザベーション(「last.idno」)を読み込むと、最低の試験種類「lowtype」と成績「lowgrade」に対応するデータがデータセットlowestに出力されます。(「if last.idno then output;」という文は、オブザベーションを被験者ごとに1つにまとめる効果があります。) データセットlowestをデータセットgradesに「idno」と「gtype」で結合できるよう、変数名「lowtype」を「gtype」に変更しています。

DOループ#

プログラミングをする際、同じステートメントを繰り返し実行する必要がある場合があります。そんな時にDOループが活躍します。DOループの中には、無条件で実行されるものがあります。20回実行するよう指示すれば、SASは20回実行します。このようなループを反復型DOループと呼びます。一方で、特定の条件が満たされるまで実行を続けるDOループや、特定の条件が満たされている間実行を続けるDOループもあります。前者をDO UNTILループ、後者をDO WHILEループと呼びます。このレッスンでは、これら3種類のループの概要と多くの例を見ていきます。この次のセクションでは、DOループを使って配列を処理する方法を扱います。

反復型DOループ#

このセクションでは、反復型DOループ、つまり単一または一連のステートメントを特定の回数実行する方法を見ていきます。いくつかの例を見てみましょう。

#

次のプログラムは、DOループを使って4×3がいくつになるかを計算させています。

data multiply;
  answer = 0;
  do i = 1 to 4;
    answer + 3;
  end;
run;

title 'Four Times Three Equals...'; 
proc print data=multiply noobs;    
run;
SAS 出力

Four Times Three Equals...

answer i
12 5

確かに、4×3を計算するにはもっと簡単な方法がありますが、それではDOループを使う楽しみがありません!このデータステップを理解するカギは、掛け算は足し算の繰り返しだということを思い出すことです。つまり、4×3は3+3+3+3と同じことです。データステップのDOループは、この計算を行うよう指示しているだけなのです。最初に「answer」に0を代入した後、3を「answer」に加え、さらに3を加え、さらに3を加え、さらに3を加えます。「answer」に3を4回加えた後、DOループから抜け出し、データステップの終わりなので次のプロシージャに進みます。

このデータステップについて気づいてほしいもう一つの点は、入力データセットや入力データファイルがないことです。ここでは、何らかの入力ソースからではなく、データをゼロから生成しています。プログラムを開いて実行し、PRINTプロシージャの出力からコードが正しく4×3を計算していることを確認してください。

さて、データセットmultiplyに表示されるその変数「i」についてはどうでしょうか?もう一度データステップを見ると、それがDOループに由来することがわかります。これはインデックス変数(またはカウンター変数)と呼ばれます。通常、出力データセットから削除することをお勧めしますが、ここでは学習目的のために残しています。ご覧の通り、現在の値は5です。これがDOループを終了するのを可能にしています… 「i」が4になるまでループ内のアクションを実行し、「i」が4を超えるとループを抜け、データステップの次のステートメントに進みます。反復DOループの一般的な形式を見てみましょう。

反復DOループを構築するには、DOステートメントで開始し、いくつかのステートメントを含み、ENDステートメントで終了する必要があります。以下は単純な反復DOループの例です:

DO index-variable = start TO stop BY increment;
    action statements;
END;

ここで、

  • DO、index-variable、start、TO、stop、およびENDはすべての反復DOループで必須です

  • index-variableはDOループの現在の反復の値を格納する任意の有効なSAS変数名であり、通常は一文字、特にiとjがよく使われます

  • startはループを開始するインデックス変数の値です

  • stopはループを終了するインデックス変数の値です

  • incrementは各反復後にSASがインデックス変数を変更する量です。最も一般的に使用されるの1で、BYステートメントを指定しない場合、デフォルトでは1が使用されます。

例えば、

do jack = 1 to 5;

は「jack」というインデックス変数を作成し、1から始まり、1ずつ増加し、5で終了するように指示します。したがって、ジャックの値は反復ごとに1、2、3、4、および5です。このDOステートメント:

do jill = 2 to 12 by 2;

は「jill」というインデックス変数を作成し、2から始まり、2ずつ増加し、12で終了するように指示します。したがって、ジルの値は反復ごとに2、4、6、8、10、および12です。

#

次のプログラムは反復DOループを使用して1ずつカウントダウンします:

data backwardsbyone;
  do i = 20 to 1 by -1;
    output;
  end;
run;

title 'Counting Backwards by 1'; 
proc print data = backwardsbyone noobs;    
run;
SAS 出力

Counting Backwards by 1

i
20
19
18
17
16
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1

ご覧のように、このDOステートメントでは、BYステートメントに負の値を指定することでDOループのインデックス変数を減少させることができます。ここでは、20から始めてインデックス変数を1ずつ減少させ、1に達するまでループするように指示しています。OUTPUTステートメントは、DOループの各反復でインデックス変数「i」の値を出力するようにSします。プログラムを開いて実行し、PRINTプロシージャの出力をからコードが20から1まで正しくカウントダウンしていることを確認してください。

DOステートメントで開始、停止、および増分値を指定する代わりに、シリーズ内の項目をリストしてDOループを何回実行するか指示することができます。この場合、反復DOループの一般的な形式は次のようになります:

DO index-variable = value1, value2, value3, ...;
    action statements;
END;

ここで、値は文字または数値のいずれかです。DOループが実行されると、シリーズ内の各項目に対して一度ずつ実行されます。インデックス変数は現在の項目の値に等しくなります。シリーズ内の項目をリストするには、カンマで区切る必要があります。シリーズ内の項目をリストするには、すべての数値を指定する必要があります:

DO i = 1, 2, 3, 4, 5;

すべての文字値は、各値を引用符で囲みます:

DO j = 'winter', 'spring', 'summer', 'fall';

またはすべての変数名:

DO k = first, second, third;

この場合、インデックス変数は指定された変数の値を取ります。変数名は引用符で囲まれていませんが、文字値には引用符が必要です。

ネストされたDOループ#

他のプログラミング言語と同様に、ループを互いにネストすることができます。

#

たとえば、AとBという2つの因子を使って実験を行いたいとします。因子Aは、例えば、水の量であり、水準は1、2、3、および4です。因子Bは、例えば、日光の量であり、水準は1、2、3、4、および5です。次のコードはネストされた反復DOループを使用して4x5の要因計画を生成します:

data design;
  do i = 1 to 4;
    do j = 1 to 5;
      output;
    end;
  end;
run;

title '4 by 5 Factorial Design'; 
proc print data = design;    
run;
SAS 出力

4 by 5 Factorial Design

OBS i j
1 1 1
2 1 2
3 1 3
4 1 4
5 1 5
6 2 1
7 2 2
8 2 3
9 2 4
10 2 5
11 3 1
12 3 2
13 3 3
14 3 4
15 3 5
16 4 1
17 4 2
18 4 3
19 4 4
20 4 5

まず、プログラムを開いて実行します。その後、PRINTプロシージャの出力からデザインデータセットの内容を確認してください。これにより、ネストされたDOループの動作についての理解が深まります。まず、

  • インデックス変数「i」の値を1に設定し、次にもう一つの反復DOループに進みます。「i」が1の間、「j」の値を1に設定し、i=1およびj=1のオブザベーションを出力します。

  • 「j」の値を2に設定し、i=1およびj=2のオブザベーションを出力します。

  • 「j」の値を3に設定し、i=1およびj=3のオブザベーションを出力します。

  • 「j」の値を4に設定し、i=1およびj=4のオブザベーションを出力します。

  • 「j」の値を5に設定し、i=1およびj=5のオブザベーションを出力します。

  • 「j」の値を6に設定し、内側のDoループを抜けて、外側の反復Doループの終端である次のステートメントに進みます。

次に「i」の値が2に設定され、上と同様に処理が再び繰り返されます。このプロセスを繰り返しインデックス変数「i」の値が5になると、外側のDOループを抜けデータステップを終了します。

DO UNTILおよびDO WHILEループ#

反復DOループでは、DOループの反復回数を指定する必要があることがわかっています。しかし、条件が達成されるまで、または条件が存在する間、DOループを実行したいが、必要な反復回数がわからない場合があります。その場合、DO UNTILループおよびDO WHILEループが役立ちます。

このセクションでは、まずDO UNTILおよびDO WHILEループについて扱います。次に、条件付きおよび無条件のDOループの両方の機能を組み合わせた反復DOループの別の形式を見てみましょう。

DO UNTILループを使用する場合、指定した式が真になるまでDOループを実行します。DO UNTILループの一般的な形式は次のようになります:

DO UNTIL (expression);
    action statements;
END;

ここで、expressionは括弧で囲まれた任意の有効なSASの評価式です。重要なことは、式はループの最後まで評価されないことです。したがって、DO UNTILループは必ず少なくとも一度実行されます。式が真と判断されたら、DOループは再び実行されません。

#

毎年\(1200を口座に預け、5%の利子が付く場合、\)50000を超えるのに何年かかるかを知りたいとします。以下のコードはDO UNTILループを使用してこの計算を行います:

data investment;
  retain value 0 year 0;
  do until (value >= 50000);
    value = value + 1200;
    value = value + value * 0.05;
    year = year + 1;
    output;
  end;
run;

title 'Years until at least $50,000'; 
proc print data = investment noobs;    
run;
SAS 出力

Years until at least $50,000

value year
1260.00 1
2583.00 2
3972.15 3
5430.76 4
6962.30 5
8570.41 6
10258.93 7
12031.88 8
13893.47 9
15848.14 10
17900.55 11
20055.58 12
22318.36 13
24694.28 14
27188.99 15
29808.44 16
32558.86 17
35446.80 18
38479.14 19
41663.10 20
45006.26 21
48516.57 22
52202.40 23

DO UNTILステートメントの式はループの一番下まで評価されないことを思い出してください。したがって、DO UNTILループは少なくとも1回は実行されます。最初の繰り返しで、変数「value」は 1200 だけ増加します。次に、変数「value」は 1200 + 1200*0.05 を計算して 1260 に更新されます。year=1、value=1260の最初のオブザベーションが出力データセットinvestmentに書き込まれます。DO UNTILループの最下部に到達すると、式(value >= 50000)が真かどうかを判断するために評価されます。「value」はちょうど1260なので、式は真ではなく、DO UNTILループがもう一度実行されます。この処理は、「value」が少なくとも50000であると判断し、DO UNTILループの実行を停止するまで続けられます。
プログラムを開いて実行し、PRINTプロシージャの出力から少なくとも$50,000を蓄積するには23年かかることを確認してください。

data investtwo;
  retain value 0 year 0;
  do while (value < 50000);
    value = value + 1200;
    value = value + value * 0.05;
    year = year + 1;
    output;
  end;
run;

title 'Years until at least $50,000'; 
proc print data = investtwo noobs;   
run;
SAS 出力

Years until at least $50,000

value year
1260.00 1
2583.00 2
3972.15 3
5430.76 4
6962.30 5
8570.41 6
10258.93 7
12031.88 8
13893.47 9
15848.14 10
17900.55 11
20055.58 12
22318.36 13
24694.28 14
27188.99 15
29808.44 16
32558.86 17
35446.80 18
38479.14 19
41663.10 20
45006.26 21
48516.57 22
52202.40 23

処理は前と同様に進行します。最初に、変数「value」は0 + 1200の計算によって1200に更新されます。次に、変数「value」は1200 + 1200*0.05の計算によって1260に更新されます。次に、変数「year」が1増えて、この場合は1に設定されます。year=1、value=1260の最初のオブザベーションが、出力データセット「investthree」に書き込まれます。その後、DO WHILE ループの先頭に戻り、(value < 50000)の式が真であるかどうかを判断します。値はまだ1260なので、式は真であり、したがってDO WHILEループがもう一度実行されます。この過程は「value」が50000より大きいと判断するまで続き、その時点でDO WHILEループを止めます。
プログラムを開いて実行し、PRINTプロシージャの出力からこのプログラムでも$50,000より多くなるのには23年かかることを確認してください。

WHILE条件をvalue < 50000 からvalue >= 50000 に変更して、何が起こるか試してみてください。(ヒント: 出力がありませんが、なぜでしょうか?)

これまでに、DO WHILEループとDO UNTILループを使って、条件付きでステートメントを繰り返し実行する方法を見てきました。また、反復DOループを使って、無条件で一定回数のステートメントを実行する方法も見てきました。次にこれら2つを組み合わせて、条件付きでも無条件でも実行できるDOループの形式を作成します。

#

再び、毎年\(1200を預け5%の利息が付く口座を\)50000にするのにどれくらいの年数がかかるかを知りたいとします。しかし今度は、投資期間を15年までに制限したいとします。次のプログラムでは、条件付きの反復DOループを使って、15年に達するか、投資額が$50000を超えるまで投資します。

data investfour (drop = i);
  retain value 0 year 0;
  do i = 1 to 15 until (value >= 50000);
    value = value + 1200;
    value = value + value * 0.05;
    year = year + 1;
    output;
  end;
run;

title 'Value of Investment'; 
proc print data = investfour noobs;   
run;
SAS 出力

Value of Investment

value year
1260.00 1
2583.00 2
3972.15 3
5430.76 4
6962.30 5
8570.41 6
10258.93 7
12031.88 8
13893.47 9
15848.14 10
17900.55 11
20055.58 12
22318.36 13
24694.28 14
27188.99 15

前のDO UNTILによる例とこのプログラムの違いは2点だけです。:
i) i = 1 to 15の反復がDO UNTILステートメントに挿入されています。
ii)インデックス変数「i」がDOループ用に作成されていますが、プログラムデータベクトル(PDV)の内容を出力データセットinvestfourに書き込む前に変数が削除されています。

配列#

このセクションでは、SASでの基本的な配列処理について扱います。データステップのプログラミングでは、複数の変数に同じ処理を一度に行う必要がしばしばあります。変数を個別に処理することも可能ですが、通常はグループとして扱う方が簡単です。配列はそのためのオプションを提供します。例えば、今までデータセットの50の数値変数の平方根を求めたい場合、50の割り当てステートメントを書く必要がありました。代わりに配列を使用すると、この作業を簡単にできます。

配列は、次のようなタスクを簡素化したい場合に使用できます。

  • 繰り返し計算を実行する

  • 同じ属性を持つ多くの変数を作成する

  • データを読み込む

  • 横持ちのデータセットを縦持ちのデータセットに転置する(データセットの変数をオブザベーションに変換する)

  • 縦持ちのデータセットを横持ちのデータセットに転置する(データセットのオブザベーションを変数に変換する)

  • 変数を比較する

このレッスンでは、そのようなタスクをどのように実行するかを扱います。適切な場面で配列を使用すると、プログラムを大幅に簡略化・短縮化できます!

1次元配列#

配列 は、単一の名前で一時的に変数をグループ化したものです。例えば、「winter」、「spring」、「summer」、「fall」という4つの変数がある場合、「seasons」という配列名でそれらの変数を関連付け、seasons[1]、seasons[2]、seasons[3]、seasons[4] として参照できます。配列を反復DOループと組み合わせると、プログラムを効率的に記述できる強力なツールになります。例を見てみましょう。

#

次のプログラムは、米国の10都市の月別気温(摂氏)の平均を一時データセットavgcelsiusに読み込みます。

data avgcelsius;
  input City $ 1-18 jan feb mar apr may jun
                      jul aug sep oct nov dec;
  datalines;
State College, PA  -2 -2  2  8 14 19 21 20 16 10  4 -1
Miami, FL          20 20 22 23 26 27 28 28 27 26 23 20
St. Louis, MO      -1  1  6 13 18 23 26 25 21 15  7  1
New Orleans, LA    11 13 16 20 23 27 27 27 26 21 16 12
Madison, WI        -8 -5  0  7 14 19 22 20 16 10  2 -5
Houston, TX        10 12 16 20 23 27 28 28 26 21 16 12
Phoenix, AZ        12 14 16 21 26 31 33 32 30 23 16 12
Seattle, WA         5  6  7 10 13 16 18 18 16 12  8  6
San Francisco, CA  10 12 12 13 14 15 15 16 17 16 14 11
San Diego, CA      13 14 15 16 17 19 21 22 21 19 16 14
;
run;

title 'Average Monthly Temperatures in Celsius'; 
proc print data = avgcelsius;    
  id City;
  var jan feb mar apr may jun 
      jul aug sep oct nov dec;
RUN;
SAS 出力

Average Monthly Temperatures in Celsius

City jan feb mar apr may jun jul aug sep oct nov dec
State College, PA -2 -2 2 8 14 19 21 20 16 10 4 -1
Miami, FL 20 20 22 23 26 27 28 28 27 26 23 20
St. Louis, MO -1 1 6 13 18 23 26 25 21 15 7 1
New Orleans, LA 11 13 16 20 23 27 27 27 26 21 16 12
Madison, WI -8 -5 0 7 14 19 22 20 16 10 2 -5
Houston, TX 10 12 16 20 23 27 28 28 26 21 16 12
Phoenix, AZ 12 14 16 21 26 31 33 32 30 23 16 12
Seattle, WA 5 6 7 10 13 16 18 18 16 12 8 6
San Francisco, CA 10 12 12 13 14 15 15 16 17 16 14 11
San Diego, CA 13 14 15 16 17 19 21 22 21 19 16 14

摂氏温度にあまり慣れていないことから華氏温度に変換したいとします。次のプログラムでは、標準的な変換式:

Fahrenheit temperature = 1.8*Celsius temperature + 32

を使って、データセットavgcelsiusの摂氏温度を華氏温度に変換し、新しいデータセットavgfahrenheitに格納します。

data avgfahrenheit;
  set avgcelsius;
  janf = 1.8*jan + 32;
  febf = 1.8*feb + 32;
  marf = 1.8*mar + 32;
  aprf = 1.8*apr + 32;
  mayf = 1.8*may + 32;
  junf = 1.8*jun + 32;
  julf = 1.8*jul + 32;
  augf = 1.8*aug + 32;
  sepf = 1.8*sep + 32;
  octf = 1.8*oct + 32;
  novf = 1.8*nov + 32;
  decf = 1.8*dec + 32;
  drop jan feb mar apr may jun
          jul aug sep oct nov dec;
run;
 
proc print data = avgfahrenheit;
  title 'Average Monthly Temperatures in Fahrenheit';
  id City;
  var janf febf marf aprf mayf junf 
      julf augf sepf octf novf decf;
run;
SAS 出力

Average Monthly Temperatures in Fahrenheit

City janf febf marf aprf mayf junf julf augf sepf octf novf decf
State College, PA 28.4 28.4 35.6 46.4 57.2 66.2 69.8 68.0 60.8 50.0 39.2 30.2
Miami, FL 68.0 68.0 71.6 73.4 78.8 80.6 82.4 82.4 80.6 78.8 73.4 68.0
St. Louis, MO 30.2 33.8 42.8 55.4 64.4 73.4 78.8 77.0 69.8 59.0 44.6 33.8
New Orleans, LA 51.8 55.4 60.8 68.0 73.4 80.6 80.6 80.6 78.8 69.8 60.8 53.6
Madison, WI 17.6 23.0 32.0 44.6 57.2 66.2 71.6 68.0 60.8 50.0 35.6 23.0
Houston, TX 50.0 53.6 60.8 68.0 73.4 80.6 82.4 82.4 78.8 69.8 60.8 53.6
Phoenix, AZ 53.6 57.2 60.8 69.8 78.8 87.8 91.4 89.6 86.0 73.4 60.8 53.6
Seattle, WA 41.0 42.8 44.6 50.0 55.4 60.8 64.4 64.4 60.8 53.6 46.4 42.8
San Francisco, CA 50.0 53.6 53.6 55.4 57.2 59.0 59.0 60.8 62.6 60.8 57.2 51.8
San Diego, CA 55.4 57.2 59.0 60.8 62.6 66.2 69.8 71.6 69.8 66.2 60.8 57.2

変換に必要な割り当てステートメントの数から、この作業は根気が必要であることが分かります。平均月別気温が12列あるため、12の割り当てステートメントを書かなければなりません。各割り当てステートメントは同じ計算を実行します。変数名だけが異なります。プログラムを開いて実行し、PRINTプロシージャの出力から摂氏温度が適切に華氏温度に変換されたことを確認してください。
上記のプログラムは配列の使用を求めています。配列を使う主な理由の1つは、変数の処理に必要なステートメントの数を減らすことです。例を見てみましょう。
次のプログラムでは、1次元の配列fahrを使って、データセットavgcelsiusの平均摂氏温度を平均華氏温度に変換し、新しいデータセットavgfahrenheitに格納します。

data avgfahrenheit;
  set avgcelsius;
  array fahr[12] jan feb mar apr may jun
                 jul aug sep oct nov dec;
  do i = 1 to 12;
    fahr[i] = 1.8*fahr[i] + 32;
  end;
run;

title 'Average Monthly Temperatures in Fahrenheit'; 
proc print data = avgfahrenheit;    
  id City;
  var jan feb mar apr may jun 
      jul aug sep oct nov dec;
run;
SAS 出力

Average Monthly Temperatures in Fahrenheit

City jan feb mar apr may jun jul aug sep oct nov dec
State College, PA 28.4 28.4 35.6 46.4 57.2 66.2 69.8 68.0 60.8 50.0 39.2 30.2
Miami, FL 68.0 68.0 71.6 73.4 78.8 80.6 82.4 82.4 80.6 78.8 73.4 68.0
St. Louis, MO 30.2 33.8 42.8 55.4 64.4 73.4 78.8 77.0 69.8 59.0 44.6 33.8
New Orleans, LA 51.8 55.4 60.8 68.0 73.4 80.6 80.6 80.6 78.8 69.8 60.8 53.6
Madison, WI 17.6 23.0 32.0 44.6 57.2 66.2 71.6 68.0 60.8 50.0 35.6 23.0
Houston, TX 50.0 53.6 60.8 68.0 73.4 80.6 82.4 82.4 78.8 69.8 60.8 53.6
Phoenix, AZ 53.6 57.2 60.8 69.8 78.8 87.8 91.4 89.6 86.0 73.4 60.8 53.6
Seattle, WA 41.0 42.8 44.6 50.0 55.4 60.8 64.4 64.4 60.8 53.6 46.4 42.8
San Francisco, CA 50.0 53.6 53.6 55.4 57.2 59.0 59.0 60.8 62.6 60.8 57.2 51.8
San Diego, CA 55.4 57.2 59.0 60.8 62.6 66.2 69.8 71.6 69.8 66.2 60.8 57.2

このプログラムを前のプログラムと比較すると、12の割り当てステートメントを置き換えたステートメントがあります。ARRAYステートメントは、配列fahrを定義します。「jan」、「feb」、…、「dec」の12か月の変数を配列fahrにグループ化することを伝えます。()内の(12)は、配列宣言に必須の部分で、配列の次元と呼ばれます。これにより、グループ化したい変数の数を知らせます。配列に含める変数名を指定する際は、スペースで区切って変数を列挙します。他のSASステートメントと同様に、ARRAYステートメントはセミコロン(;)で終わります。
一度配列fahrを定義すれば、個々の変数名の代わりにコードで使用できます。配列の個々の要素は、fahr[i]のように名前とインデックスで参照します。ARRAYステートメントでの変数の並び順が、配列内の変数の位置を決めます。例えば、fahr[1]は「jan」、fahr([2])は「feb」、fahr[12]は「dec」に対応します。配列をfahrのように反復DOループとともに使うと、コードをとても簡略化できます。
DOループは配列fahrの要素を処理させ、その度に摂氏温度を華氏温度に変換します。例えば、インデックス変数「i」が1の場合、割り当てステートメントは次のようになります。

fahr[1] = 1.8*fahr[1] + 32;

つまり、

jan = 1.8*jan + 32;

であると考えることができます。等号の右側の「jan」の値は摂氏温度です。割り当てステートメントが実行された後、等号の左側の「jan」の値が更新され、華氏温度を反映します。

プログラムを開いて実行し、PRINTプロシージャの出力から摂氏温度が適切に華氏温度に変換されたことを確認してください。最後に付け加えますが、PRINTプロシージャのVARステートメントで列挙されている変数は、jan、feb、…、decの元の変数名であり、fahr[1]、fahr[2]、…のように配列でグループ化された変数ではありません。配列はデータステップの間だけ存在するためです。PRINTプロシージャでfahr[1]、fahr[2]、…と指定すると、エラーとなります。これまでの内容を要約してみましょう!

配列を定義するには、ARRAYステートメントを使って、以前に定義されたデータセット変数をグループ化する必要があります。ARRAYステートメントの一般的な形式は次の通りです。

ARRAY 配列名[次元] ;

ここで:

  • 配列名は、配列の名前を指定する有効なSAS名でなければなりません。

  • 次元は、配列要素の数と配列の構成を記述します。デフォルトの次元は1です。

  • 要素は、配列を形成するためにグループ化される変数のリストです。配列要素はすべて数値またはすべて文字でなければなりません。標準的なSASのヘルプでの表記では、<>の角括弧で囲まれた用語は省略可能であることを示しています。つまり、ARRAYステートメントで要素を指定する必要はありません。要素が指定されていない場合、デフォルトの名前で新しい変数が作成されます。

配列名についてもう少し説明しておく必要があります。SASを混乱させたくない場合は、同じデータステップ内に現れる変数と同じ名前の配列を作ってはいけません。また、有効な関数と同じ名前の配列を作るのも避けるべきです。SASはそうすることを許可しますが、その場合、同じデータステップ内でその関数を使用できなくなります。例えば、データステップでと配列meanを作った場合、そのデータステップ内でmean関数を使うことはできません。SASはログウィンドウに警告メッセージを出力して知らせます。最後に、配列名はLABEL、FORMAT、DROP、KEEP、LENGTHステートメントでは使用できません。

別の方法で摂氏温度から華氏温度への変換に使用する配列を定義した例を見てみましょう。

#

次のプログラムは前の例と同じですが、ARRAYステートメントの12が*(アスタリスク)に変更されており、変数のリストを使って配列の変数を指定しています。

data avgfahrenheittwo;
  set avgcelsius;
  array fahr[*] jan -- dec;
  do i = 1 to 12;
    fahr[i] = 1.8*fahr[i] + 32;
  end;
run;

title 'Average Monthly Temperatures in Fahrenheit'; 
proc print data = avgfahrenheittwo;    
  id City;
  var jan feb mar apr may jun 
      jul aug sep oct nov dec;
run;
SAS 出力

Average Monthly Temperatures in Fahrenheit

City jan feb mar apr may jun jul aug sep oct nov dec
State College, PA 28.4 28.4 35.6 46.4 57.2 66.2 69.8 68.0 60.8 50.0 39.2 30.2
Miami, FL 68.0 68.0 71.6 73.4 78.8 80.6 82.4 82.4 80.6 78.8 73.4 68.0
St. Louis, MO 30.2 33.8 42.8 55.4 64.4 73.4 78.8 77.0 69.8 59.0 44.6 33.8
New Orleans, LA 51.8 55.4 60.8 68.0 73.4 80.6 80.6 80.6 78.8 69.8 60.8 53.6
Madison, WI 17.6 23.0 32.0 44.6 57.2 66.2 71.6 68.0 60.8 50.0 35.6 23.0
Houston, TX 50.0 53.6 60.8 68.0 73.4 80.6 82.4 82.4 78.8 69.8 60.8 53.6
Phoenix, AZ 53.6 57.2 60.8 69.8 78.8 87.8 91.4 89.6 86.0 73.4 60.8 53.6
Seattle, WA 41.0 42.8 44.6 50.0 55.4 60.8 64.4 64.4 60.8 53.6 46.4 42.8
San Francisco, CA 50.0 53.6 53.6 55.4 57.2 59.0 59.0 60.8 62.6 60.8 57.2 51.8
San Diego, CA 55.4 57.2 59.0 60.8 62.6 66.2 69.8 71.6 69.8 66.2 60.8 57.2

簡単ですね!変数の数を数えたり、配列にグループ化する変数を個別に列挙する必要なく、SASにその作業をさせることができます。そのためには、次元を*(アスタリスク)で定義し、変数リストの省略形を使用します。配列にグループ化する変数が多すぎて、個別にカウントして列挙するのが面倒な場合、この方法が便利でしょう。ちなみにここでは、配列の次元(またはインデックス変数)を[]で囲んでいますが、{}または()を使ってもかまいません。
上記のプログラムでは、変数のリストを使って、配列fahrにグループ化された変数名の指定を短縮しました。場合によっては、_ALL_、_CHARACTER_、_NUMERIC_などの特別な名前のリストを使うこともできます。

  • _ALL_を使うと、データセット内の同じ型(すべて数値または文字列)の変数をすべて使用します。

  • _CHARACTER_を使うと、データセット内のすべての文字列変数を使用します。

  • _NUMERIC_を使うと、データセット内のすべての数値変数を使用します。

この場合、次のプログラムのように_NUMERIC_キーワードを使うこともできます。

data avgfahrenheitthree;
  set avgcelsius;
  array fahr[*] _numeric_;
  do i = 1 to 12;
    fahr[i] = 1.8*fahr[i] + 32;
  end;
run;

title 'Average Monthly Temperatures in Fahrenheit'; 
proc print data = avgfahrenheitthree;  
  id City;
  var jan feb mar apr may jun 
      jul aug sep oct nov dec;
run;
SAS 出力

Average Monthly Temperatures in Fahrenheit

City jan feb mar apr may jun jul aug sep oct nov dec
State College, PA 28.4 28.4 35.6 46.4 57.2 66.2 69.8 68.0 60.8 50.0 39.2 30.2
Miami, FL 68.0 68.0 71.6 73.4 78.8 80.6 82.4 82.4 80.6 78.8 73.4 68.0
St. Louis, MO 30.2 33.8 42.8 55.4 64.4 73.4 78.8 77.0 69.8 59.0 44.6 33.8
New Orleans, LA 51.8 55.4 60.8 68.0 73.4 80.6 80.6 80.6 78.8 69.8 60.8 53.6
Madison, WI 17.6 23.0 32.0 44.6 57.2 66.2 71.6 68.0 60.8 50.0 35.6 23.0
Houston, TX 50.0 53.6 60.8 68.0 73.4 80.6 82.4 82.4 78.8 69.8 60.8 53.6
Phoenix, AZ 53.6 57.2 60.8 69.8 78.8 87.8 91.4 89.6 86.0 73.4 60.8 53.6
Seattle, WA 41.0 42.8 44.6 50.0 55.4 60.8 64.4 64.4 60.8 53.6 46.4 42.8
San Francisco, CA 50.0 53.6 53.6 55.4 57.2 59.0 59.0 60.8 62.6 60.8 57.2 51.8
San Diego, CA 55.4 57.2 59.0 60.8 62.6 66.2 69.8 71.6 69.8 66.2 60.8 57.2

ARRAYステートメントによる新しい変数の作成#

これまでに、既存の変数をグループ化して配列を作る方法をいくつか学びました。しかし、ARRAY ステートメントから配列要素を省略すれば、新しい変数も作ることができます。ARRAYステートメントで既存の変数を参照しない場合、SASは自動的に新しい変数を作成し、デフォルトの名前を割り当てます。

#

次のプログラムでは、再び10都市の摂氏温度の月平均気温を華氏温度に変換しています。既存の摂氏温度「jan」、「feb」、…、「dec」は配列celsiusにグループ化され、計算結果の華氏温度は「janf」、「febf」、…、「decf」という新しい変数に格納され、配列fahrにグループ化されています。

data avgtemps;
  set avgcelsius;
  array celsius[12] jan feb mar apr may jun 
                    jul aug sep oct nov dec;
  array fahr[12] janf febf marf aprf mayf junf
                 julf augf sepf octf novf decf;
  do i = 1 to 12;
    fahr[i] = 1.8*celsius[i] + 32;
  end;
run;

title 'Average Monthly Temperatures'; 
proc print data = avgtemps;   
  id City;
  var jan janf feb febf mar marf;
  var apr aprf may mayf jun junf;
  var jul julf aug augf sep sepf;
  var oct octf nov novf dec decf;
run;
SAS 出力

Average Monthly Temperatures

City jan janf feb febf mar marf apr aprf may mayf jun junf jul julf aug augf sep sepf oct octf nov novf dec decf
State College, PA -2 28.4 -2 28.4 2 35.6 8 46.4 14 57.2 19 66.2 21 69.8 20 68.0 16 60.8 10 50.0 4 39.2 -1 30.2
Miami, FL 20 68.0 20 68.0 22 71.6 23 73.4 26 78.8 27 80.6 28 82.4 28 82.4 27 80.6 26 78.8 23 73.4 20 68.0
St. Louis, MO -1 30.2 1 33.8 6 42.8 13 55.4 18 64.4 23 73.4 26 78.8 25 77.0 21 69.8 15 59.0 7 44.6 1 33.8
New Orleans, LA 11 51.8 13 55.4 16 60.8 20 68.0 23 73.4 27 80.6 27 80.6 27 80.6 26 78.8 21 69.8 16 60.8 12 53.6
Madison, WI -8 17.6 -5 23.0 0 32.0 7 44.6 14 57.2 19 66.2 22 71.6 20 68.0 16 60.8 10 50.0 2 35.6 -5 23.0
Houston, TX 10 50.0 12 53.6 16 60.8 20 68.0 23 73.4 27 80.6 28 82.4 28 82.4 26 78.8 21 69.8 16 60.8 12 53.6
Phoenix, AZ 12 53.6 14 57.2 16 60.8 21 69.8 26 78.8 31 87.8 33 91.4 32 89.6 30 86.0 23 73.4 16 60.8 12 53.6
Seattle, WA 5 41.0 6 42.8 7 44.6 10 50.0 13 55.4 16 60.8 18 64.4 18 64.4 16 60.8 12 53.6 8 46.4 6 42.8
San Francisco, CA 10 50.0 12 53.6 12 53.6 13 55.4 14 57.2 15 59.0 15 59.0 16 60.8 17 62.6 16 60.8 14 57.2 11 51.8
San Diego, CA 13 55.4 14 57.2 15 59.0 16 60.8 17 62.6 19 66.2 21 69.8 22 71.6 21 69.8 19 66.2 16 60.8 14 57.2

データステップは前のものとよく似ています。違いは摂氏温度を上書きするのではなく、計算した華氏温度を新しい変数「janf」、「febf」、…、「decf」に保存する点です。最初のARRAYステートメントは、データセットavgcelsiusの変数「jan」、「feb」、…、「dec」を1次元配列celsiusにグループ化します。2つ目のARRAYステートメントは、新しい変数「janf」、「febf」、…、「decf」を作成し、配列fahrにグループ化しています。DOループは配列celsiusの12の要素を処理し、摂氏温度を華氏温度に変換して、結果を配列fahrに格納します。PRINTプロシージャは、12の摂氏温度と12の華氏温度を横に並べて出力します。プログラムを開いて実行し、PRINTプロシージャの出力から摂氏温度が華氏温度に適切に変換されたことを確認してください。
別の方法として、配列fahrの命名を任せることもできます。

data avgtempsinF;
  set avgcelsius;
  array celsius[12] jan feb mar apr may jun 
                    jul aug sep oct nov dec;
  array fahr[12];
  do i = 1 to 12;
    fahr[i] = 1.8*celsius[i] + 32;
  end;
run;

title 'Average Monthly Temperatures in Fahrenheit'; 
proc print data = avgtempsinF;    
  id City;
  var fahr1-fahr12;
run;
SAS 出力

Average Monthly Temperatures in Fahrenheit

City fahr1 fahr2 fahr3 fahr4 fahr5 fahr6 fahr7 fahr8 fahr9 fahr10 fahr11 fahr12
State College, PA 28.4 28.4 35.6 46.4 57.2 66.2 69.8 68.0 60.8 50.0 39.2 30.2
Miami, FL 68.0 68.0 71.6 73.4 78.8 80.6 82.4 82.4 80.6 78.8 73.4 68.0
St. Louis, MO 30.2 33.8 42.8 55.4 64.4 73.4 78.8 77.0 69.8 59.0 44.6 33.8
New Orleans, LA 51.8 55.4 60.8 68.0 73.4 80.6 80.6 80.6 78.8 69.8 60.8 53.6
Madison, WI 17.6 23.0 32.0 44.6 57.2 66.2 71.6 68.0 60.8 50.0 35.6 23.0
Houston, TX 50.0 53.6 60.8 68.0 73.4 80.6 82.4 82.4 78.8 69.8 60.8 53.6
Phoenix, AZ 53.6 57.2 60.8 69.8 78.8 87.8 91.4 89.6 86.0 73.4 60.8 53.6
Seattle, WA 41.0 42.8 44.6 50.0 55.4 60.8 64.4 64.4 60.8 53.6 46.4 42.8
San Francisco, CA 50.0 53.6 53.6 55.4 57.2 59.0 59.0 60.8 62.6 60.8 57.2 51.8
San Diego, CA 55.4 57.2 59.0 60.8 62.6 66.2 69.8 71.6 69.8 66.2 60.8 57.2

2つ目のARRAYステートメントで配列fahrを定義する際、配列fahrに含める要素の数(12)を指定しますが、配列にグループ化する変数は指定しません。これは、i) 12個の新しい変数を作成すること、ii) 変数名の付け方はSASに任せること、の2点を示しています。この場合、デフォルトの名前として、配列名と1、2、3…配列の次元までの番号を連結したものを作成します。ここでは例えば、「fahr1」、「fahr2」、「fahr3」、…、「fahr12」という名前が作成されます。そのため、PRINTプロシージャのVARステートメントでは、華氏温度を「fahr1」-「fahr12」と参照しています。プログラムを開いて実行し、PRINTプロシージャの出力を確認して、摂氏温度が華氏温度に適切に変換されたことを確認してください。

一時的な配列要素#

配列の要素が、データステップの間だけ必要な定数の場合、配列グループに関連付けられた変数を省略し、代わりに一時的な配列要素を使用できます。一時的な配列要素は変数のように振る舞いますが、以下の特徴があります。

  • 結果のデータセットには現れない

  • 名前がなく、配列名と次元でのみ参照できる

  • 自動的に保持され、データステップの次の反復の開始時に欠損値にリセットされない

このセクションでは、 Quality of Lifeデータのサブセットにエラーがないか確認する3つの例を見ていきます。最初の例では、10変数「qul3a」、「qul3b」、…、「qul3j」に記録されたデータが予想される範囲内にあるかを、配列を使わずに確認します。次に、同じ10変数に記録されたデータが予想される範囲内にあるかを、新しい変数「error1」、「error2」、「error3」に対応する配列を使って確認します。最後に、一時的な要素のみを含む配列を使って、同じ10変数に記録されたデータが予想される範囲内にあるかを確認します。

#

次のプログラムでは、最初に Quality of Life データのサブセット(変数「qul3a」、「qul3b」、…、「qul3j」)をデータセットqulに読み込みます。次に、各変数の値がデータ形式から予想される1、2、3のいずれかとして記録されていることを確認します。変数の値が1、2、3以外の場合、そのオブザベーションはデータセットerrorsに出力されます。それ以外の場合は、データセットqulに出力されます。エラーチェックは配列を使わずに行われるため、プログラムには関係する10変数ごとに10個のif/thenステートメントがあります。

data qul errors;
  input subj qul3a qul3b qul3c qul3d qul3e 
             qul3f qul3g qul3h qul3i qul3j;
  flag = 0;
  if qul3a not in (1, 2, 3) then flag = 1;
  if qul3b not in (1, 2, 3) then flag = 1;
  if qul3c not in (1, 2, 3) then flag = 1;
  if qul3d not in (1, 2, 3) then flag = 1;
  if qul3e not in (1, 2, 3) then flag = 1;
  if qul3f not in (1, 2, 3) then flag = 1;
  if qul3g not in (1, 2, 3) then flag = 1;
  if qul3h not in (1, 2, 3) then flag = 1;
  if qul3i not in (1, 2, 3) then flag = 1;
  if qul3j not in (1, 2, 3) then flag = 1;
  if flag = 1 then output errors;
              else output qul;
  drop flag;
  datalines;
110011 1 2 3 3 3 3 2 1 1 3
210012 2 3 4 1 2 2 3 3 1 1
211011 1 2 3 2 1 2 3 2 1 3
310017 1 2 3 3 3 3 3 2 2 1
411020 4 3 3 3 3 2 2 2 2 2
510001 1 1 1 1 1 1 2 1 2 2
;
run;

title 'Observations in Qul data set with no errors';
proc print data = qul;
run;

title 'Observations in Qul data set with errors';
proc print data = errors;    
run;
SAS 出力

Observations in Qul data set with no errors

OBS subj qul3a qul3b qul3c qul3d qul3e qul3f qul3g qul3h qul3i qul3j
1 110011 1 2 3 3 3 3 2 1 1 3
2 211011 1 2 3 2 1 2 3 2 1 3
3 310017 1 2 3 3 3 3 3 2 2 1
4 510001 1 1 1 1 1 1 2 1 2 2

Observations in Qul data set with errors

OBS subj qul3a qul3b qul3c qul3d qul3e qul3f qul3g qul3h qul3i qul3j
1 210012 2 3 4 1 2 2 3 3 1 1
2 411020 4 3 3 3 3 2 2 2 2 2

INPUTステートメントで最初に、被験者のQuality of Lifeデータのオブザベーションを読み込みます。オブザベーションはエラーありと見なされる(10の値のいずれかが範囲外だとフラグが1に設定)まで、エラーがない(フラグは最初0に設定)と見なされます。オブザベーションがエラーを含む(flag=1)と判断された場合、errorsデータセットに出力されます。そうでない場合(flag=0)、qulデータセットに出力されます。

入力データセットの2つのオブザベーションにデータ記録エラーがあることに注意してください。被験者210012の「qul3c」の値が4と記録され、被験者411020の「qul3a」の値も4と記録されています。次にプログラムを開いて実行します。データセットqulには4つのクリーンなオブザベーションが含まれ、データセットerrorsには2つの不正なオブザベーションが含まれていることを確認してください。
また、このようなケースは典型的に配列を使うべき状況であることにも注目してください。まだ納得がいかない場合は、100個程度の変数についてエラーをチェックするためのif/thenステートメントを書く必要があると想像してみてください。
次のプログラムでは、前のプログラムと同じエラーチェックを行いますが、boundsとquldataの2つの配列が使用されます

data qul errors;
  input subj qul3a qul3b qul3c qul3d qul3e 
        qul3f qul3g qul3h qul3i qul3j;
  array bounds [3] error1 - error3 (1 2 3);
  array quldata [10] qul3a -- qul3j;
  flag = 0;
  do i = 1 to 10;
    if quldata[i] ^= bounds[1] and
      quldata[i] ^= bounds[2] and
      quldata[i] ^= bounds[3]
      then flag = 1;
  end;
  if flag = 1 then output errors;
              else output qul;
  drop i flag;
  datalines;
110011 1 2 3 3 3 3 2 1 1 3
210012 2 3 4 1 2 2 3 3 1 1
211011 1 2 3 2 1 2 3 2 1 3
310017 1 2 3 3 3 3 3 2 2 1
411020 4 3 3 3 3 2 2 2 2 2
510001 1 1 1 1 1 1 2 1 2 2
;
run;

title 'Observations in Qul data set with no errors'; 
proc print data = qul;
run;

title 'Observations in Qul data set with errors'; 
proc print data = errors;    
run;
SAS 出力

Observations in Qul data set with no errors

OBS subj qul3a qul3b qul3c qul3d qul3e qul3f qul3g qul3h qul3i qul3j error1 error2 error3
1 110011 1 2 3 3 3 3 2 1 1 3 1 2 3
2 211011 1 2 3 2 1 2 3 2 1 3 1 2 3
3 310017 1 2 3 3 3 3 3 2 2 1 1 2 3
4 510001 1 1 1 1 1 1 2 1 2 2 1 2 3

Observations in Qul data set with errors

OBS subj qul3a qul3b qul3c qul3d qul3e qul3f qul3g qul3h qul3i qul3j error1 error2 error3
1 210012 2 3 4 1 2 2 3 3 1 1 1 2 3
2 411020 4 3 3 3 3 2 2 2 2 2 1 2 3

前のプログラムとこのプログラムを比べると、違いは2つのARRAYを定義するステートメントと、エラーチェックを行う反復DOループ内のIF/THENステートメントの存在のみです。

最初のARRAYステートメントでは、変数名の範囲リストを使って配列boundsを定義し、新しい変数「error1」、「error2」、「error3」の3つを含みます。変数リスト「error1」-「error3」の後ろの”(1 2 3)”は、配列boundsの要素を1、2、3に初期化するように指示しています。一般にはこの方法で配列を初期化します。つまり、配列の要素数と同じ数値をリストアップし、各値の間にスペースを入れます。配列に文字定数を含める場合は、値をシングルクォートで囲む必要があります。例えば、次のARRAYステートメントは、文字列配列weekdays(文字列のため「$」)を定義し、要素を”M”、”T”、”W”、”R”、”F”に初期化するようにします。

ARRAY weekdays[5] $ ('M' 'T' 'W' 'R' 'F');

2つ目のARRAYステートメントでは、変数順の範囲を使って配列quldataを定義し、10のQuality of Life変数を含みます。IF/THENステートメントは、前のプログラムとは少し異なるロジックを使い、配列quldataの要素と配列boundsの要素を比較して、値が範囲外かどうかを確認しています。

次に、プログラムを開いて実行します。前と同様に、データセットqulには4つのクリーンなオブザベーションが含まれ、errorsには2つの不正なオブザベーションが含まれていることを、出力から確認してください。また、新しい変数「error1」、「error2」、「error3」がデータセットに残っていることにも注目してください。

前のプログラムでは、有効値1、2、3が一時的にしか必要ありません。したがって、配列boundsを定義する際に、一時的な配列要素を使うこともできました。次のプログラムはそうしたもので、前のプログラムと同じですが、配列boundsを定義する際に、新しい変数「error1」、「error2」、「error3」ではなく、一時的な配列要素を使っている点が異なります。

data qul errors;
  input subj qul3a qul3b qul3c qul3d qul3e 
          qul3f qul3g qul3h qul3i qul3j;
  array bounds [3] _temporary_ (1 2 3);
  array quldata [10] qul3a -- qul3j;
  flag = 0;
  do i = 1 to 10;
    if quldata[i] ^= bounds[1] and
       quldata[i] ^= bounds[2] and
       quldata[i] ^= bounds[3]
      then flag = 1;
  end;
  if flag = 1 then output errors;
              else output qul;
  drop i flag;
  datalines;
  110011 1 2 3 3 3 3 2 1 1 3
  210012 2 3 4 1 2 2 3 3 1 1
  211011 1 2 3 2 1 2 3 2 1 3
  310017 1 2 3 3 3 3 3 2 2 1
  411020 4 3 3 3 3 2 2 2 2 2
  510001 1 1 1 1 1 1 2 1 2 2
  ;
run;

title 'Observations in Qul data set with no errors'; 
proc print data = qul;
run;

title 'Observations in Qul data set with errors'; 
proc print data = errors;    
run
SAS 出力

Observations in Qul data set with no errors

OBS subj qul3a qul3b qul3c qul3d qul3e qul3f qul3g qul3h qul3i qul3j
1 110011 1 2 3 3 3 3 2 1 1 3
2 211011 1 2 3 2 1 2 3 2 1 3
3 310017 1 2 3 3 3 3 3 2 2 1
4 510001 1 1 1 1 1 1 2 1 2 2

Observations in Qul data set with errors

OBS subj qul3a qul3b qul3c qul3d qul3e qul3f qul3g qul3h qul3i qul3j
1 210012 2 3 4 1 2 2 3 3 1 1
2 411020 4 3 3 3 3 2 2 2 2 2

前のプログラムとこのプログラムを比べると、違いは配列boundsの定義に _TEMPORARY_ が存在する点のみです。配列boundsは再び有効値”(1 2 3)”で初期化されています。

プログラムを開いて実行してください。前と同様に、データセットqulには4つのクリーンなオブザベーションが含まれ、errorsには2つの不正なオブザベーションが含まれていることを、出力から確認してください。また、一時的な配列要素がデータセットに現れないことにも注目してください。

配列の境界#

これまで検討してきた配列はそれぞれデフォルトで下限1と次元の要素数に等しい上限が定義されています。例えば、配列my_arrayは

ARRAY my_array[4] el1 el2 el3 el4;

下限1、上限4を持っています。このセクションでは、配列の境界に関連する3つの例を見ていきます。最初の例では、DIM関数を使ってDO ループのインデックス変数の上限を動的に変更します(あらかじめ指定はしません)。2番目の例では、1次元配列の下限と上限を定義して、境界付き配列を作成します。3番目の例では、LBOUND関数とHBOUND関数を使って、DO ループのインデックス変数の下限と上限を動的に変更します。

#

次のプログラムは、5人の被験者の調査における6つの質問(q1、q2、…、q6)への yes/no 回答を一時データセットsurveyに読み込みます。yes の回答は 2 としてコード化され、no の回答は 1 としてコード化されます。変数 「q3」、「q4」、「q5」、「q6」 の 4 つだけが1次元配列qxsに格納されます。次にDOループとDIM関数を組み合わせて、4つの回答の変数を再コード化し、2 を 1 に、1 を 0 に変更します。

data survey (drop = i);
  input subj q1 q2 q3 q4 q5 q6;
  array qxs[4] q3-q6;
  do i = 1 to dim(qxs);
    qxs[i] = qxs[i] - 1;
  end;
  datalines;
1001 1 2 1 2 1 1
1002 2 1 2 2 2 1
1003 2 2 2 1 . 2
1004 1 . 1 1 1 2
1005 2 1 2 2 2 1
;
run;

title 'The survey data using dim function'; 
proc print data = survey;    
run;
SAS 出力

The survey data using dim function

OBS subj q1 q2 q3 q4 q5 q6
1 1001 1 2 0 1 0 0
2 1002 2 1 1 1 1 0
3 1003 2 2 1 0 . 1
4 1004 1 . 0 0 0 1
5 1005 2 1 1 1 1 0

最初に注目すべきは、すべての調査の変数(q1、…、q6)がデータセットsurveyに読み込まれるものの、ARRAYステートメントでは変数の4つ(q3、q4、q5、q6)のみが 1次元配列qxsにグループ化されている点です。例えば、qxs[1] は変数 q3 に、qxs[2] は変数 q4 に対応しています。次に、配列を要素 1 から要素 4 まで処理するのではなく、DO ループでは DIM(qxs)までの要素を処理するように指示しています。一般に、DIM 関数は配列の要素数を返します。この場合は 4 です。DO ループでは値から1引いて再コード化します。そしてインデックス変数「i」がデータセットsurveyにデフォルトで出力されるため、DROPで除外します。

#

前に説明と例示したように、配列の下限を特に指定しない場合、下限を 1 と見なします。ほとんどの配列では、下限 1 と要素数が上限というのが便利なので、通常は上限と下限の両方を指定する必要はありません。しかし、より便利になるような場合は、任意の次元の上限と下限の両方を変更することができます。
前の例では、配列要素 qxs[1] が変数「q3」に、qxs[2] が「q4」に対応するのは少し面倒かもしれません。qxs[3] が「q3」に、qxs[4] が「q4」に対応する方が分かりやすいかもしれません。次のプログラムは機能的には前のプログラムと同じですが、ここでは qxs 配列の下限を 3、上限を 6 と定義することで再コード化を行っています。

data survey2 (drop = i);
  input subj q1 q2 q3 q4 q5 q6;
  array qxs[3:6] q3-q6;
  do i = 3 to 6;
    qxs[i] = qxs[i] - 1;
  end;
  datalines;
1001 1 2 1 2 1 1
1002 2 1 2 2 2 1
1003 2 2 2 1 . 2
1004 1 . 1 1 1 2
1005 2 1 2 2 2 1
;
run;

title 'The survey data using bounded arrays'; 
proc print data = survey2;    
run;
SAS 出力

The survey data using bounded arrays

OBS subj q1 q2 q3 q4 q5 q6
1 1001 1 2 0 1 0 0
2 1002 2 1 1 1 1 0
3 1003 2 2 1 0 . 1
4 1004 1 . 0 0 0 1
5 1005 2 1 1 1 1 0

この プログラムと前のプログラムを比較すると、2 つの違いがあることがわかります。1 つ目は、ARRAY ステートメントでここでは 配列qxsの下限を 3、上限を 6 と定義している点です。一般に、この方法で任意の配列次元の下限と上限を定義できます。つまり、下限を指定し、その後にコロン(:)、そして上限を指定します。2 つ目の違いは、DO ループでは、インデックス変数「i」の範囲が明示的に 3 から 6 まで定義されている点です。これは前のプログラムの 1 から DIM(qxs) (この場合は 4) までとは異なります。

#

さらに、配列の次元の境界の扱いを自動化することができます。次のプログラムでは、前の 2 つのプログラムと同様に、1 次元配列 qxs を使って 4 つの調査変数を再コード化しています。しかし、ここではアスタリスク(*) を使って配列qxsの次元を取得し、LBOUND 関数と HBOUND 関数を使って、DO ループのインデックス変数の下限と上限をそれぞれ動的に指定しています。

data survey3 (drop = i);
  input subj q1 q2 q3 q4 q5 q6;
  array qxs[*] q3-q6;
  do i = lbound(qxs) to hbound(qxs);
      qxs[i] = qxs[i] - 1;
  end;
  datalines;
1001 1 2 1 2 1 1
1002 2 1 2 2 2 1
1003 2 2 2 1 . 2
1004 1 . 1 1 1 2
1005 2 1 2 2 2 1
;
run;

title 'The survey data by changing upper and lower bounds automatically';
proc print data = survey3;
run;
SAS 出力

The survey data by changing upper and lower bounds automatically

OBS subj q1 q2 q3 q4 q5 q6
1 1001 1 2 0 1 0 0
2 1002 2 1 1 1 1 0
3 1003 2 2 1 0 . 1
4 1004 1 . 0 0 0 1
5 1005 2 1 1 1 1 0

このプログラムと前のプログラムを比較すると、2 つの違いがあります。1 つ目は、ARRAY ステートメントの中のアスタリスク (*) が、配列qxs の宣言時に次元を判断させる点です。配列の要素数をカウントし、qxsの次元は4とされます。2つ目は、DO ループでは、インデックス変数「i」の範囲が LBOUND(qxs) から HBOUND(qxs) に動的に決定されている点です。

2次元配列#

2次元配列は、1次元配列をそのまま拡張したようなものです。次のような1次元配列

ARRAY barkers[4] dog1-dog4;

を変数の1行

dog1 dog2 dog3 dog4

と考えることができます。一方、次のような2次元配列

ARRAY pets[2,4] dog1-dog4 cat1-cat4;

は、変数の複数行として考えられます。

dog1 dog2 dog3 dog4  
cat1 cat2 cat3 cat4  

上の ARRAY ステートメントが示すように、2 次元配列を定義するには、カンマ区切りで各次元の要素数を指定します。一般に、最初の次元数が配列の行数を、2 番目の次元数が列数を示します。
2次元配列を定義すると、配列要素は ARRAY ステートメントの順序でグループ化されます。例えば、配列 horse を

ARRAY horse[3,5] x1-x15;

と定義すると、SAS は以下のように要素を割り当てます。

x1 x2 x3 x4 x5  
x6 x7 x8 x9 x10
x11 x12 x13 x14 x15

このセクションでは、Family History データのサブセットで欠損値を検索する2つの例を見ていきます。 editという2次元配列を1つ使用し、最初の行には実際のデータが入り、2 番目の行には、2 次元配列の対応する列の値についての欠損の有無を示す 0/1 のステータスが入ります。

#

このプログラムはFamily Historyデータのサブセットの欠損値を探します。2 次元配列editを使用して、最初の次元にfamily historyの変数をグループ化し、ステータス変数(stat1、…、stat14)を 2 番目の次元にグループ化するよう指示しています。

data fhx;
  input subj v_date mmddyy8. fhx1-fhx14;
  array edit[2,14] fhx1-fhx14 stat1-stat14;
  do i = 1 to 14;
    edit[2,i] = 0;
    if edit[1,i] = . then edit[2,i] = 1;
  end;
  datalines;
220004  07/27/93  0  0  0  .  8  0  0  1  1  1  .  1  0  1
410020  11/11/93  0  0  0  .  0  0  0  0  0  0  .  0  0  0
520013  10/29/93  0  0  0  .  0  0  0  0  0  0  .  0  0  1
520068  08/10/95  0  0  0  0  0  1  1  0  0  1  1  0  1  0
520076  08/25/95  0  0  0  0  1  8  0  0  0  1  1  0  0  1
;
run;

title 'The FHX data itself'; 
proc print data = fhx;
  var fhx1-fhx14;  
run;

title 'The presence of missing values in FHX data'; 
proc print data = fhx;
  var stat1-stat14;    
run;
SAS 出力

The FHX data itself

OBS fhx1 fhx2 fhx3 fhx4 fhx5 fhx6 fhx7 fhx8 fhx9 fhx10 fhx11 fhx12 fhx13 fhx14
1 3 0 0 0 . 8 0 0 1 1 1 . 1 0
2 3 0 0 0 . 0 0 0 0 0 0 . 0 0
3 3 0 0 0 . 0 0 0 0 0 0 . 0 0
4 5 0 0 0 0 0 1 1 0 0 1 1 0 1
5 5 0 0 0 0 1 8 0 0 0 1 1 0 0

The presence of missing values in FHX data

OBS stat1 stat2 stat3 stat4 stat5 stat6 stat7 stat8 stat9 stat10 stat11 stat12 stat13 stat14
1 0 0 0 0 1 0 0 0 0 0 0 1 0 0
2 0 0 0 0 1 0 0 0 0 0 0 1 0 0
3 0 0 0 0 1 0 0 0 0 0 0 1 0 0
4 0 0 0 0 0 0 0 0 0 0 0 0 0 0
5 0 0 0 0 0 0 0 0 0 0 0 0 0 0

2行14列の2次元配列editを定義するARRAYステートメントが1つだけあります。ARRAYステートメントは、family history変数(fhx1, …, fhx14)を1番目の次元にグループ化し、ステータス変数(stat1, …, stat14)を2番目の次元にグループ化します。そして、DOループは、14個の変数の内容を確認し、ステータス次元の各要素に0を代入します(edit[2,i] = 0;)。しかし、1番目の次元の要素が欠落している場合、2番目の次元の要素を0から1に変更します(if edit[1,i] = . then edit[2,i] = 1)。

データの変形#

横持ち/縦持ちデータとは何でしょうか?

横持ち(Wide)とは、複数の列が1つのオブザベーションに対応していることを意味します。例えば、「visit1」、「visit2」、「visit3」などです。

data wide;
  input id visit1 visit2 visit3;
  infile datalines;
  datalines;
1 10 4 3
2 5 6 .
;
run;

title 'Wide Dataset';
proc print data = wide;   
run;
SAS 出力

Wide Dataset

OBS id visit1 visit2 visit3
1 1 10 4 3
2 2 5 6 .

縦持ち(Long)とは、1つのオブザベーションに対して複数の行があることを意味します。

data long;
  input id visit value;
  infile datalines;
  datalines;
1 1 10 
1 2 4 
1 3 3
2 1 5 
2 2 6 
2 3 .
;
run;

title 'Long Dataset';
proc print data = long;   
run;
SAS 出力

Long Dataset

OBS id visit value
1 1 1 10
2 1 2 4
3 1 3 3
4 2 1 5
5 2 2 6
6 2 3 .

SASでは、横持ち形式と縦持ち形式の間でデータを変形する方法が2つあります。

  • PROC TRANSPOSE

  • データステップ

いくつかの例をから両方の方法を見ていきます。

縦持ち(Long)形式から横持ち(Wide)形式への変形#

前のセクションのデータセットtallgradesを思い出してください。

data tallgrades;
  input idno 1-2 l_name $ 5-9 gtype $ 12-13 grade 15-17;
  cards;
10  Smith  E1  78
10  Smith  E2  82
10  Smith  E3  86
10  Smith  E4  69
10  Smith  P1  97
10  Smith  F1 160
11  Simon  E1  88
11  Simon  E2  72
11  Simon  E3  86
11  Simon  E4  99
11  Simon  P1 100
11  Simon  F1 170
12  Jones  E1  98
12  Jones  E2  92
12  Jones  E3  92
12  Jones  E4  99
12  Jones  P1  99
12  Jones  F1 185
;
run;

title 'The tall grades data set'; 
proc print data = tallgrades noobs;   
run;
SAS 出力

The tall grades data set

idno l_name gtype grade
10 Smith E1 78
10 Smith E2 82
10 Smith E3 86
10 Smith E4 69
10 Smith P1 97
10 Smith F1 160
11 Simon E1 88
11 Simon E2 72
11 Simon E3 86
11 Simon E4 99
11 Simon P1 100
11 Simon F1 170
12 Jones E1 98
12 Jones E2 92
12 Jones E3 92
12 Jones E4 99
12 Jones P1 99
12 Jones F1 185

データセットtallgradesには、各学生の各成績が1行ずつ含まれています。学生は、IDナンバー(idno)と苗字(l_name)で識別されています。このデータセットには6種類の成績が含まれています。それぞれ100点満点の試験1「E1」、試験2「E2」、試験3「E3」、試験4「E4」、100点満点のプロジェクト1「P1」、および200点満点の期末試験「F1」です。プログラムを開いて実行し、次の2つの例でデータセットtallgradesを操作できるようにしましょう。

#

この例では、データステップを使ってデータセットtallgradesを横持ち形式から縦持ち形式に転置します。これには、配列、RETAINステートメント、OUTPUTステートメント、そしてFIRST.およびLAST.SAS変数の使用が必要になります。

data fatgrades;
  set tallgrades;
  by idno;
  retain E1-E4 P1 F1 i;
  array allgrades [6] E1-E4 P1 F1;
  if first.idno then i = 1;
  allgrades[i] = grade;
  if last.idno then output;
  i = i + 1;
  drop i gtype grade;
run;

title 'The fat grades data set'; 
proc print data=fatgrades;  
run;
SAS 出力

The fat grades data set

OBS idno l_name E1 E2 E3 E4 P1 F1
1 10 Smith 78 82 86 69 97 160
2 11 Simon 88 72 86 99 100 170
3 12 Jones 98 92 92 99 99 185

このコードは難しそうですね!少し分解しましょう。まず、データセットtallgradesは「idno」ごとに処理されます。これにより、変数「first.idno」と「last.idno」を使用できるようになります。ARRAYステートメントは、配列allgradesを定義し、番号付きの範囲リストを使って、この配列を6つの(初期化されていない)変数「E1」、「E2」、「E3」、「E4」、「P1」、「F1」に関連付けています。配列allgradesは、転置される前に各学生の6つの成績を保持するために使用されます。
allgradesを含む配列の要素は、インデックス変数を使って割り当てられなければならないため、この転置は次のように行われます:

  • (“if first.idno then i = 1;”) 入力オブザベーションに、まだデータセットで現れていない「idno」が含まれている場合、インデックス変数「i」は1に初期化されます。入力オブザベーションに新しい「idno」が含まれていない場合は、何もせずに次のステップに進みます。

  • (“allgrades[i] = grade;”) 現在のオブザベーションからの成績が、配列allgradesに割り当てられます。(例えば、入力オブザベーションが”Smith”の最初の成績である場合、allgrades[1]に値78が割り当てられます。入力オブザベーションが”Smith”の2番目の成績である場合、allgrades[2]に値82が割り当てられます。以下同様です。) これは、各学生の成績の順序が同じであることを前提としています。

  • (“if last.idno then output;”) 入力オブザベーションがその「idno」を含む最後のオブザベーションである場合、プログラムデータベクトル(allgradesを含む)を出力データセットに出力します。(例えば、入力オブザベーションが”Smith”の期末試験の成績である場合、彼の6つの成績を含む横に長いオブザベーションを出力します)。入力オブザベーションがその「idno」を含む最後のオブザベーションでない場合は、何もせずに次のステップに進みます。

  • (“i = i + 1;”) 次に、インデックス変数「i」を1増やします。(例えば、「i」が1の場合、2に変更します)。

  • (“retain E1-E4 P1 F1 i;”) 次の反復の開始時に「E1」、「E2」、「E3」、「E4」、「P1」、「F1」、「i」をミッシングに設定するのではなく、現在の値を保持します。(例えば、”Smith”の場合、allgrades(1)は値78を保持し、allgrades(2)は値82を保持します。など) インデックス変数「i」についても同様です。新しい学生が現れるまで、この変数をリセットしたくありません。

プログラムは、データセットの最後のオブザベーションが現れるまで、上記の5ステップを繰り返し行います。その後、変数「i」、「gtype」、「grade」は出力データセットfatgradesから削除されます。

#

SASにはTRANSPOSEというプロシージャがあり、横持ち形式から縦持ち形式にデータセットを転置するのに使うことができます。個人的には、この機能は(少なくともデータステップの使用に慣れてしまうと)データステップの方法と比べるとやや直感的でないと感じるので、私はデータステップを使用することが多いです。しかし、この次の例では、PROC TRANSPOSEを使って同じ転置を行う方法を示し、どちらの方法が好ましいかは読者の判断に任せることにします。

proc transpose data = tallgrades 
               out = fatgrades2 (drop = _NAME_) ;
  by idno l_name;
  var grade;
  id gtype;
run;

title 'The fat grades data set'; 
proc print data = fatgrades2;    
run;
SAS 出力

The fat grades data set

OBS idno l_name E1 E2 E3 E4 P1 F1
1 10 Smith 78 82 86 69 97 160
2 11 Simon 88 72 86 99 100 170
3 12 Jones 98 92 92 99 99 185

PROC TRANSPOSEでは

  • OUTステートメントは PROC SORT と同様、PROC TRANSPOSE によって作成される出力データセットの名前を指定してます。

  • BYステートメントは、1 行に転置されたグループ内で一意である変数を指定します。この場合、1つのオブザベーションは学生1人に対応します。「idno」が主キーですが、名字(l_name)も保持する必要があります。BYステートメントに追加しないと、「l_name」は転置されたデータセットから除外されてしまいます。

  • VARステートメントは、転置される値を含む変数を指定します。この場合、ここでは「grade」です。

  • IDステートメントは、既存の変数の値から列名を指定するために使用します。この場合、どの成績かを識別するために「gtype」を使用します。

横持ち(Wide)形式から縦持ち(Long)形式への変形#

次の2つの例では、データセットgradesを横持ちにしたものを元の縦持ちのデータセットに変換する方法を扱います。

#

この例では、データステップを使ってデータセットgradesを横持ちの形式から縦持ちの形式に転置します。

data tallgrades2;
  set fatgrades;
  array gtypes[6] $ _temporary_ ('E1' 'E2' 'E3' 'E4' 'P1' 'F1');
  array grades[*] E1 -- F1;
  do i = 1 to 6;
    gtype = gtypes[i];
    grade = grades[i];
    output;
  end;
  drop E1--F1 i ;
run;

title 'Tallgrades2 Data';
proc print data = tallgrades2 noobs;   
run;
SAS 出力

Tallgrades2 Data

idno l_name gtype grade
10 Smith E1 78
10 Smith E2 82
10 Smith E3 86
10 Smith E4 69
10 Smith P1 97
10 Smith F1 160
11 Simon E1 88
11 Simon E2 72
11 Simon E3 86
11 Simon E4 99
11 Simon P1 100
11 Simon F1 170
12 Jones E1 98
12 Jones E2 92
12 Jones E3 92
12 Jones E4 99
12 Jones P1 99
12 Jones F1 185

「gtypes」は一時的な文字列配列で、列名(評価の種類)を変数「gtypes」に割り当て、配列gradesは現在の行(つまり現在の学生)の成績を格納して、変数「grade」に割り当てるためのものです。DOループにはOUTPUTステートメントがあり、各成績を独立した行に出力します。「idno」と「l_name」は、出力するべき成績が残っている限り、行から行へと引き継がれます。そして、DOループを抜けて次の行(学生)に移ります。最後に、(横長の)列「E1」、「E2」、「E3」、「E4」、「P1」、「F1」とインデックス「i」を最終的なデータセットから削除して、元の縦持ちのデータセットを取得します。
列名を取得する別の方法に、文字列配列にそれらを手動でリストアップするのではなく、vname関数を使う方法があります。vname関数は、変数に適用すると、その変数名を文字列として返します。

data tallgrades3;
  set fatgrades;
  array grades[*] E1 -- F1;
  do i = 1 to 6;
    gtype = vname(grades[i]);
    grade = grades[i];
    output;
  end;
  drop E1--F1 i ;
run;

title 'Tallgrades3 Data';
proc print data = tallgrades3 noobs;   
run;
SAS 出力

Tallgrades3 Data

idno l_name gtype grade
10 Smith E1 78
10 Smith E2 82
10 Smith E3 86
10 Smith E4 69
10 Smith P1 97
10 Smith F1 160
11 Simon E1 88
11 Simon E2 72
11 Simon E3 86
11 Simon E4 99
11 Simon P1 100
11 Simon F1 170
12 Jones E1 98
12 Jones E2 92
12 Jones E3 92
12 Jones E4 99
12 Jones P1 99
12 Jones F1 185

#

PROC TRANSPOSE を使って前の例と同じ操作を行う方法も紹介します。

proc transpose data = fatgrades 
               out = tallgrades3 (RENAME = (COL1 = grade 
                                            _NAME_ = gtype));
  by idno l_name;
  var E1--F1;
run;

proc print data = tallgrades3;
run;
SAS 出力

Tallgrades3 Data

OBS idno l_name gtype grade
1 10 Smith E1 78
2 10 Smith E2 82
3 10 Smith E3 86
4 10 Smith E4 69
5 10 Smith P1 97
6 10 Smith F1 160
7 11 Simon E1 88
8 11 Simon E2 72
9 11 Simon E3 86
10 11 Simon E4 99
11 11 Simon P1 100
12 11 Simon F1 170
13 12 Jones E1 98
14 12 Jones E2 92
15 12 Jones E3 92
16 12 Jones E4 99
17 12 Jones P1 99
18 12 Jones F1 185

BYステートメントには、単一のオブザベーションを定義するグループ化変数を指定し、横持ちの形式から縦持ちの形式に変形する際に、指定された変数を新しい行にコピーします。VARステートメントは、複数行に集約される列をすべて定義します。2つの新しい列が追加され_NAME_ には以前の列名、_COL1_ には現在の行に対応する変換前の列での値が格納されます。一般には、データセットオプションのRENAME=オプションを使って、これらのデフォルト名を変更したいと思うでしょう。

データセットの結合#

このセクションでは、以下の方法で複数のデータセットを単一のデータセットに結合する方法を扱います。

  • SETステートメントを使用して2つのデータセット縦に連結する

  • MERGEステートメントを使用してデータセットをマージし、内部結合、外部結合、左結合、右結合を行う

2つ以上のデータセットの連結#

2つ以上のデータセットを連結するとは、単一のデータセットに1つの”上に”もう1つを積み重ねることを意味します。例えば、データセットstore1には、3つの変数「store」(番号)、「day」(曜日)、「sales」(ドル)が含まれているとします。

Store

Day

Sale

1

M

1200

1

T

1435

1

W

1712

1

R

1529

1

F

1920

1

S

2325

そしてデータセットstore2には同じ3変数が含まれます。

Store

Day

Sale

2

M

2215

2

T

2458

2

W

1789

2

R

1692

2

F

2105

2

S

2847

そしてこの2つを連結すると、私が”背の高い”データセットと呼ぶものが得られます:

Store

Day

Sale

1

M

1200

1

T

1435

1

W

1712

1

R

1529

1

F

1920

1

S

2325

2

M

2215

2

T

2458

2

W

1789

2

R

1692

2

F

2105

2

S

2847

連結すると、データセットが上に積み重なった形になります。新しいデータセットのオブザベーションの数は、元のデータセットのオブザベーションの数の合計になることに注意してください。データセットを連結するには、単にSETステートメントで一覧のデータセット名を指定するだけです。
変数の属性には、変数の種類(文字型か数値型か)、インフォーマット(変数の読み込み方法)とフォーマット(値の出力方法)、変数の長さ、ラベル(変数名の出力方法)などがあります。入力データセット間で変数属性が異なる場合、連結にはいくつかの問題があります。

  • SETステートメントで名前を指定したデータセットに、名前と種類が同じ変数が含まれている場合は、データセットを変更せずに連結できます。

  • 変数の種類が異なる場合は、連結する前に1つ以上のデータセットを修正する必要があります。SASはデータセットを連結しません。

  • 長さ、フォーマット、インフォーマット、ラベルが異なる場合は、連結する前に1つ以上のデータセットを修正したい場合があります。SASはデータセットを連結しますが、結果が気に入らない可能性があります。これらの属性は、最初に読み込まれた(SETステートメントの最初の)データセットから取得されます。

#

以下のプログラムはデータセットstore1とstore2を連結し、新しい縦持ちのデータセットbothstoreを作成します。

data store1;
  input Store Day $ Sales;
  datalines;
1 M 1200
1 T 1435
1 W 1712
1 R 1529
1 F 1920
1 S 2325
;
run;
 
data store2;
  input Store Day $ Sales;
  datalines;
2 M 2215
2 T 2458
2 W 1798
2 R 1692
2 F 2105
2 S 2847
;
run;
 
data bothstores;
  set store1 store2;
run;

title 'The bothstores data set'; 
proc print data = bothstores noobs;  
run;
SAS 出力

The bothstores data set

Store Day Sales
1 M 1200
1 T 1435
1 W 1712
1 R 1529
1 F 1920
1 S 2325
2 M 2215
2 T 2458
2 W 1798
2 R 1692
2 F 2105
2 S 2847

データセットstore1とstore2には、同じ変数「Store」、「Day」、「Sales」が同じ属性で含まれています。3番目のデータステップでは、DATAステートメントは新しいデータセットbothstoresを作成し、SETステートメントはこのデータセットに最初にstore1のオブザベーションを、次にstore2のオブザベーションを含めるように指示しています。ここでは2つの入力データセットのみ指定していますが、SETステートメントには任意の数の入力データセットを含めることができます。
プログラムを開いて実行し、PRINTプロシージャの出力を確認からstore1とstore2のデータセットを結合して縦持ちのデータセットbothstoresを作成したことを確認してください。次に、SETステートメントを編集してstore2の後にstore1が来るようにし、データセットbothstoresでstore2の内容の後にstore1の内容が続くようになったことを確認するため、再実行してみてください。
一般に、データセットを連結して作成したデータセットには、すべての入力データセットの変数とすべてのオブザベーションが含まれます。したがって、新しいデータセットに含まれる変数の数は、すべての入力データセットのユニークな変数の総数と常に等しくなります。また、新しいデータセットのオブザベーションの数は、入力データセットのオブザベーションの数の合計になります。ここで使用したデータの例に戻りましょう。

#

以下のプログラムはデータセットoneとtwoを連結し、新しく縦持ちのデータセットonetopstwoを作成します。

data one;
  input ID VarA $ VarB $;
  datalines;
10 A1 B1
20 A2 B2
30 A3 B3
;
run;
 
data two;
  input ID VarB $ VarC $;
  datalines;
40 B4 C1
50 B5 C2
;
run;
 
data onetopstwo;
  set one two;
run;

title 'The onetopstwo data set'; 
proc print data = onetopstwo noobs;    
run;
SAS 出力

The onetopstwo data set

ID VarA VarB VarC
10 A1 B1  
20 A2 B2  
30 A3 B3  
40   B4 C1
50   B5 C2

最初の2つのデータテップでそれぞれデータセットoneとtwoの読み込みを確認すると、ユニークな変数の総数は4つ(ID、VarA、VarB、VarC)であることがわかります。2つの入力データセットのオブザベーションの総数は3+2=5です。したがって、連結されたデータセットonetopstwoには4つの変数と5つのオブザベーションが含まれることが予想されます。プログラムを開いて実行し、出力からデータセットoneのすべての変数とすべてのオブザベーション、次にデータセットtwoのすべての変数とすべてのオブザベーションを取り込んだことを確認してください。ご覧のとおり、うまく機能するためには、データセットone由来のオブザベーショでは「VarC」が欠損値になり、データセットtwo由来のオブザベーションでは「VarA」が欠損値になります。

データセットのマッチマージ#

マッチマージは、2つ以上のデータセットを結合する最も強力な方法の1つです。マッチマージでは、1つ以上の共通変数の値に基づいて、データセット間でオブザベーションを組み合わせます。

架空のデータについて、データセットbaseには、idが 1〜10の被験者のベースラインデータと年齢が含まれており、データセットvisitsには、idが1〜8と11のデータ、来院番号(すべての被験者でで3回の来院)、および定量的な結果測定値が含まれているとします。

マッチマージを行うには、マージするデータセットをMERGEステートメントで指定し、マージ対象の変数をBYステートメントで指定します。ただし、マージする変数(BYステートメントに現れる変数)でソートされていないと、データセットをマッチマージできないことに注意が必要です。マージ対象の変数は、両方のデータセットで同じ名前でなければなりません。そうでない場合は、マージする前に、renameオプションを使って一方のデータセットの変数名を変更する必要があります。

#

以下のプログラムでは、被験者のidを使用してデータセットbaseとvisitsを外部結合します。

data base;
   input id age;
   datalines;
1 50
2 51
3 52
4 53
5 54
6 55
7 56
8 57
9 58
10 59
;
run;

data visits;
  do id = 1 to 8;
    do visit = 1 to 3;
      outcome = 10*id + visit;
      output;
    end;
  end;
  id = 11;
  visit = 3;
  outcome = 50;
  output;
run;

title "Baseline Dataset";
proc print data = base;
run;

title "Visits Dataset";
proc print data = visits;   
run;
SAS 出力

Baseline Dataset

OBS id age
1 1 50
2 2 51
3 3 52
4 4 53
5 5 54
6 6 55
7 7 56
8 8 57
9 9 58
10 10 59

Visits Dataset

OBS id visit outcome
1 1 1 11
2 1 2 12
3 1 3 13
4 2 1 21
5 2 2 22
6 2 3 23
7 3 1 31
8 3 2 32
9 3 3 33
10 4 1 41
11 4 2 42
12 4 3 43
13 5 1 51
14 5 2 52
15 5 3 53
16 6 1 61
17 6 2 62
18 6 3 63
19 7 1 71
20 7 2 72
21 7 3 73
22 8 1 81
23 8 2 82
24 8 3 83
25 11 3 50

データセットbaseとvisitsを外部結合するには、MERGEステートメントでそれら2つのデータセットを指定し、共通の変数「id」woBYステートメントで指定するだけです。これら2つのデータセットはすでに「id」によりソート済みである点に注意してください。そうでなければ両方のデータセットを先にソートしておく必要があります。

data outer;
  merge base visits;
  by id;
run;

title "Outer Join";
proc print data = outer;   
run;
SAS 出力

Outer Join

OBS id age visit outcome
1 1 50 1 11
2 1 50 2 12
3 1 50 3 13
4 2 51 1 21
5 2 51 2 22
6 2 51 3 23
7 3 52 1 31
8 3 52 2 32
9 3 52 3 33
10 4 53 1 41
11 4 53 2 42
12 4 53 3 43
13 5 54 1 51
14 5 54 2 52
15 5 54 3 53
16 6 55 1 61
17 6 55 2 62
18 6 55 3 63
19 7 56 1 71
20 7 56 2 72
21 7 56 3 73
22 8 57 1 81
23 8 57 2 82
24 8 57 3 83
25 9 58 . .
26 10 59 . .
27 11 . 3 50

外部結合では、両方のデータセットからすべての変数と行が保持され、両方のデータセットの「id」が単一の列に結合されています。「id」の値がデータセットbaseにあり、visitsにない場合、baseに含まれない変数「visit」および「outcome」欠損値に設定されます(id 9と10の行を参照)。一方、「id」の値がデータセットvisitにありbaseにない場合、visitsには含まれていない変数「age」が欠損値に設定されます(id 11の行を参照)。

内部結合、左結合、および右結合を行う方法を学ぶ前に、データセットオプションのIN= オプションについて説明する必要があります。IN=オプションは、ステータス変数を作成し、その値は現在のオブザベーションが入力データセットから来るかどうかによって0または1になります。オブザベーションが入力データセットから来ない場合、ステータス変数の値は0になります。オブザベーションが入力データセットから来る場合、ステータスの値は1になります。IN=オプションは、次の2つで扱うするデータセットのマージと連結時に特に便利です。INオプションの基本的な形式は次のとおりです。

IN = 変数名

ここで、変数名は入力データセットの変数名です。前の例を見直し、IN=変数 の値を追跡してみましょう。

#

データセットbaseとvisitをもう一度結合しますが、今度はそれぞれのデータセットのIN=変数を作成し、その値を2つの永続的な変数「in_base」と「in_visit」に格納します。

data outer2;
  merge base (in = in1) 
        visits (in = in2);
  by id;
  in_base = in1;
  in_visit = in2;
run;

title "Outer Join with IN Variables";
proc print data = outer2 (firstobs=22 obs=27);   
run;
SAS 出力

Outer Join with IN Variables

OBS id age visit outcome in_base in_visit
22 8 57 1 81 1 1
23 8 57 2 82 1 1
24 8 57 3 83 1 1
25 9 58 . . 1 0
26 10 59 . . 1 0
27 11 . 3 50 0 1

「id」が1から8は両方のデータセットに現れるため、これらの「id」に対応するすべての行で「in_base」と「in_visit」の値は両方とも1になります。これは、両方のデータセットからデータを組み合わせてこれらの行を作成したためです。しかし、「id」 9と10はデータセットbaseにのみ存在するため、「in_base」は1ですが「in_visit」は0です。これは、visitsのデータを使ってこれらの行を作成しなかったためです。同様に、id 11の場合、データはデータセットvisitsにのみ含まれていたため、「in_visit」は1ですが「in_base」は0です。

内部結合を行う場合、両方のデータセットで一致する「id」を持つ行のみを保持します。IN=変数を使用して両方(または複数のデータセットがある場合はすべて)が1の行のみを保持します。

#

次のプログラムは、データセットbaseとvisitsで内部結合を実行します。つまり、キー変数「id」の値が両方のデータセットに存在する行のみを保持します。

data inner;
  merge base (in = in1) 
        visits (in = in2);
  by id;
  if in1 = 1 and in2 = 1; *subsetting IF. Only keep these rows;
run;

title "Inner Join";
proc print data = inner;   
run;
SAS 出力

Inner Join

OBS id age visit outcome
1 1 50 1 11
2 1 50 2 12
3 1 50 3 13
4 2 51 1 21
5 2 51 2 22
6 2 51 3 23
7 3 52 1 31
8 3 52 2 32
9 3 52 3 33
10 4 53 1 41
11 4 53 2 42
12 4 53 3 43
13 5 54 1 51
14 5 54 2 52
15 5 54 3 53
16 6 55 1 61
17 6 55 2 62
18 6 55 3 63
19 7 56 1 71
20 7 56 2 72
21 7 56 3 73
22 8 57 1 81
23 8 57 2 82
24 8 57 3 83

これで、「id」の値が1から8までの行のみが結合されたデータセットに残りました。これらがbaseおよびvisitの両方のデータセットに存在する「id」の値だったためです。

#

この例では、IN=変数を使用して左結合と右結合を実行します。左結合では、左側(MERGEステートメントの最初のデータセット)のすべてのレコードを保持し、右側のデータセットから一致するレコードを結合し、残りは削除します。右結合ではその逆となります。

data left;
  merge base (in = in1) 
        visits (in = in2);
  by id;
  if in1 = 1; *subsetting IF. Only keep these rows;
run;

title "Left Join";
proc print data = left; 
run;

data right;
  merge base (in = in1) 
        visits (in = in2);
  by id;
  if in2 = 1; *subsetting IF. Only keep these rows;
run;

title "Right Join";
proc print data = right;
run;
SAS 出力

Left Join

OBS id age visit outcome
1 1 50 1 11
2 1 50 2 12
3 1 50 3 13
4 2 51 1 21
5 2 51 2 22
6 2 51 3 23
7 3 52 1 31
8 3 52 2 32
9 3 52 3 33
10 4 53 1 41
11 4 53 2 42
12 4 53 3 43
13 5 54 1 51
14 5 54 2 52
15 5 54 3 53
16 6 55 1 61
17 6 55 2 62
18 6 55 3 63
19 7 56 1 71
20 7 56 2 72
21 7 56 3 73
22 8 57 1 81
23 8 57 2 82
24 8 57 3 83
25 9 58 . .
26 10 59 . .

Right Join

OBS id age visit outcome
1 1 50 1 11
2 1 50 2 12
3 1 50 3 13
4 2 51 1 21
5 2 51 2 22
6 2 51 3 23
7 3 52 1 31
8 3 52 2 32
9 3 52 3 33
10 4 53 1 41
11 4 53 2 42
12 4 53 3 43
13 5 54 1 51
14 5 54 2 52
15 5 54 3 53
16 6 55 1 61
17 6 55 2 62
18 6 55 3 63
19 7 56 1 71
20 7 56 2 72
21 7 56 3 73
22 8 57 1 81
23 8 57 2 82
24 8 57 3 83
25 11 . 3 50

演習#

以下の演習では、Bike_Lanes_Wide.csv、Bike_Lanes.csv、crashes.csv、roads.csvを使用します。これらのデータセットがコンピューター上の任意の場所にダウンロードされていることを確認してください(README参照)。

  1. Bike_Lanes_Wide.csvデータセットをPROC IMPORTでデータセットwideとして読み込み、最初の数行を出力してください。

  2. データセットwideをPROC TRANSPOSEまたはデータステップを使って縦持ち形式に変形してください。「name」以外のすべての列を対象にする必要があります。新しい2つの列「lanetype」(元の列名)と「the_length」(データ値)を持つ縦持ちのデータセットに変換します。変数「the_length」の’NA’の値は.に置き換え、数値型に変換します。

  3. roads.csvとcrashes.csvファイルをデータセットroadcrashとして読み込みんでください。

  4. データセットcrashの変数「Road」にあるハイフン(-)をすべて空白に置き換え(tranwrdを使用)、データセットcrash2とします。変数「Road」をPROC FREQにより度数表を作成してください。

  5. データセットcrashroadのそれぞれに何件のオブザベーションがありますか?

  6. crash2の「Road」scanを使って(「type」と「number」)に分割します。この結果を再びcrash2に割り当てます。crash2の「type」をPROC FREQで度数表を作成してください。次に、連結関数(CATなど)を使って新しい変数「road_hyphen」を作成します。「type」と「number」の列をハイフン(-)で結合し、「road_hyphen」をPROC FREQで度数表を作成してください。

  7. crashデータセットでは、どの年のデータが収集されましたか?何年分ありますか?

  8. Bike_Lanes.csvをデータセットbikeとして読み込んでください。

  9. 「type」と「name」に欠損値がない行のみを残し、出力をbikeに再度割り当ててください。

  10. PROC MEANSでBYステートメントを使い、「name」と「type」でグループ化(各nameごとのtypeごとの意味)し、「length」の合計を求めてください。OUTPUTステートメントを使ってこの要約データセットを出力し、「name」、「type」と合計した「length」(これをlengthに名前を変更)のみを保持し、データセットsubとしてください。

  11. subを縦持ちの形式から「type」を変数名、「length」を値とした横持ちの形式に変換してください。(注: 「name」にはスペースが含まれます。変数名にする前にスペースを置き換える必要はあるでしょうか? データステップによる方法はすべての「name」が同じ「type」の値を持っていないことから、難しくなっています。)

  12. データセットcrashroadを「Road」をキーとして結合し完全ケースだけを含むようにして(内部結合を使用)、名前をmergedとしてください。いくつのオブザベーションが含まれますか?

  13. 外部結合により結合して、名前はfull_joinとしてください。 いくつのオブザベーションが含まれますか?

  14. roadcrashを左結合により結合してください。ここでは順番が重要になります。いくつのオブザベーションが含まれますか?

  15. 上記を右結合により結合してください。いくつのオブザベーションが含まれますか?