title: python数据可视化:pyecharts v1版本
date: 2021-06-05 10:29:26
tags: python数据分析
sticky: 996
Echarts是一个由百度开源的数据可视化,结合巧妙的交互性,精巧的图表设计;而 Python 是一门富有表达力的语言,很适合用于数据处理。分析遇上数据可视化时,pyecharts诞生了。
个人觉得可视化最好用的,不接受反驳,毕竟用echarts的都那么多;
官方文档
- 自己挑了一些碰到的坑记了一下,分享一些自己学习过程中发现的资源,其他看文档就够了
基础柱状图
from pyecharts import options as opts
from pyecharts.charts import Bar
from pyecharts.faker import Faker
c = (
Bar()
.add_xaxis(Faker.choose())
.add_yaxis("商家A", Faker.values())#gap="0%";category_gap="80%";stack="stack1"
.add_yaxis("商家B", Faker.values())#gap="0%";is_selected=False;stack="stack1";label_opts=opts.LabelOpts(is_show=False)
.set_global_opts(title_opts=opts.TitleOpts(title="Bar-基本示例", subtitle="我是副标题"))
# datazoom_opts=[opts.DataZoomOpts(), opts.DataZoomOpts(type_="inside")],#时间轴显示并可同通过鼠标滑动
.set_series_opts(
label_opts=opts.LabelOpts(is_show=False),
markpoint_opts=opts.MarkPointOpts(
data=[
opts.MarkPointItem(type_="max", name="最大值"),
opts.MarkPointItem(type_="min", name="最小值"),
# opts.MarkPointItem(type_="average", name="平均值"),
]
),
markline_opts= opts.MarkLineOpts(data = [opts.MarkLineItem(type_ = "average",name = "平均值")])
)
.render_notebook()
)
c
只能通过滑动水平轴缩放x轴:只传入datazoom_opts=[opts.DataZoomOpts()];省略掉 opts.DataZoomOpts(), 删除水平轴,就只能通过鼠标拖动;
显示水平轴的比例(默认):
opts.DataZoomOpts(range_start=20,range_end=80)取消柱子上方数据显示:
label_opts=opts.LabelOpts(is_show=False)添加y轴水平轴:
datazoom_opts=opts.DataZoomOpts(orient="vertical"),可缩放大小;柱间距离:
加入参数gap="0%"(两个add_yaxis里面都要传);
加入参数category_gap="80%",表示单系柱间距离;默认取消显示某 Series:
加入参数is_selected=False,默认取消显示某 Series堆叠数据:传入stack="stack1",希望哪几个指标堆叠就传那几个add_yaxis中;
显示ToolBox:在set_global_opts()中传入 toolbox_opts=opts.ToolboxOpts(), brush_opts=opts.BrushOpts(),;
XY轴名称:在set_globel_opts()中加入 yaxis_opts=opts.AxisOpts(name="我是 Y 轴"),xaxis_opts=opts.AxisOpts(name="我是 X 轴"), ;
Y轴数据单位:在set_globel_opts()中加入 yaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(formatter="{value} /月")), ;
加入标记线
.set_series_opts(
label_opts=opts.LabelOpts(is_show=False),
markline_opts=opts.MarkLineOpts(
data=[opts.MarkLineItem(y=50, name="yAxis=50")]
),
)
#set_global_opts()后加入set_series_opts,加入标记线,这里是y=50;
- 标记线的维度(当然可以是横轴)
.set_series_opts(
markline_opts=opts.MarkLineOpts(
data=[opts.MarkLineItem(type_="average",value_dim="x", name="xAxis"),
opts.MarkLineItem(type_="average",value_dim="y", name="xAxis")
]
- 加入标记值
#set_series_opts中加入,表示传入标记,最大、最小、平均值。
markpoint_opts=opts.MarkPointOpts(
data=[
opts.MarkPointItem(type_="max", name="最大值"),
opts.MarkPointItem(type_="min", name="最小值"),
opts.MarkPointItem(type_="average", name="平均值"),
]
- xy轴互换
#设置完add_yaxis后接着
.reversal_axis()#xy轴互换
.set_series_opts(label_opts=opts.LabelOpts(position="right"))#显示数据在柱子右边
- x轴名字标签过长,旋转角度
在set_global_opts()中传入
xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=-15)),
- 连接空数据
import pyecharts.options as opts
from pyecharts.charts import Line
from pyecharts.faker import Faker
y = Faker.values()
y[3], y[5] = None, None
c = (
Line()
.add_xaxis(Faker.choose())
.add_yaxis("商家A", y, is_connect_nones=True)
.set_global_opts(title_opts=opts.TitleOpts(title="Line-连接空数据"))
.render("line_connect_null.html")
)
- 自定义标记点
import pyecharts.options as opts
from pyecharts.charts import Line
from pyecharts.faker import Faker
x, y = Faker.choose(), Faker.values()
c = (
Line()
.add_xaxis(x)
.add_yaxis(
"商家A",
y,
markpoint_opts=opts.MarkPointOpts(
data=[opts.MarkPointItem(name="自定义标记点", coord=[x[2], y[2]], value=y[2])]
),
)
.set_global_opts(title_opts=opts.TitleOpts(title="Line-MarkPoint(自定义)"))
.render("line_markpoint_custom.html")
)
- 作条形图时由于有时候座标轴文字过长,缩放后导致无法完全显示,此时则需要调整座标轴与图像的边距。调整边距是采用grid()方法。在图形设置完成后,使用grid设置座标轴与图像边界的距离。
report_day_line = Grid()
report_day_line.add(report_day_line1,opts.GridOpts(pos_left="10%",pos_top ="10%"), is_control_axis_index=True)
report_day_line.render_notebook()
- 指定chart_id时要将写好的图形定义成函数,这样子可以复用后面的page中图片位置,方便更改
- jscode的妙用 https://zhuanlan.zhihu.com/p/133533187
tooltip_opts=opts.TooltipOpts(
formatter=JsCode(
"function (params) {return params.value[2] + ' :复购率:' +(Number(params.value[0])* 100).toFixed(2) + '%'+',销售额:'+ params.value[1];}"
)
# trigger="axis", axis_pointer_type="cross"
),
pyecharts画时间排序图
from pyecharts import options as opts
from pyecharts.charts import Bar,Timeline
from pyecharts.faker import Faker
import random
import pandas as pd
import numpy as np
# 受这篇文章启发https://blog.csdn.net/weixin_42512684/article/details/108176613
data = np.array([random.randint(30,150) for i in range(52*7) ]).reshape(52,7)
df = pd.DataFrame(data,index=[i for i in range(1969,2021)],columns = random.sample(attr, len(attr)))
df
df_sorted = [r[:].sort_values() for i,r in df.iterrows()]
df_sorted[0].values
t1 = Timeline()# 创建 Timeline对象
for j in range(1969,2021):
bar = (
Bar()
.add_xaxis([str(i) for i in df_sorted[j-1969].index])
.add_yaxis('Data',[int(i) for i in df_sorted[j-1969].values]
,label_opts = opts.LabelOpts(position = 'right'),
)
.set_series_opts(label_opts = opts.LabelOpts(is_show = True,position = 'right'))
.reversal_axis()
.set_global_opts(title_opts = opts.TitleOpts("{}".format(j),
pos_left = '50%',
),
legend_opts = opts.LegendOpts(pos_right = '10%'))
)
t1.add(bar,'{}年'.format(j))
t1.add_schema(
symbol = 'arrow',# 设置标记时间;
#orient = 'vertical',
symbol_size = 2,# 标记大小;
play_interval = 1000,# 播放时间间隔;
control_position = 'right',# 控制位置;
linestyle_opts = opts.LineStyleOpts(width = 5,
type_ = 'dashed',
color = 'rgb(255,0,0,0.5)'),
label_opts = opts.LabelOpts(color = 'rgb(0,0,255,0.5)',
font_size = 15,
font_style = 'italic',
font_weight = 'bold',
font_family ='Time New Roman',
position = 'left',
interval = 20,
)
)
t1.render_notebook()
pyecharts画地理图-geo,map
def my_geo(city, city_value):
c = (
Geo(init_opts=opts.InitOpts(theme=ThemeType.DARK,chart_id=1))
.add_schema(maptype="china")
.add("geo", [list(z) for z in zip(city, city_value)])
.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
.set_global_opts(
visualmap_opts=opts.VisualMapOpts(), title_opts=opts.TitleOpts(title="用户城市分布")
) # .render("geo_base.html")
)
return c
is_piecewise参数:在 set_global_opts()中的visualmap_opts=opts.VisualMapOpts(is_piecewise=True),表示分段显示;
def my_map(province, num):
c = (
Map(init_opts=opts.InitOpts(theme=ThemeType.DARK,chart_id=2))
.add("map", [list(z) for z in zip(province, num)], "china")
.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
.set_global_opts(visualmap_opts=opts.VisualMapOpts(), title_opts=opts.TitleOpts(title="用户省份分布"))
) # .render("map_base.html")
return c
pyecharts画内层嵌套饼图
c = (
Pie()
.add("", [list(z) for z in zip(Faker.choose(), Faker.values())])#center=["35%", "50%"],
.set_global_opts(title_opts=opts.TitleOpts(title="Pie-基本示例"))#legend_opts=opts.LegendOpts(pos_left="15%"),
.set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}"))
# .render("pie_base.html")
)
c.render_notebook()
内嵌饼图nested_pies
inner_x_data = ["直达", "营销广告", "搜索引擎"]
inner_y_data = [335, 679, 1548]
inner_data_pair = [list(z) for z in zip(inner_x_data, inner_y_data)]
outer_x_data = ["直达", "营销广告", "搜索引擎", "邮件营销", "联盟广告", "视频广告", "百度", "谷歌"]
outer_y_data = [335, 310, 234, 135, 1048, 251, 147, 102]
outer_data_pair = [list(z) for z in zip(outer_x_data, outer_y_data)]
(
Pie(init_opts=opts.InitOpts())#theme=ThemeType.DARK 指定主题为黑色
.add(
series_name="访问来源",
data_pair=inner_data_pair,#指定数据源
radius=[0, "30%"],#半径
label_opts=opts.LabelOpts(position="inner"),#图形位置
)
.add(
series_name="访问来源",
radius=["40%", "55%"],
data_pair=outer_data_pair,
label_opts=opts.LabelOpts(
position="outside",
formatter="{a|{a}}{abg|}\n{hr|}\n {b|{b}: }{c} {per|{d}%} ",#"{a} <br/>{b}: {c} ({d}%)" 文本样式
background_color="#eee", #背景颜色
border_color="#aaa", #边框颜色
border_width=1, #边框宽度
border_radius=4, #边界半径
rich={
"a": {"color": "#999", "lineHeight": 22, "align": "center"},
"abg": {
"backgroundColor": "#e3e3e3",
"width": "100%",
"align": "right",
"height": 22,
"borderRadius": [4, 4, 0, 0],
},
"hr": {
"borderColor": "#aaa",
"width": "100%",
"borderWidth": 0.5,
"height": 0,
},
"b": {"fontSize": 16, "lineHeight": 33},#,"color":"#999"
# "c":{"color":"#999"},
"per": {
"color": "#eee",
"backgroundColor": "#334455",
"padding": [2, 4],
"borderRadius": 2,
},
},
),
)
.set_global_opts(legend_opts=opts.LegendOpts(pos_left="left", orient="vertical",textstyle_opts={"color":"#999"}))#设置图例字体为白色
.set_series_opts(
tooltip_opts=opts.TooltipOpts(
trigger="item", formatter="{a} <br/>{b}: {c} ({d}%)" # 'item': 数据项图形触发,主要在散点图,饼图等无类目轴的图表中使用。
)
)
# .render("nested_pies.html")
).render_notebook()
那个formatter配置项可以在这里了解:
{a}(系列名称),{b}(数据项名称),{c}(数值), {d}(百分比)
https://echarts.apache.org/zh/option.html#grid.tooltip.formatter
pyecharts中page的使用类似BI大屏展示
page = Page(layout= Page.DraggablePageLayout, page_title= "大屏展示")
# page = Page()
page.add(
bar_datazoom_slider(),
line_markpoint(),
pie_rosetype(),
grid_mutil_yaxis(),
liquid_data_precision(),
table_base(),
)
page.render("temp.html")
然后自己点那开个temp.html文件进行拖拽,缩放,布局称自己想要的样子,最后点击save_config按钮,下载那个chart_config.json文件。
不带标题,修改配置文件为百分比
- 修改json配置文件,改成百分比主要是为了页面自适应屏幕大小。
df = pd.read_json("chart_config.json")
df["width"] = df.width.apply(lambda x: x[:-2])
df["height"] = df.height.apply(lambda x: x[:-2])
df["top"] = df.top.apply(lambda x: x[:-2])
df["left"] = df.left.apply(lambda x: x[:-2])
# df.astype({'width':'float','height':'float','top':'float','height':'float'}).dtypes
df["width"]= df.width.apply(lambda x: str(round(float(x)/1256*100,4))+"%")#这里1256、640是你自己电脑屏幕的px值
df["height"] = df.height.apply(lambda x: str(round(float(x)/640*100*11/12,4))+"%")#如果出现右侧滑块,可适当进行缩放,这里按11/12的比例缩放
df["top"] = df.top.apply(lambda x: str(round(float(x)/640*100*11/12,4))+"%")
df["left"] = df.left.apply(lambda x: str(round(float(x)/1256*100,4))+"%")
df.to_json("chart_config2.json",orient = "records")
- 修改好json数据之后,再运行这行代码
#第一个字段是前面拖拽的html文件名,cfg_file就是你刚save的json文件名,dest是你要生成的文件名
page.save_resize_html( 'temp.html', cfg_file= 'chart_config2.json',dest= '设置好位置后的BI看板.html')
加上标题,可自定义html
这样弄完有点问题就是背景页面可能和图片背景有违和感,你可以使用BeautifulSoup再修改下页面背景颜色啥的,相关网课链接https://www.bilibili.com/video/BV1KT4y1c7pb?p=22
from bs4 import BeautifulSoup
import os
#这里要先读取那个json文件
with open(os.path.join(os.path.abspath("."), "设置好位置后的BI看板.html"), 'r+', encoding="utf8") as html:
html_bf = BeautifulSoup(html, "lxml")
divs = html_bf.find_all("div")
#读取config_json文件,循环写入到HTML
for i in range(len(df_config)):
width = df_config.loc[i,'width']
height = df_config.loc[i,'height']
top = df_config.loc[i,'top']
left = df_config.loc[i,'left']
divs[i+1]["style"] = f"width:{width};height:{height};position:absolute;top:{top};left:{left};"
body = html_bf.find("body")
body["style"]="background-color:#333333;"
# div_title = "<div align=\"center\" style=\"width:100%;\">\n<span style=\"font-size:30px;font face=\'微软雅黑\';color :#FFFFFF\"><b>CD_NOW数据看板</b></div>" # 修改页面背景色、追加标题
# body.insert(0, BeautifulSoup(div_title, "lxml").div)
html_new = str(html_bf)
html.seek(0, 0)
html.truncate()
html.write(html_new)
html.close()
当然也可以把那个json文件里面的值传到对应的样式代码中在HTML页面中进行布局,需要注意的是画图时指定好每个图表的chart_id,便于通过字符串格式进行指定对应图表位置。
注意点:(来自https://www.jianshu.com/p/47e8f056e5cf)
1.由于图片的布局是根据chart_config.json中图片id的对应关系进行布局,因此每张图片均需要指定其id;
如:
Bar(init_opts=opts.InitOpts(chart_id=1))
2.在本次操作工程中,发现Table类中不包含init_opts参数,可以通过修改源码,在D:\Anaconda3\Lib\site-packages\pyecharts\component\table指定char_id,具体修改的代码如下:
class Table(ChartMixin):
def __init__(self, page_title: str = CurrentConfig.PAGE_TITLE, js_host: str = "",chart_id=None):
self.page_title = page_title
self.js_host = js_host or CurrentConfig.ONLINE_HOST
self.js_dependencies: OrderedSet = OrderedSet()
self.js_functions: OrderedSet = OrderedSet()
self.title_opts: ComponentTitleOpts = ComponentTitleOpts()
self.html_content: str = ""
self._component_type: str = "table"
if chart_id:
self.chart_id: str = chart_id
else:
self.chart_id: str = uuid.uuid4().hex
https://www.bilibili.com/video/BV1KT4y1c7pb 网课里有资源自己去取