NumPy初心者必見!import numpy as npの使い方


NumPy初心者必見!import numpy as npの使い方を徹底解説

Pythonを使ったデータ分析、機械学習、科学技術計算の世界に足を踏み入れた皆さん、こんにちは!

おそらく、インターネット上のチュートリアルやサンプルコードで、import numpy as np という呪文のような一行を頻繁に見かけることでしょう。この一行が何をしているのか、なぜ必要なのか、そしてこれを使うことで何ができるようになるのか、最初は全く理解できないかもしれません。

でも、安心してください!このimport numpy as npは、NumPyという強力なライブラリをPythonで使うための「おまじない」のようなものです。そして、このNumPyこそが、Pythonを数値計算やデータ処理において世界中で愛される言語に押し上げた立役者の一つなのです。

この記事では、Python初心者の皆さん、そしてこれからNumPyを使ってデータ分析や数値計算を始めたいと考えている皆さんに向けて、import numpy as npが持つ意味、NumPyの基本、そしてなぜこれが必要なのかを、徹底的かつ分かりやすく解説していきます。

この記事を読み終える頃には、あなたは自信を持って import numpy as np とタイプし、NumPyの素晴らしい機能を使い始めることができるようになっているはずです。さあ、NumPyの世界への扉を開きましょう!


目次

  1. はじめに:なぜNumPyが必要なのか?Pythonリストの限界
  2. NumPyとは何か?数値計算のための強力なライブラリ
  3. import文の基本:Pythonで外部ライブラリを使う方法
  4. import numpy as npを分解する
    • import numpy: モジュールをインポートする
    • as np: エイリアス(別名)を付ける
    • なぜas npを使うのか?その理由とメリット
  5. NumPyの核心:ndarray(N次元配列)
    • ndarrayとは?その特徴とメリット
    • 配列の作成方法:np.array(), np.zeros(), np.ones(), np.full(), np.arange(), np.linspace(), ランダムな配列
    • 配列の基本情報:shape, dtype, size, ndim
    • データ型(dtype)の重要性
  6. NumPy配列の基本操作
    • 要素ごとの演算(ベクトル化)
    • ブロードキャスティング(Broadcasting)とは?
    • 比較演算子と真偽値配列
    • ユニバーサル関数(ufunc):様々な数学関数
    • 集約関数(Aggregation):合計、平均、最大値、最小値など
    • axisパラメータの理解
  7. NumPy配列のインデックス付けとスライス
    • 基本的なインデックス付け(1次元、2次元)
    • スライス:配列の一部を取り出す
    • 真偽値インデックス付け(Boolean Indexing):条件を満たす要素の選択
    • ファンシーインデックス付け(Fancy Indexing):特定のインデックスによる選択
  8. 配列の形状操作(Reshaping and Manipulating)
    • reshape():配列の形状を変更する
    • ravel() / flatten():配列を1次元にする
    • transpose():転置
    • 配列の結合と分割
  9. NumPyが高速な理由:ベクトル化と内部実装
  10. NumPyの典型的な使用例
  11. まとめ:NumPyとimport numpy as npを使いこなすために

1. はじめに:なぜNumPyが必要なのか?Pythonリストの限界

Pythonのリストは非常に柔軟で便利です。数値、文字列、 bahkan 別のリストなど、様々な型の要素を混在させることができます。しかし、数値計算、特に大量の数値データを扱う場面では、Pythonのリストにはいくつかの限界があります。

考えてみてください。例えば、2つのリスト[1, 2, 3][4, 5, 6]があり、これらを要素ごとに足し合わせたいとします。つまり、結果として[1+4, 2+5, 3+6]、つまり[5, 7, 9]というリストを得たい場合です。

Pythonのリストでこれを実現するにはどうすればよいでしょうか?

“`python
list1 = [1, 2, 3]
list2 = [4, 5, 6]

要素ごとに足し合わせたい

期待する結果: [5, 7, 9]

Pythonの標準的な方法(ループやリスト内包表記)

result_list = []
for i in range(len(list1)):
result_list.append(list1[i] + list2[i])

print(result_list) # 出力: [5, 7, 9]

またはリスト内包表記で少し簡潔に

result_list_comp = [list1[i] + list2[i] for i in range(len(list1))]
print(result_list_comp) # 出力: [5, 7, 9]
“`

確かにこれで目的は達成できます。しかし、この方法は以下の点で効率が悪いです。

  • 速度: 要素数が非常に多くなった場合(例えば100万個、1億個)、ループやリスト内包表記を使った処理は時間がかかります。Pythonのループはインタープリタのオーバーヘッドがあり、コンパイルされたコードに比べて遅い傾向があります。
  • メモリ効率: Pythonのリストは各要素を個別のオブジェクトとして管理するため、数値だけでも多くのメモリを消費します。また、同じ型の数値だけを連続して格納するようには最適化されていません。
  • 機能: 要素ごとの四則演算のような単純な操作でも、明示的にループを書く必要があります。行列の積や統計量の計算(平均、標準偏差など)のような複雑な操作をするためには、さらに多くのコードを書く必要が出てきます。

例えば、リスト全体に一斉に5を足したい、リスト全体の平均値を計算したい、といった操作も、リストでは直接的にはできません。

“`python
my_list = [10, 20, 30]

リスト全体に5を足したい (期待: [15, 25, 35])

これはエラーになる!

print(my_list + 5) # TypeError: can only concatenate list (not “int”) to list

ループで書く必要がある

added_list = [x + 5 for x in my_list]
print(added_list) # 出力: [15, 25, 35]

平均値を計算したい

print(sum(my_list) / len(my_list)) # 出力: 20.0

これはシンプルですが、統計関数としては基本的なものしかない

“`

このように、大量の数値データを扱う際や、複雑な数値計算を行う際には、Pythonの標準的なリストだけでは力不足になることが分かります。ここでNumPyの出番です!

2. NumPyとは何か?数値計算のための強力なライブラリ

NumPy(ナンパイ)は、”Numerical Python” の略です。その名の通り、Pythonで効率的な数値計算を行うための、デファクトスタンダード(事実上の標準)となっているライブラリです。

NumPyの最も重要な特徴は、N次元配列オブジェクト ndarray(n-dimensional array) を提供することです。このndarrayは、Pythonのリストが抱える課題を解決するために設計されています。

ndarrayは:

  • 高速: 大量のデータに対する操作が、Pythonのリストやループに比べて圧倒的に高速です。これは、NumPyの多くの処理がCやFortranといった低レベル言語で書かれており、コンパイル済みで実行されるためです。また、データをメモリ上に連続的に配置するため、効率的なアクセスが可能です。
  • メモリ効率が良い: 同じデータ型の要素だけを格納することに特化しており、Pythonのリストに比べてメモリ使用量が少なくなります。
  • 便利な関数: 配列全体や要素ごとの演算(ベクトル化)、数学関数、線形代数、フーリエ変換、乱数生成など、数値計算やデータ分析に必要な多くの関数が豊富に用意されています。

Pythonでデータサイエンスや機械学習を学ぶ上で、NumPyは避けて通れない存在です。Pandas、Matplotlib、Scikit-learn、TensorFlow、PyTorchといった他の主要なライブラリも、内部的にNumPyを利用しているか、NumPy配列をデータの標準形式として扱っています。

つまり、NumPyを理解することは、Pythonで高度なデータ処理を行うための基礎中の基礎なのです。

3. import文の基本:Pythonで外部ライブラリを使う方法

さて、いよいよ本題に近づいてきました。NumPyのような外部ライブラリを使うためには、まずPythonのコードの中で「使いたい」という意思表示をする必要があります。そのための構文が import 文です。

Pythonのコードは、通常、.pyという拡張子を持つファイルに書かれます。これらのファイルをモジュールと呼びます。関連するモジュールをまとめたものがパッケージです。NumPyは、多数のモジュールを含む大きなパッケージです。

import文を使うことで、別のファイルやパッケージで定義されている関数やクラス、変数などを、現在のコードから利用できるようになります。

import文の基本的な形はいくつかあります。

  1. モジュール全体をインポートする:
    python
    import モジュール名

    この方法でインポートすると、モジュール内の要素にアクセスする際は、モジュール名.要素名のように、モジュール名をプレフィックスとして付ける必要があります。

    例: mathモジュールで平方根を計算する関数sqrtを使う場合
    python
    import math
    print(math.sqrt(16)) # 出力: 4.0

  2. モジュール全体をインポートし、エイリアス(別名)を付ける:
    python
    import モジュール名 as エイリアス名

    この方法でインポートすると、モジュール内の要素にアクセスする際に、元のモジュール名ではなく、付けたエイリアス名をプレフィックスとして使用できます。これは、モジュール名が長かったり、タイプするのが面倒だったりする場合によく使われます。

    例: matplotlib.pyplotモジュールにpltというエイリアスを付ける場合
    “`python
    import matplotlib.pyplot as plt

    これにより、plt.plot() のように短く書ける

    “`

  3. モジュールから特定の要素だけをインポートする:
    python
    from モジュール名 import 要素名
    # または複数の要素
    # from モジュール名 import 要素名1, 要素名2

    この方法でインポートすると、指定した要素をモジュール名を付けずに直接使用できます。

    例: mathモジュールからsqrt関数だけをインポートする場合
    python
    from math import sqrt
    print(sqrt(25)) # 出力: 5.0

  4. モジュールから全ての要素をインポートする(非推奨):
    python
    from モジュール名 import *

    この方法でインポートすると、モジュール内の全ての公開されている要素をモジュール名を付けずに直接使用できます。しかし、どの要素がどこから来たのか分かりにくくなったり、異なるモジュール間で同じ名前の要素があった場合に衝突(名前の競合)が発生したりする可能性があるため、特別な理由がない限り推奨されません

4. import numpy as npを分解する

さて、いよいよおなじみのこの一行を詳しく見ていきましょう。

import numpy as np

これは、先ほど紹介したimport モジュール名 as エイリアス名の形式に従っています。

  • import numpy:
    これはPythonに「numpyという名前のモジュール(あるいはパッケージ)を使いたい」と伝えています。Pythonは、numpyという名前のモジュールがどこにあるかを探しに行きます。通常、これはPythonがライブラリをインストールしているディレクトリ(site-packagesなど)の中を探すことになります。NumPyが正しくインストールされていれば、PythonはNumPyパッケージを見つけ、その中にあるコードをメモリに読み込み、使える状態にします。

    この時点でも、import numpy だけ書いて使うことは可能です。その場合、NumPyの機能を使うには numpy.機能名 のように書くことになります。例えば、NumPy配列を作る場合は numpy.array([1, 2, 3]) となります。

  • as np:
    これは、今インポートしたnumpyというモジュールに、コードの中で一時的にnpという短いエイリアス(別名)を付ける、という意味です。このエイリアスは、そのPythonファイルの中でだけ有効です。他のファイルには影響しません。

    これにより、NumPyの機能を使う際に、毎回 numpy.機能名 とフルネームで書く代わりに、np.機能名 と短く書けるようになります。

    例えば、NumPy配列を作る場合、np.array([1, 2, 3]) と書くことになります。

なぜas npを使うのか?その理由とメリット

「なぜ、わざわざ短い名前に置き換える必要があるの?」と思うかもしれません。これにはいくつかの理由があります。

  1. コードの簡潔さ: NumPyの機能は頻繁に使われます。毎回 numpy. と書くのは手間がかかりますし、コードが長くなって読みにくくなります。np. と書くことで、コードが短く、スッキリします。
  2. コーディング規約: import numpy as np は、Pythonコミュニティ、特にデータサイエンスや数値計算の分野における強い慣習(コンベンション)となっています。世界中のNumPyを使うほとんどの人がこのエイリアスを使っています。これにより、他の人が書いたコードを読むときに、「あ、npが出てきたらこれはNumPyのことだな」とすぐに理解できます。共同開発や、他人のコードを参考にするときに非常に重要です。
  3. タイプミス軽減: 短いエイリアスは、長い名前をタイプするよりもタイプミスをする可能性が減ります。

特別な理由がない限り、NumPyをインポートする際は 必ず import numpy as np という形式を使うべきです。これは単なる省略記法ではなく、Pythonコミュニティにおける標準的なコミュニケーション手段の一部なのです。

では、実際にimport numpy as npを使って、NumPyの機能を使ってみましょう。

“`python

NumPyライブラリをインポートし、npというエイリアスを付ける

import numpy as np

NumPyを使って配列を作成してみる

np.array() は、PythonのリストなどからNumPy配列を作成する関数

my_numpy_array = np.array([1, 2, 3, 4, 5])

作成した配列を表示

print(my_numpy_array)

配列の型を確認

print(type(my_numpy_array))

NumPy配列を使った要素ごとの計算

array1 = np.array([1, 2, 3])
array2 = np.array([4, 5, 6])

NumPyでは、+ 演算子で要素ごとの足し算ができる

result_array = array1 + array2
print(result_array) # 出力: [5 7 9]

配列全体にスカラー値を足す

result_scalar = array1 + 5
print(result_scalar) # 出力: [ 6 7 8]

平均値を計算するNumPy関数

print(np.mean(array1)) # 出力: 2.0

NumPy配列の属性を確認

print(my_numpy_array.shape) # 出力: (5,) (1次元で要素が5つ)
print(my_numpy_array.dtype) # 出力: int64 (要素のデータ型)
“`

このように、import numpy as np と書くことで、np. というプレフィックスを使ってNumPyの様々な関数(例: np.array(), np.mean()) や演算子(例: +)を利用できるようになります。

5. NumPyの核心:ndarray(N次元配列)

import numpy as np を書くことで、あなたはNumPyの最も重要なオブジェクトである ndarray を扱えるようになります。ndarrayは「n-dimensional array」の略で、日本語では「N次元配列」と呼ばれます。

ndarrayとは?その特徴とメリット

ndarrayは、同じデータ型の要素が規則正しく並んだ多次元のデータの集まりです。これが、Pythonリストとの決定的な違いです。

  • 単一のデータ型: ndarrayの全ての要素は同じデータ型(整数、浮動小数点数など)でなければなりません。これにより、メモリ上に効率的にデータを格納し、高速な処理が可能になります。
  • 固定サイズ: 一度作成されたndarrayは、要素の数を後から簡単に増減させることはできません(完全に新しい配列を作成し直す必要があります)。これも効率化のためです。
  • 多次元: 1次元(ベクトル)、2次元(行列)、3次元(テンソル)といった、好きな数の次元を持つことができます。

配列の作成方法

NumPy配列(ndarray)を作成する最も一般的な方法を見ていきましょう。これら全てに np. が必要になることに注目してください。

  1. Pythonのリストやタプルから作成:
    “`python
    arr1d = np.array([1, 2, 3, 4, 5]) # 1次元配列
    arr2d = np.array([[1, 2, 3], [4, 5, 6]]) # 2次元配列 (行列)
    arr3d = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) # 3次元配列

    print(arr1d)
    print(arr2d)
    print(arr3d)
    “`

  2. 全ての要素がゼロの配列:
    “`python
    # 形状を指定 (タプルで指定)
    zeros1d = np.zeros(5) # [0. 0. 0. 0. 0.] (浮動小数点型になる)
    zeros2d = np.zeros((3, 4)) # 3行4列のゼロ行列

    print(zeros1d)
    print(zeros2d)
    “`

  3. 全ての要素がイチの配列:
    “`python
    # 形状を指定 (タプルで指定)
    ones1d = np.ones(5) # [1. 1. 1. 1. 1.] (浮動小数点型になる)
    ones2d = np.ones((2, 3)) # 2行3列のイチ行列

    print(ones1d)
    print(ones2d)
    “`

  4. 全ての要素が指定した値の配列:
    “`python
    # 形状と値を指定
    full_array = np.full((2, 2), 7) # 2×2で全ての要素が7の配列

    print(full_array)
    “`

  5. 指定した範囲の連番配列:
    “`python
    # np.arange(start, stop, step) – stopは含まれない
    range_array = np.arange(0, 10, 2) # [0 2 4 6 8]

    print(range_array)
    “`

  6. 指定した範囲を等分割した配列:
    “`python
    # np.linspace(start, stop, num) – stopは含まれる
    linear_array = np.linspace(0, 1, 5) # 0から1までを5等分割 [0. 0.25 0.5 0.75 1. ]

    print(linear_array)
    “`

  7. ランダムな値の配列:
    “`python
    # 0から1の間の乱数 (一様分布)
    rand_uniform = np.random.rand(3, 2) # 3行2列の配列

    標準正規分布に従う乱数 (平均0, 標準偏差1)

    rand_normal = np.random.randn(2, 4) # 2行4列の配列

    指定した範囲の整数乱数 (low <= x < high)

    rand_int = np.random.randint(0, 10, size=(3, 3)) # 0から9までの整数で3×3の配列

    print(rand_uniform)
    print(rand_normal)
    print(rand_int)

    注意: 乱数生成のためには np.random サブモジュールを使います

    シードを設定して毎回同じ乱数を生成することも可能

    np.random.seed(42) # シード設定
    rand_fixed = np.random.rand(2)
    print(rand_fixed) # 実行するたびに同じ値が出力される
    “`

配列の基本情報:shape, dtype, size, ndim

作成したNumPy配列は、いくつかの便利な属性を持っています。これらを使って、配列の基本的な情報を知ることができます。

“`python
arr = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])

print(arr)

配列の形状 (各次元の要素数)

print(“Shape:”, arr.shape) # 出力: Shape: (3, 4) (3行4列)

配列の要素のデータ型

print(“Data type:”, arr.dtype) # 出力例: Data type: int64 (整数型)

配列の総要素数

print(“Size:”, arr.size) # 出力: Size: 12 (3 * 4)

配列の次元数

print(“Number of dimensions:”, arr.ndim) # 出力: Number of dimensions: 2 (2次元配列)
“`

データ型(dtype)の重要性

NumPy配列の重要な特徴は、全ての要素が同じデータ型であることです。このデータ型は dtype 属性で確認できます。

NumPyは様々な数値型をサポートしています。

  • 整数型: int8, int16, int32, int64 など(数字はビット数)
  • 浮動小数点型: float16, float32, float64 など
  • 複素数型: complex64, complex128 など
  • 真偽値型: bool
  • 文字列型: string_ など (ただし、数値計算が目的ならあまり使わない)

デフォルトでは、NumPyはPythonのリストから配列を作成する際に、最適なデータ型を自動的に推測します。例えば、整数のリストからはint64、浮動小数点数のリストからはfloat64(またはシステムに応じたデフォルト)になることが多いです。

明示的にデータ型を指定して配列を作成することも可能です。

“`python

明示的にデータ型を指定

float_arr = np.array([1, 2, 3], dtype=np.float32)
print(float_arr) # 出力: [1. 2. 3.]
print(float_arr.dtype) # 出力: float32

異なる型の要素を含むリストから作成した場合(通常はより汎用的な型に変換される)

mixed_arr = np.array([1, 2.5, 3])
print(mixed_arr) # 出力: [1. 2.5 3. ]
print(mixed_arr.dtype) # 出力: float64 (整数が浮動小数点に変換された)

どうしても異なる型を混在させたい場合は dtype=object になるが、これはNumPyのメリットを失う

object_arr = np.array([1, ‘hello’, True], dtype=object)

print(object_arr.dtype) # 出力: object

Note: これはPythonリストとほぼ同じになり、NumPyの高速性は得られない

“`

dtypeを理解し、必要に応じて適切な型を指定することは、メモリ使用量を削減したり、計算精度を制御したりするために重要です。例えば、大量の画像を扱う際に各ピクセルの値をint64ではなくuint8(符号なし8ビット整数、0-255の範囲)にすることで、大幅にメモリを節約できます。

6. NumPy配列の基本操作

import numpy as np して ndarray を手に入れたら、いよいよNumPyの強力な操作を試すことができます。NumPyの操作は、Pythonのリストを使ったループ処理とは異なり、ベクトル化(Vectorization)されているのが特徴です。

要素ごとの演算(ベクトル化)

NumPy配列に対する四則演算(+, -, , /, )や比較演算(>, <, ==など)は、デフォルトで要素ごと*に行われます。これが「ベクトル化」された演算の一例です。明示的なループを書く必要がありません。

“`python
arr1 = np.array([1, 2, 3, 4])
arr2 = np.array([10, 20, 30, 40])

要素ごとの足し算

sum_arr = arr1 + arr2
print(“Sum:”, sum_arr) # 出力: Sum: [11 22 33 44]

要素ごとの引き算

diff_arr = arr1 – arr2
print(“Difference:”, diff_arr) # 出力: Difference: [-9 -18 -27 -36]

要素ごとの掛け算

prod_arr = arr1 * arr2
print(“Product:”, prod_arr) # 出力: Product: [ 10 40 90 160]

要素ごとの割り算

div_arr = arr1 / arr2
print(“Division:”, div_arr) # 出力: Division: [0.1 0.1 0.1 0.1] (結果は浮動小数点になる)

要素ごとの累乗

power_arr = arr1 ** 2
print(“Power:”, power_arr) # 出力: Power: [ 1 4 9 16]

配列とスカラー値の演算も要素ごとに行われる

scalar_op = arr1 + 100
print(“Scalar operation:”, scalar_op) # 出力: Scalar operation: [101 102 103 104]
“`

この要素ごとの演算は、Pythonリストで同じことをするためにループを書く必要があったことと比較すると、非常に簡潔で効率的です。

ブロードキャスティング(Broadcasting)とは?

上の例で配列とスカラー値を演算しました (arr1 + 100)。これもベクトル化された演算ですが、厳密にはブロードキャスティングというNumPyの機能によるものです。

ブロードキャスティングは、形状が異なる配列同士で演算を行う際に、NumPyが小さい方の配列を大きい方の配列の形状に合わせて「引き延ばす」かのように振る舞うメカニズムです。これにより、ループを使わずに効率的に異なる形状の配列を組み合わせた演算ができます。

先ほどの例 arr1 + 100 では、スカラー値 100arr1 と同じ形状 (4,) に引き延ばされるかのように扱われ、[100, 100, 100, 100] という配列と arr1 の要素ごとの足し算が行われたイメージです。

ブロードキャスティングには特定のルールがありますが、基本的には以下の考え方です。
1. 次元数が少ない方の配列の先頭に次元を1として追加する。
2. 2つの配列の次元数が同じになるように、各次元についてサイズを比較する。
3. どちらかのサイズが1であれば、もう一方のサイズに合わせてその次元を引き延ばす。
4. どちらのサイズも1でなく、かつサイズが一致しない場合はエラーとなる。

例:2次元配列と1次元配列の足し算

“`python
matrix = np.array([[1, 2, 3], [4, 5, 6]]) # 形状 (2, 3)
vector = np.array([10, 20, 30]) # 形状 (3,)

matrix と vector を足し算

vectorが [[10, 20, 30], [10, 20, 30]] に引き延ばされるイメージ

result = matrix + vector
print(result)

出力:

[[11 22 33]

[14 25 36]]

“`

ブロードキャスティングは非常に強力ですが、慣れるまでは少し難しく感じるかもしれません。最初は簡単なケースから試していくと良いでしょう。重要なのは、形状が異なる配列でも、ブロードキャスティングのルールに合致すれば要素ごとの演算が可能になる、という点です。

比較演算子と真偽値配列

NumPy配列に比較演算子を使うと、結果として要素ごとの比較結果を示す真偽値(boolean)の配列が得られます。

“`python
arr = np.array([10, 5, 12, 8, 15])

各要素が10より大きいか?

greater_than_10 = arr > 10
print(greater_than_10) # 出力: [False False True False True]

各要素が偶数か?

is_even = (arr % 2 == 0)
print(is_even) # 出力: [True False True True False]
“`

この真偽値配列は、後述する「真偽値インデックス付け」で非常に役立ちます。

ユニバーサル関数(ufunc):様々な数学関数

NumPyには、配列の各要素に対して高速に計算を行うための多くの「ユニバーサル関数」(ufunc と呼ばれます)が用意されています。これらは数学的な関数(平方根、三角関数、指数関数、対数関数など)やビット演算など多岐にわたります。

“`python
arr = np.array([0, np.pi/2, np.pi]) # np.pi は円周率

要素ごとのサイン (sin)

sin_arr = np.sin(arr)
print(“Sin:”, sin_arr) # 出力例: Sin: [0.00000000e+00 1.00000000e+00 1.22464680e-16] (πでのsinはほぼ0)

要素ごとの平方根 (sqrt)

sqrt_arr = np.sqrt([1, 4, 9, 16])
print(“Sqrt:”, sqrt_arr) # 出力: Sqrt: [1. 2. 3. 4.]

要素ごとの指数関数 (e^x)

exp_arr = np.exp([0, 1, 2])
print(“Exp:”, exp_arr) # 出力例: Exp: [ 1. 2.71828183 7.3890561 ]

要素ごとの対数関数 (ln)

log_arr = np.log([1, np.exp(1), np.exp(2)])
print(“Log:”, log_arr) # 出力例: Log: [0. 1. 2.]
“`

これらの関数もベクトル化されており、NumPy配列を渡すと、配列の各要素に対して関数が適用された結果が新しいNumPy配列として返されます。ループを書く必要はありません。

集約関数(Aggregation):合計、平均、最大値、最小値など

データ分析では、配列全体の合計や平均、最大値、最小値などを計算することがよくあります。NumPyはこれらのための効率的な集約関数を提供しています。これらの関数は、配列全体を1つの数値にまとめ上げます。

“`python
arr = np.array([1, 5, 2, 8, 3, 9])

print(“Sum:”, np.sum(arr)) # 出力: Sum: 28 (全要素の合計)
print(“Mean:”, np.mean(arr)) # 出力: Mean: 4.666… (全要素の平均)
print(“Maximum:”, np.max(arr)) # 出力: Maximum: 9 (最大値)
print(“Minimum:”, np.min(arr)) # 出力: Minimum: 1 (最小値)
print(“Standard deviation:”, np.std(arr)) # 出力: Standard deviation: 3.091… (標準偏差)
print(“Variance:”, np.var(arr)) # 出力: Variance: 9.555… (分散)
print(“Median:”, np.median(arr)) # 出力: Median: 4.0 (中央値)
“`

これらの集約関数は、NumPy配列のメソッドとしても利用できます(どちらを使っても結果は同じです)。

“`python
print(“Sum (method):”, arr.sum())
print(“Mean (method):”, arr.mean())

etc.

“`

axisパラメータの理解

集約関数は、多次元配列の場合にどの軸(次元)に沿って計算を行うかを指定できる axis という重要なパラメータを持っています。これは、特に2次元配列(行列)を扱う際によく使われます。

2次元配列を考えるとき、
* axis=0を指します。軸0に沿って計算するということは、各列ごとに計算を行うということです。結果として、列の数と同じ要素を持つ1次元配列が得られます。
* axis=1を指します。軸1に沿って計算するということは、各行ごとに計算を行うということです。結果として、行の数と同じ要素を持つ1次元配列が得られます。

“`python
matrix = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]) # 形状 (3, 3)

print(“Matrix:\n”, matrix)

全体の合計

print(“Sum of all elements:”, np.sum(matrix)) # 出力: Sum of all elements: 45

axis=0 で合計 (列ごとの合計)

[1+4+7, 2+5+8, 3+6+9] を計算

print(“Sum along axis=0 (columns):”, np.sum(matrix, axis=0)) # 出力: Sum along axis=0 (columns): [12 15 18]

axis=1 で合計 (行ごとの合計)

[1+2+3, 4+5+6, 7+8+9] を計算

print(“Sum along axis=1 (rows):”, np.sum(matrix, axis=1)) # 出力: Sum along axis=1 (rows): [ 6 15 24]

同様に平均

print(“Mean along axis=0 (columns):”, np.mean(matrix, axis=0)) # 出力: Mean along axis=0 (columns): [4. 5. 6.]
print(“Mean along axis=1 (rows):”, np.mean(matrix, axis=1)) # 出力: Mean along axis=1 (rows): [2. 5. 8.]

最大値

print(“Max along axis=0 (columns):”, np.max(matrix, axis=0)) # 出力: Max along axis=0 (columns): [7 8 9]
print(“Max along axis=1 (rows):”, np.max(matrix, axis=1)) # 出力: Max along axis=1 (rows): [3 6 9]
“`

axisパラメータは最初は混乱しやすいですが、NumPyを使ったデータ処理では非常によく使う概念です。多次元配列の場合、どの方向に計算を進めたいのかを考えるときに必要になります。

7. NumPy配列のインデックス付けとスライス

Pythonリストと同様に、NumPy配列の要素にもインデックスを使ってアクセスしたり、スライスを使って部分配列を取り出したりできます。しかし、NumPy配列のインデックス付けとスライスは、Pythonリストよりもさらに強力で柔軟です。

基本的なインデックス付け(1次元、2次元)

1次元配列では、リストと同じようにインデックスを指定します(0から始まります)。負のインデックスも使えます。

“`python
arr = np.array([10, 20, 30, 40, 50])

print(arr[0]) # 出力: 10 (最初の要素)
print(arr[2]) # 出力: 30 (インデックス2の要素)
print(arr[-1]) # 出力: 50 (最後の要素)
“`

2次元配列(行列)では、[行インデックス, 列インデックス] の形式で要素にアクセスします。

“`python
matrix = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])

print(matrix[0, 0]) # 出力: 1 (0行0列目の要素)
print(matrix[1, 2]) # 出力: 6 (1行2列目の要素)
print(matrix[2, 1]) # 出力: 8 (2行1列目の要素)

行だけを指定すると、その行全体が取得できる

print(matrix[0]) # 出力: [1 2 3] (0行目全体)

列だけを指定することはできない (後述のスライスやファンシーインデックスが必要)

例: print(matrix[, 0]) はエラー

“`

3次元以上の配列でも同様に、次元の数だけインデックスを指定していきます(例: arr3d[z, y, x])。

スライス:配列の一部を取り出す

NumPy配列のスライスは、Pythonリストのスライスに似ていますが、多次元に対応しています。構文は [start:stop:step] で、stop は含まれません。

1次元配列のスライス:

“`python
arr = np.array([10, 20, 30, 40, 50, 60])

print(arr[1:4]) # 出力: [20 30 40] (インデックス1から3まで)
print(arr[:3]) # 出力: [10 20 30] (最初からインデックス2まで)
print(arr[3:]) # 出力: [40 50 60] (インデックス3から最後まで)
print(arr[::2]) # 出力: [10 30 50] (最初から最後まで2つ飛ばし)
print(arr[::-1]) # 出力: [60 50 40 30 20 10] (逆順)
“`

2次元配列のスライス:[行スライス, 列スライス] の形式を使います。行と列を独立してスライスできます。

“`python
matrix = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])

最初の2行と最初の2列を選択

print(matrix[:2, :2])

出力:

[[1 2]

[4 5]]

2行目全体を選択 (行スライスにインデックス、列スライスは全ての列 [:])

print(matrix[1, :]) # 出力: [4 5 6]

これは matrix[1] と同じ結果になる

2列目全体を選択 (行スライスは全ての行 [:]、列スライスにインデックス)

print(matrix[:, 1]) # 出力: [2 5 8]

1行目と2行目(インデックス0と1)の、2列目以降(インデックス1以降)を選択

print(matrix[:2, 1:])

出力:

[[2 3]

[5 6]]

“`

スライスを使って取り出した配列は、元の配列のビュー(View)であることが多いです。つまり、スライスされた配列を変更すると、元の配列も変更される可能性があります(NumPyのバージョンや操作による違いもあるため、注意が必要な場合があります)。明示的にコピーを作成したい場合は .copy() メソッドを使います。

“`python
slice_view = matrix[:2, :2]
print(“View before change:\n”, slice_view)

スライスされた配列を変更

slice_view[0, 0] = 99

print(“View after change:\n”, slice_view)
print(“Original matrix after view change:\n”, matrix) # 元の配列も変わっている!

出力:

View after change:

[[99 2]

[ 4 5]]

Original matrix after view change:

[[99 2 3]

[ 4 5 6]

[ 7 8 9]]

コピーを作成する場合

slice_copy = matrix[:2, :2].copy()
print(“Copy before change:\n”, slice_copy)

コピーを変更

slice_copy[0, 0] = 100

print(“Copy after change:\n”, slice_copy)
print(“Original matrix after copy change:\n”, matrix) # 元の配列は変わらない

出力:

Copy after change:

[[100 2]

[ 4 5]]

Original matrix after copy change:

[[99 2 3]

[ 4 5 6]

[ 7 8 9]]

“`

真偽値インデックス付け(Boolean Indexing):条件を満たす要素の選択

NumPy配列の強力な機能の一つに、真偽値配列を使ったインデックス付けがあります。これにより、特定の条件を満たす要素だけを選択したり、変更したりすることが簡単に行えます。

先ほど作成した真偽値配列を使います。

“`python
arr = np.array([10, 5, 12, 8, 15])

条件に基づいて真偽値配列を作成

greater_than_10 = arr > 10 # [False False True False True]

真偽値配列を使って要素を選択

selected_elements = arr[greater_than_10]
print(selected_elements) # 出力: [12 15] (Trueに対応する要素だけが選択される)

条件を直接インデックスとして使うことも多い

even_elements = arr[arr % 2 == 0]
print(even_elements) # 出力: [10 12 8]

条件を満たす要素に値を代入することも可能

arr[arr > 10] = 99
print(arr) # 出力: [10 5 99 8 99]
“`

真偽値インデックス付けは、データの中から特定の条件に合致する行や列を抽出したり、欠損値をフィルタリングしたりする際に非常に役立ちます。Pandasでも内部的に広く利用されています。

ファンシーインデックス付け(Fancy Indexing):特定のインデックスによる選択

ファンシーインデックス付けは、整数の配列やリストを使って、配列から特定のインデックス位置にある要素を(元の順序に関係なく)まとめて取り出す方法です。

“`python
arr = np.array([‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’])

インデックスのリストや配列を指定

indices = [0, 2, 5]
selected = arr[indices]
print(selected) # 出力: [‘a’ ‘c’ ‘f’]

インデックスの順序は任意

indices_reordered = [5, 0, 2]
selected_reordered = arr[indices_reordered]
print(selected_reordered) # 出力: [‘f’ ‘a’ ‘c’]

2次元配列でのファンシーインデックス付けは少し複雑

matrix = np.array([[10, 20, 30],
[40, 50, 60],
[70, 80, 90]])

行インデックスと列インデックスを別々のリストで指定した場合

matrix[[行1, 行2, …], [列1, 列2, …]] は、

(行1, 列1), (行2, 列2), … の要素を選択する

selected_elements = matrix[[0, 1, 2], [0, 1, 0]]
print(selected_elements) # 出力: [10 50 70] (matrix[0,0], matrix[1,1], matrix[2,0] を選択)

行全体をファンシーインデックスで選択する場合

row_indices = [0, 2]
selected_rows = matrix[row_indices, :] # または matrix[row_indices]
print(selected_rows)

出力:

[[10 20 30]

[70 80 90]]

列全体をファンシーインデックスで選択する場合

col_indices = [0, 2]
selected_cols = matrix[:, col_indices]
print(selected_cols)

出力:

[[10 30]

[40 60]

[70 90]]

“`

ファンシーインデックス付けは、真偽値インデックス付けと異なり、常に元の配列のコピーを返します。

8. 配列の形状操作(Reshaping and Manipulating)

NumPy配列は、その形状(shape)を変更したり、複数の配列を結合・分割したりする便利な機能を持っています。データの前処理や整形において非常に重要です。

reshape():配列の形状を変更する

reshape()メソッドは、配列の総要素数を変えずに、その形状を変更します。新しい形状のタプルを指定します。ただし、新しい形状の要素数の積は、元の配列の総要素数と一致する必要があります。

“`python
arr = np.arange(1, 13) # [ 1 2 3 4 5 6 7 8 9 10 11 12] (総要素数12)

print(“Original array:”, arr)

3行4列の行列に変換

reshaped_matrix = arr.reshape((3, 4))
print(“Reshaped to 3×4:\n”, reshaped_matrix)

出力:

[[ 1 2 3 4]

[ 5 6 7 8]

[ 9 10 11 12]]

2行6列の行列に変換

reshaped_matrix2 = arr.reshape((2, 6))
print(“Reshaped to 2×6:\n”, reshaped_matrix2)

-1 を使うと、NumPyが自動的にその次元のサイズを計算してくれる

例: 1次元のままにする (総要素数12 なので (12,))

reshaped_1d = reshaped_matrix.reshape(-1)
print(“Reshaped to 1D:”, reshaped_1d) # 出力: [ 1 2 3 4 5 6 7 8 9 10 11 12]

例: 4列の行列にする (総要素数12 なので 3行必要 (3, 4))

reshaped_unknown = arr.reshape((-1, 4))
print(“Reshaped with unknown rows:\n”, reshaped_unknown)

出力:

[[ 1 2 3 4]

[ 5 6 7 8]

[ 9 10 11 12]]

例: 3行の行列にする (総要素数12 なので 4列必要 (3, 4))

reshaped_unknown2 = arr.reshape((3, -1))
print(“Reshaped with unknown columns:\n”, reshaped_unknown2)

出力:

[[ 1 2 3 4]

[ 5 6 7 8]

[ 9 10 11 12]]

“`

reshape()は、通常、元の配列のビューを返します(つまり、メモリをコピーしないため高速ですが、変更が元の配列に影響する可能性があります)。

ravel() / flatten():配列を1次元にする

ravel()flatten()は、多次元配列を1次元配列に変換するメソッドです。

“`python
matrix = np.array([[1, 2, 3], [4, 5, 6]])

ravel(): 通常はビューを返す(元の配列の変更に影響される/与える)

ravelled = matrix.ravel()
print(“Ravelled:”, ravelled) # 出力: [1 2 3 4 5 6]

flatten(): 常にコピーを返す(元の配列とは独立)

flattened = matrix.flatten()
print(“Flattened:”, flattened) # 出力: [1 2 3 4 5 6]

違いの確認

ravelled[0] = 99
print(“Matrix after ravelled change:\n”, matrix)

出力:

[[99 2 3]

[ 4 5 6]] (元のmatrixも変わった)

flattened[0] = 100
print(“Matrix after flattened change:\n”, matrix)

出力:

[[99 2 3]

[ 4 5 6]] (元のmatrixは変わらない)

``
一般的には、メモリ効率が良い
ravel()が好まれますが、元の配列に影響を与えたくない場合はflatten()reshape(-1).copy()`を使用します。

transpose():転置

transpose()メソッドや.T属性は、配列の軸を入れ替える「転置」を行います。特に2次元配列(行列)の場合は、行と列を入れ替える操作になります。

“`python
matrix = np.array([[1, 2, 3],
[4, 5, 6]]) # 形状 (2, 3)

print(“Original matrix:\n”, matrix)

転置 (メソッド)

transposed_matrix = matrix.transpose()
print(“Transposed matrix (method):\n”, transposed_matrix)

出力:

[[1 4]

[2 5]

[3 6]] # 形状 (3, 2)

転置 (属性)

transposed_matrix_T = matrix.T
print(“Transposed matrix (.T):\n”, transposed_matrix_T)

出力は上と同じ

“`

.Tを使うのが一般的で簡潔です。

配列の結合と分割

複数の配列を一つに結合したり、一つの配列を複数に分割したりする関数もあります。

  • 結合:
    • np.concatenate(): 指定した軸に沿って配列を結合します。
    • np.vstack() / np.row_stack(): 行方向に配列を積み重ねます。
    • np.hstack() / np.column_stack(): 列方向に配列を積み重ねます。

“`python
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])

1次元配列の結合

combined_1d = np.concatenate([arr1, arr2])
print(“Concatenated 1D:”, combined_1d) # 出力: [1 2 3 4 5 6]

matrix1 = np.array([[1, 2], [3, 4]]) # 形状 (2, 2)
matrix2 = np.array([[5, 6], [7, 8]]) # 形状 (2, 2)

行方向への結合 (垂直スタック)

vstacked = np.vstack([matrix1, matrix2])
print(“Vstacked:\n”, vstacked)

出力:

[[1 2]

[3 4]

[5 6]

[7 8]]

列方向への結合 (水平スタック)

hstacked = np.hstack([matrix1, matrix2])
print(“Hstacked:\n”, hstacked)

出力:

[[1 2 5 6]

[3 4 7 8]]

concatenate で軸を指定しても同じことができる

行方向 (axis=0 が行):

concatenated_rows = np.concatenate([matrix1, matrix2], axis=0)
print(“Concatenated rows:\n”, concatenated_rows) # vstacked と同じ

列方向 (axis=1 が列):

concatenated_cols = np.concatenate([matrix1, matrix2], axis=1)
print(“Concatenated columns:\n”, concatenated_cols) # hstacked と同じ
“`

  • 分割:
    • np.split(): 指定した軸に沿って、配列を指定した数または位置で分割します。
    • np.vsplit(): 行方向(垂直)に分割します。
    • np.hsplit(): 列方向(水平)に分割します。

“`python
arr = np.arange(16).reshape((4, 4)) # 4×4の配列
print(“Original array for splitting:\n”, arr)

行方向に2つに分割 (vsplit)

split_rows = np.vsplit(arr, 2) # 2つの配列のリストが返される
print(“Split rows:\n”, split_rows)

出力:

[array([[0, 1, 2, 3],

[4, 5, 6, 7]]),

array([[ 8, 9, 10, 11],

[12, 13, 14, 15]])]

列方向に4つに分割 (hsplit)

split_cols = np.hsplit(arr, 4) # 4つの配列のリストが返される
print(“Split columns:\n”, split_cols)

出力:

[array([[ 0], [ 4], [ 8], [12]]),

array([[ 1], [ 5], [ 9], [13]]),

array([[ 2], [ 6], [10], [14]]),

array([[ 3], [ 7], [11], [15]])]

split で軸と分割位置を指定

axis=1 (列方向)で、インデックス1と3の後に分割

split_general = np.split(arr, [1, 3], axis=1)
print(“General split:\n”, split_general)

出力:

[array([[ 0],

[ 4],

[ 8],

[12]]),

array([[ 1, 2],

[ 5, 6],

[ 9, 10],

[13, 14]]),

array([[ 3],

[ 7],

[11],

[15]])]

“`

これらの形状操作関数は、データセットを訓練用/テスト用に分割したり、特徴量とターゲット変数を分離したりする際に非常に便利です。

9. NumPyが高速な理由:ベクトル化と内部実装

ここまでNumPyの様々な機能を見てきましたが、Pythonのリストに比べてなぜNumPyが圧倒的に高速なのか、改めてその理由をまとめておきます。

  1. ndarrayのメモリ構造: NumPy配列の要素は、メモリ上で連続して配置されます。一方、Pythonリストの要素はメモリ上の様々な場所にバラバラに配置され、それぞれがオブジェクトへの参照を持っています。連続したメモリ配置は、CPUのキャッシュ効率を高め、データの読み書きを高速化します。また、全ての要素が同じデータ型であることも、この効率的なメモリ配置を可能にしています。
  2. ベクトル化演算: NumPyの多くの操作(四則演算、ユニバーサル関数、集約関数など)は、ベクトル化されています。これは、内部的にCやFortranといったコンパイル言語で実装された高度に最適化されたコードが実行されることを意味します。これらのコードは、ループ処理を非常に高速に行うことができます。Pythonレベルで明示的にループを書く必要がないため、Pythonインタープリタのオーバーヘッドが発生しません。
  3. 高度なライブラリとの連携: NumPyは、線形代数計算などでBLAS (Basic Linear Algebra Subprograms) や LAPACK (Linear Algebra Package) といった、さらに高度に最適化された外部ライブラリを利用するように設定されることが多いです(Anacondaなどの多くのPythonディストリビューションではデフォルトで有効になっています)。これにより、行列演算などが非常に高速に実行されます。

簡単に言うと、NumPyは「遅いPythonのループ」の代わりに「速いC/Fortranのループ」を内部で実行し、さらに「効率の良いデータの並べ方」をしている、ということです。

例えば、100万個の要素を持つ2つの配列の足し算を考えてみましょう。

“`python

Pythonリストの場合

list1 = list(range(1000000))
list2 = list(range(1000000))

ループまたはリスト内包表記が必要

start_time = time.time()

result_list = [list1[i] + list2[i] for i in range(1000000)]

end_time = time.time()

print(“List addition time:”, end_time – start_time)

NumPy配列の場合

import numpy as np

arr1 = np.arange(1000000)

arr2 = np.arange(1000000)

シンプルな + 演算子でOK

start_time = time.time()

result_array = arr1 + arr2

end_time = time.time()

print(“NumPy addition time:”, end_time – start_time)

実際に実行してみると、NumPyの方が圧倒的に高速であることが分かります。

(実行には時間がかかるためコメントアウトしています)

“`
このように、NumPyを使うことは、単にコードが短くなるだけでなく、計算速度が飛躍的に向上するという大きなメリットがあります。これが、データ分析や科学技術計算でNumPyが必須とされる最大の理由です。

10. NumPyの典型的な使用例

import numpy as np をしてNumPyを使いこなせるようになると、様々な分野でその威力を発揮できます。

  • データ分析: Pandasライブラリの基盤として使われます。PandasのDataFrameやSeriesは内部的にNumPy配列を使用しています。NumPyは、データの読み込み、前処理(欠損値処理、正規化)、統計量計算、フィルタリング、集約などに使われます。
  • 機械学習: Scikit-learnなどの機械学習ライブラリは、入力データとしてNumPy配列を期待することが多いです。モデルの訓練、予測結果の評価などにNumPyが使われます。TensorFlowやPyTorchといった深層学習ライブラリも、内部でNumPyライクな多次元配列(テンソル)を扱います。
  • 画像処理: 画像はピクセルの集まりであり、NumPyの2次元または3次元配列として表現できます(例えば、(高さ, 幅, 色チャンネル) の形状)。NumPyは、画像の読み込み、加工(リサイズ、フィルタリング、色変換)、分析などに使われます。
  • シミュレーションとモデリング: 物理シミュレーション、金融モデリング、統計モデリングなどで、方程式の計算や確率分布からのサンプリングなどにNumPyが使われます。np.randomは乱数を用いたシミュレーションに不可欠です。
  • 信号処理: 音声やセンサーデータなどの時系列データはNumPy配列として扱われ、フーリエ変換(np.fft)などの処理が行われます。
  • 線形代数: NumPyは線形代数計算のための豊富な関数(np.linalgサブモジュール)を提供しています。行列の積、逆行列、固有値分解などが効率的に行えます。

NumPyは単なる配列ライブラリではなく、数値計算を行うための包括的なツールボックスなのです。

11. まとめ:NumPyとimport numpy as npを使いこなすために

この記事では、NumPy初心者の方を対象に、import numpy as np という一行の意味から始まり、NumPyの核となるndarray、基本的な操作、インデックス付け、形状操作、そしてなぜNumPyが高速なのかまで、幅広くかつ詳細に解説しました。

重要なポイントを改めてまとめます。

  • Pythonのリストは便利だが、大量の数値計算には速度とメモリ効率、機能の面で限界がある。
  • NumPyは、効率的なN次元配列オブジェクト ndarray を提供し、数値計算の課題を解決するライブラリである。
  • import numpy as np は、NumPyを使うための標準的な方法であり、numpyモジュールをインポートして np という短いエイリアスを付ける。これはコミュニティの慣習であり、コードの簡潔さと可読性を高める。
  • ndarray は、同じデータ型の要素を持ち、メモリ上で連続的に配置されるため、高速なベクトル化演算が可能。
  • NumPyは、要素ごとの演算、ブロードキャスティング、ユニバーサル関数、集約関数、柔軟なインデックス付けとスライス、形状操作など、数値計算に必要な多くの機能を提供している。
  • これらの機能は、Pythonのループを使うよりもはるかに高速であり、データ分析、機械学習、科学計算など、様々な分野でNumPyが必須とされる理由となっている。

import numpy as np と書くことは、NumPyという強力な道具を使うための最初のステップです。この一行が持つ意味と、その背後にあるNumPyの基本的な考え方を理解することで、あなたはPythonを使った数値計算やデータ処理の可能性を大きく広げることができます。

NumPyの学習は、実際にコードを書いて手を動かすことが最も重要です。この記事で紹介した様々な配列作成方法、演算、インデックス付け、形状操作などを、実際にPythonインタプリタやJupyter Notebookで試してみてください。最初はエラーが出るかもしれませんが、それも学習の一部です。

NumPyは非常に奥深く、この記事で紹介できたのはそのほんの一部に過ぎません。線形代数、乱数生成、統計関数など、さらに多くの強力な機能があります。公式ドキュメント(英語ですが、豊富な例があります)や、NumPyを使ったデータ分析の教材などを参考に、ぜひ学習を続けてみてください。

さあ、自信を持って import numpy as np と書き、NumPyを使ったデータ探索と数値計算の旅を始めましょう!あなたのPythonスキルが、NumPyによってさらにパワフルになることを願っています。

Happy Coding!


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール