楔子
Python 在数据可视化方面有非常多的第三方库,比如 matplotlib, pyecharts, bokeh 等等,但个人最喜欢的莫过于 plotly 这个库。plotly 被称为数据可视化神器,首先它支持很多很多种图表,并且参数可以自由设置,最关键的是画出来的图非常漂亮。毕竟在数据可视化方面,图表的颜值也是很重要的。
很多人认为,pandas 和 plotly 这两个库所带来的影响力甚至超越了 Python 这门语言本身。
下面我们就来全方位介绍一下 plotly 的用法,不过首先要安装它,直接 pip 安装即可,非常方便。
对于学习数据可视化框架而言,本质上就是学习如何使用它来绘制图表。plotly 支持很多图表,包括 3D 动态图等等,这里我们先介绍一些常用的,至于更复杂的图表后续会说。
import plotly.graph_objs as go
import numpy as np
import pandas as pd
上面几个模块先导入就行,我们的代码会在 jupyter notebook 上运行,可以直接显示图表。至于图表如何保存成图片,我们后续再说。
为了更好地理解 plotly,我们要记住两个核心概念,分别是轨迹和画布。上面导入的 graph_objs 专门用来绘制图表,比如 go.Scatter 是散点图,在 plotly 中,图表被称为轨迹(trace)。而轨迹如果想显示,那么必须显示在画布上,当然一个画布可以显示多个轨迹。
所以结论如下:我们根据自己的需要来创建轨迹,然后再创建一张画布,它是用于展示轨迹(图表)所不可或缺的舞台,然后将轨迹展示在画布上即可。
而我们也可以调整轨迹、画布的一些属性,让它们整体看起来更加的完美,也就是我们说的设置属性参数。plotly 里面图表、画布的属性参数非常多,但我们都会说。而且也正因为如此,图表中任何位置,只要我们不满意都可以进行设置。
下面就来介绍一些常见的图表。
散点图
散点图(scatter)是将数据以点的形式展现在平面直角坐标系上的统计图表,它至少需要两个不同变量,一个沿 X 轴绘制,另一个沿 Y 轴绘制。每个点在 X、Y 轴上都有一个确定的位置。众多的散点叠加后,即可展示数据集的整体景观,从而帮助我们分析两个变量之间的相关性,或找出趋势和规律。此外,我们还可以添加附加的变量,来给散点分组、着色、确定透明度等等。
# 生成100个点
x = np.linspace(0, 2, 100)
y0 = np.random.randn(100) + 5
y1 = np.random.randn(100)
y2 = np.random.randn(100) - 5
# 里面的参数一会儿解释
trace0 = go.Scatter(x=x, # x 轴的坐标y=y0, # y 轴的坐标mode="markers", # 纯散点绘图name="散点" # 曲线名称
)trace1 = go.Scatter(x=x, y=y1, # 散点 + 线段绘图mode="markers + lines", name="散点 + 线段"
)trace2 = go.Scatter(x=x, y=y2, mode="lines", # 线段绘图name="线段"
)
# 我们看到比较神奇的地方是,Scatter 居然也可以绘制线段
# 是的,如果不指定 mode 为 "markers",默认绘制的就是线段# 以上就创建了三条轨迹,下面该干什么了?对,创建画布
# 将轨迹组合成列表传进去,因为一张画布是可以显示多条轨迹的
fig = go.Figure(data=[trace0, trace1, trace2])
# 在 notebook 中,直接通过 fig 即可显示图表
fig
看一下画出来的图是什么样子?
可以看到比 matplotlib 漂亮多了,在绘制轨迹的时候我们使用了 4 个参数,回顾一下:
-
x:很好理解,就是 x 轴的坐标;
-
y:很好理解,就是 y 轴的坐标;
-
name:轨迹的名称,就是显示在画布右上方的那个;
-
mode:轨迹的种类,主要有三种,"markers" 表示纯散点,"markers+lines" 表示散点加上线段,"lines" 是线段;
然后再来看一个参数 marker,它接收一个字典,用来设置散点的样式。
x = np.linspace(0, 2, 100)
y0 = np.random.randn(100)
trace0 = go.Scatter(x=x, y=y0, mode="markers", name="散点图",marker={# 点的大小"size": 8,# 点的颜色"color": "rgba(102, 198, 147, 0.7)",# 除此之外,还可以设置点的轮廓"line": {# 线条大小"width": 10, # 线条的颜色"color": "rgba(1, 170, 118, 0.3)" }}
)fig = go.Figure(data=[trace0])
fig
这里我们没有指定 name 参数,所以右上角不会显示轨迹的名称。再来总结一下 marker 参数:
marker = {# 点的大小"size": n,# 点的颜色,可以是 rgba,可以是 rgb;# 可以是颜色的英文名,比如 green,yellow;# 可以是一个 16 进制颜色码,比如:#FF6A04# 以及它还可以是一个元素个数与轨迹的点的个数相同的数组# 对应着数组中相同的值的多个点,会被标记为同一种颜色# 在机器学习里面进行聚类的时候,非常常见"color": "rgba(n1, n2, n3, n4)",# 点的线条、轮廓"line": {"width": n, "color": "green"}# 是否在右侧显示一个颜色条,默认为 False,不显示"showscale": True
}
基于以上参数,我们可以将散点图的样式绘制得非常漂亮。
问题来了,如果我想添加标题以及坐标轴名称该怎么办?
# 其它代码不变
fig = go.Figure(data=[trace0],layout={"title": "这是标题","xaxis_title": "这是x轴","yaxis_title": "这是y轴",# x轴坐标倾斜60度"xaxis": {"tickangle": 60}}
)
fig
我们看到指定 layout 参数即可给画布设置一些额外属性,该参数可以接收一个字典,通过里面的 key 来指定画布属性,比如:
-
title:标题;
-
xaxis_title:x 轴标题;
-
yaxis_title:y 轴标题;
-
xaxis:x 坐标轴的属性,可以传入一个字典,来设置坐标轴,比如:tickangle 就是将坐标倾斜。尤其在坐标值比较长的时候,我们就可以通过倾斜的方式来避免堆叠在一起。角度大于0顺时针,小于0逆时针。
-
yaxis:y 坐标轴的属性,和 x 坐标轴一样,可以设置非常多的属性。具体能设置哪些,后面单独罗列出来;
-
width:画布的宽度;
-
height:画布的高度;
-
template:画布的风格,可以选择 ggplot2, seaborn, simple_white, plotly, plotly_white, plotly_dark, presentation, xgridoff, ygridoff, gridon, none,默认是 plotly;
散点图的存在意义
散点图常被用于分析变量之间的相关性,如果两个变量的散点看上去都在某条直线附近波动,则称变量之间是线性相关的;如果所有点看上去都在某条曲线(非直线)附近波动,则称变量之间是非线形相关的;如果所有点在图中没有显示任何关系,则称变量之间是不相关的。
如果散点图呈现出一个集中的大致趋势,并且该趋势可以用一条光滑的曲线来近似,那么这个近似的过程就是曲线拟合,而这条曲线则被称为最佳拟合线或趋势线。如果图中存在个别远离集中区域的数据点,那么这样的点被称为离群点或异常值。
折线图
折线图(Line)是一个由笛卡尔坐标系、一些点、以及线段组成的统计图表,常用来表示数值随连续时间间隔或有序类别的变化。在折线图中,x 轴通常用作连续时间间隔或有序类别(比如阶段1、阶段2、阶段3);y 轴用于量化的数据,如果为负值则绘制于 y 轴下方;而连线用于连接两个相邻的数据点。
# covid_19_deaths 是数据集,这个不需要关心
# 你完全可以使用其他的数据集代替
trace0 = go.Scatter(x=covid_19_deaths["date"], y=covid_19_deaths["count"],
)fig = go.Figure(data=[trace0], layout={"template": "plotly_dark","title": "当前死亡人数"})
fig
虽然用的是 go.Scatter,但默认绘制的是折线图。还记得怎么绘制散点图吗?对的,将 mode 指定为 "markers" 即可。
然后是给折线添加样式,我们给散点图添加样式是通过 marker 参数,而给折线添加样式也有对应的参数。
trace0 = go.Scatter(x=covid_19_deaths["date"], y=covid_19_deaths["count"], line={# 折线的宽度和颜色"width": 3, "color": "rgba(255, 30, 186, 1)" }
)fig = go.Figure(data=[trace0], layout={"template": "plotly_dark","title": "当前死亡人数"})
fig
此时折线的样式就改变了,但是我们记得之前在设置散点图的时候好像也用到了 line。没错,只不过那个 line 是传给 marker 参数的字典里面的一个 key,用来设置点的线条、或者轮廓的样式。而这里的 line 它是一个参数,和 marker 参数是同级别的,是用来设置折线样式的。
参数 line 里面还可以指定折线的种类,是虚线、实线等等之类的。
trace0 = go.Scatter(x=covid_19_deaths["date"], y=covid_19_deaths["count"], line={"width": 3, "color": "rgba(255, 30, 186, 1)","dash": "dot" # 指定为虚线}
)fig = go.Figure(data=[trace0], layout={"template": "plotly_dark","title": "当前死亡人数"})
fig
"dash" 表示线条的种类,可选的 value 如下:
-
"dot":由点组成的虚线;
-
"dash":由短线组成的虚线;
-
"dashdot":由点和短线组成的虚线;
绘制直线的时候,数据可能不是连续的,那么绘制出来的图像就会出现断层。这个时候我们可以指定 connectgaps 参数,举个例子:
x = np.array([1, 2, np.nan, 4, 5])
y1 = x * 3 + 1
y2 = x * 3 + 2trace0 = go.Scatter(x=x, y=y1,
)trace1 = go.Scatter(x=x,y=y2,connectgaps=True
)
fig = go.Figure(data=[trace0, trace1], layout={"template": "plotly_dark"})
fig
当数据出现了空值的时候,那么折线默认是会出现断层的,而将 connectgaps 指定为 True ,那么会将缺失值两端的点直接相连,使得直线是完整的。
折线图的存在意义
折线图用于分析事物随时间或有序类别而变化的趋势,如果有多组数据,则用于分析多组数据随时间变化或有序类别的相互作用和影响。折线的方向表示正 / 负变化,折线的斜率表示变化的程度。
柱状图
柱状图,是一种使用矩形条,对不同类别进行数值比较的统计图表。最基础的柱形图,需要一个分类变量和一个数值变量。在柱状图上,分类变量的每个实体都被表示为一个矩形(通俗来讲就是柱子),而数值则决定了柱子的高度。
trace0 = go.Bar(x=["古明地觉", "芙兰朵露", "古明地恋"], y=[17, 400, 16],
)fig = go.Figure(data=[trace0],layout={"template": "plotly_dark"})
fig
这是最简单的柱状图,很明显,即便我们什么参数都不加,样式就已经把 matplotlib 给秒杀了。所以先不管 plotly 这个框架的功能如何(很强),光图表的颜值就足以让我们去学习它。
当然我们也可以绘制多个柱状图:
trace0 = go.Bar(x=["古明地觉", "芙兰朵露", "古明地恋"], y=[17, 400, 16],name="bar1"
)
trace1 = go.Bar(x=["古明地觉", "芙兰朵露", "古明地恋"], y=[86, 90, 96],name="bar2"
)fig = go.Figure(data=[trace0, trace1],layout={"template": "plotly_dark"})
fig
可以看到柱状图并排显示在一起了,此外我们还可以设置层叠柱状图。
trace0 = go.Bar(x=["古明地觉", "芙兰朵露", "古明地恋"], y=[17, 400, 16],name="bar1"
)
trace1 = go.Bar(x=["古明地觉", "芙兰朵露", "古明地恋"], y=[86, 90, 96],name="bar2"
)# 指定 barmode 为 stack,会将柱状图堆叠在一起
# 这里又多了一个 barmode,所以可调节的属性非常多
# 但是不同的属性适用于不同的轨迹,我们需要哪个就设置哪个即可
fig = go.Figure(data=[trace0, trace1],layout={"template": "plotly_dark","barmode": "stack"})
fig
所以层叠柱状图是在画布里面设置的,因为两个轨迹要显示在画布上。至于 Bar 里面的参数是设置轨迹本身, 比如我们还可以调节颜色什么的,通过参数 marker 指定:
trace0 = go.Bar(x=["古明地觉", "芙兰朵露", "古明地恋"], y=[87, 97, 85],name="bar1",marker={# 除了rgba,还可以通过颜色的名称指定"color": "pink",# 指定透明度# 这里指定透明度的方式,同样适用于Scatter"opacity": 0.5, "line": {"width": 3, # 轮廓的宽度"color": "cyan", # 轮廓的颜色}}
)fig = go.Figure(data=[trace0],layout={"template": "plotly_dark"})
fig
柱状图如果想调节样式,也是通过 marker 参数,和 Scatter 里面 marker 一样。
柱状图的存在意义
柱状图最适合对分类的数据进行比较,尤其是当数值比较接近时,由于人眼对于高度的感知优于其它视觉元素(如面积、角度等),因此使用柱状图更加合适。
作为人们最常用的图表之一,柱状图也衍生出多种多样的图表形式。例如,将多个并列的类别聚类、形成一组,再在组与组之间进行比较,这种图表叫做分组柱状图或簇状柱形图。还可以将类别拆分成多个子类别,形成堆叠柱状图。再比如将柱形图和折线图结合起来,共同绘制在一张图上,俗称双轴图等等。
水平柱状图
水平柱状图也叫条形图,和柱状图类似,也是通过 go.Bar 来绘制,只不过需要多加一个参数。
trace0 = go.Bar(# 方向变了,所以 x 轴和 y 轴的数据也要调换位置y=["古明地觉", "芙兰朵露", "古明地恋"], x=[87, 97, 85],marker={"color": "pink","opacity": 0.5, "line": {"width": 3, "color": "cyan", }},# 指定为水平方向即可orientation="h"
)fig = go.Figure(data=[trace0],layout={"template": "plotly_dark"})
fig
柱状图有多高取决于 y 轴,而水平柱状图有多长就取决于 x 轴了,所以此时 x 轴就是代表数值的那一方。至于设置颜色、轮廓等参数,和之前的柱状图一样。
面积图
面积图,又称区域图,是一种随有序变量的变化,反映数值变化的统计图表,原理与折线图相似。而面积图的特点在于,折线与自变量坐标轴之间的区域,会用颜色或者纹理填充。
绘制面积图依旧使用 Scatter,只需要指定一个 fill 参数即可。
x = np.linspace(0, 2, 100)
y0 = np.cos(x)
y1 = np.sin(x)trace0 = go.Scatter(x=x,y=y0,name="cos",mode="markers", fill="tozeroy"
)
trace1 = go.Scatter(x=x,y=y1,name="sin",# mode还可以设置为none,表示将线段隐藏mode="none", fill="tozeroy"
)
fig = go.Figure(data=[trace0, trace1], layout={"title": "面积图", "template": "plotly_dark"})
fig
我们看到面积图就是将轨迹和 x 轴围成的部分涂上颜色,仔细观察橘色的 sin,线段没了,所以线段或者此时的面积图的边界线就相当于被隐藏掉了。如果不指定 fill,直接指定 mode="none" 也是可以隐藏的,只不过此时绘制的线段你就看不到了。另外我们看到即使是散点,也是可以围成面积图的,会用线将点拟合起来,再绘制围成的面积图。
然后注意 fill 参数,它的可选值如下:
-
tozeroy:与 x 轴围成的面积;
-
tozerox:与 y 轴围成的面积;
-
toself:与自身围成的面积;
除了这三个可选值之外,其实还有几个,不过一般用不到。我们主要用的还是 tozeroy,绘制和 x 轴围成的面积。
x = np.array([1, 2, 3, 4, 5])
y0 = np.array([2, 6, 9, 7, 3])
y1 = y0 + 1
y2 = y0 + 2
y3 = y0 + 3trace0 = go.Scatter(x=x,y=y0,name="y0",fill="tonexty"
)
trace1 = go.Scatter(x=x,y=y1,name="y1",fill="tonexty"
)
trace2 = go.Scatter(x=x,y=y2,name="y2",fill="tonexty"
)
trace3 = go.Scatter(x=x,y=y3,name="y3",fill="tonexty"
)
fig = go.Figure(data=[trace0, trace1, trace2, trace3],layout={"title": "面积图", "template": "plotly_dark"})
fig
还是很简单的。
面积图的存在意义
面积图可用于多个系列数据的比较,此时面积图的外观看上去类似层叠的山脉,在错落有致的外形下表达数据的总量和趋势。相较于折线图,面积图不仅可以清晰地反映出数据的趋势变化,也能够强调不同类别的数据间的差距对比。
但它的劣势在于,填充会让形状互相遮盖,反而看不清变化。我们上面的数据弄的比较规整,所以看起来很清晰,但实际工作中绘制出来的面积图就不一定了。而一种解决方法是使用有透明度的颜色,来让出覆盖区域。
甘特图
甘特图又称横道图,是用来显示项目进度等与时间相关的数据的。直接看个例子就很好理解了:
# 创建甘特图,使用plotly.figure_factory
# 然后调用内部的create_gantt即可
import plotly.figure_factory as fftasks = [{"Task": "任务A", "Start": "2018-1-1", "Finish": "2018-3-1"},{"Task": "任务B", "Start": "2018-2-1", "Finish": "2018-5-1"},{"Task": "任务C", "Start": "2018-2-1", "Finish": "2018-6-1"},{"Task": "任务D", "Start": "2018-4-1", "Finish": "2018-8-1"},{"Task": "任务E", "Start": "2018-8-1", "Finish": "2019-1-1"}
]
# 但是数据格式有要求,里面是一个列表,列表里面是字典
# 字典包含至少三个键值对,分别是 Task、Start、Finish
# 表示任务、开始时间、结束时间,不能是其它的名字
fig = ff.create_gantt(tasks, title="这是甘特图")
# 此时会直接返回画布,想调整属性的话
# 通过 fig.layout.update 即可
fig.layout.update({"template": "plotly_dark"})
fig
创建甘特图的时候,除了传递一个包含字典的列表,还可以传递 DataFrame,当然列名同样必须是 Task, Start, Finish。
此外我们还可以指定颜色,以及进度。
# 创建甘特图,使用plotly.figure_factory
# 然后调用内部的create_gantt即可
import plotly.figure_factory as fftasks = [{"Task": "任务A", "Start": "2018-1-1", "Finish": "2018-3-1", "Complete": "干了一小半"},{"Task": "任务B", "Start": "2018-2-1", "Finish": "2018-5-1", "Complete": "干了一半"},{"Task": "任务C", "Start": "2018-2-1", "Finish": "2018-6-1", "Complete": "干了一半"},{"Task": "任务D", "Start": "2018-4-1", "Finish": "2018-8-1", "Complete": "干了一大半"},{"Task": "任务E", "Start": "2018-8-1", "Finish": "2019-1-1", "Complete": "全干完了"}
]
# 为不同的进度赋予不同的颜色
colors = {"干了一小半": "rgb(125, 135, 144)","干了一半": "rgb(187, 20, 168)","干了一大半": "rgb(14, 199, 250)","全干完了": "rgb(250, 1, 144)"
}
fig = ff.create_gantt(tasks, # 为不同的进度赋予不同的颜色colors=colors,# 表示进度,名字任意# 这里我们叫 "Complete"index_col="Complete", # 显示颜色条show_colorbar=True)
# fig.data 获取所有的轨迹
# fig.layout 获取画布的属性
fig = go.Figure(fig.data, layout={"template": "plotly_dark","title": "我的甘特图"})
fig
甘特图感觉在工作中用的不是很多,了解一下就好。
直方图
直方图,又称质量分布图,用于表示数据的分布情况,是一种常见的统计图表。一般用横轴表示数据区间,纵轴表示分布情况,柱子越高,则落在该区间的数量越大。根据数据分布状况不同,直方图展示的数据有不同的模式,包括对称单峰、偏左单峰、偏右单峰、双峰、多峰以及对称多峰。
构建直方图,首先要确定组距、对数值的范围进行分区,通俗的说就是划定有几根柱子(例如 0-100 分,每隔20分划一个区间,共5个区间)。接着,对落在每个区间的数值进行频次计算(如落在 80-100 分的 10 人,60-80 分的 20 人,以此类推)。最后,绘制矩形,高度由频数决定。
直方图与上面介绍的柱状图有点相像,但其实是完全不同的。前者反映数据分布情况,后者则不具备此功能,只能对数值进行比较。从数据结构来说,柱状图需要 1 个分类变量,是离散的(如一班、二班、三班),因此柱子间有空隙。但直方图的数据均为连续的数值变量(如成绩),因此柱子间是没有空隙的。
x = np.random.randint(1, 20, 1000)
trace0 = go.Histogram(x=x)
fig = go.Figure(data=[trace0], layout={"title": "直方图","template": "plotly_dark"})
fig
横坐标是数值,纵坐标是出现的次数。但是有些不完美,就是跨度太大了,所以我们可以指定 xaxis 属性,让跨度小一些。
x = np.random.randint(1, 20, 1000)
trace0 = go.Histogram(x=x,# 指定histnorm为probability# 那么y轴将显示出现的次数所占的比例histnorm="probability"
)
fig = go.Figure(data=[trace0], layout={"title": "直方图","template": "plotly_dark",# range表示坐标范围# dtick表示相邻坐标之间的差值# 这里是 2,所以就是 0 2 4 6..."xaxis": {"dtick": 2, "range": [0, 20]} })
fig
忘记说了,其实所有的图表对应的类里面,很多参数都是相同的,比如 marker 设置图表本身的颜色透明度、以及轮廓的颜色、大小,name 设置图表的名称等等,这些参数我们不再赘述,而是会直接使用。
然后我们也可以绘制多个直方图:
x0 = np.random.randn(1000)
x1 = np.random.chisquare(5, 1000)
trace0 = go.Histogram(x=x0,histnorm="probability",marker={"opacity": 0.75}
)
trace1 = go.Histogram(x=x1,histnorm="probability",marker={"opacity": 0.75}
)
fig = go.Figure(data=[trace0, trace1], layout={"title": "直方图","template": "plotly_dark","xaxis": {"dtick": 2, "range": [1, 20]} })
fig
但是我们发现这个直方图貌似有些不对劲,因为 plotly 将多个直方图强制变窄了,我们需要将 barmode 指定为 "overlay"。
x0 = np.random.randn(1000)
x1 = np.random.chisquare(5, 1000)
trace0 = go.Histogram(x=x0,histnorm="probability",marker={"opacity": 0.75}
)
trace1 = go.Histogram(x=x1,histnorm="probability",marker={"opacity": 0.75}
)
fig = go.Figure(data=[trace0, trace1], layout={"title": "直方图", "template": "plotly_dark","barmode": "overlay"})
fig
如果将 barmode 改成 "stack",那么就会创建堆叠直方图。
x0 = np.random.randn(1000)
x1 = np.random.randn(1000)
trace0 = go.Histogram(x=x0,histnorm="probability",marker={"opacity": 0.75}
)
trace1 = go.Histogram(x=x1,histnorm="probability",marker={"opacity": 0.75}
)
fig = go.Figure(data=[trace0, trace1], layout={"title": "堆叠直方图", "template": "plotly_dark","barmode": "stack"})
fig
最后是累计直方图,它的特点是:第n+1个区间的样本数是第n-1个区间的样本数加上第n个区间的样本数。
x0 = np.random.randn(1000)
trace0 = go.Histogram(x=x0,histnorm="probability",marker={"opacity": 0.75},# 指定 cumulative 即可cumulative={"enabled": True}
)
fig = go.Figure(data=[trace0], layout={"title": "累计直方图", "template": "plotly_dark","barmode": "stack"})
fig
直方图的种类还是蛮多的,但说实话,感觉直方图用的也不是很多。如果只是做简单的报表统计,那么几乎用不到直方图。但我们接下来要介绍的饼图,使用就很广泛了。
饼图
饼图是一种划分为几个扇形的圆形统计图表。在饼图中,每个扇形的弧长(以及圆心角和面积)大小,表示该种类占总体的比例,并且这些扇形合在一起刚好是一个完全的圆形。
饼图最显著的功能在于表现占比。习惯上,人们也用饼图来比较扇形的大小,从而获得对数据的认知。但是,由于人类对角度的感知能力不如长度,因此在需要准确的表达数值(尤其是当数值接近、或数值很多)时,饼图常常不能胜任,建议用柱状图代替。
# 饼图就很简单了,使用 Pie 这个类
trace0 = go.Pie(labels=["古明地觉", "芙兰朵露", "古明地恋", "雾雨魔理沙", "紫妈"],values=[10, 25, 5, 35, 41]
)
fig = go.Figure(data=[trace0], layout={"title": "饼图", "template": "plotly_dark"})
fig
我们还可以设置环形饼图,就是在中间挖一个洞。
trace0 = go.Pie(labels=["古明地觉", "芙兰朵露", "古明地恋", "雾雨魔理沙", "紫妈"],values=[10, 25, 5, 35, 41],hole=0.7 # 从中心挖掉百分之70的部分
)
fig = go.Figure(data=[trace0], layout={"title": "饼图", "template": "plotly_dark"})
fig
我们还可以将饼图旋转一定角度,因为最上方的两个部分明显是垂直分隔的,另外也可以使某一个部分突出。
trace0 = go.Pie(labels=["古明地觉", "芙兰朵露", "古明地恋", "雾雨魔理沙", "紫妈"],values=[10, 25, 5, 35, 41],pull=[0, 0, 0, 0, 0.1], # 突出最后一个rotation=30, # 旋转30度marker={# 饼图也有marker参数,基本上所有图表都有marker参数# 散点图可以指定一种颜色来让所有的点都呈现相同的颜色# 但是饼图的每一部分应该是不同的颜色,这才符合饼图这种图形的意义# 所以我们要传入一个列表,而数据有五个,那么也要指定五种颜色# 但是即便不指定五个、或者颜色重复也是可以的# 如果颜色不够,plotly会帮你补充,颜色多了,会只选列表的前五个"colors": ["yellow", "green", "cyan", "pink", "blue"], # 并且这里不叫 color 了,而是叫 colors,因为多个颜色"line": {"width": 3,"color": "white", # 轮廓颜色}}
)
fig = go.Figure(data=[trace0], layout={"title": "饼图", "template": "plotly_dark"})
fig
作为最常见的图表之一,饼图大量应用于各行各业的报告中。比如一家公司有着 5 个业务,现在要看每个业务的营收占据总营收的比例,那么饼图再合适不过了。
气泡图
气泡图是一种多变量的统计图表,由笛卡尔坐标系和大小不一的圆组成,可以看作是散点图的变形。在气泡图中,每个气泡都代表着一组三个维度的数据,其中前两个维度决定了气泡在笛卡尔坐标系中的位置(即x,y轴上的值),另外一个则通过气泡的大小来表示。例如,x 轴表示产品销量,y 轴表示产品利润,气泡大小代表产品市场份额百分比。
当然,气泡图也可以容纳更多维的数据,例如用第4个变量决定气泡的颜色、透明度等。
# 生成100个点,x 是横坐标
x = np.linspace(0, 2, 50)
# y 是纵坐标
y = np.random.uniform(1, 8, 50)
# x 和 y 确定了气泡的位置
# 而数值的大小,则确定气泡的大小
trace0 = go.Scatter(x=x,y=y,mode="markers",marker={# size 还可以是一个数组# 手动指定每一个气泡的大小"size": 13 * y / np.min(y),# color 同样可以是数组# 值相同的气泡会使用同一种颜色"color": y,# 显示颜色条"showscale": True}
)
fig = go.Figure(data=[trace0], layout={"title": "气泡图", "template": "plotly_dark"})
fig
气泡图通常用于展示和比较数据之间的关系和分布,通过比较气泡位置和大小来分析数据维度之间的相关性。
气泡图也可以用于二维数据,即 y 轴和气泡大小使用同一维度的数据(y 轴和气泡大小的双视觉编码)。这种情况下,它实际上是作为柱状图的替代,用于对比分类数据,但比柱状图更加简洁美观。
旭日图
旭日图是一种表现层级数据的图表,它以父子层次结构来显示数据,并构成一个同心圆,因此又被成为称为多层饼图。离原点越近,数据的层级越高。换句话说,相邻的两层,内层是外层的父。
同时,一个父可以有多个子,即某一个环形又可以被分割成多个环形,环形的弧度由它在父层中所占比例来决定。若无占比关系,则处理成均分。
labels=["河南省", "信阳市", "郑州市", "洛阳市","山东省", "济南市", "青岛市", "烟台市","安徽省", "凤阳市", "合肥市", "芜湖市"]
parents=["", "河南省", "河南省", "河南省","", "山东省", "山东省", "山东省","", "安徽省", "安徽省", "安徽省"]
# values 为出生了多少万人(数字瞎编的)
values=[0, 30, 20, 10, 0, 50, 40, 10, 0, 20, 15, 15] trace0 = go.Sunburst(# 注意:每一个层级都要表示labels=labels,parents=parents,values=values
)
fig = go.Figure(data=[trace0], layout={"template": "plotly_dark"})
fig
市的父级是省,基于每个市的总人数可以算出省的人数,因此内层的圆对应每个省人数所占的比例。然后每个省根据比例再对应外层的圆的一片扇形区域,每个区域再根据比例划分为不同的子区域。
我们可以绘制很多层,目前有两层。但要注意的是,我们需要将每一层的对应关系都描述出来,比如市的父级是省,那么省的父级呢?这里我们假设省为最高级,那么它的父级就是空字符串,父级的值就写成 0。
然后我们改一下,我们将省的父级设置为中国。
labels=["河南省", "信阳市", "郑州市", "洛阳市","山东省", "济南市", "青岛市", "烟台市","安徽省", "凤阳市", "合肥市", "芜湖市"]
parents=["中国", "河南省", "河南省", "河南省","中国", "山东省", "山东省", "山东省","中国", "安徽省", "安徽省", "安徽省"]
values=[ 0, 30, 20, 10, 0, 50, 40, 10, 0, 20, 15, 15] trace0 = go.Sunburst(labels=labels,parents=parents,values=values
)
fig = go.Figure(data=[trace0], layout={"template": "plotly_dark"})
fig
然后再来关注 values 里面的几个 0,它们表示啥含义呢?
labels=["河南省", "信阳市", "郑州市", "洛阳市","山东省", "济南市", "青岛市", "烟台市","安徽省", "凤阳市", "合肥市", "芜湖市"]
parents=["中国", "河南省", "河南省", "河南省","中国", "山东省", "山东省", "山东省","中国", "安徽省", "安徽省", "安徽省"]
# 河南省、山东省分别还有 10 万人、20 万人没有统计
# 这些人可能来自于别的市
values=[ 10, 30, 20, 10, 20, 50, 40, 10, 0, 20, 15, 15] trace0 = go.Sunburst(labels=labels,parents=parents,values=values,
)
fig = go.Figure(data=[trace0], layout={"template": "plotly_dark"})
fig
旭日图的本质是树状关系,与树图是等价的,因此也被称为极坐标下的矩形树图。它可以在承载大量数据的同时,清晰的显示数据间的结构关系。在许多工具中,旭日图被赋予交互功能,方便读图者自行探索。如支持鼠标悬浮高亮、筛选、显示当前层级等等。
桑基图
桑基图 (Sankey Diagram),是一种表现流程的示意图,用于描述一组值到另一组值的流向,分支的宽度对应了数据流量的大小。
1869年,查尔斯米纳德绘制了1812年拿破仑征俄图,描绘了拿破仑大军在东进时,兵力是如何一步步削弱的,这也是目前公认较早的桑基图。
假设有上面这种数据,我们来看看如何将它绘制成桑基图。
trace0 = go.Sankey(node={"pad": 85,"thickness": 40,"line": {"color": "green", "width": 0.5},"label": ["女", "男", "狮子座", "双子座", "巨蟹座"],"color": ["#f8ade8", "#aff8e9", "#eaf863", "#6fe2f8", "#f8d47d"],},link={# source 和 target 中的元素都表示上面 label 的索引# 两者都是一一对应的,比如只看三个数组中的第一个元素# (0, 2, 5) 就表示 ("女", "狮子座", 5)"source": [0, 0, 0, 1, 1, 1],"target": [2, 3, 4, 2, 3, 4],"value": [5, 2, 3, 4, 2, 2],"color": ["rgba(172, 163, 105, 0.6)","rgba(196, 166, 255, 0.6)","rgba(252, 255, 212, 0.6)", "rgba(188, 255, 199, 0.6)", "rgba(255, 185, 217, 0.6)", "rgba(255, 182, 129, 0.6)"],},
)fig = go.Figure(data=[trace0],layout={"template": "plotly_dark"})
fig
绘制出来就长这个样子,我们来解释一下这张图。
Sankey 里面的参数 node 就表示这几个节点,键 "pad" 表示节点之间的距离,"thickness" 表示节点宽度,"line" 表示线条,"label" 表示节点的名称,"color" 表示节点的颜色。
参数 link 表示节点之间的连接情况,source 和 target 决定了流向,value 表示每个方向的流量,color 表示颜色。
当然我们这里只有一个流出和一个流入,但其实一个流入还可以作为下一个流入的流出,比如:
以上便是桑基图,适合表现分配情况、归类情况,以及变化和流动情况。
漏斗图
漏斗图,形如漏斗,用于单流程分析,在开始和结束之间由N个流程环节组成。漏斗图的起始总是100%,并在各个环节依次减少,每个环节用一个梯形来表示,整体形如漏斗。一般来说,所有梯形的高度应是一致的,这会有助人们辨别数值间的差异。
trace0 = go.Funnel(y = ["浏览量", "注册用户", "Vip", "SVip"],x = [20000, 15000, 8000, 2000],# 以前指定透明度的时候,是通过 marker 参数实现的# 在 marker 参数里面指定一个 "opacity"# 但有的图表不行,需要单独使用 opacity 参数opacity = 0.8,marker={# 漏斗每部分的颜色"color": ["deepskyblue", "tan", "teal", "silver"],# 轮廓"line": {"width": 4,"color": ["wheat", "wheat", "blue", "wheat"]}},# 漏斗之间的连接部分connector={"fillcolor": "pink","line": {"width": 2, "dash": "dot"}}
)
fig = go.Figure(data=[trace0],layout={"template": "plotly_dark"})
fig
同样的,你也可以绘制多个漏斗图,比如:
需要注意的是,漏斗图的各个环节,有逻辑上的顺序关系,同时漏斗图的所有环节的流量都应该使用同一个度量。漏斗图最适宜用来呈现业务流程的推进情况,如用户的转化情况、订单的处理情况、招聘的录用情况等。通过漏斗图,可以较直观的看出流程中各部分的占比、发现流程中的问题,进而做出决策。
雷达图
雷达图是一种显示多变量数据的图形方法,通常从同一中心点开始等角度间隔地射出三个以上的轴,每个轴代表一个定量变量,各轴上的点依次连接成线或几何图形。
雷达图可以用来在变量间进行对比,或者查看变量中有没有异常值。另外,多幅雷达图之间或者雷达图的多层数据线之间,还可以进行总体数值情况的对比。
trace0 = go.Scatterpolar(r=[3, 5, 3, 4, 5],theta=["速度", "力量", "技巧", "反应", "心态"],fill='toself'
)
fig = go.Figure(data=[trace0],layout={"template": "plotly_dark"})
fig
使用雷达图时,特征类别不能过多,并且特征之间要归一化,或者按照统一标准来标准化。
热力图
热力图是一种通过对色块着色来显示数据的统计图表,绘图时需指定颜色映射的规则。例如,较大的值由较深的颜色表示,较小的值由较浅的颜色表示;或者较大的值由较暖的颜色表示,较小的值由较冷的颜色表示等等。
trace0 = go.Heatmap(x=["星期一", "星期二", "星期三", "星期四", "星期五"],y=["早上", "中午", "晚上"],z=[ # 星期一到星期五的早上[12, 13, 17, 14, 15],# 星期一到星期五的中午[32, 33, 37, 34, 35],# 星期一到星期五的晚上[22, 23, 27, 24, 25],]
)
fig = go.Figure(data=[trace0],layout={"template": "plotly_dark"},
)
fig
我们还可以调整颜色:
trace0 = go.Heatmap(x=["星期一", "星期二", "星期三", "星期四", "星期五"],y=["早上", "中午", "晚上"],z=[[12, 13, 17, 14, 15],[32, 33, 37, 34, 35],[22, 23, 27, 24, 25],],colorscale = 'Viridis'
)
通过指定 colorscale,即可选择不同风格的色块,它的可选值如下:
从数据结构来划分,热力图需要 2 个分类字段和 1 个数值字段,分类字段确定 x、y 轴,将图表划分为规整的矩形块,而数值字段决定了矩形块的颜色。
热力图适合用于查看总体的情况、发现异常值、显示多个变量之间的差异,以及检测它们之间是否存在任何相关性。它的优势在于空间利用率高,可以容纳较为庞大的数据。热力图不仅有助于发现数据间的关系、找出极值,也常用于刻画数据的整体样貌,方便在数据集之间进行比较。
但热力图也有缺陷,尽管热力图能够容纳较多的数据,然而人们很难将其中的色块转换为精确的数字。因此当需要清楚知道数值的时候,可能需要额外的标注,plotly 支持这一点,当把鼠标放到色块上时,会显示相应的数值。
其它轨迹
我们上面已经介绍了工作中常用的图表,如果你还想知道更多的图表,可以查看 plotly 官网。
https://plotly.com/python/
画布属性 layout
重点来了,我们创建画布的时候用的是 Figure 类,它里面接收两个参数。第一个参数 data 负责接收轨迹,第二个参数 layout 负责调整画布属性。我们创建完画布之后,也可以通过 fig.data 或 fig.layout 拿到相应的轨迹和画布。
然后我们来看看画布都支持哪些属性。
title
标题,可以是一个字符串,也可以是一个字典。
layout = {"title": "这是标题"
}
# 或者传递一个字典,指定标题的同时
# 还可以指定字体、位置
layout = {"title": {"text": "这是标题",# 字体(一会单独说)"font": {"family": "STKaiti", "size": 30},# 水平位置,0.5 表示居中"x": 0.5}
}
xaxis_title、yaxis_title
x 轴名称和 y 轴名称,同样支持字符串和字典。
x = np.linspace(0, 2, 100)
y = np.random.randn(100)
trace0 = go.Scatter(x=x, y=y,mode="markers"
)
fig = go.Figure(data=[trace0],layout={"template": "plotly_dark","title": {"text": "我是散点图","font": {"family": "STKaiti", "size": 30},"x": 0.5},"xaxis_title": "我是 x 轴","yaxis_title": {"text": "我是 y 轴","font": {"family": "STKaiti", "size": 20, "color": "pink"}},}
)
fig
我们看到标题和 y 轴的字体变了,但 x 轴没有变。因为 x 轴传递的字符串,它只能表示标题内容,样式则使用默认的。
这些用表格罗列出来的,就不演示了,可以自己测试一下,看看这些参数的效果。
font
字体,和 titlefont 属性一致,通过传递一个字典来指定颜色、大小、种类等等。但 titlefont 的效果只会作用于标题,而这里的 font 会作用于所有地方,比如标题、x 轴和 y轴的名称、坐标刻度等等。
x = np.linspace(0, 2, 100)
y0 = np.random.randn(100) + 5
y1 = np.random.randn(100) - 5trace0 = go.Scatter(x=x,y=y0,name="one"
)
trace1 = go.Scatter(x=x,y=y1,name="two"
)
fig = go.Figure(data=[trace0, trace1],layout={"font": {"color": "cyan","size": 20,"family": "stcaiyun"},"template": "plotly_dark"}
)
fig
所有能看到字的部分,其字体的颜色、大小、类型都变了。
legend
设置展示轨迹名称时的属性。
x = np.linspace(0, 2, 100)
y0 = np.random.randn(100) + 5
y1 = np.random.randn(100) - 5trace0 = go.Scatter(x=x,y=y0,name="one"
)
trace1 = go.Scatter(x=x,y=y1,name="two"
)
fig = go.Figure(data=[trace0, trace1],layout={"legend": {"bgcolor": "orange","font": {"size": 30, "color": "blue"}},"template": "plotly_dark"}
)
fig
paper_bgcolor、plot_bgcolor
分别表示画布背景颜色、和图表背景颜色。
x = np.linspace(0, 2, 100)
y0 = np.random.randn(100) + 5
y1 = np.random.randn(100) - 5trace0 = go.Scatter(x=x,y=y0,name="one"
)
trace1 = go.Scatter(x=x,y=y1,name="two"
)
fig = go.Figure(data=[trace0, trace1],layout={"paper_bgcolor": "yellow","plot_bgcolor": "cyan"}
)
fig
images
我个人觉得最牛的一个功能,就是可以将图片作为背景。
from PIL import Image
x = np.linspace(0, 2, 100)
y0 = np.random.randn(100) + 5
y1 = np.random.randn(100) - 5
# 使用 PIL 打开图片
im = Image.open("../lemu.jpg")
trace0 = go.Scatter(x=x, y=y0, name="one")
trace1 = go.Scatter(x=x, y=y1, name="two")fig = go.Figure(data=[trace0, trace1],layout={"images": [{# 可以是PIL读取的Image对象,也可以是base64字符"source": im, "sizex": 1,"sizey": 1,"yanchor": "bottom","opacity": 0.3, }],"showlegend": False,"template": "plotly_dark"})
fig
以上就是画布 layout 支持的一些属性,但还有两个最重要没有说,就是 xaxis 和 yaxis。
xaxis 和 yaxis
xaxis 和 yaxis 非常重要,关键的东西我们留到最后,由于它们支持的属性是一样的,都是坐标轴,就一起说了。首先 xaxis 和 yaxis 同样是传递给 layout 的字典里面的 key,但关键的是它们对应的 value 也是一个字典,这个字典里面同样可以支持很多的属性。
color:
颜色,针对于当前坐标轴的刻度
x = np.linspace(0, 2, 100)
y = np.random.randn(100) + 5
trace0 = go.Scatter(x=x, y=y)fig = go.Figure(data=[trace0],layout={"xaxis": {"color": "red"},"template": "plotly_dark"})
fig
坐标轴刻度变成了红色。
gridcolor、gridwidth
网格线颜色和网格线宽度。
x = np.linspace(0, 2, 100)
y = np.random.randn(100) + 5
trace0 = go.Scatter(x=x, y=y)fig = go.Figure(data=[trace0],layout={"xaxis": {"gridwidth": 5, "gridcolor": "cyan"},"template": "plotly_dark",})
fig
linecolor、linewidth
坐标轴线条颜色与宽度
x = np.linspace(0, 2, 100)
y = np.random.randn(100) + 5
trace0 = go.Scatter(x=x, y=y)fig = go.Figure(data=[trace0],layout={"xaxis": {"linewidth": 5, "linecolor": "cyan"},"template": "plotly_dark",})
fig
rangeslider
范围滑块
x = np.linspace(0, 2, 100)
y = np.random.randn(100) + 5
trace0 = go.Scatter(x=x, y=y)fig = go.Figure(data=[trace0],layout={"xaxis": {"rangeslider": {"bgcolor": "cyan"}},"template": "plotly_dark"})
fig
下方有一个滑块,可以拖动,然后显示对应的图形部分。
showgrid
是否显示网格。
x = np.linspace(0, 2, 100)
y = np.random.randn(100) + 5
trace0 = go.Scatter(x=x, y=y)fig = go.Figure(data=[trace0],layout={"xaxis": {"showgrid": False},"yaxis": {"showgrid": False},"template": "plotly_dark"})
fig
side
设置坐标轴刻度位置
x = np.linspace(0, 2, 100)
y = np.random.randn(100) + 5
trace0 = go.Scatter(x=x, y=y)fig = go.Figure(data=[trace0],layout={"xaxis": {"side": "top"},"yaxis": {"side": "right"},"template": "plotly_dark"})
fig
showline:显示线条开关;
spikecolor:峰值数据颜色;
tickangle:刻度倾斜角度,之前用过的。
tickprefix、ticksuffix:刻度前缀和后缀
x = np.linspace(0, 2, 100)
y = np.random.randn(100) + 5
trace0 = go.Scatter(x=x, y=y)fig = go.Figure(data=[trace0],layout={"xaxis": {"tickprefix": "<<", "ticksuffix": ">>"},"template": "plotly_dark"})
fig
title、titlefont
设置坐标轴名称,类似于 layout 中的 title 和 titlefont,当然坐标轴名称还可以通过 xaxis_title 和 yaxis_title 指定。
x = np.linspace(0, 2, 100)
y = np.random.randn(100) + 5
trace0 = go.Scatter(x=x, y=y)fig = go.Figure(data=[trace0],layout={# title 也可以是一个字典# 在里面通过 "font" 指定字体"xaxis": {"title": "我是 x 轴","titlefont": {"color": "cyan", "size": 20}},"template": "plotly_dark"})
fig
type:刻度类型
可以是 '-', 'linear', 'log', 'date', 'category', 'multicategory' 之一。
zeroline:是否显示零线、就是值全部为 0 的线
对于 xaxis 就是 y 轴,对于 yaxis 就是 x轴。
zerolinecolor、zerolinewidth
零线的颜色和宽度
x = np.linspace(0, 2, 100)
y = np.random.randn(100)
trace0 = go.Scatter(x=x, y=y)fig = go.Figure(data=[trace0],layout={"xaxis": {"zerolinecolor": "cyan","zerolinewidth": 5},"yaxis": {"zerolinecolor": "pink","zerolinewidth": 5},"template": "plotly_dark"})
fig
以上就是两个坐标轴支持的属性设置,可以看到支持的属性非常多,而且都可以自定制,只不过很多我们都用不到。
小结
plotly 真的是一个绘图神器,支持大量的图表,当然我们只介绍了一部分,不过它们都是最常用的。在使用 plotly 的时候要记住轨迹和画布两个概念,我们说图表在 plotly 中就是轨迹,然后轨迹需要展示在画布上,而画布有很多属性,这些我们上面介绍的比较详细了,虽然很多都用不到。
然后就是 plotly 的图表保存问题,我这里都是直接在 jupyter 上生成,然后下载下来的。至于如何用 plotly 的 API 来生成图片,以及多图绘制,我们下一篇文章再说。
申明:本文中对一些图表所做的概念上的解析,来自于网站 "图之典" ,这是由一些爱好数据可视化的人创建的。里面对图表进行了概念上的详细描述,包括来源、适用场景、以及不适用场景等等,都做了详细的说明。如果你喜欢数据可视化,那么这个网站你一定不能错过。
写在最后,截止到 2022年11月20号,我的公众号关注人数突破 3000 了(喜大普奔)。尽管和其他号主相比,我连人家的二十分之一、三十分之一都不到,但也很开心了。其实我涨粉的主要途径也全靠同行转发,在其他号主转发我文章的时候吸一波粉。
总之,今后我也会努力地输出更多内容,认真写好每一篇文章。
猫哥注:推荐大家关注“古明地觉的编程教室”,宝藏公众号!(当然“Python猫”也是*^_^*)