Магия таблиц стилей Matplotlib. Пошаговая инструкция Визуализации данных на Python

Визуализация данных — важная компетенция любого специалиста по данным. К сожалению, создание готовых к публикации визуализаций данных занимает очень много времени и хорошего вкуса. В мире Python + Matplotlib специалисты по данным зачастую строят графики низкого качества, которые, мягко говоря, не вдохновляют.

https://t.me/data_analysis_ml

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

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

Сначала нам нужно создать визуализацию по умолчанию в Matplotlib, и для этого нам нужны данные для использования. Я собрал набор данных о ВВП страны из Kaggle (ссылка здесь). Затем я импортировал pandas и matplotlib, загрузил данные в Dataframe, а затем построил график.

import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv('gdp_csv.csv')
df.head()
fig, ax = plt.subplots()
usa = df[df['Country Code'] == 'USA']
china = df[df['Country Code'] == 'CHN']
ax.plot(usa['Year'], usa['Value'] / 1_000_000_000_000)
ax.plot(china['Year'], china['Value'] / 1_000_000_000_000)
ax.set_title("USA and China GDP: 1960-2020");

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

plt.style.use('fivethirtyeight')

fig, ax = plt.subplots()
usa = df[df['Country Code'] == 'USA']
china = df[df['Country Code'] == 'CHN']
ax.plot(usa['Year'], usa['Value'] / 1_000_000_000_000)
ax.plot(china['Year'], china['Value'] / 1_000_000_000_000)
ax.set_title("USA and China GDP: 1960-2020");

Изменение таблицы стилей по умолчанию — это снова всего лишь одна строка кода.

plt.style.use('default')

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

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

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

# LINES
#lines.linewidth: 1.5               # line width in points
#lines.linestyle: -                 # solid line
#lines.color: C0                    # has no affect on plot(); see axes.prop_cycle
#lines.marker: None                 # the default marker

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

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

import matplotlib as mpl
mpl.rcParams.keys()

Функция вернет длинный список, который вы можете прокручивать.

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

Prop Cycler
Pro Cycler задает свойства Matplotlib (например, цвет или стиль линии) для каждого последующего вызова plot. В таблице стилей это выглядит так.

# Set custom colors. All colors are in web style hex format.
axes.prop_cycle: cycler('color', ['1879CE', 'FC4F30', '3EBCD2', '379A8B', 'EBB434', '758D99'])

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

Если мы хотим указать rcparam непосредственно в коде, мы можем сделать это, как показано ниже. В этом случае нам также нужно импортировать класс Cycler.

from cycler import cycler
import matplotlib as mpl

mpl.rcParams['axes.prop_cycle'] = cycler(color=['#1879CE', '#FC4F30', '#3EBCD2', '#379A8B', '#EBB434', '#758D99'])

Теперь мы видим, что цвет двух линий соответствует первым двум цветам в нашем prop cycler.

fig, ax = plt.subplots()
usa = df[df['Country Code'] == 'USA']
china = df[df['Country Code'] == 'CHN']
ax.plot(usa['Year'], usa['Value'] / 1_000_000_000_000)
ax.plot(china['Year'], china['Value'] / 1_000_000_000_000)
ax.set_title("USA and China GDP: 1960-2020");

Стиль линий

Мы можем установим ширину линии мы можем изменить формы и толщину линий, можем скруглить концы наших линий и многое другое.

# Style spines
axes.linewidth: 1               # Spine edge line width
axes.spines.top: False          # Display axis spines (True or False)
axes.spines.left: True          # We only want the left and bottom spines
axes.spines.right: False
axes.spines.bottom: True

# Set line styling for line plots
lines.linewidth: 4              # line width in points
lines.solid_capstyle: butt      # Makes a square ending of the line stopping at datapoint

код:

mpl.rcParams['axes.linewidth'] = 1
mpl.rcParams['axes.spines.top'] = False
mpl.rcParams['axes.spines.left'] = True
mpl.rcParams['axes.spines.right'] = False
mpl.rcParams['axes.spines.bottom'] = True

mpl.rcParams['lines.linewidth'] = 4
mpl.rcParams['lines.solid_capstyle'] = 'butt'

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

fig, ax = plt.subplots()
usa = df[df['Country Code'] == 'USA']
china = df[df['Country Code'] == 'CHN']
ax.plot(usa['Year'], usa['Value'] / 1_000_000_000_000)
ax.plot(china['Year'], china['Value'] / 1_000_000_000_000)
ax.set_title("USA and China GDP: 1960-2020");

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

Лично мне Grid нравится, но только по одной оси. Наличие как вертикальной, так и горизонтальной сетки создает перенасыщенный вид, который может испортить график.

# Grid style
axes.grid: true                 # display grid or not
axes.grid.axis: y               # which axis the grid should apply to          
grid.linewidth: 1               # in points
grid.color: A5A5A5              # grid color
axes.axisbelow: True            # Sets axis gridlines below lines and patches.
mpl.rcParams['axes.grid'] = True
mpl.rcParams['axes.grid.axis'] = 'y'
mpl.rcParams['grid.linewidth'] = 1
mpl.rcParams['grid.color'] = '#A5A5A5'
mpl.rcParams['axes.axisbelow'] = True

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

fig, ax = plt.subplots()
usa = df[df['Country Code'] == 'USA']
china = df[df['Country Code'] == 'CHN']
ax.plot(usa['Year'], usa['Value'] / 1_000_000_000_000)
ax.plot(china['Year'], china['Value'] / 1_000_000_000_000)
ax.set_title("USA and China GDP: 1960-2020");

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

Значение DPI по умолчанию равно 100. Это не страшно, но дает заметное размытие на большинстве экранов.. Я обнаружил, что в некоторых IDE, таких как JupyterLab, установка DPI для построения графика на 300 или выше увеличивает размер графика на экране.

Я установил DPI на 300. В результате файл с графиком при печати выглядит великолепно при увеличении.

Размер фигуры можно установить в таблице стилей. Размеры фигур задаются в дюймах в Matplotlib.

Графики Matplotlib по умолчанию почти квадратные (на самом деле это соотношение сторон 4: 3, 6,4×4,8 дюйма). Это хорошо, потому что линия с наклоном 1 будет подниматься на 45 градусов. Расширение графика уменьшит этот угол. В зависимости от данных, которые вы рисуете, вы можете изменить соотношение сторон.

Во времена смартфонов визуализация должна хорошо выглядеть на разных типах экранов. Я выбрал размер фигурки 16х11. Это почти широкоэкранный формат для монитора, но когда заголовок и нижний колонтитул будут добавлены позже, изображение будет иметь соотношение сторон почти 4:3.

# Set spacing for figure and also DPI.
figure.subplot.left: 0.08       # the left side of the subplots of the figure
figure.subplot.right: 0.95      # the right side of the subplots of the figure
figure.subplot.bottom: 0.07     # the bottom of the subplots of the figure
figure.figsize: 16, 11          # figure size in inches
figure.dpi: 150                 # figure dots per inch

# Properties for saving the figure. Ensure a high DPI when saving so we have a good resolution.
savefig.dpi:       300          # figure dots per inch or 'figure'
savefig.facecolor: white        # figure face color when saving
savefig.bbox:      tight        # {tight, standard}
savefig.pad_inches:   0.2       # padding when bbox is set to tight

# Legend Styling
legend.framealpha: 1

код:

# Set spacing for figure and also DPI.
mpl.rcParams['figure.subplot.left'] = 0.08
mpl.rcParams['figure.subplot.right'] = 0.95
mpl.rcParams['figure.subplot.bottom'] = 0.07
mpl.rcParams['figure.figsize'] = 16, 11
mpl.rcParams['figure.dpi'] = 150

# Properties for saving the figure.
# Ensure a high DPI when saving so we have a good resolution.
mpl.rcParams['savefig.dpi'] = 300
mpl.rcParams['savefig.facecolor'] = 'white'
mpl.rcParams['savefig.bbox'] = 'tight'
mpl.rcParams['savefig.pad_inches'] = 0.2

# Legend Styling
mpl.rcParams['legend.framealpha'] = 1

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

fig, ax = plt.subplots()
usa = df[df['Country Code'] == 'USA']
china = df[df['Country Code'] == 'CHN']
ax.plot(usa['Year'], usa['Value'] / 1_000_000_000_000)
ax.plot(china['Year'], china['Value'] / 1_000_000_000_000)
ax.set_title("USA and China GDP: 1960-2020");

Метки и размеры шрифта
Тикеры — это маленькие линии, выходящие из графика по осям X и Y. Они указывают на даты, которые в данном случае являются годами и цифрами ВВП. Это может показаться небольшими деталями, но эти небольшие изменения могут придавать вашей визуализации особый вид.

Кроме того, очень важны размеры шрифта. Текст должен легко читаться. При увеличении размера нашего графика мы увидели, что весь текст на графике стал меньше. Мы продолжим и поднимем их повыше и установим тикеры на 18 пунктов, а заголовок на 28.

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

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

axes.titlelocation: left        # alignment of the title: {left, right, center}
axes.titlepad: 20               # pad between axes and title in points
axes.titlesize: 28              # font size of the axes title
axes.titleweight: bold          # font weight of title

# Remove major and minor ticks except for on the x-axis.
xtick.major.size: 5             # major tick size in points
xtick.minor.size: 0             # minor tick size in points
ytick.major.size: 0
ytick.minor.size: 0

код:

# Setting font sizes and spacing
mpl.rcParams['axes.labelsize'] = 18
mpl.rcParams['xtick.labelsize'] = 18
mpl.rcParams['ytick.labelsize'] = 18
mpl.rcParams['font.size'] = 18
mpl.rcParams['xtick.major.pad'] = 8
mpl.rcParams['ytick.major.pad'] = 12

# Title styling
mpl.rcParams['axes.titlelocation'] = 'left'
mpl.rcParams['axes.titlepad'] = 20
mpl.rcParams['axes.titlesize'] = 28
mpl.rcParams['axes.titleweight'] = 'bold'

# Remove major and minor ticks except for on the x-axis.
mpl.rcParams['xtick.major.size'] = 5
mpl.rcParams['xtick.minor.size'] = 0
mpl.rcParams['ytick.major.size'] = 0
mpl.rcParams['ytick.minor.size'] = 0

На данный момент наш график выглядит довольно хорошо. Давайте соберем все вместе и сделаем таблицу стилей!

fig, ax = plt.subplots()
usa = df[df['Country Code'] == 'USA']
china = df[df['Country Code'] == 'CHN']
ax.plot(usa['Year'], usa['Value'] / 1_000_000_000_000)
ax.plot(china['Year'], china['Value'] / 1_000_000_000_000)
ax.set_title("USA and China GDP: 1960-2020");

Таблица стилей Datafantic

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

Используя свои настройки стилей, я создал таблицу стилей под названием datafantic.mplstyle. Все строки rcparam выше, добавленные в один файл.

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

# Set custom colors. All colors are in web style hex format.
axes.prop_cycle: cycler('color', ['1879CE', 'FC4F30', '3EBCD2', '379A8B', 'EBB434', '758D99'])


# Style spines
axes.linewidth: 1               # Spine edge line width
axes.spines.top: False          # Display axis spines (True or False)
axes.spines.left: True          # We only want the left and bottom spines
axes.spines.right: False
axes.spines.bottom: True

# Set line styling for line plots
lines.linewidth: 4              # line width in points
lines.solid_capstyle: butt      # Makes a square ending of the line stopping at datapoint

# Grid style
axes.grid: true                 # display grid or not
axes.grid.axis: y               # which axis the grid should apply to          
grid.linewidth: 1               # in points
grid.color: A5A5A5              # grid color
axes.axisbelow: True            # Sets axis gridlines below lines and patches.

# Setting font sizes and spacing
axes.labelsize: 18              # font size of the x and y labels
xtick.labelsize: 18             # font size of the x tick labels      
ytick.labelsize: 18             # font size of the y tick labels
font.size: 18                   # default font size for text, given in points.
xtick.major.pad: 8              # distance to major tick label in points  
ytick.major.pad: 12             # distance to major tick label in points

# Title styling
axes.titlelocation: left        # alignment of the title: {left, right, center}
axes.titlepad: 20               # pad between axes and title in points
axes.titlesize: 28              # font size of the axes title
axes.titleweight: bold          # font weight of title

# Remove major and minor ticks except for on the x-axis.
xtick.major.size: 5             # major tick size in points
xtick.minor.size: 0             # minor tick size in points
ytick.major.size: 0
ytick.minor.size: 0

# Set spacing for figure and also DPI.
figure.subplot.left: 0.08       # the left side of the subplots of the figure
figure.subplot.right: 0.95      # the right side of the subplots of the figure
figure.subplot.bottom: 0.07     # the bottom of the subplots of the figure
figure.figsize: 16, 11          # figure size in inches
figure.dpi: 150                 # figure dots per inch

# Properties for saving the figure. Ensure a high DPI when saving so we have a good resolution.
savefig.dpi:       300          # figure dots per inch or 'figure'
savefig.facecolor: white        # figure face color when saving
savefig.bbox:      tight        # {tight, standard}
savefig.pad_inches:   0.2       # padding when bbox is set to tight

# Legend Styling
legend.framealpha: 1

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

plt.style.use('datafantic.mplstyle')

fig, ax = plt.subplots()
usa = df[df['Country Code'] == 'USA']
china = df[df['Country Code'] == 'CHN']
ax.plot(usa['Year'], usa['Value'] / 1_000_000_000_000)
ax.plot(china['Year'], china['Value'] / 1_000_000_000_000)
ax.set_title("USA and China GDP: 1960-2020");

В целом, это намного лучше, чем график по умолчанию. Кроме того, настройка DPI и размера фигуры делает все надписи четче. Нам все еще не хватает легенды графика, и еще многое предстоит сделать, чтобы этот график был готов к публикации. Хотя начало хорошее!

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

Итак, давайте переместим метки оси Y вправо. Это также устранит необходимость в оси Y (вертикальная линия). Я создал еще одну таблицу стилей под названием datafantic-right.mplstyle, которая делает все это.

# Set custom colors. All colors are in web style hex format.
axes.prop_cycle: cycler('color', ['1879CE', 'FC4F30', '3EBCD2', '379A8B', 'EBB434', '758D99'])


# Style spines
axes.linewidth: 1               # Spine edge line width
axes.spines.top: False          # Display axis spines (True or False)
axes.spines.left: False         # We only want the bottom spines
axes.spines.right: False
axes.spines.bottom: True

# Set line styling for line plots
lines.linewidth: 4              # line width in points
lines.solid_capstyle: butt      # Makes a square ending of the line stopping at datapoint

# Grid style
axes.grid: true                 # display grid or not
axes.grid.axis: y               # which axis the grid should apply to          
grid.linewidth: 1               # in points
grid.color: A5A5A5              # grid color
axes.axisbelow: True            # Sets axis gridlines below lines and patches.

# Move tick labels to right side
ytick.labelleft: False          # draw tick labels on the left side
ytick.labelright: True          # draw tick labels on the right side
ytick.alignment: bottom         # alignment of yticks

# Setting font sizes and spacing
axes.labelsize: 18              # font size of the x and y labels
xtick.labelsize: 18             # font size of the x tick labels      
ytick.labelsize: 18             # font size of the y tick labels
font.size: 18                   # default font size for text, given in points.
xtick.major.pad: 8              # distance to major tick label in points  
ytick.major.pad: -40            # distance to major tick label in points

# Remove major and minor ticks except for on the x-axis.
xtick.major.size: 5             # major tick size in points
xtick.minor.size: 0             # minor tick size in points
ytick.major.size: 0
ytick.minor.size: 0

# Title styling
axes.titlelocation: left        # alignment of the title: {left, right, center}
axes.titlepad: 20               # pad between axes and title in points
axes.titlesize: 28              # font size of the axes title
axes.titleweight: bold          # font weight of title

# Set spacing for figure and also DPI.
figure.subplot.left: 0.08       # the left side of the subplots of the figure
figure.subplot.right: 0.95      # the right side of the subplots of the figure
figure.subplot.bottom: 0.07     # the bottom of the subplots of the figure
figure.figsize: 16, 11          # figure size in inches
figure.dpi: 150                 # figure dots per inch

# Properties for saving the figure. Ensure a high DPI when saving so we have a good resolution.
savefig.dpi:       300          # figure dots per inch or 'figure'
savefig.facecolor: white        # figure face color when saving
savefig.bbox:      tight        # {tight, standard}
savefig.pad_inches:   0.2       # padding when bbox is set to tight

# Legend Styling
legend.framealpha: 1

Давайте установим эту таблицу стилей и посмотрим, как она выглядит!

plt.style.use('datafantic-right.mplstyle')

fig, ax = plt.subplots()
usa = df[df['Country Code'] == 'USA']
china = df[df['Country Code'] == 'CHN']
ax.plot(usa['Year'], usa['Value'] / 1_000_000_000_000)
ax.plot(china['Year'], china['Value'] / 1_000_000_000_000)
ax.set_title("USA and China GDP: 1960-2020");

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

Наконец, минимум по оси Y должен быть равен 0. Если вы имеете дело со всеми положительными числами.

Давайте добавим на график еще несколько стран, пометим оси, установим легенду и установим минимум оси Y равным 0.

fig, ax = plt.subplots()
usa = df[df['Country Code'] == 'USA']
china = df[df['Country Code'] == 'CHN']
uk = df[df['Country Code'] == 'GBR']
russia = df[df['Country Code'] == 'RUS']
ax.plot(usa['Year'], usa['Value'] / 1_000_000_000_000, label='USA')
ax.plot(china['Year'], china['Value'] / 1_000_000_000_000, label='China')
ax.plot(uk['Year'], uk['Value'] / 1_000_000_000_000, label='UK')
ax.plot(russia['Year'], russia['Value'] / 1_000_000_000_000, label='Russia')

ax.legend()
ax.set_title("USA and China GDP: 1960-2020")
ax.set_xlabel("Year")
ax.yaxis.set_label_position("right")
ax.set_ylabel("GDP in Trillions of USD")
ax.set_ylim(0)

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

Добавьте подзаголовок, который лучше описывает данные.
Добавим нижний колонтитул под графиком с моим логотипом и источником данных.
Нам нужно добавить подзаголовок вручную, используя функцию ax.text в Matplotlib.

fig, ax = plt.subplots()
usa = df[df['Country Code'] == 'USA']
china = df[df['Country Code'] == 'CHN']
uk = df[df['Country Code'] == 'GBR']
russia = df[df['Country Code'] == 'RUS']
ax.plot(usa['Year'], usa['Value'] / 1_000_000_000_000, label='USA')
ax.plot(china['Year'], china['Value'] / 1_000_000_000_000, label='China')
ax.plot(uk['Year'], uk['Value'] / 1_000_000_000_000, label='UK')
ax.plot(russia['Year'], russia['Value'] / 1_000_000_000_000, label='Russia')

ax.set_title("Rising Above the Pack")
ax.set_ylim(0)

# Add in title and subtitle
ax.text(x=.08, y=.86, 
        s="GDP from 1960-2020, in Trillions of USD", 
        transform=fig.transFigure, 
        ha='left', 
        fontsize=20, 
        alpha=.8)

# Set source text
ax.text(x=.08, y=0, 
        s="""Source: "GDP of all countries(1960-2020)" via Kaggle.com""", 
        transform=fig.transFigure, 
        ha='left', 
        fontsize=14, 
        alpha=.7)

# Label the lines directly
ax.text(x=.8, y=.75, s="""USA""", 
        transform=fig.transFigure, ha='left', fontsize=20, alpha=.7)
ax.text(x=.82, y=.5, s="""China""", 
        transform=fig.transFigure, ha='left', fontsize=20, alpha=.7)
ax.text(x=.83, y=.2, s="""UK""", 
        transform=fig.transFigure, ha='left', fontsize=20, alpha=.7)
ax.text(x=.83, y=.11, s="""Russia""", 
        transform=fig.transFigure, ha='left', fontsize=20, alpha=.7)

# Export plot as high resolution PNG
plt.savefig('rising_above.png')

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

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

Надеюсь, статья вдохновит вас на создание собственных таблиц стилей в Matplotlib. Это мощный способ задать собственный стиль визуализации данных для ваших проектов.

Вы можете просмотреть весь код здесь.

https://t.me/ai_machinelearning_big_data

Источник

Ответить