【问题标题】:Look up and replace values from a separate table in SAS从 SAS 中的单独表中查找和替换值
【发布时间】:2018-05-26 22:45:35
【问题描述】:

数据集 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

我需要HAVE 中的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 作为查找表进行这些替换?

【问题讨论】:

    标签: replace sas recode


    【解决方案1】:

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

    不过,首先,我建议对您的查找表进行重复数据删除(例如,使用 PROC SORT 和 Data Step/Set/By) - 决定保留哪个重复项(如果存在)。

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

    “OLD SCHOOL”方式 - 适用于审核输入和输出(当输入表按所需顺序时,更容易验证连接结果):

    *** data to validate;
    data have;
    length name $10. age 4. friend $10.;
    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 $10.  resolved_names $10.;
    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 表进行排序:

    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() 用于将缺少的 resolved_names 值(即没有更正时)替换为 have 表中的名称

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

    基于更正这 2 个字段,还有许多方法,但本质是仅在查找(更正)表中存在值时才更新值。一旦你理解了它的声明,散列对象就非常擅长这一点。

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

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

    data have_corrected;
    keep name age friend;
    length current_names $10.;
    
        *** 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;
    

    编辑:回答 cmets re ChrisJ 的 SQL/Update 替代方案

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

    注意。 AFAIK,满足您要求的 SQL 解决方案将需要超过 1 遍基表和查找表。

    然而,查找/哈希表需要基表的单次传递、查找表的加载以及查找操作本身。可以在日志中看到性能差异...

    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;
    

    【讨论】:

    • 问题中不包含这个
    • 问题指出:“我需要在 HAVE 中的任何名称或朋友中的任何名称与 CORRECTIONS 的 wrong_names 列中的名称相匹配,以便重新编码为 resolve_name 中的相应字符串。”显示的 WANT 数据集对 NAMEFRIEND 都有更正。
    • @Quentin:抱歉,你是对的 - 错过了。更新答案
    • 为避免需要事先手动定义哈希表中变量的长度,只需在if _n_ = 1 ...语句之前添加if 0 then set corrections;即可。
    • @ChrisJ:绝对。无论哪种方式,重点是 SAS 需要知道(在编译时)散列对象键中使用的变量的长度和类型
    【解决方案2】:

    给定数据

    *** data to validate;
    data have;
    length name $10. age 4. friend $10.;
    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 $10.  to_name $10.;
    input  from_name       to_name;
    datalines;
    Jon             John
    Ann             Anne
    Jimb            Jim
    run;
    

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

    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;
    

    【讨论】:

      【解决方案3】:

      您可以在proc sql 中使用UPDATE

      过程 sql ; 更新有一个 set name = (select resolved_names b from corrections where a.name = b.current_names) 其中名称(从更正中选择 current_names) ; 更新有一个 设置朋友=(从a.friend = b.current_names的更正中选择resolved_names b) 朋友在哪里(从更正中选择 current_names) ; 辞职 ;

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

      /* 创建格式 */ 数据 current_fmt ; 保留 fmtname 'NAMEFIX' 类型 'C' ; 设置已解决的名称; 开始 = 当前名称; 标签 = 已解决的名称; 跑 ; proc 格式 cntlin=current_fmt ;跑 ; /* 应用格式 */ 数据要; 设置有; name = put(name ,$NAMEFIX.) ; 朋友=放(朋友,$NAMEFIX。); 跑 ;

      【讨论】:

      • 酷 - 我从未使用过更新。但是,当我尝试您的 PROC SQL 解决方案时,我在您的别名上收到错误以进行更正(我认为是 b)。当我修改代码以显式调用每个数据集(例如,用 have.name 而不是 a.name 和corrections.resolved_names)时,我得到了一个非常奇怪的结果——其中的“name”和“friend”中的单元格大多为空拥有的新版本。有什么想法吗?
      • 编辑为拆分为两个更新,并将选择标准应用于更新。
      【解决方案4】:

      试试这个:

      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
      

      如有任何澄清,请告诉我。

      【讨论】:

      • 这不会产生 WANT 数据集 - 如果您使用数据线,您会在第一行看到 John 是 John(他自己?!)的朋友,但他应该是 Anne 的朋友。快速问一下,为什么在 b.current_names 中搜索“null”条件时你的情况?
      • 我已经进行了必要的更改并附上了所需的输出。为了回答您的快速问题,我搜索了当前名称的“空”条件,因为我已经加入了名称和当前名称,因此,当前名称是否为空,这意味着不会进行任何更改,如果当前不为空,则意味着要更正的名称存在于更正表中。
      猜你喜欢
      • 2020-05-07
      • 2023-01-31
      • 1970-01-01
      • 1970-01-01
      • 2021-11-06
      • 1970-01-01
      • 2016-12-05
      • 2016-10-31
      • 2014-06-17
      相关资源
      最近更新 更多