文字列関数#

ここでは文字列変数にのみ適用できる関数をいくつか確認していきます。例えば、文字列からブランクを削除したい場合は、compress関数を検討するかもしれません。あるいは、フルネームから一部の名前(名字など)を抽出したい場合は、substr関数を活用するかもしれません。今回学ぶ関数には、古くから使われている lengthsubstrcompblcompressverifyinputputtranwrdscantrimupcaselowcase| |(連結)、indexindexcspedisなどがあります。また、SASバージョン9で新しく追加された関数として、anyalphaanydigitcatxcatslengthcpropcasestripcountcountcなどがあります。

また、このタイミングでSASの様々なトピックに関する優れた情報源を紹介します。1つ目はsasCommunity.orgです。サイトを訪問すれば、このコミュニティについてもっと詳しく知ることができます。もう1つの情報源は、毎年開催されるSAS Global Forumカンファレンスです。しかし、本当に役立つのは、このカンファレンスの過去の論文やプレゼンテーションを検索できるリソースです。今回の内容はそこに基づいています!

今回の内容はRon CodyのAn Introduction to SAS Character Functionを参考にしながら進めていきます。

文字変数の長さ#

これらの関数について実際に説明する前に、SASが文字変数にどのように記憶領域の長さを割り当てるかを理解する必要があります。重要なのは、1) 文字変数の記憶領域の長さはコンパイル時に設定される 2) この長さはDATAステップで文字変数が最初に現れた時点で決まる、ことの2つです。
データセットの文字変数の記憶領域の長さを確認する方法はいくつかあります。1つの方法は、PROC CONTENTSを実行することです。SAS 9以降では、新しいlengthc関数を使って文字変数の記憶領域の長さを確認することができます。

#

次のプログラムでは、SASの文字変数の長さの性質を示しています。

data chars1;
  file print;
  string = 'abc';
  length string $ 7;  /* Does this do anything */
  storage_length = lengthc(string);
  display = ":" || string || ":";
  put storage_length= ;
  put display= ;
run;
SAS 出力

SAS システム

storage_length=3                                                                                                                    
display=:abc:                                                                                                                       

プログラムを確認してみましょう。一時データセットchars1を作成しています。FILEステートメントは、PUTステートメントの出力先を決定します。ここでは、PRINTオプションを使ってPUTの出力をPROCステップからのプロットや他の出力と同様に、ODS出力先のアウトプットに出力します。次に、文字列’abc’を含む文字変数「string」を作成しています。変数を最初に作成しているので、次の行でstringの長さを7文字に設定しても無視されます。lengthc関数を使ってstringの長さを数値変数storage_lengthに保存し、PUTステートメントで出力すると、このことが分かります。2つのPUTステートメントはどちらも

PUT <varname>=;

の形式を使用しているため、変数名と格納された値の両方が出力されています。また、文字地の連結演算子「||」を使って、変数「string」に含まれる文字値の前後にセミコロンを付けた文字列「display」を作成しています。
「string」の長さを希望する7(文字)に正しく設定するには、以下のプログラムのようにLENGTHステートメントを変数stringに値を代入する前に実行する必要があります。

data chars2;
  file print;
  length string $ 7;  /* Does this do anything */
  string = 'abc';
  storage_length = lengthc(string);
  display = ":" || string || ":";
  put storage_length= ;
  put display= ;
run;
SAS 出力

SAS システム

storage_length=7                                                                                                                    
display=:abc    :                                                                                                                   

LENGTHステートメントと変数「string」の作成の順序を入れ替えると、「string」の長さが7に設定され、空白が追加されて余分な部分が埋められていることが分かります。

文字列からの削除#

このセクションでは、様々なCOMPRESS関数を使って、文字列から文字を削除する方法を検討します。

#

この例では、複数のブランクを1つのブランクに変換する方法を示します。名前と住所がファイルに入力されていて、データ入力担当者がある箇所で名字と名前の間や住所欄に余分な空白を入れてしまった場合を考えてみましょう。すべての名前と住所を単一のブランクで保存したい場合は、次のようにします。

data multiple;
  input #1 @1  name    $20.
        #2 @1  address $30.
        #3 @1  city    $15.
           @20 state    $2.
           @25 zip      $5.;
  name = compbl(name);
  address = compbl(address);
  city = compbl(city);
datalines;
Ron Cody
89 Lazy Brook Road
Flemington         NJ    08822
Bill     Brown
28   Cathy   Street
North   City       NY    11518
;
run;

proc print data = multiple;
run;
SAS 出力

SAS システム

OBS name address city state zip
1 Ron Cody 89 Lazy Brook Road Flemington NJ 0882
2 Bill Brown 28 Cathy Street North City NY 1151

compbl関数は、連続するブランクを1つのブランクにします。出力を見ると、2番目のaddressの28とCathyの間、Streetの前などの余分な空白が削除されていることが分かります。

より一般的な問題として、文字列から選択した文字を削除したい場合があります。例えば、文字値として格納された電話番号からブランク、括弧、ハイフンを除去したい場合を考えてみましょう。ここでCOMPRESS関数が役立ちます!COMPRESS関数は、指定した任意の文字を文字変数から削除することができます。

#

次のプログラムでは、COMPRESS関数を2回使用しています。1回目はブランクを削除し、2回目はブランクと「()」、「-」を削除しています。

data phone;
  input phone $ 1-15;
  phone1 = compress(phone);
  phone2 = compress(phone,'(-) ');
datalines;
(908)235-4490
(201) 555-77 99
;
run;

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

The phone data set

phone phone1 phone2
(908)235-4490 (908)235-4490 9082354490
(201) 555-77 99 (201)555-7799 2015557799

変数「PHONE1」はブランクのみが削除されています。COMPRESS関数に2番目の引数がないので、COMPRESS関数はブランクのみを削除します。「PHONE2」の場合、COMPRESS関数の2番目の引数には、削除する文字のリスト(左括弧、ブランク、右括弧、ハイフン)が単一引用符または二重引用符で囲まれています。削除する文字のリストを指定する場合は、ブランクを明示的にリストに含めない限り、ブランクは削除対象に含まれないことに注意してください。

文字データの検証#

データ処理で一般的なタスクに、データの検証があります。例として、文字変数に特定の値のみが存在することを確認したい場合があります。

#

以下の例では、’A’、’B’、’C’、’D’、’E’のみが妥当なデータ値です。妥当でない値が存在するかどうかを簡単にテストする方法を示します。

data verify;
  input @1  id $3.
        @5  answer $5.;
  position = verify(answer,'abcde');
  datalines;
001 acbed
002 abxde
003 12cce
004 abc e
;
run;

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

The verify data set

id answer position
001 acbed 0
002 abxde 3
003 12cce 1
004 abc e 4

ここで有用なのはVERIFY関数ですが、やや複雑です。第1引数のすべての文字を検証し、検証用文字列 (第2引数) に含まれない値が見つかった場合、最初のその値の位置を返します。すべての文字が検証用文字列にある場合は、0 が返されます。上の出力を見ると動作がより明確になります。

VERIFY関数(およびその他の多くの文字関数)を使用する際の注意点は、末尾のブランクです。例えば、次のようなケースを見てみましょう。

data trailing;
  length string $ 10;
  string = 'abc';
  pos1 = verify(string,'abcde');
  pos2 = verify(trim(string),'abcde');
run;

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

The trailing data set

string pos1 pos2
abc 4 0

LENGTH ステートメントにより、SASは「string」に 7個の末尾のブランクを追加して長さ10にするため、「POS1」の値は 4 (最初の末尾ブランクの位置) となります。末尾のブランク問題を回避する1つの方法は、verify関数を使用する前にTRIM関数で末尾ブランクを削除することです。「POS2」では最初にTRIM関数を使用しているので末尾ブランクが削除されたため、無効な文字はなくVERIFYは0を返します。ただし、TRIMは先頭と末尾のブランクしか削除しないため、ブランクが文字列の中央にある場合は機能しません。その場合は検証用文字列にブランクを追加するか、compress関数を使ってすべてのブランクを削除する必要があります。

部分文字列の抽出#

部分文字列とは、文字列の一部のことです(実際には文字列と同じ長さの場合もありますが、あまり意味がありません)。SASではsubstr関数を使って部分文字列を抽出できます。

#

この例では、IDコードの最初の文字が州の略称になっており、さらに7~9文字目が数値コードになっているとします。新しい2つの変数を作成したいとしましょう。1つは2文字は州コードで、もう1つは7、8、9桁目の3桁の数字から構成される数値変数です。

data pieces_parts;
  input id $ 1-9;
  length state $ 2;
  state = substr(id,1,2);
  num = input(substr(id,7,3),3.);
datalines;
NYXXXX123
NJ1234567
;
run;

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

The pieces_parts data set

id state num
NYXXXX123 NY 123
NJ1234567 NJ 567

州コードの作成は簡単です。substr関数を使用します。第1引数は、部分文字列を抽出したい変数、第2引数は部分文字列の開始位置、最後の引数は部分文字列の長さ(終了位置ではないことに注意)です。また、「STATE」の長さを2バイトに設定するためにLENGTHステートメントを使用していることにも注目してください。LENGTHステートメントがないと、「STATE」の長さはIDの長さと同じになります。なぜでしょうか?文字変数の長さはコンパイル時に設定されることを覚えていますか。開始位置と長さのパラメータはここでは定数ですが、計算されるか、外部ファイルから読み込まれる場合もあります。LENGTHステートメントがない場合、どのような動作になるでしょうか? 長さnの文字列から抽出できる最長の部分文字列の長さは何でしょうか? 答えはnで、部分文字列の長さのデフォルト値として使用されます。

3桁の番号コードを抽出するのはより複雑です。最初にsubstr関数を使って3桁の数字(数字の文字表現)を取り出します。しかし、substr関数の結果は常に文字値になります。文字値を数値に変換するには、input関数を使用します。input関数は、第1引数を第2引数のインフォーマットに従ってファイルから読み込まれたかのように読み込みます。したがって、最初のオブザベーションの場合、substr関数は文字列’123’を返し、input関数はこれを数値123に変換します。ちなみに、第2引数としてより長いinformatを使用しても問題ありません。例えば、次のように入力ステートメントを書くことができます。

input(substr(id,7,3),8.);

これは問題なく機能するので、文字列の長さをあらかじめ知らない場合に役立ちます。

substr関数のやや隠れた便利な使い方をご紹介したいと思います。等号の左側にsubstr関数を置くことで(古いマニュアルでは疑似substr関数と呼ばれていたと思います)、文字列の特定の位置に文字を配置することができます。

#

収縮期血圧(SBP)と拡張期血圧(DBP)がデータセットに入力されているとします。これらの値を出力する際に、高い値にアスタリスクを付けたいとしましょう。 次のプログラムでは、等号の左側のsubstr関数を使って実行しています。

data pressure;
  input sbp dbp @@;
  length sbp_chk dbp_chk $ 4;
  sbp_chk = put(sbp,3.);
  dbp_chk = put(dbp,3.);
  if sbp > 160 then substr(sbp_chk,4,1) = '*';
  if dbp >  90 then substr(dbp_chk,4,1) = '*';
datalines;
120 80 180 92 200 110
;
run;

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

The pressure data set

sbp dbp sbp_chk dbp_chk
120 80 120 80
180 92 180* 92*
200 110 200* 110*

まず、「SBP_CHK」と「DBP_CHK」のLengthを4(値用に3文字と、アスタリスクの可能性がある1文字)に設定する必要があります。次に、put関数を使って数値から文字への変換を行います。put関数は、ある意味でinput関数と似ています。第1引数の値を等号の左側の変数に「出力」するわけですが、第2引数のフォーマットに従って行います。「出力」とは実際には等号の左側の変数に値を割り当てることを意味します。 出力から確認できますが、substr関数は、「SBP」の値が160を超えるか「DBP」の値が90を超える場合に、4文字目にアスタリスクを割り当てます。

SCAN関数による文字列の解析(パース)#

文字列の解析(パース)とは、ある規則に従ってそれを分割することを意味します。文字列を区切る文字がわかっている場合は、scan関数を使って解析することができます。

#

次の例では、5つの別々の文字値が1行に空白、コンマ、セミコロン、ピリオド、感嘆符のいずれかで区切られて入力されています。これらの5つの値を抽出し、5つの文字変数に割り当てたいとします。scan関数がを使わないと難しいですが、使えば簡単です。

data parse;
  input long_str $ 1-80;
  array pieces[5] $ 10 piece1-piece5;
  do i = 1 to 5;
    pieces[i] = scan(long_str,i,',.! ');
  end;
  drop long_str i;
datalines;
this line,contains!five.words
abcdefghijkl xxx yyy
;
run;

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

The parse data set

piece1 piece2 piece3 piece4 piece5
this line contains five words
abcdefghij xxx yyy    

scan関数の構文は次のようになります。

SCAN(char_var,n,'list-of-delimiters');

scan関数は「char_var」のn番目の”単語”(2つの区切り文字の間にあるもの)を返します。「char_var」にn個未満の単語しかない場合、scan関数はブランクを返します。nが負の場合は、右から左へのスキャンが行われます。nが文字列内の単語の数を超える場合は、欠損値が返されます。
scan関数をDOループに入れることで、文字列からn番目の単語を取り出すことができます。SASのループと配列はまだ説明していませんが、Rなど他の言語でのループと配列の一般的な構文から理解できる人もいるかもしれません。

#

ここではscan関数を使って、ミドルネームやイニシャルを含む可能性のある姓名から姓を抽出する例を示します。この例では、姓でアルファベット順にリストを作成したいとします。まずはプログラムを見てもらい、次に説明します。

data first_last;
  input @1  name  $20.
        @21 phone $13.;
  *** The next statement extracts the last name from name;
  last_name = scan(name,-1,' ');    /* scans from the right */
datalines;
Jeff W. Snoker        (908)782-4382
Raymond Albert        (732)235-4444
Alred Edward Newman   (800)123-4321
Steven J. Foster      (201)567-9876
Jose Romerez          (516)593-2377
;
run;

title 'Names and Phone Numbers in Alphabetical Order (by Last Name)';
proc report data = first_last nowindows;    
  columns name phone last_name;
  define last_name / order noprint width = 20;
  define name      / display 'Name' left width = 20;
  define phone     / display 'Phone Number' width = 13 format=$13.;
run;
SAS 出力

Names and Phone Numbers in Alphabetical Order (by Last Name)

Name Phone Number
Raymond Albert (732)235-44
Steven J. Foster (201)567-98
Alred Edward Newman (800)123-43
Jose Romerez (516)593-23
Jeff W. Snoker (908)782-43

姓を抽出するのは簡単で、scan関数の第2引数に-1を指定するだけです。負の値を指定すると、右から左へのスキャンが行われることを思い出してください。REPORTプロシージャの出力が上に示されています。

REPORTプロシージャは単に表を作成しています。COLUMNSステートメントで表に使用する変数を定義し、DEFINEステートメントでその使い方を記述します。例えば、オプションorderとnoprintが「last_name」に使用されているので、姓を使って行を順序付けますが、列自体は出力表に表示されません。

文字列内のある文字列の位置を特定する#

INDEX関数とINDEXC関数の2つの似た関数を使うと、文字列内の別の文字列の位置を特定できます。例えば、’ABCDEFG’という文字列があり、文字列’DEF’の位置(開始位置4)を知りたい場合は、次のINDEX関数を使用できます。

INDEX('ABCDEFG','DEF');

第1引数は検索対象の文字列、第2引数は検索する文字列です。この関数は4を返します。つまり、文字列’DEF’の開始位置です。いくつかの文字のいずれかの開始位置を知りたい場合は、indexc関数を使用できます。例えば、文字列’ABCDEFG’内の文字’G’、’C’、’B’、’F’のいずれかの開始位置を知りたい場合は、次のようにコーディングします。

INDEXC('ABCDEFG','GCBF');

または

INDEXC('ABCDEFG','G','C','B','F');  

この関数は2を返します。つまり、’B’の位置で、第1引数で最初に見つかった文字の位置です。

#

以下の短いプログラムはこれらの2つの関数を動作を示すものです。文字列を見つけられなかった場合、両方の関数は0を返します。

data locate;
  input string $ 1-10;
  first   = index(string,'xyz');
  first_c = indexc(string,'x','y','z');
datalines;
abcxyz1234
1234567890
abcx1y2z39
abczzzxyz3
;
run;

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

The locate data set

string first first_c
abcxyz1234 4 4
1234567890 0 0
abcx1y2z39 0 4
abczzzxyz3 7 4

大文字・小文字の変換と先頭のみ大文字化#

UPCASE関数とLOWCASE関数は、その名前から推測できるとおり、それぞれ文字列を大文字と小文字に変換します。これらの2つの関数は、特にデータ入力担当者が注意を欠いて同じ変数に大文字と小文字が混在して入力された場合に便利です。すべての文字変数を配列に入れると一括してUPCASE(またはLOWCASE)処理することができます。

#

次の例ではupcase関数を使って、すべての文字変数を大文字に変換しています。

data up_down;
  length a b c d e $ 1;
  input a b c d e x y;
  datalines;
M f P p D 1 2
m f m F M 3 4
;
run;

data upper;
  set up_down;
  array all_c[*] _character_;
  do i = 1 to dim(all_c);
    all_c[i] = upcase(all_c[i]);
  end;
  drop i;
run;

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

The upper data set

a b c d e x y
M F P P D 1 2
M F M F M 3 4

このプログラムでは、_CHARACTER_キーワードを使って配列作成時に文字変数を個別にリストアップするのではなく、すべての文字変数を選択しています。プログラムを実行すると、「A」、「B」、「C」、「D」、「E」のすべての値が大文字に変換されます。UPCASE関数の代わりにLOWCASE関数を使えば、すべての文字値が小文字になります。

繰り返しになりますが、配列とdoループの説明はまだこれからですので、完全には理解できないかもしれません。これらについては後ほどで詳しく取り上げます。

SAS V9で追加された便利な新しい関数にPROPCASEがあります。この関数は、各単語の最初の文字を大文字にします。

#

次の例ではPROPCASE関数を使って、文字変数に格納されたテキストを各単語の最初の文字が大文字で、それ以外は小文字に変換しています。

data proper;
  input name $40.;
  propname = propcase(name);
  datalines;
rOn coDY
the tall and the short
the "%$#@!" escape
;
run;

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

The proper data set

name propname
rOn coDY Ron Cody
the tall and the short The Tall And The Short
the "%$#@!" escape The "%$#@!" Escape

文字列内の単語の置換#

TRANWRD(translate word)関数を使えば、文字列変数内で検索と置換を行うことができます。TRANWRD関数の構文は次のとおりです。

TRANWRD(char_var,'find_str','replace_str');

ここで、

  • char_varは検索対象の文字列

  • find_strは検索する文字列

  • replace_strはfind_strが見つかった場合に置き換える文字列

つまり、この関数はfind_strの出現箇所すべてをreplace_strに置き換えます。

#

次の例では、住所内の単語’Street’、’Avenue’、’Road’をそれぞれ’St.’、’Ave.’、’Rd.’に置き換えて標準化したいとしています。プログラムを確認してみてください。

data convert;
  input @1 address $20.;
  *** Convert Street, Avenue, and Road 
      to their abbreviations;
  address = tranwrd(address,'Street','St.');
  address = tranwrd(address,'Avenue','Ave.');
  address = tranwrd(address,'Road','Rd.');
  datalines;
89 Lazy Brook Road
123 River Rd.
12 Main Street
;
run;

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

The convert data set

OBS address
1 89 Lazy Brook Rd.
2 123 River Rd.
3 12 Main St.

TRANWRD関数は、nullではなく別の文字列で置換する必要があります。replace_strがnull・空文字列’’の場合、TRANWRD関数は代わりに空白’ ‘を使用します。空文字列で置換したい場合は、trimn(‘’)を置換文字列としてTRANSTRN関数を使う必要があります。

title "Comparison of TRANSWRD and TRANSTRN with " ;
data _null_;
  file print;
  string1='*' || tranwrd('abcxabc', 'abc', '') || '*';
  put string1=;
  string2='*' || transtrn('abcxabc', 'abc', '') || '*';
  put string2=;
  string3='*' || transtrn('abcxabc', 'abc', trimn('')) || '*';
  put string3=;
run;
SAS 出力

Comparison of TRANSWRD and TRANSTRN with

string1=* x *                                                                                                                       
string2=* x *                                                                                                                       
string3=*x*                                                                                                                         

ファジー比較: SPEDIS関数#

SPEDIS関数は、2つの文字列間の「スペリング距離」を測定します。2つの文字列が同一の場合、この関数は0を返します。スペリングエラーのカテゴリごとに、「ペナルティ」ポイントが割り当てられます。例えば、2つの文字列の最初の文字が異なる場合、比較的大きなペナルティがあります。2文字が入れ替わっている場合、ペナルティポイント数は小さくなります。この関数はスペリングに違いがあるファイル同士のファジーな結合に非常に役立ちます。数字だけで構成される文字データ(社会保障番号など)にも使用できます。この関数の構文は次のとおりです。

SPEDIS(string1,string2);

#

次のプログラムでは、いくつかの文字列間の距離を示しています。

data compare;
  length string1 string2 $ 15;
  input string1 string2;
  points = spedis(string1, string2);
  datalines;
same same
same sam
firstletter xirstletter
lastletter lastlettex
receipt reciept
;
run;

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

The compare data set

string1 string2 points
same same 0
same sam 8
firstletter xirstletter 18
lastletter lastlettex 10
receipt reciept 7

ANY関数#

SAS 9の「ANY」関数を使うと、数字、英数字、アルファベット、空白、句読点などの文字クラスの位置を特定できます。関数は次のとおりです。

  • anyalnum - 任意の英数字(文字または数字)を見つける

  • anyalpha - 任意の文字を見つける

  • anydigit - 任意の数字を見つける

  • anypunct - 任意の句読点を見つける

  • anyspace - 任意の空白を見つける

これらの関数は、対象の文字クラスの文字が最初に現れる位置を返します。該当する文字が見つからない場合は0を返します。

#

次のプログラムでは、ANYALPHA関数とANYDIGIT関数の例を示しています。

data find_alpha_digit;
  input string $20.;
  first_alpha = anyalpha(string);
  first_digit = anydigit(string);
  datalines;
no digits here
the 3 and 4
123 456 789
;
run;

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

The find_alpha_digit data set

string first_alpha first_digit
no digits here 1 0
the 3 and 4 1 5
123 456 789 0 1

NOT関数#

「NOT」関数は、「ANY」関数と同様に動作しますが、指定された文字クラスに該当しない最初の文字の位置を返します。

関数は次のとおりです。

  • notalnum

  • notalpha

  • notdigit

  • notpunct

  • notspace

これらの関数は、適切なクラスに合致しない最初の文字の位置を返します。該当しない文字が見つからない場合(指定と一致する文字しかない場合)は0を返します。

#

次のプログラムでは、NOTALPHA関数とNOTDIGIT関数を示しています。

data data_cleaning;
  input string $20.;
  only_alpha = notalpha(trim(string));
  only_digit = notdigit(trim(string));
  datalines;
abcdefg
1234567
abc123
1234abcd
;
run;

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

The data_cleaning data set

string only_alpha only_digit
abcdefg 0 1
1234567 1 0
abc123 4 1
1234abcd 1 5

上のプログラムでTRIM関数を使用している理由に注目してください。それがなければ、NOTALPHAとNOTDIGITはともに最初のブランクの位置を返します。データセットの名前がDATA_CLEANINGであることから分かるように、文字列が単一の文字クラスのみで構成されているかどうかを簡単に判断する方法です。

連結関数: CAT、CATS、CATX#

||または!!の連結演算子を使って2つの文字列を連結することはできますが、SAS 9で新しく追加された関数およびコールルーチンのCAT、CATS、CATXを代わりに使うこともできます。CAT関数は||や!!と同様に文字列を連結するだけですが、他の新しい関数の利点は、先頭と末尾のブランクを自動的に取り除いたり、区切り文字を挿入できることです。ここではCATSとCATXの2つの連結関数のみを示します。CATS関数は、2つ以上の文字列を連結する前に先頭と末尾のブランクを削除します。CATX関数はCATSと同様に動作しますが、文字列間に挿入する区切り文字を指定できます。これらの2つの関数の構文は次のとおりです。

CATS(string1,string2,);

CATX(separator,string1,string2,);  

#

次のプログラムでは、異なる連結関数の違いを示しています。

data join_up;
  file print;
  length cats $ 6 catx $ 17;
  string1 = 'ABC   ';
  string2 = '   XYZ   ';
  string3 = '12345';
  cat_op = string1 || string2;
  cat  = cat(string1, string2);
  cats = cats(string1, string2);
  catx = catx('***', string1, string2, string3);
  put string1=;
  put string2=;
  put string3=;
  put cat_op=;
  put cat=;
  put cats=;
  put catx=;
run;

title 'The join_up data set';
proc print data = join_up noobs;
  var string1 string2 string3 cat_op cat cats catx;
run;
SAS 出力

The data_cleaning data set

string1=ABC                                                                                                                         
string2=XYZ                                                                                                                         
string3=12345                                                                                                                       
cat_op=ABC      XYZ                                                                                                                 
cat=ABC      XYZ                                                                                                                    
cats=ABCXYZ                                                                                                                         
catx=ABC***XYZ***12345                                                                                                              

The join_up data set

string1 string2 string3 cat_op cat cats catx
ABC XYZ 12345 ABC XYZ ABC XYZ ABCXYZ ABC***XYZ***12345

LENGTHステートメントがない場合、CATSとCATXの2つの変数の長さはCAT関数のデフォルト長さ200になります。連結演算子を使う場合、デフォルトの長さは連結される引数の長さの合計になることに注意してください。

LENGTH、LENGTHN、LENGTHC関数#

前に文字変数の記憶領域の長さについて説明しました。このセクションのLENGTH関連の関数にはそれぞれ異なる役割があります。LENGTHC関数(V9)は文字変数の記憶領域の長さを返します。
他の2つのLENGTHとLENGTHNはいずれも末尾のブランクを除いた文字変数の長さを返します。LENGTHとLENGTHNの違いは、LENGTHNがヌル文字列の場合0を返すのに対し、LENGTHは1を返すことです。

#

次のプログラムでは、LENGTH、LENGTHN、LENGTHC関数を示しています。

data how_long;
  one = 'ABC   ';
  two = ' ';   /* character missing value */
  three = 'ABC   XYZ';
  length_one = length(one);
  lengthn_one = lengthn(one);
  lengthc_one = lengthc(one);
  length_two = length(two);
  lengthn_two = lengthn(two);
  lengthc_two = lengthc(two);
  length_three = length(three);
  lengthn_three = lengthn(three);
  lengthc_three = lengthc(three);
run;

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

The how_long data set

one two three length_one lengthn_one lengthc_one length_two lengthn_two lengthc_two length_three lengthn_three lengthc_three
ABC   ABC XYZ 3 3 6 1 0 1 9 9 9

COMPARE関数による2つの文字列の比較#

2つの文字列を比較するのに関数が必要だろうかと疑問に思うかもしれません。等号を使えばいいのではないでしょうか。COMPARE関数を使えば、より柔軟に文字列を比較できます。構文は次のとおりです。

COMPARE(string1, string2 <,'modifiers'>)

次のリストから1つ以上の修飾子を選び、単一引用符または二重引用符で囲んで指定できます。

  • i または I - 大文字小文字を区別しない

  • l または L - 先頭のブランクを削除する

  • n または N - n-リテラルのクォートを削除し、大文字小文字を区別しない。n-リテラルとは、クォートで囲まれた文字列の後にnが付いたもので、SASの有効な名前でない場合に便利です。

  • : (コロン) - 長い方の文字列を短い方の長さに切り詰める。デフォルトでは、比較前に短い方の文字列にブランクを詰めます。

例えば、大文字小文字を区別せず、先頭のブランクを削除したい場合は、次のようにコーディングできます。

yes_or_no = compare(string1,string2,'il');

COMPARE関数は、2つの文字列が異なる最初の位置を返します。string1がstring2よりも辞書順で前にある場合は負の数を返します。2つの文字列が同じ場合は0を返します。

data compare_str;
  string1 = "   Robert Parker";
  string2 = "ROBERT PARKER";
  compare1 = compare(string1, string2);
  compare2 = compare(string1, string2, 'i');
  compare3 = compare(string1, string2, 'l');
  compare4 = compare(string1, string2, 'il');
run;

title "Compare Strings Dataset";
proc print data = compare_str;    
run;
SAS 出力

Compare Strings Dataset

OBS string1 string2 compare1 compare2 compare3 compare4
1 Robert Parker ROBERT PARKER -1 -1 2 0

先頭/末尾のブランクの削除#

STRIP関数は先頭と末尾のブランクを削除します。TRIM関数は末尾のブランクを、LEFT関数は先頭のブランクを削除します。

title "Whitespace Example";

data _null_;
  file print;
  string = "    ROBERT    ";
  strip = '*' || strip(string) || '*';
  trim = '*' || trim(string) || '*';
  left = '*' || left(string) || '*';
  put strip;
  put trim;
  put left;
run;
SAS 出力

Whitespace Example

*ROBERT*                                                                                                                            
*    ROBERT*                                                                                                                        
*ROBERT        *                                                                                                                    

文字や部分文字列の出現回数のカウント: COUNTとCOUNTC#

SAS 9の新しい関数にCOUNTとCOUNTCがあります。COUNT関数は特定の部分文字列が文字列内に出現する回数をカウントするのに使用します。COUNTC関数は、1つ以上の文字が出現する回数をカウントします。’i’修飾子を使うと大文字小文字を区別せず、’t’修飾子を使うと文字列と検索文字列の末尾のブランクを無視することもできます。

これら2つの関数の構文は次のとおりです。

count(string,find_string,<'modifiers'>)

countc(string,find_string,<'modifiers'>)

#

次のプログラムでは、COUNTとCOUNTC関数の例を示しています。

data Dracula;
  input string $20.;
  count_a_or_b = count(string,'ab');
  countc_a_or_b = countc(string,'ab');
  count_abc = count(string,'abc');
  countc_abc = countc(string,'abc');
  case_a = countc(string,'a','i');
  datalines;
xxabcxabcxxbbbb
cbacba
aaAA
;
run;

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

The Dracula data set

string count_a_or_b countc_a_or_b count_abc countc_abc case_a
xxabcxabcxxbbbb 2 8 2 10 2
cbacba 0 4 0 6 2
aaAA 0 2 0 2 4

PRX関数: 正規表現#

SASには、固定文字列ではなく正規表現に基づいた文字列マッチングを行うPRX関数の系列もあります。正規表現とは何でしょうか?

  • 特定の文字列を検索する方法

  • 複雑なものから簡単なものまで対応可能

  • 非常に便利 - 「Find」の強化版のようなもの

  • http://www.regular-expressions.info/reference.html

  • 1つのステートメントで多数の文字列をマッチングできます

  • . は任意の1文字にマッチ

  • * は直前の文字を0回以上繰り返しマッチ

  • ? は直前の部分が省略可能になる

  • ^ は先頭にマッチ ^a - “a”で始まる

  • $ は末尾にマッチ b$ - “b”で終わる

ここでは、正規表現を使用する次のSAS関数について説明します。

  • PRXPARSE - 正規表現(および置換文字列)を定義する

  • PRXMATCH - PRXPARSEで定義した正規表現で文字列を検索し、マッチするパターンが見つかった場合はその開始位置を、見つからなかった場合は0を返す

  • CALL PRXSUBSTR - PRXMATCHと似ていますが、PRXPARSEで定義した正規表現で文字列を検索し、マッチした場合はその開始位置と長さを返し、マッチしなかった場合は開始位置と長さが0になります。

  • PRXCHANGE - PRXPARSEで定義した正規表現を検索し、置換文字列に置き換えます。置換回数を指定するパラメータがあり、-1を指定すると見つかったマッチをすべて置換します。

データセットsalariesを使ってこれらの関数を説明しましょう。

filename salaries '/folders/myfolders/SAS_Notes/data/Baltimore_City_Employee_Salaries_FY2015.csv';

proc import datafile = salaries out = sal dbms = csv replace;
  getnames = yes;
run;

proc print data = sal (obs=5);
  var name;
run;
SAS 出力

The Dracula data set

OBS name
1 Aaron,Patricia G
2 Aaron,Petra L
3 Abaineh,Yohannes T
4 Abbene,Anthony M
5 Abbey,Emmanuel

#

次のプログラムでは、正規表現を使って’Rawlings’を含む名前をすべて検索する方法を示しています。

data sal_rawlings;
  set sal;
  re = prxparse('/Rawlings/i');
  if missing(re) then do;
    putlog 'ERROR: regex is malformed';
    stop;
  end;
  if prxmatch(re, name) = 0 then delete;
run;

proc print data = sal_rawlings;
  var name;
run;
SAS 出力

The Dracula data set

OBS name
1 Rawlings,Kellye A
2 Rawlings,Paula M
3 Rawlings-Blake,Stephani

変数「re」にはPRXPARSE関数で定義された正規表現が格納されています。正規表現は2つの前向きスラッシュ ‘/regex/’ で囲む必要があることに注意してください。正規表現の定義 ‘/Rawlings/i’ の i は、大文字小文字を区別しないことを示しています。したがって、’Rawlings’ と ‘rawlings’ の両方にマッチします。次に、この正規表現をPRXMATCH関数に渡し、変数「name」を検索します。文字列のどこかにマッチがあれば、0でないインデックスが返されます。正規表現を間違えた場合のためにif文を使って、正規表現にミスがあればプログラムを停止するようにしています。

PRXSUBSTR関数はPRXMATCH関数と似ていますが、2つの変数「pos」と「len」を渡し、文字列のマッチ開始位置と長さを格納します。SUBSTRの後に続く処理のために、この情報が必要になる可能性があります。

#

次のプログラムでは、PRXCHANGEを使ってデータセットsalaryの「name」の “a” を “j” に置き換える方法を示しています。

data sal_a1;
  set sal;
  re = prxparse('s/a/j/');
  if missing(re) then do;
    putlog 'ERROR: regex is malformed';
    stop;
  end;
  name2 = prxchange(re, 1, name);
run;

proc print data = sal_a1(obs=5);
  var name name2;
run;

data sal_a2;
  set sal;
  re = prxparse('s/a/j/');
  if missing(re) then do;
    putlog 'ERROR: regex is malformed';
    stop;
  end;
  name2 = prxchange(re, -1, name);
run;

proc print data = sal_a2(obs=5);
  var name name2;
run;
SAS 出力

The Dracula data set

OBS name name2
1 Aaron,Patricia G Ajron,Patricia G
2 Aaron,Petra L Ajron,Petra L
3 Abaineh,Yohannes T Abjineh,Yohannes T
4 Abbene,Anthony M Abbene,Anthony M
5 Abbey,Emmanuel Abbey,Emmjnuel

The Dracula data set

OBS name name2
1 Aaron,Patricia G Ajron,Pjtricij G
2 Aaron,Petra L Ajron,Petrj L
3 Abaineh,Yohannes T Abjineh,Yohjnnes T
4 Abbene,Anthony M Abbene,Anthony M
5 Abbey,Emmanuel Abbey,Emmjnuel

最初のコードでは置換回数に1を指定しているので、”a”と一致した最初の箇所のみが”j”に置き換えられています。一方、2番目のコードでは-1を指定しているので、”a”のすべての出現箇所が”j”に置き換えられています。

演習#

  1. PROC IMPORTを使ってBaltimore cityの給与データセット Baltimore_City_Employee_Salaries_FY2015.csvを読み込んでください。

  2. 一時データセット health_sal を作成し、そこには (大文字小文字を区別せずに) 職名「JobTitle」に “fire” が含まれるもののみを含めでください。

  3. 一時データセット trans を作成し、そこには (大文字小文字を区別して) “TRANS” が含まれるもののみを含めてください。

  4. Baltimoreの給与データで、名前に “abra” が含まれる人の職業は何ですか? 大文字小文字は区別しないでください。

  5. 警察局、消防局、保安官事務所のみを含む給与データセットのサブセットを作成します。変数「Agency」を使って(大文字小文字を区別した)文字列のマッチングを行ってください。このデータセットemerには何人の従業員が含まれていますか?

  6. データセットemer に変数「dept」を作成し、変数「Agency」から ‘ment’ または ‘ice’ の手前の部分を抽出してください。例えば、ment または ice までの文字を抜き出し (正規表現ではカッコで囲むとグループ化できます)、その後の文字は削除します。PROC FREQを使って変数「dept」の度数表を作成します。目的の部分文字列を抽出するには、次のようにします。

re = prxparse('/.*(ment|ice)/'); 
call prxsubstr(re, Agency, pos, len);
dept = substr(Agency, pos, len);