python 逐步回归

分析建模,日常问题整理(二十八)


2019.8.5~2019.9.14


  • 1 逐步回归

在训练评分卡模型的时候要注意系数全为正且具可解释性。
算法:
每一步加入一个变量,是否保留该变量取决于筛选标准;

  • 标准可是AIC,BIC,SSR,F显著性,t显著性等;
  • 在评分卡场景中,如果F显著,所有参数显著,且参数为正,则保留变量;
    下一轮迭代,重新从剩余变量中取出,参照以上规则。
    为了在模型的精确度和过拟合方面的权衡(模型方差和偏差的权衡),设定一些信息准则,通过加入模型复杂度的惩罚项来避免过拟合问题。损失函数控制了模型精度,惩罚项限制了模型的复杂度。
    AIC:赤池信息准则。aic = 2k-ln(L)
    ln(L)是似然函数:已知观察结果,使得能出现该结果的参数的条件概率。等价于在某参数情况下x的条件概率,L(θ|x)=P(X=x|θ)。极大似然估计(已知抽样结果,寻找出能使抽样结果出现可能性最大的参数估计)
    BIC:贝叶斯信息准则。bic = kln(n)-ln(L)
    n>=8时,BIC的第一项大于aic的第一项,bic更倾向于选择简单的模型。
    RSS/SSR:残差平方和\displaystyle\sum_{i=0}^{n} (predicted-observed)^2
    F:(R^2/k-1)/[(1-R^2)/n-k)]=(SSE/(k-1))/(SSR/(n-k))
    (逻辑回归logit输出的是 Pseudo R-squ.虚拟判定系数)
    借鉴的这篇文章啦(๑•ω•๑)
def stepwise(df, response, intercept=True, normalize=False, criterion='bic', 
             f_pvalue_enter=.05, p_value_enter=.05, direction='backward', show_step=True, 
             criterion_enter=None, criterion_remove=None,max_iter=200, **kw):
    '''
    逐步回归

    参数
    ----
    df : dataframe
        分析用数据框,response为第一列。
    response : str
        回归分析相应变量。
    intercept : bool, 默认是True
        模型是否有截距项。
    criterion : str, 默认是'bic'
        逐步回归优化规则。
    f_pvalue_enter : float, 默认是.05
        当选择criterion=’ssr‘时,模型加入或移除变量的f_pvalue阈值。
    p_value_enter : float, 默认是.05
        当选择derection=’both‘时,移除变量的pvalue阈值。
    direction : str, 默认是'backward'
        逐步回归方向。
    show_step : bool, 默认是True
        是否显示逐步回归过程。
    criterion_enter : float, 默认是None
        当选择derection=’both‘或'forward'时,模型加入变量的相应的criterion阈值。
    criterion_remove : float, 默认是None
        当选择derection='backward'时,模型移除变量的相应的criterion阈值。
    max_iter : int, 默认是200
        模型最大迭代次数。
    '''
    criterion_list = ['bic', 'aic', 'ssr', 'rsquared', 'rsquared_adj']
    if criterion not in criterion_list:
        raise IOError('请输入正确的criterion, 必须是以下内容之一:', '\n', criterion_list)

    direction_list = ['backward', 'forward', 'both']
    if direction not in direction_list:
        raise IOError('请输入正确的direction, 必须是以下内容之一:', '\n', direction_list)

    # 默认p_enter参数    
    p_enter = {'bic':0.0, 'aic':0.0, 'ssr':0.05, 'rsquared':0.05, 'rsquared_adj':-0.05}
    if criterion_enter:  # 如果函数中对p_remove相应key传参,则变更该参数
        p_enter[criterion] = criterion_enter

    # 默认p_remove参数    
    p_remove = {'bic':0.01, 'aic':0.01, 'ssr':0.1, 'rsquared':0.05, 'rsquared_adj':-0.05}
    if criterion_remove:  # 如果函数中对p_remove相应key传参,则变更该参数
        p_remove[criterion] = criterion_remove

    if normalize: # 如果需要标准化数据
        intercept = False  # 截距强制设置为0
        df_std = StandardScaler().fit_transform(df)
        df = pd.DataFrame(df_std, columns=df.columns, index=df.index)  

    ''' forward '''
    if direction == 'forward':
        remaining = list(df.columns)  # 自变量集合
        remaining.remove(response)
        selected = []  # 初始化选入模型的变量列表
        # 初始化当前评分,最优新评分
        if intercept: # 是否有截距
            formula = "{} ~ {} + 1".format(response, remaining[0])
        else:
            formula = "{} ~ {} - 1".format(response, remaining[0])

        result = smf.ols(formula, df).fit() # 最小二乘法回归模型拟合            
        current_score = eval('result.' + criterion)
        best_new_score = eval('result.' + criterion)

        if show_step:    
            print('\nstepwise starting:\n')
        iter_times = 0
        # 当变量未剔除完,并且当前评分更新时进行循环
        while remaining and (current_score == best_new_score) and (iter_times<max_iter):
            scores_with_candidates = []  # 初始化变量以及其评分列表
            for candidate in remaining:  # 在未剔除的变量中每次选择一个变量进入模型,如此循环
                if intercept: # 是否有截距
                    formula = "{} ~ {} + 1".format(response, ' + '.join(selected + [candidate]))
                else:
                    formula = "{} ~ {} - 1".format(response, ' + '.join(selected + [candidate]))

                result = smf.ols(formula, df).fit() # 最小二乘法回归模型拟合
                fvalue = result.fvalue
                f_pvalue = result.f_pvalue    
                params = result.params  
                t_pvalue = result.pvalues
                score = eval('result.' + criterion)                    
                scores_with_candidates.append((score, candidate, fvalue, f_pvalue,
                                               len([x for x in result.params[1:] if x<0]),
                                              len([x for x in result.pvalues[1:] if x>0.05]))) # 记录此次循环的变量、评分列表

            if criterion == 'ssr':  # 这几个指标取最小值进行优化
                scores_with_candidates.sort(reverse=True)  # 对评分列表进行降序排序
                best_new_score, best_candidate, best_new_fvalue, best_new_f_pvalue,len_paras_neg,len_tvaluenot = scores_with_candidates.pop()  
                # 提取最小分数及其对应变量
                if ((current_score - best_new_score) > p_enter[criterion]) and (best_new_f_pvalue < f_pvalue_enter) and len_paras_neg==0 and len_tvaluenot==0: 
                    # 如果当前评分大于最新评分
                    remaining.remove(best_candidate)  # 从剩余未评分变量中剔除最新最优分对应的变量
                    selected.append(best_candidate)  # 将最新最优分对应的变量放入已选变量列表
                    current_score = best_new_score  # 更新当前评分
                    if show_step:  # 是否显示逐步回归过程                             
                        print('Adding %s, SSR = %.3f, Fstat = %.3f, FpValue = %.3e' %
                              (best_candidate, best_new_score, best_new_fvalue, best_new_f_pvalue))
                elif (current_score - best_new_score) >= 0 and (best_new_f_pvalue < f_pvalue_enter) and iter_times == 0 and len_paras_neg==0 and len_tvaluenot==0: # 当评分差大于等于0,且为第一次迭代
                    remaining.remove(best_candidate)
                    selected.append(best_candidate)
                    current_score = best_new_score
                    if show_step:  # 是否显示逐步回归过程                             
                        print('Adding %s, %s = %.3f' % (best_candidate, criterion, best_new_score))
                elif (best_new_f_pvalue < f_pvalue_enter) and iter_times == 0:  # 当评分差小于p_enter,且为第一次迭代
                    selected.append(remaining[0])
                    remaining.remove(remaining[0])
                    if show_step:  # 是否显示逐步回归过程                             
                        print('Adding %s, %s = %.3f' % (remaining[0], criterion, best_new_score))
            elif criterion in ['bic', 'aic']:  # 这几个指标取最小值进行优化
                scores_with_candidates.sort(reverse=True)  # 对评分列表进行降序排序
                best_new_score, best_candidate, best_new_fvalue, best_new_f_pvalue,len_paras_neg,len_tvaluenot = scores_with_candidates.pop()  # 提取最小分数及其对应变量
                if (current_score - best_new_score) > p_enter[criterion] and len_paras_neg==0 and len_tvaluenot==0:  # 如果当前评分大于最新评分
                    remaining.remove(best_candidate)  # 从剩余未评分变量中剔除最新最优分对应的变量
                    selected.append(best_candidate)  # 将最新最优分对应的变量放入已选变量列表
                    current_score = best_new_score  # 更新当前评分
                    #print(iter_times)
                    if show_step:  # 是否显示逐步回归过程  
                        print('Adding %s, %s = %.3f' % (best_candidate, criterion, best_new_score))
                elif (current_score - best_new_score) >= 0 and iter_times == 0 and len_paras_neg==0 and len_tvaluenot==0: # 当评分差大于等于0,且为第一次迭代
                    remaining.remove(best_candidate)
                    selected.append(best_candidate)
                    current_score = best_new_score
                    if show_step:  # 是否显示逐步回归过程                             
                        print('Adding %s, %s = %.3f' % (best_candidate, criterion, best_new_score))
                elif iter_times == 0:  # 当评分差小于p_enter(这里是0),且为第一次迭代
                    selected.append(remaining[0])
                    remaining.remove(remaining[0])
                    if show_step:  # 是否显示逐步回归过程                             
                        print('Adding %s, %s = %.3f' % (remaining[0], criterion, best_new_score))
            else:
                scores_with_candidates.sort()
                best_new_score, best_candidate, best_new_fvalue, best_new_f_pvalue,len_paras_neg,len_tvaluenot = scores_with_candidates.pop() 
                if (best_new_score - current_score) > p_enter[criterion] and len_paras_neg==0 and len_tvaluenot==0:
                    remaining.remove(best_candidate)
                    selected.append(best_candidate)
                    current_score = best_new_score
                    print(iter_times, flush=True)
                    if show_step:  # 是否显示逐步回归过程                             
                        print('Adding %s, %s = %.3f' % (best_candidate, criterion, best_new_score))
                elif (best_new_score - current_score) >= 0 and iter_times == 0 and len_paras_neg==0 and len_tvaluenot==0: # 当评分差大于等于0,且为第一次迭代
                    remaining.remove(best_candidate)
                    selected.append(best_candidate)
                    current_score = best_new_score
                    if show_step:  # 是否显示逐步回归过程                             
                        print('Adding %s, %s = %.3f' % (best_candidate, criterion, best_new_score))
                elif iter_times == 0:  # 当评分差小于p_enter,且为第一次迭代
                    selected.append(remaining[0])
                    remaining.remove(remaining[0])
                    if show_step:  # 是否显示逐步回归过程                             
                        print('Adding %s, %s = %.3ssrf' % (remaining[0], criterion, best_new_score))
            iter_times += 1                        

        if intercept: # 是否有截距
            formula = "{} ~ {} + 1".format(response, ' + '.join(selected))
        else:
            formula = "{} ~ {} - 1".format(response, ' + '.join(selected))
        print('\n', formula)
        print(df.info())
        stepwise_model = smf.ols(formula, df).fit()  # 最优模型拟合

        if show_step:  # 是否显示逐步回归过程                
            print('\nLinear regression model:', '\n  ', stepwise_model.model.formula)
#             print('\n', stepwise_model.summary())
    return stepwise_model

from statsmodels.formula import api as smf
stepwise_ = stepwise(data.iloc[:,1:1000], 'y', intercept=True, normalize=False, criterion='bic', 
             f_pvalue_enter=.05, p_value_enter=.05, direction='forward', show_step=True, 
             criterion_enter=None, criterion_remove=None,max_iter=200)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,552评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,666评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,519评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,180评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,205评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,344评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,781评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,449评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,635评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,467评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,515评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,217评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,775评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,851评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,084评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,637评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,204评论 2 341