Python pandas：正規表現を使いDataFrameを検索

pandasにはデータを検索する機能があります。
何桁かのIDのうち最初の1文字目によってグループわけされている場合など文字列の一部を検索条件にしたいときの方法で躓いたのでメモ。
コードの実行には[Jupyter](http://jupyter.org/)を利用しました。
**pipなどを利用してpandasをインストールしてください。**

#環境
Python3.5
pandas0.17
Jupyter notebook

#IDの仕様
1桁目:A~E
2桁目:A~E
3桁目:0~9
4桁目:0~9
5桁目:0~9
6桁目:0~9

例:AD3489
#準備：データの作成
##IDっぽいデータを大量生成
あまり賢いコードではありませんがIDっぽい文字列と名前を200個生成しました。

```lang:python
import random
id = []
text=['A','B','C','D','E']
name = []
namelistF = ['Thom','Walter','Alexis','Allan','Hubert','Amy','Anastasia','Lorna','Barbara']
namelistL=['Abernethy','Ackroyd','Collins','Guises','Haworth','Hayden','Kingsley','Leyland','McLennan']

for i in range(200):
    id.append(text[random.randint(0,len(text))-1] + text[random.randint(0,len(text))-1] + str(random.randint(0,len(text))) + str(random.randint(0,9)) + str(random.randint(0,9) )+ str(random.randint(0,9)))
    name.append(namelistF[random.randint(0,len(namelist)-1)] + ' ' + namelistL[random.randint(0,len(namelist)-1)])

```
このように200個のデータが生成されました。

```lang:得られたIDデータ
['AD4865', 'DE5898', 'ED1278', 'EA4809', 'CA1251', 'EC2877', 'EC0579', 'EC3476', 'EE1624', 'AD3489', 'AC1209', 'EE3963', 'BC4656', 'BE3524', 'BE1321', 'CA4381', 'BB4813', 'AC4885', 'EA2290', 'DE3520', 'EC3145', 'CE5665', 'BE0071', 'EA0096', 'DC3792', 'CC1623', 'EE3194', 'EE1599', 'EE3398', 'EE0891', 'AB2412', 'AB3631', 'EC3118', 'DE2280', 'DA2018', 'CE4929', 'DA0157', 'BE3865', 'EE5122', 'ED5983', 'CD3569', 'BA3981', 'DE3913', 'BC3383', 'EE1456', 'AB0498', 'DA3937', 'AE2070', 'CE5804', 'DE2952', 'EB5014', 'AB0785', 'EC5061', 'AE1615', 'AC5219', 'EB3453', 'BB4372', 'EC4597', 'EE3370', 'CC1251', 'AA2228', 'CE3570', 'AC2734', 'DC0885', 'AC1142', 'DE3956', 'EE4113', 'EA4665', 'EB5010', 'CE1369', 'EC4333', 'AC1651', 'CE2802', 'BE3500', 'BB5521', 'DC5230', 'BA3568', 'AD4049', 'EE0666', 'CB1121', 'AA1281', 'CC1060', 'AD3520', 'BC3767', 'CD1125', 'DA0091', 'DA1978', 'BD2344', 'EE1345', 'AE2442', 'BC2306', 'CD1526', 'DD0514', 'EE0192', 'AE0517', 'ED3991', 'EE4694', 'AA1882', 'AC3296', 'ED1368', 'BD2772', 'BC1574', 'AD4402', 'DA2235', 'EC0168', 'CD0686', 'AB1050', 'BB2185', 'EA0274', 'DE4259', 'CB1594', 'ED3869', 'EA4427', 'CE0328', 'DD1004', 'BC4501', 'EE4727', 'CC5595', 'ED2344', 'ED4679', 'EE4167', 'AD5802', 'CB4401', 'EB2113', 'ED1493', 'CD3285', 'AA2717', 'AA1299', 'DA0957', 'CB5816', 'EE3520', 'DB2187', 'EA4323', 'DC3989', 'AA0663', 'DE5448', 'EE1418', 'EE2066', 'AA1373', 'EE1518', 'EE1782', 'CB2087', 'CE0266', 'AC4397', 'EC1308', 'EE2743', 'AD3849', 'CC3094', 'EE2725', 'BD3893', 'EC4543', 'DE2661', 'BE2766', 'DC2414', 'ED1347', 'CB2313', 'AD0026', 'ED1356', 'BE3893', 'EE3442', 'EC1069', 'EE2767', 'CA1410', 'EC5553', 'EA2432', 'AB5636', 'ED1639', 'DB3176', 'EE5932', 'EC1606', 'CC4372', 'EA2838', 'DE2964', 'DD3804', 'BC5171', 'BD4210', 'EE3239', 'AA3637', 'DA4393', 'CB3981', 'CD4627', 'EE0674', 'BE3231', 'BE0014', 'EE4350', 'BD1956', 'CE3526', 'EB2453', 'AB3957', 'EE5343', 'AD1178', 'CC4550', 'DD0465', 'CE2580', 'CD1344', 'ED0247', 'ED4614', 'DE0976', 'CE5883', 'AA2922']
```
```lang:得られた名前データ
['Lorna Leyland', 'Anastasia Guises', 'Hubert Guises', 'Allan Kingsley', 'Lorna Kingsley', 'Amy Hayden', 'Hubert Abernethy', 'Anastasia Guises', 'Thom Hayden', 'Walter Kingsley', 'Allan Ackroyd', 'Thom Kingsley', 'Anastasia Collins', 'Thom Ackroyd', 'Thom Collins', 'Lorna Haworth', 'Anastasia Hayden', 'Thom Leyland', 'Alexis Leyland', 'Anastasia McLennan', 'Walter Haworth', 'Amy Collins', 'Barbara Leyland', 'Anastasia Ackroyd', 'Amy Kingsley', 'Barbara Haworth', 'Allan Ackroyd', 'Barbara Guises', 'Thom Haworth', 'Thom Ackroyd', 'Allan Guises', 'Barbara McLennan', 'Barbara Guises', 'Hubert Abernethy', 'Anastasia Haworth', 'Walter Collins', 'Allan Abernethy', 'Hubert Ackroyd', 'Thom McLennan', 'Thom Abernethy', 'Allan McLennan', 'Hubert Kingsley', 'Allan Leyland', 'Allan Haworth', 'Alexis Collins', 'Thom Ackroyd', 'Anastasia Haworth', 'Alexis McLennan', 'Amy Haworth', 'Walter Guises', 'Barbara Hayden', 'Lorna Collins', 'Lorna Abernethy', 'Alexis Abernethy', 'Anastasia Ackroyd', 'Allan Kingsley', 'Thom McLennan', 'Thom Hayden', 'Walter Hayden', 'Lorna Leyland', 'Alexis Leyland', 'Barbara Leyland', 'Barbara Collins', 'Amy Leyland', 'Lorna Hayden', 'Lorna Leyland', 'Walter Kingsley', 'Allan McLennan', 'Anastasia Haworth', 'Allan Guises', 'Anastasia Abernethy', 'Walter Kingsley', 'Amy Abernethy', 'Allan Guises', 'Lorna Ackroyd', 'Alexis Guises', 'Thom Abernethy', 'Lorna McLennan', 'Allan Abernethy', 'Barbara Hayden', 'Amy Collins', 'Allan Kingsley', 'Thom Haworth', 'Thom Kingsley', 'Alexis Leyland', 'Amy Hayden', 'Hubert Hayden', 'Thom Ackroyd', 'Walter Abernethy', 'Barbara Guises', 'Walter Collins', 'Hubert Abernethy', 'Allan Ackroyd', 'Barbara Kingsley', 'Anastasia Hayden', 'Lorna Abernethy', 'Thom Hayden', 'Thom Leyland', 'Amy Leyland', 'Thom Guises', 'Alexis Hayden', 'Walter Guises', 'Anastasia Leyland', 'Thom Ackroyd', 'Allan Collins', 'Barbara Collins', 'Allan Hayden', 'Thom Collins', 'Barbara McLennan', 'Lorna Haworth', 'Walter Hayden', 'Barbara Guises', 'Alexis Collins', 'Lorna Hayden', 'Barbara Haworth', 'Thom Guises', 'Hubert Guises', 'Anastasia Haworth', 'Thom Ackroyd', 'Hubert Haworth', 'Hubert Abernethy', 'Anastasia Leyland', 'Amy Leyland', 'Walter Guises', 'Thom McLennan', 'Hubert Collins', 'Barbara McLennan', 'Anastasia Guises', 'Amy Ackroyd', 'Barbara McLennan', 'Anastasia Guises', 'Walter Hayden', 'Amy Kingsley', 'Lorna Hayden', 'Walter Guises', 'Barbara Guises', 'Thom Kingsley', 'Anastasia Abernethy', 'Amy Kingsley', 'Walter Kingsley', 'Walter McLennan', 'Alexis Ackroyd', 'Lorna Kingsley', 'Amy Collins', 'Anastasia Haworth', 'Amy Haworth', 'Lorna Kingsley', 'Allan Guises', 'Anastasia Guises', 'Amy Haworth', 'Barbara Guises', 'Lorna Leyland', 'Allan Hayden', 'Alexis Leyland', 'Lorna Ackroyd', 'Alexis Guises', 'Walter Collins', 'Lorna Abernethy', 'Hubert Collins', 'Hubert McLennan', 'Alexis Collins', 'Lorna Leyland', 'Barbara Hayden', 'Lorna Kingsley', 'Barbara Haworth', 'Thom Leyland', 'Lorna Ackroyd', 'Walter Guises', 'Allan Haworth', 'Hubert McLennan', 'Walter Haworth', 'Lorna Ackroyd', 'Allan Hayden', 'Lorna Abernethy', 'Allan Abernethy', 'Allan Ackroyd', 'Lorna Haworth', 'Barbara Hayden', 'Thom Haworth', 'Alexis Hayden', 'Barbara Leyland', 'Alexis Ackroyd', 'Walter Abernethy', 'Anastasia Abernethy', 'Thom Kingsley', 'Hubert Haworth', 'Amy Hayden', 'Hubert Guises', 'Walter Hayden', 'Allan Guises', 'Allan Ackroyd', 'Hubert Abernethy', 'Amy McLennan', 'Allan Abernethy', 'Walter Kingsley', 'Allan Haworth', 'Hubert Hayden', 'Lorna McLennan', 'Lorna Collins', 'Lorna Haworth']
```
##ディクショナリの作成
このID配列と名前配列をdataというディクショナリ型にいれます。

```
data = {
'ID':
['AD4865', 'DE5898', 'ED1278', 'EA4809', 'CA1251', 'EC2877', 'EC0579', 'EC3476', 'EE1624', 'AD3489', 'AC1209', 'EE3963', 'BC4656', 'BE3524', 'BE1321', 'CA4381', 'BB4813', 'AC4885', 'EA2290', 'DE3520', 'EC3145', 'CE5665', 'BE0071', 'EA0096', 'DC3792', 'CC1623', 'EE3194', 'EE1599', 'EE3398', 'EE0891', 'AB2412', 'AB3631', 'EC3118', 'DE2280', 'DA2018', 'CE4929', 'DA0157', 'BE3865', 'EE5122', 'ED5983', 'CD3569', 'BA3981', 'DE3913', 'BC3383', 'EE1456', 'AB0498', 'DA3937', 'AE2070', 'CE5804', 'DE2952', 'EB5014', 'AB0785', 'EC5061', 'AE1615', 'AC5219', 'EB3453', 'BB4372', 'EC4597', 'EE3370', 'CC1251', 'AA2228', 'CE3570', 'AC2734', 'DC0885', 'AC1142', 'DE3956', 'EE4113', 'EA4665', 'EB5010', 'CE1369', 'EC4333', 'AC1651', 'CE2802', 'BE3500', 'BB5521', 'DC5230', 'BA3568', 'AD4049', 'EE0666', 'CB1121', 'AA1281', 'CC1060', 'AD3520', 'BC3767', 'CD1125', 'DA0091', 'DA1978', 'BD2344', 'EE1345', 'AE2442', 'BC2306', 'CD1526', 'DD0514', 'EE0192', 'AE0517', 'ED3991', 'EE4694', 'AA1882', 'AC3296', 'ED1368', 'BD2772', 'BC1574', 'AD4402', 'DA2235', 'EC0168', 'CD0686', 'AB1050', 'BB2185', 'EA0274', 'DE4259', 'CB1594', 'ED3869', 'EA4427', 'CE0328', 'DD1004', 'BC4501', 'EE4727', 'CC5595', 'ED2344', 'ED4679', 'EE4167', 'AD5802', 'CB4401', 'EB2113', 'ED1493', 'CD3285', 'AA2717', 'AA1299', 'DA0957', 'CB5816', 'EE3520', 'DB2187', 'EA4323', 'DC3989', 'AA0663', 'DE5448', 'EE1418', 'EE2066', 'AA1373', 'EE1518', 'EE1782', 'CB2087', 'CE0266', 'AC4397', 'EC1308', 'EE2743', 'AD3849', 'CC3094', 'EE2725', 'BD3893', 'EC4543', 'DE2661', 'BE2766', 'DC2414', 'ED1347', 'CB2313', 'AD0026', 'ED1356', 'BE3893', 'EE3442', 'EC1069', 'EE2767', 'CA1410', 'EC5553', 'EA2432', 'AB5636', 'ED1639', 'DB3176', 'EE5932', 'EC1606', 'CC4372', 'EA2838', 'DE2964', 'DD3804', 'BC5171', 'BD4210', 'EE3239', 'AA3637', 'DA4393', 'CB3981', 'CD4627', 'EE0674', 'BE3231', 'BE0014', 'EE4350', 'BD1956', 'CE3526', 'EB2453', 'AB3957', 'EE5343', 'AD1178', 'CC4550', 'DD0465', 'CE2580', 'CD1344', 'ED0247', 'ED4614', 'DE0976', 'CE5883', 'AA2922'],

'Name':
['Lorna Leyland', 'Anastasia Guises', 'Hubert Guises', 'Allan Kingsley', 'Lorna Kingsley', 'Amy Hayden', 'Hubert Abernethy', 'Anastasia Guises', 'Thom Hayden', 'Walter Kingsley', 'Allan Ackroyd', 'Thom Kingsley', 'Anastasia Collins', 'Thom Ackroyd', 'Thom Collins', 'Lorna Haworth', 'Anastasia Hayden', 'Thom Leyland', 'Alexis Leyland', 'Anastasia McLennan', 'Walter Haworth', 'Amy Collins', 'Barbara Leyland', 'Anastasia Ackroyd', 'Amy Kingsley', 'Barbara Haworth', 'Allan Ackroyd', 'Barbara Guises', 'Thom Haworth', 'Thom Ackroyd', 'Allan Guises', 'Barbara McLennan', 'Barbara Guises', 'Hubert Abernethy', 'Anastasia Haworth', 'Walter Collins', 'Allan Abernethy', 'Hubert Ackroyd', 'Thom McLennan', 'Thom Abernethy', 'Allan McLennan', 'Hubert Kingsley', 'Allan Leyland', 'Allan Haworth', 'Alexis Collins', 'Thom Ackroyd', 'Anastasia Haworth', 'Alexis McLennan', 'Amy Haworth', 'Walter Guises', 'Barbara Hayden', 'Lorna Collins', 'Lorna Abernethy', 'Alexis Abernethy', 'Anastasia Ackroyd', 'Allan Kingsley', 'Thom McLennan', 'Thom Hayden', 'Walter Hayden', 'Lorna Leyland', 'Alexis Leyland', 'Barbara Leyland', 'Barbara Collins', 'Amy Leyland', 'Lorna Hayden', 'Lorna Leyland', 'Walter Kingsley', 'Allan McLennan', 'Anastasia Haworth', 'Allan Guises', 'Anastasia Abernethy', 'Walter Kingsley', 'Amy Abernethy', 'Allan Guises', 'Lorna Ackroyd', 'Alexis Guises', 'Thom Abernethy', 'Lorna McLennan', 'Allan Abernethy', 'Barbara Hayden', 'Amy Collins', 'Allan Kingsley', 'Thom Haworth', 'Thom Kingsley', 'Alexis Leyland', 'Amy Hayden', 'Hubert Hayden', 'Thom Ackroyd', 'Walter Abernethy', 'Barbara Guises', 'Walter Collins', 'Hubert Abernethy', 'Allan Ackroyd', 'Barbara Kingsley', 'Anastasia Hayden', 'Lorna Abernethy', 'Thom Hayden', 'Thom Leyland', 'Amy Leyland', 'Thom Guises', 'Alexis Hayden', 'Walter Guises', 'Anastasia Leyland', 'Thom Ackroyd', 'Allan Collins', 'Barbara Collins', 'Allan Hayden', 'Thom Collins', 'Barbara McLennan', 'Lorna Haworth', 'Walter Hayden', 'Barbara Guises', 'Alexis Collins', 'Lorna Hayden', 'Barbara Haworth', 'Thom Guises', 'Hubert Guises', 'Anastasia Haworth', 'Thom Ackroyd', 'Hubert Haworth', 'Hubert Abernethy', 'Anastasia Leyland', 'Amy Leyland', 'Walter Guises', 'Thom McLennan', 'Hubert Collins', 'Barbara McLennan', 'Anastasia Guises', 'Amy Ackroyd', 'Barbara McLennan', 'Anastasia Guises', 'Walter Hayden', 'Amy Kingsley', 'Lorna Hayden', 'Walter Guises', 'Barbara Guises', 'Thom Kingsley', 'Anastasia Abernethy', 'Amy Kingsley', 'Walter Kingsley', 'Walter McLennan', 'Alexis Ackroyd', 'Lorna Kingsley', 'Amy Collins', 'Anastasia Haworth', 'Amy Haworth', 'Lorna Kingsley', 'Allan Guises', 'Anastasia Guises', 'Amy Haworth', 'Barbara Guises', 'Lorna Leyland', 'Allan Hayden', 'Alexis Leyland', 'Lorna Ackroyd', 'Alexis Guises', 'Walter Collins', 'Lorna Abernethy', 'Hubert Collins', 'Hubert McLennan', 'Alexis Collins', 'Lorna Leyland', 'Barbara Hayden', 'Lorna Kingsley', 'Barbara Haworth', 'Thom Leyland', 'Lorna Ackroyd', 'Walter Guises', 'Allan Haworth', 'Hubert McLennan', 'Walter Haworth', 'Lorna Ackroyd', 'Allan Hayden', 'Lorna Abernethy', 'Allan Abernethy', 'Allan Ackroyd', 'Lorna Haworth', 'Barbara Hayden', 'Thom Haworth', 'Alexis Hayden', 'Barbara Leyland', 'Alexis Ackroyd', 'Walter Abernethy', 'Anastasia Abernethy', 'Thom Kingsley', 'Hubert Haworth', 'Amy Hayden', 'Hubert Guises', 'Walter Hayden', 'Allan Guises', 'Allan Ackroyd', 'Hubert Abernethy', 'Amy McLennan', 'Allan Abernethy', 'Walter Kingsley', 'Allan Haworth', 'Hubert Hayden', 'Lorna McLennan', 'Lorna Collins', 'Lorna Haworth']
}
```

乱数を使ってデータを生成しているため、この記事と結果が変わってしまいますが以下のようにしても同じです。

```
data = {
'ID':id,
'Name':name
}
```

##DataFrameの作成
先ほど作成したdataからDataFrameを作成します。

```
import pandas ps pd
frame = pd.DataFrame(data)
```
このようにpandasでDataFrameが作成されました。
<img width="270" alt="スクリーンショット 2015-10-31 9.59.20.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/95987/8c9c0c91-7a2a-a52a-4d64-f65ea4990f85.jpeg">


#データの検索
正規表現を使ってpandasのデータを検索するには**str.contains()**を利用します。
「6桁のIDのうち1文字目がEではじまる場合」を調べたいときは
```
frame[frame['ID'].str.contains('E.....')]
```
とするとこのようにIDがEからはじまる行の一覧を取得できます。71件のデータがヒットしました。'E.....'の部分は正規表現です。「.」はなにか1文字分という意味です。'E.....'はEからはじまる6文字を探せという事になります。ためしに'E......'とするとEからはじまる7文字という意味になるのでヒットしません。

<img width="257" alt="スクリーンショット 2015-10-31 10.07.50.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/95987/18a6e059-c33c-59bb-0de7-e178bd7f79a8.jpeg">

3桁目が1のデータを探すには
```
frame[frame['ID'].str.contains('..1...')]
```
となります。

##複数条件での検索
正規表現の書き方はまだほかにもありますが、たとえば1桁目がAAまたはABの場合を検索したいときは
```
frame[frame['ID'].str.contains('(AA|AB)....')]
```
とします。この書き方をするとUserWarning: This pattern has match groups. To actually get the groups, use str.extract.という警告がでてきますが、まぁ問題はないです。

どちらでかいても結果は同じですが、pandasとしてはor条件でかかれた

```
frame[(frame['ID'].str.contains('AA....')) | (frame['ID'].str.contains('AB....')) ]
```
の文法のほうが正しいのだと思います。

pandasでand条件を利用する場合は

```
frame[(frame['ID'].str.contains('A.....')) & (frame['ID'].str.contains('...1..')) ]
```
のようにかきます。

andとorの書き方はpandasで躓くポイントな気がします。
**frame[(条件1) & (条件2) | (条件3)]**のようにかいていきます。

##2つのカラムの条件で検索
「IDの1桁目がAで名前がWalter」という条件で検索したい場合はand条件を利用して

```
frame[(frame['ID'].str.contains('A.....')) & (frame['Name'].str.contains('Walter.*'))]
```

とかきます。
<img width="213" alt="スクリーンショット 2015-10-31 10.31.25.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/95987/c710f407-bd01-918b-0b4e-54ab5488e6e7.jpeg">

'.\*'というのは「なんでもいいので文字列が続く」という意味です。'Walter.\*'とすると'Walter'で始まる文字列が全てヒットします。

先ほどの条件のなかで「Walterからはじまり最後はsで終わる」検索条件の場合は

```
frame[(frame['ID'].str.contains('A.....')) & (frame['Name'].str.contains('Walter.*s'))]
```
のようにすればいけそうです。

<img width="213" alt="スクリーンショット 2015-10-31 10.40.33.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/95987/6255c0d4-332d-2588-2800-aee6de2acbfa.jpeg">
しかし、実行結果をみるとWalter Kingsleyという結果がでてしまいました。どうやらKing「s」leyのsにヒットしてしまったようです。
sが最後の文字という指定をするには$を使います。

```
frame[(frame['ID'].str.contains('A.....')) & (frame['Name'].str.contains('Walter.*s$'))]
```

とすると

<img width="197" alt="スクリーンショット 2015-10-31 10.42.08.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/95987/68d404b8-554d-351d-fd20-a5e1553e8c69.jpeg">
$マークは正規表現の文字列の最後を検索するという意味を持っています。なのでKingsleyのsにはヒットせず、GuisesとCollinsにヒットしました。


#まとめ
DataFrameに格納されたデータの1文字目を条件に検索する方法がわからず、forループをまわして無理矢理検索をしていましたが、この方法を利用すれば短い構文ですばやく検索ができます。