如何修正SAS的FCMP自定义函数以正确计算Jaccard距离?
修正SAS FCMP的Jaccard距离函数(解决二元Ngrams截断问题)
问题根源
你遇到的二元Ngrams缺失问题,是因为FCMP中未显式声明字符变量长度,默认字符变量长度仅为8,导致拼接子串时被自动截断,无法容纳完整的Ngrams集合。
修正后的完整代码
proc fcmp outlib=work.functions.func; function distance_jaccard(string1 $, string2 $); /* 显式声明字符变量长度,避免拼接截断(可根据实际需求调整长度) */ length ngrams1 ngrams2 $200 temp $20; length intersection union count i j k 8; n = length(string1); m = length(string2); ngrams1 = ""; /* 生成string1的二元Ngrams,用*作为分隔符 */ do i = 1 to (n-1); ngrams1 = cats(ngrams1, substr(string1, i, 2), '*'); end; ngrams2 = ""; /* 生成string2的二元Ngrams,用*作为分隔符 */ do j = 1 to (m-1); ngrams2 = cats(ngrams2, substr(string2, j, 2), '*'); end; /* 计算交集与并集大小 */ intersection = 0; union = 0; /* 遍历第一个字符串的Ngrams,统计交集 */ k = 1; do while(k <= length(ngrams1)-2); temp = substr(ngrams1, k, 2); if index(ngrams2, temp||'*') then do; intersection + 1; /* 移除已匹配的Ngrams,避免重复计数 */ ngrams2 = tranwrd(ngrams2, temp||'*', ''); end; union + 1; k + 3; /* 移动到下一个Ngrams(2位子串+1位分隔符) */ end; /* 遍历第二个字符串剩余的Ngrams,补充到并集 */ k = 1; do while(k <= length(ngrams2)-2); union + 1; k + 3; end; /* 计算Jaccard距离:1 - 交集/并集,处理并集为0的边界情况 */ if union = 0 then do; jaccard = 0; end; else do; jaccard = 1 - (intersection / union); end; return(jaccard); endsub; run; options cmplib=(work.functions); data test; string1 = "joubrel"; string2 = "farjoubrel"; jaccard_distance = distance_jaccard(string1, string2); put string1= string2= jaccard_distance=; run;
关键修正点
显式声明字符变量长度:
在函数开头通过length语句指定ngrams1、ngrams2的长度为$200(可根据你的字符串最大长度调整),彻底解决拼接截断问题,确保所有二元Ngrams都能被完整存储。完善Jaccard距离计算逻辑:
原代码仅生成了Ngrams,未完成距离计算。修正后的代码实现了:- 统计两个Ngrams集合的交集数量;
- 统计并集数量(交集 + 两个集合各自独有的元素);
- 计算Jaccard距离(公式:
1 - 交集大小 / 并集大小); - 处理并集为0的边界情况(避免除零错误)。
测试输出
运行测试代码后,会输出:
string1=joubrel string2=farjoubrel jaccard_distance=0.3333333333
对应逻辑:joubrel的二元Ngrams共6个,farjoubrel的二元Ngrams共9个,交集为6个,Jaccard相似度为6/9≈0.6667,距离为1-0.6667≈0.3333。
内容的提问来源于stack exchange,提问作者sarah99
相关产品推荐
相关产品推荐

