如何在SAS数据步中实现Jaccard宏解决匹配问题并排查报错
SAS Jaccard距离宏报错修复及字符串匹配方案
问题背景
尝试通过SAS宏实现Jaccard距离计算,为数据集添加距离列时,出现「变量名&无效」「keep选项值无效」错误。需求是解决数据集内两字符串列的匹配问题,同时处理输入数据错误。原代码如下:
data table_test; input nom1 $3. nom2 $3.; cards; abcade vdenfr azfefs ; run; %macro kshingling (string ,k=5 ,out=&sysmacroname. ) ; data &out.; string = strip(prxchange('s#\s# #',-1,symget('string'))); do _n_ = 1 to lengthn(string)-&k.+1; ngram = substr(string,_n_,&k.); output; end; run; %mend; %macro jaccard (string1 ,string2 ) ; %kshingling(&string1.,k=2,out=s1) %kshingling(&string2.,k=2,out=s2) proc append base=s1 data=s2; run; proc freq data=s1 noprint; tables string*ngram / out=s2; run; proc transpose data=s2 out=s1(drop=_name_ _label_); by string notsorted; var count; id ngram; run; proc stdize data=s1 out=s2 missing=0 reponly; var _numeric_; run; proc distance data=s2 method=jaccard absent=0 out=s1; var anominal(_numeric_); id string; run; data t(keep=&string1.); set s1(firstobs=2); run; data _null_; set t; call symput('Jaccard',&string1.); %put Distance de Jaccard = &Jaccard; run; %mend; data test; set table_test; call symput('n1',nom1); call symput('n2',nom2); %jaccard(&n1,&n2); run; data Jacc; Dist_Jacc=&Jaccard; run; data Final; merge table_test Jacc; run;
错误原因分析
- 宏执行时机冲突:SAS宏在数据步编译阶段执行,而
call symput在数据步执行阶段赋值。数据步内调用%jaccard(&n1,&n2)时,&n1和&n2尚未被赋值,宏解析后会生成无效变量名,导致keep=&string1.报错。 - 输入数据格式错误:
input nom1 $3. nom2 $3.;定义3字符长度,但输入数据每行是6个字符,且未按双变量格式分隔,导致字符串截断、变量值缺失,后续计算完全错误。 - 字符变量引用错误:
call symput('Jaccard',&string1.)中,&string1.为字符型变量,直接引用会被SAS当作代码解析,引发语法错误。
修复后的代码
步骤1:修正输入数据格式
确保完整读取字符串并匹配双变量输入格式:
data table_test; input nom1 $6. nom2 $6.; /* 匹配输入字符串的6字符长度 */ cards; abcade vdenfr /* 每行两个字符串用空格分隔 */ azfefs qwerty /* 新增测试行验证逻辑 */ xyzwab mnopqr ; run;
步骤2:重写Jaccard宏,支持批量处理
调整宏逻辑,直接接收数据集和列名参数,批量计算每行的Jaccard距离:
%macro kshingling(string, k=5, out=&sysmacroname.); data &out.; length string $200 ngram $&k.; string = strip(prxchange('s#\s+# #', -1, symget('string'))); do _i = 1 to lengthn(string) - &k. + 1; ngram = substr(string, _i, &k.); output; end; run; %mend; %macro jaccard(data=, var1=, var2=, out=jaccard_result); /* 创建结果存储数据集 */ data &out.; set &data.; length Dist_Jacc 8; _id = _n_; /* 生成行唯一标识,避免临时表冲突 */ run; /* 循环处理每行数据 */ %do _row = 1 %to %sysfunc(count(&data.)); /* 获取当前行的两个字符串及行ID */ data _null_; set &data.(firstobs=&_row. obs=&_row.); call symputx('_str1', &var1.); call symputx('_str2', &var2.); call symputx('_curr_id', _id); run; /* 生成ngram分词结果 */ %kshingling(&_str1., k=2, out=s1_&_row.) %kshingling(&_str2., k=2, out=s2_&_row.) /* 合并分词结果并计算词频 */ data s_comb_&_row.; set s1_&_row. s2_&_row.; run; proc freq data=s_comb_&_row. noprint; tables string*ngram / out=s_freq_&_row.; run; /* 转置为宽表格式 */ proc transpose data=s_freq_&_row. out=s_trans_&_row.(drop=_name_ _label_); by string notsorted; var count; id ngram; run; /* 填充缺失值为0 */ proc stdize data=s_trans_&_row. out=s_std_&_row. missing=0 reponly; var _numeric_; run; /* 计算Jaccard距离 */ proc distance data=s_std_&_row. method=jaccard absent=0 out=s_dist_&_row.; var anominal(_numeric_); id string; run; /* 提取距离值并更新结果表 */ data _null_; set s_dist_&_row.(firstobs=2); call symputx('_dist', &_str1.); run; data &out.; modify &out.; where _id = &_curr_id.; Dist_Jacc = &_dist.; replace; run; /* 清理临时表 */ proc datasets lib=work nolist; delete s1_&_row. s2_&_row. s_comb_&_row. s_freq_&_row. s_trans_&_row. s_std_&_row. s_dist_&_row.; run; %end; /* 删除临时行标识列 */ data &out.; set &out.; drop _id; run; %mend; /* 调用宏计算Jaccard距离 */ %jaccard(data=table_test, var1=nom1, var2=nom2, out=Final); /* 查看最终结果 */ proc print data=Final; run;
关键修复点说明
- 解决宏执行时机冲突:通过循环遍历数据集每行,在数据步执行阶段用
call symputx赋值后再调用计算逻辑,避免宏编译时变量未解析的问题。 - 修正输入数据格式:调整
input语句的字符长度匹配输入数据,规范每行双变量的输入格式,避免字符串截断和变量缺失。 - 修复字符变量引用错误:使用
call symputx替代call symput,确保字符变量正确解析,避免语法错误。 - 优化临时表管理:为每行生成带唯一后缀的临时表,避免多处理时的表名冲突,处理完成后自动清理临时表,节省资源。
内容的提问来源于stack exchange,提问作者sarah99
相关产品推荐
相关产品推荐

