データ分析や機械学習のプロジェクトにおいて、数値データの集計は避けては通れない非常に重要な工程です。
Pythonで数値計算を効率的に行うためのライブラリであるNumPyには、合計値を算出するsumや平均値を算出するmeanといった関数が用意されています。
これらの関数は単に計算を行うだけでなく、大規模な多次元配列に対しても高速に処理を実行できるという大きなメリットがあります。
本記事では、NumPyのsumとmeanの基本的な使い方から、実務で頻出するaxis(軸)の指定、さらには計算をより高度に制御するためのオプション引数について詳しく解説します。
NumPyのsumとmeanとは?基本操作を理解する
NumPyは「ベクトル演算」と呼ばれる手法を用いることで、Python標準のリスト処理よりも圧倒的に高速な計算を実現しています。
その中でも、np.sumとnp.meanは、データの全体像を把握するために最も頻繁に使用される基本統計量です。
まずは、1次元の配列(ベクトル)を用いた基本的な計算方法を確認していきましょう。
np.sumによる合計値の算出
np.sumは、配列内のすべての要素、あるいは指定した軸に沿った要素の合計値を計算する関数です。
Python標準のsum()関数と比較して、NumPyのnp.sumは大規模なデータセットに対して劇的なパフォーマンス向上を期待できます。
import numpy as np
# 配列の作成
data = np.array([10, 20, 30, 40, 50])
# 合計値の計算
total = np.sum(data)
print(f"合計値: {total}")
合計値: 150
このように、関数に配列を渡すだけで簡単に合計値を求めることができます。
np.meanによる平均値の算出
np.meanは、配列内の要素の算術平均を算出します。
データセットの中心傾向を把握する際に不可欠なツールであり、異常値の影響を考慮しながら全体を要約する際に利用されます。
# 平均値の計算
average = np.mean(data)
print(f"平均値: {average}")
平均値: 30.0
NumPyの関数は非常に直感的であり、基本的な引数のみであれば迷うことなく使用できるでしょう。
多次元配列における集計:axis(軸)の重要性
NumPyが本領を発揮するのは、行列(2次元配列)やテンソル(3次元以上の配列)を扱う場面です。
多次元配列において、単に全体の合計を出すだけでなく、「行ごとの合計」や「列ごとの平均」を求めたいケースが多々あります。
この集計の方向を指定するのがaxisパラメータです。
axisの基本的な考え方
2次元配列(行列)の場合、axis=0とaxis=1の意味を正確に理解することが重要です。
| 設定値 | 集計の方向 | 結果のイメージ |
|---|---|---|
axis=None (デフォルト) | 配列内の全要素 | スカラー値(1つの数値)が返される |
axis=0 | 行を潰して列方向に計算 | 各列の集計結果が返される(列数分の配列) |
axis=1 | 列を潰して行方向に計算 | 各行の集計結果が返される(行数分の配列) |
初心者が混同しやすいポイントですが、「どの次元(軸)に沿ってデータを畳み込むか」を考えると理解しやすくなります。
axisを指定した具体的な計算例
以下のコードで、実際に行列に対してaxisを適用した際の変化を確認してみましょう。
# 2次元配列(3行4列)の作成
matrix = np.array([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]
])
# 全体の合計
sum_all = np.sum(matrix)
# 列ごとの合計(行方向に集計)
sum_col = np.sum(matrix, axis=0)
# 行ごとの合計(列方向に集計)
sum_row = np.sum(matrix, axis=1)
print(f"全合計: {sum_all}")
print(f"列合計 (axis=0): {sum_col}")
print(f"行合計 (axis=1): {sum_row}")
全合計: 78
列合計 (axis=0): [15 18 21 24]
行合計 (axis=1): [10 26 42]
axis=0を指定すると、垂直方向に足し算が行われ、列数である「4」つの要素を持つ結果が得られます。
対してaxis=1を指定すると、水平方向に足し算が行われ、行数である「3」つの要素を持つ結果が得られます。
この概念は、機械学習での特徴量スケーリング(正規化)など、あらゆる高度な処理で必須となる知識です。
集計をより便利にする高度な引数とテクニック
NumPyのsumやmeanには、単純な集計以外にも多くの便利な機能が備わっています。
これらを使いこなすことで、コードの記述量を減らしつつ、より堅牢なプログラムを記述できるようになります。
keepdims引数で形状を保持する
通常、集計を行うと結果の次元は一つ減ってしまいます。
しかし、計算結果を元の多次元配列と組み合わせて利用したい場合(例:各要素をその行の平均値で引く)、次元数が変わってしまうとエラーの原因になります。
そこで役立つのがkeepdims=Trueオプションです。
# keepdimsを指定しない場合
mean_no_keep = np.mean(matrix, axis=1)
print(f"通常の結果形状: {mean_no_keep.shape}")
# keepdims=Trueを指定した場合
mean_keep = np.mean(matrix, axis=1, keepdims=True)
print(f"keepdimsありの形状: {mean_keep.shape}")
通常の結果形状: (3,)
keepdimsありの形状: (3, 1)
keepdims=Trueを指定することで、1次元配列に崩れることなく「3行1列」の行列として結果が保持されます。
これにより、NumPyのブロードキャスト機能を利用して、元の行列から直接平均値を引くといった演算がスムーズに行えるようになります。
dtype引数による精度の指定
大量のデータを合算する場合、データの型によっては「オーバーフロー(数値の溢れ)」が発生する可能性があります。
特に整数の配列をsumする場合、dtypeを明示的に指定することで、計算精度を高めたり、メモリ消費を最適化したりすることが可能です。
# 整数型で合計
sum_int = np.sum(matrix, dtype=np.int64)
# 浮動小数点型で合計
sum_float = np.sum(matrix, dtype=np.float32)
特に機械学習のモデル実装など、数値の精度が結果に直結する場面では、この指定を意識することが重要です。
欠損値(NaN)を含むデータの扱い
実際のデータ分析では、すべてのデータが揃っていることは稀であり、どこかに欠損値(NaN: Not a Number)が含まれていることが一般的です。
通常のnp.sumやnp.meanは、配列の中に一つでもNaNが含まれていると、結果もNaNになってしまいます。
np.nanmeanとnp.nansumの活用
欠損値を無視して集計を行いたい場合には、専用の関数であるnp.nansumやnp.nanmeanを使用します。
# NaNを含む配列
nan_array = np.array([1, 2, np.nan, 4])
# 通常の平均(NaNになる)
normal_mean = np.mean(nan_array)
# NaNを除外した平均(正しく計算される)
safe_mean = np.nanmean(nan_array)
print(f"通常の平均: {normal_mean}")
print(f"NaN除外後の平均: {safe_mean}")
通常の平均: nan
NaN除外後の平均: 2.3333333333333335
このように、データのクレンジングが不十分な状態でも、安全に統計値を算出できる点はNumPyの大きな強みです。
パフォーマンス比較:なぜNumPyを使うのか
Python標準のリストとループ処理(for文)を用いて合計や平均を求めることは可能ですが、なぜNumPyが推奨されるのでしょうか。
その理由は、内部的にC言語で実装されており、メモリ配置が連続しているため高速なアクセスが可能だからです。
大規模データでの速度差
100万個の要素を持つ配列に対して合計を求める際、標準のPythonループとNumPyでは数倍から数十倍の速度差が生じます。
以下の表は、一般的な実行速度の傾向を示したものです。
| 手法 | 処理速度(イメージ) | 推奨されるケース |
|---|---|---|
| Python 標準ループ | 低速 | 非常に小規模、または計算が複雑な場合 |
| Python 組み込み関数 sum() | 中速 | リスト形式の簡易な集計 |
| NumPy (np.sum) | 高速 | 大量の数値データ、多次元配列の集計 |
近年のデータサイエンスにおいて、データ量は増加の一途を辿っています。
効率的なコードを書くことは、単に処理を早くするだけでなく、クラウドコンピューティングなどのコスト削減にも直結します。
実践:NumPyの統計関数を活用したデータ加工
最後に、これまで学んだ知識を組み合わせて、実践的なデータ加工の例を紹介します。
ここでは、複数の生徒のテスト点数データを想定し、各科目(列)の平均点と、各生徒(行)の合計点を一度に算出してみましょう。
# 生徒ごとのテスト結果(数学、英語、理科)
scores = np.array([
[85, 90, 78], # 生徒A
[72, 88, 92], # 生徒B
[95, 80, 85], # 生徒C
[60, 65, 70] # 生徒D
])
# 科目ごとの平均(縦方向)
subject_averages = np.mean(scores, axis=0)
# 生徒ごとの合計点(横方向)
student_totals = np.sum(scores, axis=1)
print(f"科目別平均: {subject_averages}")
print(f"生徒別合計: {student_totals}")
科目別平均: [78. 80.75 81.25]
生徒別合計: [253 252 260 195]
このように、複雑な入れ子のループを書くことなく、わずか数行で多角的な分析が行えるのがNumPyの魅力です。
さらに、ここから平均値による偏差の算出など、より高度な分析へとステップアップすることが可能です。
まとめ
本記事では、NumPyの基本的な集計関数であるsumとmeanについて、その基礎から応用までを解説しました。
単一の数値を求めるだけでなく、axisパラメータによる軸の制御をマスターすることで、多次元データの扱いが格段にスムーズになります。
また、実務においては欠損値処理のためのnanmeanや、ブロードキャストを支えるkeepdimsオプションの活用も非常に重要です。
NumPyは、より高度なデータ分析ライブラリであるPandasや機械学習フレームワークの基礎にもなっています。
本記事で紹介した内容を繰り返し実践し、数値計算の効率化に向けた第一歩を踏み出してください。
適切なツールを選択し、その機能を最大限に引き出すことが、データサイエンティストやエンジニアとしての価値を高めることに繋がります。
