pythonのthisモジュールに定義されている変数について

以前紹介した The Zen of Python の記事の中で、
thisというモジュールに仕掛けられたEaster Eggを紹介しました。

その記事中ではインポートした瞬間に表示される文字列にしか焦点を当ててませんでしたが、
このモジュールに含まれている関数や定数についてもちょっと面白いので紹介します。

このthisの中で、どんな変数やメソッドが定義されているのか、dir関数で見てみましょう。


import this
# 略

dir(this)
'''
['__builtins__',
 '__cached__',
 '__doc__',
 '__file__',
 '__loader__',
 '__name__',
 '__package__',
 '__spec__',
 'c',
 'd',
 'i',
 's']
'''

“__”で始まる特別な値以外に、c, d, i, s の4つの変数が含まれています。
これらのうち、iとcはそれぞれ整数ですがあまり意味はありません。
ちょっと面白いのは、dとsです。

まず、 dの方は次の辞書です。


print(this.d)
# 以下出力
{'A': 'N', 'B': 'O', 'C': 'P', 'D': 'Q', 'E': 'R', 'F': 'S', 'G': 'T', 'H': 'U', 'I': 'V', 'J': 'W', 'K': 'X', 'L': 'Y', 'M': 'Z', 'N': 'A', 'O': 'B', 'P': 'C', 'Q': 'D', 'R': 'E', 'S': 'F', 'T': 'G', 'U': 'H', 'V': 'I', 'W': 'J', 'X': 'K', 'Y': 'L', 'Z': 'M', 'a': 'n', 'b': 'o', 'c': 'p', 'd': 'q', 'e': 'r', 'f': 's', 'g': 't', 'h': 'u', 'i': 'v', 'j': 'w', 'k': 'x', 'l': 'y', 'm': 'z', 'n': 'a', 'o': 'b', 'p': 'c', 'q': 'd', 'r': 'e', 's': 'f', 't': 'g', 'u': 'h', 'v': 'i', 'w': 'j', 'x': 'k', 'y': 'l', 'z': 'm'}

そして、 sは次の意味不明な文字列が入ってます。


print(this.s)
Gur Mra bs Clguba, ol Gvz Crgref

Ornhgvshy vf orggre guna htyl.
Rkcyvpvg vf orggre guna vzcyvpvg.
Fvzcyr vf orggre guna pbzcyrk.
Pbzcyrk vf orggre guna pbzcyvpngrq.
Syng vf orggre guna arfgrq.
Fcnefr vf orggre guna qrafr.
Ernqnovyvgl pbhagf.
Fcrpvny pnfrf nera'g fcrpvny rabhtu gb oernx gur ehyrf.
Nygubhtu cenpgvpnyvgl orngf chevgl.
Reebef fubhyq arire cnff fvyragyl.
Hayrff rkcyvpvgyl fvyraprq.
Va gur snpr bs nzovthvgl, ershfr gur grzcgngvba gb thrff.
Gurer fubhyq or bar-- naq cersrenoyl bayl bar --boivbhf jnl gb qb vg.
Nygubhtu gung jnl znl abg or boivbhf ng svefg hayrff lbh'er Qhgpu.
Abj vf orggre guna arire.
Nygubhtu arire vf bsgra orggre guna *evtug* abj.
Vs gur vzcyrzragngvba vf uneq gb rkcynva, vg'f n onq vqrn.
Vs gur vzcyrzragngvba vf rnfl gb rkcynva, vg znl or n tbbq vqrn.
Anzrfcnprf ner bar ubaxvat terng vqrn -- yrg'f qb zber bs gubfr!

ただこの文字列、薄目にみてみると、各単語の文字数が The Zen of Python が似ています。

実はこれはちょっとした暗号になっていて、
this.s の各文字を、this.dの辞書で変換すると、 The Zen of Python が現れます。

書き方はいろいろあると思いますが次のような形でやってみましょう。


print("".join(map(lambda x: this.d.get(x, x), this.s)))
# 以下出力
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

モジュール this の実装自体が
The Zen of Python に反してややこしい値になっているというちょっとした遊び心のようです。

ちなみに、 this モジュールのソースコードも読んでみたのですが、 mapではなく、内包表記でやっているようですね。
sやdを定義したあとに、次のように書いてありました。
よく考えなくてもこれで十分ですね。


print("".join([d.get(c, c) for c in s]))

pythonでアスキー文字の一覧を得る

本来は前回の平仮名やカタカナの一覧を作る記事よりこちらを先に書くべきでした。
参考:pythonでひらがなとカタカナのリストを作成する

ここでは、abcなどのアルファベットや0123といった数値のリストを得る方法を紹介します。
実はこれらは組み込み関数にあらかじめ定義されている定数があるので、
平仮名のように文字コードから作ったりする必要はありません。

string — 一般的な文字列操作

各定数の説明は上のドキュメントに書いてあるので、
ここでは具体的にその内容を表示しておきましょう。
タブや空白、改行などもあり、printすると逆に見えなくなる例もあるので、
jupyter notebook で裸で実行した時に表示される文字列をコメントとしてつけました。


import string

string.ascii_letters
# 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'

string.ascii_lowercase
# 'abcdefghijklmnopqrstuvwxyz'

string.ascii_uppercase
# 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'

string.digits
# '0123456789'

string.hexdigits
# '0123456789abcdefABCDEF'

string.octdigits
# '01234567'

string.printable
#  '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'

string.punctuation
# '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'

string.whitespace
# ' \t\n\r\x0b\x0c'

標準化レーベンシュタイン距離

以前の記事で、レーベンシュタイン距離を計算できるライブラリを紹介しました。
参考:pythonで編集距離(レーベンシュタイン距離)を求める

どちらかというと、ライブラリよりもアルゴリズム側の説明の続きなのですが、
標準化されたレーベンシュタイン距離(normalized Levenshtein distance)というものも提案されています。
これは、二つの文字列のレーベンシュタイン距離を、文字数が多い方の文字数で割った値として定義されます。
固有名詞の名寄せなどでレーベンシュタイン距離を使う場合、
こちらを使った方がうまく行くことが多いようです(個人的な経験から。)

以前紹介した、 python-Levenshtein
実装されているんじゃないかと期待してしばらく調べていたのですが、どうやらこれは実装されてないようです。
特にLevenshtein.ratio という関数に期待したのですがこれは全然違いました。

ということで自分で実装しましょう。


import Levenshtein


def normalized_distance(text0, text1):
    dist = Levenshtein.distance(text0, text1)
    max_len = max(len(text0), len(text1))
    return dist / max_len

関数名は normalized_levenshtein_distance にしたかったが流石に長すぎるので少し短縮。
ただ、これでも長いですね。(pep8的につらい)

これによって、例えば通常のレーベンシュタイン距離では、
“バニラ” と “アイス” の組み合わせも “チョコレート” と “チョコレートアイス” もどちらも距離は3でしたが、
標準化した距離を使うことで、
前者は距離1、後者は 1/3(=0.333…)と算出されるようになり、前者の方が離れてると見なせます。

使ってみた結果がこちら。


print(Levenshtein.distance("アイス", "バニラ"))  # 3
print(Levenshtein.distance("チョコレートアイス", "チョコレート"))  # 3
print(normalized_distance("アイス", "バニラ"))  # 1.0
print(normalized_distance("チョコレートアイス", "チョコレート"))  # 0.3333333333333333

NLTKを使う準備をする

普段、文章の形態素解析にはMeCabを使用しているのですが、
とあるサンプルコードを動かそうとした時に、その中でNLTKが使われており、思ったように動かなかったのでそのメモです。

ちなみに、 Anaconda で環境を作ったので、 nltk自体はインストールされていました。


~$ python --version
Python 3.6.8 :: Anaconda, Inc.
~$ pip freeze | grep nltk
nltk==3.3

サンプルコードを動かした時にデータエラーがこちら


LookupError:
**********************************************************************
  Resource punkt not found.
  Please use the NLTK Downloader to obtain the resource:

  >>> import nltk
  >>> nltk.download('punkt')

ドキュメントを読むと、何かダウンロードをやらないといけないようです。

Installing NLTK Data

こういう、基本的な処理であってもエラーになります。


>>> import nltk
>>> nltk.word_tokenize('hello nltk!')
Traceback (most recent call last):
    (略)
LookupError:
**********************************************************************
  Resource punkt not found.
  Please use the NLTK Downloader to obtain the resource:

  >>> import nltk
  >>> nltk.download('punkt')

  Searched in:
    (略)
**********************************************************************

エラーメッセージを見る限りでは、 ‘punkt’ってのだけで良さそうですが、一気に入れてしまっておきましょう。


~$ python
>>> import nltk
>>> nltk.download()
showing info https://raw.githubusercontent.com/nltk/nltk_data/gh-pages/index.xml

CLI内で完結すると思ったら windowが立ち上がったので少しびっくりしました。

all を選んで Downloadします。
結構時間がかかります。

これで動くようになりました。


>>> import nltk
>>> nltk.word_tokenize("hello nltk!")
['hello', 'nltk', '!']

pandasで散布図行列を書く

何かしらのデータについて調べる時、各データごとの関係をまとめて可視化するために
散布図行列(matrix of scatter plots)を描きたくなることがあります。

matplotlibでゴリゴリ実装するのは手間なので、seabornが良く使われていると思うのですが、
実はpandasでも作成できるのでその紹介です。
(seabornを使えばいいじゃん、というのはごもっともなのですが、
なぜか僕の自宅環境ではwarningが発生し、その原因調査中なのであまり使いたくないのです。)

ドキュメントはこちら。
pandas.plotting.scatter_matrix

早速ですが、irisでやってみましょう。


import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris

iris = load_iris()
df = pd.DataFrame(
        iris.data,
        columns=iris.feature_names,
    )
pd.plotting.scatter_matrix(df, figsize=(15, 15), c=iris.target)
plt.show()

そして表示される結果がこちらです。

オプションで、 引数cにクラス情報を渡して色もつけました。
このコーディング量でかける図としては十分なクオリティではないでしょうか。

pythonでMD5

業務でMD5を使って文字列をハッシュ化する機会があったのでそのメモです。
(最近ではMD5自体があまり安全では無く、暗号学的な強度が必要なときは使うべきでは無いのでご注意ください。)

python3では、標準ライブラリにhashlibというものがあり、これにMD5が実装されています。
ドキュメント: hashlib — セキュアハッシュおよびメッセージダイジェスト

使い方は簡単で、インポートして hashlib.md5(“text”) するだけです、
となれば楽なのですが、これでは動きません。こんなふうにエラーになります。


import hashlib
hashlib.md5("text")

# 以下出力
TypeError                                 Traceback (most recent call last)
 in ()
----> 1 hashlib.md5("text")

TypeError: Unicode-objects must be encoded before hashing

これは、bytes型でデータを渡す必要があるからです。
そのため、b"text""text".encode("utf-8")のようにする必要があります。

これで一応エラーは無くなりますが、まだ少し不十分です。
というのも、戻り値がHASH object という型で返ってきます。


import hashlib
text = "Hello, World!"
bytes_text = text.encode("utf-8")
print(hashlib.md5(bytes_text))

# 出力
<md5 HASH object @ 0x10e3f7620>

欲しいのは、 16進数で表記された文字列だと思うので、もう一手間かけて、hexdigestという関数を実行します。


import hashlib
text = "Hello, World!"
bytes_text = text.encode("utf-8")
hash_obj = hashlib.md5(bytes_text)
print(hash_obj.hexdigest())

# 出力
65a8e27d8879283831b664bd8b7f0ad4

# 1行でやる方法
print(hashlib.md5(b"Hello, World!").hexdigest())

# 出力は同じ
65a8e27d8879283831b664bd8b7f0ad4

これでできました。

sha1やsha256もhashlibに実装されているので同じように使えます。

JSON文字列をオブジェクトに変換する

昨日の記事の逆です。(まとめて書けばよかった)

ドキュメントも同じ場所。
json — JSON エンコーダおよびデコーダ

JSON型の文字列でデータが渡されたとき、それをオブジェクトに変換するには、
json.loads という関数を使います。

早速やってみましょう。
printするだけだと型が変わっていることがわかりにくいので、
typeの結果も出しました。


import json
json_data = '{"key1": "value1", "key2": "value2", "ary1": ["element1", "element2"]}'
data = json.loads(json_data)

print(data)
print(type(data))

# 出力
# {'key1': 'value1', 'key2': 'value2', 'ary1': ['element1', 'element2']}
# <class 'dict'>

辞書型のオブジェクトをJSON文字列に変換する

仕事で必要になったのでメモです。

pythonには jsonという標準ライブラリがあり、
それを使うことで、配列や辞書型のオブジェクトをJSON文字列に簡単に変換することができます。

ドキュメントはこちら。
json — JSON エンコーダおよびデコーダ

インポートして dumps関数を使うだけなので早速やってみましょう。


import json
data = {
        "key1": "value1",
        "key2": "value2",
        "ary1": ["element1", "element2"],
    }
json_data = json.dumps(data)
print(json_data)

# 出力
# {"key1": "value1", "key2": "value2", "ary1": ["element1", "element2"]}

ちなみに、json.dumpsを使わず、str(data)で文字列に変換すると、結果は
"{'key1': 'value1', 'key2': 'value2', 'ary1': ['element1', 'element2']}"
になります。
JSONのルールでは文字列はダブルクオーテーションで囲まないといけないので、
これはJSONではありません。

厳密にJSON型を要求する関数やAPIに渡すときはこれだと受け付けられないので、
json.dumpsを使いましょう。

定常自己回帰過程AR(3)の自己相関係数を具体的に求めてみる

自己回帰過程の定義と定常になる条件、定常自己回帰過程の性質
という記事で、定常自己回帰過程の性質を紹介しました。
その中に、逐次的に自己相関係数を算出する次の公式があります。

$$
\rho_k = \phi_1\rho_{k-1}+\phi_2\rho_{k-2}+\cdots+\phi_p\rho_{k-p}, k\geq1
$$
これをユール・ウォーカー方程式と言います。
こいつを使って、具体的に自己相関過程の自己相関係数を求めてみようというのが今回の記事です。

$p=1$や$p=2$の例は本に載っているので、$p=3$でやってみます。
$$
\rho_k = \phi_1\rho_{k-1}+\phi_2\rho_{k-2}+\phi_p\rho_{k-3}, k\geq1
$$
また、例として扱う定常AR(3)過程は、以前の記事でグラフをプロットした、こちらです。
$$y_t=1+\frac{11}{15}y_{t-1}-\frac{1}{3}y_{t-2}+\frac{1}{15}y_{t-3}+\varepsilon_t$$

$\phi_1,\phi_2,\phi_3$の値がわかるので、あとは、$\rho_0,\rho_1,\rho_2$さえわかれば残りの$\rho_k$は順番に計算できます。

これは、次の4つの関係式を使って計算できます。最初の2つは、ユール・ウォーカー方程式に$k=1,2$を代入したもの、
残り2つは自己相関係数の性質です。

$$
\rho_1 = \phi_1\rho_0 + \phi_2\rho_{-1} + \phi_3\rho_{-2}\\
\rho_2 = \phi_1\rho_1 + \phi_2\rho_0 + \phi_3\rho_{-1}\\
\rho_0 = 1\\
\rho_{k} = \rho_{-k}
$$
これを整理すると、
$$
\rho_1 = \phi_1 + \phi_2\rho_1 + \phi_3\rho_2\\
\rho_2 = \phi_1\rho_1 + \phi_2 + \phi_3\rho_1\\
$$
となり、さらに
$$
(1-\phi_2)\rho_1 – \phi_3\rho_2= \phi_1\\
(\phi_1+\phi_3)\rho_1 -\rho_2 =-\phi_2\\
$$
と変形できます。
あとは$\phi_k$の値を代入して、
$$
\frac{4}{3}\rho_1 – \frac{1}{15}\rho_2= \frac{11}{15}\\
\frac{4}{5}\rho_1 -\rho_2 =\frac{1}{3}\\
$$
となり、これを解くと次の二つが得られます。
$$\rho_1=\frac{5}{9},\rho_2=\frac19$$

あとは順番に代入するだけなので、pythonにやってもらいましょう。


import numpy as np
phi_1 = 11/15
phi_2 = -1/3
phi_3 = 1/15
rho_values = np.zeros(6)
rho_values[0] = 1
rho_values[1] = 5/9
rho_values[2] = 1/9
for i in range(3, 6):
    rho_values[i] = phi_1*rho_values[i-1]\
                                + phi_2*rho_values[i-2]\
                                + phi_3*rho_values[i-3]
for rho in rho_values:
    print(rho)

# 以下出力
1.0
0.5555555555555556
0.1111111111111111
-0.037037037037037035
-0.027160493827160487
-0.0001646090534979357

これで計算できました。
自己相関が指数関数的に減衰している様子も確認できます。

pythonでアルファベットの大文字小文字変換

自然言語処理系の機械学習を行うときやテキストマイニングをやるとき、
前処理としてアルファベットの大文字小文字を統一することがよくあります。
(失われる情報もあるのでしない方が良いという主張も見たことがありますが、僕は大抵の場合小文字に統一します。)

これはpythonの文字列が持っている
str.lower()str.upper()
を使うことで実現できます。
pythonの組み込み型のドキュメントを見ると乗っています。


text = "Hello World!"
print(text.lower())  # hello world!
print(text.upper())  # HELLO WORLD!

これだけだと、記事にしなかったと思うのですが、ドキュメントを読んでいると他にも関数が準備されていることがわかりました。


text = "Hello World!"
# 大文字と小文字を入れ替える
print(text.swapcase())  # hELLO wORLD!

text = "HELLO world!"
# 各単語の1文字目を大文字に、残りを小文字に変換する
print(text.title())  # Hello World!

text = "HELLO world!"
# 最初の文字を大文字に、残りを小文字に変換する
print(text.capitalize())  # Hello world!

正直、使う場面を思いつかないのですが面白いですね。
英語ネイティブな人たちが使うのでしょうか。