100 numpy exercisesの解説 1~50


このエントリーをはてなブックマークに追加

NumPyでよく使うテクニックが網羅されたnumpy-100/100 Numpy exercises.ipynb at master · rougier/numpy-100 · GitHubを一通りやってみています。以下はその途中で調べたメモ書きです。自分が知らなかった機能や、一見して理解が難しい問題に絞ってメモしています。断らない限り64bit Python 3.6 + numpy 1.11で試しています。

問題数が多いので、この記事では50問目までを対象とします。

20. Consider a (6,7,8) shape array, what is the index (x,y,z) of the 100th element?

unravelは、「〈もつれた糸・編み物などを〉解く」という意味の動詞です(〈もつれた糸・編み物などを〉解く)。

26. What is the output of the following script?

python組み込みのsum()関数は、sum(iterable[, start])という引数を持ちます。これは以下のように計算されます。(参考:python sum function - `start` parameter explanation required - Stack Overflow)

def sum(values, start = 0):
    total = start
    for value in values:
        total = total + value
    return total

なので、print(sum(range(5),-1))を計算すると -1 + 0 + 1 + 2 + 3 + 4 = 9となります。

一方、from numpy as npすると、python組み込みのsum()関数の代わりに、np.sum()が呼ばれます。np.sum()の引数をnumpy.sum — NumPy v1.12 Manual で調べると、この問題の最終行は

print(np.sum(range(5), axis=-1))

と等価であることがわかります。

axisは何番目の軸を指定するかを意味します。負の値をいれた場合は、軸を逆順で数えます。今回の場合、range(5)は1次元なので、-1の指定は0の指定と同じです。結局、0 + 1 + 2 + 3 + 4 = 10となります。

27. Consider an integer vector Z, which of these expressions are legal?

Z <- Zが最初理解不能でした。<-という演算子があるわけではなく、Z < -Zをわざと変なスタイルで書いているだけだと思います。

>>> a = np.array([0,-3,4])
>>> a <- a
array([False,  True, False], dtype=bool)

28. What are the result of the following expressions?

まずnp.array(0)が理解できませんでした。np.array([0])であれば長さ1のベクトルですが、np.array(0)とは何なのでしょうか?

実はnp.array(0)は「0という値を持つ0次元のarray」を表すようです。コンソールで値をいじくってみると性質をつかめると思います。(参考:numpy - error extracting element from an array. python - Stack Overflow

>>> a = np.array(7)
>>> type(a)
<class 'numpy.ndarray'>

# 普通のndarrayが持つ関数はすべて使える
>>> a.ndim
0
>>> a.dtype
dtype('int64')
>>> a.min(), a.max()
(7, 7)

# 0次元なのでインデックスでアクセスはできない
>>> a[0]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: too many indices for array

# item()関数を使うと値を取り出せる
>>> a.item()
7
>>> type(a.item())
<class 'int'>

0次元のarrayというのは数学的にはスカラーだと思うのですが、NumPyにおける0次元のndarrayは、NumPyにおけるscalarとは別物のようです。

>>> np.isscalar(np.array(0))
False
>>> np.isscalar(np.int_(4))
True

この問題に関しては python - Why are 0d arrays in Numpy not considered scalar? - Stack Overflow に詳しいですが、自分は深入りしていません。0-dimension array問題 - Qiita にも0次元のndarrayに関する詳細があります。

以上を踏まえて、28. を試してみます。実はPython 3とPython 2で挙動が変わります。まずはPython 3.6の場合。

>>> print(np.array(0) / np.array(0))
__main__:1: RuntimeWarning: invalid value encountered in true_divide
nan
>>> print(np.array(0) // np.array(0))
__main__:1: RuntimeWarning: divide by zero encountered in floor_divide
0

このRuntimeWarningは初回実行時のみ表示されるもので、同じ演算を2回目以降行っても表示されません。

次にPython 2.7 + numpy 1.21で試した場合。from __future__ import divisionは設定していません。

>>> print(np.array(0) / np.array(0))
__main__:1: RuntimeWarning: divide by zero encountered in divide
0
>>> print(np.array(0) // np.array(0))
__main__:1: RuntimeWarning: divide by zero encountered in floor_divide
0

ちょっと自分には理解不能な結果です。

3つ目はPython2, Python3ともに以下の結果になりました。

>>> print(np.array([np.nan]).astype(int).astype(float))
[ -9.22337204e+18]

np.float64型であるNaNをnp.int64型として解釈し、再びnp.float64型として解釈するとNaNに戻らないことを示した例なのですが、残念ながら何が起こっているのかここに書けるほどのことは分かりませんでした。

29. How to round away from zero a float array ?

“round away from zero”, つまり0から遠い方向に小数点を丸める方法について問うています。例えば、2.2は3に丸め、-7.4は-8に丸めることになります。 いったんndarrayの要素をすべて正の数にして、np.ceil()で0から遠い方向に丸めたあと、copysign()で元の符号を復活させています。

32. Is the following expressions true?

np.sqrt(-1)nanになり、np.emath.sqrt(-1)1jになります。ここで、jは複素数を表す記号です。np.emath複素数を扱うときに使うようですが、emathが何の略かはわかりませんでした。

38. Consider a generator function that generates 10 integers and use it to build an array

np.fromiter()は、イテレータからndarrayを生成する関数です。引数のcountは、要素数の上限です。

41. How to sum a small array faster than np.sum?

実際に試してみました。確かにnp.add.reduce()のほうがnp.sum()より高速です。

# setupで指定した部分は最初の1回のみ実行される。最初の引数は100万回実行される。
>>> timeit.timeit("np.add.reduce(a)", setup="import numpy as np; a = np.arange(10);")
0.7812885560124414
>>> timeit.timeit("np.sum(a)", setup="import numpy as np; a = np.arange(10);")
1.9236456239887048

python - What is the difference between np.sum and np.add.reduce? - Stack Overflow によると、``np.sum()は内部でnp.add.reduce()を呼んでいるので、オーバーヘッド分だけnp.sum()のほうが遅くなるようです。

43. Make an array immutable (read-only)

writableではなくwriteableなのに注意。

44. Consider a random 10x2 matrix representing cartesian coordinates, convert them to polar coordinates

R = np.sqrt(X**2+Y**2)の部分はR = np.hypot(X, Y)としたほうが簡潔です。

47. Given two arrays, X and Y, construct the Cauchy matrix C (Cij =1/(xi - yj))

C = 1.0 / np.subtract.outer(X, Y)とありますが、実際は添字の順序を考えるとC = 1.0 / np.subtract.outer(Y, X)が正しいような気がします(違っていたらすみません)。

このouterという関数は、ベクトルAに含まれる要素aと、ベクトルBに含まれる要素bとの全ペアに対して演算を施すときに使う関数のようです。(参照:numpy.ufunc.outer — NumPy v1.12 Manual