Matplotlib实现二维随机游走

一个经典小问题

随机漫步问题是数学上的经典问题,有着广泛的应用,特别是对问题进行抽象时。本文假设一个醉汉,每一次可以向四个方向随机的行走一步,最终画出这个醉汉走了N步的脚印记录。

思路分析

首先要确定醉汉的每一个随机步伐,结合Python实现质数求和的思路,随机步伐可以通过for循环每一走一步计算一次,但是计算量较大。或者利用随机向量,一次生成所有的随机数。计算了随机步伐,还需要求出每走一次的对应坐标,可以利用循环计算,熟悉numpy的话,可以直接用np.cumsum计算累积值,大大提高计算效率。
有了基本的数据,剩下的就是利用Matplotlib作图展示了,先展示一幅图,然后考虑展示九幅图,并且最终把每一幅图的内容合成到一张大图上。作图时需要考虑图像的查看效果,比如增加辅助线、起始点等。

探索过程

初步实现

首先需要通过Numpy实现随机步伐,通过步伐计算出走了N步的坐标,这里以10步为例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
#生成随机漫步的步长
x_arr = np.random.randint(-1,2,10)
y_arr = np.random.randint(-1,2,10)
step = np.column_stack((x_arr,y_arr))
step
coordinate = np.empty([10,2],dtype = int)
coordinate[0] = [0,0]
for i in range (0,9):
coordinate[i+1] = coordinate[i] + step[i]
coordinate

计算出结果后,针对coordinate作图

1
2
coordinate = coordinate.T
plt.plot(coordinate[0],coordinate[1])

上面计算坐标时的方法复杂了,可以利用np.cumsum一步实现,代码如下。

封装自定义函数

将10步变为n步,然后封装为自定义函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 输出随机游走的坐标函数,方法一:
def random_coordinate(z):
x_arr = np.random.randint(-1,2,z)
y_arr = np.random.randint(-1,2,z)
steps = np.vstack((x_arr,y_arr))
steps = steps.T
random_coordinate = np.empty([z+1,2],dtype=int)
random_coordinate[0] = [0,0]
for i in range(0,z):
random_coordinate[i+1] = random_coordinate[i] + steps[i]
random_coordinate = random_coordinate.T
return random_coordinate
# 优化后的代码如下:
def random_coordinate(x):
step = np.random.randint(-1,2,(2,x))
random_position = np.hstack(([[0],[0]],np.cumsum(step,axis = 1)))
return random_position
random_coordinate(100)
# 定义作图函数
def random_walk_test(z):
x = random_coordinate(z)
ax = plt.subplot()
out = ax.plot(x[0],x[1])
return out
random_walk_test(100)

上面的函数对比可以发现代码简洁了不少,对基础包越熟悉越有可能写出简洁的代码。

功能增强

优化作图细节

增加了基于零点的辅助线,两条虚线坐标轴,起始点和终点的单独标注。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
z = 100
x_arr = np.random.randint(-1,2,z)
y_arr = np.random.randint(-1,2,z)
steps = np.vstack((x_arr,y_arr))
steps = steps.T
random_coordinate = np.empty([z+1,2],dtype=int)
random_coordinate[0] = [0,0]
for i in range(0,z):
random_coordinate[i+1] = random_coordinate[i] + steps[i]
random_coordinate = random_coordinate.T
plt.plot(random_coordinate[0],random_coordinate[1]);
plt.plot([0],[0],marker = "o",color = 'green')
plt.plot(random_coordinate[0,z],random_coordinate[1,z],marker = 'o',color = 'red')
plt.plot([0,0],[random_coordinate[1].min()-1,random_coordinate[1].max()+1],color = 'black',lw = 0.5,ls = '--')
plt.plot([random_coordinate[0].min()-1,random_coordinate[0].max()+1],np.zeros_like([random_coordinate[0].min()-1,random_coordinate[0].max()+1]),color = 'black',lw = 0.5,ls = '--')
plt.xlim(random_coordinate[0].min()-1,random_coordinate[0].max()+1)
plt.ylim(random_coordinate[1].min()-1,random_coordinate[1].max()+1)

把优化的代码封装为自定义函数

1
2
3
4
5
6
7
8
9
10
11
12
13
def random_walk_final(z):
x = random_coordinate(z)
ax = plt.subplot()
out = ax.plot(x[0],x[1])
out = ax.plot([0],[0],marker = "o",color = 'green')
out = ax.plot(x[0,z],x[1,z],marker = 'o',color = 'red')
out = ax.plot([0,0],[x[1].min()-1,x[1].max()+1],color = 'black',lw = 0.5,ls = '--')
out = ax.plot([x[0].min()-1,x[0].max()+1],np.zeros_like([x[0].min()-1,x[0].max()+1]),color = 'black',lw = 0.5,ls = '--')
out = plt.xlim(x[0].min()-1,x[0].max()+1)
out = plt.ylim(x[1].min()-1,x[1].max()+1)
out = ax.get_xaxis().set_visible(False)
out = ax.get_yaxis().set_visible(False)
return out

九宫格函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def rand_walk_9_test2(y):
fig1,ax = plt.subplots(3,3,figsize=(12,6))
flag = 0
for i in range(0,3):
for j in range(0,3):
x = random_coordinate(y)
flag += 1
out = ax[i][j].get_xaxis().set_visible(False)
out = ax[i][j].get_yaxis().set_visible(False)
out = ax[i][j].set_title('Random Walk %d' %flag )
out = ax[i][j].plot(x[0],x[1])
out = ax[i][j].plot([0],[0],marker = "o",color = 'green')
out = ax[i][j].plot(x[0,y],x[1,y],marker = 'o',color = 'red')
out = ax[i][j].plot([0,0],[x[1].min()-1,x[1].max()+1],color = 'black',lw = 0.5,ls = '--')
out = ax[i][j].plot([x[0].min()-1,x[0].max()+1],np.zeros_like([x[0].min()-1,x[0].max()+1]),color = 'black',lw = 0.5,ls = '--')
out = plt.xlim(x[0].min()-1,x[0].max()+1)
out = plt.ylim(x[1].min()-1,x[1].max()+1)
return out
rand_walk_9_test2(100)

增加“九合一”内容

上面的代码已经很完善了,但是如何把几幅图的内容合到一起呢?因为Matplotlib是面向对象的方式进行作图,所以在循环中不仅仅输出每一次的图像,把内容同时存储到右侧的大图中,每一个循环做一次,就能把内容汇总起来了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def random_walk_final(times):
fig,ax = plt.subplots(3,3,figsize=(10,10))
ax0 = fig.add_axes((1.05,0.3,0.5,0.5))
flag = 0
for i in range (0,3):
for j in range(0,3):
position = random_coordinate(times)
out = ax[i][j].set_title('random walk %d' %flag)
out = ax[i][j].get_xaxis().set_visible(False)
out = ax[i][j].get_yaxis().set_visible(False)
out = ax[i][j].plot(position[0],position[1])
out = ax[i][j].plot([0],[0],marker='o',color='green')
out = ax[i][j].plot(position[0][times],position[1][times],marker='o',color='red')
out = ax[i][j].plot([0,0],[position[1].min()-1,position[1].max()+1],ls = '--',lw = 0.5,color = 'black')
out = ax[i][j].plot([position[0].min()-1,position[0].max()+1],[0,0],ls = '--',lw = 0.5,color = 'black')
flag += 1
out = ax0.plot(position[0],position[1])
out = ax0.get_xaxis().set_visible(False)
out = ax0.get_yaxis().set_visible(False)
out = ax0.plot([0],[0],marker = 'o',color = 'green')
out = ax0.plot(position[0,times],position[1,times], marker = 'o',color = 'red')
return out
random_walk_final(100)

坐标轴固定

(16年12月6日添加)如果坐标轴在中间会是什么情况呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def random_walk_change_spine(times):
fig,ax = plt.subplots(3,3,figsize=(10,10))
ax0 = fig.add_axes((1.05,0.3,0.5,0.5))
flag = 0
for i in range (0,3):
for j in range(0,3):
position = random_step(times)
out = ax[i][j].set_title('random walk %d' %flag)
out = ax[i][j].plot(position[0],position[1])
out = ax[i][j].plot([0],[0],marker='o',color='green')
out = ax[i][j].plot(position[0][times],position[1][times],marker='o',color='red')
flag += 1
out = ax[i][j].get_xaxis().set_visible(False)
out = ax[i][j].get_yaxis().set_visible(False)
out = ax[i][j].spines['top'].set_color('none')
out = ax[i][j].spines['right'].set_color('none')
out = ax[i][j].xaxis.set_ticks_position('bottom') # 理解这个是做什么的
out = ax[i][j].yaxis.set_ticks_position('left') # 理解这个是做什么的
out = ax[i][j].spines['left'].set_position(('data',0)) # 理解这个是做什么的
out = ax[i][j].spines['bottom'].set_position(('data',0)) # 理解这个是做什么的
out = ax0.plot(position[0],position[1])
out = ax0.get_xaxis().set_visible(False)
out = ax0.get_yaxis().set_visible(False)
out = ax0.plot([0],[0],marker = 'o',color = 'green')
out = ax0.plot(position[0,times],position[1,times], marker = 'o',color = 'red')
out = ax0.spines['top'].set_color('none')
out = ax0.spines['right'].set_color('none')
out = ax0.xaxis.set_ticks_position('bottom') # 理解这个是做什么的
out = ax0.yaxis.set_ticks_position('left') # 理解这个是做什么的
out = ax0.spines['left'].set_position(('data',0)) # 理解这个是做什么的
out = ax0.spines['bottom'].set_position(('data',0)) # 理解这个是做什么的
return out

醉汉的脚步还真是“随机”啊~

彩蛋

到这里可以结束,但是并没有结束。还做了其他的尝试,但是还没有得到结果:

  • 在九宫格自定义函数中调用已经完成的单独的画出随机漫步图像的函数,失败了,得到了其他的图像,如果有成功的小伙伴欢迎留言沟通。
  • 把九个格子的内容整合到一张图看一下效果,还没有进行尝试,期待能看出什么不一样的地方。
  • 关于代码中用到的函数,涉及到比较特殊的会在其他文章中讲解。

以上的代码在我的github均有源码可以下载:

声明: 本文转载需标明出处,禁止用于商业目的。

ChangeLog

161205 新建
161206 新增优化算法,和九宫格合一的内容