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类:用于分析模型预测与实验数据之间的误差,并提供了一些可视化的功能
- 初始化方法 (init):
接收三个参数:一个数据框 (g_df),一个标签 (label),以及直方图的分箱数量 (bins)。
计算模型的预测误差与绝对误差。
使用 NumPy 的直方图函数生成实验数据和模型数据的直方图。
将数据划分为多个组(3组),并根据每一组的数据计算不同的统计量(如均值、标准差、平均绝对误差等)。
最后,提取每组中绝对误差最大的索引及其对应的错误值。
打印方法 (print):
- 输出统计结果,包括直方图数据、均值、标准差、平均绝对误差以及最大误差值及其对应的 ID。
方法:compare_s_dist_plotly:
用于比较多个分布的 Plotly 可视化。
创建子图来显示每一组的分布情况,可以选择是否显示图例。
方法:compare_s_dist_matplotlib:
使用 Matplotlib 和 Seaborn 比较多个分布的可视化。
生成多个子图,每个子图展示一组数据的概率密度。
方法:compare_g_dist_plotly:
也是用于绘制多个分布的 Plotly 图形,但适用于不分组的简单数据。
方法:compare_g_dist_matplotlib:
使用 Matplotlib 和 Seaborn 绘制非分组的多个分布图
浙公网安备 33010602011771号