Изучаем Python — страница 43 из 61


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

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

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


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

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

random_walk.py

(1) from random import choice


class RandomWalk():

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

. .

(2) . .def __init__(self, num_points=5000):

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

. . . .self.num_points = num_points

. . . .

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

(3) . . . .self.x_values = [0]

. . . .self.y_values = [0]

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

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


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

random_walk.py

def fill_walk(self):

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

. .

. .# Шаги генерируются до достижения нужной длины.

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

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

(2) . . . . . .x_direction = choice([1, -1])

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

(3) . . . . . .x_step = x_direction * x_distance

. . . . . .y_direction = choice([1, -1])

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

(4) . . . . . .y_step = y_direction * y_distance

. . . .

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

(5) . . . . . .if x_step == 0 and y_step == 0:

. . . . . . . .continue

. . . .

. . . . . .# Вычисление следующих значений x и y.

? . . . . . .next_x = self.x_values[-1] + x_step

. . . . . .next_y = self.y_values[-1] + y_step

. . . .

. . . . . .self.x_values.append(next_x)

. . . . . .self.y_values.append(next_y)

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

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

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

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

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


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

rw_visual.py

import matplotlib.pyplot as plt


from random_walk import RandomWalk


# Построение случайного блуждания и нанесение точек на диаграмму.

(1) rw = RandomWalk()

rw.fill_walk()

(2)plt.scatter(rw.x_values, rw.y_values, s=15)

plt.show()

Сначала программа импортирует pyplot и RandomWalk. Затем она создает случайное блуждание и сохраняет его в rw (1) , не забывая вызвать fill_walk(). В точке (2) программа передает scatter() координаты x и y блуждания и выбирает подходящий размер точки.

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

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

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


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

rw_visual.py

import matplotlib.pyplot as plt


from random_walk import RandomWalk


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

while True:

# Построение случайного блуждания и нанесение точек на диаграмму.

rw = RandomWalk()

rw.fill_walk()

plt.scatter(rw.x_values, rw.y_values, s=15)

plt.show()


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

. .if keep_running == 'n':

. . . .break

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

Примечание

Если у вас версия Python 2.7, используйте raw_input() вместо input() в точке (1) .

Оформление случайного блуждания


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

Назначение цветов


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

rw_visual.py

...

while True:

# Построение случайного блуждания и нанесение точек на диаграмму.

rw = RandomWalk()

rw.fill_walk()


(1) . .point_numbers = list(range(rw.num_points))

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

. . . .edgecolor='none', s=15)

plt.show()


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

...

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

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

Начальные и конечные точки


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

rw_visual.py

...

while True:

...

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

edgecolor='none', s=15)


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

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

. .plt.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:

...

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

s=100)


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

(1) . .plt.axes().get_xaxis().set_visible(False)

. .plt.axes().get_yaxis().set_visible(False)

. . . .

plt.show()

...

Функция plt.axes() (1) переводит флаг видимости каждой оси в состояние False. При работе с визуализацией подобные цепочки вызовов встречаются очень часто.

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

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


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

rw_visual.py

...

while True:

# Построение случайного блуждания и нанесение точек на диаграмму.

. .rw = RandomWalk(50000)

rw.fill_walk()

. .

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

point_numbers = list(range(rw.num_points))

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

. . . .edgecolor='none', s=1)

...

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

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

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

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


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

rw_visual.py

...

while True:

# Make a random walk, and plot the points.

rw = RandomWalk()

rw.fill_walk()

. .

. .# Назначение размера области просмотра.

. .plt.figure(figsize=(10, 6))

...

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

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

plt.figure(dpi=128, figsize=(10, 6))

Упражнения

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 = get_step()

y_step = get_step()

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

Моделирование бросков кубиков в Pygal