pandasのSeriesを辞書型に変換する3つの方法

以前書いた、 DataFrameの2列の値からdictを作る に近い話です。
今回の対象は Dataframeではなく Series。
元々、辞書と同じようにSeries[kye]で、値を取り出すことができるので、
DataFrameに比べて辞書型に変換するニーズも少ないと思うのですが、
to_dict() メソッドを持ってることを最近知ったのでこの記事を書きました。

まず、サンプルルとなるデータを作ります。


import pandas as pd
data = pd.Series({chr(i): i for i in range(97, 105)})
print(data)
"""
a     97
b     98
c     99
d    100
e    101
f    102
g    103
h    104
dtype: int64
"""

それでは、これを3種類の方法で辞書に変換してみましょう。
それぞれ、内包表記を使う方法(昔よく使っていた。)、 to_dict()を使う方法、 dict()にいれてしまう方法(一番楽)です。


print({k: v for k, v in chr_codes.items()})
# {'a': 97, 'b': 98, 'c': 99, 'd': 100, 'e': 101, 'f': 102, 'g': 103, 'h': 104}

print(chr_codes.to_dict())
# {'a': 97, 'b': 98, 'c': 99, 'd': 100, 'e': 101, 'f': 102, 'g': 103, 'h': 104}

print(dict(chr_codes))
# {'a': 97, 'b': 98, 'c': 99, 'd': 100, 'e': 101, 'f': 102, 'g': 103, 'h': 104}

dictに変換するメリットとして、dictならgetメソッドが使えて、keyの中に取りたい値がない場合のデフォルト値の設定もできますよっていう話を
最後に書こうと思っていたのですが、
Seriesにも getメソッドは用意されていて、ほぼ同じように動作するようでした。
pandas.Series.get

どちらかというと、辞書に変換する方法よりも辞書に変換しなくても
同じように使えるってことを覚えておいた方が有益に思います。

matplotlibで二重軸

matplotlibで一つの枠の中に複数のグラフを書く場合、値のレンジが近ければ良いのですが、
10倍以上も違うと片方のグラフが潰れてしまうなど、不便なことがあります。

そのような時はだいたいグラフを分けて書いたり、
Tableauなどの別のソフトを使って2重軸のグラフを書くなどの対応をしているのですが、
matplotlibでも左右の軸を使ったグラフを書くことはできます。

その際は、 twinx というメソッドを使います。
以下、サンプルコードです。
この時、凡例をつけておかないと、それぞれのグラフがどちらの軸を見るのかわからないので、つけるのですが、
ちょっとつけかたが特殊なので、そのサンプルも兼ねています。
(get_legend_handles_labels というメソッドを使います。)


import matplotlib.pyplot as plt
import numpy as np

# データの作成
x = np.arange(0, 20)
y1 = np.random.randint(300, 500, size=20)
y2 = np.random.randint(0, 20, size=20)

fig = plt.figure(facecolor="w")
ax1 = fig.add_subplot(1, 1, 1)
ax1.plot(x, y1, label="折れ線グラフ")
ax1.set_ylabel("折れ線グラフ")
ax1.set_ylim([200, 500])

# 2重軸の作成
ax2 = ax1.twinx()
ax2.bar(x, y2, label="棒グラフ", color="g", alpha=0.5)
ax2.set_ylabel("棒グラフ")
ax2.set_ylim([0, 60])

# 凡例をまとめて出力する
handler1, label1 = ax1.get_legend_handles_labels()
handler2, label2 = ax2.get_legend_handles_labels()
ax1.legend(handler1 + handler2, label1 + label2)

plt.show()

出力される図がこちら。

scikit-imageで画像データの読み書き

常にデータセットの画像データばかり使うのも飽きてくるので、そのほかの画像ファイルをpythonで読み込む方法を調べました。
いくつか方法があるようですが、anacondaに付いてきた、scikit-imageというライブラリが以前から気になっていたので今回はこれを使います。

ドキュメント: scikit-image

どうやら、 scikit-image.io.img_data で読み込めそうです。

試しに画像を読み込んでみて、numpy配列で読み込めたことを確認しました。
また、ついでにimsaveで別名をつけて保存しています。


from skimage import io
# 画像の読み込み
img_data = io.imread("./sample.jpg")
# 縦*横*チャンネル数のnumpy配列で読み込まれていることを確認。
print(type(img_data))
# 
print(img_data.shape)
# (1066, 1600, 3)

# 別名で保存
io.imsave("./sample_2.png", img_data)

このほか、 as_gray オプションで白黒画像としても読み込めました。
配列の次元数も違えば値の範囲も違うので扱いは要注意です。


# 白黒で読み込み
gray_image_data = io.imread("./sample.jpg", as_gray=True)
# チャンネルがなくなり、2次元のデータになる。
print(gray_image_data.shape)
# (1066, 1600)

# カラーで読み込むと、0〜255の整数値
print(img_data.max())
# 255
# 白黒で読み込むと、0.0〜1.0の浮動小数
print(gray_image_data.max())
# 0.9910556862745099

(最大値が中途半端なのは読み込んだ画像によるものです。)

これで、画像を扱うモデルを作ったら任意の画像で試せそうですね。
(あとはサイズの加工とか必要かな。)

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]