#!/usr/bin/env python
# coding: utf-8
# # Python数据科学分享——3.数据可视化(1)
#
# > 数据可视化是描述性统计不可或缺的亮点,在matplotlib、JavaScript(D3.js)、OpenGL的基础上百花齐放,百家争鸣
#
# - toc: true
# - badges: true
# - comments: true
# - categories: [jupyter,Python,Data Science]
#
#
#
# ![](3.data-viz/1pic.jpg)
# # 目标与原则
#
# 以最小的复杂度展示足够多的信息
#
# 1. 目标:如果不能为用户提供有用的信息,那么就没啥用;如果信息展现形式太复杂,那么就会被噪声干扰
# 3. 原则:
# 1. 对比(Contrast):让页面引人注目,避免页面上的元素太过相似。如果元素(字体、颜色、大小、线宽、形状、空间等)不相同,那就干脆让它们截然不同。
# 2. 重复(Repetition):让设计中的视觉要素在整个作品中重复出现。既能增加条理性,还可以加强统一性。
# 3. 对齐(Alignment):任何东西都不能在页面上随意安放。每个元素都应当与页面上的另一个元素有某种视觉联系,建立清晰、精巧而且清爽的外观。
# 4. 亲密性(Proximity):彼此相关的项应当靠近,归组在一起。如果多个项相互之间存在很近的亲密性,它们就会成为一个视觉单元,而不是多个孤立的元素。这有助于组织信息,减少混乱,为读者提供清晰的结构。
#
# > 美国教育家、设计师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](https://pyviz.org/)网站整理了Python数据可视化工具
# ![](3.data-viz/pyviz.png)
# # [matplotlib](https://matplotlib.org/)基础图库
#
# Matplotlib的设计哲学是让Python程序员完全控制可视化应用。Matplotlib中文字体显示问题,请参考[中文设置方法](https://blog.csdn.net/wangyaninglm/article/details/84901376#seaborn_381)
#
# 1. 模仿MatLab,上手简单,理工科同学上手成本低
# 1. 许多渲染接口
# 1. 功能齐全、文档完整
# 1. 测试容易、源代码质量高
#
#
#
#
# In[5]:
get_ipython().run_line_magic('load_ext', 'autoreload')
get_ipython().run_line_magic('autoreload', '2')
get_ipython().run_line_magic('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
# In[16]:
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]
# ## 绘图环境
#
# 1. 在Jupyter(IPython) Notebook中画图:
# - `%matplotlib notebook`会在Notebook中启动**交互式**图形
# - `%matplotlib inline`会在Notebook中启动**静态**图形
# 1. 在.py文件中画图:执行使用matplotlib的脚本后,会看到一个新窗口,里面会显示图形
# 1. 在IPython shell中画图(在图形界面系统中启动):在IPython shell中启动`ipython`后使用`%matplotlib`魔法命令,每个plt命令都会自动打开一个图形窗口
# In[3]:
get_ipython().run_line_magic('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()
# In[10]:
get_ipython().run_line_magic('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()
# In[11]:
# 保存图形
fig.savefig('sin_cos.png')
# In[41]:
ls -lh sin_cos.png
# In[12]:
# 用IPython的`Image`对象显示图形
from IPython.display import Image
Image('sin_cos.png')
# 用markdown语法显示图形
#
# ![](3.data-viz/sin_cos.png)
# ## 绘图接口
#
# 1. 图形结构:Artist(Figure、Axes、Axis)
# 1. MATLAB风格接口:通过pyplot(`plt`)接口绘图,与MATLAB语法类似,plt是**有状态的**(stateful),会持续跟踪“当前的”图形和坐标轴,控制子图时比较麻烦
# 2. 面向对象接口:通过`Figure`和`Axes`**方法**控制图形,可以按照行列控制子图,操作非常灵活
# - figure:`plt.Figure`类的一个实例,是一个能够容纳各种坐标轴、图形、文字和标签的容器
# - axes:`plt.Axes`类的一个实例,是一个带有刻度和标签的矩形,包含所有可视化的图形元素
#
#
#
# ### MATLAB风格接口
# In[13]:
plt.figure() # 创建图形
# 创建两个子图中的第一个,设置坐标轴
plt.subplot(2, 1, 1) # (行、列、子图编号)
plt.plot(x, np.sin(x))
# 创建两个子图中的第二个,设置坐标轴
plt.subplot(2, 1, 2)
plt.plot(x, np.cos(x));
# ### 面向对象接口
# In[14]:
# 先创建图形网格
# 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`参数)
# In[33]:
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()
# In[34]:
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()`函数的一个参数使用:
# In[35]:
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'); # 红色实点线
# ### 图形标签
#
# 1. 图标题
# 1. 轴标题
# 1. 图例
# In[32]:
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`方法性能好。
# In[ ]:
rng = np.random.RandomState(0)
x = rng.randn(100)
y = rng.randn(100)
colors = rng.rand(100)
sizes = 1000 * rng.rand(100)
# In[42]:
plt.figure(figsize=(10, 10))
plt.scatter(x, y, c=colors, s=sizes, alpha=0.3, cmap="viridis")
# 显示颜色条
plt.colorbar();
# ### MNIST手写数字可视化
#
# MNIST手写数字是机器学习图像识别经典示例,在Scikit-Learn里面,包含近2000份8×8的手写数字缩略图,每个图片都是8x8=64像素,展开成特征矩阵是64维空间。
# In[80]:
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)](https://scikit-learn.org/stable/modules/manifold.html)最早的算法Isomap(Isometric Mapping)将64维空间降成2维平面实现可视化,对比PCA(主成分分析),Isomap可以学习到非线性特征
#
# > "流形学习"——中国拓扑学家江泽涵院士取自文天祥《正气歌》的“天地有正气,杂然赋流形”,表示“多样体”
# In[79]:
from sklearn.manifold import Isomap
iso = Isomap(n_components=2).fit_transform(digits.data)
# In[92]:
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);
# ## 子图
#
# Matplotlib通过**子图**(subplot)的概念,实现多角度数据对比:
#
# 1. `plt.axes`:手动创建子图
# 2. `plt.subplot`:简易网格子图
# 3. `plt.subplots`:用NumPy数组创建网格
# 4. `plt.GridSpec`:自由排列网格
#
# ### `plt.axes`:手动创建子图
#
# `plt.axes`函数默认创建一个标准的坐标轴,填满整张图。其可选参数用4个值分别表示图形坐标的 `[bottom, left, width, height]`(底坐标、左坐标、宽度、高度),数值的取值范围是左下角(原点)为0,右上角为1。
# In[102]:
sns.set_style("white", {"font.sans-serif": ["SimHei", "Arial"]})
# In[109]:
plt.figure(figsize=(10, 10))
ax1 = plt.axes() # 默认坐标轴
ax2 = plt.axes([0.65, 0.65, 0.2, 0.2])
# 面向对象画图接口中类似的命令有`fig.add_axes()`。用这个命令创建两个竖直排列的坐标轴:
# In[112]:
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开始,从左上角到右下角依次增大:
# In[113]:
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`参数设置与图形高度与宽度一致的子图间距
# In[115]:
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`,通过它们可以设置不同子图之间的关联关系。
# In[119]:
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`:实现更复杂的排列方式
#
# `plt.GridSpec()`可以实现不规则的多行多列子图网格。例如,一个带行列间距的2x3网格的配置代码如下所示:
# In[122]:
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画三维图
#
# 可以用`ax.plot3D`与`ax.scatter3D`函数来创建三维坐标点构成的线图与散点图,需要用`%matplotlib notebook`实现交互
#
# [ipyvolume](https://github.com/maartenbreddels/ipyvolume):通过WebGL在Jupyter notebook实现3D交互,支持百万散点的页面渲染和交互
# In[3]:
get_ipython().run_line_magic('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都是二维网格数据的形式:
# In[4]:
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")
# ## [pandas plot]()与[pandas-alive](https://github.com/JackMcKew/pandas_alive)
#
# Pandas以Matplotlib实现了plot接口(Matlab风格),可以快速实现Serise与Datafram的可视化,pandas-alive增加了时间序列的动态图效果
# In[2]:
get_ipython().run_line_magic('matplotlib', 'inline')
# In[3]:
get_ipython().system('head -n 20 timeseries.json')
# In[4]:
df_covid.diff().hist(figsize=(20,15), sharey=True);
# In[4]:
spain = df_covid['Spain'].diff()
spain[spain<0]
# In[5]:
df_covid.loc["2020-04-20":"2020-04-30", "Spain"]
# In[5]:
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)
# In[1]:
from IPython.display import display, Video
display(Video('3.data-viz/covid19.mp4'))