NumPyを使って、Pythonの値コピーを理解する

皆さん、こんにちは。

南波真之(なんばさねゆき)と申します。

私はエンジニアではない文系の人間ですが、Pythonの可能性やデータ分析を使った仕事に興味があります。

前回はPythonを扱う上でほぼ利用する「Jupyter Notebook」について学んできました。そしてその後継である「Jupyter Lab」も触れながらいくつかPythonのコードを実行して見るということを行いました。ぜひご興味ありましたらそちらの記事も御覧ください。

今回は、NumPy(ナムパイ)を使ってみます。

NumPyは、科学技術計算に特化したサードパーティ製のパッケージで、これにより数値計算や多次元配列のデータ(ベクトルや行列など)をPython標準の機能に加えて利用できるようになります。つまり、より実践で利用できる形でデータを効率的に、高速に処理して使うことができるようになるのです。

NumPyはpandasやmatplotlibのような別のパッケージを利用する際にも数値を計算するために利用することが多いため、今回取り上げることにしました。

目次

NumPy環境の利用

NumPyはpipやAnacondaを使って利用することができますが、まずは前回もやったとおり仮想環境を作り、インストールします。

$ python3.9 -m venv venv-test001
$ ls venv-test001
bin include lib pyvenv.cfg
$ source venv-test001/bin/activate
(venv-test001)$ pip install numpy

また、Jupyter Labを使ってNumPyを勉強していきたいのでJupyter Labの起動もしておきます。

(venv-test001)$ pip install jupyterlab
(venv-test001)$ jupyter-lab

私がNumPyを学習している際に理解するのが難しかったことの1つは、コピーの概念です。

今回は、ここに絞って調べてみました。

値のコピー

NumPyでは、1次元配列だけでなく、多次元配列もndarrayという独自のデータ構造を用いて表現できます。

例えば、np.arrayを使って配列を作り、オブジェクトの型を見てみます。

import numpy as np
a = np.array([[1, 2, 8],[3, 6, 9]])
a

(出力)

array([[1, 2, 8],
       [3, 6, 9]])
a.shape

(出力)2行3列のデータであることがわかります。

(2, 3)
type(a)

(出力)

numpy.ndarray

今回は、NumPyを使って、Pythonで紛らわしくなる値コピーを見ていきます。

書籍には、このように書かれています。

コピーという言葉には、広義に参照を渡す場合も含まれます。明確に参照とコピーを分けるために、参照の場合を浅いコピー(Shallow Copy)といい、そうでない場合に深いコピー(Deep Copy)といいます。

浅いコピー

浅いコピーとはどういうことかというと、「浅いコピー = 参照渡し」のことで、参照元のデータが参照先に渡されるため、片方の要素を変更すると、もう一方の要素も変更されることになります。

例えば、こんな形です。

先程作った変数aを使ってみます。

## 変数aの値を、変数a1に代入
a1 = a
a1

(出力)

array([[1, 2, 8],
       [3, 6, 9]])
## 変数a1の0行目1列目(0から数えるため、「2」の値が該当する)の値を100に変更
a1[0,1] = 100
a1

(出力)

# 2が100に変更されたことがわかる
array([[  1, 100,   8],
       [  3,   6,   9]])

ここで、参照元としていたaの値を確認します。

a

(出力)

array([[  1, 100,   8],
       [  3,   6,   9]])

参照元も2の部分が100になっていることがわかりました。

このように、浅いコピー(参照渡し)の場合は、参照元の値にも影響があるということになります。

Pythonは基本的には浅いコピー(参照渡し)と覚えておくといいです。

もう少し突っ込んで考えてみると、メモリへの記録のさせ方に関係しており、浅いコピーの場合はもとの配列と同じメモリを参照しているために、配列が大きくなればそれだけ深いコピーに比べてメモリ効率がよくなるということになります。

ただし、注意点としては、元のデータも変更されてしまう点です。

深いコピー

一方で深いコピーはというと、浅いコピーとは異なり、値を変更しても参照元のデータは変更されません。

コピーして完全に独立したデータとして扱うようなイメージでしょうか。まさに深いコピーです。

こちらも実際に確認してみます。

新しい変数bを用意します。

## 変数bを多次元配列で用意
b = np.array([[1, 2, 8],[3, 6, 9]])
b

(出力)

array([[1, 2, 8],
       [3, 6, 9]])
.copy()を利用し、bの値を新しく作った変数b1にコピー
b1 = b.copy()
b1
変数b1の0行目1列目(0から数えるため、「2」の値が該当する)の値を1000に変更し、値を確認。
b1[0, 1] = 1000
b1

(出力)

# 2が1000に変更されたことがわかる
array([[  1, 1000,   8],
       [  3,   6,   9]])

ここで、参照元としていたbの値を確認します。

b

(出力)

array([[1, 2, 8],
       [3, 6, 9]])

参照元bは、2の部分はそのままになっていることがわかりました。

つまり、更新されていないということです。

これは、コピー先の配列の要素に対して、新しくメモリが割り当てられて記録されるためで、コピー元の要素とは別々であることからもわかります。

ここまで来ると少しピンと来るかもしれませんが、この深いコピーは配列の大きさによってメモリを大きく消費する可能性があることがわかります。

ちなみに、b1 = b.copy()としていたところは、b1 = np.copy(b)としても同様の結果が得られます。

補足:ravelメソッドとflattenメソッド

次元変換の部分で浅いコピー(参照渡し)と、深いコピー(コピー)の話と関連があるポイントがありますので、補足します。

次元変換とは、1次元の配列を2次元の配列に変更するような作業です。

reshapeというメソッドを使うのですが、配列を1次元配列に戻す際にravelメソッドとflattenメソッドを利用することがあります。

その際の値の返し方が、ravelメソッドは参照を返し、flattenメソッドはコピーを返すようになっています。

NumPyの活用で、Pythonを効率化

いかがでしたでしょうか。

今回は、数値計算や多次元配列のデータをPython標準の機能に加えて利用するためのNumPyについて、その中でもコピー部分について書いてきました。

Pythonでデータ分析をする上では、NumPyは必須項目です。理解すべき項目も多いですが、興味のあるところからじっくりやっていきましょう。

インターネット・アカデミーは、Python講座が充実しています。Python認定スクールにもなっているため質の高い知識を得ることができ、基礎学習の先にあるそれぞれの目標を目指していくためには良い場所となります。

よく、「プログラミングは独学でもなんとかなる」という情報もありますが、新しいことを学んでいく際の近道は「プロに教えてもらうこと」だと思います。

インターネット・アカデミーはキャリアサポートも充実しており、一人ひとりに専任のキャリアプロデューサーがサポートしてくれるため中途半端になることがありません。

ご興味ある方は各講座のページを覗いてみてください。無料カウンセリングもできます。

良い記事だなって思ったら、是非シェアをお願いします!
  • URLをコピーしました!
  • URLをコピーしました!
目次