mirror of
https://github.com/kuhyx/praca_magisterska.git
synced 2026-07-04 12:03:01 +02:00
621 lines
25 KiB
Python
621 lines
25 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Generate diagrams for PYTANIE 31: Interaktywne wspomaganie decyzji w warunkach ryzyka.
|
||
|
||
Diagrams:
|
||
1. Payoff matrix + all criteria results comparison (bar chart)
|
||
2. Regret matrix construction step-by-step
|
||
3. Hurwicz α interpolation between maximax and maximin
|
||
4. Decision criteria mnemonic map
|
||
5. Expected value criterion with probability-weighted bars
|
||
6. Decision conditions spectrum (pewność → ryzyko → niepewność)
|
||
|
||
All: A4-compatible, B&W, 300 DPI, laser-printer-friendly.
|
||
"""
|
||
|
||
import matplotlib
|
||
matplotlib.use('Agg')
|
||
import matplotlib.pyplot as plt
|
||
import matplotlib.patches as mpatches
|
||
from matplotlib.patches import FancyBboxPatch
|
||
import numpy as np
|
||
import os
|
||
|
||
DPI = 300
|
||
BG = 'white'
|
||
LN = 'black'
|
||
FS = 8
|
||
FS_TITLE = 11
|
||
OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img')
|
||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||
|
||
GRAY1 = '#E8E8E8'
|
||
GRAY2 = '#D0D0D0'
|
||
GRAY3 = '#B8B8B8'
|
||
GRAY4 = '#F5F5F5'
|
||
GRAY5 = '#C0C0C0'
|
||
|
||
|
||
def draw_box(ax, x, y, w, h, text, fill='white', lw=1.2, fontsize=FS,
|
||
fontweight='normal', ha='center', va='center', rounded=True):
|
||
if rounded:
|
||
rect = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.05",
|
||
lw=lw, edgecolor=LN, facecolor=fill)
|
||
else:
|
||
rect = mpatches.Rectangle((x, y), w, h, lw=lw, edgecolor=LN, facecolor=fill)
|
||
ax.add_patch(rect)
|
||
ax.text(x + w/2, y + h/2, text, ha=ha, va=va, fontsize=fontsize,
|
||
fontweight=fontweight, wrap=True)
|
||
|
||
|
||
def draw_arrow(ax, x1, y1, x2, y2, lw=1.2, style='->', color=LN):
|
||
ax.annotate("", xy=(x2, y2), xytext=(x1, y1),
|
||
arrowprops=dict(arrowstyle=style, color=color, lw=lw))
|
||
|
||
|
||
# ============================================================
|
||
# 1. PAYOFF MATRIX + ALL CRITERIA BAR CHART
|
||
# ============================================================
|
||
def draw_criteria_comparison():
|
||
fig, axes = plt.subplots(1, 2, figsize=(8.27, 4.5), gridspec_kw={'width_ratios': [1.2, 1]})
|
||
|
||
# -- Left: Payoff matrix as styled table --
|
||
ax = axes[0]
|
||
ax.axis('off')
|
||
ax.set_xlim(0, 6)
|
||
ax.set_ylim(0, 6)
|
||
ax.set_title('Macierz wypłat (tys. zł)', fontsize=FS_TITLE, fontweight='bold', pad=8)
|
||
|
||
# Headers
|
||
headers_col = ['', 'S₁\n(dobra)', 'S₂\n(średnia)', 'S₃\n(zła)']
|
||
rows = [
|
||
['A₁ (fabryka)', '200', '50', '−100'],
|
||
['A₂ (sklep)', '80', '70', '40'],
|
||
['A₃ (obligacje)', '30', '30', '30'],
|
||
]
|
||
|
||
col_w = [1.8, 1.2, 1.2, 1.2]
|
||
row_h = 0.7
|
||
start_y = 4.5
|
||
start_x = 0.2
|
||
|
||
# Draw header row
|
||
x = start_x
|
||
for j, h in enumerate(headers_col):
|
||
fill = GRAY2 if j > 0 else GRAY3
|
||
rect = mpatches.Rectangle((x, start_y), col_w[j], row_h, lw=1, edgecolor=LN, facecolor=fill)
|
||
ax.add_patch(rect)
|
||
ax.text(x + col_w[j]/2, start_y + row_h/2, h, ha='center', va='center',
|
||
fontsize=FS, fontweight='bold')
|
||
x += col_w[j]
|
||
|
||
# Draw data rows
|
||
for i, row in enumerate(rows):
|
||
x = start_x
|
||
y = start_y - (i+1) * row_h
|
||
for j, val in enumerate(row):
|
||
fill = GRAY4 if j == 0 else ('white' if i % 2 == 0 else GRAY1)
|
||
# Highlight negative
|
||
if val.startswith('−'):
|
||
fill = '#D8D8D8'
|
||
rect = mpatches.Rectangle((x, y), col_w[j], row_h, lw=1, edgecolor=LN, facecolor=fill)
|
||
ax.add_patch(rect)
|
||
fw = 'bold' if j == 0 else 'normal'
|
||
ax.text(x + col_w[j]/2, y + row_h/2, val, ha='center', va='center',
|
||
fontsize=FS, fontweight=fw)
|
||
x += col_w[j]
|
||
|
||
# Probability row for EV
|
||
x = start_x
|
||
y = start_y - 4 * row_h
|
||
probs = ['p (dla E[X]):', '0.5', '0.3', '0.2']
|
||
for j, val in enumerate(probs):
|
||
fill = GRAY5 if j > 0 else GRAY3
|
||
rect = mpatches.Rectangle((x, y), col_w[j], row_h * 0.7, lw=1, edgecolor=LN, facecolor=fill)
|
||
ax.add_patch(rect)
|
||
ax.text(x + col_w[j]/2, y + row_h*0.35, val, ha='center', va='center',
|
||
fontsize=7, fontweight='bold', style='italic')
|
||
x += col_w[j]
|
||
|
||
# -- Right: Bar chart comparing criteria results --
|
||
ax2 = axes[1]
|
||
criteria = ['E[X]', 'Laplace', 'Maximax', 'Maximin', 'Hurwicz\nα=0.6', 'Savage']
|
||
|
||
# Recalculate with probabilities 0.5, 0.3, 0.2
|
||
# E[X]: A1=200*0.5+50*0.3+(-100)*0.2=100+15-20=95
|
||
# A2=80*0.5+70*0.3+40*0.2=40+21+8=69
|
||
# A3=30*0.5+30*0.3+30*0.2=15+9+6=30
|
||
ev = [95, 69, 30]
|
||
laplace = [50, 63.3, 30]
|
||
maximax = [200, 80, 30]
|
||
maximin = [-100, 40, 30]
|
||
hurwicz = [80, 64, 30] # α=0.6
|
||
savage_maxregret = [140, 120, 170] # lower = better
|
||
|
||
# Which alternative wins for each criterion?
|
||
winners = [0, 1, 0, 1, 0, 1] # index of winning alternative
|
||
winner_vals = [95, 63.3, 200, 40, 80, 120]
|
||
|
||
# Display as grouped bar chart - each criterion shows the 3 alternatives
|
||
x_pos = np.arange(len(criteria))
|
||
width = 0.22
|
||
hatches = ['///', '...', 'xxx']
|
||
labels = ['A₁ (fabryka)', 'A₂ (sklep)', 'A₃ (obligacje)']
|
||
|
||
all_vals = [
|
||
[ev[0], laplace[0], maximax[0], maximin[0], hurwicz[0], savage_maxregret[0]],
|
||
[ev[1], laplace[1], maximax[1], maximin[1], hurwicz[1], savage_maxregret[1]],
|
||
[ev[2], laplace[2], maximax[2], maximin[2], hurwicz[2], savage_maxregret[2]],
|
||
]
|
||
|
||
for i in range(3):
|
||
bars = ax2.bar(x_pos + (i - 1) * width, all_vals[i], width,
|
||
label=labels[i], color='white', edgecolor=LN, hatch=hatches[i], lw=0.8)
|
||
|
||
# Mark winners with star
|
||
for c_idx in range(len(criteria)):
|
||
w = winners[c_idx]
|
||
val = all_vals[w][c_idx]
|
||
ax2.text(x_pos[c_idx] + (w - 1) * width, val + 5, '★',
|
||
ha='center', va='bottom', fontsize=10, fontweight='bold')
|
||
|
||
ax2.set_xticks(x_pos)
|
||
ax2.set_xticklabels(criteria, fontsize=7)
|
||
ax2.set_ylabel('Wartość kryterium', fontsize=8)
|
||
ax2.set_title('Porównanie kryteriów', fontsize=FS_TITLE, fontweight='bold', pad=8)
|
||
ax2.legend(fontsize=7, loc='upper right')
|
||
ax2.axhline(y=0, color=LN, lw=0.5, ls='-')
|
||
ax2.spines['top'].set_visible(False)
|
||
ax2.spines['right'].set_visible(False)
|
||
ax2.tick_params(labelsize=7)
|
||
|
||
# Note about Savage
|
||
ax2.text(5, -30, '(Savage: niżej\n= lepiej)', fontsize=6, ha='center',
|
||
va='top', style='italic')
|
||
|
||
plt.tight_layout()
|
||
outpath = os.path.join(OUTPUT_DIR, 'q31_criteria_comparison.png')
|
||
fig.savefig(outpath, dpi=DPI, bbox_inches='tight', facecolor=BG)
|
||
plt.close(fig)
|
||
print(f" Saved: {outpath}")
|
||
|
||
|
||
# ============================================================
|
||
# 2. REGRET MATRIX CONSTRUCTION
|
||
# ============================================================
|
||
def draw_regret_matrix():
|
||
fig, ax = plt.subplots(1, 1, figsize=(8.27, 5))
|
||
ax.axis('off')
|
||
ax.set_xlim(0, 10)
|
||
ax.set_ylim(0, 7)
|
||
ax.set_title('Kryterium Savage\'a — budowa macierzy żalu',
|
||
fontsize=FS_TITLE + 1, fontweight='bold', pad=10)
|
||
|
||
# --- Step 1: Original payoff matrix (left) ---
|
||
ax.text(2.2, 6.3, 'Krok 1: Macierz wypłat', fontsize=9, fontweight='bold',
|
||
ha='center', va='center')
|
||
|
||
col_w = 1.0
|
||
row_h = 0.55
|
||
headers = ['', 'S₁', 'S₂', 'S₃']
|
||
data = [
|
||
['A₁', '200', '50', '−100'],
|
||
['A₂', '80', '70', '40'],
|
||
['A₃', '30', '30', '30'],
|
||
]
|
||
start_x = 0.3
|
||
start_y = 5.5
|
||
|
||
for j, h in enumerate(headers):
|
||
w = 0.7 if j == 0 else col_w
|
||
x = start_x + (0 if j == 0 else 0.7 + (j-1)*col_w)
|
||
rect = mpatches.Rectangle((x, start_y), w, row_h, lw=1, edgecolor=LN, facecolor=GRAY2)
|
||
ax.add_patch(rect)
|
||
ax.text(x + w/2, start_y + row_h/2, h, ha='center', va='center',
|
||
fontsize=FS, fontweight='bold')
|
||
|
||
for i, row in enumerate(data):
|
||
y = start_y - (i+1) * row_h
|
||
for j, val in enumerate(row):
|
||
w = 0.7 if j == 0 else col_w
|
||
x = start_x + (0 if j == 0 else 0.7 + (j-1)*col_w)
|
||
fill = GRAY4 if j == 0 else 'white'
|
||
rect = mpatches.Rectangle((x, y), w, row_h, lw=1, edgecolor=LN, facecolor=fill)
|
||
ax.add_patch(rect)
|
||
ax.text(x + w/2, y + row_h/2, val, ha='center', va='center', fontsize=FS)
|
||
|
||
# Max per column annotation
|
||
max_y = start_y - 3 * row_h - 0.1
|
||
ax.text(start_x + 0.7 + 0.5*col_w, max_y, 'max=200', fontsize=7,
|
||
ha='center', va='top', fontweight='bold', color='#333')
|
||
ax.text(start_x + 0.7 + 1.5*col_w, max_y, 'max=70', fontsize=7,
|
||
ha='center', va='top', fontweight='bold', color='#333')
|
||
ax.text(start_x + 0.7 + 2.5*col_w, max_y, 'max=40', fontsize=7,
|
||
ha='center', va='top', fontweight='bold', color='#333')
|
||
|
||
# Arrow
|
||
ax.annotate("", xy=(5.0, 4.8), xytext=(4.2, 4.8),
|
||
arrowprops=dict(arrowstyle='->', color=LN, lw=2))
|
||
ax.text(4.6, 5.0, 'rᵢⱼ = max − aᵢⱼ', fontsize=8, ha='center', va='bottom',
|
||
fontweight='bold')
|
||
|
||
# --- Step 2: Regret matrix (right) ---
|
||
ax.text(7.5, 6.3, 'Krok 2: Macierz żalu', fontsize=9, fontweight='bold',
|
||
ha='center', va='center')
|
||
|
||
regret_data = [
|
||
['A₁', '0', '20', '140'],
|
||
['A₂', '120', '0', '0'],
|
||
['A₃', '170', '40', '10'],
|
||
]
|
||
headers2 = ['', 'S₁', 'S₂', 'S₃', 'max rᵢ']
|
||
start_x2 = 5.3
|
||
|
||
for j, h in enumerate(headers2):
|
||
w = 0.7 if j == 0 else (0.9 if j < 4 else 1.0)
|
||
x = start_x2
|
||
if j == 0:
|
||
x = start_x2
|
||
elif j <= 3:
|
||
x = start_x2 + 0.7 + (j-1)*0.9
|
||
else:
|
||
x = start_x2 + 0.7 + 3*0.9
|
||
rect = mpatches.Rectangle((x, start_y), w, row_h, lw=1, edgecolor=LN,
|
||
facecolor=GRAY2 if j < 4 else GRAY3)
|
||
ax.add_patch(rect)
|
||
ax.text(x + w/2, start_y + row_h/2, h, ha='center', va='center',
|
||
fontsize=FS, fontweight='bold')
|
||
|
||
max_regrets = [140, 120, 170]
|
||
for i, row in enumerate(regret_data):
|
||
y = start_y - (i+1) * row_h
|
||
for j, val in enumerate(row):
|
||
w = 0.7 if j == 0 else 0.9
|
||
x = start_x2 + (0 if j == 0 else 0.7 + (j-1)*0.9)
|
||
fill = GRAY4 if j == 0 else 'white'
|
||
# Highlight the max regret cell
|
||
if j > 0 and int(val) == max_regrets[i]:
|
||
fill = GRAY2
|
||
rect = mpatches.Rectangle((x, y), w, row_h, lw=1, edgecolor=LN, facecolor=fill)
|
||
ax.add_patch(rect)
|
||
fw = 'bold' if (j > 0 and int(val) == max_regrets[i]) else 'normal'
|
||
ax.text(x + w/2, y + row_h/2, val, ha='center', va='center',
|
||
fontsize=FS, fontweight=fw)
|
||
|
||
# Max regret column
|
||
x = start_x2 + 0.7 + 3*0.9
|
||
w = 1.0
|
||
fill = '#C8C8C8' if max_regrets[i] == min(max_regrets) else GRAY1
|
||
rect = mpatches.Rectangle((x, y), w, row_h, lw=1.5 if max_regrets[i]==min(max_regrets) else 1,
|
||
edgecolor=LN, facecolor=fill)
|
||
ax.add_patch(rect)
|
||
marker = ' ★' if max_regrets[i] == min(max_regrets) else ''
|
||
ax.text(x + w/2, y + row_h/2, f'{max_regrets[i]}{marker}', ha='center', va='center',
|
||
fontsize=FS, fontweight='bold')
|
||
|
||
# Bottom conclusion
|
||
ax.text(5.0, 2.8, 'Krok 3: Wybierz min z max żalu → A₂ (max żal = 120)',
|
||
fontsize=10, ha='center', va='center', fontweight='bold',
|
||
bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY1, edgecolor=LN, lw=1.5))
|
||
|
||
# Interpretatation examples
|
||
ax.text(5.0, 2.0, 'Interpretacja żalu: r₁₃ = 140 oznacza:\n'
|
||
'„Gdyby nastąpił S₃ (zła koniunktura), a wybrałbym A₁,\n'
|
||
'żałowałbym, bo najlepszą opcją byłoby A₂ z wynikiem 40 — traciłbym 140"',
|
||
fontsize=7.5, ha='center', va='center', style='italic',
|
||
bbox=dict(boxstyle='round,pad=0.3', facecolor=GRAY4, edgecolor=GRAY3, lw=0.8))
|
||
|
||
# Mnemonic
|
||
ax.text(5.0, 0.8, 'Mnemonik: Savage = „Żal jak nóż"\nMaksymalny żal to nóż '
|
||
'— wybierz opcję z NAJMNIEJSZYM nożem',
|
||
fontsize=8, ha='center', va='center', fontweight='bold',
|
||
bbox=dict(boxstyle='round,pad=0.3', facecolor='white', edgecolor=LN, lw=1))
|
||
|
||
plt.tight_layout()
|
||
outpath = os.path.join(OUTPUT_DIR, 'q31_regret_matrix.png')
|
||
fig.savefig(outpath, dpi=DPI, bbox_inches='tight', facecolor=BG)
|
||
plt.close(fig)
|
||
print(f" Saved: {outpath}")
|
||
|
||
|
||
# ============================================================
|
||
# 3. HURWICZ α INTERPOLATION
|
||
# ============================================================
|
||
def draw_hurwicz_interpolation():
|
||
fig, ax = plt.subplots(1, 1, figsize=(8.27, 4))
|
||
ax.set_title('Kryterium Hurwicza — wpływ α na wybór alternatywy',
|
||
fontsize=FS_TITLE + 1, fontweight='bold', pad=10)
|
||
|
||
alphas = np.linspace(0, 1, 200)
|
||
|
||
# V(Ai) = α * max_i + (1-α) * min_i
|
||
# A1: max=200, min=-100
|
||
# A2: max=80, min=40
|
||
# A3: max=30, min=30
|
||
v1 = alphas * 200 + (1 - alphas) * (-100)
|
||
v2 = alphas * 80 + (1 - alphas) * 40
|
||
v3 = alphas * 30 + (1 - alphas) * 30
|
||
|
||
ax.plot(alphas, v1, 'k-', lw=2, label='A₁ (fabryka): V = 300α − 100')
|
||
ax.plot(alphas, v2, 'k--', lw=2, label='A₂ (sklep): V = 40α + 40')
|
||
ax.plot(alphas, v3, 'k:', lw=2, label='A₃ (obligacje): V = 30')
|
||
|
||
# Find crossover points
|
||
# A2 = A1: 40α + 40 = 300α - 100 → 140 = 260α → α = 140/260 ≈ 0.538
|
||
alpha_cross_12 = 140 / 260
|
||
v_cross_12 = 40 * alpha_cross_12 + 40
|
||
|
||
# A2 = A3: 40α + 40 = 30 → 40α = -10 → α = -0.25 (never — A2 always > A3)
|
||
# A1 = A3: 300α - 100 = 30 → 300α = 130 → α = 130/300 ≈ 0.433
|
||
alpha_cross_13 = 130 / 300
|
||
v_cross_13 = 30
|
||
|
||
ax.plot(alpha_cross_12, v_cross_12, 'ko', markersize=8, zorder=5)
|
||
ax.annotate(f'α ≈ {alpha_cross_12:.2f}\nA₁ = A₂', xy=(alpha_cross_12, v_cross_12),
|
||
xytext=(alpha_cross_12 + 0.12, v_cross_12 - 30),
|
||
fontsize=8, fontweight='bold',
|
||
arrowprops=dict(arrowstyle='->', color=LN, lw=1))
|
||
|
||
# Shade winning regions
|
||
ax.axvspan(0, alpha_cross_12, alpha=0.08, color='black', label='_')
|
||
ax.axvspan(alpha_cross_12, 1, alpha=0.15, color='black', label='_')
|
||
|
||
ax.text(alpha_cross_12 / 2, -60, 'A₂ wygrywa\n(pesymistycznie)',
|
||
fontsize=8, ha='center', va='center',
|
||
bbox=dict(boxstyle='round', facecolor='white', edgecolor=LN))
|
||
ax.text((alpha_cross_12 + 1) / 2, 160, 'A₁ wygrywa\n(optymistycznie)',
|
||
fontsize=8, ha='center', va='center',
|
||
bbox=dict(boxstyle='round', facecolor='white', edgecolor=LN))
|
||
|
||
# Special α values
|
||
ax.axvline(x=0, color=LN, lw=0.5, ls=':')
|
||
ax.axvline(x=1, color=LN, lw=0.5, ls=':')
|
||
ax.text(0, -115, 'α=0\nmaximin', fontsize=7, ha='center', va='top', fontweight='bold')
|
||
ax.text(1, -115, 'α=1\nmaximax', fontsize=7, ha='center', va='top', fontweight='bold')
|
||
|
||
ax.set_xlabel('Współczynnik optymizmu α', fontsize=9)
|
||
ax.set_ylabel('V(Aᵢ) = α·max + (1−α)·min', fontsize=9)
|
||
ax.legend(fontsize=8, loc='upper left')
|
||
ax.spines['top'].set_visible(False)
|
||
ax.spines['right'].set_visible(False)
|
||
ax.set_xlim(-0.05, 1.05)
|
||
ax.axhline(y=0, color=LN, lw=0.3, ls='-')
|
||
ax.tick_params(labelsize=8)
|
||
|
||
plt.tight_layout()
|
||
outpath = os.path.join(OUTPUT_DIR, 'q31_hurwicz_alpha.png')
|
||
fig.savefig(outpath, dpi=DPI, bbox_inches='tight', facecolor=BG)
|
||
plt.close(fig)
|
||
print(f" Saved: {outpath}")
|
||
|
||
|
||
# ============================================================
|
||
# 4. DECISION CRITERIA MNEMONIC MAP
|
||
# ============================================================
|
||
def draw_criteria_mnemonic():
|
||
fig, ax = plt.subplots(1, 1, figsize=(8.27, 6))
|
||
ax.set_xlim(0, 10)
|
||
ax.set_ylim(0, 8)
|
||
ax.set_aspect('equal')
|
||
ax.axis('off')
|
||
ax.set_title('Mapa mnemoniczna — 6 kryteriów decyzyjnych',
|
||
fontsize=FS_TITLE + 2, fontweight='bold', pad=10)
|
||
|
||
# Central node
|
||
draw_box(ax, 3.5, 3.5, 3, 1, 'MACIERZ\nWYPŁAT', fill=GRAY2, lw=2,
|
||
fontsize=11, fontweight='bold')
|
||
|
||
# Criteria boxes around the center
|
||
criteria = [
|
||
# (x, y, w, h, title, mnemonic, formula)
|
||
(0, 6.5, 3, 1.2, 'WARTOŚĆ OCZEKIWANA', '„Mam prawdopodobieństwa"', 'E[Aᵢ] = Σ pⱼ·aᵢⱼ'),
|
||
(3.5, 6.5, 3, 1.2, 'LAPLACE', '„Wszystko po równo"', 'V = Σaᵢⱼ / n'),
|
||
(7, 6.5, 3, 1.2, 'MAXIMAX', '„Optymista: max z max"', 'max maxⱼ aᵢⱼ'),
|
||
(0, 0.5, 3, 1.2, 'MAXIMIN (Wald)', '„Pesymista: max z min"', 'max minⱼ aᵢⱼ'),
|
||
(3.5, 0.5, 3, 1.2, 'HURWICZ', '„α pomiędzy"', 'α·max + (1−α)·min'),
|
||
(7, 0.5, 3, 1.2, 'SAVAGE', '„Min max żalu"', 'min maxⱼ rᵢⱼ'),
|
||
]
|
||
|
||
fills = [GRAY3, GRAY1, 'white', 'white', GRAY1, GRAY3]
|
||
|
||
for i, (x, y, w, h, title, mnem, formula) in enumerate(criteria):
|
||
rect = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.08",
|
||
lw=1.5, edgecolor=LN, facecolor=fills[i])
|
||
ax.add_patch(rect)
|
||
ax.text(x + w/2, y + h*0.78, title, ha='center', va='center',
|
||
fontsize=8, fontweight='bold')
|
||
ax.text(x + w/2, y + h*0.45, mnem, ha='center', va='center',
|
||
fontsize=7, style='italic')
|
||
ax.text(x + w/2, y + h*0.15, formula, ha='center', va='center',
|
||
fontsize=7, fontweight='bold', family='monospace')
|
||
|
||
# Arrows from center to each box
|
||
cx, cy = 5, 4 # center of macierz
|
||
bx, by = x + w/2, y + h/2
|
||
if by > cy:
|
||
ax.annotate("", xy=(bx, y), xytext=(cx, 4.5),
|
||
arrowprops=dict(arrowstyle='->', color=LN, lw=1,
|
||
connectionstyle='arc3,rad=0'))
|
||
else:
|
||
ax.annotate("", xy=(bx, y + h), xytext=(cx, 3.5),
|
||
arrowprops=dict(arrowstyle='->', color=LN, lw=1,
|
||
connectionstyle='arc3,rad=0'))
|
||
|
||
# Labels on arrows
|
||
ax.text(1.2, 5.6, 'znane p', fontsize=7, ha='center', va='center',
|
||
bbox=dict(boxstyle='round,pad=0.15', facecolor='white', edgecolor=GRAY3, lw=0.5))
|
||
ax.text(5, 5.6, 'p = 1/n', fontsize=7, ha='center', va='center',
|
||
bbox=dict(boxstyle='round,pad=0.15', facecolor='white', edgecolor=GRAY3, lw=0.5))
|
||
ax.text(8.7, 5.6, 'max ↑', fontsize=7, ha='center', va='center',
|
||
bbox=dict(boxstyle='round,pad=0.15', facecolor='white', edgecolor=GRAY3, lw=0.5))
|
||
ax.text(1.2, 2.5, 'min ↑', fontsize=7, ha='center', va='center',
|
||
bbox=dict(boxstyle='round,pad=0.15', facecolor='white', edgecolor=GRAY3, lw=0.5))
|
||
ax.text(5, 2.5, 'podaj α', fontsize=7, ha='center', va='center',
|
||
bbox=dict(boxstyle='round,pad=0.15', facecolor='white', edgecolor=GRAY3, lw=0.5))
|
||
ax.text(8.7, 2.5, 'macierz\nżalu', fontsize=7, ha='center', va='center',
|
||
bbox=dict(boxstyle='round,pad=0.15', facecolor='white', edgecolor=GRAY3, lw=0.5))
|
||
|
||
plt.tight_layout()
|
||
outpath = os.path.join(OUTPUT_DIR, 'q31_criteria_mnemonic.png')
|
||
fig.savefig(outpath, dpi=DPI, bbox_inches='tight', facecolor=BG)
|
||
plt.close(fig)
|
||
print(f" Saved: {outpath}")
|
||
|
||
|
||
# ============================================================
|
||
# 5. EXPECTED VALUE CRITERION WITH PROBABILITY BARS
|
||
# ============================================================
|
||
def draw_expected_value():
|
||
fig, axes = plt.subplots(1, 3, figsize=(8.27, 3.5), sharey=True)
|
||
fig.suptitle('Kryterium wartości oczekiwanej E[X] — rozkład wyników per alternatywa',
|
||
fontsize=FS_TITLE, fontweight='bold', y=1.02)
|
||
|
||
# Probabilities: p1=0.5, p2=0.3, p3=0.2
|
||
probs = [0.5, 0.3, 0.2]
|
||
states = ['S₁\n(dobra)\np=0.5', 'S₂\n(średnia)\np=0.3', 'S₃\n(zła)\np=0.2']
|
||
alts = [
|
||
('A₁ (fabryka)', [200, 50, -100], 95),
|
||
('A₂ (sklep)', [80, 70, 40], 69),
|
||
('A₃ (obligacje)', [30, 30, 30], 30),
|
||
]
|
||
|
||
hatches = ['///', '...', 'xxx']
|
||
|
||
for idx, (ax, (name, vals, ev)) in enumerate(zip(axes, alts)):
|
||
# Bar: height = payoff, width proportional to probability
|
||
x_positions = [0, 0.6, 1.0]
|
||
widths = [p * 0.9 for p in probs]
|
||
|
||
for i, (v, p, h) in enumerate(zip(vals, probs, hatches)):
|
||
color = 'white' if v >= 0 else GRAY2
|
||
bar = ax.bar(x_positions[i], v, width=widths[i], color=color,
|
||
edgecolor=LN, hatch=h, lw=0.8, align='edge')
|
||
# Value label
|
||
offset = 8 if v >= 0 else -12
|
||
ax.text(x_positions[i] + widths[i]/2, v + offset, f'{v}',
|
||
ha='center', va='center', fontsize=8, fontweight='bold')
|
||
# Probability contribution
|
||
contrib = v * p
|
||
ax.text(x_positions[i] + widths[i]/2, v/2,
|
||
f'{v}×{p}\n={contrib:.0f}', ha='center', va='center',
|
||
fontsize=6, style='italic')
|
||
|
||
# Expected value line
|
||
ax.axhline(y=ev, color=LN, lw=2, ls='--')
|
||
ax.text(1.35, ev, f'E[X]={ev}', fontsize=8, fontweight='bold',
|
||
va='center', ha='left',
|
||
bbox=dict(boxstyle='round,pad=0.15', facecolor=GRAY1, edgecolor=LN))
|
||
|
||
ax.set_title(name, fontsize=9, fontweight='bold')
|
||
ax.set_xticks([0.225, 0.735, 1.09])
|
||
ax.set_xticklabels(['S₁', 'S₂', 'S₃'], fontsize=7)
|
||
ax.axhline(y=0, color=LN, lw=0.5)
|
||
ax.spines['top'].set_visible(False)
|
||
ax.spines['right'].set_visible(False)
|
||
ax.tick_params(labelsize=7)
|
||
|
||
# Star on winner
|
||
if ev == 95:
|
||
ax.text(0.7, ev + 20, '★ MAX', fontsize=9, fontweight='bold',
|
||
ha='center', va='bottom')
|
||
|
||
axes[0].set_ylabel('Wypłata (tys. zł)', fontsize=8)
|
||
|
||
plt.tight_layout()
|
||
outpath = os.path.join(OUTPUT_DIR, 'q31_expected_value.png')
|
||
fig.savefig(outpath, dpi=DPI, bbox_inches='tight', facecolor=BG)
|
||
plt.close(fig)
|
||
print(f" Saved: {outpath}")
|
||
|
||
|
||
# ============================================================
|
||
# 6. DECISION CONDITIONS SPECTRUM
|
||
# ============================================================
|
||
def draw_conditions_spectrum():
|
||
fig, ax = plt.subplots(1, 1, figsize=(8.27, 3.5))
|
||
ax.set_xlim(0, 10)
|
||
ax.set_ylim(0, 5)
|
||
ax.set_aspect('equal')
|
||
ax.axis('off')
|
||
ax.set_title('Warunki decyzyjne — spektrum wiedzy decydenta',
|
||
fontsize=FS_TITLE + 1, fontweight='bold', pad=10)
|
||
|
||
# Three zones
|
||
zones = [
|
||
(0.3, 1.5, 2.8, 2.5, 'PEWNOŚĆ', 'white', [
|
||
'Znamy dokładny wynik',
|
||
'Przykład: lokata 5%',
|
||
'Metoda: po prostu wybierz',
|
||
'najlepszy wynik'
|
||
]),
|
||
(3.5, 1.5, 2.8, 2.5, 'RYZYKO', GRAY1, [
|
||
'Znamy wyniki I prawdop.',
|
||
'Przykład: gra w kości',
|
||
'Metoda: wartość',
|
||
'oczekiwana E[X]'
|
||
]),
|
||
(6.7, 1.5, 2.8, 2.5, 'NIEPEWNOŚĆ', GRAY3, [
|
||
'Znamy wyniki, ale',
|
||
'NIE znamy prawdop.',
|
||
'Metody: Laplace, maximax,',
|
||
'maximin, Hurwicz, Savage'
|
||
]),
|
||
]
|
||
|
||
for x, y, w, h, title, fill, lines in zones:
|
||
rect = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.1",
|
||
lw=2, edgecolor=LN, facecolor=fill)
|
||
ax.add_patch(rect)
|
||
ax.text(x + w/2, y + h - 0.3, title, ha='center', va='center',
|
||
fontsize=11, fontweight='bold')
|
||
for i, line in enumerate(lines):
|
||
ax.text(x + w/2, y + h - 0.7 - i*0.4, line, ha='center', va='center',
|
||
fontsize=7)
|
||
|
||
# Arrows between zones
|
||
ax.annotate("", xy=(3.4, 2.75), xytext=(3.15, 2.75),
|
||
arrowprops=dict(arrowstyle='->', color=LN, lw=2))
|
||
ax.annotate("", xy=(6.6, 2.75), xytext=(6.35, 2.75),
|
||
arrowprops=dict(arrowstyle='->', color=LN, lw=2))
|
||
|
||
# Bottom: knowledge gradient bar
|
||
gradient_y = 0.5
|
||
gradient_h = 0.5
|
||
n_steps = 50
|
||
for i in range(n_steps):
|
||
x = 0.3 + i * (9.2 / n_steps)
|
||
w = 9.2 / n_steps + 0.01
|
||
gray_val = 1 - (i / n_steps) * 0.7
|
||
rect = mpatches.Rectangle((x, gradient_y), w, gradient_h, lw=0,
|
||
facecolor=str(gray_val))
|
||
ax.add_patch(rect)
|
||
|
||
rect = mpatches.Rectangle((0.3, gradient_y), 9.2, gradient_h, lw=1.5,
|
||
edgecolor=LN, facecolor='none')
|
||
ax.add_patch(rect)
|
||
|
||
ax.text(0.3, gradient_y - 0.15, 'Dużo wiedzy', fontsize=7, ha='left', va='top')
|
||
ax.text(9.5, gradient_y - 0.15, 'Mało wiedzy', fontsize=7, ha='right', va='top')
|
||
ax.text(4.95, gradient_y + gradient_h / 2, 'POZIOM WIEDZY DECYDENTA',
|
||
fontsize=8, fontweight='bold', ha='center', va='center', color='white')
|
||
|
||
plt.tight_layout()
|
||
outpath = os.path.join(OUTPUT_DIR, 'q31_conditions_spectrum.png')
|
||
fig.savefig(outpath, dpi=DPI, bbox_inches='tight', facecolor=BG)
|
||
plt.close(fig)
|
||
print(f" Saved: {outpath}")
|
||
|
||
|
||
# ============================================================
|
||
# MAIN
|
||
# ============================================================
|
||
if __name__ == '__main__':
|
||
print("Generating PYTANIE 31 diagrams...")
|
||
draw_criteria_comparison()
|
||
draw_regret_matrix()
|
||
draw_hurwicz_interpolation()
|
||
draw_criteria_mnemonic()
|
||
draw_expected_value()
|
||
draw_conditions_spectrum()
|
||
print("Done! All Q31 diagrams saved to:", OUTPUT_DIR)
|