Skip to content

4. データ操作の基礎 - NumPy

第 4 回ではデータ操作の基礎として、NumPy の基本的な使い方を紹介します。

4.1NumPy の概要

NumPy は、Python で効率的に数値計算を行うための強力なライブラリです。特に NumPy の配列用の型である ndarray は、Python のリストよりも簡単に記述でき、メモリ効率が高く演算も高速になります。また、NumPy はさまざまなデータ分析・機械学習ライブラリの土台にもなっています。

4.1.1 NumPy のインポート

NumPy は以下のようにインポートして使います。

import numpy as np

4.2 配列の作成

NumPy における配列 (ndarray) は、以下のようにして作成することができます。

ndarray の作成
import numpy as np

# 1 次元配列
arr1 = np.array([1, 2, 3, 4])

# 2 次元配列
arr2 = np.array([[1, 2], [3, 4]])

# 特殊な配列
arr3 = np.zeros((2, 3))     # 0 で埋めた 2×3 の配列
arr4 = np.ones(5)           # 1 で埋めた 1 次元配列
arr5 = np.arange(0, 10, 2)  # [0, 2, 4, 6, 8]
arr6 = np.linspace(0, 1, 5) # [0, 0.25, ..., 1]

# 配列の表示('\n' は改行を意味)
print("arr1:\n", arr1)
print("arr2:\n", arr2)
print("arr3:\n", arr3)
print("arr4:\n", arr4)
print("arr5:\n", arr5)
print("arr6:\n", arr6)
Output
arr1:
 [1 2 3 4]
arr2:
 [[1 2]
 [3 4]]
arr3:
 [[0. 0. 0.]
 [0. 0. 0.]]
arr4:
 [1. 1. 1. 1. 1.]
arr5:
 [0 2 4 6 8]
arr6:
 [0.   0.25 0.5  0.75 1.  ]

4.3 配列の基本操作

4.3.1 配列の形状変更

NumPy の配列の形状は、numpy.shape() 関数で確認し、numpy.reshape() 関数で変更することができます。

ndarray の形状変更
reshaped = np.reshape(arr1, (2, 2))
print(reshaped)
Output
[[1 2]
 [3 4]]

上の例は、reshaped = arr1.reshape((2, 2)) と書き直すこともできます。

4.3.2 配列の結合

複数の配列を結合する場合は、numpy.concatenate() 関数を使います。ただし、結合する配列の形状(軸のサイズ)が同じでないとエラーが出るので注意してください。

ndarray の結合
concat = np.concatenate((arr1, arr4))
print(concat)
Output
[1. 2. 3. 4. 1. 1. 1. 1. 1.]

4.3.3 配列の平坦化

多次元配列を一次元に平坦化するには、numpy.ndarray.flatten() 関数を使います。

二次元配列の一次元化
flat = arr2.flatten()
print(flat)
Output
[1 2 3 4]

4.4 インデックスとスライス

4.4.1 インデックス

NumPy の配列は、リストと同様にインデックスを使って要素を取得することができます。インデックスは 0 からはじまり、負の数値を指定すると配列の最後からアクセスできます。

インデックスによる要素の取得
arr = np.array([10, 20, 30, 40, 50])
print(arr[0])  # 出力: 10
print(arr[-1]) # 出力: 50

mat = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(mat[0, 1])  # 出力: 2
print(mat[2, -1]) # 出力: 9

4.4.2 スライス

スライスを使うと、複数の要素をまとめて取得することができます。

  • スライスは、[開始:終了] または [開始:終了:ステップ] の形式で指定します。
  • [開始] を省略すると 0 から、[終了] を省略すると最後まで取得します。
  • [ステップ] を省略すると 1 になります。
一次元配列のスライス
arr = np.array([10, 20, 30, 40, 50])
print(arr[1:4])   # 出力: [20 30 40]
print(arr[:3])    # 出力: [10 20 30]
print(arr[2:])    # 出力: [30 40 50]
print(arr[::2])   # 出力: [10 30 50]

多次元配列についても同様に、スライスによって配列の一部をまとめて取得することができます。

二次元配列のスライス
mat = np.array([
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]]
)
print(mat[1, :])      # 出力: [4 5 6]
print(mat[1:, :])     # 出力: [[4 5 6], [7 8 9]]
print(mat[:, 2])      # 出力: [3 6 9]
print(mat[:, :2])     # 出力: [[1 2], [4 5], [7 8]]
print(mat[1:, :2])    # 出力: [[4 5], [7 8]]
print(mat[:, [0, 2]]) # 出力: [[1 3], [4 6], [7 9]]

4.5 条件抽出

条件に従った要素の抽出も簡単に行うことができます(ブールインデックス)。例えば、15 より大きい要素のみ抽出したい場合は以下のようにします。

ブールインデックスによる抽出
arr = np.array([10, 20, 30])
print(arr[arr > 15])
Output
[20 30]

さらに、&(AND)や |(OR)を使えば、複数の条件を組み合わせて抽出することもできます。Python の and / or ではなく、ビット演算子の & / | を用いる点と、各条件を () で囲う必要がある点に注意してください。

複合条件による抽出
arr = np.array([10, 20, 30, 40, 50])
print(arr[(arr > 15) & (arr < 45)])  # 出力: [20 30 40]
print(arr[(arr < 20) | (arr > 40)])  # 出力: [10 50]

2 次元配列に対しても、ブールインデックスで条件に合う要素をまとめて抽出することができます。結果は 1 次元配列になります。

二次元配列の条件抽出
mat = np.array([
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
])
print(mat[mat > 4])  # 出力: [5 6 7 8 9]

また、条件式そのものを変数に保存しておくこともできます(マスクと呼ばれます)。これにより、同じ条件を別の配列で再利用することができます。

マスクによる条件抽出
scores = np.array([72, 85, 60, 90, 55, 78])
names = np.array(['A', 'B', 'C', 'D', 'E', 'F'])

mask = scores >= 70
print(scores[mask])  # 出力: [72 85 90 78]
print(names[mask])   # 出力: ['A' 'B' 'D' 'F']

4.6 配列演算とブロードキャスト

NumPy の配列は、以下のような単純な計算式で、各要素に対する演算を行うことができます。このような異なる形状(次元)の配列同士を自動的に形を合わせて計算する仕組みを、ブロードキャストと呼びます。

配列のブロードキャスト
# 各要素を2倍
arr = np.array([10, 20, 30])
print("arr * 2:\n", arr * 2)

# 各要素に 10 を加算
matrix = np.array([[1, 2], [3, 4]])
print("\nmatrix + 10:\n", matrix + 10)

# 1 次元配列と 2 次元配列のブロードキャスト
mat = np.array([[1, 2, 3], [4, 5, 6]])
vec = np.array([10, 20, 30])
print("\nmat + vec:\n", (mat + vec))
Output
arr * 2:
 [20 40 60]

matrix + 10:
 [[11 12]
 [13 14]]

mat + vec:
 [[11 22 33]
 [14 25 36]]

4.7 数学関数

NumPy には、配列の各要素に対して数学関数を一括適用できる関数が多数用意されています。代表的なものは以下の通りです。

関数 説明
np.sin(x) 正弦
np.cos(x) 余弦
np.tan(x) 正接
np.exp(x) 指数関数(e の x 乗)
np.log(x) 自然対数
np.log10(x) 10 を底とする対数
np.sqrt(x) 平方根
np.abs(x) 絶対値

円周率 π は np.pi として用意されています。以下は np.linspace() で 0〜2π を 5 分割した値に対して、複数の数学関数を一括適用する例です。

数学関数の一括適用
import numpy as np

x = np.linspace(0, 2 * np.pi, 5)
print("x:   ", x)
print("sin: ", np.sin(x))
print("cos: ", np.cos(x))
print("exp: ", np.exp(x))
Output
x:    [0.         1.5707963  3.14159265 4.71238898 6.28318531]
sin:  [ 0.0000000e+00  1.0000000e+00  1.2246468e-16 -1.0000000e+00 -2.4492936e-16]
cos:  [ 1.000000e+00  6.123234e-17 -1.000000e+00 -1.836970e-16  1.000000e+00]
exp:  [  1.           4.81047738  23.14069263 111.31777849 535.49165552]

for 文を書かずに、配列の全要素に対して関数を適用できる点がポイントです。

4.8 集計・統計関数

NumPy には、配列に対する合計・平均などの集計関数が用意されています。データ分析で最もよく使われる機能の一つです。

関数 説明
np.sum() 合計
np.mean() 平均
np.std() 標準偏差
np.var() 分散
np.min() 最小値
np.max() 最大値
np.median() 中央値
基本的な集計
import numpy as np

arr = np.array([10, 20, 30, 40, 50])
print("合計   :", np.sum(arr))    # 150
print("平均   :", np.mean(arr))   # 30.0
print("標準偏差:", np.std(arr))   # 14.142...
print("最小   :", np.min(arr))    # 10
print("最大   :", np.max(arr))    # 50
print("中央値 :", np.median(arr)) # 30.0

4.8.1 軸 (axis) を指定した集計

2 次元以上の配列では、axis 引数で「どの方向に集計するか」を指定できます。

  • axis=0:列方向(縦) に集計(=行方向につぶす)
  • axis=1:行方向(横) に集計(=列方向につぶす)
axis 指定による集計
mat = np.array([
    [1, 2, 3],
    [4, 5, 6]
])

print("全体合計        :", np.sum(mat))           # 21
print("列ごとの合計 (0):", np.sum(mat, axis=0))   # [5 7 9]
print("行ごとの合計 (1):", np.sum(mat, axis=1))   # [ 6 15]
print("列ごとの平均 (0):", np.mean(mat, axis=0))  # [2.5 3.5 4.5]

axis の指定は、表形式データ(行=サンプル、列=特徴量)の集計で頻繁に使うので、しっかり押さえておきましょう。

4.9 乱数生成

NumPy には乱数を生成するモジュール numpy.random があります。シミュレーションや、可視化のためのダミーデータ生成に便利です。代表的な関数は以下の通りです。

関数 説明
np.random.rand(n) [0, 1) の一様分布から n 個
np.random.randn(n) 標準正規分布(平均 0、標準偏差 1)から
np.random.randint(low, high, n) low 以上 high 未満の整数を n 個
np.random.normal(mu, sigma, n) 平均 mu、標準偏差 sigma の正規分布
np.random.uniform(low, high, n) low 以上 high 未満の一様分布
乱数の生成
import numpy as np

# [0, 1) の一様分布から 5 個
print(np.random.rand(5))

# 0 以上 100 未満の整数を 10 個
print(np.random.randint(0, 100, 10))

# 平均 50、標準偏差 10 の正規分布から 5 個
print(np.random.normal(50, 10, 5))

4.9.1 シードによる再現性

乱数は実行のたびに値が変わりますが、np.random.seed() で「シード」を固定すると、毎回同じ乱数列を得ることができます。実験結果の再現や、講義資料・レポート用に同じグラフを生成したい場合に有用です。

np.random.seed(42)
print(np.random.rand(3))  # 何度実行しても同じ値

その他にも NumPy には多くの便利な関数が用意されていますので、適宜ドキュメントを参照するようにしてください。

演習

演習 4-1:模試データのスライスと条件抽出

ある模試の結果が、名前と点数の 2 つの配列で与えられています。次の操作を行ってください。

  1. 名前配列のうち、最初の 3 人の名前を出力する(スライス)
  2. 点数が 70 点以上の人の「名前」と「点数」を両方出力する(マスクを 2 つの配列に再利用)
テンプレートコード
import numpy as np

names = np.array(['佐藤', '鈴木', '高橋', '田中', '伊藤', '渡辺', '山本', '中村'])
scores = np.array([72, 85, 60, 90, 55, 78, 65, 95])

# 1. 最初の 3 人の名前

# 2. 70 点以上の人の名前と点数

演習 4-2:ブロードキャストで気温を一括変換

ある都市の 1 週間の最高気温(華氏)が与えられています。NumPy のブロードキャストを使って摂氏に一括変換し、棒グラフで可視化してください。変換式は 摂氏 = (華氏 - 32) × 5 / 9 です。

テンプレートコード
import numpy as np
import matplotlib.pyplot as plt

fahrenheit = np.array([68, 72, 75, 80, 77, 73, 70])
days = ['月', '火', '水', '木', '金', '土', '日']

# 摂氏に一括変換

# 棒グラフで描画

plt.show()

演習 4-3:都道府県データから複合条件で抽出して可視化

以下の都道府県データから、人口が 250 万以上 800 万未満 の都道府県を、& を使った複合条件で抽出し、棒グラフで可視化してください。Y 軸ラベルやタイトルも適切に設定してください。

テンプレートコード
import numpy as np
import matplotlib.pyplot as plt

prefectures = np.array([
    '北海道', '宮城', '茨城', '東京', '愛知',
    '京都', '大阪', '兵庫', '岡山', '広島', '福岡'
])
population = np.array([515, 230, 283, 1404, 753, 256, 880, 543, 188, 280, 512])  # 万人

# 人口が 250 万以上 800 万未満の都道府県を抽出(複合条件)

# 棒グラフで描画

plt.tight_layout()
plt.show()

演習 4-4:axis 指定で行/列方向に集計し可視化

4 商品 × 7 日の売上データが 2 次元配列で与えられています。axis を使って、商品ごとの 1 週間合計と、日ごとの全商品合計を計算し、plt.subplots(1, 2) で並べて棒グラフで描画してください。

テンプレートコード
import numpy as np
import matplotlib.pyplot as plt

sales = np.array([
    [10, 12, 15, 11, 13, 20, 18],  # 商品A
    [ 8,  9,  7, 10, 12, 15, 14],  # 商品B
    [20, 22, 25, 23, 24, 30, 28],  # 商品C
    [ 5,  6,  8,  7,  9, 12, 11],  # 商品D
])
products = ['A', 'B', 'C', 'D']
days = ['月', '火', '水', '木', '金', '土', '日']

# 商品ごとの合計(axis を指定)

# 日ごとの合計(axis を指定)

# 1x2 のサブプロットで並べて描画

plt.tight_layout()
plt.show()

演習 4-5:数学関数とノイズを使った測定値の可視化

np.linspace() で 0 から 4π までの 100 点を作成し、その sin 値を「真の曲線」として計算してください。さらに np.random.normal(0, 0.2, 100) でノイズを生成し、「測定値 = 真値 + ノイズ」を作ってください。最後に、真の曲線を折れ線で、測定値を散布図として、1 枚のグラフに重ねて描画してください。

テンプレートコード
import numpy as np
import matplotlib.pyplot as plt

# 再現性のためシードを固定
np.random.seed(0)

# 0 から 4π までの 100 点

# 真の曲線(sin)

# ノイズを加えた測定値

# 重ねて描画

plt.show()