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

如何在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;

错误原因分析

  1. 宏执行时机冲突:SAS宏在数据步编译阶段执行,而call symput在数据步执行阶段赋值。数据步内调用%jaccard(&n1,&n2)时,&n1和&n2尚未被赋值,宏解析后会生成无效变量名,导致keep=&string1.报错。
  2. 输入数据格式错误:input nom1 $3. nom2 $3.;定义3字符长度,但输入数据每行是6个字符,且未按双变量格式分隔,导致字符串截断、变量值缺失,后续计算完全错误。
  3. 字符变量引用错误: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 08:40:30