Книга: Изучаем Python: программирование игр, визуализация данных, веб-приложения. 3-е изд. дополненное и переработанное
Назад: 14. Игровой счет
Дальше: 16. Загрузка данных

15. Генерирование данных

26415.png

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

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

К счастью, для визуализации сложных данных не нужен суперкомпьютер. Благодаря эффективности Python вы сможете быстро исследовать наборы данных из миллионов отдельных элементов данных (точек данных, data points) на обычном ноутбуке. Элементы данных даже не обязаны быть числовыми. Приемы, о которых вы узнали в первой части книги, позволят вам проанализировать даже нечисловые данные.

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

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

Установка Matplotlib

Чтобы использовать библиотеку Matplotlib для исходных визуализаций, необходимо установить ее с помощью модуля pip, как мы делали с модулем pytest в главе 11 (см. раздел «Установка pytest с помощью pip»). Введите следующую команду в приглашении терминала:

$ python -m pip install --user matplotlib

Если для запуска программ или терминального сеанса вы вместо python используете другую команду (например, python3), то ваша команда будет выглядеть так:

$ python3 -m pip install --user matplotlib

Чтобы получить представление о визуализациях, которые можно создать с помощью средств Matplotlib, посетите главную страницу библиотеки по адресу https://matplotlib.org/ и перейдите в раздел Plot types (Типы графиков). Щелкая на визуализации в галерее, вы сможете просмотреть код, использованный для ее создания.

Создание простого графика

Начнем с создания простого линейного графика, а затем настроим его для более содержательной визуализации данных. В качестве данных для графика будет использоваться последовательность квадратов 1, 4, 9, 16 и 25.

Передайте Matplotlib числа так, как показано ниже, а библиотека сделает все остальное:

mpl_squares.py

import matplotlib.pyplot as plt

 

squares = [1, 4, 9, 16, 25]

❶ fig, ax = plt.subplots()

ax.plot(squares)

 

plt.show()

Сначала импортируйте модуль pyplot с псевдонимом plt, чтобы вам не приходилось многократно вводить слово pyplot. (Это сокращение часто встречается в онлайн-примерах, поэтому мы поступим так же.) Модуль pyplot содержит ряд функций для рисования диаграмм и графиков.

Мы создаем список squares для хранения данных, которые будем отображать на графике. Затем используем еще одно общепринятое правило Matplotlib — вызов функции subplots() . Она позволяет сгенерировать одну или несколько поддиаграмм на одном рисунке. Переменная fig обозначает весь рисунок, который представляет собой набор генерируемых диаграмм. Переменная ax соответствует одной диаграмме на рисунке; это переменная, которую мы будем использовать бо́льшую часть времени при определении и настройке отдельного графика.

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

15_01.tif 

Рис. 15.1. Пример простейшего графика в Matplotlib

Изменение типа надписей и толщины графика

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

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

mpl_squares.py

import matplotlib.pyplot as plt

 

squares = [1, 4, 9, 16, 25]

 

fig, ax = plt.subplots()

❶ ax.plot(squares, linewidth=3)

 

# Задание заголовка диаграммы и меток осей.

❷ ax.set_title("Square Numbers", fontsize=24)

❸ ax.set_xlabel("Value", fontsize=14)

ax.set_ylabel("Square of Value", fontsize=14)

 

# Задание размера шрифта делений на осях.

❹ ax.tick_params(labelsize=14)

 

plt.show()

Параметр linewidth управляет толщиной линии, которая создается вызовом plot() . Существует множество способов изменить созданную диаграмму перед представлением. Метод set_title() устанавливает заголовок диаграммы . Параметры fontsize, неоднократно встречающиеся в коде, управляют размером текста различных элементов диаграммы.

Методы xlabel() и ylabel() позволяют установить метки (заголовки) каждой из осей , а функция tick_params() определяет оформление делений на осях . Аргументы, использованные в данном примере, устанавливают для меток делений размер шрифта 14 (labelsize=14).

Как видно из рис. 15.2, график выглядит гораздо лучше. Текст надписей стал крупнее, а линия графика — толще. Часто стоит поэкспериментировать с этими значениями, чтобы получить представление о том, какой вариант оформления будет лучше смотреться на полученной диаграмме.

15_02.tif 

Рис. 15.2. График выглядит гораздо лучше

Корректировка графика

Теперь, когда текст на графике стало легче читать, мы видим, что данные помечены неправильно. Обратите внимание: для точки 4,0 в конце графика указан квадрат 25! Исправим эту проблему.

Если plot() передается числовая последовательность, то функция считает, что первый элемент данных соответствует координате x со значением 0, однако в нашем примере первая точка соответствует значению 1. Чтобы переопределить значение по умолчанию, передайте plot() как входные значения, так и квадраты:

mpl_squares.py

import matplotlib.pyplot as plt

 

input_values = [1, 2, 3, 4, 5]

squares = [1, 4, 9, 16, 25]

 

fig, ax = plt.subplots()

ax.plot(input_values, squares, linewidth=3)

 

# Задание заголовка диаграммы и меток осей.

--пропуск--

Теперь функции plot() не нужно предполагать, как был сгенерирован выходной набор чисел. На рис. 15.3 изображен правильный график.

15_03.tif 

Рис. 15.3. График с правильными данными

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

Встроенные стили

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

>>> import matplotlib.pyplot as plt

>>> plt.style.available

['Solarize_Light2', '_classic_test_patch', '_mpl-gallery',

--пропуск--

Чтобы использовать эти стили, добавьте одну строку кода перед вызовом функции subplots():

mpl_squares.py

import matplotlib.pyplot as plt

 

input_values = [1, 2, 3, 4, 5]

squares = [1, 4, 9, 16, 25]

 

plt.style.use('seaborn')

fig, ax = plt.subplots()

--пропуск--

Этот код создает график, изображенный на рис. 15.4. Существует множество разнообразных стилей; поэкспериментируйте и найдите те, которые вам больше нравятся.

15_04.tif 

Рис. 15.4. Встроенный стиль seaborn

Нанесение и оформление отдельных точек с помощью функции scatter()

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

Для нанесения на диаграмму отдельной точки используется функция scatter(). Передайте ей координаты (x, y) нужной точки, и функция нанесет эти значения на диаграмму:

scatter_squares.py

import matplotlib.pyplot as plt

 

plt.style.use('seaborn')

fig, ax = plt.subplots()

ax.scatter(2, 4)

 

plt.show()

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

import matplotlib.pyplot as plt

 

plt.style.use('seaborn')

fig, ax = plt.subplots()

❶ ax.scatter(2, 4, s=200)

 

# Задание заголовка диаграммы и меток осей.

ax.set_title("Square Numbers", fontsize=24)

ax.set_xlabel("Value", fontsize=14)

ax.set_ylabel("Square of Value", fontsize=14)

 

# Задание размера шрифта делений на осях.

ax.tick_params(labelsize=14)

 

plt.show()

Вызывается функция scatter(); аргумент s задает размер точек, используемых для рисования диаграммы . Если запустить программу scatter_squares.py в текущем состоянии, то вы увидите одну точку в середине диаграммы (рис. 15.5).

15_05.tif 

Рис. 15.5. Вывод одной точки

Вывод серии точек с помощью функции scatter()

Чтобы вывести на диаграмме серию точек, передайте scatter() списки значений координат x и y:

scatter_squares.py

import matplotlib.pyplot as plt

 

x_values = [1, 2, 3, 4, 5]

y_values = [1, 4, 9, 16, 25]

 

plt.style.use('seaborn')

fig, ax = plt.subplots()

ax.scatter(x_values, y_values, s=100)

 

# Задание заголовка диаграммы и меток осей.

--пропуск--

В списке x_values содержатся числа, возводимые в квадрат, а в y_values — сами квадраты. При передаче этих списков функции scatter() библиотека Matplotlib считывает по одному значению из каждого списка и наносит их на диаграмму как точку. Таким образом, на диаграмму будут нанесены точки (1, 1), (2, 4), (3, 9), (4, 16) и (5, 25); результат показан на рис. 15.6.

15_06.tif 

Рис. 15.6. Точечная диаграмма с несколькими точками

Автоматическое вычисление данных

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

Вот как выглядит такой цикл для тысячи точек:

scatter_squares.py

import matplotlib.pyplot as plt

 

❶ x_values = range(1, 1001)

y_values = [x**2 for x in x_values]

 

plt.style.use('seaborn')

fig, ax = plt.subplots()

❷ ax.scatter(x_values, y_values, s=10)

 

# Задание заголовка диаграммы и меток осей.

--пропуск--

 

# Задание диапазона для каждой оси.

❸ ax.axis([0, 1100, 0, 1_100_000])

 

plt.show()

Все начинается со списка значений координаты x с числами от 1 до 1000 . Затем генератор списка создает значения y, перебирая значения x (for x in x_values), возводя каждое число в квадрат (x**2) и сохраняя результаты в y_values. Затем оба списка (входной и выходной) передаются функции scatter(). Набор данных велик, поэтому мы задаем меньший размер точек.

До вывода диаграммы метод axis() используется для задания диапазона каждой оси . Метод axis() получает четыре значения: минимум и максимум по осям X и Y. В данном случае по оси X откладывается диапазон от 0 до 1100, а по оси Y — диапазон от 0 до 1 100 000. На рис. 15.7 показан результат.

15_07.tif 

Рис. 15.7. Диаграмма с тысячью точками создается так же легко, как и диаграмма с пятью точками

Настройка меток на осях

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

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

--пропуск--

# Назначение диапазона для каждой оси.

ax.axis([0, 1100, 0, 1_100_000])

ax.ticklabel_format(style='plain')

 

plt.show()

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

Определение пользовательских цветов

Чтобы изменить цвет точек, передайте scatter() аргумент c с именем используемого цвета, заключенным в одинарные кавычки:

ax.scatter(x_values, y_values, c='red', s=10)

Кроме того, можно определять пользовательские цвета в цветовой модели RGB. Чтобы сделать это, передайте аргумент c с кортежем из трех дробных значений (для красной, зеленой и синей составляющих) в диапазоне от 0 до 1. Например, следующая строка создает диаграмму со светло-зелеными точками:

ax.scatter(x_values, y_values, c=(0, 0.8, 0), s=10)

Значения, близкие к 0, дают более темные цвета, а значения, близкие к 1, — более светлые.

Цветовые карты

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

Модуль pyplot содержит набор встроенных цветовых карт. Чтобы воспользоваться одной из них, вы должны указать, как модуль должен присваивать цвет каждой точке набора данных. В следующем примере цвет каждой точки присваивается на основании значения по оси Y:

scatter_squares.py

--пропуск--

plt.style.use('seaborn')

fig, ax = plt.subplots()

ax.scatter(x_values, y_values, c=y_values, cmap=plt.cm.Blues, s=10)

 

# Задание заголовка диаграммы и меток осей.

--пропуск--

Аргумент c аналогичен аргументу color, но используется для связывания последовательности значений с цветовым сопоставлением. Мы передаем в c список значений по оси Y, а затем указываем pyplot, какая цветовая карта должна использоваться, через аргумент cmap. Следующий код окрашивает точки с меньшими значениями y в светло-синий цвет, а точки с бо́льшими значениями y — в темно-синий. Полученная диаграмма изображена на рис. 15.8.

15_08.tif 

Рис. 15.8. Точечная диаграмма, составленная с помощью цветовой карты Blues

ПРИМЕЧАНИЕ

Все цветовые карты, доступные в pyplot, можно просмотреть на сайте https://matplotlib.org/; откройте раздел Tutorials (Руководства), прокрутите содержимое до пункта Colors (Цвета) и щелкните на ссылке Choosing Colormaps in Matplotlib (Выбор цветовых карт в Matplotlib).

Автоматическое сохранение диаграмм

Если вы хотите, чтобы программа автоматически сохраняла диаграмму в файле, то замените вызов plt.show() вызовом plt.savefig():

plt.savefig('squares_plot.png', bbox_inches='tight')

Первый аргумент содержит имя файла, в котором должна сохраняться диаграмма; файл будет расположен в одном каталоге с scatter_squares.py. Второй аргумент удаляет из диаграммы лишние пробельные символы. Если вы хотите оставить эти символы, то данный аргумент можно опустить. Вы также можете вызвать функцию savefig(), передав ей объект Path, и сохранить выходной файл в любой каталог вашей системы.

Упражнения

15.1. Кубы. Число, возведенное в третью степень, называется кубом. Нанесите на диаграмму первые пять кубов, а затем первые 5000 кубов.

15.2. Цветные кубы. Примените цветовую карту к диаграмме с кубами.

Случайное блуждание

В этом разделе мы используем Python для генерирования данных для случайного обхода, а затем с помощью Matplotlib создадим привлекательное представление сгенерированных данных. Случайным блужданием (random walk) называется путь, который не имеет четкого направления, но определяется серией полностью случайных решений. Представьте, что муравей делает каждый новый шаг в случайном направлении; его путь напоминает случайное блуждание.

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

Создание класса RandomWalk

Чтобы создать путь случайного блуждания, мы напишем класс RandomWalk, который принимает случайные решения по выбору направления. Классу нужны три атрибута: переменная для хранения количества точек в пути и два списка для координат x и y каждой точки.

Класс RandomWalk содержит всего два метода: __init__() и fill_walk() для вычисления точек случайного блуждания. Начнем с метода __init__():

random_walk.py

❶ from random import choice

 

class RandomWalk:

    """Класс для генерирования случайных блужданий."""

 

❷     def __init__(self, num_points=5000):

        """Инициализирует атрибуты блуждания."""

        self.num_points = num_points

 

        # Все блуждания начинаются с точки (0, 0).

❸         self.x_values = [0]

        self.y_values = [0]

Чтобы принимать случайные решения, мы сохраним возможные варианты в списке и используем функцию choice() из модуля random для принятия решения . Затем для списка задаем количество точек по умолчанию равным 5000 — достаточно большим, чтобы генерировать интересные закономерности, но достаточно малым, чтобы блуждания генерировались быстро . Затем в строке создаем два списка для хранения значений x и y, после чего каждый путь начинается с точки (0, 0).

Выбор направления

Метод fill_walk(), как показано ниже, заполняет путь точками и определяет направление каждого шага. Добавьте этот метод в файл random_walk.py:

random_walk.py

    def fill_walk(self):

        """Вычисляет все точки блуждания."""

 

        # Шаги генерируются, пока не будет достигнута нужная длина.

❶         while len(self.x_values) < self.num_points:

 

            # Определение направления и длины перемещения.

❷             x_direction = choice([1, -1])

            x_distance = choice([0, 1, 2, 3, 4])

❸             x_step = x_direction * x_distance

 

            y_direction = choice([1, -1])

            y_distance = choice([0, 1, 2, 3, 4])

❹             y_step = y_direction * y_distance

 

            # Отклонение нулевых перемещений.

❺             if x_step == 0 and y_step == 0:

                continue

 

            # Вычисление следующей позиции.

❻             x = self.x_values[-1] + x_step

            y = self.y_values[-1] + y_step

 

            self.x_values.append(x)

            self.y_values.append(y)

Сначала запускается цикл, который выполняется вплоть до заполнения пути правильным количеством точек . Главная часть метода fill_walk() сообщает Python, как следует моделировать четыре случайных решения: двигаться вправо или влево? Как далеко идти в этом направлении? Двигаться ли вверх или вниз? Как далеко идти в этом направлении?

Выражение choice([1, –1]) выбирает значение x_direction; оно возвращает 1 для перемещения вправо или –1 для движения влево . Затем выражение choice([0, 1, 2, 3, 4]) определяет дальность перемещения в этом направлении (x_distance) случайным выбором целого числа от 0 до 4. (Добавление 0 позволяет выполнять шаги по оси Y, а также шаги со смещением по обеим осям.)

В точках и определяется длина каждого шага в направлениях x и y, для чего направление движения умножается на выбранное расстояние. При положительном результате x_step смещает вправо, при отрицательном — влево, при нулевом — вертикально. При положительном результате y_step смещает вверх, при отрицательном — вниз, при нулевом — горизонтально. Если оба значения x_step и y_step равны 0, то блуждание останавливается, но цикл продолжается .

Чтобы получить следующее значение x, мы прибавляем значение x_step к последнему значению, хранящемуся в x_values , и делаем то же самое для значений y. После того как значения будут получены, они присоединяются к x_values и y_values.

Вывод случайного блуждания

Ниже приведен код отображения всех точек блуждания:

rw_visual.py

import matplotlib.pyplot as plt

 

from random_walk import RandomWalk

 

# Создание случайного блуждания.

❶ rw = RandomWalk()

rw.fill_walk()

 

# Нанесение точек на диаграмму.

plt.style.use('classic')

fig, ax = plt.subplots()

❷ ax.scatter(rw.x_values, rw.y_values, s=15)

❸ ax.set_aspect('equal')

plt.show()

Сначала программа импортирует модуль pyplot и класс RandomWalk. Затем создает случайное блуждание и сохраняет его в rw , не забывая вызвать fill_walk(). Далее программа передает функции scatter() координаты x и y блуждания и выбирает подходящий размер точки . По умолчанию Matplotlib масштабирует каждую ось независимо. Но такой подход растягивает большинство блужданий по горизонтали или вертикали. Здесь мы используем метод set_aspect(), чтобы обе оси имели равные расстояния между делениями .

На рис. 15.9 показана диаграмма с 5000 точками. (В изображениях этого раздела область просмотра Matplotlib не показана, но вы увидите ее при запуске программы rw_visual.py.)

15_09.tif 

Рис. 15.9. Случайное блуждание с 5000 точками

Генерирование нескольких случайных блужданий

Все случайные блуждания отличаются друг от друга; интересно понаблюдать за тем, какие узоры генерирует программа. Один из способов использования предыдущего кода — создать несколько блужданий, не запуская многократно программу в цикле while:

rw_visual.py

import matplotlib.pyplot as plt

 

from random_walk import RandomWalk

 

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

while True:

    # Создание случайного блуждания.

    --пропуск--

    plt.show()

 

    keep_running = input("Make another walk? (y/n): ")

    if keep_running == 'n':

        break

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

Форматирование блужданий

В этом подразделе мы настроим диаграмму так, чтобы выделить важные характеристики каждого блуждания и отвести на второй план несущественные элементы. Для этого мы определим характеристики, которые нужно выделить (например, откуда началось блуждание, где оно закончилось и по какому пути следовало). Затем определим характеристики, которым нужно уделять меньше внимания (например, деления шкалы и метки). Результатом должно быть простое визуальное представление, которое четко описывает путь, использованный в каждом случайном блуждании.

Колоризация точек

Мы используем цветовую карту для отображения точек блуждания, а также удаляем черный контур из каждой точки, чтобы цвет точек был лучше виден. Чтобы точки окрашивались в соответствии с их позицией в блуждании, мы передаем в аргументе c список с позицией каждой точки. Так как точки выводятся по порядку, список просто содержит числа от 1 до 4999:

rw_visual.py

--пропуск--

while True:

    # Создание случайного блуждания

    rw = RandomWalk()

    rw.fill_walk()

 

    # Нанесение точек на диаграмму.

    plt.style.use('classic')

    fig, ax = plt.subplots()

❶     point_numbers = range(rw.num_points)

    ax.scatter(rw.x_values, rw.y_values, c=point_numbers, cmap=plt.cm.Blues,

        edgecolors='none', s=15)

    ax.set_aspect('equal')

    plt.show()

    --пропуск--

Функция range() используется для генерирования списка чисел, размер которого равен количеству точек в блуждании . Полученный результат сохраняется в списке point_numbers, который используется для назначения цвета каждой точки в блуждании. Мы передаем point_numbers в аргументе c, используем цветовую карту Blues и затем передаем edgecolors='none', чтобы удалить черный контур вокруг каждой точки. В результате создается диаграмма блуждания с градиентным переходом от светло-синего к темно-синему (рис. 15.10).

15_10.tif 

Рис. 15.10. Случайное блуждание, окрашенное с применением цветовой карты Blues

Форматирование начальной и конечной точек

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

rw_visual.py

--пропуск--

while True:

    --пропуск--

    ax.scatter(rw.x_values, rw.y_values, c=point_numbers, cmap=plt.cm.Blues,

        edgecolors='none', s=15)

    ax.set_aspect('equal')

 

    # Выделение первой и последней точек.

    ax.scatter(0, 0, c='green', edgecolors='none', s=100)

    ax.scatter(rw.x_values[-1], rw.y_values[-1], c='red', edgecolors='none',

        s=100)

 

    plt.show()

    --пропуск--

Чтобы вывести начальную точку, мы рисуем точку (0, 0) зеленым цветом и придаем ей больший размер (s=100) по сравнению с остальными точками. Для выделения конечной точки последняя пара координат x и y выводится с размером 100. Обязательно вставьте этот код непосредственно перед вызовом plt.show(), чтобы начальная и конечная точки выводились поверх всех остальных.

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

Удаление осей

Уберем оси с диаграммы, чтобы они не отвлекали зрителя от общей картины. Для удаления осей используется следующий код:

rw_visual.py

--пропуск--

while True:

    --пропуск--

    ax.scatter(rw.x_values[-1], rw.y_values[-1], c='red', edgecolors='none',

        s=100)

 

    # Удаление осей.

    ax.get_xaxis().set_visible(False)

    ax.get_yaxis().set_visible(False)

 

    plt.show()

    --пропуск--

Мы используем методы ax.get_xaxis() и ax.get_yaxis() для получения доступа к каждой оси, а затем цепочку методов set_visible(), чтобы скрыть оси. В процессе работы над визуализацией данных вы часто будете встречать такую совокупность методов для настройки различных аспектов диаграмм.

Запустите программу rw_visual.py; теперь выводимые диаграммы не имеют осей.

Добавление точек

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

rw_visual.py

--пропуск--

while True:

    # Создание случайного блуждания.

    rw = RandomWalk(50_000)

    rw.fill_walk()

 

    # Вывод точек и отображение диаграммы.

    plt.style.use('classic')

    fig, ax = plt.subplots()

    point_numbers = range(rw.num_points)

    ax.scatter(rw.x_values, rw.y_values, c=point_numbers, cmap=plt.cm.Blues,

        edgecolor='none', s=1)

    --пропуск--

В этом примере создается случайное блуждание из 50 000 точек (что в большей степени соответствует реальным данным), и каждая из них рисуется размером s=1. Как видно из рис. 15.11, изображение получается эфемерным и туманным. Простая точечная диаграмма превратилась в произведение искусства!

15_11.tif 

Рис. 15.11. Случайное блуждание c 50 000 точек

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

Изменение размера диаграммы для заполнения экрана

Визуализация гораздо эффективнее передает закономерности в данных, если адаптирована под размер экрана. Чтобы диаграмма лучше смотрелась на экране, измените размер области просмотра Matplotlib в вызове subplots():

fig, ax = plt.subplots(figsize=(15, 9))

Функция figure() управляет шириной, высотой, разрешением и цветом фона диаграммы. Параметр figsize получает кортеж с размерами окна диаграммы в дюймах.

Matplotlib предполагает, что разрешение экрана составляет 100 пикселов на дюйм; если этот код не дает точного размера, то внесите необходимые изменения в числа. Или, если знаете разрешение экрана в вашей системе, передайте его subplots() в параметре dpi для выбора размера, эффективно использующего доступное пространство:

fig, ax = plt.subplots(figsize=(10, 6), dpi=128)

Так пространство на экране будет использоваться наиболее эффективно.

Упражнения

15.3. Движение молекул. Измените программу rw_visual.py и замените plt.scatter() вызовом plt.plot(). Чтобы смоделировать путь пылинки на поверхности водной капли, передайте значения rw.x_values и rw.y_values и добавьте аргумент linewidth. Используйте 5000 точек вместо 50 000, чтобы не перегружать диаграмму.

15.4. Измененные случайные блуждания. В классе RandomWalk значения x_step и y_step генерируются по единому набору условий. Направление выбирается случайно из списка [1, –1], а расстояние — из списка [0, 1, 2, 3, 4]. Измените значения в этих списках и посмотрите, что произойдет с общей формой диаграммы. Попробуйте применить расширенный список вариантов расстояния (например, от 0 до 8) или удалите –1 из списка направлений по оси X или Y.

15.5. Рефакторинг. Метод fill_walk() получился слишком длинным. Создайте новый метод get_step(), который определяет расстояние и направление для каждого шага, после чего вычисляет этот шаг. В результате метод fill_walk() должен содержать два вызова get_step():

x_step = self.get_step()

y_step = self.get_step()

Рефакторинг сокращает размер fill_walk(), а метод становится более простым и понятным.

Моделирование бросков кубиков с помощью Plotly

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

В этом проекте мы займемся анализом результатов бросков кубиков. При броске одного шестигранного кубика существует равная вероятность выпадения любого числа от 1 до 6. С другой стороны, при броске двух кубиков одни суммы выпадают с большей вероятностью, чем другие. Чтобы определить, какие числа наиболее вероятны, мы сгенерируем набор данных, представляющих брошенные кубики. Затем на базе данных большого количества бросков будет создана диаграмма, по которой можно определить, какие результаты более вероятны.

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

Установка Plotly

Установите Plotly с помощью модуля pip по аналогии с тем, как вы делали это с Matplotlib:

$ python -m pip install --user plotly

$ python -m pip install --user pandas

Для эффективной работы с данными Plotly Express требуется библиотека pandas, поэтому нам нужно установить и ее. Если при установке Matplotlib вы использовали команду python3 или другую, то убедитесь в том, что в данном случае используется та же команда.

Примеры визуализаций, которые могут быть созданы с помощью Plotly, представлены в галерее диаграмм: зайдите на сайт https://plotly.com/python. Каждый пример сопровождается исходным кодом, так что вы сможете увидеть, как была создана каждая из визуализаций.

Создание класса Die

Для моделирования броска одного кубика будет использоваться класс Die:

die.py

from random import randint

 

class Die:

    """Класс, представляющий один кубик."""

 

❶     def __init__(self, num_sides=6):

        """По умолчанию используется шестигранный кубик."""

        self.num_sides = num_sides

 

    def roll(self):

        """Возвращает случайное число от 1 до количества граней."""

❷         return randint(1, self.num_sides)

Метод __init__() получает один необязательный аргумент . Если при создании экземпляра кубика аргумент с количеством сторон не передается, то по умолчанию создается шестигранный кубик. Если же аргумент имеется, то переданное значение используется для определения количества граней. (Кубики принято обозначать по количеству граней: шестигранный кубик — D6, восьмигранный — D8 и т.д.)

Метод roll() использует функцию randint() для получения случайного числа в диапазоне от 1 до количества граней . Функция может вернуть начальное значение (1), конечное значение (num_sides) или любое целое число в этом диапазоне.

Бросок кубика

Прежде чем создавать визуализацию на основе этого класса, бросим кубик D6, выведем результаты и убедимся в том, что они имеют смысл:

die_visual.py

from die import Die

 

# Создание кубика D6.

❶ die = Die()

 

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

results = []

❷ for roll_num in range(100):

    result = die.roll()

    results.append(result)

 

print(results)

Сначала создается экземпляр Die с шестью гранями по умолчанию . Затем моделируются 100 бросков кубика , а результат каждого броска сохраняется в списке results. Выборка выглядит примерно так:

[4, 6, 5, 6, 1, 5, 6, 3, 5, 3, 5, 3, 2, 2, 1, 3, 1, 5, 3, 6, 3, 6, 5, 4,

1, 1, 4, 2, 3, 6, 4, 2, 6, 4, 1, 3, 2, 5, 6, 3, 6, 2, 1, 1, 3, 4, 1, 4,

3, 5, 1, 4, 5, 5, 2, 3, 3, 1, 2, 3, 5, 6, 2, 5, 6, 1, 3, 2, 1, 1, 1, 6,

5, 5, 2, 2, 6, 4, 1, 4, 5, 1, 1, 1, 4, 5, 3, 3, 1, 3, 5, 4, 5, 6, 5, 4,

1, 5, 1, 2]

Беглое знакомство с результатами показывает, что класс Die работает. В результатах встречаются граничные значения 1 и 6, то есть модель возвращает наименьшее и наибольшее возможные значения; значения 0 и 7 не встречаются, а значит, все результаты лежат в диапазоне допустимых значений. Кроме того, в выборке встречаются все числа от 1 до 6, то есть представлены все возможные результаты.

Анализ результатов

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

die_visual.py

--пропуск--

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

results = []

❶ for roll_num in range(1000):

    result = die.roll()

    results.append(result)

 

# Анализ результатов.

frequencies = []

❷ poss_results = range(1, die.num_sides+1)

for value in poss_results:

❸     frequency = results.count(value)

❹     frequencies.append(frequency)

 

print(frequencies)

Поскольку мы больше не выводим результаты, количество моделируемых бросков можно увеличить до 1000 . Чтобы проанализировать броски, создадим пустой список frequencies, в котором хранится количество выпадений каждого значения. Программа перебирает возможные значения (от 1 до количества сторон кубика) , подсчитывает количество вхождений каждого числа в результатах , после чего присоединяет полученное значение к списку frequencies . Содержимое списка выводится перед созданием визуализации:

[155, 167, 168, 170, 159, 181]

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

Создание гистограммы

Теперь, обладая нужными данными, мы можем сгенерировать визуализацию буквально за пару строк кода с помощью Plotly Express:

die_visual.py

import plotly.express as px

 

from die import Die

--пропуск--

 

for value in poss_results:

    frequency = results.count(value)

    frequencies.append(frequency)

 

# Визуализация результатов.

fig = px.bar(x=poss_results, y=frequencies)

fig.show()

Сначала мы импортируем модуль plotly.express под псевдонимом px. Затем с помощью функции px.bar() создаем гистограмму. Для создания простейшей гистограммы нам нужно передать этой функции лишь значения координат по осям X и Y. В данном случае значения x — это вероятные результаты броска одного кубика, а значения y — частота выпадения каждого возможного значения.

В последней строке вызывается функция fig.show(), с помощью которой Plotly визуализирует полученную гистограмму в HTML-файл и открывает его на новой вкладке браузера. Результат показан на рис. 15.12.

Получился очень простой график, и это, разумеется, черновой вариант. Но именно так и следует использовать Plotly Express: вы пишете пару строк кода, изучаете диаграмму и проверяете, что она передает данные корректно. Если вам нравится результат, то вы можете настроить диаграмму, например, изменив ее внешний вид или метки. А если хотите опробовать другие виды диаграмм, то можете заменить функцию px.bar() на px.scatter() или px.line(). Полный список доступных вариантов диаграмм опубликован на сайте https://plotly.com/python/plotly-express.

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

15_12.tif 

Рис. 15.12. Начальная гистограмма, созданная с помощью Plotly Express

Настройка диаграммы

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

Один из вариантов настройки диаграммы с помощью Plotly заключается в использовании опциональных параметров при вызове функции, генерирующей график, в данном случае px.bar(). Ниже показано, как добавить общий заголовок и метку к каждой оси:

die_visual.py

--пропуск--

# Визуализация результатов.

❶ title = "Results of Rolling One D6 1,000 Times"

❷ labels = {'x': 'Result', 'y': 'Frequency of Result'}

fig = px.bar(x=poss_results, y=frequencies, title=title, labels=labels)

fig.show()

Сначала мы добавляем заголовок с помощью переменной title . Для добавления меток к осям используем словарь . Ключи в нем ссылаются на оси, метки к которым мы добавляем, а значения содержат сам текст меток. В данном примере мы присваиваем оси X метку Result, а оси Y — метку Frequency of Result. Вызов функции px.bar() теперь содержит необязательные аргументы title и labels.

Теперь при создании диаграммы добавляются указанные заголовок и метки каждой оси (рис. 15.13).

15_13.tif 

Рис. 15.13. Простая диаграмма, созданная с помощью Plotly

Бросок двух кубиков

При броске двух кубиков вы получаете большие значения с другим распределением результатов. Изменим наш код и создадим два кубика D6, моделирующих бросок пары кубиков. При броске каждой пары программа складывает два числа (по одному с каждого кубика) и сохраняет сумму в results. Сохраните копию файла die_visual.py под именем dice_visual.py и внесите следующие изменения:

dice_visual.py

import plotly.express as px

 

from die import Die

 

# Создание двух кубиков D6.

die_1 = Die()

die_2 = Die()

 

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

results = []

for roll_num in range(1000):

❶     result = die_1.roll() + die_2.roll()

    results.append(result)

 

# Анализ результатов.

frequencies = []

❷ max_result = die_1.num_sides + die_2.num_sides

❸ poss_results = range(2, max_result+1)

for value in poss_results:

    frequency = results.count(value)

    frequencies.append(frequency)

 

# Визуализация результатов.

title = "Results of Rolling Two D6 Dice 1,000 Times"

labels = {'x': 'Result', 'y': 'Frequency of Result'}

fig = px.bar(x=poss_results, y=frequencies, title=title, labels=labels)

fig.show()

Создав два экземпляра Die, мы бросаем кубики и вычисляем сумму для каждого броска . Наименьший возможный результат (2) равен сумме наименьших результатов на обоих кубиках. Наибольший возможный результат (12) вычисляется путем суммирования наибольших результатов на обоих кубиках; мы сохраняем его в max_result . Переменная max_result упрощает код для генерации poss_results . Кроме того, можно было использовать диапазон range(2, 13), но он работал бы только для двух кубиков D6. При моделировании реальных ситуаций лучше писать код, который легко адаптируется для разных ситуаций. В частности, этот код позволяет смоделировать бросок пары кубиков с любым количеством граней.

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

15_14.tif 

Рис. 15.14. Смоделированные результаты 1000 бросков двух шестигранных кубиков

На диаграмме показаны примерные результаты, которые могут быть получены для пары кубиков D6. Как видите, реже всего выпадают результаты 2 и 12, а чаще всего 7, поскольку эта комбинация может быть выброшена шестью способами, а именно: 1 + 6, 2 + 5, 3 + 4, 4 + 3, 5 + 2 и 6 + 1.

Дальнейшая настройка диаграммы

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

В Plotly доступен метод update_layout(), используемый для внесения самых разных изменений в диаграмму после ее создания. Рассмотрим, как добавить метки ко всем столбцам:

dice_visual.py

--пропуск--

fig = px.bar(x=poss_results, y=frequencies, title=title, labels=labels)

 

# Дальнейшая настройка диаграммы.

fig.update_layout(xaxis_dtick=1)

 

fig.show()

Метод update_layout() влияет на объект fig, представляющий собой сам график. В нашем примере мы используем аргумент xaxis_dtick, отвечающий за расстояние между метками на оси X. Мы установили его равным 1, чтобы каждый столбец сопровождался меткой. Снова запустив программу dice_visual.py, вы увидите метки у всех столбцов.

Броски кубиков с разным количеством граней

Создадим кубики с шестью и десятью гранями и посмотрим, что произойдет, если бросить их 50 000 раз:

dice_visual_d6d10.py

import plotly.express as px

 

from die import Die

 

# Создание кубиков D6 и D10.

die_1 = Die()

❶ die_2 = Die(10)

 

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

results = []

for roll_num in range(50_000):

    result = die_1.roll() + die_2.roll()

    results.append(result)

 

# Анализ результатов.

--пропуск--

 

# Визуализация результатов.

❷ title = "Results of Rolling a D6 and a D10 50,000 Times"

labels = {'x': 'Result', 'y': 'Frequency of Result'}

--пропуск--

Чтобы добавить модель кубика D10, мы передаем аргумент 10 при создании второго экземпляра Die и изменяем первый цикл для моделирования 50 000 бросков вместо 1000. Затем соответственно изменяем заголовок .

На рис. 15.15 показана полученная диаграмма. Вместо одного наиболее вероятного результата их стало пять. Это объясняется тем, что наименьшее (1 + 1) и наибольшее (6 + 10) значения по-прежнему могут быть получены только одним способом, но кубик D6 ограничивает количество способов генерирования средних чисел: суммы 7, 8, 9, 10 и 11 можно выбросить шестью способами. Следовательно, именно эти результаты являются наиболее частыми, и все эти числа выпадают с равной вероятностью.

15_15.tif 

Рис. 15.15. Результаты 50 000 бросков шести- и десятигранного кубиков

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

Сохранение диаграммы в файл

Подготовленную диаграмму вы всегда можете сохранить в HTML-файл и просмотреть в браузере. Но можно сделать это и с помощью кода. Чтобы сохранить диаграмму в HTML-файл, замените вызов функции fig.show() на вызов fig.write_html():

fig.write_html('dice_visual_d6d10.html')

Метод write_html() принимает лишь один аргумент: имя сохраняемого файла. Если вы вместо полного пути укажете только имя файла, то он будет сохранен в том же каталоге, что и файл .py. Вы также можете передать функции write_html() объект Path и записать выходной файл в любой каталог вашей системы.

Упражнения

15.6. Два кубика D8. Создайте модель, которая показывает, что происходит при 1000-кратном бросании двух восьмигранных кубиков. Попробуйте заранее (перед моделированием) представить, как будет выглядеть визуализация; проверьте правильность своих интуитивных представлений. Постепенно увеличивайте количество бросков, пока не начнете замечать ограничения, связанные с ресурсами вашей системы.

15.7. Три кубика. При броске трех кубиков D6 наименьший возможный результат равен 3, а наибольший — 18. Создайте визуализацию, которая показывает, что происходит при броске трех кубиков D6.

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

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

15.10. Эксперименты с библиотеками. Попробуйте использовать Matplotlib для создания визуализации бросков кубиков, а Plotly — для создания визуализации случайного блуждания. (Для выполнения этого упражнения вам придется обратиться к документациям обеих библиотек.)

Резюме

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

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

В главе 16 вы скачаете данные из сетевого источника и продолжите использовать Matplotlib и Plotly для анализа данных.

Назад: 14. Игровой счет
Дальше: 16. Загрузка данных