import matplotlib.pyplot as plt
import numpy as np
from io import StringIO
from sklearn.preprocessing import MaxAbsScaler, MinMaxScaler, RobustScaler, StandardScaler

plt.rcParams.update({'text.color': 'gray', 'axes.labelcolor': 'gray', 'axes.edgecolor': 'gray',
                     'xtick.color': 'gray', 'ytick.color': 'gray'})

# Monthly income (R$ thousand) of five households; the fifth is an outlier
x = np.array([[1.], [2.], [3.], [4.], [40.]])
rows = [('StandardScaler   (x − mean) / std', StandardScaler()),
        ('MinMaxScaler   (x − min) / (max − min)', MinMaxScaler()),
        ('MaxAbsScaler   x / max|x|', MaxAbsScaler()),
        ('RobustScaler   (x − median) / IQR', RobustScaler())]
lo, hi = -1.4, 2.6                      # one common axis for every scaler

fig, ax = plt.subplots(figsize=(8.5, 3.6))
for i, (name, scaler) in enumerate(rows):
    yrow = len(rows) - 1 - i
    z = scaler.fit_transform(x).ravel()
    ax.axhline(yrow, color='gray', lw=0.6, alpha=0.4)
    ax.scatter(z[:4], np.full(4, yrow), s=55, color='#58a6ff', zorder=3, edgecolor='white', lw=0.6)
    ax.plot([z[0], z[3]], [yrow - 0.22] * 2, color='#58a6ff', lw=2.5, solid_capstyle='butt')
    ax.text(z[0], yrow - 0.42, f'ordinary households span {z[3] - z[0]:.3g}', fontsize=8.5,
            color='#58a6ff', va='center')
    if z[4] <= hi:
        ax.scatter(z[4], yrow, s=70, color='#ff7b72', zorder=3, edgecolor='white', lw=0.6)
        ax.text(z[4], yrow + 0.25, f'outlier {z[4]:.2f}', fontsize=8.5, color='#ff7b72', ha='center')
    else:
        ax.annotate(f'outlier at {z[4]:.3g} →', xy=(hi - 0.02, yrow), fontsize=8.5, color='#ff7b72',
                    ha='right', va='bottom', xytext=(hi - 0.02, yrow + 0.12))
    ax.text(lo + 0.03, yrow + 0.28, name, fontsize=9, va='center')
ax.set_xlim(lo, hi)
ax.set_ylim(-0.7, len(rows) - 0.4)
ax.set_yticks([])
ax.tick_params(labelsize=8)
ax.set_xlabel('scaled value (same axis for all four)', fontsize=9)
for side in ['left', 'right', 'top']:
    ax.spines[side].set_visible(False)
ax.set_title('Incomes 1, 2, 3, 4 and 40 (R$ thousand): only the robust scaler keeps the ordinary ones apart',
             fontsize=9.5)
plt.tight_layout()

buffer = StringIO()
plt.savefig(buffer, format="svg", transparent=True)
print(buffer.getvalue())

plt.close()
