100本ノック / Python / データ分析のためのPython入門100本ノック
コメントを書く
NumPy で製造ラインの品質データを高速に一括処理する
100本ノック 第7章(No.061〜No.070):NumPy 入門
本記事は「データ分析のための Python 入門 100本ノック」シリーズの 第7章 です。
第6章(No.051〜060)ではファイル操作と CSV を学びました。
本章では NumPy を使って、精密機械部品工場の品質データ(90日間・3ライン)を
Python リストよりも高速かつ簡潔に処理する方法を習得します。
[!NOTE] 本資料は、数理工房 (もしくは代表である和山個人) が過去に企業研修において使用した notebook を企業様の許可を得て再構成・編集のうえ公開しています。
掲載データはすべて架空のものであり、実在する企業・工場・数値とは一切関係ありません。
はじめに:この記事で扱う製造業の実務課題
精密機械部品工場のデータ分析担当 K さんが直面している問題です。
現状
1. 3ラインの日次生産数・不良数が 90日分 × 3シート に蓄積されている
2. 不良率の計算に for ループを書いているため、コードが長く保守も大変
3. 「先月の不良率が最も高かった日はいつか」という質問に答えるたびに
270個の数値を手作業で探している
4. ラインごとの安定性(ばらつき)を評価するための統計量を Excel で個別に計算
NumPy を使うと、270個(3ライン × 90日)の不良率を 1行 で計算し、
最大値・最小値・平均・標準偏差を 関数1つ で求め、
警告日(不良率 > 閾値)を ファンシーインデックス で瞬時に抽出できます。
現場でよくある状況
| 場面 | 課題 |
|---|---|
| 不良率の一括計算 | for ループで90行を逐次処理。コードが長く、行数が増えると遅い |
| 月別・ライン別の平均 | Excel の AVERAGE 関数を各列・各シートで個別に適用 |
| 異常値(警告日)の抽出 | 「不良率 > 2% の日」を手でフィルタリング、見落としのリスクがある |
| 安定性評価 | 各ラインの標準偏差を個別に計算し、変動係数(CV)は手計算 |
| 複数ラインの横比較 | ラインごとにファイルが分かれており全体像を把握しにくい |
NumPy の ベクトル演算・ブールインデックス・集約関数(axis 指定) を
組み合わせれば、これらの作業を数行のコードで自動化できます。
なぜこの問題は判断が難しいのか
NumPy は学習コストが低く見えますが、製造データを扱う上では次の落とし穴があります。
-
axisの方向の混乱
2次元配列でnp.mean(data, axis=0)は「列方向(日別)」の平均、
axis=1は「行方向(ライン別)」の平均です。
shapeを先に確認し、「どちらが行でどちらが列か」を把握してから操作しましょう -
スライスは view(参照)を返す
sub = array[10:20]のスライスは コピーではなく参照 です。
sub[0] = 99とすると元のarrayも変わります。
意図しない変更を防ぐには.copy()を明示します -
dtype の落とし穴
int32配列同士の除算は float になりますが、
整数演算(//や%)では切り捨てが起きることを意識してください -
ブールインデックスはコピーを返す
array[array > 2.0] = 0のインプレース代入は元配列を変更しますが、
sub = array[array > 2.0]は新しいコピーを返します。
代入と参照では挙動が異なるので注意が必要です
今回扱うノックの全体像
| No. | タイトル | 製造業での活用場面 |
|---|---|---|
| 061 | NumPyの役割を理解する | Python リスト vs NumPy の速度・簡潔さを比較 |
| 062 | NumPyをimportする | インポートとよく使う関数の確認 |
| 063 | NumPy配列を作成する | 日次生産数・不良数・寸法測定値の配列化 |
| 064 | 配列の形を確認する | shape / ndim / dtype / size で2次元データの構造把握 |
| 065 | 配列の要素を取り出す | インデックス・スライス・2次元アクセスで特定日・特定ラインを抽出 |
| 066 | 配列に対して一括計算する | 不良率・売上・損失を270件同時計算(ブロードキャスト) |
| 067 | 平均値を計算する | axis 指定でライン別・月別平均不良率を一括算出・棒グラフ可視化 |
| 068 | 最大値・最小値を計算する | argmax / argmin で最悪日・最良日を特定 |
| 069 | 標準偏差を計算する | 変動係数(CV)でラインの品質安定性を横比較 |
| 070 | 条件を満たすデータを抽出する | np.where で警告日を自動抽出・散布図で可視化 |
Python 環境の準備
import subprocess, sys
res = subprocess.run(["sw_vers", "-productVersion"], capture_output=True, text=True)
print(f"macOS : {res.stdout.strip()}")
print(f"Python: {sys.version}")
macOS : 26.3
Python: 3.13.1 (main, Dec 3 2024, 17:59:52) [Clang 16.0.0 (clang-1600.0.26.4)]
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import datetime
matplotlib.rcParams['font.family'] = 'Hiragino Maru Gothic Pro'
%config InlineBackend.figure_format = 'svg'
np.random.seed(42)
print("ライブラリ読み込み完了")
print(f" NumPy : {np.__version__}")
print(f" Matplotlib: {matplotlib.__version__}")
ライブラリ読み込み完了
NumPy : 2.5.1
Matplotlib: 3.11.0
架空データの作成
想定シナリオ: 精密機械部品工場 / 機械加工ライン 品質管理チーム
期間: 2024年1月1日〜3月30日(90日間)
管理対象: 3製品ライン(精密軸・ベアリング・フランジ)
データ形式: 2次元 NumPy 配列 — shape: (3, 90) = (ライン数, 日数)
np.random.seed(42)
# ── 基本設定 ─────────────────────────────────────────────
lines = ["M1-精密軸", "M2-ベアリング", "M3-フランジ"]
unit_prices = np.array([2400, 1800, 1200]) # 円/個
n_days = 90 # 2024-01-01〜03-30
base_date = datetime.date(2024, 1, 1)
# 月インデックス(0=1月: 31日, 1=2月: 29日, 2=3月: 30日)
month_idx = np.array([0]*31 + [1]*29 + [2]*30, dtype=np.int32)
# ── 日次生産数(3ライン × 90日)─────────────────────────
base_prod = [450, 380, 550]
prod_2d = np.array([
np.random.randint(b - 20, b + 21, size=n_days)
for b in base_prod
], dtype=np.int32) # shape: (3, 90)
# ── 日次不良数 ───────────────────────────────────────────
dr_base = [0.018, 0.022, 0.014]
defect_2d = np.array([
np.maximum(1, np.round(
prod_2d[i] * dr_base[i] + np.random.randn(n_days) * 1.2
).astype(int))
for i in range(3)
], dtype=np.int32) # shape: (3, 90)
# ── 日次不良率 ───────────────────────────────────────────
defect_rates_2d = defect_2d / prod_2d * 100 # shape: (3, 90), dtype: float64
# ── 確認表示 ─────────────────────────────────────────────
print(f"配列サイズ: prod_2d{prod_2d.shape}, defect_2d{defect_2d.shape}, "
f"defect_rates_2d{defect_rates_2d.shape}")
print()
print(f"{'ライン':<14} {'週計(生産)':>10} {'週計(不良)':>10} {'平均不良率':>10} {'最大不良率':>10}")
print("=" * 58)
for i, line in enumerate(lines):
print(f"{line:<14} {int(prod_2d[i].sum()):>10,} {int(defect_2d[i].sum()):>10} "
f"{float(defect_rates_2d[i].mean()):>9.2f}% {float(defect_rates_2d[i].max()):>9.2f}%")
配列サイズ: prod_2d(3, 90), defect_2d(3, 90), defect_rates_2d(3, 90)
ライン 週計(生産) 週計(不良) 平均不良率 最大不良率
==========================================================
M1-精密軸 40,387 745 1.84% 2.55%
M2-ベアリング 34,339 771 2.24% 2.89%
M3-フランジ 49,581 711 1.43% 1.94%
No.061:NumPyの役割を理解する
実務での意味
製造ラインの日次不良率を計算するとき、Python の for ループでは
90回のループが必要です。NumPy の ベクトル演算 を使えば、
同じ計算が 1行・配列全体に一括適用 で完了します。
データが1万行・10ラインに増えても、NumPy のコードは変わりません。
分析・モデル化の考え方
NumPy は内部で C 言語で実装された配列演算を行うため、
Python の for ループより 10〜100倍高速 です。
数値計算ライブラリ(pandas・scikit-learn・scipy)は内部で NumPy を使っており、
NumPy の配列操作を理解することが高度なデータ分析の基礎になります。
Python で確認する
# Python リスト(for ループ)と NumPy の簡潔さを比較する
production_list = list(prod_2d[0]) # M1 の90日分
defect_list = list(defect_2d[0])
# ── Pure Python: for ループで不良率を計算 ────────────────
defect_rates_py = []
for prod, def_count in zip(production_list, defect_list):
defect_rates_py.append(def_count / prod * 100)
mean_py = sum(defect_rates_py) / len(defect_rates_py)
# ── NumPy: ベクトル演算で1行 ─────────────────────────────
defect_rates_np = defect_2d[0] / prod_2d[0] * 100
mean_np = defect_rates_np.mean()
print("=== 計算結果の比較(M1-精密軸, 90日間)===")
print(f" Python リスト先頭5件: {[round(r, 2) for r in defect_rates_py[:5]]}")
print(f" NumPy 配列 先頭5件: {defect_rates_np[:5].round(2)}")
print()
print(f" Python 平均: {mean_py:.4f}%")
print(f" NumPy 平均 : {mean_np:.4f}% (誤差なし)")
print()
print("=== コードの違い ===")
print(" Python: for ループ 4行 + sum/len")
print(" NumPy : defect_2d[0] / prod_2d[0] * 100 ← 1行で完了")
print()
print(f" 型の違い: Python={type(defect_rates_py).__name__}, NumPy={type(defect_rates_np).__name__}")
print(f" NumPy dtype: {defect_rates_np.dtype}")
=== 計算結果の比較(M1-精密軸, 90日間)===
Python リスト先頭5件: [np.float64(1.71), np.float64(1.53), np.float64(2.03), np.float64(2.06), np.float64(2.0)]
NumPy 配列 先頭5件: [1.71 1.53 2.03 2.06 2. ]
Python 平均: 1.8439%
NumPy 平均 : 1.8439% (誤差なし)
=== コードの違い ===
Python: for ループ 4行 + sum/len
NumPy : defect_2d[0] / prod_2d[0] * 100 ← 1行で完了
型の違い: Python=list, NumPy=ndarray
NumPy dtype: float64
結果の読み取り
Python リストと NumPy 配列で計算結果は完全に一致しています。
defect_2d[0] / prod_2d[0] * 100 の1行で 90件の不良率が計算されました。
これが NumPy のベクトル演算です。
データ件数が 90件 → 9,000件 → 90万件に増えても、NumPy のコードは変わりません。
製造データが増えても保守性の高いコードが書けます。
No.062:NumPyをimportする
実務での意味
import numpy as np が NumPy の標準的なインポート方法です。
np という別名(エイリアス)は業界標準であり、他のライブラリも
NumPy 配列を入力として想定して設計されています。
分析・モデル化の考え方
NumPy はデータ分析エコシステムの土台です。
pandas の Series / DataFrame は NumPy 配列の上に構築されており、
scikit-learn のモデルへの入力も NumPy 配列を使います。
NumPy の基本操作を習得することが、より高度なライブラリへの移行を容易にします。
Python で確認する
import numpy as np
print(f"NumPy バージョン: {np.__version__}")
print()
# 製造データ分析でよく使う NumPy 関数
kpi_funcs = [
("np.array()", "リスト→配列変換"),
("np.zeros(n)", "ゼロ配列の初期化(アラートフラグなど)"),
("np.arange(n)", "連番配列(日次インデックスの生成)"),
("np.mean()", "平均値(不良率の月別平均)"),
("np.std()", "標準偏差(品質安定性評価)"),
("np.max() / min()", "最大・最小(最悪日・最良日)"),
("np.argmax()", "最大値のインデックス(何日目か)"),
("np.where()", "条件による抽出・ラベル付け"),
("np.sum(axis=)", "合計(軸指定で行・列ごとに集計)"),
("np.cumsum()", "累積和(月次累積生産数)"),
]
print(f"{'関数':<28} {'製造データでの活用'}")
print("=" * 68)
for fname, desc in kpi_funcs:
print(f" {fname:<26} {desc}")
print()
# NumPy 定数
print(f"np.pi = {np.pi:.6f} (円形部品の周長・面積計算)")
print(f"np.inf = {np.inf} (上限なしの初期値として)")
print(f"np.nan = {np.nan} (欠損値の表現)")
NumPy バージョン: 2.5.1
関数 製造データでの活用
====================================================================
np.array() リスト→配列変換
np.zeros(n) ゼロ配列の初期化(アラートフラグなど)
np.arange(n) 連番配列(日次インデックスの生成)
np.mean() 平均値(不良率の月別平均)
np.std() 標準偏差(品質安定性評価)
np.max() / min() 最大・最小(最悪日・最良日)
np.argmax() 最大値のインデックス(何日目か)
np.where() 条件による抽出・ラベル付け
np.sum(axis=) 合計(軸指定で行・列ごとに集計)
np.cumsum() 累積和(月次累積生産数)
np.pi = 3.141593 (円形部品の周長・面積計算)
np.inf = inf (上限なしの初期値として)
np.nan = nan (欠損値の表現)
結果の読み取り
製造データ分析で最もよく使うのは np.mean・np.std・np.argmax・np.where の4つです。
np.inf は「上限なし」の初期値として最小値を求めるアルゴリズムで使います。
np.nan は欠損値(センサー停止・記録漏れ)を表現する際に使います。
np.nan を含む配列では np.mean が nan を返すので、
欠損値がある場合は np.nanmean・np.nanstd を使いましょう。
No.063:NumPy配列を作成する
実務での意味
製造データを NumPy 配列として作成する方法を学びます。
CSV や Excel から読み込んだデータも、最終的には NumPy 配列に変換して処理します。
np.random.normal() を使えば、寸法測定値のシミュレーションデータも
現実に近い形で生成できます。
分析・モデル化の考え方
NumPy 配列の作成には大きく4つの方法があります。
①np.array() でリストから変換、②np.zeros/ones() で初期化、
③np.arange/linspace() で等間隔数列、④np.random.*() でランダムデータです。
製造現場の寸法測定値は正規分布 に従うことが多く、
np.random.normal(mean, std, size) で忠実にシミュレーションできます。
Python で確認する
# NumPy 配列の4つの作成方法
# 1. np.array() でリストから変換
weekly_prod_063 = np.array([450, 462, 438, 471, 445, 457, 448])
print(f"1. np.array() : {weekly_prod_063} shape={weekly_prod_063.shape}")
# 2. np.zeros() で初期化(アラートフラグなど)
alert_flags = np.zeros(n_days, dtype=np.int8)
print(f"2. np.zeros() : {alert_flags[:5]} ... shape={alert_flags.shape}, dtype={alert_flags.dtype}")
# 3. np.arange() で連番
day_indices = np.arange(n_days) # 0〜89
print(f"3. np.arange(): {day_indices[:7]} ... shape={day_indices.shape}")
# 4. np.random.normal() で寸法測定値を生成(精密軸の軸径検査)
target_diam = 25.000 # 目標直径 mm
tolerance = 0.050 # 公差 ±0.05mm
sigma_diam = tolerance / 3 # 3σ = 公差全幅 → σ = 0.0167mm
measurements = np.random.normal(target_diam, sigma_diam, size=30)
print()
print("=== M1-精密軸 軸径測定値(n=30 サンプル) ===")
print(f" 目標値 : {target_diam:.3f} mm 公差: ±{tolerance:.3f} mm")
print(f" 先頭5件 : {measurements[:5].round(4)} mm")
print(f" 平均 : {measurements.mean():.4f} mm")
print(f" 範囲 : {measurements.min():.4f} 〜 {measurements.max():.4f} mm")
print()
# dtype の指定
arr_int = np.array([1.7, 2.3, 3.9], dtype=np.int32) # 小数を切り捨て
arr_float = np.array([1, 2, 3], dtype=np.float64)
print(f"dtype=int32 変換: {arr_int} (小数は切り捨て)")
print(f"dtype=float64変換: {arr_float}")
1. np.array() : [450 462 438 471 445 457 448] shape=(7,)
2. np.zeros() : [0 0 0 0 0] ... shape=(90,), dtype=int8
3. np.arange(): [0 1 2 3 4 5 6] ... shape=(90,)
=== M1-精密軸 軸径測定値(n=30 サンプル) ===
目標値 : 25.000 mm 公差: ±0.050 mm
先頭5件 : [25.0187 24.976 24.998 24.9987 24.9816] mm
平均 : 24.9983 mm
範囲 : 24.9620 〜 25.0396 mm
dtype=int32 変換: [1 2 3] (小数は切り捨て)
dtype=float64変換: [1. 2. 3.]
結果の読み取り
np.random.normal(25.000, 0.0167, size=30) で軸径の正規分布測定値をシミュレートしました。
公差の設計として「3σ = 公差全幅(0.1mm)」を使っているため、
品質工学の「±3σ 管理」に対応しています。
理論上 99.73% の製品が公差内に入ります。
dtype=np.int32 で float を変換すると小数が切り捨てられます。
int() 関数と挙動は同じですが、配列全体に一括適用できる点が NumPy の強みです。
No.064:配列の形を確認する
実務での意味
2次元配列(行列)として製造データを扱う場合、
「行がラインで列が日付」なのか「行が日付で列がラインなのか」を
必ず shape で確認してから操作することが重要です。
軸の方向を間違えると平均・集計の結果が完全に逆転します。
分析・モデル化の考え方
shape・ndim・dtype・size の4属性は NumPy 配列の「住所」です。
配列操作の前に print(array.shape) を1行追加する習慣を持てば、
axis の方向ミスによるバグの大半は防げます。
Python で確認する
# shape / ndim / dtype / size で配列の構造を把握する
print("=== 1次元配列(M1 日次生産数)===")
a1 = prod_2d[0] # shape: (90,)
print(f" shape: {a1.shape} → {a1.shape[0]} 日分")
print(f" ndim : {a1.ndim} → 1次元")
print(f" dtype: {a1.dtype} → 整数型")
print(f" size : {a1.size} → 要素数")
print()
print("=== 2次元配列(全ライン × 全日)===")
print(f" shape: {prod_2d.shape}")
print(f" → 行 = {prod_2d.shape[0]}ライン(M1, M2, M3)")
print(f" → 列 = {prod_2d.shape[1]}日(2024-01-01〜03-30)")
print(f" ndim : {prod_2d.ndim} → 2次元")
print(f" dtype: {prod_2d.dtype}")
print(f" size : {prod_2d.size} → 全要素数({prod_2d.shape[0]} × {prod_2d.shape[1]})")
print()
print("=== 不良率配列(float64)===")
print(f" shape: {defect_rates_2d.shape}, dtype: {defect_rates_2d.dtype}")
print()
# shape を使った axis の確認
print("=== axis=0 vs axis=1 の違い(np.sum で確認)===")
sum_axis0 = prod_2d.sum(axis=0) # 各日の全ライン合計 → shape: (90,)
sum_axis1 = prod_2d.sum(axis=1) # 各ラインの全日合計 → shape: (3,)
print(f" axis=0(列方向、日ごと合計): shape={sum_axis0.shape} 先頭3日={sum_axis0[:3]}")
print(f" axis=1(行方向、ライン合計): shape={sum_axis1.shape} 値={sum_axis1}")
print()
for i, line in enumerate(lines):
print(f" {line} 90日間合計生産: {int(sum_axis1[i]):,} 個")
=== 1次元配列(M1 日次生産数)===
shape: (90,) → 90 日分
ndim : 1 → 1次元
dtype: int32 → 整数型
size : 90 → 要素数
=== 2次元配列(全ライン × 全日)===
shape: (3, 90)
→ 行 = 3ライン(M1, M2, M3)
→ 列 = 90日(2024-01-01〜03-30)
ndim : 2 → 2次元
dtype: int32
size : 270 → 全要素数(3 × 90)
=== 不良率配列(float64)===
shape: (3, 90), dtype: float64
=== axis=0 vs axis=1 の違い(np.sum で確認)===
axis=0(列方向、日ごと合計): shape=(90,) 先頭3日=[1414 1416 1369]
axis=1(行方向、ライン合計): shape=(3,) 値=[40387 34339 49581]
M1-精密軸 90日間合計生産: 40,387 個
M2-ベアリング 90日間合計生産: 34,339 個
M3-フランジ 90日間合計生産: 49,581 個
結果の読み取り
prod_2d.shape = (3, 90) から「行 = 3ライン、列 = 90日」が確認できます。
axis=0 は行方向(列をまたいで集計)なので「各日の全ライン合計」、
axis=1 は列方向(行をまたいで集計)なので「各ラインの全日合計」になります。
np.mean(prod_2d, axis=1) で「ライン別の日次平均生産数」が計算できます。
操作前に必ず shape を確認し、axis の方向を意識することが重要です。
No.065:配列の要素を取り出す
実務での意味
「M2ラインの第2週のデータだけ取り出す」「全ラインの1月1日のデータを比較する」
というような 特定日・特定ラインへのアクセス は製造データ分析で日常的に必要です。
NumPy のインデックスとスライスを使えば、これを簡潔に記述できます。
分析・モデル化の考え方
NumPy のスライスは view(参照)を返します。コピーが必要な場合は .copy() を使います。
ファンシーインデックス(整数配列・ブール配列によるインデックス)はコピーを返します。
データ加工の際にどちらを使っているかを意識することが、バグを防ぐ鍵です。
Python で確認する
# インデックス・スライス・2次元アクセスで要素を取り出す
prod_m1 = prod_2d[0] # M1ライン, shape: (90,)
print("=== 1次元インデックスアクセス ===")
print(f" 初日 [0] : {prod_m1[0]:,} 個")
print(f" 末日 [-1] : {prod_m1[-1]:,} 個")
print(f" 10日目 [9] : {prod_m1[9]:,} 個")
print()
print("=== スライスで週単位に取り出す ===")
week1 = prod_m1[:7] # 第1週(1/1〜1/7)
week2 = prod_m1[7:14] # 第2週(1/8〜1/14)
print(f" 第1週 ( 1/ 1〜 1/ 7): {week1} 合計={int(week1.sum()):,}個")
print(f" 第2週 ( 1/ 8〜 1/14): {week2} 合計={int(week2.sum()):,}個")
print()
print("=== 2次元配列のアクセス ===")
print(f" M1 の1日目 prod_2d[0, 0] = {prod_2d[0, 0]:,} 個")
print(f" M2 の1日目 prod_2d[1, 0] = {prod_2d[1, 0]:,} 個")
print(f" 全ラインの1日目 prod_2d[:, 0] = {prod_2d[:, 0]} (各ライン生産数)")
print(f" M3 の全90日 prod_2d[2, :].shape = {prod_2d[2, :].shape}")
print()
print("=== スライスは view → .copy() でコピー ===")
sub = prod_m1[10:15].copy() # コピーを取得
original_val = prod_m1[10]
sub[0] = 9999
print(f" sub[0] を 9999 に変更 → 元配列 prod_m1[10] = {prod_m1[10]} (変化なし)")
=== 1次元インデックスアクセス ===
初日 [0] : 468 個
末日 [-1] : 464 個
10日目 [9] : 440 個
=== スライスで週単位に取り出す ===
第1週 ( 1/ 1〜 1/ 7): [468 458 444 437 450 468 448] 合計=3,173個
第2週 ( 1/ 8〜 1/14): [452 440 440 453 465 469 453] 合計=3,172個
=== 2次元配列のアクセス ===
M1 の1日目 prod_2d[0, 0] = 468 個
M2 の1日目 prod_2d[1, 0] = 394 個
全ラインの1日目 prod_2d[:, 0] = [468 394 552] (各ライン生産数)
M3 の全90日 prod_2d[2, :].shape = (90,)
=== スライスは view → .copy() でコピー ===
sub[0] を 9999 に変更 → 元配列 prod_m1[10] = 453 (変化なし)
結果の読み取り
prod_2d[:, 0] は「全ライン(すべての行)の1日目(列0)」というスライスで、
3ライン全員の当日生産数をまとめて取り出しています。
.copy() でコピーを作成することで、元の配列への影響を防止できました。
sub[0] = 9999 とした後も prod_m1[10] の値が変わっていないことで
コピーが正しく機能していることが確認できます。
No.066:配列に対して一括計算する
実務での意味
NumPy のブロードキャスト機能により、形の異なる配列同士も
自動的に形を合わせて演算できます。
unit_prices(形 (3,))を (3, 1) に変形すれば、
(3, 90) の生産数配列と掛け算して、
全ライン・全日分の売上を ループなしで一括計算 できます。
分析・モデル化の考え方
ブロードキャストのルール: 形が異なる2つの配列を演算するとき、
各次元が「同じか1かどちらか」であれば、形が1の次元が自動的に拡張されます。
(3,1) × (3,90) → (3,90) という変換が自動で行われます。
Python で確認する
# ベクトル演算とブロードキャストで 270件の KPI を一括計算する
# 良品数(ベクトル減算)
good_units_2d = prod_2d - defect_2d # shape: (3, 90)
# 売上(ブロードキャスト: unit_prices (3,) → (3,1) に変形)
revenue_2d = good_units_2d * unit_prices.reshape(3, 1) # shape: (3, 90)
# 損失(不良数 × 単価 × (1 + 廃棄コスト率))
penalty = 0.30
loss_2d = defect_2d * unit_prices.reshape(3, 1) * (1 + penalty) # shape: (3, 90)
print("=== ベクトル演算の結果(各ライン先頭3日)===")
print(f"{'ライン':<14} {'日':>4} {'生産':>6} {'不良':>5} {'不良率(%)':>10} {'良品売上':>12} {'不良損失':>10}")
print("-" * 66)
for i, line in enumerate(lines):
for j in range(3):
dr = float(defect_rates_2d[i, j])
print(f"{line if j==0 else '':<14} {j+1:>4} {int(prod_2d[i,j]):>6,} "
f"{int(defect_2d[i,j]):>5} {dr:>9.2f}% "
f"{int(revenue_2d[i,j]):>12,} {int(loss_2d[i,j]):>10,}")
print()
print("=== 週次集計(スライス + sum)===")
for i, line in enumerate(lines):
w1_prod = int(prod_2d[i, :7].sum())
w1_revenue = int(revenue_2d[i, :7].sum())
w1_loss = int(loss_2d[i, :7].sum())
print(f" {line}: 第1週生産 {w1_prod:,}個 / 売上 {w1_revenue:,}円 / 損失 {w1_loss:,}円")
print()
print("=== 全期間・全ライン合計 ===")
print(f" 総生産数 : {int(prod_2d.sum()):,} 個")
print(f" 総売上 : {int(revenue_2d.sum()):,} 円")
print(f" 総損失 : {int(loss_2d.sum()):,} 円")
=== ベクトル演算の結果(各ライン先頭3日)===
ライン 日 生産 不良 不良率(%) 良品売上 不良損失
------------------------------------------------------------------
M1-精密軸 1 468 8 1.71% 1,104,000 24,960
2 458 7 1.53% 1,082,400 21,840
3 444 9 2.03% 1,044,000 28,080
M2-ベアリング 1 394 7 1.78% 696,600 16,380
2 392 11 2.81% 685,800 25,740
3 364 7 1.92% 642,600 16,380
M3-フランジ 1 552 7 1.27% 654,000 10,920
2 566 8 1.41% 669,600 12,480
3 561 9 1.60% 662,400 14,040
=== 週次集計(スライス + sum)===
M1-精密軸: 第1週生産 3,173個 / 売上 7,473,600円 / 損失 184,080円
M2-ベアリング: 第1週生産 2,701個 / 売上 4,753,800円 / 損失 140,400円
M3-フランジ: 第1週生産 3,850個 / 売上 4,554,000円 / 損失 85,800円
=== 全期間・全ライン合計 ===
総生産数 : 124,307 個
総売上 : 214,207,200 円
総損失 : 5,237,700 円
結果の読み取り
unit_prices.reshape(3, 1) によってブロードキャストが機能し、
(3, 90) の配列全体に各ラインの単価が正しく掛けられました。
ループなしで270件(3ライン × 90日)の売上・損失が瞬時に計算されています。
prod_2d.sum() は配列全体の合計(全ライン・全日の総生産数)を返します。
prod_2d.sum(axis=1) にすればライン別合計、axis=0 なら日別合計になります。
No.067:平均値を計算する
実務での意味
np.mean() に axis を指定することで、
「ライン別の平均不良率」と「日別の全ライン平均不良率」を
同じ関数で使い分けることができます。
月別の集計も month_idx とブールマスクを組み合わせれば
ループなしで実現できます。
分析・モデル化の考え方
axis=0 は「行を潰して列方向に集計」→ 日別の集計(90個の結果)
axis=1 は「列を潰して行方向に集計」→ ライン別の集計(3個の結果)
加重平均が必要な場合は np.average(data, weights=weights) を使います。
Python で確認する
# np.mean() で軸を指定した集計
# ライン別の平均不良率(各行の平均)
line_mean_dr = np.mean(defect_rates_2d, axis=1) # shape: (3,)
# 日別の全ライン平均不良率(各列の平均)
day_mean_dr = np.mean(defect_rates_2d, axis=0) # shape: (90,)
print("=== ライン別 平均不良率(axis=1)===")
for i, line in enumerate(lines):
print(f" {line}: {line_mean_dr[i]:.3f}%")
print()
# 月別平均(month_idx を使ったブールマスク集計)
month_names_j = ["1月", "2月", "3月"]
print(f"=== ライン別・月別平均不良率 ===")
print(f"{'ライン':<14} {'1月':>8} {'2月':>8} {'3月':>8}")
print("-" * 42)
monthly_avg_067 = np.array([
[np.mean(defect_rates_2d[i][month_idx == m]) for m in range(3)]
for i in range(3)
])
for i, line in enumerate(lines):
row = " ".join(f"{monthly_avg_067[i, m]:.2f}%" for m in range(3))
print(f" {line:<14} {row}")
print()
print(f" 日別平均(先頭5日): {day_mean_dr[:5].round(3)}")
=== ライン別 平均不良率(axis=1)===
M1-精密軸: 1.844%
M2-ベアリング: 2.244%
M3-フランジ: 1.434%
=== ライン別・月別平均不良率 ===
ライン 1月 2月 3月
------------------------------------------
M1-精密軸 1.83% 1.86% 1.84%
M2-ベアリング 2.23% 2.26% 2.24%
M3-フランジ 1.43% 1.41% 1.46%
日別平均(先頭5日): [1.585 1.916 1.851 1.807 1.92 ]
# 月別平均不良率の比較棒グラフ
fig, ax = plt.subplots(figsize=(10, 5))
x = np.arange(3)
width = 0.28
colors_067 = ["#4C72B0", "#DD8452", "#55A868"]
for i, line in enumerate(lines):
ax.bar(x + (i - 1) * width, monthly_avg_067[i], width,
label=line, color=colors_067[i], alpha=0.88)
ax.axhline(2.0, color="red", linewidth=1.5, linestyle="--", alpha=0.7,
label="警告ライン(2.0%)")
ax.set_title("製品ライン別 月次平均不良率(np.mean + axis 集計)",
fontsize=13, pad=10)
ax.set_xlabel("月", fontsize=11)
ax.set_ylabel("平均不良率(%)", fontsize=11)
ax.set_xticks(x)
ax.set_xticklabels(month_names_j)
ax.legend(fontsize=10)
ax.grid(axis="y", alpha=0.3)
plt.tight_layout()
plt.show()
結果の読み取り
axis=1 で (3, 90) 配列の各行(ライン)の平均を計算し、
3つのライン別平均不良率を1行で取得できました。
棒グラフから月ごとの不良率変化をライン横断で比較できます。
M2-ベアリングは全月で警告ライン(2.0%)に近い水準で推移しており、
改善優先ラインとして特定できます。
月次トレンドが改善傾向か悪化傾向かも視覚的に確認できます。
No.068:最大値・最小値を計算する
実務での意味
「不良率が最も高かった日はいつか」「生産数が最も少なかった日はどのラインか」
という質問に答えるには、最大・最小値とそのインデックス(何日目か)の両方が必要です。
np.argmax() / np.argmin() を使えば、値と日付を同時に特定できます。
分析・モデル化の考え方
np.max() は最大値を返しますが、「どの位置か」は np.argmax() で取得します。
日次インデックス(0始まり)から実際の日付への変換には
base_date + datetime.timedelta(days=idx) を使います。
月内の「ワースト日」を定期的に特定してレポートする処理の自動化に活用できます。
Python で確認する
import datetime
# 全期間の最大・最小不良率
overall_max = float(np.max(defect_rates_2d))
overall_min = float(np.min(defect_rates_2d))
print(f"=== 全ライン・全期間の不良率極値 ===")
print(f" 最大不良率 : {overall_max:.2f}%")
print(f" 最小不良率 : {overall_min:.2f}%")
print()
# ライン別の最悪日・最良日を特定
print(f"{'ライン':<14} {'最大不良率':>10} {'最悪日':>10} {'最小不良率':>10} {'最良日':>10}")
print("=" * 56)
for i, line in enumerate(lines):
max_dr = float(defect_rates_2d[i].max())
min_dr = float(defect_rates_2d[i].min())
max_day = int(np.argmax(defect_rates_2d[i]))
min_day = int(np.argmin(defect_rates_2d[i]))
max_date = (base_date + datetime.timedelta(days=max_day)).strftime("%m/%d")
min_date = (base_date + datetime.timedelta(days=min_day)).strftime("%m/%d")
print(f" {line:<14} {max_dr:>9.2f}% {max_date:>10} {min_dr:>9.2f}% {min_date:>10}")
print()
# 全ライン合計で最も生産が多かった日
daily_total_prod = prod_2d.sum(axis=0) # shape: (90,)
best_day_idx = int(np.argmax(daily_total_prod))
worst_day_idx = int(np.argmin(daily_total_prod))
best_date = (base_date + datetime.timedelta(days=best_day_idx)).strftime("%m/%d")
worst_date = (base_date + datetime.timedelta(days=worst_day_idx)).strftime("%m/%d")
print(f"=== 全ライン合計 生産数の極値 ===")
print(f" 最大生産日: {best_date} ({int(daily_total_prod[best_day_idx]):,} 個)")
print(f" 最小生産日: {worst_date} ({int(daily_total_prod[worst_day_idx]):,} 個)")
=== 全ライン・全期間の不良率極値 ===
最大不良率 : 2.89%
最小不良率 : 0.90%
ライン 最大不良率 最悪日 最小不良率 最良日
========================================================
M1-精密軸 2.55% 01/15 1.32% 03/20
M2-ベアリング 2.89% 01/18 1.53% 01/11
M3-フランジ 1.94% 01/15 0.90% 03/20
=== 全ライン合計 生産数の極値 ===
最大生産日: 03/02 (1,433 個)
最小生産日: 03/24 (1,322 個)
結果の読み取り
np.argmax(defect_rates_2d[i]) で各ラインの「最悪日」のインデックスが取れ、
base_date + timedelta(days=idx) で実際の日付に変換しました。
最悪日(最大不良率日)がラインによって異なる場合、原因分析は個別に実施する必要があります。
同じ日に複数ラインの不良率が高い場合は、共通要因(材料ロット・作業員・環境)の
調査を優先すると効率的です。
No.069:標準偏差を計算する
実務での意味
不良率の平均が同じでも、ばらつき(標準偏差) が大きいラインは
日によって品質が安定しておらず、管理が難しい状態です。
変動係数(CV = 標準偏差 / 平均) を使うと、
単位や水準が異なるライン同士の安定性を横比較できます。
分析・モデル化の考え方
np.std(data) は 母標準偏差(分母 = n)を返します。
サンプルを用いた推定には np.std(data, ddof=1)(標本標準偏差、分母 = n-1)を使います。
製造現場では「今月の全生産日 = 母集団」として母標準偏差を使うケースが多いです。
寸法測定のような 工程能力指数(Cp / Cpk) の計算では標準偏差が基本指標になります。
Python で確認する
# np.std(), np.var() で品質安定性を評価する
print(f"{'ライン':<14} {'平均不良率':>10} {'標準偏差':>10} {'変動係数CV':>12} {'安定性評価':>12}")
print("=" * 62)
for i, line in enumerate(lines):
mean_v = float(np.mean(defect_rates_2d[i]))
std_v = float(np.std(defect_rates_2d[i])) # 母標準偏差
cv_pct = std_v / mean_v * 100
stability = "◎ 安定" if cv_pct < 10 else ("○ 普通" if cv_pct < 20 else "△ 不安定")
print(f" {line:<14} {mean_v:>9.2f}% {std_v:>9.3f}% {cv_pct:>10.1f}% {stability:>12}")
print()
# 寸法検査:工程能力指数(Cp)の計算
print("=== M1-精密軸 軸径の工程能力評価 ===")
mu_m = float(np.mean(measurements))
sigma_m = float(np.std(measurements)) # 母標準偏差
usl = target_diam + tolerance # 上限規格値
lsl = target_diam - tolerance # 下限規格値
cp = (usl - lsl) / (6 * sigma_m) # 工程能力指数 Cp
cpk = min(usl - mu_m, mu_m - lsl) / (3 * sigma_m) # Cpk(偏り考慮)
print(f" 目標値: {target_diam:.3f} mm 公差: ±{tolerance:.3f} mm")
print(f" 測定平均: {mu_m:.4f} mm 標準偏差: {sigma_m:.4f} mm")
print(f" Cp = {cp:.2f} (1.33 以上で合格: {'✅ 合格' if cp >= 1.33 else '❌ 要改善'})")
print(f" Cpk = {cpk:.2f} (1.33 以上で合格: {'✅ 合格' if cpk >= 1.33 else '❌ 要改善'})")
print()
n_ng = int(np.sum((measurements < lsl) | (measurements > usl)))
print(f" 公差外品: {n_ng} 個 / {len(measurements)} 個")
ライン 平均不良率 標準偏差 変動係数CV 安定性評価
==============================================================
M1-精密軸 1.84% 0.268% 14.6% ○ 普通
M2-ベアリング 2.24% 0.319% 14.2% ○ 普通
M3-フランジ 1.43% 0.217% 15.1% ○ 普通
=== M1-精密軸 軸径の工程能力評価 ===
目標値: 25.000 mm 公差: ±0.050 mm
測定平均: 24.9983 mm 標準偏差: 0.0165 mm
Cp = 1.01 (1.33 以上で合格: ❌ 要改善)
Cpk = 0.98 (1.33 以上で合格: ❌ 要改善)
公差外品: 0 個 / 30 個
結果の読み取り
変動係数(CV)を見ると、M3-フランジは CV が高い場合、
平均不良率は低くても日ごとのばらつきが大きい「不安定なライン」であることがわかります。
工程能力指数 Cp は「規格幅 / (6σ)」で計算されます。
Cp ≥ 1.33(工程能力が十分)の場合、理論的に不良品率は 66ppm(0.0066%)以下です。
Cpk は工程の中心ズレも考慮した指数で、Cp > Cpk の場合は
「ばらつきは小さいが平均が目標からずれている」ことを意味します。
No.070:条件を満たすデータを抽出する
実務での意味
「不良率が警告ライン(2.0%)を超えた日を自動抽出して報告する」という処理は、
製造データパイプラインで最も頻繁に必要となる操作の1つです。
NumPy のブールインデックスと np.where() を使えば、
条件フィルタリングをベクトル演算で高速に実行できます。
分析・モデル化の考え方
array[array > 閾値] はブールインデックスによるフィルタリングで、
条件を満たす要素だけを新しい配列として返します(コピー)。
np.where(条件, 真の値, 偽の値) は条件に応じて2つの値を選択する
三項演算子の配列版 です。管理グラフの警告ラベル自動付与などに活用できます。
Python で確認する
# ブールインデックスと np.where() で警告日を抽出する
threshold = 2.0 # 警告ライン(不良率 2.0%)
print("=== ブールインデックスによる警告日抽出 ===")
for i, line in enumerate(lines):
alert_mask = defect_rates_2d[i] > threshold # boolean array, shape: (90,)
alert_count = int(alert_mask.sum())
if alert_count > 0:
alert_dr = defect_rates_2d[i][alert_mask] # ファンシーインデックス
print(f" {line}: 警告日 {alert_count:2d}日 / {n_days}日 "
f"最大 {float(alert_dr.max()):.2f}% 警告日平均 {float(alert_dr.mean()):.2f}%")
else:
print(f" {line}: 警告日 0日 / {n_days}日 (全日正常)")
print()
# np.where() でラベルを付与(M1 先頭10日)
labels_m1 = np.where(defect_rates_2d[0] > threshold, "🔴 警告", "✅ 正常")
print("=== np.where() による状態ラベル(M1, 先頭10日)===")
for j in range(10):
d = (base_date + datetime.timedelta(days=j)).strftime("%m/%d")
print(f" {d}: {defect_rates_2d[0, j]:.2f}% {labels_m1[j]}")
print()
# 複数ラインが同時に警告した日
simultaneous = int(np.sum(np.sum(defect_rates_2d > threshold, axis=0) >= 2))
print(f"2ライン以上が同日に警告した日数: {simultaneous} 日")
=== ブールインデックスによる警告日抽出 ===
M1-精密軸: 警告日 26日 / 90日 最大 2.55% 警告日平均 2.17%
M2-ベアリング: 警告日 71日 / 90日 最大 2.89% 警告日平均 2.36%
M3-フランジ: 警告日 0日 / 90日 (全日正常)
=== np.where() による状態ラベル(M1, 先頭10日)===
01/01: 1.71% ✅ 正常
01/02: 1.53% ✅ 正常
01/03: 2.03% 🔴 警告
01/04: 2.06% 🔴 警告
01/05: 2.00% ✅ 正常
01/06: 2.14% 🔴 警告
01/07: 1.56% ✅ 正常
01/08: 1.77% ✅ 正常
01/09: 1.36% ✅ 正常
01/10: 1.59% ✅ 正常
2ライン以上が同日に警告した日数: 21 日
# 全ライン日次不良率推移 + 警告日ハイライト(3パネル)
fig, axes = plt.subplots(3, 1, figsize=(12, 8), sharex=True)
colors_070 = ["#4C72B0", "#DD8452", "#55A868"]
day_range = np.arange(n_days)
for i, (ax, line) in enumerate(zip(axes, lines)):
dr = defect_rates_2d[i]
alert_mask = dr > threshold
ax.plot(day_range, dr, color=colors_070[i], linewidth=1.5, alpha=0.7)
ax.scatter(day_range[alert_mask], dr[alert_mask],
color="red", zorder=5, s=45, label=f"警告日({int(alert_mask.sum())}日)")
ax.axhline(threshold, color="red", linestyle="--", linewidth=1.0, alpha=0.6)
ax.set_title(f"{line} ─ 警告日: {int(alert_mask.sum())} 日 / {n_days} 日", fontsize=11)
ax.set_ylabel("不良率(%)", fontsize=10)
ax.legend(fontsize=9)
ax.grid(alpha=0.3)
axes[-1].set_xlabel("日次インデックス(0 = 2024-01-01)", fontsize=11)
plt.suptitle("製品ライン別 日次不良率推移と警告日抽出(np.where / ブールインデックス)",
fontsize=13, y=1.01)
plt.tight_layout()
plt.show()
結果の読み取り
赤点が「不良率 > 2.0%」の警告日です。
defect_rates_2d[i][alert_mask] のブールインデックスで、
条件を満たす日のデータだけを取り出し、最大値・平均値を計算しました。
グラフで視覚的に警告日のパターンを確認できます。
「週末前に不良率が上がる」「特定の時期にクラスター状に発生する」などの
パターンが見えた場合、原因(設備メンテナンス・素材ロット・作業員)を
追跡するための手がかりになります。
対象ノックを通して見える実務上の示唆
No.061〜070 で学んだ NumPy の基礎は、製造データ分析の 高速化と精度向上 の基盤です。
1. ベクトル演算 = 「コードの簡潔化」と「スケーラビリティ」
for ループで書いた不良率計算が1行になるだけでなく、
ライン数が3本→30本に増えても (30, 90) の配列が (3, 90) と同じコードで動きます。
製造データのスケールアップに強いコードが書けます。
2. axis の指定 = 「集計粒度の制御」
axis=0(日別集計)と axis=1(ライン別集計)を使い分けることで、
「どの次元を残すか」を自分でコントロールできます。
「月別・ライン別・シフト別」などの多次元集計は
NumPy の axis と pandas の groupby の組み合わせで実現します。
3. ブールインデックス = 「動的フィルタ」
警告ライン・管理限界・公差の閾値を変数にしておくことで、
defect_rates_2d[defect_rates_2d > threshold] の1行が
「閾値変更による再集計」に即対応できます。
閾値をパラメータ化することが保守性の高い品質管理システムの設計です。
4. 工程能力指数(Cp / Cpk)の自動計算
np.std と公差設計値から Cp / Cpk を自動計算する処理は、
定期品質報告書の自動生成に直結します。
第9章(Polars によるデータ加工)では複数工程のデータを
一括で処理する仕組みを構築します。
実務導入する場合に必要なこと
本章で学んだ NumPy を製造データ分析システムに導入する際の手順です。
Step 1: CSV データの NumPy 配列変換(約半日)
既存の Excel / CSV データを np.loadtxt() または np.genfromtxt() で読み込みます。
欠損値がある場合は np.genfromtxt(file, filling_values=np.nan) を使います。
Step 2: 2次元配列の設計(約1日)
「どの軸がラインでどの軸が日付か」を最初に決め、
(n_lines, n_days) の統一フォーマットで全データを管理します。
軸設計を統一することで axis 指定のミスが防げます。
Step 3: 閾値パラメータのファイル化(約半日)
警告ライン・管理限界・公差などの閾値を CSV やJSON ファイルから読み込む設計にします。
コードを修正せずに閾値変更だけで再計算できるようになります。
Step 4: 工程能力指数の自動レポート化(約1日)
本章 No.069 の Cp / Cpk 計算を全ラインに適用し、
月次品質報告書を自動生成するスクリプトを構築します。
第10章(集計・可視化・ミニ分析)ではこれをより本格的なレポートに発展させます。
まとめ
本章(No.061〜070)で学んだ内容を整理します。
| No. | スキル | 製造現場での活用 |
|---|---|---|
| 061 | NumPyの役割を理解する | Python リストとの速度・可読性比較、ベクトル演算の実感 |
| 062 | NumPyをimportする | 主要関数の一覧と製造データ分析での使い所の把握 |
| 063 | NumPy配列を作成する | 生産数・不良数・寸法測定値の配列化 |
| 064 | 配列の形を確認する | shape/ndim/dtype/size で2次元構造の把握 |
| 065 | 配列の要素を取り出す | インデックス・スライス・view vs copy の理解 |
| 066 | 配列に対して一括計算する | ブロードキャストで270件の KPI を1行計算 |
| 067 | 平均値を計算する | axis 指定でライン別・月別平均を一括算出・棒グラフ可視化 |
| 068 | 最大値・最小値を計算する | argmax/argmin で最悪日・最良日の日付特定 |
| 069 | 標準偏差を計算する | 変動係数で安定性評価・Cp/Cpk で工程能力指数の算出 |
| 070 | 条件を満たすデータを抽出する | ブールインデックスで警告日自動抽出・散布図で可視化 |
第8章(No.071〜080)では Polars を使って、
本章の NumPy 配列を DataFrame として扱い、
列名付きの集計・フィルタリング・CSV 読み込みを学びます。
法人向けのご相談
数理工房 では、製造業・DX 推進担当者向けの Python 研修・データ分析支援を提供しています。
こんなお悩みはありませんか?
- 「本章の不良率・工程能力計算を自社の実データで動かしたい」
- 「NumPy で品質管理システムを自動化したい」
- 「管理図(Xbar-R 管理図)・工程能力指数の自動算出ツールを作りたい」
- 「社内の Python 研修をカスタマイズして製造業向けに実施してほしい」
提供サービス
| サービス | 概要 |
|---|---|
| 製造業向け Python 研修 | 現場データを使った実践型研修(オンライン/対面) |
| 品質管理自動化システム | 不良率・Cp/Cpk・管理図の自動算出・レポート化 |
| センサーデータ解析基盤 | 時系列データの異常検知・予兆保全システム構築 |
| DX 推進コンサルティング | 課題整理から実装・定着支援まで一貫対応 |
📩 お問い合わせ: surikobo.co.jp/contact
まずはお気軽にご相談ください。