# -*- coding: utf-8 -*-
"""
Модуль містить допоміжні функції для візуалізації результатів оцінювання.
"""
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import rcParams
from math import ceil, pi
from .utils import ROOT
FORMAT = "png"
MAX_PARAMS = 10
FIGURES = ROOT / "reports" / "figures"
dict_labels = {
"P1": "економіка",
"P2": "бюджет",
"P3": "приватизація",
"P4": "будівництво",
"P5": "екологія",
"P6": "освіта та здоров'я",
"P7": "соцзахист",
"P8": "праця",
}
[docs]def delete_frame(ax):
""" sns.despine() для matplotlib. """
ax.spines["top"].set_color("none")
ax.spines["bottom"].set_color("none")
ax.spines["left"].set_color("none")
ax.spines["right"].set_color("none")
[docs]def draw_spider(ax, angles, values, values_mean, cols):
"""Spider-візуалізація.
Parameters
----------
ax: matplotlib.axes._subplots.AxesSubplot
Тло візуалізації
angles: list[float]
Нахил для візулаізації
values: list[floats]
Оцінка параметрів верхнього рівня
values_mean: float
Середня оцінка параметрів верхнього рівня
cols: list[str]
Колонки параметрів верхнього рівня
"""
rot_angles = [0, -45, 90, 45, 0, -45, 90, 45, 0]
ax.set_rlabel_position(0)
ax.set_theta_offset(pi / 2)
ax.set_theta_direction(-1)
yticks = [*range(0, 12, 2)]
locs, labels = plt.xticks(angles[:-1], cols, color="grey", size=10, rotation=90)
plt.yticks(yticks, [])
plt.grid(color="#E8E8E8")
ax.axis([0, angles[-1], 0, 10])
ax.fill(angles, values_mean, color="gray", alpha=0.05)
ax.plot(angles, values, linewidth=1, linestyle="solid", color="#2f3f60")
for i, j in zip(angles, values):
ax.annotate(str(round(j, 1)), xy=(i, j + 1.3), ha="center", va="center")
plt.gcf().canvas.draw()
for label, angle in zip(labels, rot_angles):
x, y = label.get_position()
ax.text(
x,
y,
label.get_text(),
transform=label.get_transform(),
ha=label.get_ha(),
va=label.get_va(),
color="grey",
rotation=angle,
)
ax.set_xticklabels([])
[docs]def draw_multiple_spiders(df_index, cols, save=False):
"""Створює графік, що показує значення параметрів верхнього рівня для кожної з областей.
Обгорта для `draw_spider`
Parameters
----------
df_index: pd.DataFrame
Таблиця з розрахованим індексом
cols: list[str]
Колонки параметрів верхнього рівня
save: bool, defaults `False`
Чи слід зберігати зображення в ``reports/figures``
"""
rcParams["font.size"] = 7
N = len(cols)
angles = [n / float(N) * 2 * pi for n in range(N)]
angles.append(2 * pi)
values_mean = [*df_index[cols].mean()]
values_mean.append(values_mean[0])
x_labels = [dict_labels[col] for col in cols]
total = df_index.shape[0]
sub_cols = 8
sub_rows = ceil(total / sub_cols)
fig = plt.figure(figsize=(20, 10))
counter = 0
for r in range(sub_rows):
for c in range(sub_cols):
values = list(df_index.loc[counter, cols])
values.append(values[0])
title = df_index.loc[counter, "region"]
counter += 1
if counter <= total:
ax = plt.subplot2grid((sub_rows, sub_cols), (r, c), polar=True)
draw_spider(ax, angles, values, values_mean, x_labels)
ax.set_title(title, pad=17, fontsize=9)
plt.subplots_adjust(wspace=0.45, hspace=0.45)
if save:
plt.savefig(
FIGURES / f"00_index_gen_v3.{FORMAT}",
dpi=300,
bbox_inches="tight",
pad_inches=0.3,
transparent=False,
)
[docs]def draw_profile_gen(df_index, cols, reg_index, save=False):
"""Створює графік для області `reg_index` з оцінками параметрів верхнього рівня
Parameters
----------
df_index: pd.DataFrame
Таблиця з розрахованим індексом
cols: list[str]
Колонки верхнього параметрів рівня
reg_index: int
номер індексу таблиці області, яку слід візуалізувати
save: bool, defaults `False`
Чи слід зберігати зображення в ``reports/figures``
"""
rcParams["font.size"] = 12
fig = plt.figure(figsize=(6, 4))
values_mean = df_index[cols].mean().sort_index(ascending=False)
values = df_index.loc[reg_index, cols].sort_index(ascending=False)
x_labels = [dict_labels[col] for col in cols]
x_labels.reverse()
# title = df_index.loc[reg_index, "region"]
colors = []
for i in range(values_mean.shape[0]):
if values[i] >= values_mean[i]:
colors.append("#007f86")
elif values[i] < values_mean[i]:
colors.append("#a3550f")
ax = plt.subplot(111)
delete_frame(ax)
for ind in range(values_mean.shape[0]):
ax.plot(
[values_mean[ind], values[ind]],
[values_mean.index[ind], values_mean.index[ind]],
color="black",
zorder=0,
)
diff = values[ind] - values_mean[ind]
ax.annotate(
str(round(diff, 1)),
xy=(values[ind] - diff / 2, ind + 0.35),
ha="center",
va="center",
color=colors[ind],
fontsize=8,
)
ax.annotate(
str(round(values[ind], 1)),
xy=(values[ind], ind - 0.4),
ha="center",
va="center",
fontsize=8,
)
ax.scatter(values_mean, values_mean.index, color="gray", zorder=1, s=35)
ax.scatter(values, values.index, color=colors, zorder=2, alpha=1, s=35)
# ax.set_title(title+' область',pad=10)
ax.set_yticklabels(x_labels)
ax.set_xlim(0, 10)
ax.set_ylim(-1, 8)
ax.set_xlim(-1, 11)
if save:
plt.savefig(
FIGURES / f"01_region_profile_gen_{reg_index}.{FORMAT}",
dpi=300,
bbox_inches="tight",
pad_inches=0.3,
transparent=False,
)
plt.show()
plt.close()
[docs]def draw_profile_det(df_index, cols, reg_index, save=False):
"""Створює графік для області `reg_index` з оцінками параметрів нижнього рівня
Parameters
----------
df_index: pd.DataFrame
Таблиця з розрахованим індексом
cols: list[str]
Колонки нижнього параметрів рівня
reg_index: int
номер індексу таблиці області, яку слід візуалізувати
save: bool, defaults `False`
Чи слід зберігати зображення в ``reports/figures``
"""
rcParams["font.size"] = 12
x_labels = [dict_labels[col] for col in cols]
total = len(x_labels)
sub_cols = 4
sub_rows = ceil(total / sub_cols)
fig = plt.figure(figsize=(22, 10))
counter = 0
for r in range(sub_rows):
for c in range(sub_cols):
title = x_labels[counter]
counter += 1
if counter <= total:
cols_lower = df_index.loc[
:, df_index.columns.str.contains("p" + str(counter))
].columns
values_mean = df_index[cols_lower].mean().sort_index(ascending=False)
values_mean_all = list(
np.zeros(MAX_PARAMS - values_mean.shape[0])
) + list(values_mean)
index_labels_all = [
"" for i in range(MAX_PARAMS - values_mean.shape[0])
] + list(values_mean.index)
values_reg = df_index.loc[reg_index, cols_lower].sort_index(
ascending=False
)
values_reg = values_reg.map(lambda x: x + 0.0000001 if x == 0 else x)
values_reg_all = list(
np.zeros(MAX_PARAMS - values_reg.shape[0])
) + list(values_reg)
colors = []
for i in range(len(values_mean_all)):
if values_reg_all[i] >= values_mean_all[i]:
colors.append("#007f86")
elif values_reg_all[i] < values_mean_all[i]:
colors.append("#a3550f")
ax = plt.subplot2grid((sub_rows, sub_cols), (r, c))
delete_frame(ax)
for ind in range(len(values_mean_all)):
ax.plot(
[values_mean_all[ind], values_reg_all[ind]],
[ind, ind],
color="black",
zorder=0,
)
if values_reg_all[ind] > 0:
diff = values_reg_all[ind] - values_mean_all[ind]
ax.annotate(
str(round(diff, 2)),
xy=(values_reg_all[ind] - diff / 2, ind + 0.3),
ha="center",
va="center",
color=colors[ind],
fontsize=8,
)
ax.annotate(
str(round(values_reg_all[ind], 2)),
xy=(values_reg_all[ind], ind - 0.35),
ha="center",
va="center",
fontsize=8,
)
ax.scatter(
values_mean_all,
[i for i in range(MAX_PARAMS)],
s=[i if i == 0 else 35 for i in values_mean_all],
color="gray",
zorder=1,
)
ax.scatter(
values_reg_all,
[i for i in range(MAX_PARAMS)],
s=[i if i == 0 else 35 for i in values_reg_all],
color=colors,
zorder=2,
)
ax.set_yticks([i for i in range(10)])
ax.set_yticklabels(index_labels_all)
ax.set_xlim(-0.1, 1.1)
ax.set_title(title, pad=17)
title = df_index.loc[reg_index, "region"] + " область: загальний профіль"
fig.suptitle(title, fontsize=22, weight="bold", alpha=0.95)
plt.subplots_adjust(top=0.89, wspace=0.3, hspace=0.3)
if save:
plt.savefig(
FIGURES / f"02_region_profile_det_{reg_index}.{FORMAT}",
dpi=300,
bbox_inches="tight",
pad_inches=0.3,
transparent=False,
)
plt.show()
plt.close()
[docs]def draw_rankings(df_index, kvartal="III", save=False):
"""Створює відсортований барчарт з остаточними оцінками.
Parameters
----------
df_index: pd.DataFrame
Таблиця з розрахованим індексом
kvartal: str
Номер кварталу для винесення в заголовок візуалізації
save: bool, defaults `False`
Чи слід зберігати зображення в ``reports/figures``
"""
fig = plt.figure(figsize=(22, 10))
ax = plt.subplot(111)
delete_frame(ax)
df_index_hist = df_index[["region", "I"]].sort_values(by="I").reset_index(drop=True)
colors = ["#a3550f", "#f2dd05", "#007f86"]
colors_list = (
[colors[0] for r in range(3)]
+ [colors[1] for y in range(df_index_hist.shape[0] - 6)]
+ [colors[2] for g in range(3)]
)
ax.barh(df_index_hist["region"], df_index_hist["I"], color=colors_list)
for i in df_index_hist.index:
ax.annotate(
int(df_index_hist.loc[i, "I"]),
xy=(df_index_hist.loc[i, "I"] - 2.2, df_index_hist.loc[i, "region"]),
c="white",
va="center",
)
ax.set_xlim(0, 100)
fig.suptitle(
f"Індекс оцінки ОДА за {kvartal} квартал 2020 року",
fontsize=22,
weight="bold",
alpha=0.95,
)
plt.subplots_adjust(top=0.96)
if save:
plt.savefig(
FIGURES / f"00_index_ranking_v3.{FORMAT}",
dpi=300,
bbox_inches="tight",
pad_inches=0.3,
transparent=False,
)
plt.show()
plt.close()
[docs]def dynamic_rankings(df, value, value_colour, change, change_colour, save=False):
"""Створює відсортований барчарт з абсолютною оцінкою та показує
динаміку відносно минулого кварталу.
Parameters
----------
df: pd.DataFrame
Таблиця з розрахованим індексом
value: pd.Series
Колонка з абсолютною оцінкою за квартал
value_color : pd.Series
Колонка з кольорами для основного барчарту
change : pd.Series
Колонка з різницею абсолютної оцінки відносно минулого кварталу
change_colour: pd.Series
Колонка з кольорами для точкової діаграми
save: bool, defaults `False`
Чи слід зберігати зображення в ``reports/figures``
"""
OFFSETS = {"min": 2.3, "max": 0.3, "xlim_min": -10, "xlim_max": 10}
fig, (ax1, ax2) = plt.subplots(
nrows=1,
ncols=2,
figsize=(22, 10),
sharey=True,
gridspec_kw={
"wspace": 0,
"width_ratios": [3, 1],
},
)
ax1.spines["top"].set_color("none")
ax1.spines["left"].set_color("none")
ax1.spines["right"].set_color("none")
# rankings
ax1.barh(df["region"], df[value], color=df[value_colour])
ax1.barh(
df["region"],
df[value].max() - df[value],
left=df[value],
color="gray",
alpha=0.1,
)
for idx in df.index:
ax1.annotate(
df.loc[idx, value].round(2),
xy=(df.loc[idx, value] - 2.2, df.loc[idx, "region"]),
c="white",
va="center",
weight="bold",
)
# dynamics
ax2.axvline(0, color="gray", alpha=0.5)
ax2.hlines(df["region"], 0, df[change], color="gray", alpha=0.5)
ax2.scatter(df[change], df["region"], color=df[change_colour])
for idx in df.index:
data = df.loc[idx, change]
xpos = data - OFFSETS.get("min") if data < 0 else data + OFFSETS.get("max")
ax2.annotate(data.round(2), xy=(xpos, df.loc[idx, "region"]))
# axes
ax2.set_xlim(OFFSETS.get("xlim_min"), OFFSETS.get("xlim_max"))
ax1.set_title(
"Абсолютна оцінка області за сукупністю показників", loc="left", style="italic"
)
ax2.set_title(
"Зміна абсолютної оцінки відносно минулого кварталу",
loc="right",
style="italic",
)
ax2.axes.set_axis_off()
fig.suptitle(
"Індекс оцінки ОДА за 3 квартал 2020 року",
fontsize=22,
weight="bold",
alpha=0.95,
)
if save:
plt.savefig(
FIGURES / "ranking_with_dynamics.jpeg",
dpi=300,
bbox_inches="tight",
pad_inches=0.3,
transparent=False,
)
plt.show()
plt.close()
[docs]def draw_boxplot(ax, data, numbers):
"""Створює boxplot
Parameters
----------
ax: matplotlib.axes._subplots.AxesSubplot
Тло візуалізації
data: pd.DataFrame
Таблиця з розрахованим індексом
numbers: list
Перелік колонок, які слід ігнорувати
"""
ax.boxplot(data.values, vert=False, sym="")
for index, col in enumerate(data, start=1):
if col not in numbers:
arr = data[col]
y_noise = np.zeros_like(arr)
y_noise[
:,
] = index
ax.scatter(arr.values, y_noise, alpha=0.5, color="#007f86", s=10)
[docs]def draw_boxplot_dist(df_index, cols, save=False):
"""Створює зображення з розподілом показників нижнього рівня.
Обгортка для `draw_boxplot`
Parameters
----------
df_index: pd.DataFrame
asd
cols: list[str]
asd
reg_index: int
asd
save: bool, defaults `False`
asd
"""
numbers = [*range(10)]
x_labels = [dict_labels[col] for col in cols]
total = len(x_labels)
sub_cols = 4
sub_rows = ceil(total / sub_cols)
fig = plt.figure(figsize=(22, 10))
counter = 0
for r in range(sub_rows):
for c in range(sub_cols):
title = x_labels[counter]
counter += 1
df_cur = df_index.loc[:, df_index.columns.str.contains(f"p{counter}")]
df_cur = df_cur[df_cur.columns.sort_values(ascending=False)]
if df_cur.shape[1] < 10:
df_empty = pd.concat(
[
pd.Series(np.zeros(df_cur.shape[0]))
for i in range(10 - len(df_cur.columns))
],
axis=1,
)
df_all = pd.concat([df_empty, df_cur], axis=1)
else:
df_all = df_cur
ax = plt.subplot2grid((sub_rows, sub_cols), (r, c))
delete_frame(ax)
draw_boxplot(ax, df_all, numbers)
ax.set_yticklabels(["" if i in numbers else i for i in df_all.columns])
ax.set_title(title, pad=17)
title = "Розподіл значень за показниками нижнього рівня"
fig.suptitle(title, fontsize=22, weight="bold", alpha=0.95)
plt.subplots_adjust(top=0.89, wspace=0.3, hspace=0.3)
if save:
plt.savefig(
FIGURES / f"01_params_dist.{FORMAT}",
dpi=300,
bbox_inches="tight",
pad_inches=0.3,
transparent=False,
)
plt.show()
plt.close()