2019.5.13
不知不觉,已经进入第12周了,Python数据分析的学习现今也已经进入了中后期,在继上周进行了Numpy的康威生命游戏的编写之后;紧接着进行的学习就是利用Python的Matplotlib模块来练习绘图。这次由于涉及到图像,所以引用了一些丁烨老师的pdf的截图。主要是进行用Matplotlib模块来进行MATLAB能做的数据分析绘图工作,并结合Numpy和Matplotlib来做一个扫雷小游戏。
一. Matplotlib模块
1. 概述
Matplotlib是一个Python 2D绘图库,可以生成各种硬拷贝格式和跨平台的交互式环境的出版物质量数据。只需几行代码即可生成绘图,直方图,功率谱,条形图,错误图,散点图等。 对于简单的绘图,
pyplot
模块提供了类似MATLAB的接口,特别是与IPython结合使用时。而我们进行数据分析就是在使用这个模块。 对于高级用户,您可以通过面向对象的界面或MATLAB用户熟悉的一组函数完全控制线型,字体属性,轴属性等。
2. 基本用法
-
基本概念
(图片源自丁烨老师的讲义pdf,侵权删)
-
数据准备
- 输入数据应当是 np.array 或 np.ma.masked_array 格式 。
- 其他类似数组的格式,例如标准库的 list、Pandas 的 pandas.DataFrame、NumPy 的 np.matrix 等也可能能用。
- 在绘制图表前,最好将输入格式转换为 np.array。
- 函数讲解
-
matplotlib.pyplot.plot()
plot()用于在figure上画出曲线图,其中比较值得注意的参数有 x, y, label, 样式等。x, y决定了曲线的形状,即哪些点(可传入特定点,或者x ** 2等函数方式)。label决定了曲线的名字。样式决定了图像中的点的颜色,样式,线的样子。
from matplotlib import pyplot as plt import numpy as np x = np.linspace(0, 2, 100) # 利用numpy创建一个矩阵,方便用于数据输入 plt.plot(x, x, label='linear') # 画出直线 y=x plt.plot(x, x**2, label='quadratic') # 画出曲线 y = x² plt.plot(x, x**3, label='cubic') # 画出曲线 y = x^3 plt.xlabel('x label') # 设置x轴名称 plt.ylabel('y label') # 设置y轴名称 plt.title("Simple Plot") # 设置图片的标题 plt.legend() # 画出一个图例,用于标出什么颜色和样式的线代表什么 plt.show()
-
线条的样式
from matplotlib import pyplot as plt plt.plot([1, 2, 3, 4], [1, 4, 9, 16], 'ro') # 根据表格可知,画出的图形是红色的圈 plt.axis([0, 6, 0, 20]) plt.show()
-
其他几种绘图的函数
- matplotlib.pyplot.hist() 用于绘制直方图
- matplotlib.pyplot.bar() 用于绘制柱状图
- matplotlib.pyplot.scatter() 用于绘制散点图
from matplotlib import pyplot as plt import numpy as np mu, sigma = 100, 15 x = mu + sigma * np.random.randn(10000) # the histogram of the data n, bins, patches = plt.hist( x, 50, density=1, facecolor='g', alpha=0.75 ) plt.xlabel('Smarts') plt.ylabel('Probability') plt.title('Histogram of IQ') plt.text(60, .025, r'$\mu=100,\ \sigma=15$') plt.axis([40, 160, 0, 0.03]) plt.grid(True) plt.show()
from matplotlib import pyplot as plt names = ['group_a', 'group_b', 'group_c'] # 用一个列表存储三个图的名字 values = [1, 10, 100] # 用列表存储用到的点 plt.figure(1, figsize=(9, 3)) # 创建一副图 plt.subplot(131) # 创建子图 plt.bar(names, values) # 绘制柱状图 plt.subplot(132) plt.scatter(names, values) # 绘制散点图 plt.subplot(133) plt.plot(names, values) # 绘制曲线图 plt.suptitle('Categorical Plotting') plt.show()
-
绘制子图
import numpy as np from matplotlib import pyplot as plt def f(t): return np.exp(-t) * np.cos(2*np.pi*t) t1 = np.arange(0.0, 5.0, 0.1) t2 = np.arange(0.0, 5.0, 0.02) plt.figure(1) plt.subplot(211) plt.plot(t1, f(t1), 'bo', t2, f(t2), 'k') plt.subplot(212) plt.plot(t2, np.cos(2*np.pi*t2), 'r--') plt.show()
二.利用Numpy和matplotlib.pyplot来实现扫雷游戏
- 效果图
- 源代码
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import RegularPolygon
from scipy.signal import convolve2d
class MineSweeper(object):
"""docstring for MineSweeper"""
covered_color = '#DDDDDD'
uncovered_color = '#AAAAAA'
edge_color = '#888888'
count_colors = ['none', 'blue', 'green', 'red', 'darkred', 'darkgreen', 'black', 'black']
flag_vertices = np.array([[0.25, 0.2], [0.25, 0.8], [0.75, 0.65], [0.25, 0.5]])
@classmethod
def beginner(cls):
return cls(8, 8, 10)
@classmethod
def intermediate(cls):
return cls(16, 16, 40)
@classmethod
def expert(cls):
return cls(30, 16, 99)
def __init__(self, width, height, nmines):
self.width, self.height, self.nmines = width, height, nmines
# Create the figure and axes
self.fig = plt.figure(figsize=((width + 2) / 3., (height + 2) / 3.))
self.ax = self.fig.add_axes(
(0.05, 0.05, 0.9, 0.9),
aspect='equal',
frameon=False,
xlim=(-0.05, width + 0.05),
ylim=(-0.05, height + 0.05))
for axis in (self.ax.xaxis, self.ax.yaxis):
axis.set_major_formatter(plt.NullFormatter())
axis.set_major_locator(plt.NullLocator())
# Create the grid of squares
self.squares = np.array([[RegularPolygon(
(i + 0.5, j + 0.5),
numVertices=4,
radius=0.5 * np.sqrt(2),
orientation=np.pi / 4,
ec=self.edge_color,
fc=self.covered_color
) for j in range(height)] for i in range(width)])
[self.ax.add_patch(sq) for sq in self.squares.flat]
# Define internal state variables
self.mines = None
self.counts = None
self.clicked = np.zeros((self.width, self.height), dtype=bool)
self.flags = np.zeros((self.width, self.height), dtype=object)
self.game_over = False
# Create event hook for mouse clicks
self.fig.canvas.mpl_connect('button_press_event', self._button_press)
def _draw_mine(self, i, j):
self.ax.add_patch(plt.Circle((i + 0.5, j + 0.5), radius=0.25, ec='black', fc='black'))
def draw_red_x(self, i, j):
self.ax.text(i + 0.5, j + 0.5, 'X', color='r', fontsize=20, ha='center', va='center')
def _toggle_mine_flag(self, i, j):
if self.clicked[i, j]:
pass
elif self.flags[i, j]:
self.ax.patches.remove(self.flags[i, j])
self.flags[i, j] = None
else:
self.flags[i, j] = plt.Polygon(self.flag_vertices + [i, j], fc='red', ec='black', lw=2)
self.ax.add_patch(self.flags[i, j])
def _reveal_unmarked_mines(self):
for (i, j) in zip(*np.where(self.mines & ~self.flags.astype(bool))):
self._draw_mine(i, j)
def _cross_out_wrong_flags(self):
for (i, j) in zip(*np.where(self.mines & ~self.flags.astype(bool))):
self.draw_red_x(i, j)
def _mark_remaining_mines(self):
for (i, j) in zip(*np.where(self.mines & ~self.flags.astype(bool))):
self._toggle_mine_flag(i, j)
def _setup_mines(self, i, j):
# Randomly place mines on a grid, but not on space (i, j)
idx = np.concatenate([
np.arange(i * self.height + j),
np.arange(i * self.height + j + 1, self.width * self.height)
])
np.random.shuffle(idx)
self.mines = np.zeros((self.width, self.height), dtype=bool)
self.mines.flat[idx[:self.nmines]] = 1
# Count the number of mines bordering each square
self.counts = convolve2d(self.mines.astype(complex), np.ones((3, 3)), mode='same').real.astype(int)
def isclicked(self, i, j):
# to deal with the situation that the square is clikced
# If the clicked square's number equals to the numbers of flags surrounded,
# open all of squares surrounded it expect the flaged one
count = 0
for ii in range(max(0, j - 1), min(self.width, i + 2)):
for jj in range(max(0, j - 1), min(self.height, j + 2)):
if self.flags[ii, jj]:
count = count + 1
if count == self.counts[i, j]:
for ii in range(max(0, j - 1), min(self.width, i + 2)):
for jj in range(max(0, j - 1), min(self.height, j + 2)):
if (self.flags[ii, jj] == 0) and (self.clicked[ii, jj] == False):
# why it goes wrong when I write like xxx is False?
self.clicked[ii, jj] = True
if self.mines[ii, jj]:
self.squares[ii, jj].set_facecolor(self.uncovered_color)
self._draw_mine(ii, jj)
self.draw_red_x(ii, jj)
elif self.counts[ii, jj] == 0:
self.squares[ii, jj].set_facecolor(self.uncovered_color)
else:
self.squares[ii, jj].set_facecolor(self.uncovered_color)
self.ax.text(
ii + 0.5, jj + 0.5,
str(self.counts[ii, jj]),
color=self.count_colors[self.counts[ii, jj]],
ha='center', va='center', fontsize=18,
fontweight='bold'
)
return
def _click_square(self, i, j):
# If this is the first click, then set up the mines
if self.mines is None:
self._setup_mines(i, j)
# If there is a flag or square is already clicked, do nothing
if self.flags[i, j]:
return
if self.clicked[i, j]:
self.isclicked(i, j)
return
# Mark this mines is clicked
self.clicked[i, j] = True
# Hit a mine: game over
if self.mines[i, j]:
self.game_over = True
self._reveal_unmarked_mines()
self.draw_red_x(i, j)
self._cross_out_wrong_flags()
# Square with no surrounding mines: Clear out all adjacent squares
elif self.counts[i, j] == 0:
self.squares[i, j].set_facecolor(self.uncovered_color)
for ii in range(max(0, j - 1), min(self.width, i + 2)):
for jj in range(max(0, j - 1), min(self.height, j + 2)):
self._click_square(ii, jj)
# Hit an empty square: reveal the number
else:
self.squares[i, j].set_facecolor(self.uncovered_color)
self.ax.text(
i + 0.5, j + 0.5,
str(self.counts[i, j]),
color=self.count_colors[self.counts[i, j]],
ha='center', va='center', fontsize=18,
fontweight='bold'
)
# If all remaining squares are mines, mark them and end game
if self.mines.sum() == (~self.clicked).sum():
self.game_over = True
self._mark_remaining_mines()
def _button_press(self, event):
if self.game_over or (event.xdata is None) or (event.ydata is None):
return
i, j = map(int, (event.xdata, event.ydata))
if i < 0 or j < 0 or i >= self.width or j >= self.height:
return
# Left mouse button: reveal square
if event.button == 1:
self._click_square(i, j)
# Right mouse button: mark or unmark flag
elif (event.button == 3) and (not self.clicked[i, j]):
self._toggle_mine_flag(i, j)
self.fig.canvas.draw()
if __name__ == '__main__':
ms = MineSweeper.beginner()
plt.show()