数据可视化是描述性统计不可或缺的亮点,在matplotlib、JavaScript(D3.js)、OpenGL的基础上百花齐放,百家争鸣
以最小的复杂度展示足够多的信息
美国教育家、设计师Robin Williams《The Non-Designer's Design Book(写给大家看的设计书)》
随着HTML5、SVG/Canvas普及,尤其是Mike Bostock于2010开源D3.js,python数据可视化受到冲击,Facebook于2013发布react.js后,Python数据可视化开始向web组件化发展,重点方向是机器学习与Web交互
首发年份 | 名称 | 简介 |
---|---|---|
2003 | matplotlib | 基础绘图工具 |
2010 | networkx | 复杂网络与图算法工具 |
2012 | seaborn | 快速统计图 |
2012 | bokeh | 交互式 |
2012 | plotly | 交互式 |
2015 | altair | 声明式语义 |
2015 | dash | 基于plotly的web app |
2015 | tensorboard | tensorflow and keras |
2017 | ipyvolume | 3D交互 |
2018 | Vaex | 高性能渲染 |
2018 | streamlit | 机器学习web app |
2018 | volia | notebook web app |
pyviz网站整理了Python数据可视化工具
Matplotlib的设计哲学是让Python程序员完全控制可视化应用。Matplotlib中文字体显示问题,请参考中文设置方法
%load_ext autoreload
%autoreload 2
%matplotlib inline
from matplotlib.font_manager import _rebuild
_rebuild()
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style("whitegrid", {"font.sans-serif": ["SimHei", "Arial"]})
import pandas_alive
import pandas as pd
import numpy as np
df_covid = pd.read_json("3.data-viz/timeseries.json")
df_covid.index = pd.DatetimeIndex(df_covid.iloc[:, 0].apply(lambda _: _["date"]))
df_covid.index.name = "日期"
df_covid = df_covid.applymap(lambda _: int(_["confirmed"]))
df_covid.replace(0, np.nan, inplace=True)
top20 = df_covid.iloc[-1].sort_values().tail(20).index
df_covid = df_covid[top20]
%matplotlib notebook
会在Notebook中启动交互式图形%matplotlib inline
会在Notebook中启动静态图形ipython
后使用%matplotlib
魔法命令,每个plt命令都会自动打开一个图形窗口%matplotlib notebook
x = np.linspace(0, 10, 100)
fig = plt.figure()
plt.plot(x, np.sin(x))
plt.plot(x, np.cos(x))
plt.title('测试')
# plt.show()`会启动一个事件循环(event loop)
plt.show()
%matplotlib inline
x = np.linspace(0, 10, 100)
fig = plt.figure()
plt.plot(x, np.sin(x))
plt.plot(x, np.cos(x))
plt.title('测试')
# plt.show()`会启动一个事件循环(event loop)
plt.show()
# 保存图形
fig.savefig('sin_cos.png')
ls -lh sin_cos.png
-rw-r--r-- 1 toddtao staff 23K May 28 16:13 sin_cos.png
# 用IPython的`Image`对象显示图形
from IPython.display import Image
Image('sin_cos.png')
用markdown语法显示图形
plt
)接口绘图,与MATLAB语法类似,plt是有状态的(stateful),会持续跟踪“当前的”图形和坐标轴,控制子图时比较麻烦Figure
和Axes
方法控制图形,可以按照行列控制子图,操作非常灵活plt.Figure
类的一个实例,是一个能够容纳各种坐标轴、图形、文字和标签的容器plt.Axes
类的一个实例,是一个带有刻度和标签的矩形,包含所有可视化的图形元素plt.figure() # 创建图形
# 创建两个子图中的第一个,设置坐标轴
plt.subplot(2, 1, 1) # (行、列、子图编号)
plt.plot(x, np.sin(x))
# 创建两个子图中的第二个,设置坐标轴
plt.subplot(2, 1, 2)
plt.plot(x, np.cos(x));
# 先创建图形网格
# ax是一个包含两个Axes对象的数组
fig, ax = plt.subplots(2)
# 在每个对象上调用`plot()`方法
ax[0].plot(x, np.sin(x))
ax[1].plot(x, np.cos(x));
plt.plot()
函数设置颜色(color
参数)与风格(linestyle
参数)
plt.figure(figsize=(10, 5))
plt.plot(x, np.sin(x - 0), color='blue') # 标准颜色名称
plt.plot(x, np.sin(x - 1), color='g') # 缩写颜色代码(rgbcmyk)
plt.plot(x, np.sin(x - 2), color='0.75') # 范围在0~1之间的灰度值
plt.plot(x, np.sin(x - 3), color='#FFDD44') # 十六进制(RRGGBB,00~FF)
plt.show()
plt.figure(figsize=(10, 5))
plt.plot(x, np.sin(x - 0), linestyle='-') # 实线
plt.plot(x, np.sin(x - 1), linestyle='--') # 虚线
plt.plot(x, np.sin(x - 2), linestyle='-.') # 点划线
plt.plot(x, np.sin(x - 3), linestyle=':'); # 实点线
plt.show()
可以将linestyle
和color
编码组合起来,作为plt.plot()
函数的一个参数使用:
plt.figure(figsize=(10, 5))
plt.plot(x, np.sin(x - 0), '-g') # 绿色实线
plt.plot(x, np.sin(x - 1), '--c') # 青色虚线
plt.plot(x, np.sin(x - 2), '-.k') # 黑色点划线
plt.plot(x, np.sin(x - 3), ':r'); # 红色实点线
plt.figure(figsize=(10, 5))
plt.plot(x, np.sin(x), '-g', label='sin(x)')
plt.plot(x, np.cos(x), ':b', label='cos(x)')
plt.title("正弦余弦曲线")
plt.xlabel("x值")
plt.ylabel("三角函数值");
plt.legend();
创建散点图可以用plt.plot
和plt.scatter
。后者功能更强大,可以让每个散点具有不同的属性(大小、表面颜色、边框颜色等),实现多维度可视化,例如alpha
参数来调整透明度:
plt.plot
性能优于plt.scatter
:由于plt.scatter
会对每个散点进行单独渲染,因此渲染器会消耗更多的资源。而在plt.plot
中,散点基本都彼此复制,因此整个数据集中所有点的颜色、尺寸只需要配置一次,因此处理几千个点的数据集时,plt.plot
方法比plt.scatter
方法性能好。
rng = np.random.RandomState(0)
x = rng.randn(100)
y = rng.randn(100)
colors = rng.rand(100)
sizes = 1000 * rng.rand(100)
plt.figure(figsize=(10, 10))
plt.scatter(x, y, c=colors, s=sizes, alpha=0.3, cmap="viridis")
# 显示颜色条
plt.colorbar();
MNIST手写数字是机器学习图像识别经典示例,在Scikit-Learn里面,包含近2000份8×8的手写数字缩略图,每个图片都是8x8=64像素,展开成特征矩阵是64维空间。
from sklearn.datasets import load_digits
digits = load_digits()
fig, ax = plt.subplots(10, 10, figsize=(8, 8))
for i, axi in enumerate(ax.flat):
axi.imshow(digits.images[i], cmap='binary')
axi.set(xticks=[], yticks=[])
通过scikit-learn流形学习(manifold learning)最早的算法Isomap(Isometric Mapping)将64维空间降成2维平面实现可视化,对比PCA(主成分分析),Isomap可以学习到非线性特征
"流形学习"——中国拓扑学家江泽涵院士取自文天祥《正气歌》的“天地有正气,杂然赋流形”,表示“多样体”
from sklearn.manifold import Isomap
iso = Isomap(n_components=2).fit_transform(digits.data)
sns.set_style("dark", {"font.sans-serif": ["SimHei", "Arial"]})
plt.figure(figsize=(10, 10))
plt.scatter(
iso[:, 0], iso[:, 1], lw=0.1, c=digits.target, cmap=plt.cm.get_cmap("cubehelix", 10),
)
plt.colorbar(ticks=range(10), label="数字值")
plt.clim(-0.5, 9.5);
sns.set_style("white", {"font.sans-serif": ["SimHei", "Arial"]})
plt.figure(figsize=(10, 10))
ax1 = plt.axes() # 默认坐标轴
ax2 = plt.axes([0.65, 0.65, 0.2, 0.2])
面向对象画图接口中类似的命令有fig.add_axes()
。用这个命令创建两个竖直排列的坐标轴:
fig = plt.figure(figsize=(10, 10))
ax1 = fig.add_axes([0.1, 0.5, 0.8, 0.4], xticklabels=[], ylim=(-1.2, 1.2))
ax2 = fig.add_axes([0.1, 0.1, 0.8, 0.4], ylim=(-1.2, 1.2))
x = np.linspace(0, 10)
ax1.plot(np.sin(x))
ax2.plot(np.cos(x));
plt.subplot
:简易网格子图¶plt.subplot()
在一个网格中创建一个子图。这个命令有3个整型参数——将要创建的网格子图行数、列数和索引值,索引值从1开始,从左上角到右下角依次增大:
plt.figure(figsize=(10, 10))
for i in range(1, 7):
plt.subplot(2, 3, i)
plt.text(0.5, 0.5, str((2, 3, i)), fontsize=18, ha="center")
plt.subplots_adjust
命令可以调整子图之间的间隔。用面向对象接口的命令fig.add_subplot()
可以取得同样的效果:
通过
plt.subplots_adjust
的hspace
与wspace
参数设置与图形高度与宽度一致的子图间距
fig = plt.figure(figsize=(10, 10))
fig.subplots_adjust(hspace=0.4, wspace=0.4)
for i in range(1, 7):
ax = fig.add_subplot(2, 3, i)
ax.text(0.5, 0.5, str((2, 3, i)), fontsize=18, ha="center")
plt.subplots
:用一行代码创建网格¶plt.subplots()
实用一行代码创建多个子图,并返回一个包含子图的NumPy数组。参数是行数与列数,以及可选参数sharex
与sharey
,通过它们可以设置不同子图之间的关联关系。
fig, ax = plt.subplots(2, 3, sharex="col", sharey="row", figsize=(10, 10))
for i in range(2):
for j in range(3):
ax[i, j].text(0.5, 0.5, str((i, j)), fontsize=18, ha="center")
plt.GridSpec()
可以实现不规则的多行多列子图网格。例如,一个带行列间距的2x3网格的配置代码如下所示:
plt.figure(figsize=(10, 10))
grid = plt.GridSpec(2, 3, wspace=0.4, hspace=0.3)
plt.subplot(grid[0, 0])
plt.subplot(grid[0, 1:])
plt.subplot(grid[1, :2])
plt.subplot(grid[1, 2]);
%matplotlib notebook
from mpl_toolkits import mplot3d
fig = plt.figure(figsize=(10, 10))
ax = plt.axes(projection="3d")
y = 15
# 三维曲线
zline = np.linspace(0, y, 1000)
xline = np.sin(zline)
yline = np.cos(zline)
ax.plot3D(xline, yline, zline, "gray")
# 三维散点
zdata = y * np.random.random(100)
xdata = np.sin(zdata) + 0.1 * np.random.randn(100)
ydata = np.cos(zdata) + 0.1 * np.random.randn(100)
ax.scatter3D(xdata, ydata, zdata, c=zdata, cmap="Greens");
用ax.plot_surface
演示一个三维正弦函数画的三维等高曲面图,要求X,Y,Z都是二维网格数据的形式:
x = np.linspace(-6, 6, 30)
y = np.linspace(-6, 6, 30)
X, Y = np.meshgrid(x, y)
Z = np.sin(np.sqrt(X ** 2 + Y ** 2))
fig = plt.figure(figsize=(10, 10))
ax = plt.axes(projection="3d")
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap="viridis", edgecolor="none")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
Text(0.5, 0, 'z')
Pandas以Matplotlib实现了plot接口(Matlab风格),可以快速实现Serise与Datafram的可视化,pandas-alive增加了时间序列的动态图效果
%matplotlib inline
!head -n 20 timeseries.json
{ "Afghanistan": [ { "date": "2020-1-22", "confirmed": 0, "deaths": 0, "recovered": 0 }, { "date": "2020-1-23", "confirmed": 0, "deaths": 0, "recovered": 0 }, { "date": "2020-1-24", "confirmed": 0, "deaths": 0, "recovered": 0 },
df_covid.diff().hist(figsize=(20,15), sharey=True);
spain = df_covid['Spain'].diff()
spain[spain<0]
日期 2020-04-24 -10034.0 Name: Spain, dtype: float64
df_covid.loc["2020-04-20":"2020-04-30", "Spain"]
日期 2020-04-20 200210.0 2020-04-21 204178.0 2020-04-22 208389.0 2020-04-23 213024.0 2020-04-24 202990.0 2020-04-25 205905.0 2020-04-26 207634.0 2020-04-27 209465.0 2020-04-28 210773.0 2020-04-29 212917.0 2020-04-30 213435.0 Name: Spain, dtype: float64
def current_total(values):
total = values.sum()
s = f"总数 : {int(total)}"
return {"x": 0.85, "y": 0.2, "s": s, "ha": "right", "size": 11}
animated_html = df_covid.tail(60).plot_animated(period_summary_func=current_total)
Generating BarChartRace, plotting ['Netherlands', 'Pakistan', 'Belgium', 'Chile', 'Mexico', 'Saudi Arabia', 'Canada', 'China', 'Peru', 'India', 'Iran', 'Turkey', 'Germany', 'France', 'Italy', 'Spain', 'United Kingdom', 'Brazil', 'Russia', 'US']
/Users/toddtao/opt/anaconda3/lib/python3.7/site-packages/pandas_alive/charts.py:70: UserWarning: Plotting too many bars may result in undesirable output, use `n_visible=5 to limit number of bars "Plotting too many bars may result in undesirable output, use `n_visible=5 to limit number of bars"
from IPython.display import display, Video
display(Video('3.data-viz/covid19.mp4'))