pandasのデータフレームの行をランダムにシャッフルする

てっきり専用のメソッドがあると思っていたら、無さそうだったのでやり方のメモです。
pandas のデータフレームのデータをランダムにシャッフルする方法を紹介します。

これを使う場面の一例ですが、たとえばkerasで作ったモデルを学習する時にvalidation_split を使う場合、
データをシャッフルせずに後ろの方から指定した割合をvalidationデータとして使うため、
事前にデータをシャッフルしておかないとtrainとvalidationでデータの傾向が違うということが発生し得ます。
参考:validation splitはどのように実行されますか?

pandasのデータフレームをランダムに混ぜ合わせる方法は何通りも考えらるのですが、
sample()メソッドに、frac=1を渡して実行するのが一番簡単そうです。
参考:DataFrameのsampleメソッドのドキュメントを読む
そうすることによって、データフレームの全ての行がサンプリングされ、さらに、sample()の仕様により、データはシャッフルされます。

ついでにインデックスの振り直しまでやっておくと便利なので、次の例のように使うと良さそうです。


import pandas as pd
# サンプルデータ生成
df = pd.DataFrame(
    {f"col{i}": [f"value{i}{j}" for j in range(10)] for i in range(3)}
)
print(df)
"""
      col0     col1     col2
0  value00  value10  value20
1  value01  value11  value21
2  value02  value12  value22
3  value03  value13  value23
4  value04  value14  value24
5  value05  value15  value25
6  value06  value16  value26
7  value07  value17  value27
8  value08  value18  value28
9  value09  value19  value29
"""

# シャッフルしてインデックスを振り直す
df = df.sample(frac=1).reset_index(drop=True)
print(df)
"""
      col0     col1     col2
0  value08  value18  value28
1  value06  value16  value26
2  value00  value10  value20
3  value03  value13  value23
4  value05  value15  value25
5  value02  value12  value22
6  value07  value17  value27
7  value04  value14  value24
8  value09  value19  value29
9  value01  value11  value21
"""

jupyter notebook で HTMLを表示する

諸事情あって、notebookで出力するテキストに色を塗りたかったので、方法を調べました。
結論として、spanタグでbackground-colorを設定したHTMLを組み立てて、それを表示する方法をとりました。

notebookで、変数に格納されたHTMLテキストを表示するには、次の関数を使います。

class IPython.display.HTML

使い方のイメージ


from IPython.display import HTML
# 変数 html_text に表示したいhtmlが文字列で入ってるものとする。
HTML(html_text)

ただ、変数に格納されたhtmlを表示するのではなく、普通にhtmlを書くのであれば、
セルをマークダウンモードにしておけば自動的にhtmlとして解釈して表示してくれます。

この他、 %%html というマジックコマンドを使うことも可能です。
参考:cellmagic-html

bashコマンドで標準出力に出力する内容をファイルにも書き出す

久々に使おうとしたら忘れてしまっていたのでメモです。

長時間かかるコマンドの実行を待つ時、標準出力にずらずらと出てくるメッセージを見ながら、
それをファイルにも残しておきたいことがあります。
だいたい、リダイレクション(>や>>)を使って、ファイルに書き出しながら、
ファイルを tail -f コマンドで監視することが多いのですが、
tee という専用のコマンドも用意されています。

次のように、パイプラインで出力をtee コマンドに渡してあげて、 teeコマンドの引数に出力したいファイルを指定しておくと、
標準出力にはそのまま出力され、ファイルにも同じ内容が残ります。


$ echo 'tee Test' | tee file.txt
tee Test
$ cat file.txt
tee Test

-aをつけると追記です。(つけないと上書き)


$ echo 'tee Test2' | tee -a file.txt
tee Test2
$ cat file.txt
tee Test
tee Test2

printでお手軽プログレスバー

前回のprintのオプションの記事の応用です。
参考:Python3のprintの引数

大きめのデータの前処理など待ち時間が長い処理をする時に、進捗を表示したくなる時があります。
専用の良いライブラリもたくさんあるので、実用上はそれで十分なのですが、
前回のprintのオプションを使うと、簡易的なプログレスバーを作成できます。
(何も考えずに printしても進捗はわかるのですが、notebookが見にくくなったりします。)

ポイントは end を使って改行を止めることと、 “\r”(キャリッジリターン)を出力して、
次の文字の出力先を行の先頭にし、前回の出力を上書きすることです。

一例としてやってみます。
本当は重い処理のループなどの中でやるのですが、今回例として time の sleepで無理やり待ち時間を入れています。


import time
for i in range(1, 101):
    print("■" * (i//10), "□"*(10-i//10), sep="", end=" : ")
    print(str(i).zfill(3), "/", 100, "\r", sep="", end="")
    time.sleep(1)

手軽に、という割にコードが汚くなりました。
■と□は余計かもしれませんね。

これを実行すると、こんな感じの表示が出て数字が増えながら、徐々に黒く塗りつぶされていきます。
■■■■□□□□□□ : 040/100

Python3のprintの引数

printは表示したい文字列以外にも色々引数を指定して出力をカスタマイズできるよという話。

Python3系では、Python2と違って、printは関数です。(2では文)
そのため、色々と引数をとることができます。(文ならカスタマイズできないわけでもないし、関数が絶対引数とるわけでもないのですが、その辺は置いといて。)

あまりにも使い慣れすぎてドキュメントを読んでなかったのですが、printの説明には次のようにあります。
print(*objects, sep=’ ‘, end=’\n’, file=sys.stdout, flush=False)

objects を sep で区切りながらテキストストリーム file に表示し、最後に end を表示します。sep 、 end 、 file 、 flush を与える場合、キーワード引数として与える必要があります。

キーワードなしの引数はすべて、 str() がするように文字列に変換され、 sep で区切られながらストリームに書き出され、最後に end が続きます。 sep と end の両方とも、文字列でなければなりません。これらを None にすると、デフォルトの値が使われます。 objects が与えられなければ、 print() は end だけを書き出します。

file 引数は、 write(string) メソッドを持つオブジェクトでなければなりません。指定されないか、 None である場合、 sys.stdout が使われます。表示される引数は全てテキスト文字列に変換されますから、 print() はバイナリモードファイルオブジェクトには使用できません。代わりに file.write(…) を使ってください。

出力がバッファ化されるかどうかは通常 file で決まりますが、flush キーワード引数が真ならストリームは強制的にフラッシュされます。

バージョン 3.3 で変更: キーワード引数 flush が追加されました。

printにカンマ区切りで複数文字列を渡すと、スペースで結合して表示してくれるのは知っていたのですが、
それがsepって引数で調整できることは初めて知りました。


# デフォルトではsep=" "で区切って出力
print("abc", "def", "ghi")
# abc def ghi

# sepを指定すると区切り文字を変えられる
print("abc", "def", "ghi", sep="==>")
# abc==>def==>ghi

# 末尾につける文字もend(デフォルトは改行) で変更可能
print("abc", "def", "ghi", end="jkl")
# abc def ghijkl

これまで、いくつかの文字列を出力したいけど、途中にスペースや改行を入れたくないときは、
“+”で文字列を連結して表示文字列を作ったりしていましたが、
これでそんな面倒なことをしなくても大丈夫になりました。

numpyの高次元配列に対するdot積の挙動について

numpyの比較的よく使う関数に、dot積があります。
スカラーとベクトルを渡せばベクトルをスカラー倍してくれて、ベクトル同士なら内積を取ってくれ、
行列を二つ渡せばそれらの行列積を戻してくれるとても便利な関数です。
(本当は行列積は、 np.matmul を使ったほうがいいらしい。)

さて、そのnp.dot ですが、行列よりもより高次元の配列についても定義されていることを最近知りました。
ドキュメント : numpy.dot

二つの多次元配列$a$と$b$に対して、$a$の一番最後の次元の長さと、$b$の最後から2番目の次元の長さが等しい時に、
np.dot(a, b)を計算することができます。
i*j行列と、j*k行列に積が定義されて、その積がi*k行列になるのと似ています。

具体的な挙動について、コード動かしてみていきましょう。
まず、サンプルとなるデータを作ります。
aの最後の次元の要素数と、bの最後から2番目の次元の要素数は 5で揃えましたが、
それ以外の次元の要素数はバラバラにして、結果と比較しやすいようにしました。


import numpy as np
a = np.random.randn(2, 3, 4, 5).round(2)
b = np.random.randn(6, 7, 5, 8).round(2)

print(a.shape, b.shape)
# (2, 3, 4, 5) (6, 7, 5, 8)

このdot積を取って、shapeをみてみます。


c = np.dot(a, b)
print(c.shape)
# (2, 3, 4, 6, 7, 8)

5は消えましたが残りの数はそのまま残りましたね。

さて、結果の$c$の各要素の値ですが、次のように計算されたものが入っています。
$$
c[i, j, k, m, n, o] = \sum_{l} a[i, j, k, l] \cdot b[m, n, l, o].
$$
別の書き方をすればこうです。
$$
c[i, j, k, m, n, o] = np.dot(a[i, j, k, :], b[m, n, :, o]).
$$

一応確認しておきましょう。


c[1, 2, 3, 4, 5, 6] ==  np.dot(a[1, 2, 3, :], b[4, 5, :, 6])
# True

データフレームの列からapplyで新しい列を作る時、複数列まとめて作成する

DataFrame(その列なので正確にはSeries)に、何か関数を適用して新しい列を作ることは、
機械学習の特徴量作成や前処理などで頻繁に行う処理だと思います。

いつも、1列作るごとに、applyして結果を得ています。
例えば、とある列の値を2乗した列と、3乗した値が欲しいときは次のように書きます。


df = pd.DataFrame(
        np.random.randint(10, size=(5, 3)),
        columns=[f"col{str(i)}" for i in range(3)]
)

# 生成するデータごとにapplyする
df["pow_2"] = df["col0"].apply(lambda x: x**2)
df["pow_3"] = df["col0"].apply(lambda x: x**3)

print(df)

"""
   col0  col1  col2  pow_2  pow_3
0     0     1     6      0      0
1     9     8     8     81    729
2     1     6     8      1      1
3     5     1     2     25    125
4     0     2     5      0      0
"""

このくらい簡単な例であれば、計算負荷も大したことがないのですが、
物によっては、非常に無駄な処理をすることがあります。
例えば日本語の自然言語処理で大量のテキストを形態素解析し、表層形と原形と品詞の列を
それぞれ取得したいときなど、共通の形態素解析処理部分は一回で済ましたいので
3列個別にapplyするなどやりたくありません。

このような場合、applyする関数の戻り値をSeriesで戻せば、
applyの戻りを服す列にできることを知りました。

例えば次のように書きます。


# もう一度サンプルデータ生成
df = pd.DataFrame(
        np.random.randint(10, size=(5, 3)),
        columns=[f"col{str(i)}" for i in range(3)]
)

df[["pow_2", "pow_3"]] = df["col0"].apply(lambda x: pd.Series([x**2, x**3]))
print(df)
"""
   col0  col1  col2  pow_2  pow_3
0     6     3     1     36    216
1     8     4     9     64    512
2     4     4     7     16     64
3     1     1     4      1      1
4     1     8     3      1      1
"""

タプルや配列ではだめで、Seriesで返した場合のみの挙動です。
lambda式を遣わず、普通に定義したSeriesを返す関数でもできます。

なぜこのような挙動になるのか公式ドキュメント内からは該当箇所を探せていないのですが、
とても便利なので積極的に使っていきたいです。

matplotlibでgif動画生成

3次元グラフの次は動画(gif)を用いたデータの可視化方法のメモです。

matplotlibでは、次のクラスに、パラパラ漫画のようにグラフのリストを渡してあげることで、
アニメーションさせることができます。
matplotlib.animation.ArtistAnimation

例として、サインカーブを少しずつずらしながら描いてみました。


import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import ArtistAnimation

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)

# 0 <=x < 2pi の範囲の点列を作成。
x = np.linspace(0, 2*np.pi, 101)[: -1]
# 各コマの画像を格納する配列
image_list = []

for i in range(100):
    # ずらしながらsinカーブを描写し、配列に格納
    y = np.sin(np.roll(x, -i))
    image = ax.plot(x, y)
    image_list.append(image)

# アニメーションを作成
ani = ArtistAnimation(fig, image_list, interval=10)
# gifに保存
ani.save('sin_animation.gif', writer='pillow')

保存されたgifがこちらです。

フルサイズで貼り付けると記事中でも動くのですね。
(いつものようにサムネイルで張ったら止まってしまっていて、クリックしないと動画になりませんでした。)

動画が使えると少しデータの可視化の幅が広がりそうです。
とりあえず機械学習の学習の進捗とかの可視化などに使ってみたいです。

matplotlibでSurface plots

昨日に続いてmatplotlibの3次元グラフの話です。
今回のテーマは Surface plots。(日本語では表面プロットでいいのかな?)
2変数関数の可視化等に便利なやつですね。

ドキュメントは今回もこちら。 : The mplot3d Toolkit

今回は例として 鞍点を持つ次の関数を可視化してみましょう。
$$
z = f(x, y) = x^2 – y^2.
$$

比較用に等高線で可視化したグラフと並べてみました。
参考: matplotlibで等高線


import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np


def f(x, y):
    return x**2 - y**2


X, Y = np.meshgrid(
        np.linspace(-10, 10, 101),
        np.linspace(-10, 10, 101),
    )
Z = f(X, Y)

fig = plt.figure(figsize=(16, 6), facecolor="w")
ax_3d = fig.add_subplot(121, projection="3d")
ax_3d.plot_surface(X, Y, Z)

ax = fig.add_subplot(122)
contour = ax.contourf(X, Y, Z)
fig.colorbar(contour)
plt.show()

結果はこちら。

可視化する対象によって向き不向きがあるのでいつもそうだというわけではないのですが、
今回のサンプルでは圧倒的に3次元プロットの方が圧倒的に関数の形をつかみやすいですね。

matplotlibで3D散布図

matplotlibで3次元のグラフを作成する方法のメモです。
今回は散布図を描いてみます。

matplotlibで3次元のグラフを書くには、mplot3d Toolkitというのを使います。
ドキュメント: The mplot3d Toolkit
また、 3次元散布図についてはこちらのドキュメントも参考になります。 3D scatterplot

ポイントとしては、(importした後明示的には使わないので忘れがちですが、)
Axes3Dを必ずインポートしておくことと、axを取得するときに、
projection="3d"を忘れないことですね。

iris のデータの4つの特徴量の中から適当に3個選んでやってみます。


import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from sklearn.datasets import load_iris

iris = load_iris()
data = iris.data
label = iris.target

fig = plt.figure(figsize=(8, 8), facecolor="w")
ax = fig.add_subplot(111, projection="3d")
for c in range(3):
    ax.scatter(
        data[label == c, 0],
        data[label == c, 2],
        data[label == c, 3],
        label=iris.target_names[c]
    )

ax.set_xlabel(iris.feature_names[0])
ax.set_ylabel(iris.feature_names[2])
ax.set_zlabel(iris.feature_names[3])

ax.legend()
plt.show()

結果がこちら。
綺麗に3次元のプロットができました。