You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将SAS HPSPLIT输出评分代码转换为Python实现的方案问询

问题描述

我通过SAS的hpsplit过程构建模型,输出的评分代码是冗长无缩进的标准if-then格式,包含大量特征与分类变量,行数多达数千行。现在需要在Python中复现该模型的行为,寻求两种解决方案:

  1. 让SAS输出便于转换的结构化文件
  2. 将现有冗长SAS if-then代码转换为Python代码的方法

示例SAS建模代码:

proc hpsplit data=sashelp.baseball seed=123;
   class league division;
   model logSalary = nAtBat nHits nHome nRuns nRBI nBB
                     yrMajor crAtBat crHits crHome crRuns crRbi
                     crBB league division nOuts nAssts nError;
   output out=hpsplout;
   CODE FILE=  "&chemin./test_tree.sas";
run;

输出的SAS评分代码示例:

****************************************************************;
******        HP TREE (PROC HPSPLIT) SCORING CODE        ******;
****************************************************************;
 
******              LABELS FOR NEW VARIABLES              ******;
LABEL _Node_ = 'Node number';
LABEL _Leaf_ = 'Leaf number';
LABEL _WARN_ = 'Warnings';
LABEL P_logSalary = 'Predicted: logSalary';
 
 _WARN_ = ' ';
 
******      TEMPORARY VARIABLES FOR FORMATTED VALUES      ******;
 
******             ASSIGN OBSERVATION TO NODE             ******;
IF NOT MISSING(CrAtBat) AND ((CrAtBat < 1290.76))
 THEN DO;
  IF NOT MISSING(CrRbi) AND ((CrRbi < 58.5))
   THEN DO;
    _Node_ = 3;
    _Leaf_ = 0;
    P_logSalary = 4.5604216;
  END;
  ELSE DO;
    _Node_ = 4;
    _Leaf_ = 1;
    P_logSalary = 5.2806075;
  END;
END;
ELSE DO;
IF NOT MISSING(nBB) AND ((nBB >= 59.1))
 THEN DO;
  _Node_ = 6;
  _Leaf_ = 2;
  P_logSalary = 6.8728901;
END;
ELSE DO;
  IF NOT MISSING(CrRuns) AND ((CrRuns < 211.23))
   THEN DO;
    _Node_ = 7;
    _Leaf_ = 3;
    P_logSalary = 5.7858304;
  END;
  ELSE DO;
    IF NOT MISSING(nAtBat) AND ((nAtBat < 379))
     THEN DO;
      _Node_ = 9;
      _Leaf_ = 4;
      P_logSalary = 6.0810671;
    END;
    ELSE DO;
      _Node_ = 10;
      _Leaf_ = 5;
      P_logSalary = 6.5621752;
    END;
  END;
END;
END;
****************************************************************;
******     END OF HP TREE (PROC HPSPLIT) SCORING CODE    ******;
****************************************************************;

方案一:让SAS输出易于转换的结构化文件

1. 导出树结构元数据数据集

在proc hpsplit中添加OUTTREE=参数,将树的节点规则、分割条件、预测值等结构化信息导出到SAS数据集,后续可直接用Python读取解析:

proc hpsplit data=sashelp.baseball seed=123;
   class league division;
   model logSalary = nAtBat nHits nHome nRuns nRBI nBB
                     yrMajor crAtBat crHits crHome crRuns crRbi
                     crBB league division nOuts nAssts nError;
   output out=hpsplout;
   CODE FILE=  "&chemin./test_tree.sas";
   OUTTREE=tree_metadata; /* 新增参数导出树结构 */
run;

可以用pandas结合sas7bdat库读取该数据集,遍历节点规则直接生成Python预测逻辑,或者转换成scikit-learn兼容的树结构。

2. 导出PMML标准格式

若SAS版本支持,将模型导出为PMML(预测模型标记语言)——这是跨平台模型交换的标准格式,Python可通过pmml-evaluator或sklearn2pmml库加载执行预测:

proc hpsplit data=sashelp.baseball seed=123;
   class league division;
   model logSalary = nAtBat nHits nHome nRuns nRBI nBB
                     yrMajor crAtBat crHits crHome crRuns crRbi
                     crBB league division nOuts nAssts nError;
   output out=hpsplout;
   CODE FILE=  "&chemin./test_tree.sas";
run;

proc export data=tree_metadata
   file="&chemin./model.pmml"
   dbms=pmml replace;
run;

方案二:将SAS if-then评分代码转换为Python代码

1. 手动转换(适合小规模代码)

直接对应SAS逻辑转换为Python代码,核心语法映射:

  • NOT MISSING(var) → pd.notna(var)(数值型变量也可用~np.isnan(var))
  • IF ... THEN DO; ... END; → Python缩进代码块
  • 变量赋值直接对应

转换后的示例Python代码:

import pandas as pd
import numpy as np

def predict_log_salary(df):
    df['_WARN_'] = ''
    df['_Node_'] = np.nan
    df['_Leaf_'] = np.nan
    df['P_logSalary'] = np.nan

    # 第一分支
    mask1 = pd.notna(df['CrAtBat']) & (df['CrAtBat'] < 1290.76)
    # 子分支1
    mask1_1 = mask1 & pd.notna(df['CrRbi']) & (df['CrRbi'] < 58.5)
    df.loc[mask1_1, ['_Node_', '_Leaf_', 'P_logSalary']] = [3, 0, 4.5604216]
    # 子分支2
    mask1_2 = mask1 & ~mask1_1
    df.loc[mask1_2, ['_Node_', '_Leaf_', 'P_logSalary']] = [4, 1, 5.2806075]

    # 第二分支
    mask2 = ~mask1
    # 子分支1
    mask2_1 = mask2 & pd.notna(df['nBB']) & (df['nBB'] >= 59.1)
    df.loc[mask2_1, ['_Node_', '_Leaf_', 'P_logSalary']] = [6, 2, 6.8728901]
    # 子分支2
    mask2_2 = mask2 & ~mask2_1
    # 子子分支1
    mask2_2_1 = mask2_2 & pd.notna(df['CrRuns']) & (df['CrRuns'] < 211.23)
    df.loc[mask2_2_1, ['_Node_', '_Leaf_', 'P_logSalary']] = [7, 3, 5.7858304]
    # 子子分支2
    mask2_2_2 = mask2_2 & ~mask2_2_1
    # 子子子分支1
    mask2_2_2_1 = mask2_2_2 & pd.notna(df['nAtBat']) & (df['nAtBat'] < 379)
    df.loc[mask2_2_2_1, ['_Node_', '_Leaf_', 'P_logSalary']] = [9, 4, 6.0810671]
    # 子子子分支2
    mask2_2_2_2 = mask2_2_2 & ~mask2_2_2_1
    df.loc[mask2_2_2_2, ['_Node_', '_Leaf_', 'P_logSalary']] = [10, 5, 6.5621752]

    return df

2. 自动化脚本转换(适合数千行代码)

编写Python脚本解析SAS评分代码,核心步骤:过滤注释→识别条件块→转换SAS语法为Python→提取赋值逻辑。示例框架:

import re

def parse_sas_scoring_code(sas_code_path):
    with open(sas_code_path, 'r') as f:
        # 过滤注释和空行
        lines = [line.strip() for line in f if line.strip() and not line.startswith('*')]
    
    python_code = []
    python_code.append("import pandas as pd\nimport numpy as np\n\ndef predict(df):")
    python_code.append("    df['_WARN_'] = ''")
    python_code.append("    df['_Node_'] = np.nan")
    python_code.append("    df['_Leaf_'] = np.nan")
    python_code.append("    df['P_logSalary'] = np.nan")
    
    indent_level = 1
    current_conditions = []
    
    for line in lines:
        if line.startswith('IF'):
            # 提取条件表达式
            condition = re.search(r'IF (.*) THEN DO;', line).group(1)
            # SAS语法转Python
            condition = condition.replace('NOT MISSING(', 'pd.notna(')
            condition = condition.replace('AND', '&').replace('OR', '|')
            current_conditions.append(condition)
            mask_str = ' & '.join(current_conditions)
            python_code.append(f"{'    '*indent_level}mask = {mask_str}")
            indent_level += 1
        elif line.startswith('ELSE DO;'):
            # 取反上一个条件
            last_cond = current_conditions.pop()
            current_conditions.append(f"~({last_cond})")
            mask_str = ' & '.join(current_conditions)
            python_code.append(f"{'    '*(indent_level-1)}else:")
            python_code.append(f"{'    '*indent_level}mask = {mask_str}")
            indent_level += 1
        elif any(line.startswith(var) for var in ['_Node_', '_Leaf_', 'P_logSalary']):
            # 处理赋值语句
            var, val = line.split(' = ')
            val = val.rstrip(';')
            python_code.append(f"{'    '*indent_level}df.loc[mask, '{var}'] = {val}")
        elif line.startswith('END;'):
            indent_level -= 1
            if indent_level > 1:
                current_conditions.pop()
    
    python_code.append("    return df")
    return '\n'.join(python_code)

# 调用示例
# py_code = parse_sas_scoring_code('test_tree.sas')
# with open('predict_model.py', 'w') as f:
#     f.write(py_code)

注:需根据实际SAS代码格式调整正则表达式,比如处理分类变量的等值判断(如league='AL')时,要确保引号转换正确。


内容的提问来源于stack exchange,提问作者Lucas Morin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 05:14:50