100本ノック / 行列 / 行列100本ノック

工程負荷と生産計画を「行列」で見える化する:製造業の意思決定で学ぶ行列の基礎

工程負荷と生産計画を「行列」で見える化する

製造業の意思決定で学ぶ行列の基礎:100本ノック No.001〜No.010

製造業では、週別・製品別の需要、製品別・工程別の標準時間、工程別の能力など、二つの軸を持つデータを日常的に扱います。これらを行列として整理すると、Excelの表を眺めるだけでは見落としやすい関係を、再現可能な計算へ変えられます。

本シリーズ「行列100本ノック」は、行列の基礎から線形代数、固有値、最適化・機械学習、製造シミュレーション、実践的な高速計算までを全100問で学びます。第1回の本記事では、精密部品工場の生産計画を題材に、行列・ベクトル・加算・乗算・転置・単位行列・対角行列・ブロック行列・行列の表現力・NumPyを扱います。目的は記号を覚えることではなく、「どの行が何を表し、どの列が何を表すか」を定義し、工程能力や原価に関する判断へつなげることです。

[!NOTE] 本資料は、数理工房 (もしくは代表である和山個人) が過去に企業研修において使用した notebook を企業様の許可を得て再構成・編集のうえ公開しています。
掲載データはすべて架空のものであり、実在する企業・工場・数値とは一切関係ありません。

はじめに:この記事で扱う製造業の実務課題

架空の精密部品工場では、ギアA、シャフトB、ハウジングCを、切削・熱処理・研削・検査の工程で生産しています。生産管理担当者は毎週、通常需要と特急受注を満たしながら、各工程の稼働可能時間を超えない計画を作らなければなりません。

今回の実務課題は、複数の表に分かれた情報を行列として接続し、どの週の、どの工程がボトルネックになるかを早期に把握することです。最終的には、需要、歩留まり、標準時間、能力、原価を一貫した軸で計算できる状態を目指します。

現場でよくある状況

  • 営業の需要表は「週 × 製品」、製造技術の標準時間表は「製品 × 工程」、工場の能力表は「週 × 工程」で管理されている
  • 特急受注や設備保全によって、需要と能力が週ごとに変わる
  • 製品名や工程名の並び順がファイルごとに異なり、計算は合っていても対応関係を誤ることがある
  • 合計生産数だけでは工程負荷が分からず、ボトルネックが計画確定後に判明する

行列は、このような「軸を持つ複数の表」を結びつける共通言語です。

なぜこの問題は判断が難しいのか

製品数を nn、工程数を pp、期間数を mm とすると、需要だけで m×nm \times n 個、標準時間で n×pn \times p 個の値があります。さらに歩留まり、原価、能力が加わり、手計算では関係を追いにくくなります。

また、行列計算は数値だけでなく次元と順序が意味を持ちます。たとえば週別製品数量 QRm×nQ \in \mathbb{R}^{m\times n} と製品別工程時間 RRn×pR \in \mathbb{R}^{n\times p}QRQR と掛けられますが、列の製品順が一致していなければ、計算自体は動いても業務上は誤答です。したがって、形状・単位・ラベルを同時に管理する必要があります。

今回扱うノックの全体像

No.テーマ製造業での確認内容
001行列とは何か週別・製品別の生産計画を一つの表で表す
002ベクトルとは何か特定週の需要や製品別KPIを一本の軸で表す
003行列の足し算通常需要と特急受注を合算する
004行列の掛け算製品数量から工程負荷時間を算出する
005転置視点を「週×製品」から「製品×週」へ切り替える
006単位行列値を変えない変換と計算検証に使う
007対角行列製品別歩留まり・原価を混ぜずに適用する
008ブロック行列複数ラインの構造を一つの大きな行列へ統合する
009行列の表現力需要から能力・原価までを連鎖して表現する
010NumPy入門実務で使える配列演算・集計・可視化を行う

すべて同じ架空工場データを用い、前のノックの結果を次の判断へつなげます。

Python 環境の準備

数値計算には NumPy、ラベル付きの表には pandas、可視化には Matplotlib を使います。グラフの日本語表示には japanize-matplotlib を利用します。外部データは読み込みません。乱数は np.random.default_rng(42) で固定し、何度実行しても同じ架空データが得られるようにします。

import platform

import japanize_matplotlib
import matplotlib
import matplotlib.pyplot as plt
from matplotlib_inline.backend_inline import set_matplotlib_formats
import numpy as np
import pandas as pd

set_matplotlib_formats("svg")
pd.set_option("display.max_columns", 20)
pd.set_option("display.width", 120)
np.set_printoptions(precision=2, suppress=True)

SEED = 42
rng = np.random.default_rng(SEED)

print("Python     :", platform.python_version())
print("NumPy      :", np.__version__)
print("pandas     :", pd.__version__)
print("Matplotlib :", matplotlib.__version__)
print("乱数シード :", SEED)
Python     : 3.13.1
NumPy      : 2.5.1
pandas     : 3.0.3
Matplotlib : 3.11.0
乱数シード : 42

架空データの作成

6週間、3製品、4工程の小規模な計画データを作成します。通常需要は基準需要に週次変動と小さな乱数を加え、特急受注は営業情報を模した固定値とします。標準時間は「1個あたり分」、工程能力は「週あたり分」、原価は「円/個」です。

実務ではマスタの版管理、欠損値、単位、製品・工程コードの対応確認が先に必要です。本記事では行列の考え方へ集中するため、整合済みの架空マスタを使います。

weeks = [f"第{i}週" for i in range(1, 7)]
products = ["ギアA", "シャフトB", "ハウジングC"]
processes = ["切削", "熱処理", "研削", "検査"]

base_level = np.array([920, 740, 560])
weekly_factor = np.array([0.96, 1.00, 1.04, 1.08, 1.02, 1.12])[:, None]
normal_demand = np.rint(
    base_level * weekly_factor + rng.normal(0, 24, size=(6, 3))
).astype(int)
rush_orders = np.array([
    [0, 0, 0], [80, 0, 0], [0, 60, 0],
    [0, 0, 90], [40, 40, 0], [100, 0, 70],
])
total_demand = normal_demand + rush_orders

# 製品 × 工程の標準時間(分/個)
routing_minutes = np.array([
    [1.60, 0.90, 1.20, 0.35],
    [1.80, 0.70, 1.50, 0.40],
    [2.20, 1.30, 0.80, 0.50],
])

# 週 × 工程の稼働可能時間(分/週)
capacity_minutes = np.array([
    [4000, 2500, 3000, 1050], [4000, 2500, 3000, 1050],
    [3800, 2500, 3000, 1050], [4000, 2200, 3000, 1050],
    [4000, 2500, 2700, 1050], [4200, 2500, 3100, 1100],
])

yield_rate = np.array([0.980, 0.965, 0.985])
unit_cost = np.array([1250, 1680, 1420])

product_master = pd.DataFrame({
    "歩留まり": yield_rate,
    "標準原価_円個": unit_cost,
}, index=products)
product_master
歩留まり 標準原価_円個
ギアA 0.980 1250
シャフトB 0.965 1680
ハウジングC 0.985 1420
demand_df = pd.DataFrame(total_demand, index=weeks, columns=products)
routing_df = pd.DataFrame(routing_minutes, index=products, columns=processes)
capacity_df = pd.DataFrame(capacity_minutes, index=weeks, columns=processes)

print("総需要(個):週 × 製品")
display(demand_df)
print("\n標準時間(分/個):製品 × 工程")
display(routing_df)
print("\n工程能力(分/週):週 × 工程")
display(capacity_df)
総需要(個):週 × 製品
ギアA シャフトB ハウジングC
第1週 891 685 556
第2週 1023 693 529
第3週 960 822 582
第4週 973 820 713
第5週 980 822 582
第6週 1110 838 674
標準時間(分/個):製品 × 工程
切削 熱処理 研削 検査
ギアA 1.6 0.9 1.2 0.35
シャフトB 1.8 0.7 1.5 0.40
ハウジングC 2.2 1.3 0.8 0.50
工程能力(分/週):週 × 工程
切削 熱処理 研削 検査
第1週 4000 2500 3000 1050
第2週 4000 2500 3000 1050
第3週 3800 2500 3000 1050
第4週 4000 2200 3000 1050
第5週 4000 2500 2700 1050
第6週 4200 2500 3100 1100

No.001:行列とは何か

実務での意味

行列は、同じ二つの軸を持つ数値を長方形に並べたものです。ここでは行を週、列を製品とし、総需要を DR6×3D \in \mathbb{R}^{6\times3} で表します。要素 dijd_{ij} は「第 ii 週に製品 jj が何個必要か」という業務上の意味を持ちます。形状を明記すると、表同士を接続できるかを事前に確認できます。

分析・モデル化の考え方

行列を作る際は、値より先に行・列・単位を定義します。本例では、

D=[dij],DR6×3,[dij]=D = \left[d_{ij}\right], \quad D \in \mathbb{R}^{6\times3}, \quad [d_{ij}] = \text{個}

です。行列は単なる表ではなく、加算や乗算の規則を持つため、後続の工程負荷計算を一括処理できます。

Pythonで確認する

shape で次元を確認し、ヒートマップで週・製品ごとの数量水準を見ます。

print("行列 D の形状:", total_demand.shape)
print("第4週・ハウジングC d[3, 2]:", total_demand[3, 2], "個")

fig, ax = plt.subplots(figsize=(7.2, 3.8))
im = ax.imshow(total_demand, cmap="Blues", aspect="auto")
ax.set_title("週別・製品別の総需要行列")
ax.set_xlabel("製品")
ax.set_ylabel("週")
ax.set_xticks(range(len(products)), products)
ax.set_yticks(range(len(weeks)), weeks)
for i in range(len(weeks)):
    for j in range(len(products)):
        ax.text(j, i, f"{total_demand[i, j]:,}", ha="center", va="center")
ax.grid(False)
fig.colorbar(im, ax=ax, label="需要量(個)")
plt.tight_layout()
plt.show()
行列 D の形状: (6, 3)
第4週・ハウジングC d[3, 2]: 713 個


svg

結果の読み取り

需要行列は (6, 3)、つまり6週間 × 3製品です。色の濃いセルほど数量が多く、第6週は複数製品で需要が高いことが分かります。現場では、この時点で「高需要のセル」を把握できますが、工程の負荷超過までは判断できません。標準時間行列と接続する必要があります。

No.002:ベクトルとは何か

実務での意味

ベクトルは一つの軸に沿って数値を並べたものです。たとえば、第6週の3製品の需要は3次元の行ベクトル、製品別歩留まりは3次元のベクトルとして表せます。予算、在庫、単価、KPIなども、対象の並び順をそろえればベクトルになります。

分析・モデル化の考え方

第6週の需要ベクトルを d6=(d61,d62,d63)\boldsymbol{d}_6 = (d_{61},d_{62},d_{63}) とします。ベクトルでは大きさだけでなく要素の順序が重要です。[ギアA, シャフトB, ハウジングC] の順序をマスタとして固定することで、別のベクトルや行列と安全に計算できます。

Pythonで確認する

第6週の需要ベクトルを取り出し、製品構成比まで確認します。

week6_vector = total_demand[5]
week6_share = week6_vector / week6_vector.sum()
week6_df = pd.DataFrame({
    "需要量_個": week6_vector,
    "構成比_pct": week6_share * 100,
}, index=products)
display(week6_df.round(1))

fig, ax = plt.subplots(figsize=(6.8, 3.8))
ax.bar(products, week6_vector, color=["#4C78A8", "#F58518", "#54A24B"])
ax.set_title("第6週の製品別需要ベクトル")
ax.set_xlabel("製品")
ax.set_ylabel("需要量(個)")
ax.grid(axis="y", alpha=0.3)
plt.tight_layout()
plt.show()
需要量_個 構成比_pct
ギアA 1110 42.3
シャフトB 838 32.0
ハウジングC 674 25.7

svg

結果の読み取り

第6週の需要はギアAが最大ですが、必要工程時間は製品ごとに異なります。数量が最大の製品が必ずしもボトルネックの主因とは限りません。ベクトルは「製品別の状態」を簡潔に表し、後で標準時間行列と掛けるための入力になります。

No.003:行列の足し算

実務での意味

通常需要と特急受注のように、同じ週 × 製品、同じ単位で管理された表は要素ごとに加算できます。営業部門の追加情報を既存計画へ反映する、複数顧客の受注を統合する、といった処理が該当します。

分析・モデル化の考え方

通常需要行列を BB、特急受注行列を EE とすると、総需要行列は

D=B+E,dij=bij+eijD = B + E, \qquad d_{ij}=b_{ij}+e_{ij}

です。加算には BBEE の形状・行列ラベル・単位が一致する必要があります。「円」と「個」、「月次」と「週次」はそのまま足せません。

Pythonで確認する

特急受注の影響が大きい週を、通常需要との積み上げ棒グラフで確認します。

assert normal_demand.shape == rush_orders.shape
assert np.array_equal(normal_demand + rush_orders, total_demand)

addition_summary = pd.DataFrame({
    "通常需要_個": normal_demand.sum(axis=1),
    "特急受注_個": rush_orders.sum(axis=1),
    "総需要_個": total_demand.sum(axis=1),
}, index=weeks)
display(addition_summary)

fig, ax = plt.subplots(figsize=(7.2, 3.8))
ax.bar(weeks, addition_summary["通常需要_個"], label="通常需要")
ax.bar(weeks, addition_summary["特急受注_個"], bottom=addition_summary["通常需要_個"], label="特急受注")
ax.set_title("通常需要への特急受注の加算")
ax.set_xlabel("週")
ax.set_ylabel("需要量(個)")
ax.grid(axis="y", alpha=0.3)
ax.legend()
plt.tight_layout()
plt.show()
通常需要_個 特急受注_個 総需要_個
第1週 2132 0 2132
第2週 2165 80 2245
第3週 2304 60 2364
第4週 2416 90 2506
第5週 2304 80 2384
第6週 2452 170 2622

svg

結果の読み取り

第6週は通常需要自体が高いうえに特急受注も加わり、総需要が最も大きくなります。加算は簡単な演算ですが、追加受注を別表のまま見ていると能力への影響を過小評価しやすいため、同じ軸へ統合してから負荷計算へ渡すことが重要です。

No.004:行列の掛け算

実務での意味

行列の掛け算を使うと、製品別数量から工程別負荷を一括計算できます。これは生産計画、要員計画、材料所要量計算、原価配賦の基本形です。

分析・モデル化の考え方

需要行列 DR6×3D \in \mathbb{R}^{6\times3} と、標準時間行列 RR3×4R \in \mathbb{R}^{3\times4} の積を

L=DR,lik=j=13dijrjkL = DR, \qquad l_{ik}=\sum_{j=1}^{3} d_{ij}r_{jk}

とすると、LR6×4L \in \mathbb{R}^{6\times4} は週 × 工程の負荷時間です。内側の次元「3製品」が一致し、積の中で集約されます。単位は「個 × 分/個 = 分」です。

Pythonで確認する

@ 演算子で負荷を求め、工程能力との比率を計算します。

workload_minutes = total_demand @ routing_minutes
load_ratio = workload_minutes / capacity_minutes
load_ratio_df = pd.DataFrame(load_ratio * 100, index=weeks, columns=processes)

print("工程負荷率(%)")
display(load_ratio_df.round(1))
print("最大負荷率:", f"{load_ratio.max() * 100:.1f}%")

fig, ax = plt.subplots(figsize=(7.4, 3.8))
im = ax.imshow(load_ratio * 100, cmap="RdYlGn_r", vmin=60, vmax=120, aspect="auto")
ax.set_title("週別・工程別の負荷率")
ax.set_xlabel("工程")
ax.set_ylabel("週")
ax.set_xticks(range(len(processes)), processes)
ax.set_yticks(range(len(weeks)), weeks)
for i in range(len(weeks)):
    for j in range(len(processes)):
        ax.text(j, i, f"{load_ratio[i, j] * 100:.0f}%", ha="center", va="center")
ax.grid(False)
fig.colorbar(im, ax=ax, label="負荷率(%)")
plt.tight_layout()
plt.show()
工程負荷率(%)
切削 熱処理 研削 検査
第1週 97.0 80.2 84.7 82.3
第2週 101.2 83.7 89.7 85.7
第3週 113.1 87.8 95.0 91.0
第4週 115.0 108.0 98.9 97.6
第5週 108.2 88.6 106.5 91.7
第6週 113.5 98.5 100.9 96.4
最大負荷率: 115.0%


svg

結果の読み取り

数量表だけでは見えなかった工程別の逼迫度が、週 × 工程の負荷率へ変換されました。100%を超えるセルは現有能力だけでは処理できない計画です。特に切削工程は複数週で高負荷となるため、残業・外注・前倒し・受注納期調整の候補を比較する必要があります。

No.005:転置

実務での意味

同じ数値でも、会議の目的によって見たい軸は変わります。生産管理は週ごとの製品構成を見たい一方、製品責任者は製品ごとの週次推移を見たいことがあります。転置は、行と列を入れ替えて視点を変える操作です。

分析・モデル化の考え方

DR6×3D \in \mathbb{R}^{6\times3} の転置 DTR3×6D^\mathsf{T} \in \mathbb{R}^{3\times6} は、

(DT)ji=Dij,(DT)T=D(D^\mathsf{T})_{ji}=D_{ij}, \qquad (D^\mathsf{T})^\mathsf{T}=D

を満たします。値や単位は変わりませんが、行と列の意味が入れ替わります。ベクトル・行列積の向きを整えるときにも不可欠です。

Pythonで確認する

元の需要表と転置後の表を比べ、二度転置すると元に戻ることを検証します。

demand_transposed = total_demand.T
transposed_df = pd.DataFrame(demand_transposed, index=products, columns=weeks)

print("転置前:", total_demand.shape, "(週 × 製品)")
print("転置後:", demand_transposed.shape, "(製品 × 週)")
print("二度転置すると元に戻る:", np.array_equal(demand_transposed.T, total_demand))
display(transposed_df)

product_peak = transposed_df.idxmax(axis=1).to_frame("需要ピーク週")
product_peak["ピーク需要_個"] = transposed_df.max(axis=1)
display(product_peak)
転置前: (6, 3) (週 × 製品)
転置後: (3, 6) (製品 × 週)
二度転置すると元に戻る: True
第1週 第2週 第3週 第4週 第5週 第6週
ギアA 891 1023 960 973 980 1110
シャフトB 685 693 822 820 822 838
ハウジングC 556 529 582 713 582 674
需要ピーク週 ピーク需要_個
ギアA 第6週 1110
シャフトB 第6週 838
ハウジングC 第4週 713

結果の読み取り

転置後は各製品が行になり、製品ごとの需要推移やピーク週を横方向に追いやすくなります。全製品のピークが同じ週に重なる場合、平準化の余地は小さくなります。転置は「表示だけ」の操作に見えますが、分析の問いに合わせて軸を切り替える基本操作です。

No.006:単位行列

実務での意味

単位行列は、掛けても元の値を変えない行列です。変換処理の初期状態、計算ロジックのテスト、複数変換の一部を無効化する場合に使えます。設備・製品の対応を表す変換を段階的に構築するときの基準にもなります。

分析・モデル化の考え方

3製品に対応する単位行列 I3I_3 は対角要素が1、その他が0で、

DI3=D,I3x=xDI_3=D, \qquad I_3\boldsymbol{x}=\boldsymbol{x}

を満たします。これは数の掛け算における1と同じ役割です。行列処理の途中で製品順が崩れていないかを確認する最小テストとして利用できます。

Pythonで確認する

需要行列へ単位行列を掛け、差分がゼロであることを確認します。

identity_products = np.eye(len(products), dtype=int)
unchanged_demand = total_demand @ identity_products
difference = unchanged_demand - total_demand

print("3次の単位行列 I₃")
display(pd.DataFrame(identity_products, index=products, columns=products))
print("D @ I₃ と D の最大絶対差:", np.abs(difference).max())
print("完全に一致:", np.array_equal(unchanged_demand, total_demand))
3次の単位行列 I₃
ギアA シャフトB ハウジングC
ギアA 1 0 0
シャフトB 0 1 0
ハウジングC 0 0 1
D @ I₃ と D の最大絶対差: 0
完全に一致: True

結果の読み取り

D @ I₃ は元の需要行列と完全に一致し、最大差は0です。実務システムでは、この性質を回帰テストに使えます。変換なしの設定で値が変わるなら、製品順、データ型、丸め、欠損補完など別の処理が混入している可能性があります。

No.007:対角行列

実務での意味

製品ごとに異なる歩留まりや単価を適用し、他製品の値を混ぜたくない場合、対角行列が役立ちます。対角要素に製品別係数を置けば、列ごとに異なる倍率を一つの行列積として適用できます。

分析・モデル化の考え方

歩留まりベクトル y\boldsymbol{y} から対角行列 Y=diag(y)Y=\operatorname{diag}(\boldsymbol{y}) を作ると、良品需要相当は DYDY です。また、需要を満たすための投入数は、歩留まりの逆数を対角に置いて、

Q=Ddiag(1/y1,1/y2,1/y3)Q = D\operatorname{diag}(1/y_1,1/y_2,1/y_3)

と求められます。実際の計画では整数個へ切り上げます。

Pythonで確認する

歩留まり損失を考慮した必要投入数と、標準原価ベクトルによる週次投入原価を計算します。

inverse_yield_matrix = np.diag(1 / yield_rate)
required_input = np.ceil(total_demand @ inverse_yield_matrix).astype(int)
weekly_input_cost = required_input @ unit_cost

yield_summary = pd.DataFrame({
    "総需要_個": total_demand.sum(axis=0),
    "必要投入数_個": required_input.sum(axis=0),
    "歩留まり損失見込_個": required_input.sum(axis=0) - total_demand.sum(axis=0),
}, index=products)
display(yield_summary)
display(pd.DataFrame({"週次投入原価_円": weekly_input_cost}, index=weeks).style.format("{:,.0f}"))

fig, ax = plt.subplots(figsize=(7.0, 3.8))
x = np.arange(len(products))
ax.bar(x - 0.18, yield_summary["総需要_個"], width=0.36, label="良品需要")
ax.bar(x + 0.18, yield_summary["必要投入数_個"], width=0.36, label="必要投入数")
ax.set_title("歩留まりを考慮した製品別必要投入数")
ax.set_xlabel("製品")
ax.set_ylabel("6週間合計(個)")
ax.set_xticks(x, products)
ax.grid(axis="y", alpha=0.3)
ax.legend()
plt.tight_layout()
plt.show()
総需要_個 必要投入数_個 歩留まり損失見込_個
ギアA 5937 6060 123
シャフトB 4680 4852 172
ハウジングC 3636 3694 58
  週次投入原価_円
第1週 3,132,600
第2週 3,276,880
第3週 3,495,580
第4週 3,697,330
第5週 3,520,580
第6週 3,848,870

svg

結果の読み取り

歩留まりが100%未満であるため、顧客需要と同数だけ投入しても不足します。特に歩留まりの低いシャフトBは、数量差を計画へ明示的に織り込む必要があります。対角行列を使うと、製品別係数を保ったまま、需要・原価・負荷の計算体系へ組み込めます。

No.008:ブロック行列

実務での意味

複数ライン、複数工場、製品群別のモデルを統合するとき、既存の小さな行列を区画として並べたブロック行列が使えます。ライン間で工程を共有しない場合は、対角ブロックに各ラインの係数を置き、非対角ブロックを0にします。

分析・モデル化の考え方

東ラインと西ラインの「製品 × 工程」標準時間を RE,RWR_E, R_W とし、相互利用がないと仮定すると、統合行列は

Rall=[RE00RW]R_{\mathrm{all}}= \begin{bmatrix} R_E & 0\\ 0 & R_W \end{bmatrix}

です。非対角ブロックを0以外にすれば、応援生産や共通工程の関係も表現できます。ブロック構造は組織・設備の独立性を見える形にします。

Pythonで確認する

東ラインの標準時間と、それより5%速い西ラインを仮定してブロック対角行列を作ります。

east_routing = routing_minutes
west_routing = routing_minutes * 0.95
zero_block = np.zeros_like(routing_minutes)
combined_routing = np.block([
    [east_routing, zero_block],
    [zero_block, west_routing],
])

row_labels = [f"東_{p}" for p in products] + [f"西_{p}" for p in products]
column_labels = [f"東_{p}" for p in processes] + [f"西_{p}" for p in processes]
print("統合標準時間行列の形状:", combined_routing.shape)
display(pd.DataFrame(combined_routing, index=row_labels, columns=column_labels))

fig, ax = plt.subplots(figsize=(8.0, 4.3))
im = ax.imshow(combined_routing, cmap="YlOrBr", aspect="auto")
ax.set_title("東西2ラインのブロック標準時間行列")
ax.set_xlabel("ライン・工程")
ax.set_ylabel("ライン・製品")
ax.set_xticks(range(len(column_labels)), column_labels, rotation=45, ha="right")
ax.set_yticks(range(len(row_labels)), row_labels)
ax.axvline(3.5, color="black", linewidth=1.2)
ax.axhline(2.5, color="black", linewidth=1.2)
ax.grid(False)
fig.colorbar(im, ax=ax, label="標準時間(分/個)")
plt.tight_layout()
plt.show()
統合標準時間行列の形状: (6, 8)
東_切削 東_熱処理 東_研削 東_検査 西_切削 西_熱処理 西_研削 西_検査
東_ギアA 1.6 0.9 1.2 0.35 0.00 0.000 0.000 0.0000
東_シャフトB 1.8 0.7 1.5 0.40 0.00 0.000 0.000 0.0000
東_ハウジングC 2.2 1.3 0.8 0.50 0.00 0.000 0.000 0.0000
西_ギアA 0.0 0.0 0.0 0.00 1.52 0.855 1.140 0.3325
西_シャフトB 0.0 0.0 0.0 0.00 1.71 0.665 1.425 0.3800
西_ハウジングC 0.0 0.0 0.0 0.00 2.09 1.235 0.760 0.4750

svg

結果の読み取り

統合行列は6製品行 × 8工程列となり、左上と右下だけに値があります。これは東西ラインが独立しているという仮定を明示しています。実務では、共有設備やライン間移管がある場合に非対角ブロックへ係数を追加し、どこまで代替生産できるかをモデルへ反映します。

No.009:行列の表現力

実務での意味

行列の価値は、一つの表を保存することではなく、需要・歩留まり・工程負荷・能力・原価を共通の軸で連鎖できる点にあります。条件を変えた複数シナリオも同じ式で比較でき、計画会議の前提を明確にできます。

分析・モデル化の考え方

需要 DD、歩留まり補正 Y1Y^{-1}、標準時間 RR、能力 CC を用いると、投入ベースの負荷率は要素ごとの除算 \oslash を使って、

U=(DY1R)CU = (DY^{-1}R) \oslash C

と表せます。ここでは「基準」「需要10%増」「能力10%減」の3シナリオを同じ計算関数へ通し、最大負荷率と能力超過セル数をKPIにします。

Pythonで確認する

シナリオごとの最大工程負荷率、能力超過セル数、総投入原価を比較します。

def evaluate_scenario(demand_factor=1.0, capacity_factor=1.0):
    scenario_demand = np.ceil(total_demand * demand_factor)
    scenario_input = scenario_demand @ inverse_yield_matrix
    scenario_workload = scenario_input @ routing_minutes
    scenario_ratio = scenario_workload / (capacity_minutes * capacity_factor)
    scenario_cost = (scenario_input @ unit_cost).sum()
    return {
        "最大負荷率_pct": scenario_ratio.max() * 100,
        "能力超過セル数": int((scenario_ratio > 1).sum()),
        "総投入原価_百万円": scenario_cost / 1_000_000,
    }

scenario_results = pd.DataFrame({
    "基準": evaluate_scenario(),
    "需要10%増": evaluate_scenario(demand_factor=1.10),
    "能力10%減": evaluate_scenario(capacity_factor=0.90),
}).T
display(scenario_results.round(2))

fig, ax = plt.subplots(figsize=(7.0, 3.8))
colors = ["#4C78A8", "#E45756", "#F58518"]
ax.bar(scenario_results.index, scenario_results["最大負荷率_pct"], color=colors)
ax.axhline(100, color="black", linestyle="--", label="能力上限 100%")
ax.set_title("シナリオ別の最大工程負荷率")
ax.set_xlabel("シナリオ")
ax.set_ylabel("最大負荷率(%)")
ax.grid(axis="y", alpha=0.3)
ax.legend()
plt.tight_layout()
plt.show()
最大負荷率_pct 能力超過セル数 総投入原価_百万円
基準 117.76 10.0 20.96
需要10%増 129.66 17.0 23.07
能力10%減 130.85 18.0 20.96

svg

結果の読み取り

基準シナリオでも能力超過があり、需要増または能力減では最大負荷率と超過箇所がさらに増えます。この結果は「計画が厳しい」という感覚を、最大負荷率・超過セル数・投入原価という比較可能なKPIへ変えます。ただし、残業や仕掛在庫、段取り、ロット制約は未反映なので、実務判断では次段階のモデル拡張が必要です。

No.010:NumPy入門

実務での意味

NumPyは行列・ベクトルを配列として扱い、同じ処理を多くの週・製品・工程へまとめて適用できます。セル単位のループや数式コピーを減らせるため、計画条件が変わったときも再計算しやすく、検証可能な業務ロジックを作れます。

分析・モデル化の考え方

実務でまず使う操作は、shape による形状確認、@ による行列積、sum(axis=...) による軸別集計、ブール条件による抽出、where による判定です。axis=0 は行方向に集約して列別の結果を、axis=1 は列方向に集約して行別の結果を返します。

ここでは歩留まり補正後の工程負荷を再計算し、週ごとの最大負荷工程と、対策優先度を一覧化します。

Pythonで確認する

NumPyのベクトル化演算で、最終的な工程負荷ダッシュボードを作ります。

input_workload = required_input @ routing_minutes
input_load_ratio = input_workload / capacity_minutes
max_process_index = np.argmax(input_load_ratio, axis=1)
max_ratio_by_week = np.max(input_load_ratio, axis=1)
priority = np.where(max_ratio_by_week > 1.10, "緊急対策", np.where(max_ratio_by_week > 1.00, "要調整", "能力内"))

dashboard = pd.DataFrame({
    "総需要_個": total_demand.sum(axis=1),
    "最大負荷工程": np.array(processes)[max_process_index],
    "最大負荷率_pct": max_ratio_by_week * 100,
    "判定": priority,
}, index=weeks)
display(dashboard.round({"最大負荷率_pct": 1}))

fig, ax = plt.subplots(figsize=(8.0, 4.3))
for j, process in enumerate(processes):
    ax.plot(weeks, input_load_ratio[:, j] * 100, marker="o", label=process)
ax.axhline(100, color="black", linestyle="--", label="能力上限 100%")
ax.set_title("歩留まり補正後の週別・工程別負荷率")
ax.set_xlabel("週")
ax.set_ylabel("負荷率(%)")
ax.grid(alpha=0.3)
ax.legend(ncol=3)
plt.tight_layout()
plt.show()

print("全配列に有限値のみ:", np.isfinite(input_load_ratio).all())
print("能力超過セル数:", int(np.count_nonzero(input_load_ratio > 1)))
総需要_個 最大負荷工程 最大負荷率_pct 判定
第1週 2132 切削 99.4 能力内
第2週 2245 切削 103.7 要調整
第3週 2364 切削 115.8 緊急対策
第4週 2506 切削 117.8 緊急対策
第5週 2384 切削 110.8 緊急対策
第6週 2622 切削 116.3 緊急対策

svg

全配列に有限値のみ: True
能力超過セル数: 10

結果の読み取り

各週の最大負荷工程と判定を同じ表で確認でき、第6週を中心に対策優先度が高いことが分かります。折れ線グラフでは、どの工程が継続的に上限へ近づくかも確認できます。NumPyにより、入力行列を差し替えるだけで同じロジックを全期間へ再適用できます。

ここで重要なのは、@ が自動的に業務上正しい対応を保証するわけではない点です。NumPy配列はラベルを持たないため、計算前に製品・工程の並び順と単位を検証し、出力後に異常値・欠損・能力超過件数を確認する運用が必要です。

対象ノックを通して見える実務上の示唆

  1. 表の軸を定義すると部門間データを接続できる:週 × 製品、製品 × 工程、週 × 工程という形状を明示すると、営業需要から製造負荷までの関係が追跡できます。
  2. 数量の多さと負荷の大きさは同じではない:製品ごとの標準時間が異なるため、数量表だけでボトルネックを判断すると誤ります。
  3. 歩留まりは投入側へ反映する必要がある:良品需要をそのまま工程負荷へ使うと、必要投入数と負荷を過小評価します。
  4. 行列は仮定を構造として表現できる:ブロック行列の0は、ライン間の代替生産がないという業務仮定そのものです。
  5. 同じ式でシナリオを比較できる:需要増や能力減を入力変更として扱えば、会議ごとに計算方法が変わらず、説明可能性が高まります。

実務導入する場合に必要なこと

  • マスタ整備:製品コード、工程コード、標準時間、歩留まり、原価の責任部署と更新頻度を決める
  • 粒度と単位の統一:日・週・月、個・ロット・kg、分・時間を混在させず、変換規則を明文化する
  • ラベル検証:NumPyへ変換する前に、pandas等で行・列順を明示的にそろえる
  • 能力の定義:カレンダー時間ではなく、休憩、保全、段取り、稼働率を考慮した実効能力を用いる
  • モデル拡張:ロットサイズ、仕掛在庫、納期、段取り順序、外注、残業上限など、意思決定に必要な制約を追加する
  • 検証と運用:計画負荷と実績工数の差を継続測定し、標準時間や歩留まりを改訂する

小さな対象工程で試行し、現場担当者と数値の定義・結果の妥当性を確認してから対象範囲を広げることが現実的です。

まとめ

No.001〜No.010では、週別製品需要を起点に、行列とベクトルの意味、加算、乗算、転置、単位行列、対角行列、ブロック行列、行列の表現力、NumPyの基本操作を確認しました。

行列計算の核心は、複雑な公式ではなく、行・列・単位・順序の意味を明確にすることです。これができれば、需要を工程負荷へ変換し、歩留まりや能力を加味し、代替案を同じKPIで比較できます。次の段階では、ブロードキャスト、計算量、疎行列などを学び、より大規模な製造データを効率的に扱う土台へ進みます。

法人向けのご相談

数理工房では、製造業の生産計画、工程負荷の見える化、在庫・要員・設備能力の分析、数理最適化、シミュレーション、社内データ人材育成をご支援しています。

「表計算による計画業務を再現可能にしたい」「標準時間と実績の差を分析したい」「どの工程へ投資すべきか定量的に比較したい」といった課題について、データの棚卸しから小規模な検証、業務実装までご相談いただけます。

📩 お問い合わせ: surikobo.co.jp/contact
まずはお気軽にご相談ください。