【问题标题】:Merging on closest value in SAS合并 SAS 中最接近的值
【发布时间】:2014-08-02 01:49:55
【问题描述】:

SAS 中有没有一种方法可以基于数字字段进行模糊left 合并?假设我有下面的两个表,并希望合并最接近的值。

数据集 A:

id_1    label
1       a
2       b
3       c
4       d
6       e

数据集 B:

id_2    
1.1     
2.9     
3.4     
4.05   
5.1

结果应该是:

id_2    id_1    label
1.1     1       a
2.9     3       c
3.4     3       c
4.05    4       d
5.1     6       e

请注意,由于 5.1 大小写,此处不能选择舍入。

【问题讨论】:

  • 技术上2.9 更接近3 然后3.4 更接近。因此,您的结果表与标签 C 的描述不匹配。
  • 另外,2.91.12 等距,所以实际上应该为 B 选择这些值中的哪一个?
  • @RobertPenr​​idge 2.9 和 3.4 都匹配 3,因为它们是数据集 A 中最接近的选项。
  • @RobertPenr​​idge 1.1 最接近 1,因此与 1 匹配。2.9 最接近 3,因此与 3 匹配。
  • 我想你误会了。标签b 的值为 2。1.1 和 2.9 都相距 0.9,那么应该选择哪一个?

标签: sas fuzzy-comparison


【解决方案1】:

一种方式,SQL 笛卡尔连接。这不是非常快,因此对于大型数据集来说,这不是一个好的解决方案。

data have_a;
input id_1    label $;
datalines;
1       a
2       b
3       c
4       d
6       e
;;;;
run;

data have_b;
input id_2 ;
datalines; 
1.1     
2.9     
3.4     
4.05   
5.1
;;;;
run;

proc sql;
create table want as
    select B.id_2, A.label , abs(A.id_1-B.id_2) as id_dist
    from have_a A, have_b B
    group by B.id_2
    having id_dist=min(id_dist);
quit;

可以根据每个数据集的大小构建其他解决方案(都非常大,一大一小,或者都小)。例如,PROC FORMAT 返回一个不错的结果。

data have_a_fmt;
retain fmtname 'HAVE_AF';
set have_a(rename=(id_1=startpoint label=startlabel));
set have_a(firstobs=2);
set have_a(firstobs=3 rename=(id_1=endpoint label=endlabel)) end=eof;
start=id_1-(id_1-startpoint)/2;
end  =id_1+(endpoint-id_1)/2;
output;

if _n_=1 then do;
  hlo='l';
  end=start;
  start=.;
  label=startlabel;
  output;
end;
if eof then do;
  start=end;
  end=.;
  hlo='h';
  label=endlabel;
  output;
end;
run;

proc format cntlin=have_a_Fmt;
quit;

data want;
set have_b;
label=put(id_2,HAVE_AF.);
run;

格式化解决方案非常快,除非 have_A 非常大(数百万+)。它的工作原理是进行前瞻和后瞻合并(使用集合,但概念相同)一次获得 3 个值,前一个当前值和下一个值,使用它们来定义范围,并添加第一行和最后一行与 'low ' 和 'hlo' 变量的 'high' 值(基本上将“负无穷大”和“正无穷大”定义为范围内的端点)。

【讨论】:

  • 这可能会返回平局,顺便说一下,您必须做一些事情来模糊结果以避免平局(也许添加一个很小的随机数)。
【解决方案2】:

这是我想出的,但可能需要根据对问题的澄清进行轻微修改:

proc sql noprint;
  create table final as
  select a.label,
         b.id_2
  from havea a
  join haveb b on 1 = 1
  left join haveb c on abs(c.id_2 - a.id_1) < abs(b.id_2 - a.id_1)
  where c.id_2 eq .
  ;
quit;

它与 Joe 的相似之处在于它使用笛卡尔连接(在 1=1 上连接),但不同之处在于它不使用 group-by 语句。

它通过第二次加入表haveb 来查看它是否可以找到更小的值差异。如果不能,那么我们知道我们有最小的行,并且我们通过在 where 子句中应用过滤器来保留这些行。

不确定是否存在任何性能差异。我认为 Joe 的 having 方法更易于阅读/理解,所以我会采用。

【讨论】:

    【解决方案3】:

    这在概念上可以更快,尽管它确实需要首先对数据进行排序...

    没有查找,只是一步一步的线性合并。

    您需要 3 个 SET 语句才能工作(一个用于当前数据,两个用于查找中的连续记录)。那么:

    直到(数据用完)

    1. 如果您用完了查找,请将最后一个设置为无穷大(或以其他方式使其无效)

    2. 如果第一个查找记录比第二个更接近,使用它并移动到下一个数据点,保持相同的查找记录

    3. 否则,将两个查找记录推进一步,保持相同的数据记录

    结束

    根据您是否要保持联系,将步骤 2 中的检查设为

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-06-12
      • 2021-06-22
      • 2020-05-30
      • 1970-01-01
      • 2016-11-24
      • 2021-04-11
      • 1970-01-01
      相关资源
      最近更新 更多