ラベル 正規表現 の投稿を表示しています。 すべての投稿を表示
ラベル 正規表現 の投稿を表示しています。 すべての投稿を表示

フリーテキストから日付を取り出す② 複数出現した分だけオブザベーションに起こすパターン

昔、アルバイトしていた時に、シフト表組んでたことがあったのですが、バイトメンバーがそれぞれ出勤できない日をメールで送ってきてそれをスケジュール帳に書き起こして、とても面倒でした。

データセットにすると以下のような感じです。

data Q1;
length NAME $10. COMMENT $100.;
NAME='Aさん';COMMENT='2014/01/02と2014/01/03は休みたいです';output;
NAME='Bさん';COMMENT='無理な日、2014/01/01,2014/01/03,2014/01/04';output;
NAME='Cさん';COMMENT='予定があるので2014/01/07はでれません';output;
run;






さてこれを











のような形にするにはどうしましょうか?

前回の例では、1データに1回しか日付が含まれていないという強力な前提条件がありましたが
今回は違います。
データを受取るまで、その人によって無理な日が何日あるかもわかりません。

さて、そういったパターン出現数が可変、不明である場合に全てのパターンを抽出したい時は
call prxnextルーチンとsubstrtのコンボが有効であったりします。

先にコードから

data A1;
set Q1;
   ID=prxparse("/\d{4}\/\d{1,2}\/\d{1,2}/");
   START=1;
   STOP=length(COMMENT);
   call prxnext(ID, START,STOP,COMMENT,POSITION,LENGTH);
      do while (POSITION>0);
         YASUMI=substr(COMMENT,POSITION,LENGTH);
   output;
         call prxnext(ID, START,STOP,COMMENT,POSITION,LENGTH);
      end;
run;

結果は






です。

変数NAME YASUMIだけをkeepすれば目的とするデータセットと同じになります。

call prxnextルーチンは第一引数にprxparse関数で定義した正規パターン、第2引数はパターンマッチングを開始する位置を指定しておくと、マッチする部分があったらその終了位置に更新されます(正確にいうとPOSITION + MAX(1,LENGTH)の値)。第3引数はパターンマッチする終点位置、第4引数に対象とする変数、第5引数はパターンマッチでマッチした場合の開始位置が入ります、そして第6引数にマッチした部分の長さが入ります。

do while (POSITION>0);としているのは、call prxnextルーチンの特性として、順次、次にマッチする部分を情報を返してくれるということと、マッチする箇所がある限りPOSITIONは数字を返すということから成立するループです。

YASUMI=substr(COMMENT,POSITION,LENGTH);

は一致する開始する位置から、一致した部分の長さ分を抜き出せばいいでしょっていうシンプルな発想ですね。







フリーテキストから日付部分のみを取り出す

正規表現を詳細に解説していると、とんでもない量になるので、使用頻度が高そうな実用例を少しだけ紹介します。

フリーテキスト入力で収集されたデータから、日付とってくれない?ちなみに日付以外にコメントも一緒に入っているから、いい感じに抜き取ってね。

みたいなことってたまにあります。

気軽にいってくれるけど、結構困りますよね。


今回は、比較的楽なパターンを考えてみます。

つまり日付はコメント内で1回しか出現せず、その形式はyyyy/mm/ddで統一して入力されているとします。
また13月35日など、存在しない日付は入らないのでチェックの必要なしとします。

つまり

data Q1;
X='今日は2014/01/02です';output;
X='2014/3/2晴れ後曇り';output;
X='2014/07/24';output;
X='誕生日は2014/7/02';output;
run;











のようなデータセットから日付部分を抽出します。

data A1;
 set Q1;
 Y= prxchange("s/.*(\d{4}\/\d{1,2}\/\d{1,2}).*/$1/",1,X);
run;

で、結果は






です。

prxchange関数は、引数で指定した正規表現にマッチする箇所を、引数で指定した文字列に置換した結果を返します。

"s/.*(\d{4}\/\d{1,2}\/\d{1,2}).*/$1/" ですが、

s/は置換を意味する決まり文句です
.*(\d{4}\/\d{1,2}\/\d{1,2}).* の部分ですが、.は任意の文字、*は出現回数が0~∞を示します。
括弧はキャプチャといって、あとでその部分を$で指定することができます。
\dは0-9の数字 { }は出現回数で、{4}だと4回 {1,2}だと1回から2回の間となります
\/の\はエスケープ文字で、/事態に意味があるので、それを消して、スラッシュを単なる文字としてのスラッシュとして使うための指定です。

つまり.*(\d{4}\/\d{1,2}\/\d{1,2}).*は

何かしらの文章(なくてもよい)の次に、数字四ケタがきて、スラッシュがきて、1ケタか2ケタの数字がきて、スラッシュがきて、1ケタか2ケタの数字がきて、そのあと何かしらの文章(なくてもよい)が続くパターンということになります。

つまり日付が含まれるテキスト全体が対象になるわけです。

次に$1の部分、これは(\d{4}\/\d{1,2}\/\d{1,2})、のキャプチャに対応しています。キャプチャが複数存在する場合、つまり()がたくさんでてくる場合は出現順に連番で、$1 $2などと指定できます。

要するにここでは、日付を含むテキスト全体を、日付の部分だけで置換することで、結果的に日付のみを得ようぜってことです。

1は、今回1回しか日付がでてこない仮定なので1でいいです。

詳しくは、きちんと体系だてて勉強した方がいいです