【问题标题】:How can I find and replace specific text in a SAS data set?如何查找和替换 SAS 数据集中的特定文本?
【发布时间】:2016-11-10 07:07:29
【问题描述】:

我有一个数据集,其中包含 400 个 4 位代码的观察值,我想在两侧用空格填充

ex. Dataset 
obs code
1   1111 
2   1112
3   3333
.
.
.
400 5999

我怎样才能遍历另一个大型数据集,并将每次出现的任何填充的 400 代码替换为“”。

ex. Large Dataset
obs text 
1   abcdef 1111 abcdef
2   abcdef 1111 abcdef 1112 8888
3   abcdef 1111 abcdef 11128888
... 

我想要的数据集

ex. New Data set
obs text
1   abcdef   abcdef
2   abcdef   abcdef   8888
3   abcdef   abcdef 11128888
...

注意:我只想替换两边用空格填充的 4 位代码。所以在obs 3中,1112不会被替换。

我尝试过执行以下 proc sql 语句,但它只查找并替换第一个匹配项,而不是所有匹配项。

proc sql;  
    select   
    *,  
    tranwrd(large_dataset.text, trim(small_dataset.code), ' ') as new_text  
from large_dataset  
    left join small_dataset  
    on findw(large_dataset.text, trim(small_dataset.code))
;
quit;

【问题讨论】:

  • 到目前为止你尝试过什么?如果遇到类似abc111112abc 的内容有两个匹配的 4 位代码但只能匹配其中一个,你会怎么做?如果您有类似 abc00111100abc 的内容,并且在一系列数字的中间有匹配项怎么办?
  • @user667489 我已经编辑了我的问题,这样我就不会遇到你提到的两种情况的问题。我更新了我的原始帖子以包含我尝试过的内容。
  • 只有 400 个 4 字节的字符串,您可以构造正则表达式,在一次函数调用中将它们全部替换。 SAS 字符串变量限制为 32K 字节。
  • @Tom 很想学习创建正则表达式来解决这个问题的另一种方法。我尝试使用很长的“其中文本不包含 code1 .. 或文本不包含 code2 ...”子句来执行 proc sql。但我不知道如何遍历我的 400 个代码列表并提取代码并将其附加到 where 子句。

标签: replace sas dataset


【解决方案1】:

您可以只使用 DO 循环来扫描大型数据集中每条记录的小型代码数据集。如果要使用TRANWRD() 函数,则需要添加额外的空格字符。

data want ;
  set have ;
  length code $4 ;
  do i=1 to nobs while (text ne ' ');
    set codes(keep=code) nobs=nobs point=i ;
    text = substr(tranwrd(' '||text,' '||code||' ',' '),2);
  end;
  drop code;
run;

DO 循环将从您的 CODES 列表中读取记录。在 SET 语句中使用 POINT= 选项可以让您多次读取文件。如果 TEXT 字符串为空,则 WHILE 子句将停止,因为此时无需继续寻找要替换的代码。

如果您的代码列表足够小并且您可以获得正确的正则表达式,那么您可以尝试使用PRXCHANGE() 函数。您可以使用 SQL 步骤将代码生成为可以在正则表达式中使用的列表。

proc sql noprint ;
  select code into :codelist separated by '|'
  from codes
;
quit;

data want ;
  set have ;
  text=prxchange("s/\b(&codelist)\b/ /",-1,text);
run;

【讨论】:

  • 当您像这样多次读取数据集时,sasfile 语句可能会提高性能。
  • SASFILE 可能会有所帮助,但只有 400 个观测值(每个观测值 4 个字节)的文件可能已经被操作系统或 SAS 磁盘缓冲区缓冲。
  • 哇,谢谢!我认为它奏效了。我还是 SAS 的新手,我不太明白为什么或如何这个代码消除了每次观察中所有出现的 4 位代码。有人能解释一下吗?
  • 对不起,还有一个问题.. 你为什么要在“text = substr(tranwrd(' '||text,' '||code||' ', ' '),2);" ?不应该有一个''连接到文本的另一边吗?喜欢text = substr(tranwrd(' '||text||' ',' '||code||' ',' '),2);
  • TRANWRD() 是一个简单的工具,它与您所说的完全匹配。为了能够匹配出现在 TEXT 字符串开头的代码,您需要在它之前放置一个空格。如果在 TEXT 之前没有额外的空格,那么它将不匹配,因为您正在搜索字符串两侧的空格。您可能还想在末尾添加一个,但这不太可能成为问题,因为只有当 TEXT 字符串完全填充了变量的定义长度时才会出现问题。 SAS 字符变量总是用空格填充到它们的完整定义长度。
【解决方案2】:

使用数组和正则表达式:

proc transpose data=codes out=temp;
var code;
run;

data want;
if _n_=1 then  set temp;
array var col:;
set big_dataset;
do over var;
text = prxchange(cats('s/\b',var,'\b//'),-1,text);
end;
drop col:;
run;

【讨论】:

    【解决方案3】:

    采用 Tom 的解决方案并将代码查找放入哈希表中。因此,数据集只会被读取一次,实际查找速度非常快。如果大型数据集真的很大,这将产生巨大的影响。

    data want ;
      if _n_ = 1 then do;
        length code $4 ;
        declare hash h(dataset:"codes (keep=code)") ; 
        h.defineKey("code") ;
        h.defineDone() ;
        call missing (code);
        declare hiter hiter('h') ;
      end;
      set big_dataset ;
    
      rc = hiter.first() ;
      do while (rc = 0 and text ne ' ') ;
        text = substr(tranwrd(' '||text,' '||code||' ',' '),2) ;
        rc = hiter.next() ;
      end ;
      drop code rc ;
    run;
    

    【讨论】:

      【解决方案4】:

      可能有更有效的方法可以做到这一点,但这似乎效果很好:

      /*Create test datasets*/
      data codes;
      input code;
      cards;
      1111 
      1112
      3333
      5999
      ;
      run;
      
      data big_dataset;
      infile cards truncover;
      input text $100.;
      cards;
      abcdef 1111 abcdef
      abcdef 1111 abcdef 1112 8888
      abcdef 1111 abcdef 11128888
      ;
      run;
      
      /*Get the number of codes to use for array definition*/
      data _null_;
          set codes(obs = 1) nobs = nobs;
          call symput('ncodes',nobs);
      run;
      
      %put ncodes = &ncodes;
      
      data want;
          set big_dataset;
          /*Define and populate array with padded codes*/ 
          array codes{&ncodes} $6 _temporary_;
          if _n_ = 1 then do i = 1 to &ncodes;    
              set codes;
              codes[i] = cat(' ',put(code,4.),' '); 
          end;
          do i = 1 to &ncodes;
              text = tranwrd(text,codes[i],' ');
          end;
          drop i code;
      run;
      

      我希望使用prxchange 的解决方案也是可能的,但我不确定构建一个匹配所有代码的正则表达式与仅逐个替换它们相比需要多少工作。

      【讨论】:

      • 不需要data _null_ 步骤或数组。只需在 set codes 语句中使用 add NOBS=NOBSPOINT=I,这样您就可以只使用一个 DO 循环。
      • 我更喜欢只读取代码数据集并将其存储在内存中,因为磁盘 I/O 是 O(n + m) 而不是 O(n * m)。
      猜你喜欢
      • 1970-01-01
      • 2019-02-15
      • 2019-02-28
      • 2018-12-31
      • 2013-07-18
      • 1970-01-01
      • 2012-11-10
      相关资源
      最近更新 更多