Как создать привлекательные графики с рейтингами стран с помощью Python и Matplotlib
Приветствую вас в этом руководстве, я научу вас создавать диаграмму рейтинга стран с помощью Python и Matplotlib.
Что мне нравится в этой визуализации, так это чистый и красивый способ показать, как страны ранжируются друг с другом по определенному показателю.
Альтернатива в виде стандартной линейной диаграммы, отображающей фактические значения, выглядит неинформативно и беспорядочной, если некоторые страны находятся близко друг к другу или если одни страны значительно превосходят другие.
Если вы хотите получить доступ к коду из этого руководства, вы можете найти его в этом GitHub repository.
Давайте начнем.
О данных
Для этого урока я создал простой CSV–файл, содержащий значения ВВП для десяти крупнейших экономик мира на сегодняшний день.
Данные получены от Всемирного банка.
Если вы хотите узнать больше о различных способах измерения ВВП, вы можете посмотреть эту статью на Medium, где я использую тот же тип визуализации данных.
Приступим к рассмотрению учебника.
Шаг 1: Создание рейтингов
На первом этапе необходимо упорядочить страны по годам , что легко сделать с помощью pandas.
def create_rankings(df, columns):
rank_columns = ["rank_{}".format(i) for i in range(len(columns))]
for i, column in enumerate(columns):
df[rank_columns[i]] = df[column].rank(ascending=False)
return df, rank_columns
Полученные столбцы выглядят следующим образом.
Это вся предварительная обработка, необходимая для продолжения визуализации данных.
Шаг 2: Создание и стилизация сетки
Теперь, когда мы подготовили данные, пришло время создать сетку, на которой мы можем рисовать наши линии и флажки.
Прминим функцию из Seaborn, которая задает общий стиль. Она определяет такие вещи, как цвет фона и шрифты.
def set_style(font_family, background_color, grid_color, text_color):
sns.set_style({
"axes.facecolor": background_color,
"figure.facecolor": background_color,
"axes.grid": True,
"axes.axisbelow": True,
"grid.color": grid_color,
"text.color": text_color,
"font.family": font_family,
"xtick.bottom": False,
"xtick.top": False,
"ytick.left": False,
"ytick.right": False,
"axes.spines.left": False,
"axes.spines.bottom": False,
"axes.spines.right": False,
"axes.spines.top": False,
}
)
Я запускаю функцию со следующими значениями.
font_family = "PT Mono"
background_color = "#FAF0F1"
text_color = "#080520"
grid_color = "#E4C9C9"
set_style(font_family, background_color, grid_color, text_color)
Для создания сетки у меня есть функция, которая форматирует оси y и x. Она принимает несколько параметров, которые позволяют мне попробовать различные настройки, например, размер меток.
def format_ticks(ax, years, padx=0.25, pady=0.5, y_label_size=20, x_label_size=24):
ax.set(xlim=(-padx, len(years) -1 + padx), ylim=(-len(df) - pady, - pady))
xticks = [i for i in range(len(years))]
ax.set_xticks(ticks=xticks, labels=years)
yticks = [-i for i in range(1, len(df) + 1)]
ylabels = ["{}".format(i) for i in range(1, len(df) + 1)]
ax.set_yticks(ticks=yticks, labels=ylabels)
ax.tick_params("y",labelsize=y_label_size, pad=16)
ax.tick_params("x", labeltop=True, labelsize=x_label_size, pad=8)
Вот как это выглядит, когда я запускаю все, что у нас есть на данный момент.
# Load data
years = ["2000", "2005", "2010", "2015", "2020", "2022"]
df = pd.read_csv("rankings.csv", index_col=None)
df, rank_columns = create_rankings(df, years)
# Create chart
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(15, 1.6*len(df)))
format_ticks(ax, years)
И вот полученная сетка.
Теперь мы можем приступить к добавлению данных.
Шаг 3: Добавление линий
Я хочу получить линию, показывающую ранг каждой страны для каждого года в наборе данных – это простая задача в Matplotlib.
def add_line(ax, row, columns, linewidth=3):
x = [i for i in range(len(columns))]
y = [-row[rc] for rc in columns]
ax.add_artist(
Line2D(x, y, linewidth=linewidth, color=text_color)
)
Затем я запускаю функцию для каждой строки в наборе данных следующим образом.
# Load data
years = ["2000", "2005", "2010", "2015", "2020", "2022"]
df = pd.read_csv("rankings.csv", index_col=None)
df, rank_columns = create_rankings(df, years)
# Create chart
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(15, 1.6*len(df)))
format_ticks(ax, years)
# Draw lines
for i, row in df.iterrows():
add_line(ax, row, rank_columns)
Я использую один и тот же цвет для каждой линии, поскольку хочу использовать флаги стран, вместо цветов. Использование уникального цвета для каждой строки имеет смысл, но выглядит неаккуратно.
Шаг 4: Построение круговых диаграмм
Я хочу показать, как растет экономика страны с течением времени, не добавляя текста. Вместо этого я хочу донести информацию в визуальном формате.
Моя идея заключается в том, чтобы нарисовать круговую диаграмму в каждой точке, показывающую размер экономики страны по сравнению с ее лучшим годом.
Для создания изображения круговой диаграммы я использую PIL, но вы можете использовать Matplotlib. Я этого не делаю, поскольку у меня возникли проблемы с соотношением сторон.
def add_pie(ax, x, y, ratio, size=572, zoom=0.1):
image = Image.new('RGBA', (size, size))
draw = ImageDraw.Draw(image)
draw.pieslice((0, 0, size, size), start=-90, end=360*ratio-90, fill=text_color, outline=text_color)
im = OffsetImage(image, zoom=zoom, interpolation="lanczos", resample=True, visible=True)
ax.add_artist(AnnotationBbox(
im, (x, y), frameon=False,
xycoords="data",
))
Значение параметра size немного больше, чем размер моих изображений флагов, которые имеют размер 512×512. В дальнейшем я хочу вставить флаги на круговые диаграммы.
Вот обновленный код.
# Load data
years = ["2000", "2005", "2010", "2015", "2020", "2022"]
df = pd.read_csv("rankings.csv", index_col=None)
df, rank_columns = create_rankings(df, years)
# Create chart
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(15, 1.6*len(df)))
format_ticks(ax, years)
# Draw lines
for i, row in df.iterrows():
add_line(ax, row, rank_columns)
for j, rc in enumerate(rank_columns):
add_pie(ax, j, -row[rc], ratio=row[years[j]] / row[years].max())
И вот результат.
Он начинает выглядеть информативно, поэтому пора навести красоту.
Шаг 5: Добавление флагов
Я люблю использовать флаги в своих диаграммах, потому что они красивы.
В данном случае флаги служат для того, чтобы сделать диаграмму визуально привлекательной, объяснить, какие страны мы рассматриваем, и направить взгляд вдоль линий.
Я использую эти округлые флажки. Они требуют лицензии, поэтому, к сожалению, я не могу поделиться ими, но вы можете найти похожие флажки в других местах.
Вместо создавать отдельную функцию для добавления флага, я переписал функцию add_pie().
def add_pie_and_flag(ax, x, y, name, ratio, size=572, zoom=0.1):
flag = Image.open("<location>/{}.png".format(name.lower()))
image = Image.new('RGBA', (size, size))
draw = ImageDraw.Draw(image)
pad = int((size - 512) / 2)
draw.pieslice((0, 0, size, size), start=-90, end=360*ratio-90, fill=text_color, outline=text_color)
image.paste(flag, (pad, pad), flag.split()[-1])
im = OffsetImage(image, zoom=zoom, interpolation="lanczos", resample=True, visible=True)
ax.add_artist(AnnotationBbox(
im, (x, y), frameon=False,
xycoords="data",
))
Я добавляю код сразу после функции круговой диаграммы.
# загрузка данных
years = ["2000", "2005", "2010", "2015", "2020", "2022"]
df = pd.read_csv("rankings.csv", index_col=None)
df, rank_columns = create_rankings(df, years)
# Create chart
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(15, 1.6*len(df)))
format_ticks(ax, years)
# Draw lines
for i, row in df.iterrows():
add_line(ax, row, rank_columns)
for j, rc in enumerate(rank_columns):
add_pie_and_flag(
ax, j, -row[rc],
name=row.country_name,
ratio=row[years[j]] / row[years].max()
)
И теперь вы можете наблюдать магию использования флагов. Это огромная разница по сравнению с предыдущей картинкой.
В итоге у нас получилось нечто приятное на вид и понятное. Осталось добавить полезную информацию.
Шаг 5: Добавление дополнительной информации
Поскольку не все знают все флаги наизусть, я хочу добавить справа название страны.
Также я хочу показать размер экономики и сравнение каждой страны с самой высокой по рейтингу.
Вот мой код для этого.
def add_text(ax, value, max_value, y):
trillions = round(value / 1e12, 1)
ratio_to_max = round(100 * value / max_value, 1)
text = "{}\n${:,}T ({}%)".format(
row.country_name,
trillions,
ratio_to_max
)
ax.annotate(
text, (1.03, y),
fontsize=20,
linespacing=1.7,
va="center",
xycoords=("axes fraction", "data")
)
Как и прежде, я добавляю функцию в основной блок кода. Обратите внимание, что я также добавляю заголовок.
years = ["2000", "2005", "2010", "2015", "2020", "2022"]
df = pd.read_csv("rankings.csv", index_col=None)
df, rank_columns = create_rankings(df, years)
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(15, 1.6*len(df)))
format_ticks(ax, years)
for i, row in df.iterrows():
add_line(ax, row, rank_columns)
for j, rc in enumerate(rank_columns):
add_pie_and_flag(
ax, j, -row[rc],
name=row.country_name,
ratio=row[years[j]] / row[years].max()
)
add_text(ax, value=row[years[-1]], max_value=df.iloc[0][years[-1]], y=-(i + 1))
plt.title("Comparing Today's Largest Economies\nGDP (constant 2015 us$)", linespacing=1.8, fontsize=32, x=0.58, y=1.12)
Вуаля!
Все, мы закончили.
Заключение
Сегодня вы познакомились с альтернативным способом визуализации.
Мне нравится этот тип визуализации данных, потому что он прост для глаз и передает много информации с минимальным количеством текста.
Если вам понравилось так же, как и мне, обязательно подписывайтесь на мой канал, чтобы получить еще больше подобных материалов! 🙂
Спасибо, что читаете.