Pyecharts实现微博签到中国/Pyecharts中使用scatterGL详解

前言

Pyecharts实现微博签到中国/Pyecharts中使用scatterGL详解

  • 之前在【Pyecharts Gallery】中看不中用的可视化作品集合~发布过一个可视化作品——微博签到中国,不过当时存在一个比较严重的问题,数据加载太慢了,图表中总共包含了30W+个点,渲染完所有的点都得一两分钟,在图例筛选上也存在非常严重的卡顿,用户体验实在太差,最近得空又重新研究捣鼓了下,终于解决了,这次也尽可能详细的讲解下我的解决办法;

  • Echarts在17年发布了GL,对于量级较大的数据支持性能上有了质的提升,所以理论上我们只要GEO-Scatter图表更换成GEO-scatterGL就能解决问题,不过scatterGL在pyecharts中并不能直接支持,具体怎么实现,我们看下面具体实例。

  • 另外由于全国的图表数据量太大,整个图表将近80M,无法在KLab中发布,下面的实例中会以北京市的签到数据为例进行讲解,全国的代码也会在后面附上,各位可以复制到自己本地去运行!


  • 全部代码以及数据集都已上传至我的KLAB,各位可以自行取阅~

数据处理

精度处理

将签到位置的经纬度的精度降低,统一取小数点后3位。
原始文件中的经纬度精确到了小数点后3-7位,在现实中可能就是100米左右的偏差(瞎说的,没具体查~),但这距离反映到图表中可能也就几微米的差距,完全可以舍弃掉,而且这样做有以下两个好处:

  • 降低精度之后可以对数据进行一次聚合计数,计算出哪些位置是签到人数更多的地方,在可视化图表通过不同颜色来展现,让用户一眼就能发现哪些位置是热门地点;
  • 缩小数据量,整个原始数据大概是900W条记录,在降低到后两位精度的话可以减少到30W左右,可以大大的提高图表的加载速度;
df_bj = pd.read_json('/home/kesci/input/weibo_data2887/WeiboDataShare/beijing.json')
# 设置经纬度的精度为小数点后三位
df_bj['lat'] = df_bj['lat'].apply(lambda x: '{:.3f}'.format(x))
df_bj['lon'] = df_bj['lon'].apply(lambda x: '{:.3f}'.format(x))
df_bj.head()
city lat location lon
0 北京 24.115 丽景花园 102.774
1 北京 24.114 K歌城 102.761
2 北京 23.980 炸弹葱油饼 121.610
3 北京 23.991 丽轩国际酒店 121.625
4 北京 23.988 花莲丽轩大饭店 121.629

聚合计算

对降低精度之后的数据进行一次聚合计数,计算出每个地点签到的次数;

df_bj = df_bj.groupby(['lon', 'lat'])['city'].count().reset_index()
df_bj.columns = ['lon', 'lat', 'num']

新建GEO实例

因为需要将每个地点位置信息添加到GEO中去,我们在此先建立一个GEO实例,在后面一个cell遍历数据分类的时候,便可以一起将坐标位置信息添加到GEO中了~

geo = Geo(init_opts=opts.InitOpts(theme='dark', bg_color='#000000', width='1000px', height='800px'))

地点分类

将所有地点根据签到人次分为强、中、弱三类,同时将地点的经纬度信息添加到GEO实例中去~

# 根据位置签到次数分为强中弱三个级别
weak, strong, normal = [], [], []
for idx, row in df_bj.iterrows():
    if row.num < 4:
        weak.append((idx, row.num))
        geo.add_coordinate(idx, row.lon, row.lat)
    elif 4 <= row.num < 10:
        normal.append((idx, row.num))
        geo.add_coordinate(idx, row.lon, row.lat)
    elif row.num >= 10:
        strong.append((idx, row.num))
        geo.add_coordinate(idx, row.lon, row.lat)

print(f'弱分类地点数:{len(weak)}\n中分类地点数:{len(normal)}\n强分类地点数:{len(strong)}')

可视化代码

大部分代码与GEO-Scatter的配置一样,这边就不再赘述了,可以参见我以前的两个项目,里面有简单的代码实例:


这边着重说一下几个特殊的地方:

Scatter-GL的使用

以下为官方文档中Geo.add()方法的注释:

def add(
    # 系列名称,用于 tooltip 的显示,legend 的图例筛选。
    series_name: str,

    # 数据项 (坐标点名称,坐标点值)
    data_pair: Sequence,

    # Geo 图类型,有 scatter, effectScatter, heatmap, lines 4 种,建议使用
    # from pyecharts.globals import GeoType
    # GeoType.GeoType.EFFECT_SCATTER,GeoType.HEATMAP,GeoType.LINES
    type_: str = "scatter",

   # 其他部分省略,可以参见文档http://pyecharts.org/#/zh-cn/geography_charts
)

在官方代码中Geo只支持4中类型,分别是scatter(散点图),effectScatter(带涟漪效果的散点图),heatmap(热力图)和lines(线图),Scatter-GL在pyecharts中是不支持直接使用的,那如果我们强行通过geo.add( type_ = 'scatter')使用行吗?结果是不行的,我们可以参看Pyecharts中Geo.add()的源码pyecharts/charts/basic_charts/geo.py

  • type_部分处理代码:
if type_ == ChartType.SCATTER:
    self.options.get("series").append(
        {
           # 代码太长了,具体配置项省略,可以直接去github查看
        }
    )

elif type_ == ChartType.EFFECT_SCATTER:
    self.options.get("series").append(
        {
            # 代码太长了,具体配置项省略,可以直接去github查看
        }
    )

elif type_ == ChartType.HEATMAP:
    self.options.get("series").append(
        {
            # 代码太长了,具体配置项省略,可以直接去github查看
        }
    )

elif type_ == ChartType.LINES:
    self.options.get("series").append(
        {
            # 代码太长了,具体配置项省略,可以直接去github查看
        }
    )
elif type_ == ChartType.CUSTOM:
    self.options.get("series").append(
        {
            # 代码太长了,具体配置项省略,可以直接去github查看
        }
    )

如果你传入了除scatter, effectScatter, heatmap, lines, custom这五种以外的参数,程序根本就不会处理,所以如果要使用scatter-GL我们便得另谋出路了。


继续往下说之前,我们先普及一下概念,pyecharts在整个可视化过程中充当的只是一个“翻译”的作用,他只是将你的Python代码翻译成echarts能够使用的配置信息,所有的图表生成都是通过echarts来完成的,所以理论上我们并不需要按部就班的使用Pyecharts中的方法,只要自己能写清楚各项配置也是可行的;


  • 可以找个简单的例子将图表的option打印出来,各位感受一下:
# 虚假数据
x_data = ['Apple', 'Huawei', 'Xiaomi', 'Oppo', 'Vivo', 'Meizu']
y_data = [123, 153, 89, 107, 98, 23]

bar = (Bar()
       .add_xaxis(x_data)
       .add_yaxis('', y_data)
      )
# 打印Bar的option
print(bar.dump_options())
  • 打印结果如下:
{
    "animation": true,
    "animationThreshold": 2000,
    "animationDuration": 1000,
    "animationEasing": "cubicOut",
    "animationDelay": 0,
    "animationDurationUpdate": 300,
    "animationEasingUpdate": "cubicOut",
    "animationDelayUpdate": 0,
    "color": [
        "#c23531",
        "#2f4554",
        "#61a0a8",
        "#d48265",
        "#749f83",
        "#ca8622",
        "#bda29a",
        "#6e7074",
        "#546570",
        "#c4ccd3",
        "#f05b72",
        "#ef5b9c",
        "#f47920",
        "#905a3d",
        "#fab27b",
        "#2a5caa",
        "#444693",
        "#726930",
        "#b2d235",
        "#6d8346",
        "#ac6767",
        "#1d953f",
        "#6950a1",
        "#918597"
    ],
    "series": [
        {
            "type": "bar",
            "legendHoverLink": true,
            "data": [
                123,
                153,
                89,
                107,
                98,
                23
            ],
            "showBackground": false,
            "barMinHeight": 0,
            "barCategoryGap": "20%",
            "barGap": "30%",
            "large": false,
            "largeThreshold": 400,
            "seriesLayoutBy": "column",
            "datasetIndex": 0,
            "clip": true,
            "zlevel": 0,
            "z": 2,
            "label": {
                "show": true,
                "position": "top",
                "margin": 8
            }
        }
    ],
    "legend": [
        {
            "data": [
                ""
            ],
            "selected": {
                "": true
            }
        }
    ],
    "tooltip": {
        "show": true,
        "trigger": "item",
        "triggerOn": "mousemove|click",
        "axisPointer": {
            "type": "line"
        },
        "showContent": true,
        "alwaysShowContent": false,
        "showDelay": 0,
        "hideDelay": 100,
        "textStyle": {
            "fontSize": 14
        },
        "borderWidth": 0,
        "padding": 5
    },
    "xAxis": [
        {
            "show": true,
            "scale": false,
            "nameLocation": "end",
            "nameGap": 15,
            "gridIndex": 0,
            "inverse": false,
            "offset": 0,
            "splitNumber": 5,
            "minInterval": 0,
            "splitLine": {
                "show": false,
                "lineStyle": {
                    "show": true,
                    "width": 1,
                    "opacity": 1,
                    "curveness": 0,
                    "type": "solid"
                }
            },
            "data": [
                "Apple",
                "Huawei",
                "Xiaomi",
                "Oppo",
                "Vivo",
                "Meizu"
            ]
        }
    ],
    "yAxis": [
        {
            "show": true,
            "scale": false,
            "nameLocation": "end",
            "nameGap": 15,
            "gridIndex": 0,
            "inverse": false,
            "offset": 0,
            "splitNumber": 5,
            "minInterval": 0,
            "splitLine": {
                "show": false,
                "lineStyle": {
                    "show": true,
                    "width": 1,
                    "opacity": 1,
                    "curveness": 0,
                    "type": "solid"
                }
            }
        }
    ]
}

在pyecharts中options并非一个私有变量,我们是可以自行更改的,只要我们改完后的配置(options)在echarts能够使用就行;
我们可以通过如下代码来将GEO的类型更改为scatter-GL

geo.add("",
        data,
        type_='scatter')

# 将GEO的type更改为scatterGL
geo.options['series'][0]['type'] = 'scatterGL'

这样第一部分问题便解决了~


添加JS依赖

通过上述步骤配置之后,代码会执行成功,文件也会生成,不过当你打开图表html文件的时候还是会一片空白。

定位问题

在前文中提到了,pyecharts中只是一个翻译的角色,它只会管你输入的参数是不是合法,但这些参数能否起作用,pyecharts并不会知道,如果在图表生成渲染中存在问题(echarts的报错),在pyecharts中是不会产生报错的;所以我们在定位问题的时候,除了查看Python下的报错外,还可以生成html文件到本地,打开后然后F12选择console查看报错,这里面便能看到Echarts产生的报错;


我们打开生成的html文件,然后F12查看下console,可以看到如下报错:
Uncaught Error: Component series.scatterGL not exists. Load it first.
Pyecharts实现微博签到中国/Pyecharts中使用scatterGL详解

虽然不咋会用JS,但也大概能看出是出了什么问题:
其实就是我们在代码中使用了scatterGL,但并未引用它,就相当于在Python中,我们代码中使用了pandas.read_csv(),但并未在使用之前import pandas


我们切换到elements的tab下查看页面代码:

Pyecharts实现微博签到中国/Pyecharts中使用scatterGL详解

可以看到,页面中只引用了echarts.min.jsecharts.min.js只包含了一些常规的图表,如果要使用scatterGL,我们需要添加echarts-gl.min.js的引用,在Python中可以通过如下代码添加引用:

geo.js_dependencies.add("echarts-gl")

这样我们就能正常使用scatterGL图了,完整代码如下:

geo.add_schema(maptype="北京", is_roam=True, zoom=1.1,
               itemstyle_opts=opts.ItemStyleOpts(color="#000000", border_color="#1E90FF"),
               emphasis_label_opts=opts.LabelOpts(is_show=False),
               emphasis_itemstyle_opts=opts.ItemStyleOpts(color="#323c48"))

geo.add("弱",
        weak,
        type_='scatter',
        is_selected=True,
        symbol_size=1,
        is_large=True,
        itemstyle_opts=opts.ItemStyleOpts(color="#1E90FF"))

geo.add("中",
        normal,
        type_='scatter',
        is_selected=True,
        symbol_size=1,
        is_large=True,
        itemstyle_opts=opts.ItemStyleOpts(color="#00FFFF"))

geo.add("强",
        strong,
        type_='scatter',
        is_selected=True,
        symbol_size=1,
        is_large=True,
        itemstyle_opts=opts.ItemStyleOpts(color="#E1FFFF"))

geo.set_series_opts(label_opts=opts.LabelOpts(is_show=False))

geo.set_global_opts(
    title_opts=opts.TitleOpts(title="微博签到点亮中国", pos_top='top', pos_left='center'),
    tooltip_opts=opts.TooltipOpts(is_show=False),
    legend_opts=opts.LegendOpts(is_show=True, pos_left='left', orient='vertical'))

# 添加依赖,scatterGL需要使用
geo.js_dependencies.add("echarts-gl")

# 更改geo图类型
geo.options['series'][0]['type'] = 'scatterGL'
geo.options['series'][1]['type'] = 'scatterGL'
geo.options['series'][2]['type'] = 'scatterGL'

# geo.render('/home/kesci/work/test_beijing.html')
geo.render_notebook()

Pyecharts实现微博签到中国/Pyecharts中使用scatterGL详解

全国数据代码

# 读取全国数据,替换成自己的路径
df = pd.read_csv(r'/home/kesci/input/weibo_data2887/WeiboDataShare/weibo_s_size.csv')
df.head()

# 新建GEO实例
geo = Geo(init_opts=opts.InitOpts(theme='dark', bg_color='#000000', width='1000px', height='800px'))

# 将数据分为强中弱三类
weak, strong, normal = [], [], []
for idx, row in df.iterrows():
    if row.num < 10:
        weak.append((idx, row.num))
        geo.add_coordinate(idx, row.lon, row.lat)
    elif 10 <= row.num < 30:
        normal.append((idx, row.num))
        geo.add_coordinate(idx, row.lon, row.lat)
    elif row.num >= 30:
        strong.append((idx, row.num))
        geo.add_coordinate(idx, row.lon, row.lat)
				
# 设置地图
geo.add_schema(maptype="china", is_roam=False, zoom=1.2,
               itemstyle_opts=opts.ItemStyleOpts(color="#000000", border_color="#1E90FF"),
               emphasis_label_opts=opts.LabelOpts(is_show=False),
               emphasis_itemstyle_opts=opts.ItemStyleOpts(color="#323c48"))

# 添加数据
geo.add("弱",
        weak,
        type_='scatter',
        is_selected=True,
        symbol_size=1,
        is_large=True,
        itemstyle_opts=opts.ItemStyleOpts(color="#1E90FF"))

geo.add("中",
        normal,
        type_='scatter',
        is_selected=True,
        symbol_size=1,
        is_large=True,
        itemstyle_opts=opts.ItemStyleOpts(color="#00FFFF"))

geo.add("强",
        strong,
        type_='scatter',
        is_selected=True,
        symbol_size=1,
        is_large=True,
        itemstyle_opts=opts.ItemStyleOpts(color="#E1FFFF"))

# 关闭标签
geo.set_series_opts(label_opts=opts.LabelOpts(is_show=False))

geo.set_global_opts(
    title_opts=opts.TitleOpts(title="微博签到点亮中国", pos_top='top', pos_left='center'),
    tooltip_opts=opts.TooltipOpts(is_show=False),
    legend_opts=opts.LegendOpts(is_show=True, pos_left='left', orient='vertical'))

# 添加依赖
geo.js_dependencies.add("echarts-gl")

# 更改图表类型
geo.options['series'][0]['type'] = 'scatterGL'
geo.options['series'][1]['type'] = 'scatterGL'
geo.options['series'][2]['type'] = 'scatterGL'


geo.render()

Pyecharts实现微博签到中国/Pyecharts中使用scatterGL详解

上一篇:(第二十天)[js] 写一个验证身份证号的方法


下一篇:pyecharts的用法入门(四)各种各样的饼状图Pie