#!/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'))