使用 Seaborn 和 Statsmodels 在一个图中显示数据和模型预测
Showing data and model predictions in one plot using Seaborn and Statsmodels
Seaborn 是一个很棒的软件包,可以进行一些高级绘图并输出漂亮的结果。但是,我在使用 Seaborn 叠加外部拟合模型的数据和模型预测方面遇到了一些困难。在这个例子中,我在 Statsmodels 中拟合模型,这些模型对于 Seaborn 来说太复杂了,无法开箱即用,但我认为这个问题更普遍(即,如果我有模型预测并想使用 Seaborn 将它们和数据可视化).
让我们从导入和数据集开始:
import numpy as np
import pandas as pd
import seaborn as sns
import statsmodels.formula.api as smf
import patsy
import itertools
import matplotlib.pyplot as plt
np.random.seed(12345)
# make a data frame with one continuous and two categorical variables:
df = pd.DataFrame({'x1': np.random.normal(size=100),
'x2': np.tile(np.array(['a', 'b']), 50),
'x3': np.repeat(np.array(['c', 'd']), 50)})
# create a design matrix using patsy:
X = patsy.dmatrix('x1 * x2 * x3', df)
# some random beta weights:
betas = np.random.normal(size=X.shape[1])
# create the response variable as the noisy linear combination of predictors:
df['y'] = np.inner(X, betas) + np.random.normal(size=100)
我们在包含所有预测变量及其相互作用的 statsmodels 中拟合模型:
# fit a model with all interactions
fit = smf.ols('y ~ x1 * x2 * x3', df).fit()
print(fit.summary())
由于在这种情况下我们指定了所有变量组合,并且我们的模型预测是线性的,因此足以绘制新的 "predictions" 列到包含模型预测的数据框中。然而,这不是很普遍(假设我们的模型是非线性的,所以我们希望我们的图显示平滑的曲线),所以我用所有预测变量组合制作一个新的数据框,然后生成预测:
# create a new dataframe of predictions, using pandas' expand grid:
def expand_grid(data_dict):
""" A port of R's expand.grid function for use with Pandas dataframes.
from http://pandas.pydata.org/pandas-docs/stable/cookbook.html?highlight=expand%20grid
"""
rows = itertools.product(*data_dict.values())
return pd.DataFrame.from_records(rows, columns=data_dict.keys())
# build a new matrix with expand grid:
preds = expand_grid(
{'x1': np.linspace(df['x1'].min(), df['x1'].max(), 2),
'x2': ['a', 'b'],
'x3': ['c', 'd']})
preds['yhat'] = fit.predict(preds)
preds
数据框如下所示:
x3 x1 x2 yhat
0 c -2.370232 a -1.555902
1 c -2.370232 b -2.307295
2 c 3.248944 a -1.555902
3 c 3.248944 b -2.307295
4 d -2.370232 a -1.609652
5 d -2.370232 b -2.837075
6 d 3.248944 a -1.609652
7 d 3.248944 b -2.837075
由于 Seaborn 绘图命令(与 R 中的 ggplot2
命令不同)似乎接受一个 且仅接受一个 数据帧,我们需要将我们的预测合并到原始数据中:
# append to df:
merged = df.append(preds)
我们现在可以绘制模型预测和数据,我们的连续变量 x1
作为 x 轴:
# plot using seaborn:
sns.set_style('white')
sns.set_context('talk')
g = sns.FacetGrid(merged, hue='x2', col='x3', size=5)
# use the `map` method to add stuff to the facetgrid axes:
g.map(plt.plot, "x1", "yhat")
g.map(plt.scatter, "x1", "y")
g.add_legend()
g.fig.subplots_adjust(wspace=0.3)
sns.despine(offset=10);
到目前为止一切顺利。现在假设我们没有测量连续变量 x1
,我们只知道其他两个(分类)变量(即,我们有一个 2x2 因子设计)。 在这种情况下,我们如何根据数据绘制模型预测?
fit = smf.ols('y ~ x2 * x3', df).fit()
print(fit.summary())
preds = expand_grid(
{'x2': ['a', 'b'],
'x3': ['c', 'd']})
preds['yhat'] = fit.predict(preds)
print(preds)
# append to df:
merged = df.append(preds)
好吧,我们可以使用 sns.pointplot
或类似的方法绘制模型预测,如下所示:
# plot using seaborn:
g = sns.FacetGrid(merged, hue='x3', size=4)
g.map(sns.pointplot, 'x2', 'yhat')
g.add_legend();
sns.despine(offset=10);
或者像这样使用 sns.factorplot
的数据:
g = sns.factorplot('x2', 'y', hue='x3', kind='point', data=merged)
sns.despine(offset=10);
g.savefig('tmp3.png')
但我看不出如何生成类似于第一个的图(即使用 plt.plot
的模型预测线,使用 plt.scatter
的数据散点)。原因是我试图用作 x 轴的 x2
变量是一个字符串/对象,所以 pyplot 命令不知道如何处理它们。
正如我在评论中提到的,我会考虑两种方法。
首先是定义一个函数进行拟合,然后绘制并传递给FacetGrid.map
:
import pandas as pd
import seaborn as sns
tips = sns.load_dataset("tips")
def plot_good_tip(day, total_bill, **kws):
expected_tip = (total_bill.groupby(day)
.mean()
.apply(lambda x: x * .2)
.reset_index(name="tip"))
sns.pointplot(expected_tip.day, expected_tip.tip,
linestyles=["--"], markers=["D"])
g = sns.FacetGrid(tips, col="sex", size=5)
g.map(sns.pointplot, "day", "tip")
g.map(plot_good_tip, "day", "total_bill")
g.set_axis_labels("day", "tip")
第二个是计算预测值,然后将它们与一个附加变量合并到您的 DataFrame 中,该变量标识什么是数据,什么是模型:
tip_predict = (tips.groupby(["day", "sex"])
.total_bill
.mean()
.apply(lambda x: x * .2)
.reset_index(name="tip"))
tip_all = pd.concat(dict(data=tips[["day", "sex", "tip"]], model=tip_predict),
names=["kind"]).reset_index()
sns.factorplot("day", "tip", "kind", data=tip_all, col="sex",
kind="point", linestyles=["-", "--"], markers=["o", "D"])
Seaborn 是一个很棒的软件包,可以进行一些高级绘图并输出漂亮的结果。但是,我在使用 Seaborn 叠加外部拟合模型的数据和模型预测方面遇到了一些困难。在这个例子中,我在 Statsmodels 中拟合模型,这些模型对于 Seaborn 来说太复杂了,无法开箱即用,但我认为这个问题更普遍(即,如果我有模型预测并想使用 Seaborn 将它们和数据可视化).
让我们从导入和数据集开始:
import numpy as np
import pandas as pd
import seaborn as sns
import statsmodels.formula.api as smf
import patsy
import itertools
import matplotlib.pyplot as plt
np.random.seed(12345)
# make a data frame with one continuous and two categorical variables:
df = pd.DataFrame({'x1': np.random.normal(size=100),
'x2': np.tile(np.array(['a', 'b']), 50),
'x3': np.repeat(np.array(['c', 'd']), 50)})
# create a design matrix using patsy:
X = patsy.dmatrix('x1 * x2 * x3', df)
# some random beta weights:
betas = np.random.normal(size=X.shape[1])
# create the response variable as the noisy linear combination of predictors:
df['y'] = np.inner(X, betas) + np.random.normal(size=100)
我们在包含所有预测变量及其相互作用的 statsmodels 中拟合模型:
# fit a model with all interactions
fit = smf.ols('y ~ x1 * x2 * x3', df).fit()
print(fit.summary())
由于在这种情况下我们指定了所有变量组合,并且我们的模型预测是线性的,因此足以绘制新的 "predictions" 列到包含模型预测的数据框中。然而,这不是很普遍(假设我们的模型是非线性的,所以我们希望我们的图显示平滑的曲线),所以我用所有预测变量组合制作一个新的数据框,然后生成预测:
# create a new dataframe of predictions, using pandas' expand grid:
def expand_grid(data_dict):
""" A port of R's expand.grid function for use with Pandas dataframes.
from http://pandas.pydata.org/pandas-docs/stable/cookbook.html?highlight=expand%20grid
"""
rows = itertools.product(*data_dict.values())
return pd.DataFrame.from_records(rows, columns=data_dict.keys())
# build a new matrix with expand grid:
preds = expand_grid(
{'x1': np.linspace(df['x1'].min(), df['x1'].max(), 2),
'x2': ['a', 'b'],
'x3': ['c', 'd']})
preds['yhat'] = fit.predict(preds)
preds
数据框如下所示:
x3 x1 x2 yhat
0 c -2.370232 a -1.555902
1 c -2.370232 b -2.307295
2 c 3.248944 a -1.555902
3 c 3.248944 b -2.307295
4 d -2.370232 a -1.609652
5 d -2.370232 b -2.837075
6 d 3.248944 a -1.609652
7 d 3.248944 b -2.837075
由于 Seaborn 绘图命令(与 R 中的 ggplot2
命令不同)似乎接受一个 且仅接受一个 数据帧,我们需要将我们的预测合并到原始数据中:
# append to df:
merged = df.append(preds)
我们现在可以绘制模型预测和数据,我们的连续变量 x1
作为 x 轴:
# plot using seaborn:
sns.set_style('white')
sns.set_context('talk')
g = sns.FacetGrid(merged, hue='x2', col='x3', size=5)
# use the `map` method to add stuff to the facetgrid axes:
g.map(plt.plot, "x1", "yhat")
g.map(plt.scatter, "x1", "y")
g.add_legend()
g.fig.subplots_adjust(wspace=0.3)
sns.despine(offset=10);
到目前为止一切顺利。现在假设我们没有测量连续变量 x1
,我们只知道其他两个(分类)变量(即,我们有一个 2x2 因子设计)。 在这种情况下,我们如何根据数据绘制模型预测?
fit = smf.ols('y ~ x2 * x3', df).fit()
print(fit.summary())
preds = expand_grid(
{'x2': ['a', 'b'],
'x3': ['c', 'd']})
preds['yhat'] = fit.predict(preds)
print(preds)
# append to df:
merged = df.append(preds)
好吧,我们可以使用 sns.pointplot
或类似的方法绘制模型预测,如下所示:
# plot using seaborn:
g = sns.FacetGrid(merged, hue='x3', size=4)
g.map(sns.pointplot, 'x2', 'yhat')
g.add_legend();
sns.despine(offset=10);
或者像这样使用 sns.factorplot
的数据:
g = sns.factorplot('x2', 'y', hue='x3', kind='point', data=merged)
sns.despine(offset=10);
g.savefig('tmp3.png')
但我看不出如何生成类似于第一个的图(即使用 plt.plot
的模型预测线,使用 plt.scatter
的数据散点)。原因是我试图用作 x 轴的 x2
变量是一个字符串/对象,所以 pyplot 命令不知道如何处理它们。
正如我在评论中提到的,我会考虑两种方法。
首先是定义一个函数进行拟合,然后绘制并传递给FacetGrid.map
:
import pandas as pd
import seaborn as sns
tips = sns.load_dataset("tips")
def plot_good_tip(day, total_bill, **kws):
expected_tip = (total_bill.groupby(day)
.mean()
.apply(lambda x: x * .2)
.reset_index(name="tip"))
sns.pointplot(expected_tip.day, expected_tip.tip,
linestyles=["--"], markers=["D"])
g = sns.FacetGrid(tips, col="sex", size=5)
g.map(sns.pointplot, "day", "tip")
g.map(plot_good_tip, "day", "total_bill")
g.set_axis_labels("day", "tip")
第二个是计算预测值,然后将它们与一个附加变量合并到您的 DataFrame 中,该变量标识什么是数据,什么是模型:
tip_predict = (tips.groupby(["day", "sex"])
.total_bill
.mean()
.apply(lambda x: x * .2)
.reset_index(name="tip"))
tip_all = pd.concat(dict(data=tips[["day", "sex", "tip"]], model=tip_predict),
names=["kind"]).reset_index()
sns.factorplot("day", "tip", "kind", data=tip_all, col="sex",
kind="point", linestyles=["-", "--"], markers=["o", "D"])