可视化神器plotly(1):基础图表与画布、坐标轴

楔子

Python 在数据可视化方面有非常多的第三方库,比如 matplotlib、pyecharts、bokeh 等等,但个人最喜欢的莫过于 plotly 这个库。plotly 被称为数据可视化神器,首先它支持很多很多种图表,并且参数可以自由设置,最关键的是画出来的图非常漂亮。毕竟在数据可视化方面,图表的颜值也是很重要的。

很多人认为,pandas 和 plotly 这两个库所带来的影响力甚至超越了 Python 这门语言本身。

下面我们就来全方位介绍一下 plotly 的用法,不过首先要安装它,直接 pip install plotly 即可,非常方便。

图表绘制

对于学习数据可视化框架而言,本质上就是学习如何使用它来绘制图表。plotly 支持很多图表,包括 3D 动态图等等,这里我们不可能一个一个全部介绍完,但是常用的我们都会说。

import plotly.graph_objs as go
import numpy as np
import pandas as pd

上面几个模块基本上先导入就行,我们的代码会在 jupyter notebook 上运行,可以直接显示图表。至于图表如何保存成图片,我们后面说。

为了更好的理解 plotly,我们要记住两个核心概念,分别是 轨迹 画布。上面导入的 plotly.graph_objs 专门用来绘制图表,比如 go.Scatter 是散点图,在 plotly 中,图表被称为轨迹(trace)。而轨迹如果想显示,那么必须显示在画布上,当然一个画布可以显示多个轨迹。所以结论如下:我们根据自己的需要来创建轨迹,然后再创建一张画布,它是用于展示轨迹(图表)所不可或缺的舞台,最后将轨迹展示在画布上即可。

而我们也可以调整轨迹、画布的一些属性,让它们整体看起来更加的完美,也就是我们说的设置属性参数。plotly 里面图标、画布的属性参数非常多,但我们都会说。而且也正因为如此,图表中任何位置,只要我们不满意都可以进行设置。

下面就来介绍常见的一些图表。

散点图 - Scatter plot

散点图,又名点图、散布图、X-Y 图,英文 Scatter plot 或 Scatter gram。

散点图是将所有的数据以点的形式展现在平面直角坐标系上的统计图表,它至少需要两个不同变量,一个沿 x 轴绘制,另一个沿 y 轴绘制。每个点在 X、Y 轴上都有一个确定的位置。众多的散点叠加后,有助于展示数据集的 "整体景观",从而帮助我们分析两个变量之间的相关性,或找出趋势和规律。此外,我们还可以添加附加的变量,来给散点分组、着色、确定透明度等等。

random_x = np.linspace(0, 2, 100)  # 生成100个点
random_y0 = np.random.randn(100) + 5
random_y1 = np.random.randn(100)
random_y2 = np.random.randn(100) - 5

# 里面的参数我们后面会一个一个解释
# 先看一部分
trace0 = go.Scatter(
    x=random_x,  # x 轴的坐标
    y=random_y0,  # y 轴的坐标
    mode="markers",  # 纯散点绘图
    name="markers"  # 曲线名称
)

trace1 = go.Scatter(
    x=random_x,  
    y=random_y1, 
    mode="markers + lines",  # 散点 + 线段绘图
    name="markers + lines" 
)

trace2 = go.Scatter(
    x=random_x,  
    y=random_y2, 
    mode="lines",  # 线段绘图
    name="lines"  
)  # 我们看到比较神奇的地方,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:

random_x = np.linspace(0, 2, 100)  # 生成100个点
random_y0 = np.random.randn(100) + 5
random_y1 = np.random.randn(100) - 5

trace0 = go.Scatter(
    x=random_x,  
    y=random_y0, 
    mode="markers",  
    name="上方",  
    marker={
        "size": 8,  # 点的大小
        # 点的颜色,三原色加上透明度,以字符串形式
        "color": "rgba(102, 198, 147, 0.7)",  
    }
)

trace1 = go.Scatter(
    x=random_x,  
    y=random_y1, 
    mode="markers",  
    name="下方",  
    marker={
        "size": 8,  
        "color": "rgba(252, 108, 117, 1)",  
        # 除此之外,还可以设置点的轮廓
        "line": {
            "width": 10,  # 线条大小
            "color": "rgba(1, 170, 118, 0.3)"  # 线条的颜色
        }
    }
)

fig = go.Figure(data=[trace0, trace1])
fig

现在我们又接触到了一个参数 marker,这个是给点设置样式的。结构如下:

marker = {
    # 点的大小
    "size": n,
    # 颜色:可以是 rgba,也可以是 rgb;也可以是颜色的英文名,比如 green,yellow;也可以是一个 16 进制颜色码,比如:#FF6A04
    # 以及,它还可以是一个与对应轨迹的点的个数相同的数组,点对应的数组中不同的值、标记不同的颜色
    # 如果你知道机器学习里面的聚类的话,那么就当我在废话。
    "color": "rgba(n1, n2, n3, n4)",
    # 点的线条、轮廓
    "line": {"width": n, "color": "rgba(n1, n2, n3, n4)"}
    "showscale": True  # 其实还有一个showscale,默认为False,如果为True,那么会在右侧显示一个颜色条,可以自己去试一下
}

至于其它参数后面会涉及,这里先说一些常用的,但没涉及的话,也可以通过 help(go.Scatter) 查看。不过可能有人会好奇,那如果我想添加标题以及坐标轴名称该怎么办?显然是可以添加的,只不过不在轨迹里面添加,而是在画布里面添加。

random_x = np.linspace(0, 2, 100)  # 生成100个点
random_y0 = np.random.randn(100) + 5
random_y1 = np.random.randn(100) - 5

trace0 = go.Scatter(
    x=random_x,  
    y=random_y0, 
    mode="markers",  
    marker={
        "size": 8,  # 点的大小
        "color": "rgba(102, 198, 147, 0.7)",  # 点的颜色
    }
)

# 通过参数layout指定,其实应该是 layout=go.Layout(title="这是标题", ......)
# 但我们直接传入一个字典也是可以的,而且个人觉得传入字典要更加方便
fig = go.Figure(data=[trace0], layout={"title": "这是标题", 
                                       "xaxis_title": "这是x轴",
                                       "yaxis_title": "这是y轴",
                                       # x轴坐标倾斜60度
                                       "xaxis": {"tickangle": 60}
                                      })
fig

我们看到通过向画布传入 layout 即可给画布设置一些额外属性,比如:

  • title:标题
  • xaxis_title:x轴标题
  • yaxis_title:y轴标题
  • xaxis:坐标轴属性,可以传入一个字典。比如:tickangle 就是将坐标倾斜,尤其是日期比较长,那么我们就可以通过倾斜方式来避免堆叠在一起。角度大于0顺时针,小于0逆时针。同理还有 yaxis,当然这个字典里面还可以指定其它属性,但是我们不一定都要用到,而是会用到什么写什么,因为它们内部支持的属性不仅仅只针对一种图表。

当然除了上面这些,我们再说几个常用的:

  • width:画布的宽度
  • height:画布的高度
  • template:画布风格,有以下几种选择:ggplot2,seaborn,simple_white,plotly,plotly_white,plotly_dark,presentation,xgridoff,ygridoff,gridon,none。默认是plotly
random_x = np.linspace(0, 2, 100)  # 生成100个点
random_y0 = np.random.randn(100) + 5
random_y1 = np.random.randn(100) - 5

trace0 = go.Scatter(
    x=random_x,  
    y=random_y0, 
    mode="markers",  
    marker={
        "size": 8,  # 点的大小
        "color": "rgba(102, 198, 147, 0.7)",  # 点的颜色
    }
)

fig = go.Figure(data=[trace0], layout={"template": "plotly_dark"})
fig

layout 还支持其它哪些参数,可以通过 help(go.Layout) 查看,当然我们后面也会介绍。

散点图存在意义

散点图常被用于分析变量之间的相关性。如果两个变量的散点看上去都在一条直线附近波动,则称变量之间是线性相关的;如果所有点看上去都在某条曲线(非直线)附近波动,则称此相关为非线形相关的;如果所有点在图中没有显示任何关系,则称变量间是不相关的。

如果散点图呈现出一个集中的大致趋势,这种趋势通常可以用一条光滑的曲线来近似,这样近似的过程被称为曲线拟合,而这条曲线则被称为最佳拟合线或趋势线。如果图中存在个别远离集中区域的数据点,这样的点被称为离群点或异常值。

不过,分析时需注意,相关性并不等于因果关系。例如,有统计表明,冰棒卖得越多,游泳死亡人数就越高。若是在散点图上,二者应当呈正相关,但实际上,两种现象都是由天气炎热引起的,并无直接因果关系。

折线图 - Line Chart

折线图是是一个由笛卡尔坐标系(直角坐标系)、一些点、以及线段组成的统计图表,常用来表示数值随连续时间间隔或有序类别的变化。在折线图中,x 轴通常用作连续时间间隔或有序类别(比如阶段1、阶段2、阶段3)。y 轴用于量化的数据,如果为负值则绘制于 y 轴下方。连线用于连接两个相邻的数据点。

从数据上来说,折线图需要一个"连续时间字段或一个分类字段" 和 "至少一个连续数据字段"。

# sick 是数据集,这个不需要关心,你完全可以使用其他的数据集代替
trace0 = go.Scatter(
    x=sick["date"],  
    y=sick["number"], 
)

fig = go.Figure(data=[trace0], layout={"template": "plotly_dark",
                                       "title": "当前的患者人数"})
fig

虽然名叫 Scatter,但是默认绘制的是折线图,还记得怎么绘制散点图吗?对的,指定 mode="markers" 即可。

如果我想给折线添加样式肿么办?我们给散点图添加样式通过参数 marker 指定,而给折线添加样式也有对应的参数。

trace0 = go.Scatter(
    x=sick["date"],  
    y=sick["number"],
    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=sick["date"],  
    y=sick["number"],
    line={
        "width": 3,  
        "color": "rgba(255, 30, 186, 1)",  
        "dash": "dot"  # 指定为虚线
    }
)

fig = go.Figure(data=[trace0], layout={"template": "plotly_dark",
                                       "title": "当前的患者人数"})
fig

"dash" 表示指定线条的种类:

  • dot:由点组成的虚线
  • dash:由短线组成的虚线
  • dashdot:由点和短线组成的虚线

有时候,我们的数据并不是连续的,中间可能会出现断层,这个时候我们可以指定 connectgaps 参数,举个栗子:

x = np.array([1, 2, np.nan, 4, 5]) 
y1 = x * 3 + 1
y2 = x * 3 + 2

trace0 = go.Scatter(
    x=x,  
    y=y1,
)

trace1 = go.Scatter(
    x=x,
    y=y2,
    connectgaps=True
)

fig = go.Figure(data=[trace0, trace1])
fig

当数据出现了空值的时候,那么折线默认是会出现断层的,而指定 connectgaps=True ,那么会将缺失值两端的点直接相连,使得直线是完整的。

折线图存在意义

折线图用于分析事物随时间或有序类别而变化的趋势,如果有多组数据,则用于分析多组数据随时间变化或有序类别的相互作用和影响。折线的方向表示正 / 负变化,折线的斜率表示变化的程度。

同时绘制散点图和折线图

既然学习了散点图和折线图,那么我们可以将这两者绘制在一起。

random_x = np.linspace(0, 2, 100)  
random_y0 = np.random.randn(100) + 5
random_y1 = np.random.randn(100)
random_y2 = np.random.randn(100) - 5

trace0 = go.Scatter(
    x=random_x,  
    y=random_y0,  
    mode="markers",  
    name="markers",  
    marker={
        "size": 8,  
        "color": "rgba(109, 226, 46, 1)",
        "line": {
            "width": 10,  
            "color": "rgba(1, 170, 118, 0.3)"  
        }
    }
)

trace1 = go.Scatter(
    x=random_x,
    y=random_y1,
    mode="markers + lines",  # 散点+线段绘图
    name="markers + lines",
    marker={
        "size": 8,
        "color": "rgba(239, 12, 111, 0.7)",
    }
)

trace2 = go.Scatter(
    x=random_x,
    y=random_y2,
    mode="lines",  # 线段绘图
    name="lines"
)

fig = go.Figure(data=[trace0, trace1, trace2],
                layout={"title": {"text": "散点图", "font": {"family": "STKaiti", "size": 30}, "x": 0.5},
                        "xaxis": {"title": "x轴",
                                  "titlefont": {"family": "STKaiti", "size": 30},
                                  "tickangle": -60, "nticks": 20},
                        "yaxis": {"title": "y轴",
                                  "titlefont": {"family": "STKaiti", "size": 30}},
                        "font": {"size": 20},
                        "template": "plotly_dark"
                        })

里面的 layout 参数后面慢慢解释,相信目前你也能猜到是干什么的。

柱状图 - Bar Chart

柱状图,是一种使用矩形条,对不同类别进行数值比较的统计图表。最基础的柱形图,需要一个分类变量和一个数值变量。在柱状图上,分类变量的每个实体都被表示为一个矩形(通俗讲就是 "柱子"),而数值则决定了柱子的高度。

与 "柱状图" 相近的概念还有 "条形图",二者在实际使用中可能产生不同理解。例如,在 AntV 的分类中,条形图是柱状图的子集,专指 "横向的柱状图"。而在 Excel2016 的图表功能中,柱状图专指纵向、条形图专指横向。

trace0 = go.Bar(
    x=["古明地觉", "芙兰朵露", "古明地恋", "椎名真白"],  
    y=[17, 400, 16, 17],
)


fig = go.Figure(data=[trace0])
fig

这是最简单的柱状图,很明显,即便我们什么参数都不加,光默认样式就把 matplotlib 给秒杀了。所以先不管 plotly 这个框架的功能如何(很强),光图表的颜值就足以让我们去学习它。

当然我们也可以绘制多个柱状图:

trace0 = go.Bar(
    x=["古明地觉", "芙兰朵露", "古明地恋", "椎名真白"],  
    y=[17, 400, 16, 17],
    name="bar1"
)

trace1 = go.Bar(
    x=["古明地觉", "芙兰朵露", "古明地恋", "椎名真白"],  
    y=[86, 90, 96, 87],
    name="bar2"
)

fig = go.Figure(data=[trace0, trace1])
fig

除此之外我们还可以指定层叠柱状图:

trace0 = go.Bar(
    x=["古明地觉", "芙兰朵露", "古明地恋", "椎名真白"],  
    y=[17, 400, 16, 17],
    name="bar1"
)

trace1 = go.Bar(
    x=["古明地觉", "芙兰朵露", "古明地恋", "椎名真白"],  
    y=[86, 90, 96, 87],
    name="bar2"
)

# 指定 layout 里面的 barmode 为 stack,会将柱状图堆叠在一起
# 我们看到又多了一个 barmode,所以我们说可调节的属性非常多,但是不同的属性适用于不同的轨迹,我们需要哪个就设置哪个即可。
fig = go.Figure(data=[trace0, trace1], layout={"barmode": "stack"})
fig

我们看到层叠柱状图是在画布里面设置的,因为将两个轨迹怎么组合是通过画布来设置的,至于 Bar 里面的参数是设置轨迹本身, 比如我们还可以调节颜色什么的,通过参数 marker 指定:

trace0 = go.Bar(
    x=["古明地觉", "芙兰朵露", "古明地恋", "椎名真白"],  
    y=[17, 400, 16, 17],
    name="bar1",
    marker={
        # 除了rgba,还可以通过颜色的名称指定
        "color": "pink",
        "opacity": 1,  # 指定透明度,当然这里指定透明度的方式,同样适用于Scatter,这里为 1 相当于不指定
        "line": {
            "width": 3,  # 轮廓的宽度
            "color": "cyan",  # 轮廓的颜色
        },
    }
)

trace1 = go.Bar(
    x=["古明地觉", "芙兰朵露", "古明地恋", "椎名真白"],  
    y=[86, 90, 96, 87],
    name="bar2",
    marker={
        "color": "green"
    }
)

fig = go.Figure(data=[trace0, trace1], layout={"template": "plotly_dark"})
fig

我们看到轨迹的颜色什么的也是通过 marker 指定的,里面也可以设置轮廓,和 Scatter 是类似的。

柱状图存在意义

作为人们最常用的图表之一,柱状图也衍生出多种多样的图表形式。例如,将多个并列的类别聚类、形成一组,再在组与组之间进行比较,这种图表叫做 "分组柱状图" 或 "簇状柱形图"。将类别拆分称多个子类别,形成堆叠柱状图。再比如将柱形图和折线图结合起来,共同绘制在一张图上,俗称 "双轴图" 等等。

柱状图最适合对分类的数据进行比较。尤其是当数值比较接近时,由于人眼对于高度的感知优于其他视觉元素(如面积、角度等),因此,使用柱状图更加合适。

水平柱状图

水平柱状图和柱状图类似,也是通过 go.Bar 来绘制,只不过需要多加一个参数。

trace0 = go.Bar(
    # 方向变了,所以 x 轴和 y 轴的数据也要调换位置
    y=["古明地觉", "芙兰朵露", "古明地恋", "椎名真白"],  
    x=[17, 400, 16, 17],
    name="bar1",
    marker={
        "color": "pink",
    },
    # 指定为水平方向即可
    orientation="h"
)

trace1 = go.Bar(
    y=["古明地觉", "芙兰朵露", "古明地恋", "椎名真白"],  
    x=[86, 90, 96, 87],
    name="bar2",
    marker={
        "color": "green"
    },
    orientation="h"
)

fig = go.Figure(data=[trace0, trace1], layout={"template": "plotly_dark"})
fig

柱状图有多高取决于 y 轴,而水平柱状图有多长就取决于 x 轴了,所以此时 x 轴就是代表数值的那一方。至于设置颜色、轮廓等参数,和之前的柱状图一样。

甘特图

甘特图又称横道图,是用来显示项目进度等与时间相关的数据的。直接看个栗子就很好理解了:

# 创建甘特图,使用plotly.figure_factory,然后调用内部的create_gantt即可
import plotly.figure_factory as ff

tasks = [
    {"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="这是甘特图")
# 当然此时调整画布属性的参数就不能通过layout={}指定了,而是直接在里面传递
# 因为它创建轨迹的时候,直接就把画布顺便给你创建了,所以返回的就是一个画布,我们可以直接显示
fig

里面也可以直接传递一个DataFrame:

import plotly.figure_factory as ff
# 除了Task、Start、Finish,之外还可以有一个Complete,来表示任务完成的进度。
# 0表示无进展,100表示全部完成
df = pd.DataFrame({"Task": ["任务A", "任务B", "任务C", "任务D","任务E"],
                   "Start": ["2018-1-1", "2018-2-1", "2018-2-1", "2018-4-1", "2018-8-1"],
                   "Finish": ["2018-3-1", "2018-5-1", "2018-6-1", "2018-8-1", "2019-1-1"],
                   "Complete": [10, 80, 80, 76, 100]
                  })

# 但是有了进度还不行,我们需要设置index_col="Complete",这样进度相同的条就会显示一样的颜色
# 并且我们还可以通过show_colorbar=True,来将颜色进行标记,到底哪种颜色对应的进度高、哪种对应的进度低
fig = ff.create_gantt(df, index_col="Complete", show_colorbar=True)
# 另外虽说自动帮我们创建了画布,但我们还是希望自己指定画布。所以我们就需要将自动创建的画布上面的轨迹放在我们自己创建的画布上
traces = fig.data  # 这里就拿到了画布上的轨迹,因为画布上可以有多条轨迹,所以得到的是一个元组
# 我们直接根据traces创建即可,data接收列表或者元组都是一样的
fig = go.Figure(data=traces, 
                layout={"template": "plotly_dark", 
                        "title": "这是我们自己创建的画布上面的甘特图"})  # 此时就可以像之前一样指定属性了
fig

我们看到 Complete 对应值相同的任务,颜色是一致的。并且右侧的条表示颜色对应的任务进度,这是通过 show_colorbar=True 显示的。并且此时的数据我们用到了四个参数:Task、Start、Finish、Complete,前三个是固定的必须叫这几个名字,至于 Complete 就没有要求了,叫其他的名字也是可以的,但是最好要见名知意,然后设置为 index_col,指定 show_colorbar=True。

并且我们除了通过数值来表示进度,还可以使用文字。

df = pd.DataFrame({"Task": ["任务A", "任务B", "任务C", "任务D","任务E"],
                   "Start": ["2018-1-1", "2018-2-1", "2018-2-1", "2018-4-1", "2018-8-1"],
                   "Finish": ["2018-3-1", "2018-5-1", "2018-6-1", "2018-8-1", "2019-1-1"],
                   "Complete": ["干了一小半", "干了一半", "干了一半", "干了一大半", "全干完了"]
                  })

# 为不同的进度赋予不同的颜色,通过rgb指定
colors = {
    "干了一小半": "rgb(125, 135, 144)",
    "干了一半": "rgb(187, 20, 168)",
    "干了一大半": "rgb(14, 199, 250)",
    "全干完了": "rgb(250, 1, 144)"
}

# 通过colors参数指定颜色,如果不指定会默认会为不同的进度附上不同的颜色
fig = ff.create_gantt(df, index_col="Complete", show_colorbar=True, colors=colors)  
traces = fig.data 
fig = go.Figure(data=traces, 
                layout={"template": "plotly_dark", 
                        "title": "这是我们自己创建的画布上面的甘特图"})  
fig

面积图 - Area Chart

面积图,或称区域图,是一种随有序变量的变化,反映数值变化的统计图表,原理与折线图相似。而面积图的特点在于,折线与自变量坐标轴之间的区域,会由颜色或者纹理填充。

绘制面积图依旧使用 Scatter,所以 Scatter 可以用来绘制三种图形了,我们说里面参数非常多,因为它可以针对不同的图表。所以这些参数没必要在刚学的 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,这个 fill 就是用来绘制面积图的,当然在学习或者绘制散点图的时候,这个 fill 无需鸟它。我们将 fill 指定为 "tozeroy" 表示绘制和 x 轴围城的部分,很好理解,"tozeroy"不是就是"to"(到) "zeroy" (y为0的地方)嘛,所以是 x 轴。当然除了,"tozeroy",还可以为 "none",表示不围成面积,当然此时直接不指定 fill 就行了。除了这些,还可以指定为其它的一些值:

  • none:不绘制面积
  • tozeroy:与x轴围成的面积
  • tozerox:与y轴围成的面积
  • toself:与自身围成的面积。
x = np.linspace(0, 2, 100)
y0 = np.cos(x)
y1 = np.sin(x)

trace0 = go.Scatter(
    x=x,
    y=y0,
    name="cos",
    mode="none",  
    fill="tozerox"  # 和y轴围成的面积
)
trace1 = go.Scatter(
    x=x,
    y=y1,
    name="sin",
    mode="none",  
    fill="toself"  # 和自身围成的面积
)
fig = go.Figure(data=[trace0, trace1], layout={"title": "面积", "template": "plotly_dark"})
fig

  • tonext:和下一条线围成的面积
  • tonexty:和下一条线在x轴上围成的面积
  • tonextx:和下一条线在y轴上围成的面积

上面三个属性则是专门用来绘制两条线围成的面积,只需要第一条轨迹不填充,第二条轨迹填充即可。

x = np.linspace(0, 2, 100)
y0 = np.cos(x)
y1 = np.sin(x)

trace0 = go.Scatter(
    x=x,
    y=y0,
    name="cos"  # 不指定 fill,也就是不填充
)
trace1 = go.Scatter(
    x=x,
    y=y1,
    name="sin",
    mode="none",
    fill="tonexty"  # 和另一条轨迹y轴围成的面积
)
fig = go.Figure(data=[trace0, trace1], layout={"title": "面积", "template": "plotly_dark"})
fig

我们变得到了两条线、或者轨迹在 x 轴上围成的面积图,同理 tonextx 和 tonext可以自己绘制一下。但是一般我们使用的是 tonexty,即、绘制两条线在 x 轴围成的面积图。

x = np.array([1, 3, 2, 3, 5])
y0 = x + 1
y1 = x + 2
y2 = x + 3
y3 = x + 4

trace0 = 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

面积图存在意义

面积图也可用于多个系列数据的比较。这时,面积图的外观看上去类似层叠的山脉,在错落有致的外形下表达数据的总量和趋势。相较于折线图,面积图不仅可以清晰地反映出数据的趋势变化,也能够强调不同类别的数据间的差距对比。但它的劣势在于,填充会让形状互相遮盖,反而看不清变化。一种解决方法,是使用有透明度的颜色,来 "让" 出覆盖区域。

直方图 - Histogram

直方图,又称质量分布图,用于表示数据的分布情况,是一种常见的统计图表。 一般用横轴表示数据区间,纵轴表示分布情况,柱子越高,则落在该区间的数量越大。根据数据分布状况不同,直方图展示的数据有不同的模式,包括对称单峰、偏左单峰、偏右单峰、双峰、多峰以及对称多峰。

构建直方图,首先要确定 "组距"、对数值的范围进行分区,通俗的说即是划定有几根柱子(例如 0-100 分,每隔20分划一个区间,共5个区间)。接着,对落在每个区间的数值进行频次计算(如落在 80-100 分的 10 人,60-80 分的 20 人,以此类推)。 最后,绘制矩形,高度由频数决定。

直方图与柱状图看似相像,实则完全不同。前者反映数据分布情况,后者则不具备此功能,只能对数值进行比较。从数据结构来说,柱状图需要 1 个分类变量,是离散的(如一班、二班、三班),因此柱子间有空隙。但直方图的数据均为连续的数值变量(如成绩),因此柱子间是没有空隙的。

而直方图种类比较多,我们一个一个说。

基本直方图

x = np.random.randint(1, 20, 1000)  # 生成100个1到20的随机数

trace0 = go.Histogram(
    x=x,
)

fig = go.Figure(data=[trace0], layout={"title": "直方图", 
                                       "template": "plotly_dark",
                                      })
fig

但是我们看到可读有些不完美,就是跨越的太大了,从0直接到5了,所以我们可以指定 xaxis 属性。

x = np.random.randint(1, 20, 1000)  # 生成100个1到20的随机数

trace0 = go.Histogram(
    x=x,
    histnorm="probability"  # 指定histnorm为probability,那么y轴将不再显示数量而是显示比例
)

fig = go.Figure(data=[trace0], layout={"title": "直方图", 
                                       "template": "plotly_dark",
                                       # range表示坐标范围,dtick表示相邻坐标之间的差值,这里是 2,所以就是 0 2 4 6...
                                       "xaxis": {"dtick": 2, "range": [1, 20]}  
                                      })
fig

我们看到 y 轴的数量变成了比例,当然最重要的就是我们知道了如何调整坐标范围与坐标刻度了,当然我们后面会对画布、坐标什么的做一个全面的总结,里面可以指定哪些属性、都具有哪些效果之类的,当然还是只会介绍我个人认为可能会用到的。如果没有你想要的,可以去官网查看,抱歉啦。

重叠直方图

忘记说了,其实所有的图表对应的类里面,很多参数都是相同的,比如 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",
                                              })
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={"enabled": True}  # 指定 cumulative 即可
)

fig = go.Figure(data=[trace0], layout={"title": "直方图",
                                       "template": "plotly_dark",
                                       })
fig

饼图

饼图,或称饼状图,是一个划分为几个扇形的圆形统计图表。在饼图中,每个扇形的弧长(以及圆心角和面积)大小,表示该种类占总体的比例,且这些扇形合在一起刚好是一个完全的圆形。

饼图最显著的功能在于表现 "占比"。习惯上,人们也用饼图来比较扇形的大小,从而获得对数据的认知。但是,由于人类对 "角度" 的感知力并不如 "长度",在需要准确的表达数值(尤其是当数值接近、或数值很多)时,饼图常常不能胜任,建议用柱状图代替。

从数据来看,饼图一般需要一个分类数据字段、一个连续数据字段。值得注意的是,分类字段的数据,在图表使用的语境下,应当构成一个整体(例如一班、二班、三班,构成了整个高一年级),而不能是独立、无关的。

使用时,须确认各个扇形的数据加起来等于 100%;避免扇区超过 5 个,尽量让图表简洁明了;注意扇形的排布顺序,一般情况下,将最大的扇形放在 12 点钟方向,接下来按面积依次排列;最后,正确使用颜色,既区分出需要强调的扇形,又不致于让人眼花缭乱。

# 饼图就很简单了,使用 Pie 这个类,Scatter 里面的 x 和 y 对应 Pie 里面 labels 和 values
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={
        # 显然color就是饼图的颜色了,但是它和散点图不一样
        # 散点图可以指定一种颜色来让所有的点都呈现相同的颜色,但是饼图的每一部分应该是不同的颜色,这才符合饼图这种图形的意义
        # 所以我们要传入一个列表,因为我们数据有五个,那么我们要指定五种颜色。但是即便不指定五个、或者颜色重复也是可以的。
        # 如果颜色不够,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

不是总结的总结

到目前为止,我们介绍了常用的几个图表,但 plotly 里面还有很多其它图表,可以去官网查看(后续可能会继续在这里补充)。重点就是 plotly 里面的轨迹和画布两个概念,我们使用 Scatter、Pie、Bar 等等得到的都是轨迹,然后还需要创建画布。通过将轨迹写在画布上,然后再直接显示画布即可。至于轨迹,不同的轨迹的参数不同,plotly 最让人想批评的就是它的使用太复杂,其实也不算复杂,说白了就是参数太多了,但是功能强大也是没办法的事情。不过虽然如此,可不同的轨迹有很多参数都是类似的,首先两个坐标轴的数据就不用说了。其次就是 name 和 marker,name 是给轨迹起名字,如果画布上有多条轨迹能够让我们区分。还有就是 marker,我们说 marker 结构如下:

{
    "color或者colors": "表示轨迹本身的颜色",
    "opacity": "透明度",
    "line": {
        "width": "线条或者轮廓的宽度",
        "color": "线条或者轮廓的颜色"
    }
}
# 其中color的表现形式有很多,可以是颜色的英文名,比如:"yellow"
# 也可以是rgb三原色,比如:"rgb(255, 255, 255)"
# 还可以是rgba,比如:"rgba(255, 255, 255, 0.5)",相当于在 rgb 基础上加上了透明度
# 也可以是 16 进制颜色码
# 另外 rgba 有一个好处就是,"line": {}里面是不支持"opacity"的,如果想设置轮廓的透明度,可以通过rgba设置颜色的同时指定透明度
# 但是对于 marker={} 里面是可以支持指定 key 为 "opacity" 来设置透明度的,当然通过 rgba 也可以

# 当然marker里面还可以指定其它的 key,比如"size",这个是适用于散点图的,用来指定点的大小,但是对于饼图就不行
# 而 "color或者colors"、"opacity"、"line" 对于所有图形都是适用的

而我们说通过 marker 就可以绘制出漂亮的图形了,其实 plotly 的图表本身就很漂亮。至于轨迹的其它参数就就取决于轨迹本身的类型了,其实只要把绘制每个轨迹的关键参数记住,也就那么几个,再搭配 marker,我们就能绘制出漂亮的图表,因为 plotly 默认绘制的图表就很漂亮。

至于像标题、坐标轴名称什么的,我们是通过画布来指定的。我们使用 go.Figure 来创建画布,我们实例化 Figure 这个类的时候,里面传递了两个参数,一个是data、一个是layout。data 很好理解,就是多个轨迹组成的列表,即便一个轨迹也要放在列表里面传进去。而 layout 则就是我们关心的、设置画布的属性了,而layout我们目前是通过字典传递的。

# 我们目前是这样传递layout的
fig = go.Figure(data=[trace0, ...], layout={"title": "xxx"})

# 其实layout,我们可以实例化一个类
layout = go.Layout(title="xxx")  # 等价于layout = {"title": "xxx"}
fig = go.Figure(data=[trace0, ...], layout=layout)

# 字典传递比较方便,所以我个人更倾向使用字典,但是具体使用哪种,看你个人。

而 layout 我们目前都设置了哪些属性呢?

layout = {
    "title": "图表的标题",
    "xaxis_title": "x轴的名称",
    "yaxis_title": "y轴的名称",
    "template": "图表的风格,支持哪几种可以回头翻一翻,我个人非常喜欢plotly_dark",
    "xaxis": {"range": "坐标轴的范围", "dtick": "刻度大小", "tickangle": "坐标旋转角度"}  # xaxis里面支持的属性也非常多,后面我们会总结
    "yaxis": "和xaxis同理",
    "barmode": "设置柱状图和直方图的堆叠属性的"
}
# layout 里面支持的属性也非常多,我们后面也会说。
# 但是有些属性并不是通用的,比如 barmode,当绘制散点图的时候,它就不需要了
# 不过 layout 的特点就在于即使你传递了也不会报错,比如明明绘制的散点图,但是在画布上却指定了 "barmode"
# 因为画布它是针对所有轨迹的,所以即使画布上有该轨迹不需要的参数也无影响。
# 然而如果是轨迹则不行了,比如在柱状图的 marker={} 里面指定了散点图才需要的 "size" 就会报错了,因为柱状图不需要 "size"

尽管 layout 里面的可设置的属性比较多,但是有的是通用的,比如:title、xaxis_title、yaxis_title、template、xaxis、yaxis,但有些则是针对特定轨迹的,比如 barmode。

其它轨迹

都说了,上面不是总结了。我们再来说一下其它的轨迹(包含上面已经介绍的轨迹),至于长什么样子,可以自己尝试。这些都可以通过 plotly.graph_objs 进行调用,里面的参数可以通过 help(go.xxx) 查看。

2d平面轨迹

  • AngularAxis:极坐标图表
  • Area:区域图
  • Bar:柱状图
  • Barpolar:极坐标柱状图
  • Box:盒形图,又称箱型图
  • Candlestick和Ohlc:金融股票行业常用的k线图和OHLC曲线图
  • ColorBar:彩条图
  • Contour:轮廓图
  • Choropleth:等值线图
  • Line:曲线图
  • Heatmap:热点图
  • Histogram:直方图
  • Histogram2d:2d平面直方图
  • Histogram2dContour:2d轮廓直方图
  • Scatter:散点图、折线图

3d立体轨迹

  • Scatter3d:3d立体散点图、折线图
  • Surface:表面图
  • Mesh3d:3d立体网格图
  • Pointcloud:云点图

maps地图

  • Scattergeo:基于地图模式的散点图、折线图
  • Choropleth:立体等值线图
  • Scattermapbox:基于地图的散点图

当然 plotly.graph_objs 里面支持的轨迹其实仍然不止上面说的这些,有兴趣可以自己查看。

画布属性 - layout

重点来了,我们说 layout 是给画布设置属性的,可以通过一个字典传递,那么通过字典都能传递哪些属性呢?下面来看一看,还是介绍我个人认为会使用的,有些参数会简单说明,有些参数我们以折线图举例说明。

title:标题,可以是一个字符串 {"title": "标题内容"},表示标题内容;也可以是一个字典,{"title": {"text": "标题内容", "font": {"family": "STKaiti", "size": 30}, "x": 0.5}},其中 text 是标题内容、font 是字体属性后面单独说、x 是水平位置。

xaxis_title:x轴名称

yaxis_title:y轴名称

template:背景风格

template:支持 ggplot2,seaborn,simple_white,plotly,plotly_white,plotly_dark,presentation,xgridoff,ygridoff,gridon,none,默认是plotly。

titlefont:标题字体,一个字典,里面可以指定字体类型、颜色和大小等等。当然我们上面介绍了 title,如果接收一个字典,那么也可以在对应的字典里面指定字体属性。

autosize:自动调整大小(不常用)

bargap:柱状图间距

bargroupgap:柱状图组间距,如果绘制多个柱状图的话

barmode:柱状图或直方图模式

boxgap:箱型图间距

boxgroupgap:箱型图组间距

boxmode:箱型图模式

calendar:日历

calendar:可以是以下字符串 gregorian,chinese,coptic,discworld,ethiopian,hebrew,islamic,julian,mayan,nanakshahi,nepali,persian,jalali,taiwan,thai,ummalqura 之一。

direction:方向

calendar:可以是 clockwise(顺时针)、counterclockwise(逆时针)之一。

dragmode:图形拽动模式

可以是 zoom,pan,select,lasso,orbit,turntable,False(布尔)之一

font:字体,和 titlefont 属性一致,通过传递一个字典来指定颜色、大小、种类等等。但 titlefont 的效果只会作用于标题,而这里的 font 会作用于所有地方,比如标题、x 轴和 y轴的名称、坐标刻度等等。

传入一个字典:{"color": "颜色", "family": "字体类型", "size": "字体大小"},当然 颜色、字体类型、大小 不一定要全部指定

random_x = np.linspace(0, 2, 100)
random_y0 = np.random.randn(100) + 5
random_y1 = np.random.randn(100) - 5

trace0 = go.Scatter(
    x=random_x,  
    y=random_y0, 
)

trace1 = go.Scatter(
    x=random_x,  
    y=random_y1, 
)

fig = go.Figure(data=[trace0, trace1], layout={"font": {"color": "green", "size": 20, "family": "stcaiyun"}})
fig

我们看到所有能看到字的部分,其字体的颜色、大小、类型都变了。

geo:地理参数

height:图表高度,默认 450

width:图表宽度,默认 700

legend:设置右上角那个东东的属性

random_x = np.linspace(0, 2, 100)  # 生成100个点
random_y0 = np.random.randn(100) + 5
random_y1 = np.random.randn(100) - 5

trace0 = go.Scatter(
    x=random_x,  
    y=random_y0, 
)

trace1 = go.Scatter(
    x=random_x,  
    y=random_y1, 
)

fig = go.Figure(data=[trace0, trace1], layout={"legend": {"bgcolor": "pink"}})
fig

我们看到变成了粉色。

hovermode:鼠标指针悬停时显示的数据

这个在 jupyter notebook 中使用,选项为: "x","y","closest",False 之一

margin:图表边缘间距

orientation:方向,针对柱状图。默认是竖向,横向需要指定此参数为 "h"

paper_bgcolor:画布背景颜色

random_x = np.linspace(0, 2, 100)  # 生成100个点
random_y0 = np.random.randn(100) + 5
random_y1 = np.random.randn(100) - 5

trace0 = go.Scatter(
    x=random_x,  
    y=random_y0, 
)

trace1 = go.Scatter(
    x=random_x,  
    y=random_y1, 
)


fig = go.Figure(data=[trace0, trace1], layout={"paper_bgcolor": "yellow"})
fig 

plot_bgcolor:图形背景颜色,坐标轴围成的区域

random_x = np.linspace(0, 2, 100)  # 生成100个点
random_y0 = np.random.randn(100) + 5
random_y1 = np.random.randn(100) - 5

trace0 = go.Scatter(
    x=random_x,  
    y=random_y0, 
)

trace1 = go.Scatter(
    x=random_x,  
    y=random_y1, 
)

fig = go.Figure(data=[trace0, trace1], layout={"plot_bgcolor": "#ACFFCF"})
fig

如果和template搭配使用会是什么效果?

指定 template 为 "plotly_dark" 的时候会变成这样子,个人觉得不太行,你觉得呢?

showlegend:默认为 True,如果为 False,那么右上角的那个东东就不显示了,就是我们在轨迹中指定的 name。

images:我个人觉得最牛的一个功能,就是可以将图片作为背景。

from PIL import Image
im = Image.open(r"C:\Users\satori\Desktop\bg\雾雨魔理沙.png")
random_x = np.linspace(0, 2, 100)  
random_y0 = np.random.randn(100) + 5
random_y1 = np.random.randn(100)
random_y2 = np.random.randn(100) - 5
trace0 = go.Scatter(
    x=random_x, 
    y=random_y0,  
    mode="markers",  
    name="markers"  
)

trace1 = go.Scatter(
    x=random_x,  
    y=random_y1, 
    mode="markers + lines", 
    name="markers + lines" 
)

trace2 = go.Scatter(
    x=random_x,  
    y=random_y2, 
    mode="lines",  
    name="lines"  
)
fig = go.Figure(data=[trace0, trace1, trace2], 
               layout={
                   "images": [
                       {"source": im,  # 可以是PIL读取的Image对象,也可以是base64字符
                        "sizex": 1, 
                        "sizey": 1, 
                        "yanchor": "bottom", 
                        "opacity": 0.3,  # 其它的不要变,只变透明度即可
                        }
                   ],
                   # 这里需要一个width和height表示图表的宽和高,我们上面说了的
                   # 主要是为了和图片进行匹配,具体多少取决于你的图片,这个可以自己试一下
                   # 另外记得把legend也考虑在内
                   "width": im.width - 41,
                   "height": im.height,
                })
fig

xaxis 和 yaxis

xaxis 和 yaxis 是非常重要的属性,关键的东西我们留到最后。首先 xaxis 和 yaxis 支持的属性是一样的,都是坐标轴,就一起说了。首先 xaxis 和 yaxis 同样是传递给 layout 的字典里面的一个 key,但关键的是它们对应的 value 也是一个字典,这个字典里面同样可以支持很多的属性。

anchor:锚点

autorange:自动范围

默认为 True,绘制的图表显示的坐标轴的范围会和数据集的范围大致匹配。

calendar:日历模式

color:颜色,针对于当前坐标轴的刻度

random_x = np.linspace(0, 2, 100) 
random_y0 = np.random.randn(100) + 5
random_y1 = np.random.randn(100) - 5

trace0 = go.Scatter(
    x=random_x,  
    y=random_y0, 
)

trace1 = go.Scatter(
    x=random_x,  
    y=random_y1, 
)


fig = go.Figure(data=[trace0, trace1], layout={"xaxis": {"color": "red"}})
fig

dtick:坐标刻度的步进值

很重要的一个参数,我们说它描述的是坐标轴上坐标的紧密程度,一般会搭配 range 来使用。

range:坐标轴范围

如果是 m 到 n,那么就是 "range": [m, n],再搭配 dtick,假设为1。那么坐标轴就是 m、m+1、m+2、......、n,两者搭配可以对坐标轴刻度做任意的调整。

fixedrange:固定坐标轴范围

我们说交互式下绘制的图表的坐标轴只会显示和数据集接近的一部分,但是我们可以对坐标轴进行拖拽让它显示其它的部分,如果设置 fixedrange 为 False,那么就不可拖拽,默认是 True。

gridcolor:网格线颜色

random_x = np.linspace(0, 2, 100) 
random_y0 = np.random.randn(100) + 5
random_y1 = np.random.randn(100) - 5

trace0 = go.Scatter(
    x=random_x,  
    y=random_y0, 
)

trace1 = go.Scatter(
    x=random_x,  
    y=random_y1, 
)


fig = go.Figure(data=[trace0, trace1], layout={"xaxis": {"gridcolor": "yellow"}})
fig

gridwidth:网格线宽度

random_x = np.linspace(0, 2, 100)  # 生成100个点
random_y0 = np.random.randn(100) + 5
random_y1 = np.random.randn(100) - 5

trace0 = go.Scatter(
    x=random_x,  
    y=random_y0, 
)

trace1 = go.Scatter(
    x=random_x,  
    y=random_y1, 
)


fig = go.Figure(data=[trace0, trace1], layout={"xaxis": {"gridwidth": 5, "gridcolor": "cyan"}})
fig

hoverformat:鼠标指针悬停格式

linecolor、linewidth:坐标轴线条颜色与宽度

这两个放在一起说,因为一起演示效果更明显。

random_x = np.linspace(0, 2, 100) 
random_y0 = np.random.randn(100) + 5
random_y1 = np.random.randn(100) - 5

trace0 = go.Scatter(
    x=random_x,  
    y=random_y0, 
)

trace1 = go.Scatter(
    x=random_x,  
    y=random_y1, 
)


fig = go.Figure(data=[trace0, trace1], layout={"xaxis": {"linecolor": "yellow", "linewidth": 5}})
fig

nticks:坐标轴呈现多少个刻度

个人觉得和 dtick 有点重合,range 的范围再除以 dtick 就等同于 nticks。因此不指定步进值,也可以直接指定坐标轴上的刻度数。

rangeslider:范围滑块

random_x = np.linspace(0, 2, 100)  # 生成100个点
random_y0 = np.random.randn(100) + 5
random_y1 = np.random.randn(100) - 5

trace0 = go.Scatter(
    x=random_x,  
    y=random_y0, 
)

trace1 = go.Scatter(
    x=random_x,  
    y=random_y1, 
)


fig = go.Figure(data=[trace0, trace1], layout={"xaxis": {"rangeslider": {"bgcolor": "cyan"}}})
fig

下方有一个滑块,可以拖动,然后显示对应的图形部分。

showgrid:显示网格

random_x = np.linspace(0, 2, 100) 
random_y0 = np.random.randn(100) + 5
random_y1 = np.random.randn(100) - 5

trace0 = go.Scatter(
    x=random_x,  
    y=random_y0, 
)

trace1 = go.Scatter(
    x=random_x,  
    y=random_y1, 
)


fig = go.Figure(data=[trace0, trace1], layout={"xaxis": {"showgrid": False},
                                               "yaxis": {"showgrid": False}})
fig

showline:显示线条开关

side:设置坐标轴刻度位置

random_x = np.linspace(0, 2, 100)  
random_y0 = np.random.randn(100) + 5
random_y1 = np.random.randn(100) - 5

trace0 = go.Scatter(
    x=random_x,  
    y=random_y0, 
)

trace1 = go.Scatter(
    x=random_x,  
    y=random_y1, 
)


fig = go.Figure(data=[trace0, trace1], layout={"xaxis": {"side": "top"},
                                               "yaxis": {"side": "right"}})
fig

spikecolor:峰值数据颜色

tickangle:刻度角度,之前用过的。

tickprefix、ticksuffix:刻度前缀和后缀

random_x = np.linspace(0, 2, 100) 
random_y0 = np.random.randn(100) + 5
random_y1 = np.random.randn(100) - 5

trace0 = go.Scatter(
    x=random_x,  
    y=random_y0, 
)

trace1 = go.Scatter(
    x=random_x,  
    y=random_y1, 
)


fig = go.Figure(data=[trace0, trace1], layout={"xaxis": {"tickprefix": "<<", "ticksuffix": ">>"}})
fig

title、titlefont:等价于 layout 中的 xaxis_title 或 yaxis_title、标题字体

random_x = np.linspace(0, 2, 100)
random_y0 = np.random.randn(100) + 5
random_y1 = np.random.randn(100) - 5

trace0 = go.Scatter(
    x=random_x,  
    y=random_y0, 
)

trace1 = go.Scatter(
    x=random_x,  
    y=random_y1, 
)


fig = go.Figure(data=[trace0, trace1], layout={"xaxis": {"title": "x轴描述", "titlefont": {"color": "cyan", "size": 30}}})
fig

type:刻度类型,可以是 '-', 'linear'、'log'、'date'、'category'、'multicategory' 之一

random_x = np.linspace(0, 2, 100) 
random_y0 = np.random.randn(100) + 5
random_y1 = np.random.randn(100) - 5

trace0 = go.Scatter(
    x=random_x,  
    y=random_y0, 
)

trace1 = go.Scatter(
    x=random_x,  
    y=random_y1, 
)


fig = go.Figure(data=[trace0, trace1], layout={"xaxis": {"type": "category"}})
fig

zeroline:是否显示零线、就是值全部为 0 的线,对于 xaxis 就是 y 轴,对于 yaxis 就是 x轴。

random_x = np.linspace(0, 2, 100)  # 生成100个点
random_y0 = np.random.randn(100) + 5
random_y1 = np.random.randn(100) - 5

trace0 = go.Scatter(
    x=random_x,  
    y=random_y0, 
)

trace1 = go.Scatter(
    x=random_x,  
    y=random_y1, 
)


fig = go.Figure(data=[trace0, trace1], layout={"xaxis": {"zeroline": False},
                                               "yaxis": {"zeroline": False},
                                              })
fig

zerolinecolor、zerolinewidth:零线的颜色和宽度

random_x = np.linspace(0, 2, 100) 
random_y0 = np.random.randn(100) + 5
random_y1 = np.random.randn(100) - 5

trace0 = go.Scatter(
    x=random_x,  
    y=random_y0, 
)

trace1 = go.Scatter(
    x=random_x,  
    y=random_y1, 
)


fig = go.Figure(data=[trace0, trace1], layout={"xaxis": {"zerolinecolor": "cyan", "zerolinewidth": 5},
                                               "yaxis": {"zerolinecolor": "pink", "zerolinewidth": 5}})
fig

plotly 的画布属性就是上面这些,我们说通过参数 layout 指定。可以看到支持的属性非常多,而且都可以让我们自定制,只不过很多我们都用不到。

保存图表

我们上面就介绍完了大部分内容,一些参数什么的,下面我们还需要将图表保存起来。

保存为 html

保存图表可以保存为 html,但是 html 显示的不是图片,而是 js 生成的可以动态交互的数据,所以保存的 html 会非常大,大概几 M。

# 保存成html有两种方式
fig.write_html("xxx.html")  # 直接通过fig.write_html

# 还可以导入一个模块
import plotly.io as pio
pio.write_html(fig, "xxx.html")

# 两种方式都是可以的

保存为json

将图表保存为json的格式,里面主要是图表的核心数据。

fig.write_json("xxx.json")  
# 或者
import plotly.io as pio
pio.write_json(fig, "xxx.json")

保存为图片

保存为图片应该是最常用的,因为很多时候我们绘制完图表之后需要展示在网页上或者做其它的什么事情,总是就是需要一个图片。

保存为图片稍微有点复杂,这也是没有放在第一个说的原因。因为保存为 html、json 直接保存即可,但是保存为图片不行,需要一个东西,叫 orca。估计很多小伙伴在保存为图片的时候都遇到这个问题:

# 保存方式,三者都是类似的
fig.write_image("xxx.png")  

# 或者
import plotly.io as pio
pio.write_image(fig, "xxx.png")

# 然鹅会报出如下错误
"""
...
...
...
If you haven't installed orca yet, you can do so using conda as follows:

    $ conda install -c plotly plotly-orca

Alternatively, see other installation methods in the orca project README at
https://github.com/plotly/orca

After installation is complete, no further configuration should be needed.

If you have installed orca, then for some reason plotly.py was unable to
locate it. In this case, set the `plotly.io.orca.config.executable`
property to the full path of your orca executable. For example:

    >>> plotly.io.orca.config.executable = '/path/to/orca'

After updating this executable property, try the export operation again.
If it is successful then you may want to save this configuration so that it
will be applied automatically in future sessions. You can do this as follows:

    >>> plotly.io.orca.config.save()

If you're still having trouble, feel free to ask for help on the forums at
https://community.plot.ly/c/api/python

"""

原因就在于你没有安装orca,所以我们需要安装一下。

Windows 安装 orca

首先要 pip3 install psutil requests,然后我们再去 https://github.com/plotly/orca/releases 上面下载对应操作系统的 orca 安装文件,各种系统都有。我们需要下载 Windows 版本的 orca 安装文件,下载之后是一个 zip 包。解压即可,然后安装的 exe 文件就在里面,点击安装,然后将安装之后的目录添加到环境变量里面即可。但是我这里直接将安装文件和安装之后的目录提供给你们,可以直接从百度云下载即可。

下面的 orca.exe 就是安装的exe文件,上面的 orca 目录就是安装之后的结果,这两个东西的百度云链接如下,我也放在了一个 zip 包里面。

链接:https://pan.baidu.com/s/1zehUeRC3U5jdmxdHIwtl8A 
提取码:pi30 

下载下来之后解压,你可以点击 orca.exe 重新安装,也可以不用管那个 exe 文件,而是直接把上面 orca 目录设置到环境变量里面即可,因为 orca 目录就是 orca.exe 安装之后的目录,这两个东西我都提供了。个人建议直接把 orca 目录设置到环境变量里面就行,没必要再单独安装了。

就是这位老铁所在的目录,生成图片就全靠它了。

Linux 安装 orca

首先还是要 pip3 install psutil requests 安装这两个包,然后:

# 安装下面这些,不管有没有,最好都执行一下
yum install fuse-libs-2.9.2-11.el7.x86_64
yum install gtk2-2.24.31-1.el7.x86_64
yum install desktop-file-utils 
yum install Xvfb
yum install xdg-utils-1.1.0-0.17.20120809git.el7.noarch

然后下载 orca,这个是绘制成图表所必须的:

# 还是去 https://github.com/plotly/orca/releases 页面下载 orca,只不过是 Linux版本的,后缀是 AppImage
# 执行以下命令,就是上面下载的文件
xvfb-run -a xxx.AppImage "$@"
# 然后改个名
mv xxx.AppImage orca
# 赋予执行权限
chmod 755 orca
# 添加到环境变量,输入 orca --help 查看
# 正常输出,则安装成功

mac 安装 orca,我没有试过,但是下载 mac 版本的 orca 还是去我们上面说的那个页面。具体怎么安装可以网上搜索,嘿嘿,让人怪不好意思的(手动表情包)

安装之后,我们就可以保存图片啦。另外除了 write_image,还有一个 to_image,这个不需要保存的文件名,而是调用之后直接返回图片的字节流,相当于使用 rb 模式对图片进行读取。比起图片本身,我们可能更常用字节流,直接渲染到页面上,或者生成 base64 字节。同理还有 to_html、to_json,可以自己尝试一下。另外:to_image 同样需要 orca,而 to_html 和 to_json 则不需要。

真的是总结

plotly 真的是一个绘图神器,支持大量的图表,当然我们只介绍了一小部分,不过它们都是最常用的。而且重点是 plotly 中的轨迹和画布两个概念,我们说图表在 plotly 中就是轨迹,然后轨迹需要展示在画布上,而画布有很多属性,这些我们上面介绍的比较详细了,虽然很多都用不到。

至于其它的图表可以去官网查看,支持图表种类非常多,学习的时候可以通过 help 命令参看使用方法,因为个人觉得绘制图表实际上就是一个调参的过程。加油,拥抱 plotly 吧,不过最让我满意的就是,居然可以嵌入图片作为背景。

申明:本文中对一些图表所做的概念上的解析,来自于 "图之典", http://www.tuzhidian.com/ ,这是由一些爱好数据可视化的人创建的。里面对图表进行了概念上的详细描述,包括适用场景、以及不适用场景等等,如果你喜欢数据可视化,那么这个网站你一定不能错过。

posted @ 2020-03-16 17:20  古明地盆  阅读(25509)  评论(5编辑  收藏  举报