praca_magisterska/pytania/generate_q31_diagrams.py

621 lines
25 KiB
Python
Raw Normal View History

2026-02-21 19:51:31 +01:00
#!/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)