Pythonでランダムデータ作成とか

Pythonで適当な数値のベクトルデータを作る状況を考える。

randomモジュールで乱数を作る。

import random
random.seed(2021)
data = [random.uniform(0, 1) for _ in range(10_000)]

random.uniform(a, b)はaとbの間の一様乱数を返す。わかりやすいが、aがbより大きくなった時、エラーにならないことに注意する。 区間が[0, 1)の場合は単にrandom.random()で良い。

import random
random.seed(2021)
data = [random.random() for _ in range(10_000)]

メモリや速度に関して色々と気にする場面が多い。 メモリが限られている場合で大量に処理しないといけない場合はジェネレータにして、イテレータで処理する。

import random


def data_generator(n_data, /, *, seed=None):
    random.seed(seed)
    for _ in range(n_data):
        yield random.random()


def main():
    for r in data_generator(10_000, seed=2021):
        print(r)


if __name__ == '__main__':
    main()

上で関数data_generator()は再現性のある乱数を作るためにseed引数を用意している。こういう書き方は良く使うが、random.seed()関数は第一引数がNoneの場合は現在時刻を用いるので、この書き方だとひとつ前の乱数生成の状態を引き継ぎたい場合にうまくいかないことに注意する。

メモリが十分にある場合は可能な限りインメモリで実行することで速度が改善する。配列の場合はイテレーションせずにベクトル的に処理した方が経験的に良い。 一つの選択肢はnumpyである。

import numpy as np
np.random.seed(2021)
data = np.random.rand(10_000)

結果はリストではなくnumpy.ndarrayになる

print(type(data))
# -> <class 'numpy.ndarray'>

numpyはデータの型にある種の制約があるようで、代入時にデータ型が若干変化する場合がある。

import numpy as np
data = [1, 3.1]
print([type(_obj) for _obj in data])
# -> [<class 'int'>, <class 'float'>]
data = np.array([1, 3.1])
print([type(_obj) for _obj in data])
# -> [<class 'numpy.float64'>, <class 'numpy.float64'>]

おそらく、配列をベクトル的に処理するために、データ型の統一が必要なのだろう。プログラムを設計する時はどの型のベクトルを使いたいのか意識するのが良い。 numpy.ndarrayのデータ型はdtypeで確認できる。

import numpy as np
data = np.random.rand(10_000)
print(data.dtype)
# -> float64

数理的な問題、特に行列演算を行いたい場合はnumpy.matrixを使用する。

import numpy as np
mat = np.matrix([[3, 1], [4, 1]])
print(mat.I)
# -> [[-1.  1.]
# ->  [ 4. -3.]]

科学寄りの用途の場合は、数値を表として扱いたい場合が多い。その場合DataFrameを用いる。

import numpy as np
import pandas as pd
data = np.random.rand(10_000)
df = pd.DataFrame(dict(data=data))
print(df)
#           data
# 0     0.289954
# 1     0.356812
# 2     0.426633
# 3     0.059936
# 4     0.739743
# ...        ...
# 9995  0.641810
# 9996  0.347087
# 9997  0.486495
# 9998  0.436858
# 9999  0.424688
# 
# [10000 rows x 1 columns]

pandasの世界では、行列をDataFrameと呼び、行または列の1次元データのことをSeriesと呼ぶ。

import numpy as np
import pandas as pd
data = np.random.rand(10_000)
sr = pd.Series(data)
print(sr)
# 0       0.289954
# 1       0.356812
# 2       0.426633
# 3       0.059936
# 4       0.739743
#           ...
# 9995    0.641810
# 9996    0.347087
# 9997    0.486495
# 9998    0.436858
# 9999    0.424688
# Length: 10000, dtype: float64

出力はDataFrameとよく似ている。 np.ndarrayに対して簡単な演算を行いたい場合は、numpyに定義されている関数を使うのが良い。典型的なのは定数倍である。

data = np.array([3, 5, 4])
print(data * 2)
# [ 6  4 10]

もう少し汎用的に処理をしたい場合がある。リスト内法表記を使いたくなる。

data = np.array([3, 5, 4])
new_data = [_d * 2 for _d in data]

こうするとリスト型になってしまうので、np.array()でnp.ndarrayに変換する必要がある。

data = np.array([3, 5, 4])
new_data = np.array([_d * 2 for _d in data])

これは、処理の過程で一度リスト型に変換してしまっている。これを避けて、ジェネレータから直接作ることもできる。

data = np.array([3, 5, 4])
new_data = np.fromiter((d * 2 for d in data), dtype=data.dtype)

np.fromiter()関数はdtype引数が必要なことに注意する。