三万字长文详解神级绘图框架 plotly

简介: 三万字长文详解神级绘图框架 plotly


楔子



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 + 2
trace0 = 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 + 3
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

还是很简单的。

面积图的存在意义

面积图可用于多个系列数据的比较,此时面积图的外观看上去类似层叠的山脉,在错落有致的外形下表达数据的总量和趋势。相较于折线图,面积图不仅可以清晰地反映出数据的趋势变化,也能够强调不同类别的数据间的差距对比。

但它的劣势在于,填充会让形状互相遮盖,反而看不清变化。我们上面的数据弄的比较规整,所以看起来很清晰,但实际工作中绘制出来的面积图就不一定了。而一种解决方法是使用有透明度的颜色,来让出覆盖区域。


甘特图



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

# 创建甘特图,使用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="这是甘特图")
# 此时会直接返回画布,想调整属性的话
# 通过 fig.layout.update 即可
fig.layout.update({"template": "plotly_dark"})
fig

创建甘特图的时候,除了传递一个包含字典的列表,还可以传递 DataFrame,当然列名同样必须是 Task, Start, Finish。

此外我们还可以指定颜色,以及进度。

# 创建甘特图,使用plotly.figure_factory
# 然后调用内部的create_gantt即可
import plotly.figure_factory as ff
tasks = [
    {"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) - 5
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={"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) - 5
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={"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) - 5
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={"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 了(喜大普奔)。尽管和其他号主相比,我连人家的二十分之一、三十分之一都不到,但也很开心了。其实我涨粉的主要途径也全靠同行转发,在其他号主转发我文章的时候吸一波粉。

总之,今后我也会努力地输出更多内容,认真写好每一篇文章。

相关文章
|
C#
C#之四十六 迷你贪吃蛇项目
C#之四十六 迷你贪吃蛇项目
75 0
|
6月前
|
Web App开发 存储 前端开发
【4万字长文吐血整理】LaTeX基础使用【助你熟练玩转LaTeX】
【4万字长文吐血整理】LaTeX基础使用【助你熟练玩转LaTeX】
|
Python
三岁学编程之python安装(最细教程)
三岁学编程之python安装(最细教程)
114 0
三岁学编程之python安装(最细教程)
|
前端开发 Python
学习笔记 | Python程序基本格式及trutle绘图函数库(奥运五环、太阳花、五角星、运动时钟、玫瑰花实例附源码)
学习笔记 | Python程序基本格式及trutle绘图函数库(奥运五环、太阳花、五角星、运动时钟、玫瑰花实例附源码)
学习笔记 | Python程序基本格式及trutle绘图函数库(奥运五环、太阳花、五角星、运动时钟、玫瑰花实例附源码)
|
存储 前端开发 uml
史上最强画图工具推荐
史上最强画图工具推荐
747 0
史上最强画图工具推荐
|
数据可视化 前端开发 安全
头疼数学图表?CMU学霸开源「玫瑰笔」,让你一键绘图自由
数学可能是最抽象的学科了。虽然有很多可视化工具,但实际使用过程中却存在学习门槛高、画图流程长等问题,一个简单的公式用画图工具可能需要做很久。为了解决这个问题,来自CMU和Technion的一组研究人员推出了Penrose,它可以将复杂的数学符号转换为各种风格的简单图表,引发社区热议。
216 0
头疼数学图表?CMU学霸开源「玫瑰笔」,让你一键绘图自由