numpy.flipで配列の反転

以前、numpy配列の要素をシフトさせるroll関数を紹介しましたが、
numpyにはこのほか、配列を反転させる、flipという関数も用意されています。
とはいえ、スライス “::-1″で実現できるので、普段はあまりありがたみもないのですが、
画像データの集合などの4次元の配列など、次元が高くなってくるとスライスで書くのは面倒になるので、便利な場面もありそうです。
(スライスで左右反転を書くと ary[:, :, ::-1, :] のようになり、可読性低いので。)

ドキュメント: numpy.flip
引数のaxisで、反転させる次元を指定するのですが、
axis=0(行列では縦の反転),とaxis=1(行列では横の反転) に対応した、
numpy.flipud と、numpy.fliplrという関数もあります。

ついでに紹介しておくと、90度回転させる numpy.rot90というのもあります。


import numpy as np
# サンプルの配列を作成
ary = np.arange(12).reshape(3, 4)
print(ary)
"""
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
"""

# 縦の反転
print(np.flip(ary, axis=0))
print(np.flipud(ary))
print(ary[::-1, :])
# 3つとも同じ出力
"""
[[ 8  9 10 11]
 [ 4  5  6  7]
 [ 0  1  2  3]]
"""

# 横の反転
print(np.flip(ary, axis=1))
print(np.fliplr(ary))
print(ary[:, ::-1])
# 3つとも同じ出力
"""
[[ 3  2  1  0]
 [ 7  6  5  4]
 [11 10  9  8]]
"""

# 90度回転
print(np.rot90(ary))
"""
[[ 3  7 11]
 [ 2  6 10]
 [ 1  5  9]
 [ 0  4  8]]
"""

numpy配列の中身をシフトさせる

pandasデータフレームにおけるshift操作のようなものを行いたくて調べたのでそのメモです。
numpyか、aryにshiftメソッドがあると勝手に決めつけていたのですが実際は無く、代わりにnumpy.rollというのを使います。

ドキュメント: numpy.roll

元々行いたかったのは、配列の要素を左右(2多次元配列の場合はそれぞれの軸方向にも)ずらして、欠損値になるところにはNoneか何か入るイメージだったのですが、
numpy.rollを使うと、名前の通り、回転させるような動きをします。
(例を見る方がわかりやすいです)

numpy.rollは次の引数を取ります。
a: 回転させる配列
shift: 回転させる幅 (値かタプル)
axis: 回転させる方向 (値かタプル。デフォルトはNone)

まず、1次元配列でやってみます。 1次元しかないのでaxisは意味がなく、shift幅だけ指定してます。


import numpy as np
# 元のデータ作成
ary1 = np.arange(7)
print(ary1)
# [0 1 2 3 4 5 6]

print(np.roll(ary1, 2))
# [5 6 0 1 2 3 4]

print(np.roll(ary1, -3))
# [3 4 5 6 0 1 2]

左右が繋がって回転してるようにずれていますね。

次に2次元です。
axisが未指定(もしくはNoneを渡す)場合と、それぞれの軸の方向を指定した場合で挙動が違うのでやってみます。


ary2 = np.arange(30).reshape(5, 6)
print(ary2)
"""
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]]
"""

# axis未指定で、行列全体で回転。左上と右下が繋がる。
print(np.roll(ary2, 2))
"""
[[28 29  0  1  2  3]
 [ 4  5  6  7  8  9]
 [10 11 12 13 14 15]
 [16 17 18 19 20 21]
 [22 23 24 25 26 27]]
"""

# axis=1 で行ごとに回転。
print(np.roll(ary2, 2, axis=1))
"""
[[ 4  5  0  1  2  3]
 [10 11  6  7  8  9]
 [16 17 12 13 14 15]
 [22 23 18 19 20 21]
 [28 29 24 25 26 27]]
"""

# axis=0 で列ごとに回転。
print(np.roll(ary2, 2, axis=0))
"""
[[18 19 20 21 22 23]
 [24 25 26 27 28 29]
 [ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]
 [12 13 14 15 16 17]]
"""

重複のある配列の要素を順序を保ったまま一意化する

業務でタイトルの処理が必要になり、スマートなやり方を探したのでそのメモです。

まずおさらいですが、配列をユニーク化するだけなら集合に変換して戻せば完成します。


sample_list = ['b', 'c', 'c', 'd', 'a', 'b', 'd', 'e', 'a', 'b', ]
print(list(set(sample_list)))
# ['c', 'e', 'a', 'd', 'b']

この時、単に一意化するのではなく、元々の配列で最初の方に出てきた要素から順番に取り出したい、という要件がありました。
この時、昨日紹介した、配列のindexという関数が使えます。
要は元々の配列で何番目に登場していたかをこの関数でえて、その順番で並べかえれば良いです。
そして、ありがたいことに、sortedや、list.sort関数が、keyという
引数を取ってくれます。
keyに引数を一つとる関数を渡すと、各要素をその関数に適用させた結果で並べ替えてくれます。

それぞれやってみます。


sample_list = ['b', 'c', 'c', 'd', 'a', 'b', 'd', 'e', 'a', 'b', ]

# list.sortを使う方法
# 一度集合に変換して、ユニーク化
sorted_list = list(set(sample_list))
# 元々のインデックスでソート
sorted_list.sort(key=sample_list.index)
print(sorted_list)
# ['b', 'c', 'd', 'a', 'e']

# sortedを使う方法
sorted_list = sorted(set(sample_list), key=sample_list.index)
print(sorted_list)
# ['b', 'c', 'd', 'a', 'e']

sorted の方は、 戻り値は配列型なので、list()でキャストする必要はありません。
どちらかというとこちらの書き方の方がスマートだと思います。

Pythonの配列に含まれる特定の要素のindexを取得する

よく使っているnumpyのarrayではなく、Pythonデフォルトの配列(list)の話です。
だいたいいつもnumpyのarrayの方が便利なのですが、listにしかないメソッドもいくつかあります。

その一つがlist.index(x[, start[, end]])です。
これを使うと、listの中から、特定の要素(x)を探して、最初に現れたインデックスを得ることができます。
また、startやendを指定して、特定の区間から探すこともできます。


# サンプルの配列を準備する
sample_list = ['b', 'c', 'a', 'd', 'a', 'b', 'd', 'b', 'a', 'b', ]

# 最初に現れるdのindex
print(sample_list.index('d'))
# 3

# 次に現れるdは 3+1 = 4番目以降を探す
print(sample_list.index('d', 4))
# 6

配列内や、検索区間内に目当ての要素が存在しない場合はValueErrorが出ます。


sample_list.index('e')
"""
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
 in ()
----> 1 sample_list.index('e')

ValueError: 'e' is not in list
"""

ちなみに、普段はenumerateを使って次のようにして算出することが多いです。


print([i for i, value in enumerate(sample_list) if value == 'd'])
# [3, 6]

ただ、ご覧の通りコードが少し長くなるので、先頭の要素だけでいい時などはindexが手軽そうです。

ERAlchemyを使ってER図を描く

以前からER図をいい感じに出力できるツールを探していたのですが、ERAlchemyというのを知ったので使ってみました。
リポジトリ:Alexis-benoist/eralchemy
PyPI: ERAlchemy

Pythonで実装されたコマンドラインツールということで、Homebrewか、pipでインストールできます。


# どちらか行う。
$ brew install eralchemy
$ pip install eralchemy

自分はbrewのほうでインストールしました。
内部でgraphvizを使っているそうなので、もしかしたら事前にインストールしておく必要があるかもしれません。

既存のデータベースから作図する機能もあるようなのですが、自分はerファイルというテキストファイルから作成する方法で使っています。
erファイルのルールですが、ERAlchemyのページには(今のところ)簡単な例以上の説明は無いようです。

元々erdというツールの影響を受けて作られたそうで、erファイルの書式等はerdのドキュメントを読んで試すのが良さそうです。

ERAlchemy was inspired by erd, though it is able to render the ER diagram directly from the database and not just only from the ER markup language.

リレーションに使える記号の一覧を、ERAlchemyのソースコード中から見つけ出したりしてたのですが、無駄な手間でした。
僕も素直にerdのドキュメントを読めばよかったです。

ちなみにこちらから分かる通り、[*?+1]の4種類と未指定が使えます。
https://github.com/Alexis-benoist/eralchemy/blob/master/eralchemy/models.py


class Relation(Drawable):
    """ Represents a Relation in the intermediaty syntax """
    RE = re.compile(
        '(?P[^\s]+)\s*(?P[*?+1])--(?P[*?+1])\s*(?P[^\s]+)')  # noqa: E501
    cardinalities = {
        '*': '0..N',
        '?': '{0,1}',
        '+': '1..N',
        '1': '1',
        '': None
    }

さて、使い方は簡単で、次のコマンドで動きます。

eralchemy -i ‘入力ファイル(erファイル)’ -o ‘出力ファイル(pdfやpngなど)’
サンプルファイルを用意していただけてるので、curlでもってくるところからやってみましょう。


$ curl 'https://raw.githubusercontent.com/Alexis-benoist/eralchemy/master/example/newsmeme.er' > markdown_file.er
$ eralchemy -i markdown_file.er -o erd_from_markdown_file.png

これで、画像ファイルが生成されました。(ブログに貼るためにpngファイルにしましたが、通常はpdfの方が使いやすいかと思います。)

erファイルの中身も少しみておきましょう。
まず、テーブル定義は次のように指定します。


[tags]
    *id {label:"INTEGER"}
    slug {label:"VARCHAR(80)"}
    name {label:"VARCHAR(80)"}

{label:hogehoge}は省略できます。

そしてリレーションは次のように指定します。


users *--? posts
posts *--? comments
users *--? comments
comments *--? comments
tags *--? post_tags
posts *--? post_tags

この例は全て 0以上 対 0or1 のリレーションですが、+で1以上、1で厳密に一つを指定できます。

サンプルの中では使われていませんが、
erdの方のドキュメントを見る限りでは、背景色やフォントの指定などもできそうなので、色々試してみたいと思います。

np.nonzero()を応用して配列中の条件を満たす要素のインデックスを取得する

前回の記事の応用版です。
参考: Numpy配列の非ゼロ要素のインデックスを取得する

非ゼロに限らず、何か特定の条件を満たす要素を抽出したい場面はよくあります。
(正の数だけとか、数値以外の条件とか)
その場合も、nozeroを使うことができます。

これは、Pythonのbool型の値、Flaseが0と見なされることを利用します。
対象の行列に対して、各要素が条件を満たすかどうかを
bool型(True/False)で示す配列を作り、それに対してnonzero()を使います。

前回の記事で使った行列の例を使って、
値が奇数の要素のインデックスを取り出してみましょう。


import numpy as np

# データの準備
ary = np.array(
        [
            [0, 0, -2, 0],
            [1, 0, 2, 0],
            [0, 0, 1, 0],
            [3, 0, 0, 0],
        ]
    )

# 奇数要素のインデックスを取得する。
xx, yy = np.nonzero(ary % 2 == 1)
for x, y in zip(xx, yy):
    print(f"ary[{x}, {y}]={ary[x, y]}")

"""
ary[1, 0]=1
ary[2, 2]=1
ary[3, 0]=3
"""

Numpy配列の非ゼロ要素のインデックスを取得する

とある大き目のマトリックスを対象にゼロでない要素のインデックス(行番号と列番号)を取得する必要があったので、
スマートな方法を探しました。
全部の行と列を2重のfor文で探索すればできるのですがあまりにも無駄が多い気がしましたので。

結果的に、nonzeroという、ずばりそのままの名前の関数があったのでそれを使っています。
ドキュメント: numpy.nonzero

まず、1次元の配列に対してやって見ましょう。


import numpy as np
ary1 = np.array([0, 0, 5, 3, 0, -1, 0])

# 非ゼロ要素のインデックスを取得する
print(np.nonzero(ary1))
# (array([2, 3, 5]),)

# 別の書き方
print(ary1.nonzero())
# (array([2, 3, 5]),)

# 非ゼロ要素の値を取得する
print(ary1[ary1.nonzero()])
# [ 5  3 -1]

てっきり、array([2, 3, 5]) が戻ってくると予想していたのに、
(array([2, 3, 5]),) というタプルの形で結果が戻ってくる点に注意が必要です。
ただ、上の例の最後に挙げたように、スライスとして使いやすいので便利です。

続いて、2次元配列、要するに行列でやってみましょう。


ary2 = np.array(
        [
            [0, 0, -2, 0],
            [1, 0, 2, 0],
            [0, 0, 1, 0],
            [3, 0, 0, 0],
        ]
    )
print(ary2.nonzero())
# (array([0, 1, 1, 2, 3]), array([2, 0, 2, 2, 0]))

これも直感的には[(0, 2), (1, 0), (1, 2), …]みたいなのが戻ってくると思ったら違う形で帰ってきました。
これは次のようにすると、非ゼロ要素に対する操作を行う場合は次のように使います。(一例です。)


xx, yy = ary2.nonzero()
for x, y in zip(xx, yy):
    print(x, y)
"""
0 2
1 0
1 2
2 2
3 0
"""

また、1次元の場合と同じ様に次のようにしてスライスとして使えます。


print(ary2[ary2.nonzero()])
# [-2  1  2  1  3]

matplotlibのグラフを高解像度で保存する

普段はmatplotlibで作ったグラフに美しさを求めるようなことは無いのですが、
とある業務で、解像度の高い状態で出力する必要が発生したので、そのメモです。

普段は画像ファイルが必要な場合もplt.show()でjupyter notebook上に表示した物を保存していましたが、
綺麗に出力するために、画像ファイルに直接書き出しました。
使うのはplt.savefig()です。
ドキュメント: matplotlib.pyplot.savefig
そして、保存するときの引数、dpiに大きめの値を与えることで、高解像度に保存することができます。
デフォルトと、dpiを指定した場合の2通りやってみましょう。


import matplotlib.pyplot as plt
import numpy as np
# ダミーデータ生成
x = np.random.randn(50)
y = np.random.randn(50)

fig = plt.figure(facecolor="w")
ax = fig.add_subplot(111)
ax.scatter(x, y)
ax.set_xlabel("x軸")
ax.set_ylabel("y軸")
ax.set_title("タイトル")
# 解像度の指定をせずに保存
plt.savefig("default_dpi_scatter.png", format="png")

# もう一度同じグラフを作る
fig = plt.figure(facecolor="w")
ax = fig.add_subplot(111)
ax.scatter(x, y)
ax.set_xlabel("x軸")
ax.set_ylabel("y軸")
ax.set_title("タイトル")
# 解像度を指定して保存
plt.savefig("300_dpi_scatter.png", format="png", dpi=300)

結果がこちらです。
デフォルトdpi。

dpi=300を設定。

デフォルトの方は明らかにピンボケしていて、dpiに300を指定した方はくっきりしています。
文字を見れば明らかです。

ちなみに、dpiを指定しなかった場合は、
rcParams[“savefig.dpi”] の値が使われます。そしてこれに”figure”が指定されているときは、
plt.rcParams[“figure.dpi”] の値が使用されます。
(matplotlibの設定フィアルでデフォルト値を変えることはできますが、僕の環境では特に変更していません。)

一応初期値を確認しておきましょう。


print(plt.rcParams["savefig.dpi"])
# figure

print(plt.rcParams["figure.dpi"])
# 72.0

何も指定しないと dpi=72 になるようですね。

DataFrameの2列の値からdictを作る

DataFrameの2列の値のうち、一方の列の値をKey、もう一方の列のValueとする辞書を作る方法の紹介です。
自分はよくやるのですが、意外に知られてないらしいことと、なぜこれが動くのか自分も十分に理解していなかったのでこの機会に調べました。

例えば次のようなデータフレームがあったとします。

id col1 col2
1 key1 value1
2 key2 value2
3 key3 value3
4 key4 value4
5 key5 value5

そして、このcol1の値をkey, col2の値をvalueとして、
{‘key1’: ‘value1’, ‘key2’: ‘value2’, ‘key3’: ‘value3’, ‘key4’: ‘value4’, ‘key5’: ‘value5’}
のようなdictを作りたいとします。

このような場合、僕は次のコードのようにデータフレームの該当の2列を抽出して、そのvaluesプロパティをdict関数に渡します。


import pandas as pd
# サンプルとなるデータフレームを作る
data = [[i, "key"+str(i), "value"+str(i)] for i in range(1, 6)]
df = pd.DataFrame(data, columns=["id", "col1", "col2"])

result_dict = dict(df[["col1", "col2"]].values)
print(result_dict)
# {'key1': 'value1', 'key2': 'value2', 'key3': 'value3', 'key4': 'value4', 'key5': 'value5'}

注意ですが .values を忘れると次のように 列名:Seriesの辞書になってしまいます。


print(dict(df[["col1", "col2"]]))
"""
{'col1': 0    key1
1    key2
2    key3
3    key4
4    key5
Name: col1, dtype: object, 'col2': 0    value1
1    value2
2    value3
3    value4
4    value5
Name: col2, dtype: object}
"""

これも参考ですがよく見かけるのは次ような書き方。


result_dict = dict()
for i in range(len(df)):
    result_dict[df.iloc[i]["col1"]] = df.iloc[i]["col2"]

print(result_dict)
# {'key1': 'value1', 'key2': 'value2', 'key3': 'value3', 'key4': 'value4', 'key5': 'value5'}

さて、話を戻してdict(df[["col1", "col2"]].values)がなぜうまく動くのかです。

改めてドキュメントを読んでみるとdictはiterableを引数に取ることができます。
class dict(iterable, **kwarg)
そして、
iterable のそれぞれの要素自身は、ちょうど 2 個のオブジェクトを持つイテラブルでなければなりません。
とのことです。

実際見てみると、df[[“col1”, “col2”]].valuesはその条件を満たすデータになっています。


print(df[["col1", "col2"]].values)
"""
[['key1' 'value1']
 ['key2' 'value2']
 ['key3' 'value3']
 ['key4' 'value4']
 ['key5' 'value5']]
"""

DataFrame側にdictに渡したら空気を読んでいい感じに変換される機能が実装されている、と勘違いしていたこともあるのですが、
dictの通常の挙動にマッチした動きだったようです。
for分で回すのに比べてかなりスマートに書けるので、最初に紹介した書き方は結構おすすめです。

ユークリッド空間内の複数の点の中から、最も近い点を探す短いコード

実務上次の問題を解く必要が発生し、短くて効率のいい書き方を探したのでそのメモです。

n次元ユークリッド空間内に、m個の点が存在し、その座標がm*n行列で与えられる。
新たに1つの点の座標がn次元ベクトルで与えられた時、m個の点のうち最も近い点のインデックスを求める。

結論から言うと次のコードで求まります。
最後の行が今回作成したコードです。(それ以外はサンプルの点を作成しています。)
ここでは、 n=10, m=5です。


import numpy as np
# m個の点の座標を生成
X = (np.random.randn(5, 10) * 20).round(2)
print(X)
"""
[[-25.3   -3.49 -21.3   -1.98  10.99   2.77  -8.37  -9.01  20.05   7.91]
 [ 16.33  -9.    16.55 -10.27   6.56   7.81  16.03 -14.13  10.58 -32.39]
 [ 12.01 -11.79  34.79  -2.94 -12.24   0.71 -35.01 -17.92  20.6   33.73]
 [-11.47  30.95   4.92 -19.94  34.81   2.54 -11.73  21.91  24.16   4.87]
 [ 38.32  22.13  11.5   18.82  -8.29   4.02 -17.15  -5.26 -35.62 -19.43]]
"""
# 新しい点の座標
y = (np.random.randn(10) * 20).round(2)
print(y)
"""
[-23.09 -57.02  28.07  39.45 -22.52  -5.91   9.58  30.66 -11.82  21.5 ]
"""
# 最も近い点のインデックス
print(((X-y)**2).sum(axis=1).argmin())
"""
2
"""

今回は最も近い点を探せればよく、具体的な距離は必要としないので、
ユークリッド距離の定義にある平方根の計算は省略しました。

各点との距離のリストが必要な場合は次のコードで求めることができます。
((X-y)**2).sum(axis=1)**0.5
もしくは線形代数モジュールを使い次のように計算することもできます。
np.linalg.norm(X-y, axis=1)

掲題の問いについても、次のようにかけるのですが、
不要な平方根の計算が入るせいか少しだけ遅かったので、最初の書き方を採用しています。
np.linalg.norm(X-y, axis=1).argmin()