在 SAS 中查找并替换来自单独 table 的值

Look up and replace values from a separate table in SAS

数据集 HAVE 包含两个名称拼写错误的变量:namesfriends

Name   Age   Friend
Jon     11   Ann
Jon     11   Tom
Jimb    12   Egg
Joe     11   Egg
Joe     11   Anne
Joe     11   Tom
Jed     10   Ann

我有一个小数据集 CORRECTIONS,其中包括 wrong_namesresolved_names

current_names   resolved_names
Jon             John
Ann             Anne
Jimb            Jim

我需要 namesfriends 中的任何名称与 CORRECTIONSwrong_names 列中的名称匹配才能重新编码到resolved_name 中对应的字符串。生成的数据集 WANT 应如下所示:

Name   Age   Friend
John    11   Anne
John    11   Tom
Jim     12   Egg
Joe     11   Egg
Joe     11   Anne
Joe     11   Tom
Jed     10   Anne

在 R 中,我可以使用 if_else() 简单地调用每个数据帧和向量,但 SAS 中的 DATA 步骤不能很好地处理多个数据集。我如何使用 CORRECTIONS 作为查找来进行这些替换 table?

试试这个:

proc sql;
create table want as
    select p.name,p.age,
        case 
            when q.current_names is null then p.friend 
            else q.resolved_names 
        end 
    as friend1
        from
            (
        select 
            case 
                when b.current_names is null then a.name 
                else b.resolved_names 
            end 
        as name,
            a.age,a.friend
        from
            have a
        left join
            corrections b
            on upcase(a.name) = upcase(b.current_names)
            ) p
        left join
            corrections q
            on upcase(p.friend) = upcase(q.current_names);
quit;  

输出:

name age friend
John 11  Anne
Jed  10  Anne
Joe  11  Anne
Jim  12  Egg
Joe  11  Egg
Joe  11  Tom
John 11  Tom

如有任何说明,请告诉我。

您可以在 proc sql 中使用 UPDATE :

proc sql ;
  update have a
    set name   = (select resolved_names b from corrections where a.name   = b.current_names)
    where name in(select current_names from corrections) 
  ;
  update have a
    set friend = (select resolved_names b from corrections where a.friend = b.current_names)
    where friend in(select current_names from corrections)
  ;
quit ;

或者,您可以使用以下格式:

/* Create format */
data current_fmt ;
  retain fmtname 'NAMEFIX' type 'C' ;
  set resolved_names ;
  start = current_names ;
  label = resolved_names ;
run ;
proc format cntlin=current_fmt ; run ;

/* Apply format */
data want ;
  set have ;
  name   = put(name  ,$NAMEFIX.) ;
  friend = put(friend,$NAMEFIX.) ;
run ;

在 SAS 中有很多方法可以进行查找。

但是,首先,我建议对您的查找进行重复数据删除 table(例如,使用 PROC SORT 和数据 Step/Set/By)- 决定保留哪些重复项(如果任何存在)。

至于查找任务本身,为了简单和学习,我建议如下:

"OLD SCHOOL" 方式 - 适用于审核输入和输出(当输入 table 符合要求的顺序时更容易验证连接的结果):

*** data to validate;
data have;
length name . age 4. friend .;
input name age friend;
datalines;
Jon     11   Ann
Jon     11   Tom
Jimb    12   Egg
Joe     11   Egg
Joe     11   Anne
Joe     11   Tom
Jed     10   Ann
run;

*** lookup table;
data corrections;
length current_names .  resolved_names .;
input current_names   resolved_names;
datalines;
Jon             John
Ann             Anne
Jimb            Jim
run;

*** de-duplicate lookup table;
proc sort data=corrections nodupkey; by current_names; run;

proc sort data=have; by name; run;   

data have_corrected;
    merge have(in=a) 
          corrections(in=b rename=(current_names=name))
          ;
    by name;
    if a;
    if b then do;
        name=resolved_names;
    end;
run;

SQL 方式 - 避免对 have table:

进行排序
proc sql;
    create table have_corrected_sql as
    select 
        coalesce(b.resolved_names, a.name) as name, 
        a.age, 
        a.friend
    from work.have as a left join work.corrections as b
    on a.name eq b.current_names
    order by name;
quit;

注意 Coalesce() 用于用 table

中的名称替换缺失的 resolved_names 值(即没有更正时)

编辑:为了反映 Quentin 的(正确)评论,即我错过了对姓名和朋友字段的更新。

基于更正 2 个字段,同样有很多方法,但本质是仅当查找中存在值时才更新值(更正)table。散列对象在这方面非常擅长,一旦您理解了它的声明。

注意:Hash 对象中的任何关键字段都需要在 Length 语句中预先指定。

编辑:根据 ChrisJ 对 Length 语句声明的替代方案,以及我的回复(见下文)- 最好声明在声明散列 table 之前需要定义关键变量。

data have_corrected;
keep name age friend;
length current_names .;

    *** load valid names into hash lookup table;
    if _n_=1 then do;
        declare hash h(dataset: 'work.corrections');
        rc = h.defineKey('current_names');
        rc = h.defineData('resolved_names');
        rc = h.defineDone();
    end;
    do until(eof);
        set have(in=a) end=eof;
        *** validate both name fields;  
        if h.find(key:name) eq 0 then
            name = resolved_names;
        if h.find(key:friend) eq 0 then
            friend = resolved_names;
        output;
    end;
run;

编辑:回答有关 ChrisJ 的 SQL/Update 替代方案的评论

基本上,您需要将每个 UPDATE 语句限制为仅在更正中具有 name 值或 friend 值的那些行 table - 这是通过在指定 set var 之后添加另一个 where 子句来完成的=(子句)。见下文。

注意。 AFAIK,满足您要求的 SQL 解决方案将需要对基础 table 和查找 table 进行 1 次以上的传递。

然而,lookup/hash table 需要单次传递基础 table,加载查找 table,然后是查找操作本身。您可以在日志中看到性能差异...

proc sql;
*** create copy of have table;
create table work.have_sql as select * from work.have;
*** correct name field;
update work.have_sql as u
    set name = (select resolved_names 
                from work.corrections as n
                where u.name=n.current_names)
    where u.name in (select current_names from work.corrections)
        ;
*** correct friend field;
update work.have_sql as u
    set friend = (select resolved_names 
                  from work.corrections as n
                  where u.friend=n.current_names)
    where u.friend in (select current_names from work.corrections)
        ;
quit;

给定数据

*** data to validate;
data have;
length name . age 4. friend .;
input name age friend;
datalines;
Jon     11   Ann
Jon     11   Tom
Jimb    12   Egg
Joe     11   Egg
Joe     11   Anne
Joe     11   Tom
Jed     10   Ann
run;

*** lookup table;
data corrections;
length from_name .  to_name .;
input  from_name       to_name;
datalines;
Jon             John
Ann             Anne
Jimb            Jim
run;

一个 SQL 替代方法是对要映射的每个字段执行现有映射 select 查找。这与为每个要映射的字段加入更正 table 一次是相反的。

proc sql;
  create table want1 as
  select 
      case when exists (select *       from corrections where from_name=name)
           then        (select to_name from corrections where from_name=name)
           else name
      end as name
    , age
    , case when exists (select *       from corrections where from_name=friend)
           then        (select to_name from corrections where from_name=friend)
           else friend
      end as friend
  from
    have
  ;

执行内联左联接的另一种 SAS 唯一方法是使用自定义格式。

data cntlin;
  set corrections;
  retain fmtname '$cohen'; /* the fixer */
  rename from_name=start to_name=label;
run;
proc format cntlin=cntlin;
run;

data want2;
  set have;
  name = put(name,$cohen.);
  friend = put(friend,$cohen.);
run;