【问题标题】:SAS Updating records sequentiallySAS 按顺序更新记录
【发布时间】:2015-11-16 14:21:28
【问题描述】:

我在一个大型数据集中有数十万个 ID。

有些记录的 ID 相同,但数据点不同。其中一些 ID 需要合并为一个 ID。多次注册系统的人应该只是数据库中的一个人。

我还有一个单独的文件告诉我哪些 ID 需要合并,但这并不总是一对一的关系。例如,在许多情况下,我有 x->y,然后是 y->z,因为它们注册了 3 次。我有一个宏,它基本上是以下一组 if-then 语句:

if ID='1111111' then do; ID='2222222'; end; 
if ID='2222222' then do; ID='3333333'; end;

我相信 SAS 会一次运行这一记录。我的合并 ID 列表几乎有 15k 长,因此它需要很长时间才能运行,而且列表只会变得更长。有没有更快的更新这些 ID 的方法?

谢谢

编辑:这是一个例子,除了宏由于所有的合并而超过 15k 行。

data one; 
input ID $5. v1 $ v2 $;
cards;
11111 a b
11111 c d
22222 e f
33333 g h
44444 i j
55555 k l
66666 m n
66666 o p
;
run;

%macro ID_Change;
if ID='11111' then do; ID='77777'; end; *77777 is a brand new ID;
if ID='22222' then do; ID='88888'; end; *88888 is a new ID but is merged below;
if ID='88888' then do; ID='99999'; end; *99999 becomes the newer ID;
%mend;

data two; set one; %ID_Change; run;

【问题讨论】:

  • 请编辑您的问题以显示一些输入记录的示例结构和带有一些示例记录的“单独文件”结构(平面文件/SAS 数据集等)以及您想要的输出根据示例输入记录看起来像。

标签: sas


【解决方案1】:

哈希表将大大加快这个过程。 Hash tables 是 SAS 中很少使用但非常有效的工具之一。它们有点奇怪,因为语法与标准 SAS 编程非常不同。目前,将其视为一种在内存中将数据合并在一起的方法(这是它如此快的一个重要原因)。

首先,创建一个包含您需要的转换的数据集。我们要匹配ID,然后将其转换为New_ID。将ID 视为您的key 列,将New_ID 视为您的data 列。

dataset: translate

ID     New_ID
111111 222222
222222 333333

在哈希表中,你需要考虑两件事:

  1. 关键列
  2. 数据列

Data 列将替换与 Key 列匹配的观察值。换句话说,每次匹配到 ID 时,都会填充 New_ID

接下来,您需要进行哈希合并。这是在数据步骤中执行的。

data want;
     set have;

     /* Only declare the hash object on the first iteration. 
        Otherwise it will do this every record. */
     if(_N_ = 1) then do;
           declare hash id_h(dataset: 'translate'); *Declare a hash object called 'id_h';
           id_h.defineKey('ID');                    *Define key for matching;
           id_h.defineData('New_ID');               *The new ID after matching;
           id_h.defineDone();                       *Done declaring this hash object;
           call missing(New_ID);                    *Prevents a warning in the log;
     end;

    /* If a customer has changed multiple times, keep iterating until 
       there is no longer a match between tables */
    do while(id_h.Find() = 0);

        _loop_count+1; *Tells us how long we've been in the loop;

       /* Just in case the while loop gets to 500 iterations, then 
          there's likely a problem and you don't want the data step to get stuck */
       if(_loop_count > 500) then do;
            put 'WARNING: ' ID ' iterated 500 times. The loop will stop. Check observation ' _N_;
            leave;
       end; 

        /* If the ID of the hash table matches the ID of the dataset, then
           we'll set ID to be New_ID from the hash object;
        ID = New_ID; 
    end;

    _loop_count = 0;

   drop _loop_count;
run;

假设您的查找表以您需要的方式编码,这应该会非常快速地运行并提供所需的输出。

【讨论】:

  • 鉴于需要多次重新映射,您可能需要一个 DO 循环,而不仅仅是一个 IF,以便在这些情况下多次分配 ID = new_id
  • 一个while循环会更好,因为迭代次数是未知的。使用 do 循环基本上是交叉手指并希望它是正确的。此外,while 循环可能会减少操作总数,因为如果只需要执行一次,您不会强迫它为每个 obs 重复 5 次。
  • 正是我的想法 :) 将其更改为灵活的选项。
  • do while(id_h.Find()=0 ); ID = New_ID; end;
  • 这是更好的解决方案。我确实认为这对于 OP 来说可能有点先进,因为他/她目前正在将值硬编码到宏中。但我也从中吸取了教训。谢谢。
【解决方案2】:
  1. 对您的单独文件使用PROC SQLMERGE 步骤(在您从中创建单独的数据集后,使用infileproc import)将此唯一ID 附加到所有记录。如果您的单独文件仅包含重复项,您将需要为非重复项创建一个虚拟唯一 ID。

  2. PROC SORT 使用 BY 唯一 ID 和注册时间戳。

  3. 使用具有相同BY 变量的DATA 步骤。根据您是要保留第一个还是最后一个注册,请执行if first.timestamp then output;(或最后一个等)

或者您可以在一个 PROC SQL 中完成所有操作,使用 left join 到单独的文件,如果它不包含在单独的文件中,则 coalesce 步骤返回一个虚拟的唯一 ID,一个 group by 唯一id 和 having max(timestamp)(或最小值)。您还可以coalesce 任何其他您可能希望尝试在注册中保留的变量 - 例如,如果第一个注册包含电话号码并且后续注册缺少该数据点。

如果没有可重复的示例,很难更具体。

【讨论】:

    猜你喜欢
    • 2017-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多