データを扱うPythonの記事を読んでいると、当たり前のように import numpy as np という行が出てきます。
説明もないまま np.array や np.mean が続いて、置いていかれた気分になったことはありませんか。
私も最初は、リストで足りているのに何を追加しているのだろうと思っていました。
けれど理由を知ってしまうと、あれを使う場面がはっきり見えてきます。速さの話だけではありません。
この記事では、NumPyが何をするライブラリなのかをゼロから整理していきます。手元で動かせるコードだけで進めます。
NumPyは、数値計算のための土台になるライブラリ¶
まずは立ち位置の確認から始めましょう。NumPyは、数値の集まりをまとめて計算するためのライブラリです。
公式ドキュメントでは、科学や工学の分野で広く使われている、多次元配列のデータ構造を持つライブラリだと説明されています。
ここで大事なのは、NumPyが単体で完結する道具ではないという点です。むしろ、他のライブラリの足場になっています。
表形式のデータを扱うpandasも、グラフを描くmatplotlibも、機械学習のライブラリも、内側ではNumPyの配列をやりとりしています。
つまり、データを扱う方向へ進むなら遅かれ早かれ通る道ということです。ここで基礎を押さえておくと、あとが楽になります。
pandasのほうが先に気になっている方は、こちらもあわせてどうぞ。【関連記事】pandas入門 データ処理をやってみよう
インストールして、配列をひとつ作ってみる¶
NumPyは標準ライブラリではないので、自分で追加する必要があります。pipで入れられます。
作業用の仮想環境を作ってから、次のコマンドを実行してください。
pip install numpy
入ったら、さっそく配列を作ってみましょう。慣例として np という短い名前で読み込みます。
import numpy as np
arr = np.array([1, 2, 3, 4, 5])
print(arr) # [1 2 3 4 5]
print(arr * 2) # [ 2 4 6 8 10]
arr * 2 の結果に注目してください。中身がすべて2倍になっています。
同じことをリストでやると、まったく違う結果になります。[1, 2, 3] * 2 はリストが2回くり返されるだけです。
同じ書き方でも、意味がまるで違う。 ここがNumPyを触りはじめて最初に驚くところです。
なお仮想環境の作り方があやしい方は、先にこちらを読んでおくと安心です。【関連記事】Pythonの仮想環境(venv)って何のためにある?プロジェクトごとに混ぜない管理法
リストとの違いは、型がそろっていること¶
なぜリストと挙動が違うのでしょうか。答えは、中に入れられるものの制約にあります。
Pythonのリストは、数値でも文字列でも辞書でも、何でも混ぜて入れられます。自由なかわりに、中身をひとつずつ確認しながら処理するしかありません。
一方のNumPyの配列は、全部が同じ型でそろっています。この配列はndarrayと呼ばれ、N次元の配列という意味の名前です。
型がそろっていると、計算のたびに中身を確かめる必要がなくなります。だから、まとめて一気に処理できるわけです。
配列が自分の情報を持っているので、その場で確認できます。
arr = np.array([1, 2, 3, 4, 5])
print(arr.dtype) # int64 ← 中身の型
print(arr.shape) # (5,) ← 形(要素5個の1次元)
print(arr.size) # 5 ← 要素の数
dtype が中身の型、shape が配列の形です。この2つは、あとあと何度も見ることになります。
違いを表で並べておきます。
| Pythonのリスト | NumPyの配列 | |
|---|---|---|
| 入れられるもの | 型が違っても混ぜられる | 全部が同じ型 |
* 2 の意味 |
中身が2回くり返される | 各要素が2倍になる |
| 大量の計算 | forループを書く | まとめて計算できる |
| 速度 | 要素が増えるほど遅くなる | 大きいほど差が開く |
| 得意なこと | 何でも入れる柔軟さ | 数値をそろえて扱うこと |
リストそのものの性質を復習したい方は、こちらが参考になります。【関連記事】Pythonのリスト(list)とタプル(tuple)、どっちを使う? それぞれの違いを徹底解説
型がそろわないときは、勝手にそろえられる¶
型が同じでなければいけないなら、混ぜたらエラーになるのでしょうか。実は、エラーにはなりません。
NumPyは、全部が収まる型を選んで自動的にそろえます。次の例を見てください。
print(np.array([1, 2, "3"]).dtype) # <U21 ← 全部が文字列になる
print(np.array([1, 2, 3.5]).dtype) # float64 ← 全部が小数になる
数値のつもりだった1と2が、文字列に変わってしまいました。これに気づかないまま計算に進むと、原因のわからないエラーに悩むことになります。
私は10年ほどエンジニアとして開発に関わってきましたが、読み込んだCSVの1行だけに空文字が混ざっていて、列ぜんぶが文字列扱いになっていた事故を何度か見てきました。計算が合わないときは、まず dtype を疑うのが近道です。
CSVの読み込みでつまずいた経験がある方は、こちらもどうぞ。【関連記事】Pythonのcsvモジュールとは?表データの読み書きと文字化けの防ぎ方を初心者向けに解説
ループを書かずに計算する¶
NumPyの書き味を、もう少し実際的な例で見てみましょう。買い物カゴの合計金額を出す場面を考えます。
単価と個数がそれぞれ並んでいるとき、リストなら for を回して掛け算を積み上げることになります。NumPyなら、そのまま掛けるだけです。
prices = np.array([1200, 150, 3000, 480])
counts = np.array([2, 3, 1, 5])
print(prices * counts) # [2400 450 3000 2400]
print((prices * counts).sum()) # 8250
ループがどこにも出てきません。この、ひとつずつ書かずにまとめて計算するやり方をベクトル化と呼びます。
ループが消えると、コードは意図そのものに近づきます。単価かける個数の合計、という説明文がほぼそのままコードになりました。
手元でどれくらい違うのか測ってみた¶
速いと言われても実感が湧かないと思うので、実際に測ってみました。100万個の数値をすべて2倍にする処理です。
私の手元の環境(Python 3.11、NumPy 2.4.6)での結果は次のとおりです。
| 処理 | 書き方 | かかった時間 |
|---|---|---|
| 100万個を2倍にする | リスト内包表記 | 約49ミリ秒 |
| 100万個を2倍にする | NumPyの配列 | 約1.2ミリ秒 |
| 100万個の合計を出す | sum(リスト) |
約6.8ミリ秒 |
| 100万個の合計を出す | 配列.sum() |
約0.4ミリ秒 |
数十倍の差がつきました。環境によって数字は変わりますが、桁が違うという感覚はどこでも同じはずです。
理由は、計算の本体がC言語で書かれた部分にまとめて渡されるからです。Pythonのループを1回ずつ回る場合と比べて、寄り道が圧倒的に少なくなります。
ただし、要素が数十個しかないような場面では差は出ません。NumPyが効くのは、同じ計算をたくさんくり返すときだけ。 ここを取り違えると、ただ読みにくいだけのコードになります。
形の違う配列どうしも計算できる¶
さきほどの例では、単価と個数の要素数がそろっていました。では、そろっていないときはどうなるのでしょうか。
全員のテストの点数に一律で5点を足す、といった場面を考えてみてください。配列と数値をそのまま足せます。
scores = np.array([[80, 65, 90],
[55, 72, 100]])
print(scores + 5)
# [[ 85 70 95]
# [ 60 77 105]]
数値の5が、配列のすべての要素に配られました。この仕組みをブロードキャストと呼びます。
公式ドキュメントでは、形がぴったり同じでない入力どうしを意味のある形で扱うための仕組みだと説明されています。要素数が1の方向は、相手の大きさに合わせて引き伸ばされます。
便利な反面、意図しない組み合わせでも計算が通ってしまうことがあります。結果の shape が想像どおりか、ときどき確かめる癖をつけておくと安全です。
行と列でまとめて集計できる¶
先ほどの scores は、2行3列の表のような形をしていました。こういう形になると、集計の指定が効いてきます。
どの方向に集計するかを axis で指定します。実際に動かしてみましょう。
print(scores.mean()) # 77.0 ← 全体の平均
print(scores.mean(axis=0)) # [67.5 68.5 95.] ← 列ごと(教科ごと)の平均
print(scores.mean(axis=1)) # [78.33333333 75.66666667] ← 行ごと(人ごと)の平均
axis=0 が縦方向、axis=1 が横方向です。最初のうちは逆に覚えてしまいがちなので、小さい配列で試して確かめるのが確実です。
条件で絞り込むこともできます。比較の式をそのまま角かっこの中へ入れます。
print(scores[scores >= 80]) # [ 80 90 100]
print((scores >= 80).sum()) # 3 ← 80点以上が3つ
scores >= 80 は、TrueとFalseが並んだ配列を返します。それを目印にして、当てはまる要素だけを取り出しているわけです。
よく使う操作を表にまとめておきます。困ったらここに戻ってきてください。
| やりたいこと | 書き方 |
|---|---|
| 連番の配列を作る | np.arange(10) |
| すべて0の配列を作る | np.zeros(5) |
| 形を変える | arr.reshape(2, 3) |
| 合計・平均 | arr.sum() / arr.mean() |
| 最大・最小 | arr.max() / arr.min() |
| 条件に合う要素だけ取り出す | arr[arr > 10] |
| 型を変える | arr.astype(float) |
スライスの結果がコピーではない¶
最後に、リストに慣れているほど引っかかる落とし穴をひとつ紹介します。切り出しの挙動です。
リストをスライスすると、新しいリストが作られます。元のリストは影響を受けません。
ところがNumPyの配列は、元の配列を覗く窓のようなものを返します。これはビューと呼ばれます。
言葉より結果を見たほうが早いので、並べて実行してみます。
lst = [1, 2, 3, 4, 5]
part = lst[1:3]
part[0] = 99
print(lst) # [1, 2, 3, 4, 5] ← 元は無傷
arr = np.array([1, 2, 3, 4, 5])
view = arr[1:3]
view[0] = 99
print(arr) # [ 1 99 3 4 5] ← 元も書き換わる
切り出したつもりの配列をいじったら、元のデータまで変わってしまいました。大きなデータを無駄にコピーしないための設計なのですが、知らないと事故になります。
元を守りたいときは .copy() を付けます。切り出した先を書き換えるなら、まずコピーする。 これだけ覚えておけば十分です。
スライスの記法そのものを整理したい方は、こちらが役に立ちます。【関連記事】Pythonのスライスとは?リストや文字列を切り出す書き方を初心者向けに解説
つまずきやすいところをまとめておく¶
ここまでの内容と、初学者がよくぶつかる点を最後に整理します。手元で困ったときの目次として使ってください。
| つまずき | 起きること | 対処 |
|---|---|---|
| 文字列が混ざっている | 列ぜんぶが文字列型になる | dtype を確認して astype で直す |
| 形が合わないと思った計算が通る | ブロードキャストで勝手に広がる | 結果の shape を確認する |
axis の向きを間違える |
平均が想定と違う値になる | 小さい配列で試して覚える |
| スライス先を書き換えた | 元の配列まで変わる | .copy() を付ける |
| 小さいデータで速くならない | むしろ読みにくいだけになる | 素直にリストとforを使う |
NumPy自体も更新が続いています。2026年8月時点の最新版は2.5.2で、Python 3.12以降が必要です。
学びはじめるなら、まずは配列を作って shape と dtype を表示するところからで十分です。そこから平均や絞り込みへ広げていけば、自然に手がなじんできます。
最初から全部を覚える必要はありません。必要になった操作だけ、そのつど調べて足していく。 それでちゃんと使えるようになります。
ここまでお読みいただきありがとうございました。