1341 字
7 分钟
使用Python+hvPlot绘制出版级可视化图表(附完整代码)

本文将以美国华盛顿2023年最高气温数据与1990至2020年日平均气温数据进行比较分析为例,介绍如何使用Python+hvPlot/HoloViews绘制更精致、更适合出版的可视化图表。

本文示例数据来源于爱荷华州立大学提供的爱荷华环境和天气监测网络-

Iowa Environmental Mesonet(IEM)的气象数据。

下面先贴上来绘制的图表结果,主要包括三个步骤(每步骤附代码)。第一步实现基于数据绘制的初级版本:

第二步实现更新颜色和样式的优化版本:

第三步实现最终版本:

  • 初级版本

  • 使用IEM提供的数据接口直接加载1928-2023年的华盛顿气象观测数据,并进行基本的数据处理,例如,将日期转换为日期时间对象以用作索引,删除一个未使用的station列,通过使用 float16类型减少内存占用,以及创建额外的字段供后续优化版本使用。

  • 计算1990-2020年的日平均气温。

  • 使用hvPlot绘制交互式图表,并将这一版本存储为first.html文件。

import pandas as pdimport hvplot.pandasimport holoviews as hvfrom bokeh.themes.theme import Theme
# 加载数据并进行基本预处理df = ( pd.read_csv( "https://mesonet.agron.iastate.edu/cgi-bin/request/daily.py?network=IA_ASOS&stations=AWG&year1=1928&month1=1&day1=1&year2=2023&month2=12&day2=31&var=max_temp_f&var=min_temp_f&var=precip_in&na=blank&format=csv", parse_dates=True, index_col="day", ) .drop(columns=["station"]) .astype("float16") .assign( dayofyear=lambda df: df.index.dayofyear, year=lambda df: df.index.year, ))
# 计算1990至2020年的日平均气温df_avg = df.loc[df["year"].between(1990, 2020)].groupby("dayofyear").mean()df_2023 = df[df.year == 2023]
# 绘制初级版本图表plot = df.hvplot(x="dayofyear", y="max_temp_f", by="year", color="grey", alpha=0.02, legend=False, hover=False)plot_avg = df_avg.hvplot(x="dayofyear", y="max_temp_f", color="grey", legend=False)plot_2023 = df_2023.hvplot(x="dayofyear", y="max_temp_f", color="black", legend=False)base = plot * plot_avg * plot_2023hv.save(base, "first.html")

30行代码实现了该可视化图表的初级版本。

  • 优化版本

由于hvPlot使用 HoloViews,而HoloViews使用Bokeh(或Matplotlib)作为绘图后端,因此我们需要对Bokeh应用某些自定义样式,以获得第二个更新的绘图。这些样式在 hvPlot / HoloViews 中是无法直接使用的。具体来说,我们需要创建一个绘图样式,并将其应用到HoloViews的对象中。在这里,我们要更新图表的背景颜色、移除图表的轮廓、调整网格和轴线,并更改标签的字体和颜色。实现代码如下:

theme = Theme( json={ "attrs": { "figure": { "background_fill_color": "#1b1e23", "border_fill_color": "#1b1e23", "outline_line_alpha": 0, }, "Grid": { "grid_line_color": "#808080", "grid_line_alpha": 0.1, }, "Axis": { # tick color and alpha "major_tick_line_color": "#4d4f51", "minor_tick_line_alpha": 0,
# tick labels "major_label_text_font": "Courier New", "major_label_text_color": "#808080", "major_label_text_align": "left", # axis labels "axis_label_text_font": "Courier New", "axis_label_text_font_style": "normal", "axis_label_text_color": "lightgrey", "axis_line_color": "#4d4f51", }, "Title": { "text_font": "Courier New", "text_font_style": "normal", "text_color": "lightgrey" }, } })
hv.renderer("bokeh").theme = theme

然后,我们要更新图表标题文字,只显示X轴上的网格,并更改X轴上刻度线的标注方式为月份。实现代码如下:

updated = (plot * plot_avg * plot_2023.opts(color="lightgrey")).opts( xlabel="TIME OF YEAR", ylabel="MAX TEMP °F",    title="WASHINGTON 2023 vs AVERAGE (1990-2020)", fontscale=1.18, gridstyle={"ygrid_line_alpha": 0}, show_grid=True, xticks=[ (1, "JAN"), (31, "FEB"), (59, "MAR"), (90, "APR"), (120, "MAY"), (151, "JUN"), (181, "JUL"), (212, "AUG"), (243, "SEP"), (273, "OCT"), (304, "NOV"), (334, "DEC"), ],)hv.save(updated, "second.html")

优化后的版如下:

  • 最终版本

要绘制最终版本的可视化图表,需要对数据进行更多的预处理操作:

  • 新创建一列用来存储 2023 年的气温数值和平均值的混合值。当 2023 年的温度超过平均值时,使用2023年的值,否则返回平均值。对于低于平均值的温度,也采用同样的逻辑。
  • 计算并存储高于或低于平均值的天数。
df_above = df_2023[["dayofyear", "max_temp_f"]].merge( df_avg.reset_index()[["dayofyear", "max_temp_f"]], on="dayofyear", suffixes=("_2023", "_avg"),)df_above["max_temp_f"] = df_above["max_temp_f_avg"]df_above["max_temp_f"] = df_above.loc[df_above["max_temp_f_2023"] >= df_above["max_temp_f_avg"], "max_temp_f_2023"]
df_below = df_2023[["dayofyear", "max_temp_f"]].merge( df_avg.reset_index()[["dayofyear", "max_temp_f"]], on="dayofyear", suffixes=("_2023", "_avg"),)df_below["max_temp_f"] = df_below["max_temp_f_avg"]df_below["max_temp_f"] = df_below.loc[df_below["max_temp_f_2023"] < df_below["max_temp_f_avg"], "max_temp_f_2023"]
days_above = df_above.query("max_temp_f_2023 >= max_temp_f_avg")["max_temp_f"].sizedays_below = df_below.query("max_temp_f_2023 < max_temp_f_avg")["max_temp_f"].size

接下来创建图表上的阴影样式:

  • 分别为红色和蓝色定义十六进制代码“#FF5555”、“#5588FF”,区域图和高于或低于平均值的天数数字都将使用这两个代码。然后,给两条曲线之间的区域着色。通过修改alpha(不透明度)和线条(边缘)+填充颜色,增加一些样式。
  • 接下来,对“高于(DAYS ABOVE)或低于(DAYS BELOW)平均值的天数“的文本进行设置。
dark_red = "#FF5555"dark_blue = "#5588FF"
plot_above = df_above.hvplot.area( x="dayofyear", y="max_temp_f_avg", y2="max_temp_f").opts(fill_alpha=0.2, line_alpha=0.8, line_color=dark_red, fill_color=dark_red)plot_below = df_below.hvplot.area( x="dayofyear", y="max_temp_f_avg", y2="max_temp_f").opts(fill_alpha=0.2, line_alpha=0.8, line_color=dark_blue, fill_color=dark_blue)
  • 为了防止上面/下面的文本重叠,将

    text_baseline设置为”bottom”和”top”。将”DAYS ABOVE”和”DAYS BELOW”文本与实际天数数值分开,并将其大小减半。

text_days_above = hv.Text( 35, df_2023["max_temp_f"].max(), f"{days_above}", fontsize=14).opts(text_align="right", text_baseline="bottom", text_color=dark_red, text_alpha=0.8)text_days_below = hv.Text( 35, df_2023["max_temp_f"].max(), f"{days_below}", fontsize=14).opts(text_align="right", text_baseline="top", text_color=dark_blue, text_alpha=0.8)text_above = hv.Text(38, df_2023["max_temp_f"].max(), "DAYS ABOVE", fontsize=7).opts( text_align="left", text_baseline="bottom", text_color="lightgrey", text_alpha=0.8)text_below = hv.Text(38, df_2023["max_temp_f"].max(), "DAYS BELOW", fontsize=7).opts( text_align="left", text_baseline="above", text_color="lightgrey", text_alpha=0.8)

最后进行最终版本图表的绘制:

final = ( plot * plot_avg * plot_above * plot_below * text_days_above * text_days_below * text_above * text_below).opts( xlabel="TIME OF YEAR", ylabel="MAX TEMP °F",    title="WASHINGTON 2023 vs AVERAGE (1990-2020)", gridstyle={"ygrid_line_alpha": 0}, xticks=[ (1, "JAN"), (31, "FEB"), (59, "MAR"), (90, "APR"), (120, "MAY"), (151, "JUN"), (181, "JUL"), (212, "AUG"), (243, "SEP"), (273, "OCT"), (304, "NOV"), (334, "DEC"), ], show_grid=True, fontscale=1.18,)hv.save(final, "final.html")


本公众号关于数据可视化相关的文章:

使用Python+hvPlot绘制出版级可视化图表(附完整代码)
https://blog.scidatalab.net/posts/使用python-hvplot绘制出版级可视化图表-附完整代码/
作者
Echo
发布于
2024-01-24
许可协议
CC BY-NC-SA 4.0