【问题标题】:Merge huge data sets in SAS在 SAS 中合并海量数据集
【发布时间】:2015-09-16 03:38:20
【问题描述】:

我需要 SAS 大师的建议 :)。
假设我有两个大数据集。第一个是一个巨大的数据集(大约 50-100Gb!),其中包含电话号码。第二个包含前缀(20-40 千次观察)。 我需要为每个电话号码的第一个表添加最合适的前缀。

例如,如果我有一个电话号码 +71230000 和前缀

+7
+71230
+7123

最合适的前缀是+71230。

我的想法。首先,对前缀表进行排序。然后在数据步骤中,处理电话号码表

data OutputTable;
    set PhoneNumbersTable end=_last;
    if _N_ = 1 then do;
        dsid = open('PrefixTable');
    end;
    /* for each observation in PhoneNumbersTable:
       1. Take the first digit of phone number (`+7`).
          Look it up in PrefixTable. Store a number of observation of
          this prefix (`n_obs`).
       2. Take the first TWO digits of the phone number (`+71`).
          Look it up in PrefixTable, starting with `n_obs + 1` observation.
          Stop when we will find this prefix
          (then store a number of observation of this prefix) or
          when the first digit will change (then previous one was the
          most appropriate prefix).
       etc....
    */
    if _last then do;
        rc = close(dsid);
    end;
run;

我希望我的想法足够清楚,但如果不是,我很抱歉)。

那你有什么建议? 感谢您的帮助。

附:当然,第一个表中的电话号码不是唯一的(可能重复),不幸的是,我的算法没有使用它。

【问题讨论】:

  • 由于这个问题是针对 SAS 专家的,并且还涉及到大数据,所以我想知道你们是否可以帮我回答这个关于 SAS 数据存储选项和大数据的一般性问题:datascience.stackexchange.com/questions/12619/…。我正在尝试将 SAS 数据存储与 SQL Server 等常规 RDBMS 进行比较。对此的任何帮助都非常感谢。

标签: sas


【解决方案1】:

有几种方法可以做到这一点,您可以使用格式或哈希表。

使用格式示例:

/* 构建所有前缀的简单格式,并确定最大前缀长度 */ 数据前缀_fmt; 设置前缀表 end=eof ; 保留 fmtname 'PREFIX' type 'C' maxlen 。 ; maxlen = max(maxlen,length(prefix)) ; /* 存储最大前缀长度 */ 开始=前缀; 标签 = 'Y' ; 输出 ; 如果 eof 则执行; hlo = 'O' ; 标签 = 'N' ; 输出 ; 调用 symputx('MAXPL',maxlen) ; 结尾 ; 删除 maxlen ; 跑 ; proc 格式 cntlin=prefix_fmt ;跑 ; /* 对于每个电话号码,从完整号码开始并减少 1 位,直到找到前缀匹配 */ /* 为了提高效率,最初将电话号码减少到最大前缀长度 */ 数据匹配前缀; 设置电话号码稳定; 长度前缀 $&MAXPL.. ; 前缀 = '' ; pnum = substr(phonenumber,1,&MAXPL) ; 直到 (not missing(prefix) or length(pnum) = 1) ; if put(pnum,$PREFIX.) = 'Y' then prefix = pnum ; pnum = substr(pnum,1,length(pnum)-1) ; /* 去掉最后一个数字 */ 结尾 ; 删除 pnum ; 跑 ;

【讨论】:

  • 太棒了!谢谢!:) 你能解释一下为什么使用drop maxlen ; 语句而不是drop=maxlen 选项吗?
  • 因为 drop= 不是 DROP 语句的正确语法。您一定在考虑 DROP 数据集选项。
【解决方案2】:

这是另一个非常效果很好的解决方案,速度方面,只要您可以在一个主要(也许可以)限制下工作:电话号码不能以 0 开头,并且必须可以是数字或可转换为数字(即,不需要查找“+”)。

我正在做的是构建一个 1/null 标志数组,每个可能的前缀一个 1/null 标志。除非这不适用于前导 0:因为 '9512' 和 '09512' 是相同的数字。这可以解决 - 在开头添加一个“1”(所以如果你有可能的 6 位前缀,那么一切都是 1000000+前缀)例如会起作用 - 但它需要调整以下内容(并且可能会影响性能,虽然我认为不会那么糟糕)。如果还需要“+”,则可能需要将其转换为数字;在这里,您可以说任何带有“+”的内容都会在开头添加 2000000,或者类似的内容。

好消息是每行最多只需要 6 次查询(左右)一个数组 - 比任何其他搜索选项都快很多(因为临时数组是连续的内存块,它只是“去检查6 个预先计算的内存地址”)。散列和格式会慢很多,因为它们必须重新查找每一个。

一个主要的性能建议:注意您的前缀可能无法匹配的方式。检查 6 然后 5 然后 4 然后 ... 可能会更快,或者检查 1 然后 2 然后 3 然后 ... 可能会更快。这完全取决于实际的前缀本身和实际的电话号码。如果您的大多数前缀都是“+11”之类的东西,那么您几乎肯定要从左边开始,如果很快就会发现带有“94”的数字不匹配。

有了这个,解决方案。

data prefix_match;
  if _n_=1 then do;
    array prefixes[1000000] _temporary_;
    do _i = 1 to nobs_prefix;
      set prefixes point=_i nobs=nobs_prefix;
      prefixes[prefix]=1;
    end;
    call missing(prefix);
  end;  
  set phone_numbers;
  do _j = 6 to 1 by -1;
    prefix = input(substr(phone_no,1,_j),6.);
    if prefix ne 0 and prefixes[prefix]=1 then leave;
    prefix=.;
  end;
  drop _:;
run;

针对具有 40k 前缀和 100m 电话号码(并且没有其他变量)的测试集,这在我的(好)笔记本电脑上运行了 1 分钟多一点,而 6 和更改格式解决方案和 4 并更改使用散列解决方案(修改它以输出所有行,因为其他两个解决方案这样做)。就性能而言,这对我来说似乎是正确的。

【讨论】:

    【解决方案3】:

    这是一个哈希表示例。

    生成一些虚拟数据。

    data phone_numbers(keep=phone)
         prefixes(keep=prefix);
         ;
    
      length phone $10 prefix $4;
      do i=1 to 10000000;
        phone = cats(int(ranuni(0) * 9999999999 + 1));
        len = int(ranuni(0) * 4 + 1);
        prefix = substr(phone,1,len);
        if input(phone,best.) ge 1000000000 then do;
          output;
        end;
      end;
    
    run;
    

    假设最长的前缀是 4 个字符,首先尝试找到最长的匹配,然后继续直到尝试了最短的前缀。如果找到匹配项,则输出记录并继续进行下一个观察。

    data ht;
      attrib prefix length=$4;
    
      set phone_numbers;
    
      if _n_ eq 1 then do;
        declare hash ht(dataset:"prefixes");
        ht.defineKey('prefix');
        ht.defineDone();
      end;
    
      do len=4 to 1 by -1;
        prefix = substr(phone,1,len);
        if ht.find() eq 0 then do;
          output;
          leave;
        end;
      end;
    
      drop len;
    
    run;
    

    如果未找到匹配项以输出记录并将前缀字段留空,可能需要添加逻辑?不确定您想如何处理这种情况。

    【讨论】:

    • 我估计运行一个 50 gig 的文件需要 1 到 2 个小时。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-26
    • 1970-01-01
    • 1970-01-01
    • 2019-12-04
    • 2018-05-04
    • 2021-06-03
    相关资源
    最近更新 更多