【SAS】CSVファイルの各行の文字列を一致検索条件として判定ループさせる 

# 目次
1. [前提](#前提)
2. [目的](#目的)
3. [方法](#方法)
4. [参考](#参考)
5. [所感](#所感)


#前提
- 実行環境
    - Windows10
    - Base SAS 9.4

- 必要な知識
    - SASコードの文法
        - DATAステップSETステートメントによるSDS作成
        - ATRRIBステートメントによる列定義
        - INFILEステートメントによる外部ファイル読み込み
        - SETステートメントのnobsオプションによる行数取得
        - SELECTステートメントによる条件分岐
        - index()による文字列検索
        - MACRO,MACRO変数の定義、呼び出し
        - %DO %TO %ENDによるループ

<a id="anchor2"></a>
#目的
次のようなPV(Page View)LogがSDS(SAS Data Set)で存在するとき、
#####pvlog.sas7bdat

|ID |url                                      |
|:--|:----------------------------------------|
|AAA|https://abcde.news/category              |
|AAA|https://abcde.topics                     |
|BBB|https://fghij/business/?cid=info         |
|CCC|https://https://klmno.weather/Japan/Tokyo|
|DDD|https://https://klmno.tenki/world        |
|EEE|https://https://pqrst/pollen?bid=forecast|

次のような表形式のcsvファイルを用いて、
#####page_ctgr_list.CSV

|検索方法|検索文字列                       |カテゴリ|
|:-------|:-------------------------------|:-------|
|部分一致|https://abcde.news               |ニュース|
|完全一致|https://fghij/business/?cid=info |ニュース|
|部分一致|https://klmno.weather            |天気    |
|完全一致|https://pqrst/pollen?bid=forecast|天気    |

以下のようにカテゴリを付与する。
#####pvlog_ctgr.sas7bdat

|ID |url                                      |カテゴリ|
|:--|:----------------------------------------|:-------|
|AAA|https://abcde.news/category              |ニュース|
|AAA|https://abcde.topics                     |        |
|BBB|https://fghij/business/?cid=info         |ニュース|
|CCC|https://https://klmno.weather/Japan/Tokyo|天気    |
|DDD|https://https://klmno.tenki/world        |        |
|EEE|https://https://pqrst/pollen?bid=forecast|天気    |

<a id="anchor3"></a>
#方法
- 処理手順概要
    1. page_ctgr.csvをSDS化して、1行目から順に各列値をマクロ変数に保存<br>
    (n行*m列のとき、n*m個のマクロ変数ができる)。
    2. pvlog.sas7bdatの各urlに対して、page_ctgr.csvの検索文字と検索方法で1行目から順に判定。
    3. 検索一致した行のカテゴリを付与。1つも一致しなければ欠損にする。

- 実装したSASコード

```SAS
* 入出力フォルダ指定(任意);
libname I_DIR = "/project/input/" access = readonly
libname O_DIR = "/project/output/"

* ページカテゴリ取得マクロ定義;
%MACRO GetPageCtgr(INPUT_DIR=
                  ,I_SDS_NM=
                  ,INFILE_NM=
                  ,OUTPUT_DIR=
                  ,O_SDS_NM=
);

* csvを読み込みSDSに変換;
data work.page_ctgr_list;
  attrib
    search_type   length = $8.   label = "検索方法"
    search_string length = $500. label = "検索文字列"
    page_ctgr     length = $8.   label = "ページカテゴリ"
  ;
  infile "&INPUT_DIR.&INFILE_NM."
    dlm = ","
    firstobs = 2
    dsd
    missover
  ;
  input search_type search_string page_ctgr;
run;

* page_ctgr_listの行数および1行目から順に各列値をマクロ変数に保存;
data _null_;
    set  work.page_ctgr_list end = eof;
    if  eof then call symputx("PAGE_CTGR_LIST_OBS_CNT", _N_);
    call symputx(cats("SEARCH_TYPE", _N_)  , search_type);
    call symputx(cats("SEARCH_STRING", _N_), search_string);
    call symputx(cats("PAGE_CTGR",_N_)     , page_ctgr);
run;

* pvlogのurlに対応するページカテゴリを付与する;
data &OUTPUT_DIR.&O_SDS_NM.;
  set &INPUT_DIR.&I_SDS_NM.;
  
  * 検索方法は部分一致と完全一致の2種類のみであるのは既知とし、部分一致を先に判定する;
  * 1つも一致しないときは何もしない＝ページカテゴリは欠損になる;
  select;
    %DO I = 1 %TO &PAGE_CTGR_LIST_OBS_CNT.;
      %IF &&SEARCH_TYPE&I.. = 部分一致 %THEN %DO;
        when(index(url, "&&SEARCH_STRING&I..")) page_ctgr = "&&PAGE_CTGR&I..";
      %END
      %IF &&SEARCH_TYPE&I.. = 完全一致 %THEN %DO;
        when(url = "&&SEARCH_STRING&I..")) page_ctgr = "&&PAGE_CTGR&I..";
      %END
    %END;
    otherwise;
  end;
run;

%MEND GetPageCtgr;

* マクロ実行;
%GetPageCtgr(INPUT_DIR  = I_DIR
            ,I_SDS_NM   = pvlog
            ,INFILE_NM  = page_ctgr_list.csv
            ,OUTPUT_DIR = O_DIR
            ,O_SDS_NM   = pvlog_ctgr
);
```
<a id="anchor4"></a>
#参考
- https://sas-boubi.blogspot.com/2014/09/call-symputx.html<br>
このページを見る前は行数と列値のマクロ変数保存を2ステップに分けて次のように実行していた。

```SAS
* page_ctgr_listの行数をマクロ変数に保存;
data _NULL_;
  set work.page_ctgr_list nobs = page_ctgr_list_obs_cnt;
  call symputx("PAGE_CTGR_LIST_OBS_CNT", page_ctgr_list_obs_cnt)
run;

* page_ctgr_listの1行目から順に各列値をマクロ変数に保存;
data _NULL_;
  set work.page_ctgr_list;
  %DO I = 1 %TO &PAGE_CTGR_LIST_OBS_CNT.
    if _N_ = &I. then do;
      call symputx("SEARCH_TYPE&I."  , search_type);
      call symputx("SEARCH_STRING&I.", search_string);
      call symputx("PAGE_CTGR&I."    , page_ctgr);
    end;
  %END;
run;
```
- 検索方法やページカテゴリ別にcsvファイルを分けて管理したいとき、<br>
一連の処理をファイル数だけループさせると再現が可能。<br>
ファイル名(文字列)リストでループを回すとき、以下が参考になる。<br>
https://y-mattu.hatenablog.com/entry/2016/08/05/010801

<a id="anchor5"></a>
#所感
- この処理を作った理由は、カテゴリリストの変更があるたびに条件式を書き換える作業を避けるためです。
- 現在Pysparkを使うようになったため、同様の処理をPysparkで実装する内容を投稿する予定です。
- QiitaのSASシンタックスハイライトではマクロ変数に色付けてくれないんですね。