dash-plotly项目


dash-plotly项目的文件解压后如下:


将项目放到同一个局域网内的一台linux服务器上运行,服务器在局域网内的ip为10.3.135.103




一、将该项目在linux服务器上运行



先把pycharm连接到linux服务器上,参考链接:https://www.cnblogs.com/kakafa/p/18405178

配置本地目录和远程目录的映射,接着上传本地项目到远程服务器上:

可以看到linux系统上已经上传上来了:

使用conda创建一个虚拟环境:

进入该虚拟环境:

下载相关依赖:在当前虚拟环境下使用pip install <包名>
eg:

接着运行mydashapp.py发现需要用浏览器访问linux上的Dash应用程序提供的网页:

因此修改以下代码,并重新upload到linux服务器上,再重新运行mydashapp.py即可访问了,修改的地方如下:

  • 允许外部访问:默认情况下,Dash 应用会在 127.0.0.1(即 localhost)上运行,只能通过本机访问。为了让其他设备能够访问,您需要将应用绑定到 0.0.0.0,这样它就可以接受来自所有网络接口的请求。修改 app.run_server 的参数,如下所示:
if __name__ == '__main__':
    app.run_server(host='0.0.0.0', port=8050, debug=True)

  • 防火墙设置:确保服务器上的防火墙允许通过端口 8050 进行访问。可以使用以下命令检查和配置防火墙(假设使用的是 UFW):
sudo ufw allow 8050

如下图所示,可以访问了:




二、具体代码解释



mydashapp.py:

import numpy as np  # 提供支持大型、多维数组和矩阵的数据结构
import pandas as pd # 数据分析工具,尤其适用于表格数据和时间序列数据

import dash
from dash import dcc
from dash import html
#import dash_daq as daq
from dash.dependencies import Input, Output
import plotly.express as px

from mymodule import results
from mymodule import compare_g_dist_plotly, compare_g_dist_matplotlib
from mymodule import compare_s_dist_plotly, compare_s_dist_matplotlib

datasets = {
    "DFT-P" : { "type" : "DFT", "filename" : "P" },
    "DFT-H" : { "type" : "DFT", "filename" : "H" },
    "DFT-S" : { "type" : "DFT", "filename" : "S" },
    "DFT-G" : { "type" : "DFT", "filename" : "G" },
    "ML-only_E" : { "type" : "ML", "filename" : "a367e11_only_E" },
    "ML-only_P" : { "type" : "ML", "filename" : "a367e11_only_P" },
    "ML-only_H" : { "type" : "ML", "filename" : "a367e11_only_H" },
    "ML-only_S" : { "type" : "ML", "filename" : "a367e11_only_S" },
    "ML-only_G" : { "type" : "ML", "filename" : "a367e11_only_G" },
    "ML-1by1_H_P_S_E_G" : {
        "type" : "ML",
        "filename" : "a367e11_1by1_H_P_S_E_G" },
    "ML-1by1_P_S_G_H_E" : {
        "type" : "ML",
        "filename" : "a367e11_1by1_P_S_G_H_E" },
    "ML-onion_EGPHS_EPHS_EHS_EH_E" : {
        "type" : "ML",
        "filename" : "02923e5_onion_EGPHS_EPHS_EHS_EH_E" },
    "ML-onion-cleaned_EGPHS_EGPS_GPS_GS_S" : {
        "type" : "ML",
        "filename" : "30f5b2b_onion-cleaned_EGPHS_EGPS_GPS_GS_S" },
    "ML-onion-cleaned_EGPHS_EPHS_EHS_EH_E" : {
        "type" : "ML",
        "filename" : "30f5b2b_onion-cleaned_EGPHS_EPHS_EHS_EH_E" },
}

df_E = pd.read_csv("data_DFT/E.csv",index_col="mp_id") # 设置 "mp_id" 列作为索引

# 直方图的区间边界为:[-2, 0.05)、[0.05, 2.0)和[2.0, 20.0)  前闭后开区间
bins = np.array([-2, 0.05, 2.0, 20.0])
res = {}
options_list = []
for label, dataset in datasets.items():
    if dataset["type"] == "DFT":
        df_X = pd.read_csv("data_DFT/"+dataset["filename"]+".csv",index_col="mp_id")
        mp_ids = np.intersect1d(df_X.index,df_E.index)
        # np.intersect1d() 是NumPy库中的函数,用于计算两个数组的交集。它返回两个输入数组中共同存在的元素,并且结果是有序的且去重(即不包含重复元素)
        # 创建一个 DataFrame,并从两个不同的数据源中提取数据填充这个DataFrame
        g_df = pd.DataFrame(mp_ids,columns=["mp_id"])
        g_df["exp_gap"] = df_E["gap"].loc[mp_ids].values
        # df_E["gap"]:从 DataFrame df_E 中选择名为 gap 的列。
        # .loc[mp_ids]:根据 mp_ids 获取对应行的 gap 值。
        # .values:将得到的值转换为 NumPy 数组,以便直接赋值给新 DataFrame 的一列。
        # g_df["exp_gap"]:在 g_df 中新增一列,命名为 exp_gap,并将获取的值分配给这一列。
        g_df["model_gap"] = df_X["gap"].loc[mp_ids].values
        res[label] = results(g_df, label, bins) # results()用于计算与实验值和模型值之间的误差相关的各种统计信息
    elif dataset["type"] == "ML":
        g_df = pd.read_csv("data_ML/"+dataset["filename"]+".csv", usecols=["mp_id", "exp_gap", "model_gap"])
        res[label] = results(g_df, label, bins)
    options_list.append({ "label" : label, "value" : label })

app = dash.Dash(__name__) # 创建 Dash 应用


# html 和 dcc: 用于生成 HTML 元素和 Dash 控件
# 设置应用布局
app.layout = html.Div([
    html.Div([

        html.H1(children="Comparing error distributions"),

        html.Div(children = [
            html.Label(
                ["Plot mode:"],
                style={"font-weight": "bold", "text-align": "center"}
            ),

            html.Div(children = [
                dcc.Dropdown(
                    id = "plot_mode",
                    options = [
                        { "label" : "Global", "value" :  1 },
                        { "label" : "Split",  "value" : 10 },
                        { "label" : "Both",   "value" : 11 },
                    ],
                    value = 1,
                ),
            ]),
        ], style = {"width": "10%", "display": "inline-block"}),

        html.Div(children = [
            html.Label(
                ["Datasets:"],
                style = {"font-weight": "bold", "text-align": "center"}
            ),

            html.Div(children = [
                dcc.Dropdown(
                    id = "selected_datasets",
                    options = options_list,
                    value = ["DFT-P", "ML-only_P"],
                    multi = True,
                )
            ])
        ], style = {"width": "40%", "margin-left": "40px", "display": "inline-block"}),
            # style={"display": "inline-block"}: 这个样式让每个 div 以行内块的形式显示,使它们可以在同一行中并排显示。
            # div块默认是上下排列的
    ]),

    html.Div(children = [
        html.Div([
            dcc.Graph(id="fig1"),
        ], style={"display": "inline-block"} ),
        html.Div([
            dcc.Graph(id="fig2"),
        ], style={"display": "inline-block"} ),
    ])

])

# 当用户在下拉菜单中选择一个选项时,Dash 会自动调用update_graph回调函数,并将选择的值作为参数传入
@app.callback(
    [
        Output( component_id = "fig1", component_property = "figure"), # 更新 ID 为 fig1 的图表的 figure 属性
        Output( component_id = "fig2", component_property = "figure"),
        Output( component_id = "fig2_style", component_property = "style"), # 更新 ID 为 fig2 的图表的 style 属性(可以用于控制可见性或其他样式)
    ],
    Input("plot_mode", "value"), # 监视 ID 为 plot_mode 的组件的值变化(例如,可以是下拉选择框)
    Input("selected_datasets", "value")) # 监视 ID 为 selected_datasets 的组件的值变化(例如,可能是多选框或下拉列表)

# 这个函数用于更新图形
# 这个函数定义了,但没有任何地方调用它。因此,除非在其他地方(例如回调或事件处理器)显式调用它,否则它不会运行。
def update_graph(plot_mode, selected_datasets):

    g_dist_data = []
    s_dist_data = []

    for dataset in selected_datasets:
        g_dist_data.append(res[dataset].g_df["abs_error"].values)
        s_dist_data.append([res[dataset].s_df[i]["abs_error"].values for i in range(3)])

    if plot_mode % 10 == 1:
        fig1 = compare_g_dist_plotly(g_dist_data, selected_datasets)
    else:
        fig1 = compare_s_dist_plotly(s_dist_data, selected_datasets)
    fig1.update_layout(
        height = 800,
        width = 800,
        legend=dict( yanchor="top", y=0.99, xanchor="right", x=0.99 )
        # yanchor="top" 表示图例将以其顶部作为锚点进行定位;指定图例相对于容器高度位置的比例值(0 到 1),y=0.99 表示图例的顶部将位于图表区域的几乎最顶端,即 99% 的高度
    ) # update_layout() 是 Plotly 中用于更新图表布局的一个方法。通过这个方法,可以调整图表的大小、图例位置、标题、坐标轴等多种属性。
    if plot_mode < 11:
        fig2 = {}
        fig2_style = {"display": "none"}
    else:
        fig2 = compare_s_dist_plotly(s_dist_data, selected_datasets, show_legend = False)
        fig2.update_layout(height = 800, width = 800)
        legend=dict( yanchor="top", y=0.99, xanchor="left", x=0.01 )
        fig2_style = {"display": "inline-block"}

    return fig1, fig2, fig2_style


# 在 Python 文件中,if __name__ == "__main__": 语句用于检查该文件是否作为主程序直接运行。
# 如果是,那么该文件包含的代码块会被执行。
if __name__ == "__main__":
    app.run_server(host='0.0.0.0', port=8050,debug=True)


mymodule.app:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.graph_objs as go
import plotly.figure_factory as ff
from plotly.subplots import make_subplots

class results(object):
	def __init__(self, g_df, label, bins):
		self.g_df = g_df
		self.label = label
		self.g_df["error"] = self.g_df["model_gap"] - self.g_df["exp_gap"]
		self.g_df["abs_error"] = abs(self.g_df["error"])
		self.hist_exp, bin_edges = np.histogram(self.g_df["exp_gap"], bins=bins)
		# NumPy库中的np.histogram函数:计算给定数据集的直方图
		# 将数据分成多个区间(bins),并统计每个区间内的数据数量,这里是这几个区间:[-2, 0.05)、[0.05, 2.0)和[2.0, 20.0)
		self.hist_mod, bin_edges = np.histogram(self.g_df["model_gap"], bins=bins)
		# self.hist_mod:这是一个数组,包含每个区间(bin)中的数据计数。例如,如果第一个区间有 3 个数据点,第二个区间有 5 个数据点,第三个区间有 2 个数据点,则 self.hist_mod 将是 [3, 5, 2]
		# bin_edges:这是一个数组,包含每个区间的边缘值。在这个例子中,是 [-2.0, 0.05, 2.0, 20.0]
		self.g_N = np.sum(self.hist_exp)# np.sum()用于计算数组中元素的总和(包括一维数组和多维数组);这里指的是实验测出的gap数据的总个数
		self.s_N = self.hist_exp
		bin_group = np.digitize(self.g_df["exp_gap"], bins) - 1
		# 使用 numpy.digitize() 函数将 self.g_df["exp_gap"] 中的值分配到指定的区间(bins)中,并通过减去 1 来调整返回的索引,使其从 0 开始
		# numpy.digitize(x, bins, right=False)  x:要分类的输入数组; bins:用于定义区间边界的一维数组,该数组必须是单调递增的; right:True,则区间包含右端点; False(默认值),则不包含右端点。
		# bin_group是一个数组;用于存储每一个exp_gap随对应的组的索引;例如:某一个exp_gap值的bin_group值为0,即属于第0组的意思。因此该数组的数量和exp_gap的数量相同。
		bin_index = [np.where(bin_group == i)[0] for i in range(3)]
		# np.where(condition) 返回满足条件的元素的索引
		# 当i=0时:会返回 bin_group 中所有等于 0 的元素的索引,即他们在bin_group中的第几个位置;以此类推,当i=1/2时...
		# 最终bin_index的结果应该为一个二维的数组[array([0, 1,...]), array([3,...]), array([2,...])]
		# np.where(condition) 返回一个元组,使用 [0] 的目的是从这个元组中获取第一个元素,即实际的索引数组
		self.s_df = [self.g_df.loc[bin_index[i]] for i in range(3)]
		# 使用列表推导式来创建一个新的列表 self.s_df,其中的每个元素都是 self.g_df 的一个子集,这些子集基于 bin_index 中的索引。
		# self.s_df和self.s_N和self.hist_exp有啥区别?答:前者包含所有列的分组数据,后连两者只包含item的个数这一列的分组数据
		self.s_ME = np.array([
			np.sum(self.s_df[i]["error"]) / self.hist_exp[i]
			for i in range(3) ]) # 计算每一组的误差均值
		self.s_stdE = np.array([
			np.sum((self.s_df[i]["error"] - self.s_ME[i])**2) / self.s_N[i]
			for i in range(3) ]) # 计算每一组的方差均值,用于求标准差
		self.s_stdE = np.sqrt(self.s_stdE) # 标准差
		self.s_MAE = np.array([
			np.sum(self.s_df[i]["abs_error"]) / self.s_N[i]
			for i in range(3) ]) # 计算每一组的绝对误差均值
		self.g_ME = np.sum(self.s_ME * self.s_N) / self.g_N # 计算总体误差均值
		# σ² = ∑ᵢ (nᵢ((σᵢ)²+(μᵢ)²))/N - μ²
		self.g_stdE = np.sqrt(
			np.sum(self.s_N * (self.s_stdE**2 + self.s_ME**2)) / self.g_N -
			self.g_ME**2
			)
		self.g_MAE = np.sum(self.s_MAE * self.s_N) / self.g_N # 计算总体绝对误差均值
		self.max_index = [ self.s_df[i]["abs_error"].idxmax() for i in range(3) ]
		# 查找每个数据框中绝对误差(abs_error)的最大值所在的索引,并将这些索引存储在列表 self.max_index 中。
		self.s_Emax = np.array([ self.g_df["error"].loc[self.max_index[i]] for i in range(3) ])
		self.s_Emax_id = np.array([ self.g_df["mp_id"].loc[self.max_index[i]] for i in range(3) ])
		# 提取了每组的最大误差值和与之相关的标识符

	def print(self):
		print(
			"Model: ", self.dataset
			)
		print(
			"  hist  ->", # Histogram直方图
			np.array2string(self.hist_exp, formatter={'int':lambda x: "%7d" % x})
			)
		print(
			"  mean  ->", # formatter 参数指定了浮点数的格式,lambda函数将每个浮点数格式化为小数点后3位,总宽度为7个字符(包括小数点)
			np.array2string(self.s_ME, formatter={'float_kind':lambda x: "%7.3f" % x}),
			"{:7.3f}".format(self.g_ME) # "{:7.3f}": 这是一个字符串格式化语法,用于指定如何显示数字,它会同样以总宽度为7,小数点后保留3位数字来显示。
			)
		print(
			"  stdev ->",
			np.array2string(self.s_stdE, formatter={'float_kind':lambda x: "%7.3f" % x}),
			"{:7.3f}".format(self.g_stdE)
			)
		print(
			"  MAE   ->",
			np.array2string(self.s_MAE, formatter={'float_kind':lambda x: "%7.3f" % x}),
			"{:7.3f}".format(self.g_MAE)
			)
		print(
			"  max   ->",
			np.array2string(self.s_Emax, formatter={'float_kind':lambda x: "%7.3f" % x}),
			[ self.s_Emax_id[i] for i in range(3) ]
			)

# 比较多个分布的数据,创建一个子图,允许在一个图形中显示多个分组的直方图和密度图
def compare_s_dist_plotly(dist_data, dist_labels, show_legend = True):
	n_dist = len(dist_data)
	n_group = len(dist_data[0]) #计算的是有几个bin区间,有前面的bins确定

	fig = make_subplots(rows=n_group, cols=1) # 创建一个包含 n_group 行和 1 列的子图布局

	max_len = 0
	min_len = 1E10
	for dist in dist_data:  # 初始化最大和最小长度:max_len 和 min_len 用于跟踪每个组的分布数据的长度
		i_max = max([len(dist[i]) for i in range(n_group)]) # 选出最大的"abs_error"值
		if i_max > max_len:
			max_len = i_max
		if i_max < min_len:
			min_len = i_max
			
	for i in range(n_group): # 获得每个组的最大和最小长度
		hist_data = [] # hist_data 是一个列表,用于存储当前组的所有分布数据

		for dist in dist_data: # 将生成的数据或原始数据追加到 hist_data 中
			if len(dist[i]) == 1:# 如果某个组的分布长度为 1,则会通过复制其值并根据 max_len 和 min_len 的比例填充额外的数据,使得这个组在可视化时更具代表性。
				tmp_dist = np.append(dist[i].copy(), dist[i][0])
                # np.append()是NumPy库中的一个函数,用于将元素添加到数组的末尾。它返回一个新的数组,包含原始数组的所有元素以及要添加的元素。
				for k in range(50*int(max_len/min_len)):
					tmp_dist = np.append(tmp_dist, dist[i][0])
				tmp_dist = np.append(tmp_dist, [-min(1, 2*dist[i][0]), max(1, 2*dist[i][0])])
				hist_data.append(tmp_dist)
			else:
				hist_data.append(dist[i])

		fig_tmp = ff.create_distplot(hist_data, dist_labels) #使用 create_distplot 方法生成当前组的分布图对象,hist_data 和 dist_labels 被传入以创建相应的图表
        # ff.create_distplot() 是 Plotly 库中的一个函数,用于创建分布图(或称为密度分布图)

		for j in range(n_dist):
			show = False
			if i == 0 and show_legend == True: # i == 0表示当前正在处理第一个组(即索引为0的组)
				show = True
			fig.add_trace( # fig.add_trace 是用于向 Plotly 图形对象 (fig) 中添加一个新的绘图轨迹(trace)
				go.Scatter(fig_tmp["data"][n_dist+j], showlegend=show), # 使用 go.Scatter 将生成的图形添加到子图中。
				row=i+1, # row=i+1, col=1 指定将这个轨迹添加到子图中的特定位置。在这里,每个组占用一行,因此 row=i+1 确保每个组的数据被放置在正确的行上(因为 Plotly 的行数是从1开始计数的,而Python的索引是从0开始的)
				col=1
				)
				
	return fig 
    # 横坐标: 绝对误差值 (如 abs_error);纵坐标: 该误差值的出现次数或比例(频率);三个子图分别表示不同bin内的数据分布的情况[-2, 0.05)、[0.05, 2.0)和[2.0, 20.0) 不太理解这块是怎么得到的???纵坐标代表的是比率???

# 这个函数好像暂时没用到
def compare_s_dist_matplotlib(dist_data, dist_labels):
	n_dist = len(dist_data)
	n_group = len(dist_data[0])

	fig, axs = plt.subplots(nrows=n_group, ncols=1)

	for i in range(n_group):
		hist_data = []

		for dist in dist_data:
			sns.kdeplot(dist[i], ax=axs[i], shade=False)

	plt.legend(dist_labels, bbox_to_anchor=(1,2.5), loc="lower left")
	return fig, axs

def compare_g_dist_plotly(dist_data, dist_labels):
	n_dist = len(dist_data) # 确定有多少个分布数据

	fig = make_subplots() # 创建一个空的子图对象,虽然没有指定行和列,但可以根据需要进行扩展

	fig_tmp = ff.create_distplot(dist_data, dist_labels) # 使用 Plotly Figure Factory 创建分布图。这会生成一个包含数据和布局信息的字典。

	for j in range(n_dist):
		fig.add_trace(go.Scatter(fig_tmp["data"][n_dist+j], showlegend=True))
		# 将生成的散点图数据添加到主图中
		# go 是 plotly.graph_objects 的别名,提供了一系列图形对象类;Scatter 是一个类,用于创建散点图和折线图。它需要传入一些参数来定义该图的属性,例如 x 和 y 值、标记样式、线条样式等
		# n_dist + j 是一个索引,用于选择要添加到主图的 trace;在这里前 n_dist 个元素通常是密度曲线(每个分布的概率密度函数),紧接着可能会有直方图数据(如果使用了直方图),这些通常是以 n_dist 为基准的索引。
		# showlegend这是 go.Scatter 类的一个参数,控制该 trace 是否在图例中显示。如果设置为 True,则该 trace 将在图例中显示;如果设置为 False,则不会显示。在这里,showlegend=True 意味着每个散点图都会出现在图例中,从而允许用户识别每个分布对应的标签
	return fig

# 这个函数好像暂时没用到
def compare_g_dist_matplotlib(dist_data, dist_labels):
	n_dist = len(dist_data)

	fig, ax = plt.subplots()

	for dist in dist_data:
		sns.kdeplot(dist, ax=ax, shade=False)

	plt.legend(dist_labels, bbox_to_anchor=(1,0.57), loc="lower left")
	return fig, ax



简单介绍:


results类:用于分析模型预测与实验数据之间的误差,并提供了一些可视化的功能


  1. 初始化方法 (init):

接收三个参数:一个数据框 (g_df),一个标签 (label),以及直方图的分箱数量 (bins)。
计算模型的预测误差与绝对误差。
使用 NumPy 的直方图函数生成实验数据和模型数据的直方图。
将数据划分为多个组(3组),并根据每一组的数据计算不同的统计量(如均值、标准差、平均绝对误差等)。
最后,提取每组中绝对误差最大的索引及其对应的错误值。
打印方法 (print):


  1. 输出统计结果,包括直方图数据、均值、标准差、平均绝对误差以及最大误差值及其对应的 ID。


方法:compare_s_dist_plotly:

用于比较多个分布的 Plotly 可视化。
创建子图来显示每一组的分布情况,可以选择是否显示图例。

方法:compare_s_dist_matplotlib:

使用 Matplotlib 和 Seaborn 比较多个分布的可视化。
生成多个子图,每个子图展示一组数据的概率密度。

方法:compare_g_dist_plotly:

也是用于绘制多个分布的 Plotly 图形,但适用于不分组的简单数据。

方法:compare_g_dist_matplotlib:

使用 Matplotlib 和 Seaborn 绘制非分组的多个分布图


posted @ 2024-10-11 13:02  卡卡发  阅读(153)  评论(0)    收藏  举报