Социология парламентарни избори 2026

Този бележник анализира разликите електоралните нагласи отчетени от различните социологически агенции и реалните изборни резултати.
sociology
Author

Григор Димитров

Published

April 23, 2026

Анализ на точността на социологическите проучвания

Този бележник анализира разликите в електоралните нагласи, отчетени от различните социологически агенции, и реалните изборни резултати.

  • Зареждане на данни: Данните се хостват публично в GitHub с цел да бъдат достъпни и проверими. Ако не сте съгласни с части от данните или анализа, можете да го модифицирате.
  • Технически параметри: Преглед на данните, публикувани към проучванията (период, метод, извадка).
  • Резултатна матрица: Сравнителна таблица с всички резултати (хоризонтално — партиите, вертикално — проучванията).
  • Матрица на отклоненията: Изчисляване на разликите между нагласите в проучванията и крайните изборни резултати.
  • Ранкинг по средна абсолютна грешка (MAE): Класиране на проучванията според тяхното средно линейно отклонение.
  • Ранкинг по средноквадратична грешка (RMSE): Класиране според корен квадратен от средната стойност на квадратите на разликите (методът наказва по-тежко големите отклонения).
  • Регресионен анализ (OLS): Изследване на влиянието на фактори като оставащо време до изборите, методология (хибридна или офлайн) и големина на извадката върху точността на прогнозите.

Забележки: Анализът е мотивиран от любопитство и има за цел да постави основи за по-сериозна работа по темата. Сегашният му статус е на ниво „чернова“. Данните не трябва да се използват за вземане на решения, преди да бъдат вложени допълнителни усилия в сравняването на входящите данни с резултатите от изследванията и валидиране на качеството на кода. В допълнение, има няколко теста, препоръчителни при валидацията на регресионния анализ, които не са направени. Чувствайте се свободни да копирате, възпроизвеждате и модифицирате кода.

Последващи интересни анализи биха били:

  • Съставяне на база данни с всички социологически проучвания от последните 8 избора.
  • Използване на други видове индикатори, които измерват процентното отклонение (например MAPE или wMAPE).
  • Анализиране на данните с MDS (Multi-dimensional Scaling) или подобни методи, за да се проверят съмненията за връзка между конкретна агенция (или поръчител) и системно надценяване или подценяване на определени партии.
  • Добавяне на допълнителни атрибути в регресионния анализ — например източник на извадката или подизпълнител на проекта.
Show the code
import pandas as pd
import requests
import json
import numpy as np

# 1. Use the RAW URL from your gist (Note the 'gist.githubusercontent' domain)
url = "https://gist.githubusercontent.com/gregordimi/e0d3fe30e4f898c84ad25c38b39f6e44/raw/311a84fb7ce48c8bc47d61b359405d602b2aabd6/results2026.json"

# 2. Fetch the data using requests
response = requests.get(url)
data = response.json()

# 3. Your existing processing logic
survey_list = []
for s in data['surveys']:
    entry = s['results'].copy()
    entry['Agency'] = s['agency']
    entry['Date'] = s['date_iso']
    entry['Label'] = f"{s['agency']} ({s['date_label']})"

    # Store metadata separately if you want to use it for the cards
    entry['metadata'] = s.get('metadata', {})

    survey_list.append(entry)

df = pd.DataFrame(survey_list).set_index('Label')
actual = pd.Series(data['actual'], name='РЕАЛНИ РЕЗУЛТАТИ')

print(f"Данните за {len(df)} проучвания са заредени успешно")
print(url)
# df.head()
Данните за 20 проучвания са заредени успешно
https://gist.githubusercontent.com/gregordimi/e0d3fe30e4f898c84ad25c38b39f6e44/raw/311a84fb7ce48c8bc47d61b359405d602b2aabd6/results2026.json

Паспорти на публичните социологически изследвания за парламентарни избори 2026

Техническите данни за изследванията

Show the code
from IPython.display import HTML, display

def render_facet_row(data):
    all_cards = ""

    for s in data['surveys']:
        m = s.get('metadata', {})
        o = m.get('other_metadata', {})

        # Strip-down card template
        card_html = f"""
        <div style="flex: 1; min-width: 220px; max-width: 300px; border: 1px solid #888;
                    border-radius: 8px; padding: 12px; background: #fff; font-family: sans-serif;
                    box-shadow: 2px 2px 5px rgba(0,0,0,0.05); font-size: 12px;">
            <div style="color: #111; font-weight: bold; border-bottom: 1px solid #eee;
                        margin-bottom: 8px; padding-bottom: 4px; font-size: 13px;">
                {s['agency']} ({s['date_label']})
            </div>
            <div style="color: #777; margin-bottom: 4px;"><b>Период:</b> {m.get('date', 'N/A')}</div>
            <div style="color: #777; margin-bottom: 4px;"><b>Поръчител:</b> {m.get('purchaser', 'N/A')}</div>
            <div style="color: #777; margin-bottom: 4px;"><b>Извадка:</b> {m.get('sample_size', 'N/A')}</div>
            <div style="color: #777; margin-bottom: 4px;"><b>Метод:</b> {m.get('methodology', 'N/A')}</div>
            <div style="color: #777; font-size: 11px; margin-top: 8px; font-style: italic;">
                Грешка: {o.get('margin_of_error', 'N/A')}
            </div>
        </div>
        """
        all_cards += card_html

    # Display wrapper with Flexbox
    display(HTML(f"""
    <div style="display: flex; flex-wrap: wrap; gap: 15px; background: #f4f4f4; padding: 20px; border-radius: 12px;">
        {all_cards}
    </div>
    """))

# Execute to render all 19 cards
render_facet_row(data)
Маркет Линкс (16/02)
Период: 07 - 13 февруари 2026 г.
Поръчител: бТВ и Маркет Линкс
Извадка: 1019 лица над 18 г. в страната
Метод: Пряко-лично интервю и онлайн анкета
Грешка: Не е посочена
Мяра (17/02)
Период: 9-15 февруари 2026 г.
Поръчител: Независима ежемесечна изследователска програма на социологическата агенция „Мяра“
Извадка: 812 пълнолетни български граждани
Метод: “Лице в лице” с таблети
Грешка: Не е посочена
Тренд (23/02)
Период: 12-18 февруари 2026 г.
Поръчител: 24 часа
Извадка: 1002 души
Метод: Стандартизирано интервю „лице в лице“
Грешка: ± 3,1%
Център за анализи и маркетинг (26/02)
Период: 17 - 24 февруари 2026 г.
Поръчител: Център за анализи и маркетинг
Извадка: 1010 души
Метод: Пряко стандартизирано интервю по домовете
Грешка: ± 3,1%
Алфа Рисърч (05/03)
Период: 23 февруари – 2 март 2026 г.
Поръчител: Реализирано със собствени средства
Извадка: 1000 души
Метод: Пряко стандартизирано интервю по домовете с таблети
Грешка: Не е посочена
Галъп (09/03)
Период: 10 - 28 февруари 2026 г.
Поръчител: Независима изследователска програма (собствени средства)
Извадка: 800 души
Метод: Стандартизирано персонално интервю тип face-to-face с таблети (TAPI)
Грешка: ±3,5%
Сова Харис (16/03)
Период: 7 - 12 март 2026 г.
Поръчител: Вестник „Труд”
Извадка: 1000 души
Метод: Стандартизирано интервю „лице в лице” в дома на респондента
Грешка: +/- 3,5%
Маркет Линкс (18/03)
Период: 7 - 15 март 2026 г.
Поръчител: бТВ и „Маркет линкс”
Извадка: 1006 души
Метод: Пряко-лично интервю и онлайн анкета
Грешка: Не е посочена
Мяра (20/03)
Период: 7 - 16 март 2026 г.
Поръчител: Редовна изследователска програма на „Мяра”
Извадка: 809 души
Метод: “Лице в лице” с таблети
Грешка: +/- 3,5%
Тренд (23/03)
Период: 13-19 март 2026 г.
Поръчител: “24 часа”
Извадка: 1001 души
Метод: Пряко полустандартизирано интервю „лице в лице” с таблет
Грешка: +/- 3,1%
Маркет Линкс (23/03)
Период: 17 - 21 март 2026 г.
Поръчител: бТВ и „Маркет линкс”
Извадка: 1008 души
Метод: Пряко-лично интервю и онлайн анкета
Грешка: Не е посочена
Алфа Рисърч (23/03)
Период: 12-20 март 2026 г.
Поръчител: бТВ
Извадка: 1000 души
Метод: Пряко стандартизирано интервю по домовете с таблети
Грешка: Не е посочена
Алфа Рисърч (29/03)
Период: 19-26 март 2026 г.
Поръчител: БНР
Извадка: 1000 души
Метод: Пряко стандартизирано интервю по домовете с таблети
Грешка: Не е посочена
Галъп (03/04)
Период: 20 - 30 март 2026 г.
Поръчител: Независима изследователска програма (собствени средства)
Извадка: 820 души
Метод: Стандартизирано персонално интервю тип face-to-face с таблети (TAPI)
Грешка: ±3,5%
Сова Харис (09/04)
Период: 2 - 6 април 2026 г.
Поръчител: Вестник „Труд”
Извадка: 800 души
Метод: Стандартизирано интервю „лице в лице” в дома на респондента
Грешка: +/- 3,5%
Маркет Линкс (16/04)
Период: 7-14 април 2026 г.
Поръчител: bTV и Маркет Линкс
Извадка: 1003 души
Метод: Пряко-лично интервю и онлайн анкета
Грешка: Липсва информация
Алфа Рисърч (17/04)
Период: 13 - 15 април 2026 г.
Поръчител: Реализирано със собствени средства
Извадка: 1000 души
Метод: Пряко стандартизирано интервю по домовете с таблети
Грешка: Не е посочена
Тренд (17/04)
Период: 13 - 16 април 2026 г.
Поръчител: “24 часа”
Извадка: 1004 души
Метод: Пряко стандартизирано интервю „лице в лице” с таблет
Грешка: +/- 3,1%
Галъп (17/04)
Период: 08 - 16 април 2026 г.
Поръчител: Реализирано със собствени средства
Извадка: 803 души
Метод: Стандартизирано персонално интервю тип face-to-face
Грешка: +/- 3,3%
Мяра (17/04)
Период: 4 - 13 април 2026 г.
Поръчител: БНР
Извадка: 1002 души
Метод: Лице в лице с таблети
Грешка: +/- 3,1%

Данните

Тази графика показва всички публикувани данни на горните проучвания които са анализирани в това мета изследване.

Show the code
import seaborn as sns
import matplotlib.pyplot as plt

# 1. Филтрираме колоните (същата логика)
cols = [c for c in df.columns if c not in ['Agency', 'Date', 'metadata', 'Не съм решил']]

# 2. Сортираме партиите по техния реален резултат
sorted_parties = actual[cols].sort_values(ascending=False).index

# 3. Вземаме СУРОВИТЕ ДАННИ (df вместо residuals)
raw_data_matrix = df[sorted_parties]

# 4. Визуализация
plt.figure(figsize=(10, 10))

# Използваме 'YlGnBu' (Yellow-Green-Blue), защото това са абсолютни стойности (0-100),
# а не отклонения около нулата.
sns.heatmap(raw_data_matrix,
            annot=True,
            fmt='.1f',
            cmap='YlGnBu',
            linewidths=.5,
            cbar_kws={'label': 'Прогнозиран процент (%)'})

plt.title('Матрица данни: Всички нагласи по партии', fontsize=16, fontweight='bold', pad=20)
plt.xlabel('Партия (подредена по реален изборен резултат)', fontsize=12)
plt.ylabel('Агенция / Дата', fontsize=12)
plt.xticks(rotation=45, ha='right')
plt.tight_layout()

plt.show()

Отклоненията (Residual Heatmap)

Тази графика показва отклоненията на публукуваните електорални нагласи в различните проучвания от реалния изборен резултат

Show the code
import seaborn as sns
import matplotlib.pyplot as plt

# 1. Филтрираме само колоните с партии (изключваме метаданните и служебните категории)
cols = [c for c in df.columns if c not in ['Agency', 'Date', 'metadata', 'Не съм решил']]

# 2. Изчисляваме остатъците (Прогноза минус Реалност)
# Резултат > 0: Партията е била надценена (Survey > Actual)
# Резултат < 0: Партията е била подценена (Survey < Actual)
residuals = df[cols].sub(actual[cols], axis=1)

# 3. Сортираме партиите по техния реален изборен резултат за по-добра четимост
sorted_parties = actual[cols].sort_values(ascending=False).index

# 4. Визуализация на Heatmap
plt.figure(figsize=(10, 10))
sns.heatmap(residuals[sorted_parties],
            annot=True,
            fmt='.1f',
            cmap='RdBu_r',
            center=0,
            linewidths=.5,
            cbar_kws={'label': 'Отклонение (Процентни пунктове)'})

plt.title('Матрица на отклоненията: Прогноза срещу Реалност', fontsize=16, fontweight='bold', pad=20)
plt.xlabel('Партия (подредена по изборен резултат)', fontsize=12)
plt.ylabel('Агенция / Дата', fontsize=12)
plt.xticks(rotation=45, ha='right')
plt.tight_layout()

plt.show()

Средна Абсолютна Грешка (MAE)

Средна абсолютна грешка. Измерва средната големина на отклоненията в набор от прогнози, без да отчита тяхната посока (дали е надценено или подценено). Всички грешки се третират с еднаква тежест.

Колкото по-ниска е стойността, толкова по-точни са били данните спрямо официално регистрирания вот в деня на изборите.

Show the code
import matplotlib.pyplot as plt
import numpy as np

# 1. Избор на партиите за сравнение (премахваме метаданните и служебните категории)
cols = [c for c in df.columns if c not in ['Agency', 'Date', 'metadata', 'Не съм решил']]

# 2. Изчисляване на MAE за всеки ред спрямо 'actual'
# Използваме .dropna(), за да сме сигурни, че сравняваме само съществуващи данни
mae = df[cols].apply(lambda x: np.mean(np.abs(x - actual[cols])), axis=1).sort_values()

# 3. Визуализация
plt.figure(figsize=(10, 8), facecolor='#f9f9f9')
colors = plt.cm.RdYlGn_r(np.linspace(0, 1, len(mae)))

# Хоризонтален бар
bars = plt.barh(mae.index, mae.values, color=colors, edgecolor='black', alpha=0.8)

# Добавяне на стойностите върху баровете
for bar in bars:
    plt.text(bar.get_width() + 0.05, bar.get_y() + bar.get_height()/2,
             f'{bar.get_width():.2f}%', va='center', fontsize=9, fontweight='bold')

plt.title('Класация по точност (MAE) - Парламентарни избори 2026', fontsize=14, pad=20, fontweight='bold')
plt.xlabel('Средна грешка в процентни пунктове (по-малко е по-добре)', fontsize=11)
plt.gca().invert_yaxis()  # Най-точните най-отгоре
plt.grid(axis='x', linestyle='--', alpha=0.5)
plt.tight_layout()

plt.show()

# Бонус: Кратка присъда
winner = mae.index[0]
print(f"🏆 НАЙ-ТОЧНА ПРОГНОЗА: {winner} (Грешка: {mae.iloc[0]:.2f}%)")

🏆 НАЙ-ТОЧНА ПРОГНОЗА: Мяра (17/04) (Грешка: 1.99%)

Средноквадратична грешка. (RMSE)

Изчислява се като корен квадратен от средната стойност на квадратите на разликите. Тъй като грешките се вдигат на квадрат преди да се усреднят, тази метрика дава много по-голяма тежест на големите отклонения.

Колкото по-ниска е стойността, толкова по-точни са били данните спрямо официално регистрирания вот в деня на изборите.

Show the code
import matplotlib.pyplot as plt
import numpy as np

# 1. Избор на партиите
cols = [c for c in df.columns if c not in ['Agency', 'Date', 'metadata', 'Не съм решил']]

# 2. Изчисляване на RMSE (Root Mean Square Error)
# RMSE = Квадратен корен от средната стойност на квадратите на разликите
rmse = df[cols].apply(lambda x: np.sqrt(np.mean((x - actual[cols])**2)), axis=1).sort_values()

# 3. Визуализация
plt.figure(figsize=(10, 8), facecolor='#f9f9f9')
colors = plt.cm.RdYlGn_r(np.linspace(0, 1, len(rmse)))

bars = plt.barh(rmse.index, rmse.values, color=colors, edgecolor='black', alpha=0.8)

for bar in bars:
    plt.text(bar.get_width() + 0.05, bar.get_y() + bar.get_height()/2,
             f'{bar.get_width():.2f}', va='center', fontsize=9, fontweight='bold')

plt.title('Класация по точност (RMSE) - Парламентарни избори 2026', fontsize=14, pad=20, fontweight='bold')
plt.xlabel('Корен квадратен от средноквадратичната грешка (по-малко е по-добре)', fontsize=11)
plt.gca().invert_yaxis()
plt.grid(axis='x', linestyle='--', alpha=0.5)
plt.tight_layout()

plt.show()

winner = rmse.index[0]
print(f"🏆 НАЙ-ТОЧНА ПРОГНОЗА (RMSE): {winner} (Грешка: {rmse.iloc[0]:.2f})")

🏆 НАЙ-ТОЧНА ПРОГНОЗА (RMSE): Мяра (17/04) (Грешка: 3.33)

Tренд

Тук проследяваме как прогнозите за основните партии са се променяли във времето спрямо крайния резултат.

Show the code
import matplotlib.pyplot as plt

# 1. Списък с партиите, които искаме да проследим
target_parties = ['Прогресивна България', 'ГЕРБ', 'ПП-ДБ', 'ДПС', 'Възраждане']

# 2. Сортираме данните хронологично
trend_df = df.sort_values('Date')

# 3. Цикъл за генериране на всяка графика
for party in target_parties:
    plt.figure(figsize=(15, 6))

    # Проверка дали партията съществува в данните (за избягване на грешки)
    if party in trend_df.columns:
        # Плотване на прогнозите
        plt.plot(trend_df.index, trend_df[party],
                 marker='o', color='#1f77b4', linewidth=2.5, markersize=8, label=f'Прогнози за {party}')

        # Линия за реалния резултат
        actual_val = actual[party]
        plt.axhline(y=actual_val, color='#d62728', linestyle='--',
                    linewidth=3, label=f'Реален резултат ({actual_val}%)')

        # Оформление
        plt.title(f'Динамика на прогнозите: {party}', fontsize=16, fontweight='bold', pad=15)
        plt.ylabel('Подкрепа (%)', fontsize=12)
        plt.xlabel('Социологическо проучване', fontsize=12)
        plt.xticks(rotation=45, ha='right')
        plt.legend(fontsize=11, loc='best')
        plt.grid(axis='y', alpha=0.3)
        plt.tight_layout()

        plt.show()
    else:
        print(f"Партията '{party}' не беше намерена в данните.")

Анализ на ефекта от методологията (Offline vs. Hybrid)

Статистическият модел (OLS) изолира „чистия“ ефект на изследователския метод върху точността на прогнозите. Вместо просто да гледаме крайната грешка, ние измерваме колко точно методологията „натежава“ върху резултата, като контролираме за фактори кога е правено проучването или колко голяма е била извадката.

Трябва да се отбележи, че в текущата извадка хибридният метод се припокрива с една от конкретните агенции и не може категорично да се изолира дали източникът на влияние е самата агенция, или методологията т.е. наличе е мултиколинеарити. Както при много статистически изследвания, основна препоръка е: „further research needed, need more data“.

Основни изводи за коефициентите:

  • Линеен шум (+1.06): Използването на хибриден метод (онлайн + офлайн) автоматично вкарва малко над 1 процентен пункт допълнително отклонение в средната грешка (MAE) спрямо чисто офлайн проучванията.
  • Квадратна грешка (+2.82): При квадратичната грешка (RMSE), наказанието за хибридния метод е близо три пъти по-високо. Това показва, че онлайн панелите са склонни да генерират „изненади“ - те не просто бъркат малко, а саклонни да генерират и по-големи пропуски.

Нека разгледаме проучване, проведено на 17 април (2 дни преди изборите) с извадка от 1000 души, за да видим как изборът на метод променя очакваната точност:

Пример 1: Средно отклонение (MAE) Ако заложите на традиционното проучване „лице в лице“, моделът предвижда средна грешка от около 2.35%. Ако при абсолютно същите условия използвате хибриден подход, очакваното средно отклонение от истината скача на 3.41% (допълнителни +1.06 пункта).

Пример 2: Риск от сериозно разминаване (RMSE) Тук разликата е драстична. При офлайн проучване очакваната квадратична грешка е 4.03. При хибридния подход обаче тя скача до 6.85. Това нарастване (с над 2.8 пункта) означава, че хибридната методология е по-несигурна. В реалността това често се изразява в „наказване“ на конкретна партия — например, докато офлайн методът би хванал вълната за Прогресивна България с малка разлика, хибридната методология може да я подцени с десетки проценти, фаворизирайки погрешно статуквото.

Относно разликата в MAE и RMSE в двете графики

Има разминаване между предходните графики и графиките на OLS модела за двата индикатора (MAE и RMSE), тъй като те са изчислени на различна база.

Защо?

  • Знаменателят (\(n\)): MAE (Средна абсолютна грешка) се пресмята, като сумата от грешките се раздели на броя на категориите (\(n\)).
  • Когато включваме категории с традиционно малка грешка (като „Други“ или „Не подкрепям никого“), делим общата сума на по-голямо число (напр. 12). Това изкуствено намалява средната стойност (в случая до 2.4%).
  • С регресионния анализ съм филтрирал само важните политически субекти (8 партии) и сумата от грешките се дели на по-малко число, което логично повишава MAE до 3.1%.

Налице са две различни стойности за MAE и RMSE за едно и също проучване, което може да изглежда объркващо: 1. 2.4% (в началните бар чартове): Изчислена върху всички категории (вкл. „Други“, „Не подкрепям никого“ + малки партии). 2. 3.1% (в OLS модела): Изчислена само върху реалните парламентарно представени партии.


Защо MAE и RMSE дават различни резултати?

Разликата в поведението на двата индикатора в графиките се дължи на начина, по който те „наказват“ отклоненията:

  • MAE (Mean Absolute Error): Това е линеен показател. Ако сгрешиш с 10% при една партия, това добавя точно 10 единици към общата сума. MAE е по-усреднен и третира всички отклонения еднакво.
  • RMSE (Root Mean Square Error): Това е квадратичен показател. Тук всяка грешка се повдига на квадрат, преди да се изчисли средното. Това означава, че голяма грешка (например при победителя) тежи прогресивно повече от много малки грешки.
    • Затова в OLS анализа хибридните методи изглеждат по-зле: Те често познават малките партии, но допускат голямо разминаване при големите играчи. RMSE улавя именно този риск.
Show the code
import statsmodels.api as sm
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# --- 1. ПРЕЦИЗНО ИЗЧИСЛЯВАНЕ НА МЕТРИКИТЕ ---
party_cols = [c for c in df.columns if c in actual.index]

def calculate_metrics(row):
    diffs = row[party_cols] - actual[party_cols]
    return pd.Series({
        'MAE': np.abs(diffs).mean(),
        'RMSE': np.sqrt((diffs**2).mean())
    })

metrics_df = df.apply(calculate_metrics, axis=1)

# --- 2. ПОДГОТОВКА НА ДАННИТЕ ЗА РЕГРЕСИЯ ---
election_date = pd.to_datetime('2026-04-19')
ols_data = []

for idx, row in df.iterrows():
    enc = row['metadata'].get('encoded', {})
    if not enc: continue

    ols_data.append({
        'Label': idx,
        'MAE': metrics_df.loc[idx, 'MAE'],
        'RMSE': metrics_df.loc[idx, 'RMSE'],
        'Is_Hybrid': 1 if enc.get('methodology') == 'hybrid' else 0,
        'Days_To_Election': (election_date - pd.to_datetime(enc.get('date'))).days,
        'Sample_Size': enc.get('sample_size', 0)
    })

reg_df = pd.DataFrame(ols_data).set_index('Label').dropna()

# --- 3. ИЗПЪЛНЕНИЕ НА РЕГРЕСИИТЕ ---
X = sm.add_constant(reg_df[['Is_Hybrid', 'Days_To_Election', 'Sample_Size']])
model_mae = sm.OLS(reg_df['MAE'], X).fit()
model_rmse = sm.OLS(reg_df['RMSE'], X).fit()

# --- 4. ВИЗУАЛИЗАЦИЯ 1: ВЛИЯНИЕ НА ФАКТОРИТЕ (КОЕФИЦИЕНТИ) ---
fig1, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 4), sharey=True)
plt.subplots_adjust(wspace=0.1)

# MAE Coefs
model_mae.params[1:].plot(kind='barh', xerr=model_mae.bse[1:], color='#3498db', ax=ax1, alpha=0.8, edgecolor='black')
ax1.axvline(0, color='red', linestyle='--', linewidth=1)
ax1.set_title('Въздействие върху MAE\n(Линейна средна грешка)', fontweight='bold')
ax1.set_xlabel('Промяна в % пунктове')

# RMSE Coefs
model_rmse.params[1:].plot(kind='barh', xerr=model_rmse.bse[1:], color='#e74c3c', ax=ax2, alpha=0.8, edgecolor='black')
ax2.axvline(0, color='red', linestyle='--', linewidth=1)
ax2.set_title('Въздействие върху RMSE\n(Наказание за големи грешки)', fontweight='bold')
ax2.set_xlabel('Промяна в % пунктове')

plt.suptitle('АНАЛИЗ НА ФАКТОРИТЕ ЗА ГРЕШКА (OLS REGRESSION COEF)', fontsize=16, fontweight='bold', y=1.05)
plt.show()

# --- 5. ВИЗУАЛИЗАЦИЯ 2: SIDE-BY-SIDE ТАГНАТА ДИНАМИКА (MAE vs RMSE) ---
fig2, (ax3, ax4) = plt.subplots(2, 1, figsize=(16, 14), sharex=True)
palette = ['#3498db', '#e74c3c']

for ax, metric, color, title in zip([ax3, ax4], ['MAE', 'RMSE'], palette, ['MAE (Линейна)', 'RMSE (Квадратична)']):
    # Scatter plot
    sns.scatterplot(
        data=reg_df, x='Days_To_Election', y=metric,
        hue='Is_Hybrid', size='Sample_Size', sizes=(150, 800),
        palette=palette, alpha=0.6, edgecolor='white', linewidth=1.5, ax=ax
    )

    # Тагване на всяка точка
    for label, row in reg_df.iterrows():
        ax.text(
            x=row['Days_To_Election'],
            y=row[metric] + (row[metric] * 0.02), # динамично отместване нагоре
            s=label, fontsize=8, fontweight='semibold', ha='center',
            bbox=dict(facecolor='white', alpha=0.5, edgecolor='none', pad=0.5)
        )

    ax.invert_xaxis()
    ax.set_title(f'Динамика на {title} грешката', fontsize=14, fontweight='bold')
    ax.set_xlabel('Дни до изборния ден', fontsize=11)
    ax.set_ylabel('Стойност на грешката', fontsize=11)
    ax.grid(True, linestyle=':', alpha=0.6)
    ax.get_legend().remove() # премахваме индивидуалните легенди за по-чист вид

# Единна легенда за целия плот
handles, labels = ax4.get_legend_handles_labels()
fig2.legend(handles, labels, title='Метод (0=Offline, 1=Hybrid) & Извадка', loc='center right', bbox_to_anchor=(1.1, 0.5))

plt.suptitle('СРАВНИТЕЛНА ХРОНОЛОГИЯ НА КАЧЕСТВОТО (ТАГНАТИ НАБЛЮДЕНИЯ)', fontsize=16, fontweight='bold', y=0.98)
plt.tight_layout()
plt.show()

# --- 6. СТРУКТУРИРАН ТЕКСТОВ АУТПУТ ---
print("\n" + "="*60)
print(" СРАВНИТЕЛЕН АНАЛИЗ НА КОЕФИЦИЕНТИТЕ (MAE vs RMSE)")
print("="*60)
summary_table = pd.DataFrame({
    'Metric': ['Is_Hybrid', 'Days_To_Election', 'Sample_Size'],
    'MAE Coef': model_mae.params[1:].values,
    'MAE P-value': model_mae.pvalues[1:].values,
    'RMSE Coef': model_rmse.params[1:].values,
    'RMSE P-value': model_rmse.pvalues[1:].values
}).round(4)
print(summary_table.to_string(index=False))
print("="*60)


============================================================
 СРАВНИТЕЛЕН АНАЛИЗ НА КОЕФИЦИЕНТИТЕ (MAE vs RMSE)
============================================================
          Metric  MAE Coef  MAE P-value  RMSE Coef  RMSE P-value
       Is_Hybrid    1.0574       0.0000     2.8161        0.0000
Days_To_Election    0.0051       0.1617     0.0056        0.4806
     Sample_Size   -0.0005       0.5626    -0.0010        0.5954
============================================================

Заключение и изводи от анализа

  • Системен пропуск:
    • Всички агенции подцениха победителя (Прогресивна България) с над 10% средно отклонение.
    • Всички агенции надцениха установените играчи (ГЕРБ, ДПС, Възраждане) в различна степен.
  • Методология:
    • Необходима е ревизия на моделите за разпределение на гласовете на нерешилите избиратели.
    • Необходима е критична ревизия на източниците и структурирането на извадката.
  • Информационна хигиена:
    • Ефирът е пълен с демографски, психографски и поведенчески разпределения на вота, към които трябва да се подходи със скептицизъм, имайки предвид мащаба на анализираните отклонения.
    • Анализи на по-малки кохорти (напр. от типа “коя партия е донор на гласоподаватели”) носят риск да са значително по-неточни.
  • Забележка:
    • Нормализирането на толкова голям набор от изследвания изисква задълбочена работа.
    • Този анализ е по-скоро отправна точка за по-задълбочени бъдещи мета-анализи, целящи по добро измерване на електоралните нагласи.
    • Най-належащата задача би била ръчното преглеждане (QA) и коригиране на входящите данни.