ウェブサイト検索

NumPy の実践例: 便利なテクニック


NumPy ライブラリは、科学計算に使用される Python ライブラリです。さまざまな方法でデータを保存および分析するための多次元配列オブジェクトを提供します。このチュートリアルでは、他のチュートリアルでは必ずしも強調されていない、NumPy が提供するいくつかの機能の例を示します。さまざまな演習で新しいスキルを練習する機会も得られます。

このチュートリアルでは、方法を学びます:

  • ファイルに保存されているデータから多次元配列を作成します
  • numpyアレイから複製データを識別して削除します
  • データセット間の差異を調整するには構造化された NumPy 配列を使用します。
  • 階層データの特定の部分を分析してチャートします
  • 独自の関数の vectorized バージョンを作成します

Numpyを初めて使用する場合は、開始する前にPythonのデータサイエンスの基本に精通することをお勧めします。また、このチュートリアルでMatplotlibを使用してチャートを作成します。それは必須ではありませんが、Matplotlibを事前に知ることは有益かもしれません。

作業環境を設定します

このチュートリアルを始める前に、最初のセットアップを行う必要があります。 Numpyに加えて、データをチャートするために使用するMatplotlibライブラリをインストールする必要があります。また、Pythonのライブラリを使用してコンピューターのファイルシステムにアクセスしますが、Pythonの標準ライブラリの一部であるため、 pathlib をインストールする必要はありません。

チュートリアルのセットアップが既存の Python 環境に干渉しないようにするために、仮想環境の使用を検討することもできます。

JupyterLab 内で Jupyter Notebook を使用して、Python REPL の代わりにコードを実行することも便利なオプションです。これにより、実験して発見したことを文書化できるだけでなく、ファイルをすばやく表示して編集することもできます。ダウンロード可能なバージョンのコードと演習ソリューションは、Jupyter Notebook 形式で提供されます。

一般的なプラットフォームでセットアップするためのコマンドを以下に示します。

最初のコードスニペットはWindowsで使用する必要があり、2番目のコードスニペットはLinux + MacOS用です。

使用しているWindowsのバージョンに応じて、 windows powershell(admin)または emerinal(admin)プロンプトを起動します。次のコマンドを入力します。

PS> python -m venv venv\
PS> venv\Scripts\activate
(venv) PS> python -m pip install numpy matplotlib jupyterlab
(venv) PS> jupyter lab

ここでは、 venv\という名前の仮想環境を作成し、それをアクティブにします。アクティベーションが成功した場合、仮想環境の名前はPowerShellプロンプトに先行します。次に、この仮想環境に numpy およびをインストールし、オプションの jupyterlab をインストールします。最後に、JupyterLabを開始します。

端末を起動し、次のコマンドを入力します。

$ python -m venv venv/
$ source venv/bin/activate
(venv) $ python -m pip install numpy matplotlib jupyterlab
(venv) $ jupyter lab

ここでは、 venv/という名前の仮想環境を作成し、それをアクティブにします。アクティベーションが成功した場合、仮想環境の名前はコマンドプロンプトに先行します。次に、この仮想環境に numpy およびをインストールし、オプションの jupyterlab をインストールします。最後に、JupyterLabを開始します。

プロンプトの前に(venv)があることに気付くでしょう。これは、この時点からあなたがすることはすべてこの環境にとどまり、あなたが他の場所に持っている他のPython作業とは別に留まることを意味します。

すべてがセットアップされたので、学習の旅の主要な部分を始める時が来ました。

NumPy 例 1: ファイルからの多次元配列の作成

Numpy配列を作成すると、高度に最適化されたデータ構造が作成されます。これの理由の1つは、numpyアレイがすべての要素を隣接するメモリの領域に保存することです。 このメモリ管理手法は、データが同じメモリ領域に保存され、アクセス時間が速くなることを意味します。もちろん、これは非常に望ましいものですが、配列を拡張する必要がある場合に問題が発生します。

複数のファイルを多次元配列にインポートする必要があるとします。それらを別々の配列に読み取り、np.concatenate() を使用して結合することができます。ただし、これにより、追加データでコピーを拡張する前に、 元の配列のコピーが作成されます。元の配列には関連のないコンテンツが隣接している可能性があるため、更新された配列がメモリ内に連続して存在することを保証するためにコピーが必要です。

ファイルから新しいデータを追加するたびに配列をコピーし続けると、処理が遅くなり、システムのメモリが無駄に消費される可能性があります。配列に追加するデータが増えるほど、問題は悪化します。このコピー プロセスは NumPy に組み込まれていますが、次の 2 つの手順でその影響を最小限に抑えることができます。

  1. 最初のアレイをセットアップするときは、 に入力する必要がある大きさを決定します。将来のデータの追加をサポートするために、そのサイズを過大評価することを検討することもできます。これらのサイズがわかったら、アレイを事前に作成できます。

  2. 2番目のステップは、ソースデータを入力することです。このデータは、拡張する必要なく、既存の配列にスロットになります。

次に、3次元のnumpyアレイを入力する方法を調べます。

配列にファイル データを設定する

この最初の例では、3つのファイルのデータを使用して、3次元配列を設定します。各ファイルのコンテンツを以下に示します。また、これらのファイルがダウンロード可能な資料にも表示されます。

最初のファイルには、次のコンテンツがある2つの行と3つの列があります。

1.1, 1.2, 1.3
1.4, 1.5, 1.6

この 2 番目のファイルも同じサイズで、次の内容が含まれています。

2.1, 2.2, 2.3
2.4, 2.5, 2.6

3番目のファイルは、再び同じ寸法で、これらの数字を保存します。

3.1, 3.2, 3.3
3.4, 3.5, 3.6

続行する前に、これら3つのファイルをプログラムフォルダーに追加します。ダウンロード可能な資料には、 file10.csv および file11.csv と呼ばれる2つのファイルも含まれており、後でこれらを使用します。

以下の図は、3 つのファイルから作成される NumPy 配列を示しています。

ご覧のとおり、file1.csv は配列の先頭を形成し、file2.csv は中央のセクションを形成し、file3.csv は配列の先頭にあります。戻る。

この配列を作成するために使用されるコードを以下に示します。このコードを実行する前に、図に表示されている3つのファイルを作成するか、ダウンロード可能な資料で提供されているバージョンを使用してください。いずれにせよ、コードを実行しているのと同じディレクトリに配置してから実行します。

>>> from pathlib import Path
>>> import numpy as np

>>> array = np.zeros((3, 2, 3))
>>> print(id(array))
2250027286128

>>> for file_count, csv_file in enumerate(Path.cwd().glob("file?.csv")):
...     array[file_count] = np.loadtxt(csv_file.name, delimiter=",")
...
>>> print(id(array))
2250027286128

>>> print(array.shape)
(3, 2, 3)

>>> array
array([[[1.1, 1.2, 1.3],
        [1.4, 1.5, 1.6]],

       [[2.1, 2.2, 2.3],
        [2.4, 2.5, 2.6]],

       [[3.1, 3.2, 3.3],
        [3.4, 3.5, 3.6]]])

まず、各ファイルを調べて、情報を使用して、予想される配列の最終形状を決定できます。この例では、3つのファイルすべてに同じ数の要素が2行と3つの列に配置されています。結果の配列には、(3、2、3) shape プロパティがあります。図をもう一度見てみると、これを見ることができます。

1 行目と 2 行目は、標準エイリアス np でインポートされる NumPy ライブラリと、pathlib ライブラリからインポートされる Path クラスを示しています。このライブラリにより、Python はオブジェクト指向のアプローチを使用してコンピューターのファイル システムにアクセスできるようになります。 Path クラスのオブジェクトを使用すると、ファイル パスを指定できるほか、オペレーティング システムへのシステム コールを実行できるメソッドも含まれます。この機能は、コードの後半で使用されます。

メモリフットプリントを削減するためにデータを入力し始める前に、前もって配列を作成することをお勧めします。 4行目では、前に決定したように、(3、2、3)の形状のゼロを含む配列を作成します。

次に、アレイにファイルからのデータを入力します。 Pythonの組み込みループのループを作成します numumerate 関数関数は8および9に表示されます。 1つ。また、遭遇したファイルの数のカウントも維持しています。各ファイル参照は csv_file 変数に保存され、増分カウンターは file_count 変数に保存されます。

3 つの .csv ファイルに順番にアクセスするには、Path を使用します。 Path.cwd() を呼び出すことで、現在の作業ディレクトリでファイルを探すように Python に指示します。言い換えれば、プログラムを実行しているディレクトリです。これにより、現在のディレクトリを表す Path オブジェクトが返されます。このオブジェクトから .glob() メソッドを呼び出して、アクセスするファイルの名前を指定します。

この場合、file1.csv、file2.csv、および file3.csv という名前のファイルにアクセスする必要があるため、それらの名前を文字列 file として渡します。 ?.csv。これにより、.glob() に対して、名前がこれらの文字と正確に一致する必要があるファイルのみを選択するように指示されますが、その 5 番目の文字は、ワイルドカード文字 (?)。

残念ながら、 .glob()は、予想される順序でファイルを返すことはできません。この例では、各ファイルの名前には5番目の文字として単一の数字が含まれているため、すべてが予想どおりに機能します。 file11.csv という名前のファイルがあった場合、それは間違った順序で読み取られていたでしょう。これがなぜなのか、そして後でそれを解決する方法について詳しく知ることができます。

>>> for csv_file in sorted(Path.cwd().glob("file?.csv")):
...     print(csv_file.name)
...
file1.csv
file2.csv
file3.csv

ご覧のとおり、番号付きファイルを使用する場合、この種のソートでは常に十分ではありません。

ループがループするたびに、np.loadtxt() 関数を呼び出し、その name プロパティを使用して指定されたファイル名を渡します。また、ファイル内の個々の数値を区切ることができるように、フィールド区切り文字としてカンマ (,) を使用するように指示します。次に、各ファイルの内容が、前に作成した array に割り当てられます。

各ファイルの内容が 0 軸に沿った正しい位置に挿入されていることを確認するには、array[file_count] を使用します。ループが初めて実行されるとき、file1.csv の内容は array[0]、つまり axis-0 に沿った位置 0 に割り当てられます。ループの次の反復では、file3.csv に割り当てられる前に、この軸に沿って file2.csvarray[1] に割り当てます。 >配列[2]。もう一度図を見ると、何が起こったのかが正確にわかります。

5行目と11行で、 id(array)の結果を印刷しました。 id()関数は、オブジェクトの ID を返します。各オブジェクトは、各オブジェクトがコンピューターメモリの一意の場所を占めるため、一意のID値を持っています。コンピューターでコードを実行すると、数字も互いに同一になりますが、おそらく表示されているものとは異なります。

6 行目と 12 行目で表示される ID 値は、最初は 0 のみを含むとして始まった array オブジェクトが、後に各ファイルの内容を含むようになった同じ配列オブジェクトであることを証明しています。これは、全体を通して 1 つのオブジェクトのみが使用され、メモリが効率的に使用されていることを示しています。

この方法で配列を作成する場合、各入力ファイルに要素の行と列の数が同じ数であることを確認することをお勧めします。次に、データファイルがそれほど均一ではない状況に対処する方法を検討します。

さまざまなデータサイズを処理します

そもそも、いくつかの小さなデータを追加します。 short_file.csv という名前のファイルに表示されます。これには、1つの行のみがあります。

4.1, 4.2, 4.3

以下に示すように、それを配列の背面に追加したい:

この 2 番目の配列の作成に使用するコードを実行する前に、short_file.csv という名前のファイルをダウンロードするか、コードを実行するのと同じディレクトリに追加してください。

>>> array = np.zeros((4, 2, 3))

>>> for file_count, csv_file in enumerate(Path.cwd().glob("file?.csv")):
...     array[file_count] = np.loadtxt(csv_file.name, delimiter=",")
...
>>> array[3, 0] = np.loadtxt("short_file.csv", delimiter=",")

>>> array
array([[[1.1, 1.2, 1.3],
        [1.4, 1.5, 1.6]],

       [[2.1, 2.2, 2.3],
        [2.4, 2.5, 2.6]],

       [[3.1, 3.2, 3.3],
        [3.4, 3.5, 3.6]],

       [[4.1, 4.2, 4.3],
        [0. , 0. , 0. ]]])

今回は、4つの個別のファイルを読んでいるため、最初に作成する配列には(4、2、3)の形状があります。 4番目のファイルに対応するには、Axis-0に沿って追加の寸法が必要になるため、1行目にこれを作成します。

forループは、以前のように最初の3つのファイルを読み取るために使用されます。短いファイルで読むには、配列に入れたい場所をPythonに正確に伝える必要があります。以前は、 array [2] などの位置を示して、Axis-0に沿って位置2にデータを挿入することでこれを行いました。このアプローチは、挿入したデータがすでにその位置に既存の配列を埋めたために機能しました。しかし、今回は物事が異なります。

Pythonに伝えるには、Axis-2のインデックス位置3の上部から始まる短いファイルを挿入するには、 array [3、0] を使用します。 3 は以前と同じ軸2位の位置を表しますが、データを行0から挿入する必要があることを示すために 0 を提供する必要があります。プログラム出力の18行目と19行で、次に図でデータがどこに配置されたかを確認します。

前と同様に、コードの開始時に作成された配列オブジェクトは、全体に使用される唯一のものです。コードは数回データを追加しましたが、配列を事前に作成したため、非効率的なコピーは必要ありませんでした。

短すぎるのではなく、4番目のファイルが長すぎたとします。あなたはそのようなファイルをどのように扱うか疑問に思うかもしれませんが、それは問題があるかもしれません。今回は、 long_file.csv という名前のファイルを使用します。

4.1, 4.2, 4.3
4.4, 4.5, 4.6
4.7, 4.8, 4.9

次に、これを配列の以下に示す位置に組み込みます。図からわかるように、配列の残りの部分は、それを収容するために追加の行によって拡張する必要があります。

この3番目の配列を作成するために使用されるコードを以下に示します。実行する前に、コードを実行しているのと同じディレクトリで long_file.csv という名前のファイルをダウンロードまたは作成してください。

>>> array = np.zeros((4, 2, 3))

>>> print(id(array))
2250027278352

>>> for file_count, csv_file in enumerate(Path.cwd().glob("file?.csv")):
...     array[file_count] = np.loadtxt(csv_file.name, delimiter=",")
...
>>> array = np.insert(arr=array, obj=2, values=0, axis=1)

>>> array[3] = np.loadtxt("long_file.csv", delimiter=",")

>>> print(id(array))
2250027286224

>>> array
array([[[1.1, 1.2, 1.3],
        [1.4, 1.5, 1.6],
        [0. , 0. , 0. ]],

       [[2.1, 2.2, 2.3],
        [2.4, 2.5, 2.6],
        [0. , 0. , 0. ]],

       [[3.1, 3.2, 3.3],
        [3.4, 3.5, 3.6],
        [0. , 0. , 0. ]],

       [[4.1, 4.2, 4.3],
        [4.4, 4.5, 4.6],
        [4.7, 4.8, 4.9]]])

今回は、元の配列が短すぎて long_file.csv の内容を収容できないため、axis-1 に沿って追加の行を追加して配列を長くする必要があります。その後、long_file.csv のコンテンツを追加できます。これは、9 行目で np.insert() 関数を使用して行います。この関数は、軸に沿って値を挿入できる関数です。

4 つのパラメータを np.insert() に渡します。 arr パラメータは値を挿入する配列を指定し、obj2 に、axis1 に設定し、values パラメータを 0 に設定すると、0 の値をインデックス位置 2 に挿入できます。 軸 1 に沿って。つまり、図に示すように、配列の一番下の行に沿って配置されます。最後に、long_file.csv のコンテンツを配列に追加するには、11 行目に示すように、もう一度 loadtxt() を使用します。

この図と、コードによって生成された結果の配列を少し見てみると、すべてが期待どおりに機能していることがわかります。

4行目と14行目は、新しいデータが挿入される前後の配列オブジェクトが異なることを示していることに注意してください。これは、 insert()関数が元の配列のコピーを返すために発生します。このメモリの無駄を避けるために、最初のアレイを正しくサイズにしていることを確認することをお勧めします。

ファイルの注文が正しいことを確認してください

path.cwd()。glob( "file?.csv")コードを実行すると、 windowspath のコレクションを表示するために使用できるジェネレーターの反復器を返します。または posixpath オブジェクト。これらはそれぞれ、ファイル?.csv パターンに一致するオペレーティングシステムファイルパスとファイル名を表します。ただし、これらのファイルがジェネレーターによって返される順序は、期待するものではありません。

この例を確認するには、ダウンロード可能なマテリアルから file10.csvfile11.csv という名前の 2 つのファイルを既存のフォルダーに追加します。

10.1,10.2,10.3
10.4,10.5,10.6

おそらく、 file10.csv にあるものをすでに推測していたでしょう。もしそうなら、 file11.csv にあるものを見るとき、あなたは驚かないでしょう:

11.1,11.2,11.3
11.4,11.5,11.6

次のコードを実行します。

>>> for csv_file in Path.cwd().glob("file*.csv"):
...     print(csv_file.name)
...
file1.csv
file10.csv
file11.csv
file2.csv
file3.csv

これらの追加ファイルがそれぞれジェネレーターで認識されるようにするには、一致基準を file*.csv に調整する必要があります。 (*) ワイルドカード文字は、任意の数の不明な文字を表します。 (?) ワイルドカード文字を保持していた場合は、文字列 file の後に 1 文字を含むファイルのみが含まれることになります。これを追加しても、まだ何かが間違っているように見えます。

新しいファイルは、 file1.csv file2.csv の間に配置されていることがわかります。この理由は、ファイル名がアルファナメラルでソートされているためです。これは、ソートがファイル名を左から右に読み取り、違いが見つかるまですべてを等しく見なすことを意味します。これができたら、その違いはその違いに基づいています。

たとえば、各ファイル名の文字が分析されている場合、各ファイルの名前の最初の4文字(この場合、ファイル)ですべてが等しいと見なされます。 Pythonは、5番目のキャラクターのどれが最初に来るかを決定する必要があります。それぞれのユニコード文字コード番号を考慮することにより、これを行います。

文字 1 の Unicode 文字コードは 49 ですが、23 のコードはそれぞれ 50 と 51 です。その結果、5 番目の文字として 1 を含むファイル名は、同じ位置に 2 または 3 を含むファイル名よりも先にソートされます。

file1.csvfile10.csv、および file11.csv の場合、各ファイル名の 5 文字目は同じです。したがって、ソート順は 6 文字目で決まります。 Unicode 文字値を考慮すると、ピリオド (.) の値は 46 であり、値がそれぞれ 48 と 49 である文字 01 の両方の前に来ます。 。したがって、並べ替え順序は file1.csv、次に file10.csv、次に file11.csv になります。

Pythonの組み込み sorted() functionを提供したい場合があります。それが役立つかどうかを確認してみてください。

>>> for csv_file in sorted(Path.cwd().glob("file*.csv")):
...     print(csv_file.name)
...
file1.csv
file10.csv
file11.csv
file2.csv
file3.csv

ただし、 sorted()関数は、以前と同じ望ましくない結果を与えました。

より自然な順序でファイルを読み取るには、natsort ライブラリを使用できます。まず、コマンド python -m pip install natsort を実行してインストールします。インストール後、natsorted() 関数をインポートし、組み込みの sorted() 関数の代わりにそれを使用して、より自然なファイルの並べ替えを実現できます。以下はこれを説明するコードです。

>>> from natsort import natsorted

>>> for csv_file in natsorted(Path.cwd().glob("file*.csv")):
...     print(csv_file.name)
...
file1.csv
file2.csv
file3.csv
file10.csv
file11.csv

最後に、ファイル名の問題を整理することができました。これで、以前のコードをさらに一歩進めて、配列の正しい場所にファイルの内容を追加できます。

>>> array = np.zeros((5, 2, 3))

>>> for file_count, csv_file in enumerate(
...     natsorted(Path.cwd().glob("file*.csv"))
... ):
...     array[file_count] = np.loadtxt(csv_file.name, delimiter=",")
...
>>> array
array([[[ 1.1,  1.2,  1.3],
        [ 1.4,  1.5,  1.6]],

       [[ 2.1,  2.2,  2.3],
        [ 2.4,  2.5,  2.6]],

       [[ 3.1,  3.2,  3.3],
        [ 3.4,  3.5,  3.6]],

       [[10.1, 10.2, 10.3],
        [10.4, 10.5, 10.6]],

       [[11.1, 11.2, 11.3],
        [11.4, 11.5, 11.6]]])

今回は、さまざまなファイルパスを natsorted()に渡します。これは、おそらく意図した方法でソートします。出力は、 file10.csv file11.csv の両方のコンテンツが、アレイ内の正しい場所にあることを示しています。繰り返しますが、ここで(*)WildCardオペレーターがどのように使用されているかに注意してください。また、このバージョンの array の寸法は(5、2、3)に増加して、新しいデータのスペースを提供しました。

ご覧のとおり、ファイル内のデータからnumpy配列を作成することは完全に可能です。ただし、さまざまなサイズが考慮されるようにする必要があります。

次に進む前に、理解度をテストするための演習を完了してください。これは、学習を定着させるためにこのチュートリアルに含まれるいくつかの演習のうちの最初の演習です。

スキルをテストする: 配列にファイル データを設定する

ファイルデータから配列の作成に関する理解をテストする時が来ました。 以下に示す最初の課題を解決できるかどうかを確認してください。

ダウンロード可能なマテリアルで ex1a.csvex1b.csv、および ex1c.csv ファイルを見つけます。 次に、学んだテクニックを適用して、各ファイルを追加できる正しいサイズの 3 次元配列を作成します。各ファイルの内容は、0 軸に沿った別のインデックスの左上隅に接するように挿入する必要があります。 ex1b.csv ファイルの内容を行として挿入し、ex1c.csv の内容を列として挿入する必要があります。

考えられる解決策の 1 つは次のとおりです。

>>> import numpy as np
>>> from pathlib import Path

>>> array = np.zeros((3, 4, 4), dtype="i8")

>>> array[0] = np.loadtxt("ex1a.csv", delimiter=",")

>>> narrow_array = np.loadtxt("ex1b.csv", delimiter=",")
>>> narrow_array = np.insert(arr=narrow_array, values=0, obj=3, axis=0)
>>> array[1, 0] = narrow_array

>>> short_array = np.loadtxt("ex1c.csv", delimiter=",")
>>> short_array = np.insert(arr=short_array, values=0, obj=3, axis=0)
>>> array[2, :, 0] = short_array

>>> array
array([[[ 5, 10, 15, 20],
        [25, 30, 35, 40],
        [45, 50, 55, 60],
        [65, 70, 75, 80]],

       [[ 1,  3,  5,  0],
        [ 0,  0,  0,  0],
        [ 0,  0,  0,  0],
        [ 0,  0,  0,  0]],

       [[ 2,  0,  0,  0],
        [ 4,  0,  0,  0],
        [ 6,  0,  0,  0],
        [ 0,  0,  0,  0]]])

まず、ファイルを調べて、サイズ(3、4、4)の配列がデータに適合するために必要であることがわかります。次に、このサイズのゼロの配列を作成し、整数を保存することを指定します。次に、配列[0] 、またはaxis-0をファイル ex1a.csv に入力する前に、 array 変数に割り当てます。 、それはぴったりと形に合います。

ex1b.csv ファイルには値が 3 つしかありません。これは、array[1] に直接挿入するには短すぎることを意味します。これを修正するには、まずそれを narrow_array に読み込み、次に np.insert() を使用してインデックス位置 3 に追加の 0 を追加します。 values パラメータと obj パラメータで定義されているとおりです。最後に、array[1, 0] を使用して、インデックス 1 の 0 軸に沿って array の一番上の行に narrow_array を挿入します。

ex1c.csv ファイルにも値は 3 つだけあります。これは、列に合わせて調整する必要があることを意味するため、列の末尾にゼロを追加します。今回は、short_array を追加するために、array[2, :, 0] を使用します。これは 0 軸に沿ったインデックス位置 2 に挿入されますが、:, 0 は最初のに挿入されることを意味します。行をまたぐのではなく、列に移動します。

読んだ配列を拡張する代わりに、値を配置する場所についてより具体的にすることができます。たとえば、 short_array array [2、0:3、0] に挿入することができます 0 を挿入する前に。

次のセクションでは、構造化された配列と、異なる配列間の違いを調整するためにそれらを使用する方法について少し学びます。

NumPy 例 2: 構造化配列を使用したデータの調整

前のセクションで作成したnumpy配列を使用すると、各列のデータの意味を知る方法はありませんでした。インデックス番号の代わりに意味のある名前で特定の列を参照できればいいのではないでしょうか?したがって、たとえば、 dustent_grades=results [:, 1] を使用する代わりに、代わりに dustent_grades=results ["exam_grade"] を使用できます。良いニュース!構造化された配列を作成することでこれを行うことができます。

構造化配列の作成

構造化配列は、タプルのセットで構成されるデータ型を持つ NumPy 配列であり、各タプルにはフィールド名と通常のデータ型が含まれます。これらを定義したら、フィールド名を使用して個々のフィールドにアクセスし、変更できるようになります。

以下のコードは、構造化されたnumpyアレイを作成および参照する方法の例を示しています。

>>> import numpy as np

>>> race_results = np.array(
...     [
...         ("At The Back", 1.2, 3),
...         ("Fast Eddie", 1.3, 1),
...         ("Almost There", 1.1, 2),
...     ],
...     dtype=[
...         ("horse_name", "U12"),
...         ("price", "f4"),
...         ("position", "i4"),
...     ],
... )

>>> race_results["horse_name"]
array(['At The Back', 'Fast Eddie', 'Almost There'], dtype='<U12')

>>> np.sort(race_results, order="position")[
...     ["horse_name", "price"]
... ]
array([('Fast Eddie', 1.3), ('Almost There', 1.1), ('At The Back', 1.2)],
      dtype={'names': ['horse_name', 'price'],
      ⮑ 'formats': ['<U12', '<f4'],
      ⮑ 'offsets': [0, 48], 'itemsize': 56})

>>> race_results[race_results["position"] == 1]["horse_name"]
array(['Fast Eddie'], dtype='<U12')

構造化された配列は3〜14行目で定義されていますが、通常のNumpyアレイのように見えるものを定義する4〜8行目を見ることから始めます。競馬に関連する3列と3列のデータで構成されています。データから馬の名前を簡単に選ぶことができますが、他の2つの数字が何を意味するのかを判断するために少し苦労するかもしれません。

9 行目でデータ型が定義されているため、この配列は実際には構造化配列です。各データ型はフィールド名と関連するデータ型で構成されます。 3 つのフィールドは、horse_nameprice、および position です。それらに関連するデータ型は、配列インターフェイス プロトコル コードを使用して定義されます。 U12 は 12 文字の文字列を定義し、f4i44 バイトの浮動小数点および整数形式を指定します。 、 それぞれ。

構造化された配列を設定したら、これらのフィールド名を使用して列を参照できます。 16行目では、 "horse_name" を使用して、競走馬名の配列を表示しました。仕上げ順序を見つけるために、 "position" フィールドを np.sort()関数に渡しました。次に、出力をフィルタリングして、 horse_name および価格のみを表示します。最後に、27行目で、勝利の馬の名前を選択しました。

さまざまな配列の調整

Numpyアレイにフィールド名を含めるには、多くの有用な目的があります。個別のnumpy配列でフィールド名を一致させてレコードを一致させたいとします。これを行うには、各配列の一致するレコードのみが表示されるように、配列を結合します。このアイデアは、2つのリレーショナルデータベーステーブル間でSQLインナー結合を実行したことがある場合、おなじみのものになります。 内側という用語は、ここで使用される結合を定義するために使用されます。

このセクションでは、issued_checks.csvCashed_checks.csv という 2 つの新しいファイルを操作します。

issued_checks.csv ファイルには、check_IDPayeeAmount、および Date_Issued の 4 つのフィールドが含まれています。code>。このファイルは、企業が債権者に発行する一連の小切手をシミュレートします。

Check_ID,Payee,Amount,Date_Issued
1341,K Starmer,150.00,2024-03-29
1342,R Sunak,175.00,2024-03-29
1343,L Truss,30.00,2024-03-29
1344,B Johnson,45.00,2024-03-22
1345,T May,65.00,2024-03-22
1346,D Cameron,430.00,2024-03-22
1347,G Brown,100.00,2024-03-15
1348,T Blair,250.00,2024-03-15
1349,J Major,500.00,2024-03-15
1350,M Thatcher,220.00,2024-03-15

Cashed_checks.csv ファイルには、check_IDAmount、および Date_Cashed の 3 つのフィールドのみが含まれています。このファイルは、ビジネスの債権者が現金化した一連の小切手をシミュレートします。

Check_ID,Amount,Date_Cashed
1341,150.00,2024-04-12
1342,175.00,2024-04-16
1343,30.00,2024-04-12
1345,65.00,2024-04-12
1346,430.00,2024-04-08
1349,500.00,2024-04-08
1350,220.00,2024-04-15

現金化された小切手の payeedate_issueddate_Cashed を確認したいとします。よく見ると、PayeeDate_Issued の詳細が Cashed_checks.csv に含まれておらず、他の 2 つの詳細が含まれていないことがわかります。フィールドは。必要なデータをすべて表示するには、両方のファイルを結合する必要があります。

issued_checks.csv ファイルと Cashed_checks.csv ファイルをプログラムのフォルダーに追加し、次のコードを実行します。

>>> import numpy.lib.recfunctions as rfn
>>> from pathlib import Path

>>> issued_dtypes = [
...     ("id", "i8"),
...     ("payee", "U10"),
...     ("amount", "f8"),
...     ("date_issued", "U10"),
... ]

>>> cashed_dtypes = [
...     ("id", "i8"),
...     ("amount", "f8"),
...     ("date_cashed", "U10"),
... ]

>>> issued_checks = np.loadtxt(
...     Path("issued_checks.csv"),
...     delimiter=",",
...     dtype=issued_dtypes,
...     skiprows=1,
... )

>>> cashed_checks = np.loadtxt(
...     Path("cashed_checks.csv"),
...     delimiter=",",
...     dtype=cashed_dtypes,
...     skiprows=1,
... )

>>> cashed_check_details = rfn.rec_join(
...     "id",
...     issued_checks,
...     cashed_checks,
...     jointype="inner",
... )

>>> cashed_check_details[
...     ["payee", "date_issued", "date_cashed"]
... ]
array([('K Starmer', '2024-03-29', '2024-04-12'),
       ('R Sunak', '2024-03-29', '2024-04-16'),
       ('L Truss', '2024-03-29', '2024-04-12'),
       ('T May', '2024-03-22', '2024-04-12'),
       ('D Cameron', '2024-03-22', '2024-04-08'),
       ('J Major', '2024-03-15', '2024-04-08'),
       ('M Thatcher', '2024-03-15', '2024-04-15')],
      dtype={'names': ['payee', 'date_issued', 'date_cashed'],
      ⮑'formats': ['<U10', '<U10', '<U10'],
      ⮑'offsets': [8, 64, 104], 'itemsize': 144})

2つのnumpyアレイを結合するには、いくつかの償還ヘルパー関数のいずれかを使用して、構造化された配列で作業できるようにします。これらにアクセスするには、最初に numpy.lib.RecFunctions ライブラリモジュールをインポートする必要があります。繰り返しになりますが、 pathlib ライブラリを使用してファイルにアクセスします。 1行目と2行目にインポートされています。

4行目から15行目で、 sudued_checks.csv ファイルとファイルの両方に使用されるデータ型を定義するタプルの2つのPythonリストを作成します。これらは、 dtype np.loadText()のパラメーターに供給され、ファイルを正しく読み取ることができます。

実際のファイルは、17 ~ 29 行目のコードによって読み取られます。前と同じように np.loadtext() を使用しますが、今回はその skiprows パラメータを < に設定します。code>1。これにより、各ファイルにはデータではなくヘッダー情報が含まれるため、各ファイルの最初の行は確実に無視されます。

ファイルから読み取られた2つのnumpy配列は、 .rec_join()ヘルパー関数を使用して、31〜36行で結合されます。この関数は4つのパラメーターを使用します。最初のパラメーターは、2つの配列が結合されるフィールドを定義します。この場合、両方のファイルで各チェックを識別する一意の数字を含む id フィールドに基づいて配列を結合する必要があります。

次に、2番目と3番目のパラメーターとして結合する配列の名前を渡します。

最後のパラメータが最も興味深いです。 jointype="inner" を指定すると、内部結合が実行されます。これは、結果の配列には両方のファイルの一致するレコードのみが含まれ、現金化されたすべての小切手の完全な詳細が提供されることを意味します。 id が一方のファイルに表示され、もう一方のファイルには表示されないレコードは、出力には表示されません。

結合が機能したことを確認するには、38 行目から 40 行目で生成された出力を確認します。 Cashed_check_details 配列には、payeedate_issued、およびdate_Cashed データ。これらのフィールドの最初の 2 つは issued_checks.csv ファイルから取得され、後者は Cashed_checks.csv から取得されました。ご覧のとおり、レコードは正しく照合されています。

重複するフィールド名の処理

ここで、小切手の金額も表示したいとします。あなたはこれを試してみたくなるかもしれません:

>>> cashed_check_details[
...     [
...         "payee",
...         "date_issued",
...         "date_cashed",
...         "amount",
...     ]
... ]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'amount'

amount フィールドが結合された配列に存在しないため、KeyError が発生しました。これは、元のデータ ファイルの両方に amount フィールドが含まれていたために発生しました。同じラベルを持つ 2 つのフィールドを含む構造化配列を持つことはできません。結合操作により、それらの名前は一意に変更されました。

それらが現在呼ばれているものを見つけるために、最初に結合された配列のデータ型を表示します。 .dtype プロパティを介してアクセスできます。

>>> cashed_check_details.dtype
dtype([('id', '<i8'), ('payee', '<U10'), ('amount1', '<f8'), ('amount2', '<f8'),
⮑ ('date_issued', '<U10'), ('date_cashed', '<U10')])

出力を注意深く見ると、元の amount フィールドの名前が amount1amount2 に変更されていることがわかります。内容を表示するには、これらの名前を使用する必要があります。このシナリオでは、両方のフィールドに同じデータが含まれるため、どちらを選択しても問題ありません。

>>> cashed_check_details[
...     [
...         "payee",
...         "date_issued",
...         "date_cashed",
...         "amount1",
...     ]
... ]
array([('K Starmer', '2024-03-29', '2024-04-12', 150.),
       ('R Sunak', '2024-03-29', '2024-04-16', 175.),
       ('L Truss', '2024-03-29', '2024-04-12',  30.),
       ('T May', '2024-03-22', '2024-04-12',  65.),
       ('D Cameron', '2024-03-22', '2024-04-08', 430.),
       ('J Major', '2024-03-15', '2024-04-08', 500.),
       ('M Thatcher', '2024-03-15', '2024-04-15', 220.)],
      dtype={'names': ['payee', 'date_issued', 'date_cashed', 'amount1'],
      ⮑ 'formats': ['<U10', '<U10', '<U10', '<f8'],
      ⮑'offsets': [8, 64, 104, 48], 'itemsize': 144})

今回は、小切手額が出力に含まれています。

どの小切手を発行し、何が換金されたかがわかったので、現在換金されていない小切手のリストを確認したい場合があります。

>>> outstanding_checks = [
...     check_id
...     for check_id in issued_checks["id"]
...     if check_id not in cashed_checks["id"]
... ]

>>> outstanding_checks
[np.int64(1344), np.int64(1347), np.int64(1348)]

>>> [int(check_id) for check_id in outstanding_checks]
[1344, 1347, 1348]

1行目では、リスト理解を使用して outstanding_checks という名前のリストを作成します。これには、 sused_checks 配列にある id 値を含むチェックのリストが含まれますが、 cashed_checks arrayには含まれません。これらはまだ未解決です。

繰り返しますが、フィールド名を使用することにより、比較に使用するフィールドをすばやく指定し、コードを非常に読み取り可能に保つことができます。

便宜上、10行目の2番目のリスト理解を使用して、結果の配列から数値を抽出します。

完全を期すために、発行していない小切手がアカウントに対して不正に換金されていないかどうかを確認してください。

>>> [
...     check_id
...     for check_id in cashed_checks["id"]
...     if check_id not in issued_checks["id"]
... ]
[]

生成された空のリストは、ありがたいことに何も存在しないことを示しています。ふぅ!

重複するキー値を扱う

Numpyアレイを結合する前に、キー値が重複していないことを確認することが重要です。これらは、1対多くの関係を作成するため、望ましくない結果を引き起こす可能性があります。たとえば、1対Manyの結合を実行する必要がある場合、重複キーが有効である場合、より良い選択肢は、パンダで merge()関数を使用することです。

以下のコードは、 check_list_duplicates.csv ファイルを使用します。このファイルは、以前に使用したファイルと同じ構造を持っていますが、重複したレコードがあります。

>>> from pathlib import Path
>>> import numpy.lib.recfunctions as rfn

>>> issued_dtypes = [
...     ("id", "i8"),
...     ("payee", "U10"),
...     ("amount", "f8"),
...     ("date_issued", "U10"),
... ]

>>> issued_checks = np.loadtxt(
...     Path("check_list_duplicates.csv"),
...     delimiter=",",
...     dtype=issued_dtypes,
...     skiprows=1,
... )

>>> rfn.find_duplicates(np.ma.asarray(issued_checks))
masked_array(data=[(1344, 'B Johnson', 45.0, '2024-03-22'),
                   (1344, 'B Johnson', 45.0, '2024-03-22')],
             mask=[(False, False, False, False),
                   (False, False, False, False)],
       fill_value=(999999, 'N/A', 1e+20, 'N/A'),
            dtype=[('id', '<i8'), ('payee', '<U10'),
            ⮑ ('amount', '<f8'), ('date_issued', '<U10')])

構造化配列内の重複行を検索するには、.find_duplicates() ヘルパー関数を使用できます。この関数では、マスクされた配列を渡す必要があります。マスクされた配列とは、エントリが欠落しているか無効である可能性がある配列です。この点では配列は問題ありませんが、関数に渡す前にマスクされた配列に変換する必要があります。出力の 19 行目から、重複行があることがわかります。1344 レコードが 2 回出現しています。

それを取り除くには、 np.unique()関数を使用できます。

>>> issued_checks = np.unique(issued_checks, axis=0)
>>> issued_checks
array([(1341, 'K Starmer', 150., '2024-03-29'),
       (1342, 'R Sunak', 175., '2024-03-29'),
       (1343, 'L Truss',  30., '2024-03-29'),
       (1344, 'B Johnson',  45., '2024-03-22'),
       (1345, 'T May',  65., '2024-03-22'),
       (1346, 'D Cameron', 430., '2024-03-22'),
       (1347, 'G Brown', 100., '2024-03-15'),
       (1348, 'T Blair', 250., '2024-03-15'),
       (1349, 'J Major', 500., '2024-03-15'),
       (1350, 'M Thatcher', 220., '2024-03-15')],
      dtype=[('id', '<i8'), ('payee', '<U10'),
      ⮑ ('amount', '<f8'), ('date_issued', '<U10')])

重複した行を削除するには、issued_checks 配列を axis=0 とともに np.unique() に渡し、行を削除するように指示します。これにより、新しい配列が作成され、重複する行が削除され、インスタンスが 1 つだけ残ります。出力を見ると、1344 行が 1 回だけ表示されていることがわかります。

スキルをテストする: 配列を結合する

次の挑戦の時が来ました。この演習を解けるかどうかを確認してください。

航空会社は、乗客に関するデータを 2 つの別々のファイル、passengers.csv ファイルと passports.csv ファイルに保存します。ダウンロード可能な資料に含まれる両方のファイルを見て、その内容をよく理解してください。次に、スキルを活用して次のタスクを解決してください。

タスク 1: 各乗客の名、姓、国籍を含む構造化配列を作成します。

タスク2:リストにパスポートを持っていない乗客がいるかどうかを判断します。

タスク3:リストには、乗客に属さないパスポートがあるかどうかを判断します。

タスク 1 に対する考えられる解決策の 1 つは次のとおりです。

>>> import numpy as np
>>> import numpy.lib.recfunctions as rfn

>>> passenger_dtype = [
...     ("passenger_no", "i8"),
...     ("first_name", "U20"),
...     ("last_name", "U20"),
... ]

>>> passport_dtype = [
...     ("passport_no", "i8"),
...     ("passenger_no", "i8"),
...     ("nationality", "U20"),
... ]

>>> passengers = np.unique(
...     np.loadtxt(
...         "passengers.csv",
...         delimiter=",",
...         dtype=passenger_dtype,
...         skiprows=1,
...     ),
...     axis=0,
... )

>>> passports = np.unique(
...     np.loadtxt(
...         "passports.csv",
...         delimiter=",",
...         dtype=passport_dtype,
...         skiprows=1,
...     ),
...     axis=0,
... )

>>> flight_details = rfn.rec_join(
...     "passenger_no",
...     passengers,
...     passports,
...     jointype="inner",
... )

>>> flight_details[
...     ["first_name", "last_name", "nationality"]
... ]
rec.array([('Olivia', 'Smith', 'British'), ('Amelia', 'Jones', 'British'),
           ('Isla', 'Williams', 'American'),
           ('Ava', 'Taylor', 'American'), ('Ivy', 'Brown', 'Austrian'),
           ('Freya', 'Davies', 'Norwegian'), ('Lily', 'Evans', 'French'),
           ('Florence', 'Wilson', 'German'), ('Mia', 'Thomas', 'Danish'),
           ('Willow', 'Johnson', 'Dutch'), ('Noah', 'Roberts', 'Dutch'),
           ('Oliver', 'Robinson', 'French'),
           ('George', 'Thompson', 'Danish'),
           ('Muhammad', 'Walker', 'Dutch'), ('Leo', 'White', 'British'),
           ('Harry', 'Edwards', 'American'),
           ('Oscar', 'Hughes', 'Spanish'),
           ('Archie', 'Green', 'Norwegian'), ('Henry', 'Hall', 'British')],
          dtype={'names': ['first_name', 'last_name', 'nationality'],
          ⮑ 'formats': ['<U20', '<U20', '<U20'],
          ⮑ 'offsets': [8, 88, 176], 'itemsize': 256})

4行目と10行に示すように、 and pressenger_dtype およびを作成することから始めます。これらの各ストアタプルは、各列のデータ型を定義するために使用されるタプルを使用します。ファイルデータを保持する構造化された配列。各タプルは、フィールド名とデータ型で構成されています。 i8 が整数を指定し、 u20 が21文字の文字列を定義することを思い出すことができます。

16 行目と 26 行目では、各ファイルのデータをそれぞれ passengers 配列と passports 配列に読み込みます。また、np.unique() を使用して重複レコードを確実に削除します。これを忘れると、結果にいくつかの N/A 値が表示されます。各ファイルの先頭行には見出しが含まれているため、スキップしていることに注意してください。

36 行目では、passenger_no フィールドを結合キーとして使用して、passengers 配列と passports 配列の両方を結合します。これは内部結合であり、パスポートを持つ乗客のみが出力に含まれることを意味します。

43行目に、必要なフライトの詳細を表示します。出力には、 Passengers.csv ファイルからの乗客の名前と passports.csv ファイルからの国籍が表示されます。このアプローチは、データを抽出するためのフィールド名を持つという利便性と明確さを利用します。

タスク 2 に対する考えられる解決策の 1 つは次のとおりです。

>>> passengers_without_passports = [
...     passenger
...     for passenger in passengers["passenger_no"]
...     if passenger not in passports["passenger_no"]
... ]

>>> passengers_without_passports
[np.int64(14)]

この質問に答えるには、passengers 配列内の各 passenger_no をループし、配列内に存在しないもののみを返すリスト内包表記を使用します。 パスポート配列。この場合、乗客 14 はパスポートの詳細を提供していません。

タスク 3 に対する考えられる解決策の 1 つは次のとおりです。

>>> passports_without_passengers = [
...     passenger
...     for passenger in passports["passenger_no"]
...     if passenger not in passengers["passenger_no"]
... ]

>>> passports_without_passengers
[np.int64(21)]

この質問に答えるには、再びリスト内包表記を使用しますが、今回は passports 配列内の各 passenger_no をループし、そうでないもののみを返します。passengers 配列に表示されます。この場合、存在しない乗客 21 のパスポートが明らかになります。

次のセクションでは、階層データを分析する方法を学びます。

NumPy 例 3: 階層データの分析とグラフ化

階層データは、さまざまなレベルで構成されるデータであり、各レベルはすぐ上下のレベルにリンクされています。多くの場合、ツリー図を使用して描かれ、さまざまなレベルが親子の関係を持っていると説明されています。

たとえば、いくつかの部門を持つ組織があり、各部門には複数のスタッフが含まれています。組織、その部門のデータ、および各部門で働くスタッフメンバーのデータとの間には階層的な関係があります。

構造化された配列は、ラベルでデータを参照できるため、階層データを結合するのに適しています。階層データでnumpyを使用するには、単一の配列に統合できます。

このセクションでは、株式ポートフォリオの分析について見ていきます。株式ポートフォリオは、さまざまな企業が保有する株式の集合に与えられた名前です。ポートフォリオを作成することは、投資リスクを分散するのに役立つため、投資家にとって賢明な戦略です。その考えは、一部の株式で発生した損失は他の株式の利益によって相殺されるということです。

株式ポートフォリオは複数の投資で構成されており、それぞれに毎日の値動きの独自のコレクションがあるため、株式ポートフォリオには階層データが含まれています。このデータを分析することで、ポートフォリオのパフォーマンスがどの程度優れているかを確認できます。

空白の配列を作成します

このセクションで使用されるデータは、いくつかの階層データをシミュレートします。株式を保有するさまざまな企業のリストを含むファイルを維持しているとします。この例では、 portfolio.csv ファイルにこの情報をダウンロードに含まれています。ここには以下に示されています:

Company,Sector
Company_A,technology
Company_B,finance
Company_C,healthcare
Company_D,technology
Company_E,finance
Company_F,healthcare

company 列には会社名が含まれていますが、セクター列には会社が属するセクターが含まれています。

1 週間にわたって毎日、関心のある各企業の株価をダウンロードし、portfolio という名前の構造化された NumPy 配列に追加します。毎日の価格データは、share_prices-n.csv という名前の一連の個別のファイルにあります。n は 1 ~ 5 の数字です。たとえば、share_prices-1.csv には月曜日の価格が含まれ、share_prices-2.csv には火曜日の価格が含まれます。

サンプル株価ファイルを以下に示します。

Company,mon
Company_A,100.5
Company_B,200.1
Company_C,50.3
Company_D,110.5
Company_E,200.1
Company_F,55.3

この share_prices-1.csv ファイルには、2つの列があります。 company 列には企業の名前が表示されますが、 mon 列には月曜日の各会社の価格が表示されます。残りのファイルは、異なる日の列を除いて、同様のパターンに従います。

このデータを分析するには、portfolio 配列に7 つのフィールドが必要です。会社名とその会社が属するセクターに加えて、各会社の日次価格を保持するための 5 つのフィールドも必要です。最初の 2 つのフィールドは文字列になり、残りは浮動小数点になります。

以下に示すコードは、初期ポートフォリオ配列を作成します。

>>> import numpy as np
>>> from pathlib import Path

>>> days = ["mon", "tue", "wed", "thu", "fri"]
>>> days_dtype = [(day, "f8") for day in days]
>>> company_dtype = [("company", "U20"), ("sector", "U20")]

>>> portfolio_dtype = np.dtype(company_dtype + days_dtype)
>>> portfolio = np.zeros((6,), dtype=portfolio_dtype)
>>> portfolio
array([('', '', 0., 0., 0., 0., 0.), ('', '', 0., 0., 0., 0., 0.),
       ('', '', 0., 0., 0., 0., 0.), ('', '', 0., 0., 0., 0., 0.),
       ('', '', 0., 0., 0., 0., 0.), ('', '', 0., 0., 0., 0., 0.)],
      dtype=[('company', '<U20'), ('sector', '<U20'), ('mon', '<f8'),
      ⮑ ('tue', '<f8'), ('wed', '<f8'), ('thu', '<f8'), ('fri', '<f8')])

前と同様に、numpy ライブラリと pathlib ライブラリの両方を使用するため、これらを 1 行目と 2 行目でインポートします。

portfolio 配列の各フィールドのフィールド名とデータ型を定義するには、まず 4、5、6 行目に示されている 3 つのリストを作成します。 days_dtype リストには、4 行目で作成した days 配列の値ごとに 1 つずつ、浮動小数点数を表すデータ型 f8 を持つ一連のタプルが含まれています。 company_dtype リストには、portfolio.csv ファイルの会社データの定義が含まれています。

portfolio 配列のデータ型を定義する実際のデータ型オブジェクトを作成するには、company_dtype リストと days_dtype リストを連結します。次に、8 行目に示すように、np.dtype() 関数を使用して結果を dtype オブジェクトにキャストします。

dtype オブジェクトは、 np.zeros()機能に dtype パラメーターに供給されます。また、(6、)の形状で配列を構成して、各共有のデータに個別の行を提供します。これにより、出力に示すように、最初の2つのフィールドに空の文字列と残りのゼロを含む配列が生成されます。

配列の居住

必要なすべてのデータを保存するのに十分な大きさのアレイを作成したので、次のステップはそれを入力し始めることです。まず、 portfolio.csv に保存されている企業の詳細を追加します。

>>> companies = np.loadtxt(
...     Path("portfolio.csv"),
...     delimiter=",",
...     dtype=company_dtype,
...     skiprows=1,
... ).reshape((6,))

>>> portfolio[["company", "sector"]] = companies
>>> portfolio
array([('Company_A', 'technology', 0., 0., 0., 0., 0.),
       ('Company_B', 'finance', 0., 0., 0., 0., 0.),
       ('Company_C', 'healthcare', 0., 0., 0., 0., 0.),
       ('Company_D', 'technology', 0., 0., 0., 0., 0.),
       ('Company_E', 'finance', 0., 0., 0., 0., 0.),
       ('Company_F', 'healthcare', 0., 0., 0., 0., 0.)],
      dtype=[('company', '<U20'), ('sector', '<U20'), ('mon', '<f8'),
      ⮑ ('tue', '<f8'), ('wed', '<f8'), ('thu', '<f8'), ('fri', '<f8')])

loadtxt()関数を再度使用します。今回は、 portfolio.csv のデータを Companies という名前の構造化された配列に追加します。 .reshape((6、))は、6行目で使用されて、以前に作成したポートフォリオ配列と同じ形状を提供することに注意してください。これは、 Companies をポートフォリオに挿入するために必要です。

8 行目で挿入が行われます。 2 つの companies 配列フィールドは、portfoliocompany フィールドと sector フィールドとして挿入されます。出力。

残っていることは、さまざまな1日の株価の追加だけです。これを行うためのコードを以下に示します。

>>> share_prices_dtype = [("company", "U20"),("day", "f8"),]

>>> for day, csv_file in zip(
...     days, sorted(Path.cwd().glob("share_prices-?.csv"))
... ):
...     portfolio[day] = np.loadtxt(
...         csv_file.name,
...         delimiter=",",
...         dtype=share_prices_dtype,
...         skiprows=1,
...     )["day"]

>>> portfolio
array([('Company_A', 'technology', 100.5, 101.2, 102. , 101.8, 112.5),
       ('Company_B', 'finance', 200.1, 199.8, 200.5, 201. , 200.8),
       ('Company_C', 'healthcare',  50.3,  50.5,  51. ,  50.8,  51.2),
       ('Company_D', 'technology', 110.5, 101.2, 102. , 111.8,  97.5),
       ('Company_E', 'finance', 200.1, 200.8, 200.5, 211. , 200.8),
       ('Company_F', 'healthcare',  55.3,  50.5,  53. ,  50.8,  52.2)],
      dtype=[('company', '<U20'), ('sector', '<U20'), ('mon', '<f8'),
      ⮑ ('tue', '<f8'), ('wed', '<f8'), ('thu', '<f8'), ('fri', '<f8')])

まず、Daily share_prices - ファイルのそれぞれ内に2つのフィールドを定義するリストを作成します。このデータをメインポートフォリオ配列に追加するには、それぞれを反復するループをもう一度作成しますが、今回はpythonの組み込み zip() を使用して行います。 関数。先ほど学んだように、ファイルは順番に処理されます。

この場合、 zip() days 以前に定義したリストと、処理した各ファイルを渡します。これにより、一連のタプルが生成されます。1つは毎日1つずつ、ファイルペアが見つかります。タプルの毎日はループの day 変数に割り当てられ、各ファイルは csv_file 変数に割り当てられます。

ループの本体内で、各ファイルは再び np.loadtxt() を使用して読み取られますが、今回はファイル全体を保持するのではなく、その day フィールド データのみが保存されます。 。ループの 1 回目では、このデータは portfolio 配列の mon フィールドに挿入され、2 回目では tue フィールドに挿入されます。等々。これを行うには、読み取られたデータを異なる日ごとに portfolio[day] に割り当てます。

配列の最終バージョンには、会社名とそれが属するセクターが含まれています。各記録の最後の5つの数字は、月曜日から金曜日の株価のセントです。

階層データを構造化配列に結合したので、簡単にするためにフィールド名を使用して分析できます。さらに詳しく調べるために 1 つの会社を抽出するとします。

>>> portfolio[portfolio["company"] == "Company_C"]
array([('Company_C', 'healthcare', 50.3, 50.5, 51., 50.8, 51.2)],
      dtype=[('company', '<U20'), ('sector', '<U20'),
      ⮑ ('mon', '<f8'), ('tue', '<f8'), ('wed', '<f8'),
      ⮑ ('thu', '<f8'), ('fri', '<f8')])

ここでは、company 列で単一の会社を見つけて抽出します。これを行うには、portfolio["company"] == "Company_C" を使用します。これにより、company 列の値が "Company_C" と一致する行が選択されます。 >。この方法は、インデックスを使用して行を選択するよりもはるかに直感的です。

同様に、ポートフォリオのテクノロジー企業が金曜日にどのように機能するかを確認したい場合は、これらの数値を選択することもできます。

>>> portfolio[portfolio["sector"] == "technology"]["fri"]
array([112.5,  97.5])

テクノロジーレコードを表示するには、 Portfolio ["sector"] == "Technology" を使用します。次に、金曜日の記録のみを見るために、 ["fri"] を使用してそれらを除外します。

各テクノロジー企業の株式を 250 株所有しているとします。週末にどれくらいの価値があるかを確認したいと思うかもしれません:

>>> portfolio[portfolio["sector"] == "technology"]["fri"] * 250 * 0.01
array([281.25, 243.75])

これを行うには、すでに学んだテクニックを使用して、ポートフォリオの各テクノロジー部分の金曜日の数値を選択します。次に、数値に 250 を掛けます。これは、所有する株式数です。最後に、結果に 0.01 を掛けて金額をドルに変換します。 純資産が必要な場合は、 sum()を使用するだけです。

>>> sum(portfolio[portfolio["sector"] == "technology"]["fri"] * 250 * 0.01)
np.float64(525.0)

ポートフォリオのテクノロジー部分は525.00ドルの価値があります。

ご覧のとおり、構造化配列を使用すると、非常に直感的な方法でデータにアクセスできます。さらに進めるために、Matplotlib チャートの基盤として構造化配列を使用するときに、このアプローチを使用することもできます。これが次に行うことです。

データのチャート

ポートフォリオの technology 部分の分析をチャートに表示するとします。繰り返しますが、構造化配列を操作しているため、コードは直感的になります。

>>> import matplotlib.pyplot as plt

>>> tech_mask = portfolio["sector"] == "technology"
>>> tech_sector = portfolio[tech_mask]["company"]
>>> tech_valuation = portfolio[tech_mask]["fri"] * 250 * 0.01

>>> (
...     plt.bar(x=tech_sector, height=tech_valuation, data=tech_valuation)[0]
...     .set_color("g")
... )
>>> plt.xlabel("Tech Companies")
>>> plt.ylabel("Friday Price ($)")
>>> plt.title("Tech Share Valuation ($)")
>>> plt.show()

最初に tech_mask ヘルパー配列を作成します。次に、プロットで使用される2つの配列を作成します。 4行目で定義されている tech_sector 配列には、各 tech_sector Companyの会社名が含まれています。 5行目で定義されている tech_valuation 配列には、各 tech_sector 会社の金曜日の評価が含まれています。

7 行目から 10 行目は棒グラフを作成します。 X 軸にはバーの作成に使用された tech_sector 会社名が含まれ、height パラメーターにはその評価額が含まれます。 tech_valuation の値がプロットされます。 11、12、13、14 行目は、軸のラベルを作成し、グラフにタイトルを付けます。最後に、プロットが表示されます。

上記のコードを Jupyter Notebook で実行する場合、plt.show() を使用する必要はありません。標準の Python REPL で実行すると、11、12、13 行目にオブジェクト参照が表示されます。これらは無視して構いません。わかりやすくするために出力から削除されています。

結果のプロットを以下に示します。

ご覧のとおり、2 つのうちの中では Company_A のほうが、わずかではありますが、より良い成績を収めているようです。

スキルのテスト:階層データの分析とチャート

次に進む前に、3 番目の課題を次に示します。

分析のために、年間の毎月の毎月の平均気温を照合するように求められています。データは、 london_temperatures.csv new_york_temperatures.csv 、および rome_temperatures.csv ファイルに保存されます。

学んだスキルを使用して、各月の 4 つのデータ値を含む適切なフィールド名とデータ型を備えた構造化された weather_data 配列を作成します。これらの最初のものには月が含まれ、他の 3 つは各都市の月の気温が含まれている必要があります。

構造化された配列を使用して、ラインプロットの3つの都市の各毎月の温度をプロットします。

配列を作成するための可能な解決策の1つは、次のとおりです。

>>> import numpy as np
>>> from pathlib import Path

>>> cities = ["london", "new_york", "rome"]
>>> cities_dtype = [(city, "i8") for city in cities]
>>> city_files_dtype = [("month", "U20"), ("temp", "i8")]
>>> weather_data_dtype = np.dtype([("month", "U20")] + cities_dtype)

>>> weather_data = np.zeros((12,), dtype=weather_data_dtype)
>>> weather_data
array([('', 0, 0, 0), ('', 0, 0, 0), ('', 0, 0, 0), ('', 0, 0, 0),
       ('', 0, 0, 0), ('', 0, 0, 0), ('', 0, 0, 0), ('', 0, 0, 0),
       ('', 0, 0, 0), ('', 0, 0, 0), ('', 0, 0, 0), ('', 0, 0, 0)],
      dtype=[('month', '<U20'), ('london', '<i8'),
      ⮑ ('new_york', '<i8'), ('rome', '<i8')])

>>> for city in cities:
...     temps = np.loadtxt(
...         Path(f"{city}_temperatures.csv"),
...         delimiter=",",
...         dtype=city_files_dtype,
...     )
...     weather_data[["month", city]] = temps
...

>>> weather_data
array([('Jan',  5,  2,  8), ('Feb',  7,  2,  9), ('Mar',  9,  4, 12),
       ('Apr', 11, 11, 14), ('May', 14, 16, 21), ('Jun', 16, 22, 23),
       ('Jul', 19, 25, 26), ('Aug', 19, 24, 24), ('Sep', 17, 20, 22),
       ('Oct', 13, 14, 18), ('Nov', 10, 12, 13), ('Dec',  7,  9, 10)],
      dtype=[('month', '<U20'), ('london', '<i8'),
      ⮑ ('new_york', '<i8'), ('rome', '<i8')])

まず、データを受け入れるために正しいサイズの配列を事前に作成します。前と同様に、9 行目と 10 行目で weather_data 配列を生成する前に、リストとリスト内包表記を使用して各フィールドとそのデータ型を定義します。結果は 11 行目から 14 行目で確認できます。month フィールドは空の文字列で初期化され、各整数フィールドは 0 に設定されます。

17 行目で、個々の都市のループを開始します。ロンドンの場合、london_tempers.csv ファイルを temps 配列に読み取り、23 行目でそのデータを month に割り当てます。 weather_data 配列の >london フィールド。ニューヨークとローマのデータは、同じ方法で読み取られて追加されます。月のラベルは都市ごとに上書きされます。ただし、すべてのデータセットで同じである限り、これは問題ありません。

27 行目から 32 行目は完全な配列を示しています。折れ線グラフの 1 つの可能性は次のとおりです。

>>> import matplotlib.pyplot as plt

>>> plt.plot(weather_data["month"], weather_data["london"])
>>> plt.plot(weather_data["month"], weather_data["new_york"])
>>> plt.plot(weather_data["month"], weather_data["rome"])

>>> plt.ylabel("Temperature (C)")
>>> plt.xlabel("Month")
>>> plt.title("Average Monthly Temperatures")
>>> plt.legend(["London", "New York", "Rome"])
>>> plt.show()

プロットを構築するには、X軸に沿って数か月をプロットし、Y軸に沿った3つの都市の温度のそれぞれに対して別のラインを作成します。次に、両方のチャート軸にラベルを追加し、タイトルと凡例を追加します。

あなたの最終出力は次のように見えるはずです:

ご覧のとおり、3 つの都市のうち、ローマは一貫して気温が高いです。

最後のセクションでは、NumPy の主な効率性の 1 つと、それを活用する関数の作成方法について学びます。

Numpy例4:独自のベクトル化された関数を書く

NumPy の効率の 1 つは、プログラマが各行または要素を手動でループする遅いループを作成することなく、配列全体の計算を実行できることです。代わりに、NumPy は基礎となる C 言語を使用して配列全体の計算を実行します。これはベクトル化と呼ばれます。

この最後のセクションでは、 full_portfolio.csv 以下のファイルを使用します。

Company,Sector,Mon,Tue,Wed,Thu,Fri
Company_A,technology,100.5,101.2,102,101.8,112.5
Company_B,finance,200.1,199.8,200.5,201.0,200.8
Company_C,healthcare,50.3,50.5,51.0,50.8,51.2
Company_D,technology,110.5,101.2,102,111.8,97.5
Company_E,finance,200.1,200.8,200.5,211.0,200.8
Company_F,healthcare,55.3,50.5,53.0,50.8,52.2

このデータは、前のセクションで使用されたファイルの合併であるため、おなじみに見えます。各列の見出しには、以前と同じ意味があります。

以下のコードは、アクションのベクトル化を示しています。

>>> import numpy as np
>>> from pathlib import Path

>>> share_dtypes = [
...     ("company", "U20"),
...     ("sector", "U20"),
...     ("mon", "f8"),
...     ("tue", "f8"),
...     ("wed", "f8"),
...     ("thu", "f8"),
...     ("fri", "f8"),
... ]

>>> portfolio = np.loadtxt(
...     Path("full_portfolio.csv"),
...     delimiter=",",
...     dtype=share_dtypes,
...     skiprows=1,
... )

>>> portfolio["fri"] - portfolio["mon"]
array([ 12. ,   0.7,   0.9, -13. ,   0.7,  -3.1])

portfolio 構造化配列を構築した後、株式の 1 週間のパフォーマンスを確認することにしました。これを行うには、月曜日の株価を含む配列と金曜日の株価を含む配列の 2 つの配列を選択します。週ごとの変化を表示するには、金曜日の価格から月曜日の価格を差し引きます。

21 行目では、一方の配列をもう一方の配列から減算しているにもかかわらず、NumPy は配列の個々の要素を個別にループするコードを記述することなく減算していることに注目してください。これがベクトル化です。

ここで、分析している週に1%以上増加した株式に10%の追加ボーナスが得られるとします。ボーナスを含めて利益を見つけるには、2つのケースを考慮する必要があります。ボーナスを獲得した株式とそうでない株式。これを行うには、次のことを試すことができます。

>>> def profit_with_bonus(first_day, last_day):
...     if last_day >= first_day * 1.01:
...         return (last_day - first_day) * 1.1
...     else:
...         return last_day - first_day
...
>>> profit_with_bonus(portfolio["mon"], portfolio["fri"])
Traceback (most recent call last):
  ...
ValueError: The truth value of an array with more than one element is ambiguous.
⮑ Use a.any() or a.all()

1 行目から 5 行目では、株価が 1 週間でどのように上昇したかに基づいて利益を返す profit_with_bonus() という名前の関数を定義しました。利益が 1% を超えている場合は、ボーナスとしてさらに 10% を追加します。

7行目で関数を呼び出すと、渡された配列を解釈できないため、 valueError 例外が発生します。 2つの番号を渡すと機能します。この関数を配列で動作させるには、読み進めてください。

np.vectorize() によるベクトル化機能の追加

frite_with_bonus()機能を配列で動作させるには、 vectorized関数に変える必要があります。 これを行う1つの方法は、 np.vectorize()関数を使用することです。この関数は、元の関数のバージョンを返しますが、スカラーの代わりに入力として配列を使用します。

以下に示すコードは、関数をベクトル化された関数に変換します。

>>> def profit_with_bonus(first_day, last_day):
...     if last_day >= first_day * 1.01:
...         return (last_day - first_day) * 1.1
...     else:
...         return last_day - first_day
...
>>> vectorized_profit_with_bonus = np.vectorize(profit_with_bonus)
>>> vectorized_profit_with_bonus(portfolio["mon"], portfolio["fri"])
array([ 13.2 ,   0.7 ,   0.99, -13.  ,   0.7 ,  -3.1 ])

profit_with_bonus() 関数をベクトル化バージョンにするには、それを 7 行目の np.vectorize() 関数に渡します。その後、ベクトル化バージョンが vectorized_profit_with_bonus の名前。

8行目では、新しい関数を呼び出し、分析するアレイを渡します。この例では、出力は追加のボーナスを含む利益を示しています。数字を以前に行った簡単な利益計算と比較すると、第1および第3の会社で株式のボーナスが得られたことに気付くでしょう。 1〜5行目で元の関数を変更する必要がないことに注意してください。

np.vectorize() の使用に関する最後のポイントは、必要に応じて元の関数が引き続き使用できることです。

>>> in_profit(3, 5)
2.2

今回は、単一の値が返されます。 5-3 2 であり、 2 3 の1%以上であるため、10%を追加します。 2.2 を取得します。

@np.vectorize を使用してベクトル化機能を追加します

これまで見てきたように、np.vectorize() 関数は 2 番目の関数を作成します。これは、元のバージョンを使用する必要がある場合に引き続き使用できることを意味します。代わりに、 np.vectorize をデコレータとして使用することもできます。

>>> @np.vectorize
... def profit_with_bonus(first_day, last_day):
...     if last_day >= first_day * 1.01:
...         return (last_day - first_day) * 1.1
...     else:
...         return last_day - first_day
...
>>> profit_with_bonus(portfolio["mon"], portfolio["fri"])
array([ 13.2 ,   0.7 ,   0.99, -13.  ,   0.7 ,  -3.1 ])

@np.vectorize を1行目の関数に適用することにより、 profit_with_bonus()関数はベクトル化バージョンに変換されます。この新しいバージョンは、前に見た vectorized_profit_with_bonus()関数と同じように機能します。それを使用するには、通常どおりを呼び出しますが、アレイを渡すようにしてください。これにより、以前と同じ利益アレイが返されます。

np.vectorize()関数によって作成されたバージョンとは異なり、 frotic_with_bonus()の元のスカラーバージョンは存在しなくなります。ただし、2つのスカラーを渡すと、引き続き機能しますが、結果を配列として返します。

>>> in_profit(3, 5)
array(2.2)

ご覧のとおり、出力は今回は配列です。

np.where() による既存のベクトル化機能の使用

利用可能なnumpy関数の多くは、すでにベクトル化をサポートしています。ドキュメントを参照して、ニーズに合わせてすでに利用できる機能があるかどうかを確認することをお勧めします。たとえば、株式からの利益を見つけるには、 np.where()関数を使用して、独自の関数を書く代わりにボーナスケースと通常の利益ケースの両方を説明できます。

>>> np.where(
...     portfolio["fri"] > portfolio["mon"] * 1.01,
...     (portfolio["fri"] - portfolio["mon"]) * 1.1,
...     portfolio["fri"] - portfolio["mon"],
... )
array([ 13.2 ,   0.7 ,   0.99, -13.  ,   0.7 ,  -3.1 ])

今回は、状態を np.where()に渡します。元の function、 .where()はベクトル化をネイティブにサポートしています。この場合、2つの配列間の比較を渡すことにより、 .where()を使用します。また、2つの計算された配列を渡します。 1つ目は10%の追加ボーナスで利益を表し、2つ目は平易な利益です。

次に、 np.where()は各要素の条件を評価し、2つの配列のいずれかから対応する要素を選択します。条件が true の場合、最初の配列の要素を使用し、 false の場合、2番目の配列から要素を選択します。

スキルのテスト:ベクトル化された関数の書き込み

最後のエクササイズチャレンジに取り組みましょう。もうすぐ終わります。

前回の課題で作成した weather_data 配列から、最低気温と最高気温を含む 2 つの配列を作成するように求められました。まだ行っていない場合は、先に進む前に、「階層データの分析とグラフ作成」演習のソリューションの最初のコード ブロックを実行して、weather_data 配列を構築します。

次に、 find_min_max()という名前の関数を書き込みます。たとえば、整数など、3つのスカラー値を受け入れ、最大値と最小値を返します。次に、@np.vectorize または np.vectorize()のいずれかを使用して、機能を強化してnumpy配列で動作します。機能のベクトル化バージョンを元の名前で呼び出すことができることを確認してください。

出発点として、あなたの最初の関数は次のようになる可能性があります:

>>> def find_min_max(first, second, third):
...     min_temp = min(first, second, third)
...     max_temp = max(first, second, third)
...     return min_temp, max_temp

>>> find_min_max(2, 1, 3)
(1, 3)

最初の find_min_max() 関数は 3 つのスカラー値を受け取り、最小値と最大値を返します。 weather_data 配列のデータを使用して試しても、機能しません。

>>> london_temps = weather_data["london"]
>>> new_york_temps = weather_data["new_york"]
>>> rome_temps = weather_data["rome"]

>>> find_min_max(london_temps, new_york_temps, rome_temps)
>>> # ...
ValueError: The truth value of an array with more than one element is ambiguous.

ご覧のとおり、ValueError 例外が発生しました。配列を操作できるように更新する必要があります。

@np.vectorize を使用した可能なソリューションは次のとおりです。

>>> @np.vectorize
... def find_min_max(first, second, third):
...     min_temp = min(first, second, third)
...     max_temp = max(first, second, third)
...     return min_temp, max_temp

>>> london_temps = weather_data["london"]
>>> new_york_temps = weather_data["new_york"]
>>> rome_temps = weather_data["rome"]

>>> find_min_max(london_temps, new_york_temps, rome_temps)
(array([ 2,  2,  4, 11, 14, 16, 19, 19, 17, 13, 10,  7]),
⮑ array([ 8,  9, 12, 14, 21, 23, 26, 24, 22, 18, 13, 10]))

find_min_max() 関数のベクトル化バージョンを使用するには、それを @np.vectorize で修飾します。ベクトル化された関数を呼び出すには、気象データの 3 つの配列を直接渡します。デコレーターを使用すると、配列を入力として理解し、出力として生成できます。この関数は、組み込みの min() 関数と max() 関数を使用して各月の 3 つの要素をそれぞれ分析し、結果を 2 つの配列で返します。

別のソリューションは、元の関数に戻し、 np.vectorize()を使用することです。

>>> def find_min_max(first, second, third):
...     min_temp = min(first, second, third)
...     max_temp = max(first, second, third)
...     return min_temp, max_temp

>>> london_temps = weather_data["london"]
>>> new_york_temps = weather_data["new_york"]
>>> rome_temps = weather_data["rome"]

>>> find_min_max = np.vectorize(find_min_max)
>>> find_min_max(london_temps, new_york_temps, rome_temps)
(array([ 2,  2,  4, 11, 14, 16, 19, 19, 17, 13, 10,  7]),
⮑ array([ 8,  9, 12, 14, 21, 23, 26, 24, 22, 18, 13, 10]))

find_min_max() 関数のベクトル化バージョンを使用するには、それを np.vectorize() に渡し、出力を変数 find_min_max に代入し直します。 に移動すると、元の名前を使用してベクトル化されたバージョンを呼び出すことができます。このベクトル化バージョンを呼び出すには、気象データの 3 つの配列を直接渡します。