データフレームの特定の文字列を含む行を抽出するときに None の列があってもエラーにならないようにする

自然言語処理をよくやっているので、データフレームに格納されたテキストデータから、
特定の文字列を含むものを抽出する作業は非常に頻繁に発生します。
そのときには、 pandas.Series.str.contains という非常に便利な関数を使うのですが、
Series の中に None や Nan があるとエラーになるのが地味に不便でした。

print(df)
```
出力結果:
     col
0  あいうえお
1  かきくけこ
2   None
3  たちつてと
```
df[df["col"].str.contains("く")]
```
出力結果:
()
ValueError: cannot index with vector containing NA / NaN values
```

まあ、空白文字列か何かで埋めてあげれば何の問題もないのですが、このエラーが出ると嫌な気持ちになります。
気をつけていてもデータフレーム同士を結合したりするとすぐ None は発生しますし。

これはしょうがないと思っていたのですが、ドキュメントを見ていると、na というパラメーターがー準備されているのを見つけました。

contains の引数に na=True を指定すると,Noneの列も抽出され、na=Falseとすると、Noneの列は含みません。

print(df[df["col"].str.contains("く",na=False)])
```
出力結果:
     col
1  かきくけこ
```
print(df[df["col"].str.contains("あ",na=True)])
```
出力結果:
     col
0  あいうえお
2   None
```

これは便利ですね。
空文字列と、Noneを区別したい場面も結構あるのでNoneをそのまま残せるのはありがたいです。

また、ついでですが、 regex というパラメーターで、正規表現の使用未使用を切り替えられることにも気づきました。
デフォルトで正規表現が使えるのでいつも便利だと思っていたのですが、
完全一致のみにすることもできたのですね。

numpy の数値を表示するときの桁数を指定する

当然ですが、numpyを使っていると数値をprintして値を確認する機会が多々あります。
そこで問題になるのが、表示形式です。
本来は利便性のためだと思うのですが、小数点以下の桁が何桁も表示されたり、突然指数表記になったりします。
正直言って、配列内のどの値が大きくてどの値が小さいのか、ぱっと見でわかりにくいです。

表示例。

>>> import numpy as np
>>> ary = np.random.randn(3, 5)
>>> print(ary)
[[-8.69277072e-01 -4.72662319e-01  5.48868554e-01 -6.03789326e-01 1.95117216e-01]
 [-1.46386861e+00  9.92037075e-01  8.04045031e-01 -1.43756938e+00 7.46898368e-02]
 [-1.05065247e+00  3.72571551e-04 -1.15836779e-01 -5.80949053e-03 1.59979389e+00]]

numpy のドキュメントによると、絶対値が一番大きいものと一番小さいものの差が一定値を超えると指数表記になるそうです。

そこで、値を確認するときは、適当なくらいで四捨五入して表示したりしていたのですが、
実はnumpyのオプションで表示桁数を指定できることがわかりました。

設定を変える前に、デフォルトの設定は下記の関数で見ることができます。
(numpyのバージョンによって設定可能項目は変わります。)

>>> np.get_printoptions()
{'edgeitems': 3, 'threshold': 1000, 'floatmode': 'maxprec', 'precision': 8, 'suppress': False, 'linewidth': 75, 'nanstr': 'nan', 'infstr': 'inf', 'sign': '-', 'formatter': None, 'legacy': False}

各設定値の意味はこちら。set_printoptions
(get_printoptionsのページにはset_printoptions を見ろと書いてある。)

これらの設定値を、set_printoptions関数で変更することができます。
この中で、よく使うのはこの二つ。
precision = 3 # 小数点以下の表記を
suppress = True # 指数表記を禁止

設定してみたのがこちら。

>>> np.set_printoptions(precision=3, suppress=True)
>>> ary = np.random.randn(5,3)
>>> print(ary)
[[ 1.611 -2.259  0.022]
 [-1.937 -0.394  2.011]
 [-0.01  -0.162 -0.823]
 [-1.818 -2.474  0.341]
 [ 0.363 -2.018 -0.667]]

見やすくなりました。

mecab-python3をつかってみる

前回の記事でインストールした mecab-python3 の使い方を書いておきます。
MeCabについてはWikiがあるのですが、このライブラリについては詳細なマニュアルはなく、
リポジトリの test.py を読むようにとそっけなく書いてあります。

ただ、実際のところつかのは非常に簡単です。
次の例のようにMeCab.Tagger() と parse を呼び出すだけで結果を得られます。

>>> import MeCab
>>> text = 'すもももももももものうち'
>>> tagger = MeCab.Tagger()
>>> print(tagger.parse(text))
すもも	名詞,一般,*,*,*,*,すもも,スモモ,スモモ
も	助詞,係助詞,*,*,*,*,,,モ
もも	名詞,一般,*,*,*,*,もも,モモ,モモ
も	助詞,係助詞,*,*,*,*,,,モ
もも	名詞,一般,*,*,*,*,もも,モモ,モモ
の	助詞,連体化,*,*,*,*,,,ノ
うち	名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
EOS

各行の出力結果は次の形です。

表層形\t品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用型,活用形,原形,読み,発音

注意点としては、 parseした戻り値は一つのテキストなので非常に使いにくいことです。

多くの場合、必要なのは原型の列です。
そこでプログラムでこのテキストから原形の情報を取り出すことになります。
僕はいつも下記のような関数を作って実行しています。

tagger = MeCab.Tagger()


def mecab_tokenizer(text):
    parsed_text = tagger.parse(text)
    parsed_lines = parsed_text.split("\n")[:-2]
    surfaces = [l.split('\t')[0] for l in parsed_lines]
    features = [l.split('\t')[1] for l in parsed_lines]
    bases = [f.split(',')[6] for f in features]
    # ここに、必要な品詞の単語だけ選抜する処理を入れることもある
    result = [b if b != '*' else s for s, b in zip(surfaces, bases)]
    return result

jupyter notebook 上で pycodestyle を使う

テキストエディタで.py ファイルを使う時はコードを綺麗にするために、
pycodestyle (pep8の新しい名前)をよく使います。名前変更の経緯はこちら

これを jupyter notebook でも使えるようにします。
その際に必要になるのが、こちらの pycodestyle_magic というツールです。
flake8というライブラリも必要になるので、一緒に入れます。
(pycodestyleがない場合はそれも必要なのでpipインストールしてください。)

pip install flake8 pycodestyle_magic

使う時は、notebookで事前に読み込み、フォマットをチェックしたいセルで、マジックコマンドを使います。

# magicコマンドを使えるように読み込む
%load_ext pycodestyle_magic

そして、チェックしたいコードが書かれたセルの一番上の行にマジックコマンドを入れて実行します。

%%pycodestyle
#ここにチェックしたいプログラムが書かれている。

問題があれば下記のように警告が表示されます。# の後ろにスペースがありませんでしたね。

2:1: E265 block comment should start with '# '

注意として、この時プログラム自体は実行されないようです。
そのため、スタイルをチェックし終わったら、
マジックコマンドを外して改めてセルを実行する必要があります。