potass' blog

ポタシウムのことが書いてないブログ。

PythonでDataFrameのすべての列を検索して特定の文字列を含む行を取得する

PythonでDataFrameのある列を検索して特定の文字列を含む行のmaskはよくありふれていて、すべての列に対して検索したものがなかったので備忘録として残す。

データの準備

import pandas as pd

df = pd.DataFrame([[11, 'red', 'a', 1],
                [22, 'black', 'e', 0],
                [33, 'yellow', 'b', '10']],
                columns=['col1', 'col2', 'col3', 'col4'])
'''
   col1    col2 col3 col4
0    11     red    a    1
1    22   black    e    0
2    33  yellow    b   10
'''

すべての列を検索して特定の文字列を含む行を取得する

# 各index内で文字列'a'が含まれているかのmask。
mask1 = df.apply(lambda row, string='a': True in (row.astype(str).str.contains(string).unique()), axis=1) 
'''
0     True
1     True
2    False
dtype: bool
'''
df[mask1] # これでdfから取り出せる。
'''
   col1   col2 col3 col4
0    11    red    a    1
1    22  black    e    0
'''

ある1列に対して特定の文字列を含む行を取得する

この内容はググればすぐ出てくる。

# col2に'e'が含まれているかのmask。
# この例ではastype(str)は不要だが例えば「col4で0が含まれているか」だと
# col4のdtypeがobject(0と'10'が混じっている)になるので必要になる。
mask2 = df['col2'].astype(str).str.contains('e')
'''
0    False
1     True
2     True
Name: col2, dtype: bool
'''
df[mask2] # これでdfから取り出せる。
'''
   col1    col2 col3 col4
1    22   black    e    0
2    33  yellow    b   10
'''

参考文献

qiita.com

競馬の血統表における2分探索木

競馬の血統表、例えば ドゥラメンテ (Duramente)の血統表 | 競走馬データ - netkeiba.com *1 においてどのようにデータを保持しておくのが便利かという話。
正直一旦読み込んでラベルさえ振ってしまえばもうどうでもいいんだが、それは言わない約束。

netkeibaのデータをそのまま読んだ場合(nk_index)

シンプルにそのまま5世代血統表を読み込んだ場合を考える。具体的にはtdタグを上から順に読むわけだがそうするとFig.1 (左)のようになる。*2各ノードの数字は5世代血統表でのナンバリング(読み込み順)でFig.1 (左)の数字をnk_index*3と呼ぶことにする。また、'父母母父'のような文字列をped_stringと呼ぶことにする。

この方法のナンバリングは2分木探索の深さ優先探索行きがけ順になっている。実装方法にはpop-pushによる方法と回帰による方法があるが前者を採用。
nk_index→ped_stringに変換する方法はpop-pushで行うが、ped_string→nk_indexはX世代血統表のもとで
 {\rm nk\_index} = {\rm generation}-1+\sum_{\rm Gen\  is \ mother} (2^{X+1-{\rm Gen}}-1)
と簡単に計算できるため、PedTree()._ped_index2nk_indexではこの式を使用している。*4

Fig1. (左) nk_indexの5世代血統表 (右) ped_indexの5世代血統表*5

  

ped_index

nk_indexではped_stringが同じでも3世代血統表か5世代血統表か…でindexが変わってしまう、かつそのときのindexとped_stringの対応付けに少し手間がかかる。
この2点を解決するため幅優先探索順でindexをふる。これをped_indexと呼ぶことにする。ped_indexはFig.1 (右)の通り。

ped_index→ped_string

実装はコード参照だが例として16を考える。これが何世代かを計算する。n世代血統表の馬の数は 2^1+2^2+\cdots+2^n = 2^{n+1}-2頭。よって 2^{3+1}-2=14\leq {\rm ped\_index}< 2^{4+1}-2=30から4世代。3世代までの頭数は2^{3+1}-2=14頭なのでそれをped_indexから引く。ped_index-14=2。これを4bit分の2進数表示にして0010となる。0→父、1→母に読み替えて'父父母父'となる。

ped_string→ped_index

実装はコード参照だが例として'父母母父'を考える。父→0、母→1と読み替えて2進数0110となり10進数に直して6。このped_stringは4世代なので3世代までの頭数2^{3+1}-2=14頭を足してped_indexは6+14=20となる。

使用例

ptree = PedTree(max_generation=5) # 5世代血統表の作成。max_generation=5の場合は引数省略可。
ptree.nk_index2ped_index(5) # max_generation=5の場合のnk_index=5に対応するped_index
# 出力: 31 
pdi = PedIndex(16)  # ped_index=16を入力
pdi.get_dict()
# 出力:  {'ped_index': 16, 'ped': '父父母父', 'ped_num_in_generation': 2}

pdi.set_ped_index(48) # ped_index=48で上書き
pdi.ped() # ped_stringだけ欲しい場合
# 出力: '母父父母父'
pdi.get_father_index() # その父のped_indexを取得。同じくget_mother_index()もある。
# 出力: 98
# 本当にやりたかったこと。もっと高速化できそうだがこれ以上の用途で使用しないのでこの程度で。
import pandas as pd

max_gen = 5 # 5世代血統表
ptree = PedTree(max_generation=max_gen)
pdi = PedIndex()
ped_string_list = [] # ped_string_list[nk_index] -> nk_indexに対応するped_string
for nk_index in range(2**(max_gen+1)-2):
    ped_index = PedTree().nk_index2ped_index(nk_index)
    pdi.set_ped_index(ped_index) # ped_indexを設定
    ped_string_list.append(pdi.ped())

df_nk_index = pd.DataFrame(ped_string_list, index=list(range(len(ped_string_list))), columns=['ped_string']) 
df_nk_index # 出力は下の画像

ソース


参考文献

qiita.com
2分探索木深さ優先探索行きがけ順をpop-pushで実装していたので参考にした。
ちなみに回帰による実装は うさぎでもわかる2分探索木 後編 2分探索木における4つの走査方法 | 工業大学生ももやまのうさぎ塾 などがある。

hdl.handle.net
今回のビット演算部分のアイデアはこれを元にしている。物性物理におけるスピン系の厳密対角化(ED)では状態|\uparrow\downarrow\uparrow\uparrow\uparrow\rangleを大きさ5の配列[1, 0, 1, 1, 1]に格納して状態として扱うのではなく|10111\rangleの2進数に読みかえる。その2進数を10進数表示した23を「(全格子点のスピン)状態」として扱うことがある。こうするとわざわざ状態の配列を用意する必要がなく、かつスピン系のハミルトニアンに作用させたときの行列成分の計算をビット演算で処理できる。詳細はP.538参照。もちろんS=1/2だけでなくS=1(3進数に読みかえる)等にも拡張は可能。*6

*1:血統にはさっぱりの自分でもドゥラメンテの血統表は感嘆する。ディープが日本近代競馬の結晶とよく言われるが血としての縮図はこっちだと思ってる。早世が惜しい。

*2:青矢印先のノードは元ノードの父親、赤矢印は母親です。

*3:ロスタートに注意。

*4:例えば5世代血統表のもとでの'父母母父'の場合を考える。4世代の親なのでgeneration=4。母となっている世代は2、3世代。Σの2世代分の項は2^{5+1-2}-1=15、3世代分は2^{5+1-3}-1=7。よってnk_index=4-1+(15+7)=25。Fig.1(右)で確認しても'父母母父'のnk_indexが25となっていることが確認できる。

*5: (左)は PedTree().graph_nk_index() 、(右)は PedTree().graph() で表示可能。

*6:S=1の場合は例えば S=1 スピン鎖のランチョス対角化 : Kobe Pack 。量子スピン系の計算物理については 計算物理〈3〉数値磁性体物性入門 基礎物理学シリーズ―15 がわかりやすかったと思います。

好きなJRA平地G1レース

今年のダービーは熱かった上に的中した!宝塚記念も良いレースだった!(が馬券は外した)
その熱いレースした2頭が凱旋門賞行こうとしてるとか楽しみやー。(と言って毎年エルコンとオルフェナカヤマフェスタってすごかったんやなーとなるのがオチだが果たして…)

JRA平地G1レース(最近昇格した大阪杯ホープフル除く)で好きなレースをメモがてら列挙。
競馬に興味を持ったのがディープ、競馬場行くようになったのがオルフェのときなのでそのへんのレースが多い。
あとちょうど競馬場行きだしたときに流れてたJRAのCM(俗に言うJRA本気CM)の影響ももろに受けてます。

一番好きなのはやっぱディープで好きなレースは春天菊花賞、宝塚 or 若駒S

フェブラリーS 1999メイセイオペラ 2009サクセスブロッケン
高松宮記念 2000キングヘイロー 2013ロードカナロア
桜花賞 2014ハープスター 2007ダスカ
皐月賞 2015ドゥラメンテ 1999テイエムオペラオー
天皇賞(春) 2006ディープ 2017キタサン
NHKマイルC 2004キンカメ 1998エルコン
VM 2009ウオッカ 2021グランアレグリア
オークス 2012ジェンティルドンナ 2010アパパネサンテミリオン
日本ダービー 1994ナリタブライアン 2022ドウデュース
安田記念 2009ウオッカ 1998タイキシャトル
宝塚記念 2001メイショウドトウ 2006ディープ
スプリンターズS 1994バクシンオー 2020グランアレグリア
秋華賞 2012ジェンティルドンナ 2018アーモンドアイ
菊花賞 1994ナリタブライアン 2005ディープ
天皇賞(秋) 2008ウオッカ 2017キタサン
エリザベス女王杯 2010スノーフェアリー 2011スノーフェアリー
マイルチャンピオンS 2003デュランダル 1998タイキシャトル
ジャパンC 1999スペシャルウィーク 2012ジェンティルドンナ
チャンピオンズC 2001クロフネ 2008カネヒキリ
阪神JF 1993ヒシアマゾン 2008ブエナビスタ
朝日杯FS 1976マルゼンスキー 1997グラスワンダー
有馬記念 2013オルフェ 2000テイエムオペラオー
有馬記念(義務教育) 1990オグリ 1993トウカイテイオー