Создание инфографики с помощью Matplotlib

Создание инфографики с помощью Matplotlib

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

В мире Python существует множество библиотек, которые позволяют специалистам по данным создавать визуализации, и одна из первых, с которой многие сталкиваются, начиная свое путешествие по Data Science — это matplotlib . Однако, поработав некоторое время с matplotlib, многие люди обращаются к другим, более современным библиотекам, так как считают базовые графики matplotlib скучными и примитивными.

Потратив немного времени, усилий, кода и понимания возможностей matplotlib, мы можем превратить простые и скучные графики во что-то гораздо более убедительное и визуально привлекательное.

В этой статье мы рассмотрим создание инфографики с помощью matplotlib.

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

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

И я сделал.

Результатом этого стала следующая инфографика, которую мы воссоздадим в этой статье.

Создание инфографики с помощью Matplotlib

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

Цель инфографики

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

Для этого примера мы собираемся использовать набор литологических измерений каротажа, полученных на норвежском континентальном шельфе. Из этого набора данных мы собираемся конкретно рассмотреть вопрос:

Какова литологическая вариация группы Цехштейн в рамках этого набора данных?

Это дает нам отправную точку.

Мы знаем, что ищем данные по литологии и данные внутри группы Цехштейн.

Импорт библиотек и загрузка данных

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

Это pandas для загрузки и хранения наших данных, numpy для выполнения математических вычислений, позволяющих нам отображать метки и данные в полярных проекциях, matplotlib для создания нашего графика и AdjustText для обеспечения того, чтобы метки не перекрывались на нашем точечном графике.

import pandas as pd import matplotlib.pyplot as plt import numpy as np from adjustText import adjust_text

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

Первый набор данных, который мы загрузим, — это литологический состав группы Цехштайн, созданный в моей предыдущей статье.

Мы можем загрузить эти данные с помощью функции pandas read_csv().

df = pd.read_csv('Data/LithologySummary.csv', index_col='WELL')

Когда мы просматриваем нашу базу данных, у нас есть следующая информация о литологиях, присутствующих в группе Цехштейн, в интерпретации каждой скважины:

Создание инфографики с помощью Matplotlib

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

Мы можем загрузить эти данные таким же образом, используя pd.read_csv(). Однако на этот раз нам не нужно устанавливать индекс.

zechstein_well_intersections = pd.read_csv('Data/Zechstein_WellIntersection.csv')

Когда мы просматриваем этот фрейм данных, мы видим следующую таблицу, содержащую название скважины, координаты координатной сетки X и Y, где скважина проникла в группу Цехштейн.

Создание инфографики с помощью Matplotlib

Подготовка и создание инфографики с помощью Matplotlib

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

Во-первых, мы получим список всех возможных литологий. Это делается путем преобразования имен столбцов в нашем сводном фрейме данных в список.

lith_names = list(df.columns)

Когда мы просматриваем этот список, мы получаем следующие литологии.

Создание инфографики с помощью Matplotlib

Затем нам нужно решить, как мы хотим настроить отдельные графики в инфографике.

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

Мы также хотим показать расположение скважин на том же рисунке. Итак, это дает нам 9 подзаголовков.

Один из способов, которым мы можем разделить нашу фигуру, состоит в том, чтобы иметь 3 столбца и 3 строки. Это позволяет нам создать нашу первую переменную num_cols, представляющую количество столбцов.

Затем мы можем обобщить переменную количества строк ( num_rows), чтобы мы могли повторно использовать ее с другими наборами данных. В этом примере она получит количество имеющихся у нас скважин (количество строк в фрейме данных) и разделит его на количество нужных столбцов. Использование позволит np.ceil нам округлить это число, чтобы у нас были все графики на рисунке.

# Set the number of columns for your subplot grid num_cols = 3 # Get the number of wells (rows in the DataFrame) num_wells = len(df) # Calculate the number of rows needed for the subplot grid num_rows = np.ceil(num_wells / num_cols).astype(int)

Следующий набор переменных, которые нам нужно объявить, выглядит следующим образом:

  • indexes: создает список чисел от 0 до общего количества элементов в нашем списке. В нашем случае это создаст список от 0 до 7, который охватывает 8 литологий в нашем наборе данных.
  • width: создает список на основе расчета ширины каждого столбца на диаграмме путем деления длины окружности на количество типов камней, которые мы имеем в rock_names
  • angles: создает список, содержащий углы для каждого из типов скал.
  • colours: список шестнадцатеричных цветов, которые мы хотим использовать для представления каждой скважины
  • label_loc: создает список равномерно распределенных значений от 0 до 2 * pi для отображения меток типа камня.
indexes = list(range(0, len(lith_names))) width = 2*np.pi / len(lith_names) angles = [element * width for element in indexes] colours = ["#ae1241", "#5ba8f7", "#c6a000", "#0050ae", "#9b54f3", "#ff7d67", "#dbc227", "#008c5c"] label_loc = np.linspace(start=0, stop=2 * np.pi, num=len(lith_names))

Создание инфографики с помощью Matplotlib

Добавление радиальных гистограмм в качестве подграфиков

Чтобы начать создавать нашу инфографику, нам сначала нужно создать объект фигуры. Это делается путем обращения к plt.figure().

Чтобы настроить нашу фигуру, нам нужно передать несколько параметров:

  • figsize: управляет размером инфографики. Поскольку у нас может быть разное количество строк, мы можем установить параметр rows кратным количеству строк. Это предотвратит искажение графиков и цифр.
  • linewidth: управляет толщиной границы фигуры.
  • edgecolor: устанавливает цвет границы
  • facecolor: устанавливает цвет фона фигуры
# Create a figure fig = plt.figure(figsize=(20, num_rows * 7), linewidth=10, edgecolor='#393d5c', facecolor='#25253c')

Далее нам нужно определить макет нашей сетки. Есть несколько способов сделать это, но в этом примере мы будем использовать GridSpec. Это позволит нам указать расположение подграфиков, а также расстояние между ними.

# Create a grid layout grid = plt.GridSpec(num_rows, num_cols, wspace=0.5, hspace=0.5)

Теперь мы готовы начать добавлять наши радиальные гистограммы.

Для этого нам нужно перебрать каждую строку в фрейме сводных данных о составе литологии и добавить ось к сетке, используя add_subplot(). Поскольку мы строим радиальные гистограммы, мы хотим установить значение projection для параметра polar.

Затем мы можем начать добавлять наши данные на график, вызывая ax.bar. В этом вызове мы передаем:

  • angles: обеспечивает расположение стержня в полярной проекции, а также используется для размещения литологических меток.
  • height: использует процентные значения для текущей строки, чтобы установить высоту каждой полосы
  • width: используется для установки ширины полосы
  • edgecolor: устанавливает цвет края радиальных полос
  • zorder: используется для установки порядка построения столбцов на рисунке. В этом случае он установлен на 2, так что он находится в верхнем слое фигуры.
  • alpha: используется для установки прозрачности полос
  • color: устанавливает цвет полосы на основе списка цветов, определенного ранее.

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

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

Для меток литологии нам нужно создать цикл for, который позволит нам расположить метки под правильным углом вокруг края полярного графика.

В этом цикле нам нужно проверить, каков текущий угол внутри цикла. Если угол стержня меньше числа p, то из угла поворота вычитается 90 градусов. В противном случае, если полоса находится в нижней половине круга, к углу поворота добавляется 90 градусов. Это позволит легко читать метки слева и справа от графика.

# Loop over each row in the DataFrame for i, (index, row) in enumerate(df.iterrows()): ax = fig.add_subplot(grid[i // num_cols, i % num_cols], projection='polar') bars = ax.bar(x=angles, height=row.values, width=width, edgecolor='white', zorder=2, alpha=0.8, color=colours[i]) bars_bg = ax.bar(x=angles, height=100, width=width, color='#393d5c', edgecolor='#25253c', zorder=1) ax.set_title(index, pad=35, fontsize=22, fontweight='bold', color='white') ax.set_ylim(0, 100) ax.set_yticklabels([]) ax.set_xticks([]) ax.grid(color='#25253c') for angle, height, lith_name in zip(angles, row.values, lith_names): rotation_angle = np.degrees(angle) if angle < np.pi: rotation_angle -= 90 elif angle == np.pi: rotation_angle -= 90 else: rotation_angle += 90 ax.text(angle, 110, lith_name.upper(), ha='center', va='center', rotation=rotation_angle, rotation_mode='anchor', fontsize=12, fontweight='bold', color='white')

Когда мы запускаем код в этот момент, мы получаем следующее изображение, содержащее все 8 скважин.

Создание инфографики с помощью Matplotlib

Добавление точечной диаграммы в качестве подграфика

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

Для этого мы можем добавить новый подзаговор вне цикла for. Поскольку мы хотим, чтобы это был последний график на нашей фигуре, нам нужно вычесть 1 из num_rows и num_cols.

Затем мы добавляем точечную диаграмму к оси, вызывая ax.scatter() и передавая местоположения X и Y из фрейма данных zechstein_well_intersections.

Оставшаяся часть кода включает в себя добавление меток к осям x и y, настройку форматирования галочки и установку белых краев (шипов) диаграммы рассеяния.

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

Наконец, нам нужно добавить имена скважин в качестве меток, чтобы наши читатели могли понять, что представляет собой каждый маркер. Мы можем сделать это как часть цикла for и добавить метки в список.

# Add the scatter plot in the last subplot (subplot 9) ax = fig.add_subplot(grid[num_rows - 1, num_cols - 1], facecolor='#393d5c') ax.scatter(zechstein_well_intersections['X_LOC'], zechstein_well_intersections['Y_LOC'], c=colours, s=60) ax.grid(alpha=0.5, color='#25253c') ax.set_axisbelow(True) ax.set_ylabel('NORTHING', fontsize=12, fontweight='bold', color='white') ax.set_xlabel('EASTING', fontsize=12, fontweight='bold', color='white') ax.tick_params(axis='both', colors='white') ax.ticklabel_format(style='plain') ax.set_title('WELL LOCATIONS', pad=35, fontsize=22, fontweight='bold', color='white') ax.spines['bottom'].set_color('white') ax.spines['top'].set_color('white') ax.spines['right'].set_color('white') ax.spines['left'].set_color('white') ax.text(0.0, -0.2, 'Well 16/11-1 ST3 does not contain location information', ha='left', va='bottom', fontsize=10, color='white', transform=ax.transAxes) labels = [] for i, row in zechstein_well_intersections.iterrows(): labels.append(ax.text(row['X_LOC'], row['Y_LOC'], row['WELL'], color='white', fontsize=14))

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

Создание инфографики с помощью Matplotlib

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

Мы можем решить эту проблему, используя библиотеку AdjustText, которую мы импортировали ранее. Эта библиотека выработает наилучшее положение метки, чтобы избежать любой из этих проблем.

Чтобы использовать это, все, что нам нужно сделать, это вызвать adjust_text и передать список меток, который мы создали в предыдущем цикле for. Чтобы уменьшить количество перекрытий, мы можем использовать параметры expand_points и expand_objects. Для этого примера хорошо подходит значение 1,2.

adjust_text(labels, expand_points=(1.2, 1.2), expand_objects=(1.2, 1.2))
Создание инфографики с помощью Matplotlib

Добавление сносок и заголовков рисунков

Чтобы закончить нашу инфографику, нам нужно дать читателю дополнительную информацию.

Мы добавим сноску к рисунку, чтобы показать, откуда были получены данные и кто их создал.

Чтобы помочь читателю понять, о чем инфографика, мы можем добавить заголовок с помощью plt.suptitle и подзаголовок с помощью fig.text. Это мгновенно скажет читателю, чего он может ожидать, глядя на графики.

footnote = """ Data Source: Bormann, Peter, Aursand, Peder, Dilib, Fahad, Manral, Surrender, & Dischington, Peter. (2020). FORCE 2020 Well well log and lithofacies dataset for machine learning competition [Data set]. Zenodo. https://doi.org/10.5281/zenodo.4351156 Figure Created By: Andy McDonald """ plt.suptitle('LITHOLOGY VARIATION WITHIN THE ZECHSTEIN GP.', size=36, fontweight='bold', color='white') plot_sub_title = """CHARTS OF LITHOLOGY PERCENTAGES ACROSS 8 WELLS FROM THE NORWEGIAN CONTINENTAL SHELF""" fig.text(0.5, 0.95, plot_sub_title, ha='center', va='top', fontsize=18, color='white', fontweight='bold') fig.text(0.1, 0.01, footnote, ha='left', va='bottom', fontsize=14, color='white') plt.show()

После завершения построения кода мы получим фигуру matplotlib, подобную приведенной ниже:

Создание инфографики с помощью Matplotlib

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

Например, скважина 15/9–13 расположена на западной стороне участка и сложена смесью доломита, ангидрита и сланца. Тогда как скважина 17/11–1 расположена на восточной стороне участка и сложена преимущественно галитом. Это может быть связано с различными условиями осадконакопления в регионе.

Полный код для инфографики

Ниже показан полный код инфографики с комментариями к каждому из основных разделов:

# Set the number of columns for your subplot grid num_cols = 3 # Get the number of wells (rows in the DataFrame) num_wells = len(df) # Calculate the number of rows needed for the subplot grid num_rows = np.ceil(num_wells / num_cols).astype(int) indexes = list(range(0, len(lith_names))) width = 2*np.pi / len(lith_names) angles = [element * width for element in indexes] colours = ["#ae1241", "#5ba8f7", "#c6a000", "#0050ae", "#9b54f3", "#ff7d67", "#dbc227", "#008c5c"] label_loc = np.linspace(start=0, stop=2 * np.pi, num=len(lith_names)) # Create a figure fig = plt.figure(figsize=(20, num_rows * 7), linewidth=10, edgecolor='#393d5c', facecolor='#25253c') # Create a grid layout grid = plt.GridSpec(num_rows, num_cols, wspace=0.5, hspace=0.5) # Loop over each row in the DataFrame to create the radial bar charts per well for i, (index, row) in enumerate(df.iterrows()): ax = fig.add_subplot(grid[i // num_cols, i % num_cols], projection='polar') bars = ax.bar(x=angles, height=row.values, width=width, edgecolor='white', zorder=2, alpha=0.8, color=colours[i]) bars_bg = ax.bar(x=angles, height=100, width=width, color='#393d5c', edgecolor='#25253c', zorder=1) # Set up labels, ticks and grid ax.set_title(index, pad=35, fontsize=22, fontweight='bold', color='white') ax.set_ylim(0, 100) ax.set_yticklabels([]) ax.set_xticks([]) ax.grid(color='#25253c') #Set up the lithology / category labels to appear at the correct angle for angle, height, lith_name in zip(angles, row.values, lith_names): rotation_angle = np.degrees(angle) if angle < np.pi: rotation_angle -= 90 elif angle == np.pi: rotation_angle -= 90 else: rotation_angle += 90 ax.text(angle, 110, lith_name.upper(), ha='center', va='center', rotation=rotation_angle, rotation_mode='anchor', fontsize=12, fontweight='bold', color='white') # Add the scatter plot in the last subplot (subplot 9) ax = fig.add_subplot(grid[num_rows - 1, num_cols - 1], facecolor='#393d5c') ax.scatter(zechstein_well_intersections['X_LOC'], zechstein_well_intersections['Y_LOC'], c=colours, s=60) ax.grid(alpha=0.5, color='#25253c') ax.set_axisbelow(True) # Set up the labels and ticks for the scatter plot ax.set_ylabel('NORTHING', fontsize=12, fontweight='bold', color='white') ax.set_xlabel('EASTING', fontsize=12, fontweight='bold', color='white') ax.tick_params(axis='both', colors='white') ax.ticklabel_format(style='plain') ax.set_title('WELL LOCATIONS', pad=35, fontsize=22, fontweight='bold', color='white') # Set the outside borders of the scatter plot to white ax.spines['bottom'].set_color('white') ax.spines['top'].set_color('white') ax.spines['right'].set_color('white') ax.spines['left'].set_color('white') # Add a footnote to the scatter plot explaining missing well ax.text(0.0, -0.2, 'Well 16/11-1 ST3 does not contain location information', ha='left', va='bottom', fontsize=10, color='white', transform=ax.transAxes) # Set up and display well name labels labels = [] for i, row in zechstein_well_intersections.iterrows(): labels.append(ax.text(row['X_LOC'], row['Y_LOC'], row['WELL'], color='white', fontsize=14)) # Use adjust text to ensure text labels do not overlap with each other or the data points adjust_text(labels, expand_points=(1.2, 1.2), expand_objects=(1.2, 1.2)) # Create a footnote explaining data source footnote = """ Data Source: Bormann, Peter, Aursand, Peder, Dilib, Fahad, Manral, Surrender, & Dischington, Peter. (2020). FORCE 2020 Well well log and lithofacies dataset for machine learning competition [Data set]. Zenodo. https://doi.org/10.5281/zenodo.4351156 Figure Created By: Andy McDonald """ # Display overall infographic title and footnote plt.suptitle('LITHOLOGY VARIATION WITHIN THE ZECHSTEIN GP.', size=36, fontweight='bold', color='white') plot_sub_title = """CHARTS OF LITHOLOGY PERCENTAGES ACROSS 8 WELLS FROM THE NORWEGIAN CONTINENTAL SHELF""" fig.text(0.5, 0.95, plot_sub_title, ha='center', va='top', fontsize=18, color='white', fontweight='bold') fig.text(0.1, 0.01, footnote, ha='left', va='bottom', fontsize=14, color='white') plt.show()

Краткое содержание

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

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

Набор данных, используемый в этом руководстве

Набор обучающих данных, используемый в рамках конкурса по машинному обучению, проводимого Xeek и FORCE 2020 (Bormann et al., 2020) . Этот набор данных находится под лицензией Creative Commons Attribution 4.0 International.

Полный набор данных доступен по следующей ссылке: https://doi.org/10.5281/zenodo.4351155 .

1
Начать дискуссию