许多模型在Python中的工作流程:第一部分

2021-03-30 00:12:34

今年夏天,我在Python的第一个实质性研究项目上工作。我以前使用过Python进行了多个小项目,但这是第一次对我来说很重要,以便一次与许多模特一起工作。在R中,我几乎所有的时间都使用了一个“许多模型”工作流程,它利用划分的列表列和巨大量的整个流行操作。它让我有一段时间在Python中找到一个类似的工作流,所以我正在为自己和可能受益的其他方记录在这里。

此帖子演示如何将模型组织到DataFrame以进行探索性数据分析,并讨论您可能想要执行此操作的原因。在未来的博客文章中,我将展示如何扩展我在此处展示的基本工作流程以处理样本拆分,自定义估算器和并行处理。

许多型号的工作流程是“拆分 - 应用组合”工作流程的扩展,在D次和Pandas中实现的数据操作的主要功能方法。 “拆分 - 申请结合”的基本思想在哈德利威克姆的短语中呈现出来,易于阅读纸张,我强烈鼓励您阅读。我假设您对这一普遍的想法感到满意,这在很大程度上促进了从数据中计算描述性统计数据的自然方式,并且在DOPLER,PANDAS或SQL中有一些体验应用这些概念。

几年前,这个想法进化了:如果,而不是计算描述性统计,我们想要计算更复杂的估算量,而利用分组操作的力量? Hadley的解决方案已被证明是非常富有成效的,并作为TidyModels,Tidyverts和其他几个建模框架的潜在想法是将模型对象本身放入DataFrame中。 Hadley介绍了这个想法,也在r的r的许多模型章节中撰写了关于数据科学的章节,它已成为相当富有成效的。

简单的答案是它会使您的模型与漂移分开的信息,因为它往往否则。

我想比较一系列封立的超参数值。经常有几个不同的超参数一次考虑1。

我想看看模型的许多不同的属性。作为一个创意的例子,我可能希望查看所有模型2的AIC,BIC,\(R ^ 2 \)和RMSE。

我想快速创建图表来比较这些模型,并且我可能是使用绘图库期望数据帧中的数据

无论如何,它就证明,只要我们有一些帮助者,Dataframes处理这个用例。整体工作流程将如下所示:

指定模型以适合:我们组织DataFrame,以便数据帧的每一行对应于我们想要适合的一个型号。例如,DataFrame的单行可以对应于单个的超参数值或数据的子集。

迭代模型拟合:我们在包含适合模型的DataFrame中创建一个新列。

迭代估计提取:我们从适合中提取我们想要的信息进入新的数据帧列,然后使用我们喜欢的DataFrame或绘图库操纵此数据。

请注意,步骤(2)和(3)需要在许多模型上进行迭代。在功能语言中,地图式操作是一种自然的(且易于平行化!)方式来做这件事;在Python中,我们可以使用列表 - 全局。

此工作流程中的另一个创新来自标准化步骤(3),其中我们将模型中的信息从模型中提取到DataFrame的新列。我们可以遇到的一个大问题是,当我们从模型对象中提取信息时,它可能会产生不方便的类型,很难放入DataFrame列中。这似乎是疏远的,但它比你预期的更多更重要。

他的扫帚包中的David Robinson提出了一个解决方案,该解决方案是R社区中的标准。这个想法是为模型对象创建特殊的吸气方法,以始终以一致格式的数据帧返回信息。对于每个模型对象,我们使用信息获取数据帧。由于有许多模型对象,我们最终结束了一列DataFrame,我们然后达到平坦。

围绕Python社区的一列Dataframe的想法有很多次要,主要是在这不是表演数据结构4的基础上。这是错过了这一点。这些工作流程中的计算时间来自模型配件,之后我们只想跟踪事物5。

无论如何,还有很多关于概念上工作流程的更多信息,但现在,现在是时候展示了一些代码,看看在实践中如何运作。对于这篇文章来说,我将重新创建哈德利在他的联系呈现中的分析,这是利用GapMinder数据。 GapMinder数据由1952年至2007年的每5年报告每5年的人寿期预期。

导入matplotlib.pyplot作为pltimport熊猫作为pdimport seabors作为snsimport statsmodels.formula.api作为smf#您可以从我的google drive#https://drive.google.com/file/d/1pagikdszpwytq2rfq14wruts23tg4lu / gapminder = pd.read_csv(' ../ data / gapminder.csv')gapminder

##乡村大陆流行GDPPERCAP ## 0 AFGHANISTAN亚洲1952年28.801 8425333 779.445314 ## 1阿富汗亚洲1962 31.997 10267083 853.100710#3 AFGHANISTAN ASIA 1967 34.020 11537966 836.100710303/297193030303030303030303030303030303030303030197 36.088 13079460 739.981106 ## ...... ...... ...... ...... ## 1699津巴布韦非洲1987 62.351 9216418 706.157306/17306/57306 ## 1700津巴布韦非洲1992 60.377 10704340 693.420786 ## 1701津巴布韦非洲1997年46.809 11404948 792.449960 ## 1702津巴布韦非洲2002 39.989 11926563 672.038623 ## 1703津巴布韦非洲2007 43.487 12311143 469.709298 #### [1704行x 6列]

为了让数据感受,让我们随着时间的推移绘制每个国家的预期寿命,由大陆的大陆平静。

在Hadley的演示之后,假设我们希望通过对来自每个国家的数据进行线性回归来总结每个国家的趋势。因此,我们有一个通信1〜来自1个国家的模型〜数据,并希望设置数据框架,以便每行对应来自单个国家/地区的数据。

groupby()加上一个列表 - 理解良好地处理,利用GapMinder.groupby('国家/地区')是一个迭代的事实。在r中,您还可以为此步骤使用Group_by(),或者另外嵌套()或rowwork(),两个tidyverse规范抽象。

模型= pd.dataframe({#这是作品,因为Pandas中的分组数据标记是可拍卖的#,因为你可以很多享受#,它们是名单'数据&#39 ;: [数据用于绘制的数据。 groupby(' country')],})models.index = [国家/地区,_在GapMinder.groupby(' Country')]#将奇怪的东西放入Pandas DataFrames的缺点是#dataframes打印不良修道框

## Data ##阿富汗国家大陆生命XP ... ##阿尔巴尼亚国家大陆年度Lifeexp Pop ... ##阿尔及利亚国家大陆年度Lifeexp Pop ... ##安哥拉乡村大陆Lifeexp Pop ... ##阿根廷国家大陆生命年份P ... ## ...... ##越南国家大陆年度Lifeexp P ... ##西岸和加沙乡村大陆生活... ##也门,代表。乡村大陆Lifeexp。 .. ##赞比亚国家大陆年度Lifeexp Po ... ##津巴布韦国家大陆年度Lifeexp ... ## ## [142行x 1列]

现在我们需要做实际的模型配件。我的首选方法是使用列表 - 全局。

def country_model(df):return smf.ols(' lifeexp〜年' data = df).fit()模型[' fit'] = [country_model(数据)for _, gapminder.groupby(' country')中的数据]

这种(有效地)在模型列表列中迭代的(有效地)功能方法的一个令人兴奋的优势是,大多数这些计算都是令人尴尬的平行,而映射() - 类似的操作通常很容易并行化。

这里的另一种方法是使用dataframe.apply()。但是,我已经找到了series.Apply()和dataframe.apply()方法难以推理,当与列表列一起使用时,我建议避免它们。

现在我们符合我们所有的模型,我们可以从他们那里提取信息。在这里,我将在扫帚的精神中非常多么辅助功能。当您不拥有您使用的模型类时,您几乎必须写入提取器功能来执行此操作;查看Michael Chow的优秀博客文章,显示如何以优雅的方式处理此操作。

即使您自己使用了您使用的模型对象,我建议使用类方法提取函数。这是因为,在EDA期间,您通常适用于一旦昂贵的模型,然后重复调查它们 - 您可以修改提取器功能并立即使用它,但如果修改模型类的方法,则必须重新修改所有模型对象。这导致缓慢迭代。

#直接从Michael Chow'博客帖子! )Rename_Cols = {'索引&#39 ;:'术语'' cof; cof&#39 ;:'估计&#39 ;,' std err&#39 ;: ' std_err'' t&#39 ;:'统计&#39 ;,' p> |' p_value', ' conf。 int。低价&#39 ;:' conf_int_low'和#39; conf。 int。 UPP。&#39 ;:' conf_int_high'返回tidied.rename(列= Rename_Cols)Def Glance(适合):返回PD.DATAFRAME({' AIC&#39 ;: fit.AIC,' BIC&#39 ;: fit.bic,&# 39; ess&#39 ;: fit.ess,#解释的正方形和#39; centered_tss&#39 ;: fit.centred_tss,' fvalue&#39 ;: fit.fvalue,' f_pvalue' f_pvalue' :fit.f_pvalue,' nobs&#39 ;: fit.nobs,' rsquared&#39 ;: fit.rsquared,' rsquared_adj&#39 ;: fit.rsquared_adj},index = [0] )#注意,Augment()需要2个输入,而Tidy()和Glance()取1def增强(适合,数据):df = data.copy()如果​​len(df)!= fit.nobs:提高valueerror(& #34;`数据`没有与训练数据相同的观察。")df ['拟合'] = fit.fittedvalues df [' resid'] = fit.resid返回df.

我们通过在使用型号的整个列表列之前,通过查看它们在单个模型对象上工作,查看辅助功能检查辅助功能。

##术语estimess std_err ... p_value conf_int_low conf_int_high ## 0拦截-507.534272 40.484162 ... 1.9340552-07 -597.738606 -597.738606 -417.329937 ## 1年0.275329 0.020451 ... 9.8352132 08 0.222961 0.320896 ##### [2行x 7列]

## AIC BIC ESS ... NOBS RSQUARED RSQUARED_ADJ ## 0 40.69387 41.663683 221.006011 ... 12.0 0.947712 0.942483 ## ## [1行x 9列]

Augment()实际上需要两个输入,其中一个输入是模型对象,另一个输入是用于适合该模型对象的训练数据。

##国大陆一年... gdpPercap装渣油## 0亚洲阿富汗1952年...... 779.445314 29.907295 -1.106295 ## 1阿富汗1957年亚洲... 820.853030 31.283938 -0.951938 ## 2阿富汗1962年亚洲... 853.100710 32.660582 -0.663582 ## 3 AFGHANISTAN亚洲1967 ... 836.197138 34.017225 -0.017225 -0.017225 -0.017225 ## 4 AFGHANISTAN ASIA 1972 ... 739.981106 35.413868 0.674132#6 AFGHANISTAN ASIA 1977 ... 786.113360 36.790512 1.6490512 1.6490512 1.6490512 1.6490512 1.647488# #7阿富汗亚洲1987 ... 852.395945 39.543798 1.278202#69.543798 1.278202 ## 8 AFGHANISTAN ASIONA ... 649.341395 40.920442 0.753558#6/920442 0.753558 ## 9阿富汗亚洲1997 ... 635.341351 42.297085 -0.534085 ## 10阿富汗亚洲2002 ... 726.734055 43.673728 -1.544728# #11阿富汗亚洲2007 ... 974.580338 45.050372 -1.222372 #### [12行x 8列]

现在我们已准备好迭代模型列表。再次,我们利用名单 - 全面了解。对于Tidy()和Glance()这些理解是简单的,但对于Augment(),这将每次消耗两列的元素,我们需要做一些小鸽友。在r,我们可以使用purrr :: map2()或purrr :: pmap(),但这里的pythonic idiom是用zip()与元组unpacking一起使用。

模型['整天'] = [整洁(适合)适用于型号.Fit]型号['镜孔闪烁'] = [GLANCE(FIT)适合型号.FIT]模型[ '增强'] = [增强(适合,数据)for fit,zip中的数据(model.fit,models.data)]

r用户注意:在r tidy()等的调用通常会在变异()呼叫中。 Pandas等效物分配,但Pandas不利用非标准评估,我通常不使用Assign(),除非我真的希望利用方法链接,因为某种原因。通常,一旦我有一个平面的DataFrame,我将保存用于数据操作的方法,否则我完全通过列表的全面操作。

##数据...增强##阿富汗国家大陆生命赛......国家大陆年...... Gdpperc ...... Gdpperc ... ##阿尔巴尼亚国家大陆年度Lifeexp Pop ......国家大陆年份Lifeexp Pop。 .. ##阿尔及利亚国家大陆Lifeexp Pop ......国家大陆年...... Gdppercap ... ##安哥拉乡村大陆Lifeexp Pop ......国家大陆年份Lifeexp Pop ... ##阿根廷乡村大陆生命克斯......国家大陆... gdpperc ... ## ...... ...... ...... ##越南国家大陆生命克斯......乡村大陆...... Gdpperca ... ##西岸和加沙乡村大陆生活......国家大陆年...... ##也门,代表。乡村大陆生命XP ...... 。国家大陆...... GDPP ...... ##赞比亚国家大陆生命XP宝......国家大陆...... Gdppercap ... ##津巴布韦国家大陆生命XP ......国家大陆一年... gdpperca ... ## ## [142行x 5列]

我们在我们可以重新创建哈德利的情节之前的最后一步是削弱或“不最愚蠢的”数据污染。 Pandas没有不最铭文的方法,但以下为我提供了很好的是,到目前为止是一个单一列。使用多线索的Dataframes不会很好地玩,我建议避免。

def Unnest(df,value_col):lst = df [value_col] .tolist()取消标准= pd.concat(lst,keys = df.index)Unnested.index = Unnested.Index.Droplevel(-1)返回df.join(未使用).drop(列= value_col)

Plance_results = Unnest(型号,'镜头')#等同viglance_results =(模型.pipe(unnest,'镜头吻')#小写额外清理.reset_index().rename(列= { '索引&#39 ;:'国家'}))glance_results

##国家...... rsquared_adj ## 0阿富汗... 0.942483 ## 1阿尔巴尼亚... 0.901636 ## 2阿尔及利亚... 0.983629 ##安哥拉... 0.876596 ## 4阿根廷... 0.995125 ##。 。... ... ... ## 137越南... 0.988353 ## 138西岸和加沙... 0.967529 ## 139也门,代表... 0.979290 ## 140赞比亚...... - 0.034180# #141 zimbabwe ... -0.038145 ## ## [142行x 14列]

现在我们可以问一个问题,比如“似乎有哪些国家在预期寿命中具有最线性趋势?”并使用R形被平方作为衡量标准。

好的,这剧情是可怕的,但目前我没有耐心,以使其更好。我们也可以查看个人适合的残差,以检查它们是否有可能表明系统错误的任何模式。

Augment_Results = Unnest(模型,'增强')p =(sns.facetgrid(data = augment_results,col ='大陆',col_wrap = 2,hue ='大陆&#39 ;,高度= 5,方面= 16/9).map(plt.plot,'年&#39 ;,' resid'))plt.show()

在哈德利曾经再次,我没有耐心或受虐狂的冲动,这将是由大陆添加平滑的难以弄清楚如何做到这一点。无论如何,残差有一些明显的趋势,特别是对于非洲,这表明一些进一步的建模是一个好主意。

所以这是许多模型工作流程背后的基本想法。请注意,我们一直在采用相当低的抽象。这意味着您对会发生的事情有很大的控制(适合研究和EDA),但必须编写大量代码。如果您只是拟合预测模型,并且您唯一想要做的是比较风险估计,您可以使用Sklearn的GridSearchCV对象节省时间和精力。

最后一个注意:在Hadley的GapMinder示例中,我们迭代不相交的数据集。在实践中,我很少这样做。更常见的是,我发现自己迭代(火车,测试)对或近似参数,或者一次。这种超级参数优化在许多CV折叠工作流程比这里的简单示例更复杂,但仍然适合我这里描述的许多模型工作流程。我将演示如何在后续帖子中做到这一点。

在Python中存储HyperParameters常常是有意义的。这意味着您无法轻易存储DICT中的模型对象,因为Model_Store [HyperParameters] = My_model_Object获取所有挑剔,因为HyperParameter字典不是Hashable。 ↩

这里的一种自然方法是有一个模型对象的列表,AIC值列表,BIC值列表等。现在您遇到了一个索引问题,在其中您必须弄清楚哪个索引对应于给定的模型和保留追踪像这样的一堆地图。自然解决方案是指符合匹配的所有索引 - 具有索引0的所有索引应对应于第一模型。恭喜,你刚刚发明了一个数据框架。 ↩

DataFrame的另一个大卖点是列上的矢量化操作。但是,您无法将操作与模型对象的操作保持一致,这导致必须显式执行Vectorize的代码,而不是让DataFrame库隐式地处理它。这里的新用户肯定有一个学习曲线,但我很确信它是值得的。 ↩

您可能具有Pythonista的另一个抱怨是您应该构建自定义模型对象以处理所有此模型跟踪。我真的很好奇这一点,很想听到想法。我的吊销是,大量挑战正在推出足够一般的框架,以适应各种不同的用例。 ↩

旁边:我发现在Python中策划是一个很大程度上不愉快的经历。在这一点上,我几乎在海运时定居为我的去绘制图书馆,我几乎所有的东西都使用sns.facetgrid,即使我不需要刻面,因为我的ggplot2直觉就可以了我可以大多数情况下完成了事情。 ↩