Logo
Section 1 / 7

可视化回归图

学习如何利用 Plotly 图形来帮助在 Python 中可视化和分析回归模型。

导入

可视化回归图

学习如何利用 Plotly 图形来帮助在 Python 中可视化和分析回归模型。

散点图平滑

散点图平滑是一种统计技术,用于通过创建一条平滑估计的曲线来理解数据中的总体模式,该曲线表示两个变量 x 和 y 之间的关系。它特别适用于评估可能影响变量间关系的二元异常值和有影响力的观测值。


这是一个关键的宏观经济数据集:J. W. Longley (1967) 从用户角度对最小二乘程序的评估。《美国统计协会杂志》62, 819–841。它记录了以下经济变量:

  • GNP.deflator:GNP隐含价格折算指数(1954=100)

  • GNP:国民生产总值

  • Unemployed:失业人数

  • Armed.Forces:武装部队人数

  • Population:14岁及以上非机构人口

  • Year:年份(时间)

  • Employed:就业人数

注意:该数据集常用于演示将 Employed 对其余变量进行回归时会导致多重共线性问题。

1# 导入库 2import pandas as pd 3import numpy as np 4 5# 导入数据集 6longley = pd.read_csv('/usr/local/csvfiles/longley.csv') 7print(longley.head())

线性回归

Plotly Express 允许我们通过指定 x 和 y 变量来为数据集拟合线性回归模型。我们通过添加 trendline="ols" 参数来说明希望使用普通最小二乘回归模型。可以使用 trendline_color_override 设置线条颜色。悬停在趋势线上时,可以获得斜率、截距、决定系数 (R2R^2) 以及预测值 y^\hat{y} 的信息。

在此示例中,我们将 Employed 变量与 GNP 进行绘制。

1# 导入库 2import pandas as pd 3import numpy as np 4import plotly.express as px 5import plotly.graph_objects as go 6 7# 导入数据集 8longley = pd.read_csv('/usr/local/csvfiles/longley.csv') 9 10# 创建 OLS 图形 11fig = px.scatter(data_frame=longley, 12 x="GNP", 13 y="Employed", 14 color_discrete_sequence=['black'], 15 trendline="ols", # 在此处查看趋势线 16 trendline_color_override='magenta') 17 18# 格式化并显示 19fig.update_layout(title='OLS 回归线') 20fig.show()

Lowess 曲线

Lowess(局部加权散点图平滑) 是一种非参数回归方法(不假设数据分布),用于在数据上拟合一条平滑曲线。

由于 Lowess 算法的特性,它可以比普通最小二乘回归提供更灵活的拟合,能够捕捉数据中更复杂的模式。然而,这种更精细的拟合也带来了过拟合的风险,我们必须始终保持警惕。

在 Plotly 中,我们可以叠加一条 LOWESS 曲线,这次设置 trendline='lowess'。

1# 导入库 2import pandas as pd 3import numpy as np 4import plotly.express as px 5import plotly.graph_objects as go 6 7# 导入数据集 8longley = pd.read_csv('/usr/local/csvfiles/longley.csv') 9 10# 创建 LOWESS 图形 11fig = px.scatter(data_frame=longley, 12 x="GNP", 13 y="Employed", 14 color_discrete_sequence=['black'], 15 trendline='lowess', # 注意此处参数不同 16 trendline_color_override="green") 17 18# 格式化并显示 19fig.update_layout(title='Lowess 曲线') 20fig.show()

我们还可以利用 Lowess 曲线拟合中的 frac 参数,这是一个用于调整 Lowess 方法的调参参数。它控制应用于数据的平滑程度,较大的值会产生更平滑的拟合,对数据波动更不敏感。我们需要在过度泛化和对数据微小变化反应过度之间取得平衡。

在 Plotly 中实现此功能,可以通过设置 trendline_options 为包含所有模型特定选项的字典来完成。

为了演示目的,我们可以尝试几个不同的 frac 值,观察它们对图表的影响:

1# 导入库 2import pandas as pd 3import numpy as np 4import plotly.express as px 5import plotly.graph_objects as go 6 7# 导入数据集 8longley = pd.read_csv('/usr/local/csvfiles/longley.csv') 9 10# 设置 frac 值 11frac_value = 0.2 12fig = px.scatter(data_frame=longley, 13 x="GNP", 14 y="Employed", 15 trendline='lowess', 16 trendline_color_override="black", 17 trendline_options=dict(frac=frac_value)) 18 19fig.update_layout(title=f"LOWESS 平滑 (frac={frac_value})") 20 21fig.show()

我们将创建一个 frac 值为 0.5 的折线图:

1# 导入库 2import pandas as pd 3import numpy as np 4import plotly.express as px 5import plotly.graph_objects as go 6 7# 导入数据集 8longley = pd.read_csv('/usr/local/csvfiles/longley.csv') 9 10# 设置 frac 值 11frac_value = 0.5 12fig = px.scatter(data_frame=longley, 13 x="GNP", 14 y="Employed", 15 trendline='lowess', 16 trendline_color_override="black", 17 trendline_options=dict(frac=frac_value)) 18 19fig.update_layout(title=f"LOWESS 平滑 (frac={frac_value})") 20 21fig.show()

现在,我们将创建一个更高 frac 值为 0.8 的折线图:

1# 导入库 2import pandas as pd 3import numpy as np 4import plotly.express as px 5import plotly.graph_objects as go 6 7# 导入数据集 8longley = pd.read_csv('/usr/local/csvfiles/longley.csv') 9 10# 设置 frac 值 11frac_value = 0.8 12fig = px.scatter(data_frame=longley, 13 x="GNP", 14 y="Employed", 15 trendline='lowess', 16 trendline_color_override="black", 17 trendline_options=dict(frac=frac_value)) 18 19fig.update_layout(title=f"LOWESS 平滑 (frac={frac_value})") 20 21fig.show()

滚动均值或移动平均

滚动均值是另一种统计数据平滑方法(传统上用于时间序列),通过计算固定数量相邻数据点的平均值来实现。pandas 文档指出,我们可以直接将列出的参数(如 window、min_periods 等)传入 trendline_options。更改 window 和 min_periods 的值,观察这些参数的变化如何影响结果。

1# 导入库 2import pandas as pd 3import numpy as np 4import plotly.express as px 5import plotly.graph_objects as go 6 7# 导入数据集 8longley = pd.read_csv('/usr/local/csvfiles/longley.csv') 9 10# 设置窗口细节 11window = 3 12min_periods = 1 13 14# 创建带有窗口细节的图形 15fig = px.scatter(data_frame=longley, 16 x="GNP", 17 y="Employed", 18 trendline='rolling', 19 trendline_color_override="black", 20 trendline_options=dict(window=window, min_periods=min_periods)) 21 22fig.update_layout(title=f"移动平均 (window={window}, min_periods={min_periods})") 23fig.show()

pandas 文档还提到一个 win_type 参数,如果以字符串形式传入,则必须是有效的 scipy.signal 窗口函数。假设我们希望使用 Kaiser 窗口平滑数据,并搜索一些可能的 β\beta 值,这可以通过 function_args 配置,其本身接受一个字典,其中包含传递给特定 win_type(例如 "kaiser")的函数参数。查阅文档可知,Kaiser 窗口根据 β\beta 的值可以近似其他方法。

我们将循环遍历一系列 β\beta 值,并为每个值创建一个图表,保持 window 和 min_periods 不变。别忘了设置 trendline='rolling' 以实现移动平均曲线。

我们利用熟悉的子图功能将这些图表整合到一个图形中。

1# 导入库 2import pandas as pd 3import numpy as np 4import plotly.express as px 5import plotly.graph_objects as go 6from plotly.subplots import make_subplots 7 8# 导入数据集 9longley = pd.read_csv('/usr/local/csvfiles/longley.csv') 10 11# 设置 beta 值 12betas = [0, 1, 5, 10] 13window = 3 14min_periods = 1 15 16# 设置子图及标题 17fig = make_subplots(rows=len(betas), cols=1, 18 shared_xaxes=True, 19 vertical_spacing=0.1, 20 subplot_titles=[f"移动平均 (window={window}, min_periods={min_periods}, beta={beta})" for beta in betas]) 21 22# 循环遍历 beta 值并创建子图 23for i, beta in enumerate(betas): 24 subplot = px.scatter( 25 data_frame=longley, 26 x="GNP", 27 y="Employed", 28 trendline='rolling', 29 trendline_color_override="black", 30 # 设置趋势线选项 31 trendline_options=dict( 32 window=window, 33 min_periods=min_periods, 34 win_type="kaiser", 35 function_args=dict(beta=beta))) 36 37 # 从子图中提取散点和趋势线轨迹 38 scatter_trace = subplot.data[0] 39 trendline_trace = subplot.data[1] 40 41 # 将散点和趋势线轨迹添加到子图 42 fig.add_trace(scatter_trace, row=i+1, col=1) 43 fig.add_trace(trendline_trace, row=i+1, col=1) 44 45 # 设置 x 轴和 y 轴标签 46 fig.update_xaxes(title_text='GNP', row=i+1, col=1) 47 fig.update_yaxes(title_text='Employed', row=i+1, col=1) 48 49# 添加总体标题并显示 50fig.update_layout(height=1200, width=800, title_text="多个移动平均子图") 51fig.show()

简单指数平滑

通过简单指数平滑创建指数加权移动平均是一种数据平滑方法(传统上用于时间序列),它给予近期数据点更高的权重,同时逐步降低旧数据点的权重。

在这里,我们可以调整 alpha 参数,它控制平滑的程度。pandas 文档指出,我们可以传递其他参数而非 alpha(例如 com、span、halflife)。

如果我们使用 alpha,请注意:

0<α≤10 < \alpha \leq 1

我们现在循环遍历不同的 α\alpha 值,设置 trendline='ewm' 和 trendline_options=dict(alpha=alpha),在循环中改变 α\alpha 值。较高的 alpha 值会产生更灵活的拟合,但必须小心避免过拟合。

1# 导入库 2import pandas as pd 3import numpy as np 4import plotly.express as px 5import plotly.graph_objects as go 6from plotly.subplots import make_subplots 7 8# 导入数据集 9longley = pd.read_csv('/usr/local/csvfiles/longley.csv') 10 11# 设置 alpha 值 12alphas = [0.1, 0.3, 0.5, 0.7, 0.9] 13 14# 设置子图及标题 15fig = make_subplots(rows=len(alphas), cols=1, 16 shared_xaxes=True, 17 vertical_spacing=0.1, 18 subplot_titles=[f"移动平均 (alpha={alpha})" for alpha in alphas]) 19 20# 循环遍历 alpha 值并创建子图 21for i, alpha in enumerate(alphas): 22 subplot = px.scatter(data_frame=longley, 23 x="GNP", 24 y="Employed", 25 trendline='ewm', 26 trendline_color_override="red", 27 trendline_options=dict(alpha=alpha)) 28 29 # 从子图中提取散点和趋势线轨迹 30 scatter_trace = subplot.data[0] 31 trendline_trace = subplot.data[1] 32 33 # 将散点和趋势线轨迹添加到子图 34 fig.add_trace(scatter_trace, row=i+1, col=1) 35 fig.add_trace(trendline_trace, row=i+1, col=1) 36 37 # 设置 x 轴和 y 轴标签 38 fig.update_xaxes(title_text='GNP', row=i+1, col=1) 39 fig.update_yaxes(title_text='Employed', row=i+1, col=1) 40 41# 添加总体标题并显示 42fig.update_layout(height=1400, width=800, title_text="多个移动平均子图") 43fig.show()

扩展窗口计算

扩展窗口函数是一种时间序列分析方法,涉及一个随新数据点出现而不断扩大的滑动窗口。在此方法中,窗口大小随时间增长,意味着对数据执行的计算会持续更新,并包含迄今为止观察到的所有数据。

在 pandas 库的上下文中,可以使用 expanding() 方法执行扩展窗口计算,所有可能的参数均可在 pandas 文档中找到。在 Plotly 中,我们可以传入 trendline='expanding'。

1# 导入库 2import pandas as pd 3import numpy as np 4import plotly.express as px 5import plotly.graph_objects as go 6from plotly.subplots import make_subplots 7 8# 导入数据集 9longley = pd.read_csv('/usr/local/csvfiles/longley.csv') 10 11# 创建图形 12fig = px.scatter(data_frame=longley, 13 x="GNP", 14 y="Employed", 15 trendline='expanding', # 此处设置趋势线参数 16 trendline_color_override="purple" 17 ) 18 19# 添加总体标题并显示 20fig.update_layout(title=f"扩展均值窗口计算") 21fig.show()

其他聚合函数

在前面的例子中,聚合函数始终是均值。然而,我们可以在 trendline_options 中传入不同的聚合函数,如 median、min、max 等。由于我们将使用滚动窗口计算,因此还需要向 trendline_options 传入窗口大小。

1# 导入库 2import pandas as pd 3import numpy as np 4import plotly.express as px 5import plotly.graph_objects as go 6from plotly.subplots import make_subplots 7 8# 导入数据集 9longley = pd.read_csv('/usr/local/csvfiles/longley.csv') 10 11# 设置聚合函数 12aggfuncs = ['mean', 'median', 'min', 'max'] 13 14# 设置子图及标题 15fig = make_subplots(rows=len(aggfuncs), cols=1, 16 shared_xaxes=True, 17 vertical_spacing=0.1, 18 subplot_titles=[f"滚动 {aggfunc} 计算 (function={aggfunc})" for aggfunc in aggfuncs]) 19 20# 循环遍历聚合函数并创建子图 21for i, aggfunc in enumerate(aggfuncs): 22 subplot = px.scatter(data_frame=longley, 23 x="GNP", 24 y="Employed", 25 trendline='rolling', 26 trendline_color_override="limegreen", 27 trendline_options=dict(window=3, function=aggfunc)) 28 29 # 从子图中提取散点和趋势线轨迹 30 scatter_trace = subplot.data[0] 31 trendline_trace = subplot.data[1] 32 33 # 将散点和趋势线轨迹添加到子图 34 fig.add_trace(scatter_trace, row=i+1, col=1) 35 fig.add_trace(trendline_trace, row=i+1, col=1) 36 37 # 设置 x 轴和 y 轴标签 38 fig.update_xaxes(title_text='GNP', row=i+1, col=1) 39 fig.update_yaxes(title_text='Employed', row=i+1, col=1) 40 41# 添加总体标题并显示 42fig.update_layout(height=1400, width=800, title_text="多个聚合函数子图") 43fig.show()

超越二维空间

当有两个自变量和一个因变量时,我们也可以使用三维散点图来可视化数据。这里我们将使用 advertising 数据,研究在 TV 和 Radio 广告上的支出如何影响 Sales 数值。我们还将拓展思路,引入 scikit-learn 回归模型。

首先,我们以三维方式绘制数据。x 轴表示 Radio 广告支出,y 轴表示 TV 广告支出,z 轴表示对应的 Sales 值。

对于三维散点图,我们可以以稍有不同的方式调整轴标题,这次将包含所有轴标题的字典传递给 fig.update_layout() 中的 scene 参数。

...
1# 导入库 2import pandas as pd 3import numpy as np 4 5# 导入数据集 6advertising = pd.read_csv('/usr/local/csvfiles/advertising.csv') 7print(advertising.head())

让我们创建三维图表:

1# 导入库 2import pandas as pd 3import numpy as np 4import plotly.express as px 5import plotly.graph_objects as go 6from plotly.subplots import make_subplots 7 8# 导入数据集 9advertising = pd.read_csv('/usr/local/csvfiles/advertising.csv') 10 11# 创建带有 x-y-z 的轨迹 12trace = go.Scatter3d( 13 x=advertising['Radio'], 14 y=advertising['TV'], 15 z=advertising['Sales'], 16 mode='markers', 17 marker=dict(size=3, color='black'), 18 # 设置一些悬停详情 19 hovertemplate='Radio=%{x}<br>TV=%{y}<br>Sales=%{z}<extra></extra>') 20 21# 将图形添加到轨迹 22fig = go.Figure(data=[trace]) 23 24# 显示前的一些格式化 25title_settings = go.layout.Title( 26 text="广告数据", x=0.5, y=0.9, font=dict(size=30)) 27# 设置不同轴的标题 28fig.update_layout(title=title_settings, 29 scene=dict(xaxis_title='Radio' 30 , yaxis_title='TV' 31 , zaxis_title='Sales')) 32fig.show()

现在我们将使用 scikit-learn 来可视化训练好的 DecisionTreeRegressor 模型如何学习数据中的模式。

我们创建一个矩阵 X,存储我们的自变量 ['Radio', 'TV']。我们还定义了我们要建模的因变量(即目标变量)在名为 y 的变量中。

然后我们使用 sklearn 中的 train_test_split 函数,将 70% 的数据用于构建模型,30% 用于评估模型在未见数据上的性能。接着我们将模型拟合到训练数据。这通过以下代码完成:

1X = advertising[['Radio', 'TV']] 2y = advertising['Sales'] 3 4X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7, random_state=1) 5 6model = DecisionTreeRegressor().fit(X_train.values, y_train.values)

然后我们可以使用以下代码生成广泛可能值范围内的模型预测。这主要是为了能够查看决策树回归器拟合的数据表面。

1x_min, x_max = X_test['Radio'].min(), X_test['Radio'].max() 2y_min, y_max = X_test['TV'].min(), X_test['TV'].max() 3xrange = np.linspace(x_min, x_max, 100) 4yrange = np.linspace(y_min, y_max, 100) 5xx, yy = np.meshgrid(xrange, yrange) 6Z = np.c_[xx.ravel(), yy.ravel()] 7 8predictions = model.predict(Z) 9predictions = predictions.reshape(xx.shape)

我们还可以使用以下代码获取模型在未见测试数据上的 R2R^2(回归模型常用的评估指标):

1r2 = model.score(X_test.values, y_test.values)

然后我们可以转向 Plotly 图形对象,定义两个轨迹:一个是三维原始数据的散点图,另一个是使用 go.Scatter3d 和 go.Surface 分别由 DecisionTreeRegressor 生成的表面。

1trace1 = go.Scatter3d(x=X_test['Radio'], 2 y=X_test['TV'], 3 z=y_test, 4 mode='markers', 5 marker=dict(size=3, color='black'), 6 hovertemplate='Radio=%{x}<br>TV=%{y}<br>Sales=%{z}<extra></extra>') 7 8trace2 = go.Surface(x=xrange, 9 y=yrange, 10 z=predictions, 11 colorscale=[[0, 'yellow'], [1, 'aqua']], 12 hovertemplate='Radio=%{x}<br>TV=%{y}<br>预测销售额=%{z}<extra></extra>') 13 14 15fig = go.Figure(data=[trace1, trace2]) 16 17fig.update_layout(title=go.layout.Title(text=f"预测表面 (未见测试数据的 R^2 = {r2})", x=0.5, y=0.9), 18 scene=dict(xaxis_title='Radio',yaxis_title='TV', zaxis_title='Sales')) 19fig.show()

让我们看一个实际例子:

1# 导入库 2import pandas as pd 3import numpy as np 4import plotly.express as px 5import plotly.graph_objects as go 6from sklearn.model_selection import train_test_split 7from sklearn.tree import DecisionTreeRegressor 8 9# 导入数据集 10advertising = pd.read_csv('/usr/local/csvfiles/advertising.csv') 11 12# 创建 X 和 Y 变量 13X = advertising[['Radio', 'TV']] 14y = advertising['Sales'] 15 16# 拆分数据集 17X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7, random_state=1) 18 19# 创建模型(拟合并训练) 20model = DecisionTreeRegressor().fit(X_train.values, y_train.values) 21 22# 使用数据集值设置三维空间 23x_min, x_max = X_test['Radio'].min(), X_test['Radio'].max() 24y_min, y_max = X_test['TV'].min(), X_test['TV'].max() 25xrange = np.linspace(x_min, x_max, 100) 26yrange = np.linspace(y_min, y_max, 100) 27xx, yy = np.meshgrid(xrange, yrange) 28Z = np.c_[xx.ravel(), yy.ravel()] 29 30# 生成预测并存储在变量中 31predictions = model.predict(Z) 32predictions = predictions.reshape(xx.shape) 33 34# 计算模型的 R 平方值 35r2 = model.score(X_test.values, y_test.values) 36 37# 创建实际值与预测值的散点图 38trace1 = go.Scatter3d(x=X_test['Radio'], 39 y=X_test['TV'], 40 z=y_test, 41 mode='markers', 42 marker=dict(size=3, color='black'), 43 hovertemplate='Radio=%{x}<br>TV=%{y}<br>Sales=%{z}<extra></extra>') 44trace2 = go.Surface(x=xrange, 45 y=yrange, 46 z=predictions, 47 colorscale=[[0, 'yellow'], [1, 'aqua']], 48 hovertemplate='Radio=%{x}<br>TV=%{y}<br>预测销售额=%{z}<extra></extra>') 49 50# 将图形添加到绘图并显示 51fig = go.Figure(data=[trace1, trace2]) 52fig.update_layout(title=go.layout.Title(text=f"预测表面 (未见测试数据的 R^2 = {r2})", x=0.5, y=0.9), 53 scene=dict(xaxis_title='Radio',yaxis_title='TV', zaxis_title='Sales')) 54fig.show()

这是本课程中所有代码练习的笔记本:

探索性数据分析 - 双变量与多变量分析

练习:可视化回归图