#!/usr/bin/env python3 # erzeuge_fluch_durchschnitt.py """ Erzeugt das Diagramm zum Fluch des Durchschnitts: bilder_04/kap_unsicherheit_durchschnitt.svg Die Handrechnung des Abschnitts vergleicht zwei Plaene fuer dieselbe unsichere Nachfrage. Das Bild zeigt, warum der eine schlechter ist - und zwar nicht als Behauptung, sondern als Kurve: die erwarteten Kosten ueber ALLEN moeglichen Bestellmengen. Was man daran sieht: Die Kurve hat einen Knick an jedem Szenario, faellt links flach und steigt rechts steil - Fehlmenge kostet 120 EUR, Ueberschuss nur 5. In dieser Schieflage liegt der ganze Effekt. Ihr Minimum liegt deshalb NICHT beim Erwartungswert der Nachfrage, sondern rechts davon. Ohne Formel gesagt: Mit dem Mittelwert zu planen unterstellt, dass sich zu viel und zu wenig gegenseitig aufheben. Sie tun es nicht. Alle Werte werden gerechnet. pruefe_gegen_handrechnung() vergleicht sie mit den im Kapitel abgedruckten Zwischenergebnissen - weicht eines ab, bricht das Skript ab. Aufruf (aus dem Repository-Wurzelverzeichnis): python3 bilder_04/erzeuge_fluch_durchschnitt.py Benoetigt: numpy, matplotlib """ from __future__ import annotations import os import sys import numpy as np sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from stil_04 import FARBEN, neue_figur, speichere # noqa: E402 # Die Instanz der Handrechnung: (Bedarf, Wahrscheinlichkeit) SZENARIEN = [(100, 0.50), (250, 0.30), (500, 0.20)] NAMEN = ["Ruhig", "Volatil", "Crash"] VORAB = 40.0 # EUR je Einheit im Voraus FEHLT = 120.0 # EUR je fehlender Einheit, kurzfristig UEBRIG = 5.0 # EUR je ueberschuessiger Einheit, Verwaltung # Aus dem Kapitel abgedruckt - nur zum Gegenpruefen. ERWARTET_IM_BUCH = {225: 16_812.50, 250: 16_375.00} def kosten(x: float) -> float: """Erwartete Gesamtkosten bei Bestellmenge x.""" return sum(p * (VORAB * x + FEHLT * max(bedarf - x, 0.0) + UEBRIG * max(x - bedarf, 0.0)) for bedarf, p in SZENARIEN) def pruefe_gegen_handrechnung() -> None: for x, erwartet in ERWARTET_IM_BUCH.items(): ist = kosten(x) if abs(ist - erwartet) > 0.01: raise SystemExit( f"Fluch des Durchschnitts: Bei x={x} ergibt die Rechnung " f"{ist:,.2f} EUR, die Handrechnung im Kapitel nennt " f"{erwartet:,.2f} EUR.") def zeichne(mittelwert: float, bestes_x: float) -> None: figur, achse = neue_figur(breite=8.0, hoehe=4.6) x = np.linspace(0, 560, 1200) y = np.array([kosten(float(k)) for k in x]) achse.plot(x, y, "-", color=FARBEN["haupt"], linewidth=2.4, zorder=4, label="erwartete Gesamtkosten") # Die Szenarien als senkrechte Marken - dort knickt die Kurve. for (bedarf, p), name in zip(SZENARIEN, NAMEN): achse.axvline(bedarf, color=FARBEN["linie"], linewidth=0.9, linestyle=":", zorder=2) achse.text(bedarf, achse.get_ylim()[0], f" {name}\n {p:.0%}", fontsize=8.5, color=FARBEN["gedaempft"], va="bottom") for stelle, farbe, beschriftung in ( (mittelwert, FARBEN["fehler"], f"Planung mit dem Mittelwert\n$x = {mittelwert:.0f}$ " f"{kosten(mittelwert):,.0f} €"), (bestes_x, FARBEN["gut"], f"stochastisch optimal\n$x = {bestes_x:.0f}$ " f"{kosten(bestes_x):,.0f} €")): achse.plot([stelle], [kosten(stelle)], "o", color=farbe, markersize=9, zorder=6) achse.annotate(beschriftung.replace(",", "."), xy=(stelle, kosten(stelle)), xytext=(14 if farbe == FARBEN["gut"] else -14, 44), textcoords="offset points", fontsize=9, color=farbe, fontweight="bold", ha="left" if farbe == FARBEN["gut"] else "right", arrowprops=dict(arrowstyle="->", color=farbe, linewidth=1.2)) mehrkosten = kosten(mittelwert) - kosten(bestes_x) achse.annotate( f"Der Mittelwertplan kostet {mehrkosten:,.0f} € mehr —\n" f"nicht weil er ungenau ist, sondern weil\n" f"Fehlmenge {FEHLT:.0f} € kostet und Überschuss nur " f"{UEBRIG:.0f} €.".replace(",", "."), xy=(0.03, 0.95), xycoords="axes fraction", fontsize=9, color=FARBEN["text"], va="top") achse.set_xlabel("vorab gekaufte Kapazität $x$") achse.set_ylabel("erwartete Gesamtkosten in €") achse.set_title("Warum der Mittelwert der falsche Plan ist", fontsize=10.5, color=FARBEN["text"]) achse.set_xlim(0, 560) speichere(figur, "kap_unsicherheit_durchschnitt") if __name__ == "__main__": pruefe_gegen_handrechnung() mittelwert = sum(bedarf * p for bedarf, p in SZENARIEN) gitter = np.arange(0, 561, 1.0) bestes_x = float(gitter[int(np.argmin([kosten(float(k)) for k in gitter]))]) print(f" Erwartungswert der Nachfrage: {mittelwert:.0f}") print(f" Kosten dort: {kosten(mittelwert):,.2f} EUR") print(f" bestes x: {bestes_x:.0f}") print(f" Kosten dort: {kosten(bestes_x):,.2f} EUR") zeichne(mittelwert, bestes_x)