TensorflowやKerasでJupyterカーネルが落ちるようになってしまった場合の対応

注意: この記事で紹介しているのは根本的解決ではなく、暫定対応です。

前回の記事: tensorflow-textのインストールに苦戦した話 で、やむなくライブラリを1つpipで入れたところ、Tensorflow(keras)を操作しているとJupyterカーネルが死んでしまう事象が再発するようになりました。
実は以前LightGBMを入れた後も同様の事象が発生していたんですよね。
その時は対応方法をメモしていなかったので、この機会に残しておきます。

まず、事象の切り分けです。
今回の事象は jupyter では、結果の出力枠には、Warning など表示せず、メッセージウィンドウで以下のメッセージを表示してお亡くなりになります。

Kernel Restarting
The kernel appears to have died. It will restart automatically.

これだけだと原因は分からないのですが、 コンソールからPythonを起動し、同じコードをコピペして実行していくと、今度は次のエラーが出ます。

OMP: Error #15: Initializing libiomp5.dylib, but found libiomp5.dylib already initialized.
OMP: Hint This means that multiple copies of the OpenMP runtime have been linked into the program. That is dangerous, since it can degrade performance or cause incorrect results. The best thing to do is to ensure that only a single OpenMP runtime is linked into the process, e.g. by avoiding static linking of the OpenMP runtime in any library. As an unsafe, unsupported, undocumented workaround you can set the environment variable KMP_DUPLICATE_LIB_OK=TRUE to allow the program to continue to execute, but that may cause crashes or silently produce incorrect results. For more information, please see http://www.intel.com/software/products/support/.
Abort trap: 6

要するに、 libiomp5.dylib というファイルがダブってるそうです。一個だけリンクされているようにしなさいと言われているのですが、実はまだこの実態ファイルがどこに存在しているのか見つけられておらず、根本的な対応が取れていません。
そこで、次の記述に頼ります。

you can set the environment variable KMP_DUPLICATE_LIB_OK=TRUE

要は問題のライブラリの重複を許して警告を止める設定のようです。
予め環境変数に入れておいても良いでしょうし、Pythonのコード中で行うには次のように設定したら大丈夫です。


import os
os.environ['KMP_DUPLICATE_LIB_OK']='TRUE'

.bash_profile に設定する時は次の記述を入れます。


export KMP_DUPLICATE_LIB_OK=TRUE

こういうのを避けるために 環境構築のconda統一を進めてたようなものなので、とても残念なのですがしばらくこの暫定対応で行かないといけないですね。

もし同じような事象で悩まれている方がいらっしゃいましたら試してみてください。

また、 Jupyter Kernelの突然死は、同じコードをコンソールで実行するとWarningやErrorを見れる場合があるということも覚えておくと便利です。
(実はこのERRORメッセージはJupyterのログに吐き出されているのですそちらから探すこともできます。)

matplotlibの3次元プロットを回転するアニメーションで保存する

matplotlobで3次元のグラフを作る時、jupyter notebookではグリグリと動かしていろんな角度から確認することができます。
それをそのままこのブログに埋め込みたくて方法を探していたのですが良いのが見つからなかったので代用としてgifアニメーションを作ることにしました。
今回の記事では、Z軸を中心にぐるっと一周回転させてみます。

以前、 matplotlib.animation.ArtistAnimation を使ったgifの作り方は紹介したことがあるので、
今回は matplotlib.animation.FuncAnimation
を使う別の方法を紹介します。

参考記事: matplotlibでgif動画生成

(ちなみにFuncAnimation自体は、かなり柔軟に動画を作ることができ、
当然3Dプロットを回す以外の使い方もできます。)

可視化の対象は前回の記事のサッカーボールです。
変数Gには、前回の記事と同じグラフが格納されているものとしてください。
無駄に長いコードなので重複部分は今回のコードに入れていません。

さて、 FuncAnimation の使い方の紹介です。

この関数は、
fig, func, frames の3つの引数を渡して使います。
figはグラフを描写するfigureオブジェクトです。
framesにはリスト等を渡します。整数値を渡すとrange()と同じ動きになり、0からその整数値-1までの値を渡したのと同じになります。
この、framesに渡したリストの値を順番にfuncに渡して関数が実行され、それぞれの実行結果をつなげたものがアニメーションになります。

今回は少し工夫して、init_func という引数も使います。
これは、最初に一回だけ実行する関数を渡します。

1. init_func で 3次元にグラフをplotする
2. func で少しづつ回転する

という流れで、func では回転以外の操作をしないようにして少しだけ効率的にしました。


import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from mpl_toolkits.mplot3d import Axes3D

# G に サッカーボルー型のグラフデータを格納する処理は略

# ノードの座標を固定
pos = nx.spring_layout(G, dim=3)
# 辞書型から配列型に変換
pos_ary = np.array([pos[n] for n in G])

# plot する figureと、 Axesを準備する
fig = plt.figure(figsize=(10, 10), facecolor="w")
ax = fig.add_subplot(111, projection="3d")


# Axes にGraph をプロットする関数を準備
def plot_graph():
    ax.scatter(
        pos_ary[:, 0],
        pos_ary[:, 1],
        pos_ary[:, 2],
        s=200,
    )

    # ノードにラベルを表示する
    for n in G.nodes:
        ax.text(*pos[n], n)

    # エッジの表示
    for e in G.edges:
        node0_pos = pos[e[0]]
        node1_pos = pos[e[1]]
        xx = [node0_pos[0], node1_pos[0]]
        yy = [node0_pos[1], node1_pos[1]]
        zz = [node0_pos[2], node1_pos[2]]
        ax.plot(xx, yy, zz, c="#aaaaaa")


# 引数を受け取って図を回転させる関数を準備
def plt_graph3d(angle):
    ax.view_init(azim=angle*5)


# アニメーションを作成
ani = FuncAnimation(
    fig,
    func=plt_graph3d,
    frames=72,
    init_func=plot_graph,
    interval=300
)

# imagemagickで作成したアニメーションをGIFで書き出す
ani.save("rolling.gif", writer="pillow")

出力結果がこちらのgifです。

もともと対称性の高い図形なので、回転させるありがたみが薄かったかもしれないですね。

図形を回転させるところでは、
view_init
という関数を使いました。
elev と azim という二つの引数をとりますが、回転の向きが違います。
使うのは二つ目の azim の方なので注意が必要です。

globでサブフォルダを含めて再帰的にファイルを探索する

普段は、DBに格納された扱いやすいデータや1ファイルにまとめられたデータばかり扱っていて、
散らばったファイルからデータを拾ってくることは少ない恵まれた環境で仕事しています。
しかし、久々にあるフォルダ配下に散ってるファイルを再帰的に探してまとめて処理する機会があったのでそのメモです。

以前、特定のフォルダの直下のファイルは、 globで手軽に見つけられるという記事を書きました。
参考: globで手軽にファイル名の一覧を取得する

今回は pathlib を紹介しようと思っていたのですが、
よく globのドキュメントを見ると、 バージョン 3.5 から、再帰的なglobが実装されていたんですね。
参考: glob — Unix 形式のパス名のパターン展開

ということでこちらを使ってみます。
recursive に True を指定し、 pathname の中に ** を含めればいいようです。

拡張子付きのファイルパスだけリストアップするには次のように書きます。


import glob
for f in glob.glob("./**/*.*", recursive=True):
    print(f)

"""
./001.txt
./folder01/002.txt
./folder01/003.sql
./folder01/subfolder011/004.txt
./folder01/subfolder011/005.sql
./folder02/006.png
./folder02/subfolder021/007.gif
"""

recursive=False (デフォルト) の場合と一応比較しておきましょう。


for f in glob.glob("./**/*.*", recursive=False):
    print(f)

"""
./folder01/002.txt
./folder01/003.sql
./folder02/006.png
"""

for f in glob.glob("./*/*.*", recursive=False):
    print(f)

"""
./folder01/002.txt
./folder01/003.sql
./folder02/006.png
"""

比較用に ** を * に変えたものも一緒に載せましたが、
recursive=False の場合は、 ** は * と同じ挙動しかしていないことがわかります。

recursive=True にすると、 ** は複数階層のフォルダ(ディレクトリ)も含めて探索してくれています。

特定拡張子のファイルのみ欲しい時は、 pathname の記述で指定しましょう。
ディレクトリだけ指定したい時は / で終えれば可能です。
また、 glob.glob の代わりに、 glob.iglob を使うと、結果をリストではなくイテレーターで返してくれます。


for f in glob.iglob("./**/*.txt", recursive=True):
    print(f)

"""
./001.txt
./folder01/002.txt
./folder01/subfolder011/004.txt
"""


for f in glob.iglob("./**/", recursive=True):
    print(f)

"""
./
./folder01/
./folder01/subfolder012/
./folder01/subfolder011/
./folder02/
./folder02/subfolder021/
"""

望む結果が得られました。

NumPyで行列の固有値と固有ベクトルを求める

最近のNetworkx関係の記事でよく行列の固有ベクトルを求めていますが、
そこで使っているNumPyの関数について紹介します。

最初に行列の固有値と固有ベクトルの定義について復習しておきます。
$\mathbf{A}$を正方行列とします。
この時、スカラー$\lambda$と、零でないベクトル$\mathbf{x}$が、
$$
\mathbf{A}\mathbf{x} = \lambda \mathbf{x}
$$
という関係を満たす時、
$\mathbf{x}$を$\mathbf{A}$の固有ベクトル、$\lambda$を$\mathbf{A}$の固有値と呼びます。

最近のネットワーク分析系の記事でも頻出しているだけでなく、
数学やデータ分析の各所に登場する非常に重要な概念です。

NumPyでは、
numpy.linalg.eig と、 numpy.linalg.eigh として実装されています。

早速、適当な行列に対して使ってみます。


import numpy as np
a = np.array(
        [[-2, -1,  2],
         [1,  4,  3],
         [1,  1,  2]]
    )
print(a)
"""
[[-2 -1  2]
 [ 1  4  3]
 [ 1  1  2]]
 """

# 固有値のリストと、固有ベクトルを列に持つ行列のタプルが戻る
values, vectors = np.linalg.eig(a)
print(values)
# [-2.37646808  4.92356209  1.452906  ]

print(vectors)
"""
[[ 0.97606147  0.04809876  0.4845743 ]
 [-0.05394264 -0.95000852 -0.73987868]
 [-0.21069932 -0.30849687  0.46665542]]
"""

eig一発で、固有値と固有ベクトルをまとめて返してくれるのでとても手軽ですね。
上のサンプルコードのように、それぞれ別の変数で受け取るのがオススメです。

なお、一つの変数で受け取ることもできます。
結果を見ていただければ若干使いにくそうな雰囲気が伝わると思います。


eig_result =  np.linalg.eig(a)
print(eig_result)
"""
(array([-2.37646808,  4.92356209,  1.452906  ]), array([[ 0.97606147,  0.04809876,  0.4845743 ],
       [-0.05394264, -0.95000852, -0.73987868],
       [-0.21069932, -0.30849687,  0.46665542]]))
"""

さて、固有値の方はvalues に入っている値がそれぞれ求めたかった値になりますが、
固有ベクトルの方は少し注意が必要です。 というのもサンプルコードの、コメントに書いている通り、
固有ベクトルは、結果の行列の列ベクトルとして格納されています。

つまり、 vectors[0], vectors[1], vectors[2] は固有ベクトルではありません。
正しい固有ベクトルは、 vectors[:, 0], vectors[:, 1], vectors[:, 2] です。
それぞれ、values[0], values[1], values[2] に対応します。
なお、固有ベクトルを0でないスカラー倍したものはそれもまた同じ固有値の固有ベクトルになりますが、
このeigの戻り値は、単位ベクトル(長さが1)になるように正規化されて戻されます。

一応、固有値と固有ベクトルの定義の両辺をそれぞれ計算して、
これらの値が本当に固有値と固有ベクトルなのか見ておきましょう。


for i in range(3):
    print(values[i] * vectors[:, i])
    print(a @ vectors[:, i])


"""
[-2.31957893  0.12819296  0.5007202 ]
[-2.31957893  0.12819296  0.5007202 ]
[ 0.23681725 -4.67742593 -1.5189035 ]
[ 0.23681725 -4.67742593 -1.5189035 ]
[ 0.70404091 -1.07497418  0.67800646]
[ 0.70404091 -1.07497418  0.67800646]
"""

バッチリですね。

もう一つのeighについての紹介です。
eigは一般の正方行列に対して利用できますが、 eighは、実対称行列と、エルミート行列に対してのみ利用できます。
なお、eighは行列の下三角行列部分だけ使って計算するので、
どちらでもない行列を渡しても普通に動いてしまいます。結果は不正確なので注意が必要です。

ついでに紹介しますが、
numpy.linalg.eigvals と、 numpy.linalg.eigvals というメソッドで、
固有値のみを得ることもできます。
固有ベクトルが不要なら、eig の戻り値の該当部分を捨てれば済むのであまり使ったことはないのですが、
メモリの節約や計算速度等のメリットがあるのかもしれません。

Pandasで欠損のある列の文字列型の数値を数値型に変換する

イケてるタイトルがつけられなくて申し訳ない。

pandas.to_numeric という関数の errors という引数が便利なことを知ったのでそれを紹介します。

データを扱っている時、文字列型の数字を数値型に型変換したいことはよくあります。

単体の変数であれば、 intflaotで変換できます。


int("123") #123
float("123") # 123.0

DataFrameや Series でも、全ての値が問題なく変換できる場合は、 .astypeで変換できます。


data1_str = pd.Series(["1", "2", "3"])
print(data1_str)
"""
0    1
1    2
2    3
dtype: object
"""

data1_int = data1_str.astype(int)
print(data1_int)
"""
0    1
1    2
2    3
dtype: int64
"""

ここで厄介なのが、元の値の中に、欠損値や数値に変換できない値が混ざっている場合です。
.astype(int).astype(float)すると、エラーが発生します。
astypeメソッド自体も、errorという引数をとりますが、
エラーを ignore で抑制した場合、変換は一切行ってくれません。
参考: pandas.Series.astype

僕が期待しているのは、 数値型に変換できる値は数値型に変換して、Noneや変換できない文字列は NaNで埋めてくれることです。
そして、それが、pandas.to_numericを使うと手軽に実現できます。


import pandas as pd

# ダミーデータ生成
df = pd.DataFrame(
    {
        "key": ["key1", "key2", "key3", "key4"],
        "value_str": ["123", "45.67", None, "八十九"],
    }
)

# value_str 列の値を数値に変えられるものは変えた列を作る
df["value_num"] = pd.to_numeric(df["value_str"], errors="coerce")

print(df)
"""
    key value_str  value_num
0  key1       123     123.00
1  key2     45.67      45.67
2  key3      None        NaN
3  key4       八十九        NaN
"""

バッチリできました。

ポイントは、 errors="coerce"の部分です。
errorsは、”ignore”, “raise”, “coerce” の3種類の値を取ります。
“raise” がデフォルトで、これを指定すると普通に例外が発生します。
“ignore” は例外を抑えますが、何の変換もせず、そのままのオブジェクトを返します。
“coerce” は、数値に変換できるものは変換して、そうでないものはNaNにしてくれます。

“raise”だとこのような例外が発生します。


try:
    pd.to_numeric(df["value_str"], errors="raise")
except Exception as e:
    print(e)

# Unable to parse string "八十九" at position 3

文字列を時刻に変える to_datetime や、汎用的な型変換の astype に比べてマイナーな印象があるのですが、
地味に使える場面が多いので、数値への型変換の機会があったら、
to_numeric を試してみてください。

Matplotlibの配色を別の処理でも流用したい

Python(と jupyter notebook)でデータを可視化する場合、色を16進法のRGBで指定できるライブラリは多くあります。
Matplotlibがベースになっているものは、そのカラーマップを指定できることも多いのですし、
「rは赤」、「bは青」など一部の色はアルファベットや色名で指定できるのですが、
もっと多くの色を使いたかったり、値によってグラデーションをつけたい場合で逐一RGBを構築するのは結構な手間です。

そこで、Matplotlibの配色をそのまま流用できないかと思って調べてみました。
結論から言うと、結構簡単に使えそうです。

まず、配色そのもののデータは、
matplotlib.cm と言うモジュールに含まれています。
配色はその名前で指定しますが、名前と実際の色の対応はこちらのリファレンスをみると良いでしょう。
Colormap reference

使いたいカラーマップが決まったら、cm.get_cmap() か、 cmの属性として、使うことができます。
要するに次の2行は同じものです。


cm.get_cmap("Greens")
cm.Greens

さて、どちらもカラーマップのオブジェクトを返してくれますが、
そのカラーマップのオブジェクトにに数値を渡すと、RGBのタプルを返してくれます。


import matplotlib.cm as cm

print(cm.get_cmap("Greens")(0.7, alpha=0.5))
# (0.18246828143021915, 0.5933256439830834, 0.3067589388696655, 0.5)
print(cm.get_cmap("Paired")(3))
# (0.2, 0.6274509803921569, 0.17254901960784313, 1.0)

渡す数値ですが、連続的に色が変化するものには、 0〜1の値を渡します。
色の値が不連続な(要はリファレンスで、Qualitativeのカテゴリにあるもの)は、0〜1の値で渡しても大丈夫ですが、
整数値で0,1,2,3などを指定しても大丈夫です。
これらは、1と1.0や2と2.0など、同じ値でも整数型と浮動小数型で結果が変わるので注意してください。
ちなみに、値はリスト形式で複数同時に渡しても大丈夫です。

さて、最初の話に戻りますが、このRGB値のタプルを他のライブラリ等で使うには、16進法の文字列に変換する必要があります。
255倍して16進法の文字列に変化して、シャープをつけて結合するコードを自分で書いてもいいのですが、
なんと Matplotlibにその関数が用意されていました。

matplotlib.colors.rgb2hex です。
これはなぜか、色のリストは受け取ってくれないので、順番に適用していかないといけないのですが、
RGBのタプルを16進法文字列に手軽に変換してくれます。
(keep_alpha=Trueを指定すると透明度も含めてくれます。デフォルトはFalseです。)

試しにカラーマップから10色取り出してみましょう。


import matplotlib.colors as mcolors
import matplotlib.cm as cm
import numpy as np

cmap = cm.get_cmap("BuGn")
for rgb in cmap(np.arange(0, 1, 0.1)):
    print(mcolors.rgb2hex(rgb))

"""
#f7fcfd
#e9f7fa
#d6f0ee
#b8e4db
#8fd4c2
#65c2a3
#48b27f
#2f9858
#157f3b
#006428
"""

10個の色が取り出せましたね。

最後に何か例を出しておきたいので、networkx で作成したグラフに中心性で色をつけてみました。
(グラフの中心性には複数の種類がありますが、今回は媒介中心性を使いました。)


import networkx as nx
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import matplotlib.colors as mcolors


while True:
    # ランダムにグラフを生成する
    G = nx.random_graphs.fast_gnp_random_graph(15, 0.2)
    # 連結なグラフが生成できたらループを抜ける
    if nx.is_connected(G):
        break

# 媒介中心性の計算
centrality = nx.betweenness_centrality(G)
# 辞書形式なので、ノードの順番と揃えてリスト化する。
centrality_list = np.array([centrality[node] for node in G.nodes])
# 媒介中心性を0〜1に正規化する
color_level = centrality_list - min(centrality_list)
color_level/=max(color_level)
# ノードの色の生成
rgb_list = cm.get_cmap("Oranges")(color_level, alpha=0.8)
node_color=[mcolors.rgb2hex(rgb, keep_alpha=True) for rgb in rgb_list]

# グラフの可視化
fig = plt.figure(figsize=(8, 8), facecolor="w")
ax = fig.add_subplot(1,1,1)
nx.draw_networkx(
                G,
                node_color=node_color,
                node_size=500,
                edge_color="#aaaaaa",
                node_shape="s"
            )

出力がこちら。

媒介中心性が高いところが色が濃くなっているのがわかります。

DataFrameの日付の欠損行を埋める方法

日付に限らず連番等でも使える方法ですが、自分が日単位の時系列データで行うことが多いのでそれで説明します。

DBなどからデータを日単位で集計してpandasのDataFrameを作った時、集計対象のデータがなかった日は行ごと欠損してしまった状態になります。

例えば次のような感じです。


print(df.head(5))
"""
         date  value
0  2020-01-02      9
1  2020-01-06      3
2  2020-01-07      4
3  2020-01-09      9
4  2020-01-11      2
"""
print(df.tail(5))
"""
          date  value
15  2020-02-01      3
16  2020-02-02      2
17  2020-02-09      3
18  2020-02-11      2
19  2020-02-18      2
"""

このままで困らないこともあるのですが、累積和をとるときや、matplotlibで可視化するときなど、
欠損してる日付を補完しておきたいことがあります。

これまで、補完対象のDataFrameを別途構成してappendすることが多かったのですが、
必要な日付の一覧を持ったDataFrameと結合(SQLでいうJoin,pandasの関数ではMerge)すると手軽に補完できることに気づきました。

具体的には次のようなコードになります。


# dates に必要な期間の日付の一覧が入ってるとします。
date_df = pd.DataFrame({"date": dates})

# date_df と 結合する
df = pd.merge(date_df, df, on="date", how="left")
# NaNを 0で埋める
df[["value"]] = df[["value"]].fillna(0)

この例だと単純なので、不足している分のデータFrameを作ってたすのと比べて、あまりメリットを感じないのですが、
これが、例えば複数のキーに対してそれぞれ日付データを全部揃えたいケースになると、かなり楽になります。

例えば、元のデータフレームが次だったとします。


print(df2)
"""
    key        date  value
0  key1  2020-01-03      5
1  key1  2020-01-04      3
2  key1  2020-01-10      5
3  key2  2020-01-02      4
4  key2  2020-01-03      1
5  key2  2020-01-04      9
6  key3  2020-01-04      2
7  key3  2020-01-06      5
8  key3  2020-01-09      8
"""

このDataFrameの key1,key2,key3 に対して、 2020-01-01〜2020-01-10の行を全て揃えたいとします。
このようなときは、次鵜のようにして、keyの値と日付の値のペア全てのDataFrameを作ってそれと結合すると簡単に保管できます。


# key と 日付のペアを網羅したDataFrameを作る
keys, dates = np.meshgrid(
        ["key1", "key2", "key3"],
        [(datetime(2020, 1, 1) + timedelta(days=i)).strftime("%Y-%m-%d") for i in range(10)]
    )

key_date_df = pd.DataFrame(
        {
            "key": keys.ravel(),
            "date": dates.ravel(),
        }
    )

# 結合してソート
df2 = pd.merge(key_date_df, df2, how="left").sort_values(["key", "date"])
# NaNを 0で埋める
df2[["value"]] = df2[["value"]].fillna(0)
# インデックのリセット
df2.reset_index(inplace=True, drop=True)

途中で meshgridを使いましたが、meshgridに慣れてない場合は別の方法でも大丈夫です。

Pythonで連続した日付のリストを作る

日付の連番を文字列で必要になったので、Pythonで生成する方法を二つメモしておきます。

一つ目は、 標準ライブラリである datetime を使うものです。
開始日を生成して、必要な日数だけtimedeltaで差分を加算したものをリスト化したら得られます。
生成したリストはdatetime.datetime型なので、strftimeで文字列に変換して完成です。


from datetime import datetime, timedelta

# 日付のリスト生成()
date_list = [datetime(2020, 1, 25) + timedelta(days=i) for i in range(10)]
# 文字列に変換
date_str_list = [d.strftime("%Y-%m-%d") for d in date_list]
print(date_str_list)
"""
['2020-01-25', '2020-01-26', '2020-01-27', '2020-01-28',
'2020-01-29', '2020-01-30', '2020-01-31',
'2020-02-01', '2020-02-02', '2020-02-03']
"""

もう一つはpandasのdate_range関数を使います。
いくつかみて回った限りではこちらの方が人気のようです。
生成されるのが、DatetimeIndex なので、DataFrameのIndexで使いたい場合はこちらの方が便利なのだと思います。
また、生成するデータの頻度を指定するオプションが異常なほど充実しています。
参考: Time series / date functionality

とりあえず、同じデータを生成してみます。


import pandas as pd 

date_index = pd.date_range("2020-01-25", periods=10, freq="D")
print(date_index)
"""
DatetimeIndex(['2020-01-25', '2020-01-26', '2020-01-27', '2020-01-28',
               '2020-01-29', '2020-01-30', '2020-01-31', '2020-02-01',
               '2020-02-02', '2020-02-03'],
              dtype='datetime64[ns]', freq='D')
"""

# 配列に変換して必要な文字列に加工
date_ary = date_index.to_series().dt.strftime("%Y-%m-%d")
print(date_ary.values)
"""
['2020-01-25' '2020-01-26' '2020-01-27' '2020-01-28' '2020-01-29'
 '2020-01-30' '2020-01-31' '2020-02-01' '2020-02-02' '2020-02-03']
"""

これだけだと、ちょっと手間が余計にかかっていて、2つ目の方法にメリットがないように見えますが、
date_rangeは指定できる引数の種類が多く、場合によってはかなり柔軟に対応できます。

たとえば、開始日時と件数の代わりに、開始日時と終了日時で指定したり、終了日時とデータ件数で指定できます。
次の3行は全て同じ結果を返します。


pd.date_range("2020-01-25", periods=10, freq="D")
pd.date_range(start="2020-01-25", end="2020-02-03", freq="D")
pd.date_range(end="2020-02-03", periods=10,  freq="D")

また、時間単位や月単位、月単位といった頻度もfreqで指定できますが、
平日のみとか、毎月の15日と月末日など、datetimeで実装するには少し面倒なものも手軽に作れます。
再掲ですが、こちらのリファレンスを見ると色々あって面白いです。
Time series / date functionality

Pythonで多変量正規分布に従う乱数を生成する

ベクトル自己回帰のダミーデータを生成するために、多変量正規分布に従う乱数が必要なので、
Pythonで生成する方法を紹介します。

numpyとscipyにそれぞれ用意されています。
同じ名前の関数だったので、どちらかの実装をもう一方がラップしているのかと思っていたのですが、
引数の微妙な違いなどあり、どうやら個別に実装されているようです。

ドキュメントはそれぞれ次のページにあります。

numpy
numpy.random.multivariate_normal
(この記事の numpy は version 1.16を使っています。 numpy 1.17.0 のリリースノートを見ると、random moduleに変更が加えられており、どうやらこの関数にも影響が出てるようなのでご注意ください。)

scipy
scipy.stats.multivariate_normal

さて、実際に期待値と分散共分散行列を指定してそれぞれ乱数を生成してみましょう。


import numpy as np
from scipy.stats import multivariate_normal

# 期待値と分散共分散行列の準備
mean = np.array([3, 5])
cov = np.array([[4, -1.2], [-1.2, 1]])

# numpy を用いた生成
data_1 = np.random.multivariate_normal(mean, cov, size=200)

# データ型の確認
print(data_1.shape)
# (200, 2)

# 期待値の確認
print(np.mean(data_1, axis=0))
# [3.00496708 4.94669956]

# 分散共分散の確認
print(np.cov(data_1, rowvar=False))
"""
[[ 3.86542859 -1.31389501]
 [-1.31389501  0.93002097]]
"""

# scipyで生成する方法
data_2 = multivariate_normal(mean, cov).rvs(size=200)

# データ型の確認
print(data_2.shape)
# (200, 2)

# 期待値の確認
print(np.mean(data_2, axis=0))
# [2.81459692 5.10444347]

# 分散共分散の確認
print(np.cov(data_2, rowvar=False))
"""
[[ 4.46151626 -1.28084696]
 [-1.28084696  1.06831954]]
"""

それぞれきちんと生成できたようです。

分散共分散行列の正定値性のバリデーションなど細かなオプションを持っていますが、
あまり使う機会はなさそうです。
(きちんと行う場合も、事前に固有値を求めて確認しておけば大丈夫だと思います。)

pandasで縦横変換(pivot_table)

前回の更新でPrestoでデータの縦横変換をする方法を紹介しましたが、
クエリで処理を完結させる必要がないときは、一旦pandasのデータフレームに格納してから処理をするのも便利です。

その場合、 pandas.pivot_table を使います。

使い方は簡単で、pd.pivot_tableに、変換したいデータフレーム、
列にするカラム、行にするカラム、集計する値、集計に使う関数を指定するだけです。
fill_value引数で欠損値を埋めるなどの細かい設定もできます。
ドキュメントの例を使ってやってみます。


import pandas as pd
# データ作成
df = pd.DataFrame(
    {
        "A": ["foo", "foo", "foo", "foo", "foo", "bar", "bar", "bar", "bar"],
        "B": ["one", "one", "one", "two", "two", "one", "one", "two", "two"],
        "C": ["small", "large", "large", "small",
              "small", "large", "small", "small", "large"],
        "D": [1, 2, 2, 3, 3, 4, 5, 6, 7],
        "E": [2, 4, 5, 5, 6, 6, 8, 9, 9]
    }
)
print(df)
"""
     A    B      C  D  E
0  foo  one  small  1  2
1  foo  one  large  2  4
2  foo  one  large  2  5
3  foo  two  small  3  5
4  foo  two  small  3  6
5  bar  one  large  4  6
6  bar  one  small  5  8
7  bar  two  small  6  9
8  bar  two  large  7  9
"""

table_0 = pd.pivot_table(
                df,
                values="D",
                index="A",
                columns="C",
                aggfunc="sum",
                fill_value=0,
        )
print(table_0)
"""
C    large  small
A
bar     11     11
foo      4      7
"""

# 行や列、集計関数は配列で複数指定することもできる
table_1 = pd.pivot_table(
                df,
                values="D",
                index=["A", "B"],
                columns="C",
                aggfunc=["sum", "count"],
                fill_value=0,
        )
print(table_1)
"""
          sum       count
C       large small large small
A   B
bar one     4     5     1     1
    two     7     6     1     1
foo one     4     1     2     1
    two     0     6     0     2
"""