将SAS HPSPLIT输出评分代码转换为Python实现的方案问询
问题描述
我通过SAS的hpsplit过程构建模型,输出的评分代码是冗长无缩进的标准if-then格式,包含大量特征与分类变量,行数多达数千行。现在需要在Python中复现该模型的行为,寻求两种解决方案:
- 让SAS输出便于转换的结构化文件
- 将现有冗长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
相关产品推荐
相关产品推荐

