2018年4月11日水曜日

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




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


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

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

  X  
 1 
 1.1 
 2 
 2.1 
 3 


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

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



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

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


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




範囲の書き方

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

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


 ●未満

・X < 2
・2 > X
 2未満

 ●●以上

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

 ●

・X > 2
・2 < X
 2超

 ●以上、以下  

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

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

・X > 1 and X < 2

 1超、2未満





 ●  or    or  …

・X in (1, 2, 2.1)

 1 or 2 or 2.1

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

 1以上、2以下の整数

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




注意点


注意①

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


注意②

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

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


注意③

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

8 件のコメント:

  1. はじめまして。最近SASを始めた者です。いつも参考にさせて頂いています。

    Whereステートメントでいう"contains"に該当するようなもので、IFステートメントの条件文で使えるものはありますでしょうか…。できるのなら、下記のように書きたいです。
    if 変数A contains "/" then 処理…;
    「="/"」ではなく、文字変数のどこかに"/"を含むものを探したいです。よろしくお願い致します。

    返信削除
  2. Kannaさん、コメントありがとうございます。

    ifステートメント単体だと仰っているような機能が存在しないのですが、
    index関数を用いることで実現できそうです。(index関数については以下を参照下さい)
    https://sas-boubi.blogspot.com/2017/05/index_7.html


    検索対象の変数がすべてシングルバイトならindex関数、シングルバイト以外(日本語等)が含まれている場合はkindex関数を用いて実現できます。


    以下は「変数xに"bc"が含まれていたら、変数bc_flag=1」とする例です。

    data have;
    input x:$10.;
    cards;
    abcd
    abc
    acb
    ab
    ;

    data want;
    set have;
    if index(x,"bc")>0 then bc_flag=1;
    run;



    ちなみに、今回、Kannnaさんの実現したいケースは変数Aに"/"を含むものを探したいので、

    if index(A,"/")>0 then 処理...;

    という感じになるかと思います。

    返信削除
  3. ご返信ありがとうございます!
    一度フラグを立てて…ということですね。無事に解決出来ました。ありがとうございました。これからもちょくちょくお邪魔させて頂きます。

    返信削除
    返信
    1. 解決されたとのことですが、念のため、index/kindex関数の動きと、今回の利用方法について補足を。。。

      【まずINDEX/KINDEX関数について】
      ①変数値がシングルバイトのみか、シングルバイト以外(日本語等)を含むかで、以下の通り、使える関数が異なる。

      (1)変数値がシングルバイトのみ:index関数で「指定した文字値が"何バイト目"に存在するか」を取得できる。

      (2)変数値がシングルバイト以外(日本語等)を含む:kindex関数で「指定した文字値が"何文字目"に存在するか」を取得できる。

      ②上記2つの関数ともに、指定した文字値が見つからなかった場合は「0」が返される。

      【IFステートメントでの利用】
      index/kindex関数の結果が"0より大きい"場合、変数値に指定した文字値が含まれていることになる、という発想から、

      ・上の方で説明した①(1)の場合は「if index(変数,"文字値")>0 then 処理;」
      ・上の方で説明した①(2)の場合は「if kindex(変数,"文字値")>0 then 処理;」

      という感じで、if条件一発で書くことが出来ます。

      削除
  4. ご丁寧にありがとうございます。
    少し理解が深まった気がします。まだ文字変数/数値変数の別や、日時値の変換に四苦八苦しているレベルですが、気長に頑張りたいと思います。これからもよろしくお願い致します。

    返信削除
  5. はじめまして。
    いつも拝見させて頂いております。

    SASでデータ解析をしているのですが例えば、2000年から2022年までのデータがあって、2010年以前のデータは使わないときにもif~then deleteを使って日付などの期間の指定などできるのでしょうか?
    if文を使って色々と試してみているのですがうまくいかずで、またそのような文献が見当たらないため質問させて頂きました。

    返信削除
    返信
    1. momomoさん、コメントありがとうございます。
      返信が遅くなり申し訳ございません。

      まずは、以下をご参照下さい。SASで「日付」「時間」「日時」の書き方を紹介しており、今回やりたい事の参考になりそうです。
      https://sas-boubi.blogspot.com/2018/03/blog-post.html


      では、ここからmomomoさんのご質問に対する回答なのですが、
      まず、データが「日付値」と「日時値」どちらなのかによって記述方法が変わってきます。

      /*** 対象変数が日付値の場合 ******************/
      /* サンプルデータ: 2010-01-01~2012-12-31までの適当なデータを作成 */
      data have;
      format d yymmdd10.;
      do d='01jan2010'd to '31dec2012'd;
      output;
      end;
      run;

      /* 2011-01-01以降を抽出 */
      data want;
      set have;
      where d >= '01jan2011'd;
      run;


      /*** 対象変数が日時値の場合 ******************/
      /* 2010-01-01:00:00:00~2012-12-31:23:59:59までの適当なデータを作成 */
      data have2;
      format dt datetime.;
      do dt='01jan2010:00:00:00'dt to '31dec2012:23:59:59'dt;
      output;
      end;
      run;

      /* 2011-01-01:00:00:00以降を抽出 */
      data want2;
      set have2;
      where dt >= '01jan2011:00:00:00'dt;
      run;

      こんな感じでやりたい事あっていますでしょうか。

      削除
  6. 丁寧なご返信頂きましてありがとうございます。
    日付値の解析時の文章の書き方があまり理解出来ておらずこのように書いていました。
    data dt2;
    set dt1;
    if d < 2011-01-01 then delete;
    run;

    参考にさせて頂いた結果
    data dt2;
    set dt1;
    if d < '01jan2011'd then delete;
    run;
    これでうまくいきました!
    ご教示頂本当にありがとうございます。

    返信削除