Как создать привлекательные графики с рейтингами стран с помощью Python и Matplotlib

Приветствую вас в этом руководстве, я научу вас создавать диаграмму рейтинга стран с помощью Python и Matplotlib.

Что мне нравится в этой визуализации, так это чистый и красивый способ показать, как страны ранжируются друг с другом по определенному показателю.

Альтернатива в виде стандартной линейной диаграммы, отображающей фактические значения, выглядит неинформативно и беспорядочной, если некоторые страны находятся близко друг к другу или если одни страны значительно превосходят другие.

Если вы хотите получить доступ к коду из этого руководства, вы можете найти его в этом GitHub repository.

Давайте начнем.


О данных

Для этого урока я создал простой CSVфайл, содержащий значения ВВП для десяти крупнейших экономик мира на сегодняшний день.

Screenshot of pandas DataFrame

Данные получены от Всемирного банка.

Если вы хотите узнать больше о различных способах измерения ВВП, вы можете посмотреть эту статью на 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

Полученные столбцы выглядят следующим образом.

Screenshot of pandas DataFrame

Это вся предварительная обработка, необходимая для продолжения визуализации данных.


Шаг 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)

И вот полученная сетка.

Matplotlib grid

Теперь мы можем приступить к добавлению данных.


Шаг 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)
Grid with lines

Я использую один и тот же цвет для каждой линии, поскольку хочу использовать флаги стран, вместо цветов. Использование уникального цвета для каждой строки имеет смысл, но выглядит неаккуратно.


Шаг 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())

И вот результат.

Grid with pie charts

Он начинает выглядеть информативно, поэтому пора навести красоту.


Шаг 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()
        )

И теперь вы можете наблюдать магию использования флагов. Это огромная разница по сравнению с предыдущей картинкой.

Grid with flags

В итоге у нас получилось нечто приятное на вид и понятное. Осталось добавить полезную информацию.


Шаг 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)

Вуаля!

Country rankings chart

Все, мы закончили.


Заключение

Сегодня вы познакомились с альтернативным способом визуализации.

Мне нравится этот тип визуализации данных, потому что он прост для глаз и передает много информации с минимальным количеством текста.

Если вам понравилось так же, как и мне, обязательно подписывайтесь на мой канал, чтобы получить еще больше подобных материалов! 🙂

Спасибо, что читаете.

+1
4
+1
2
+1
2
+1
0
+1
1

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *