У SAS недостаточно памяти при попытке выполнить нечеткое сопоставление с использованием хеш-таблиц в небольшом выборочном наборе данных - PullRequest
1 голос
/ 17 октября 2019

У меня есть список имен, телефонных номеров и адресов с ~ 5 000 000 строк. Я пытаюсь создать список уникальных клиентов по каждому адресу, каждому из которых назначается ключ. Имена клиентов не отформатированы согласованно, поэтому Джон Смит может выглядеть как, например, Смит, Джон или Джон Смит-младший и т. Д.

Логика, которой я хочу следовать, такова:

Если две записи по адресу имеют один и тот же номер телефона, это один и тот же клиент, независимо от того, имеют ли они разные имена или нет, и ему назначается один и тот же ключ customer_

Если две записи по адресу не имеют одного и того же номера телефона (или не имеют номера телефона), но нечеткое совпадение их имен превышает некоторый порог, они являются одним и тем же клиентом, и им присваивается тот же ключ customer_ *. 1011 *

Обратите внимание, что одно и то же имя клиента + номер телефона должны совпадать на двух разных адресах, и не должен быть присвоен один и тот же ключ клиента.

Вот примеры таблиц, содержащих примеры ввода и желаемого результата:

https://docs.google.com/spreadsheets/d/1RsABSFy5a5dLE8mC-ZQF_lNg4I0kLQy7dEFhbfovq40/edit?usp=sharing

Моя попытка кода с использованием этого примера набора данных приведена ниже:


data customer_keys(keep=customer_name customer_key customer_phone clean_address);
   length customer_name $50 Comp_Name $20 customer_key 8 clean_address comp_address $5 customer_phone comp_phone $11.;

   if _N_ = 1 then do;
      declare hash h(multidata:'Y');
      h.defineKey('Comp_Name','comp_address');
      h.defineData('Comp_Name', 'customer_key','comp_address', 'comp_phone');
      h.defineDone();
      declare hiter hi('h');

      declare hash hh(multidata:'Y');
      hh.defineKey('customer_key');
      hh.defineData('customer_key', 'Comp_Name','comp_address','comp_phone');
      hh.defineDone(); 

      _customer_key=0; 
   end;

   set testdat;

   rc=h.find(key:customer_name, key:clean_address);

   if rc ne 0 then do;
      rc=hi.first();
      do while (rc=0);
         if not missing(customer_phone) and clean_address=comp_address and customer_phone=comp_phone
            then do;
                  h.add(key:customer_name,key:clean_address, data:customer_name, data:customer_key, data:clean_address, data:customer_phone);
                  hh.add();
            end;
         else if not missing(customer_name) and clean_address=comp_address and jaroT(customer_name,Comp_name) ge 0.8
            then do;
            rc=hh.find();

            do while (r ne 0);

               dist2=jaroT(customer_name,Comp_name);
               hh.has_next(result:r);

               if r=0 & dist2 ge 0.8 then do;
                  h.add(key:customer_name,key:clean_address, data:customer_name, data:customer_key, data:clean_address, data:customer_phone);
                  hh.add();
                  output;return;
               end;

               else if r ne 0 & dist2 ge 0.8
                then rc=hh.find_next();

               else if dist2 < 0.8
            then leave;

            end;

         end;

         rc=hi.next();

      end;

      _customer_key+1;
      customer_key=_customer_key;
      h.add(key:customer_name,key:clean_address, data:customer_name, data:customer_key, data:clean_address, data:customer_phone);
      hh.add(key:customer_key, data:customer_key, data:customer_name, data:clean_address, data:customer_phone);
   end;

   output;
run;

Запуск этого кода приводит к ошибке:

ERROR: Hash object added 23068656 items when memory failure occurred.
FATAL: Insufficient memory to execute DATA step program. Aborted during the EXECUTION phase.
ERROR: The SAS System stopped processing this step because of insufficient memory.

Я замечаючто еслиЯ полностью удаляю дополнительную логику, связанную с номером телефона, у меня нет этой проблемы с памятью. Однако я все еще собираюсь предположить, что мой подход потерпит неудачу в любом случае из-за нехватки памяти, когда я попытаюсь запустить его на полном наборе данных.

Ответы [ 2 ]

1 голос
/ 17 октября 2019

Мне удалось решить проблему с памятью, и теперь код работает, как и ожидалось! Ошибка была в разделе, касающемся совпадения телефонных номеров. Ниже приведен исправленный код:


data test customer_keys(keep=customer_name customer_key customer_phone clean_address);
   length customer_name $50 Comp_Name $20 customer_key 8 clean_address comp_address $22 customer_phone comp_phone $14.;

   if _N_ = 1 then do;
      declare hash h(multidata:'Y');
      h.defineKey('Comp_Name','comp_address');
      h.defineData('Comp_Name', 'customer_key','comp_address', 'comp_phone');
      h.defineDone();
      declare hiter hi('h');

      declare hash hh(multidata:'Y');
      hh.defineKey('customer_key');
      hh.defineData('customer_key', 'Comp_Name','comp_address','comp_phone');
      hh.defineDone(); 

      _customer_key=0; 
   end;

   set testdat;

   rc=h.find(key:customer_name, key:clean_address);

   if rc ne 0 then do;
      rc=hi.first();
      do while (rc=0);

         if not missing(customer_phone) and clean_address=comp_address and customer_phone=comp_phone
            then do;
            rc=hh.find();

            do while (r ne 0);

               hh.has_next(result:r);

               if r=0 then do;
                  h.add(key:customer_name,key:clean_address, data:customer_name, data:customer_key, data:clean_address, data:customer_phone);
                  hh.add();
                  output;return;
               end;


            else leave;

            end;

         end;


         if not missing(customer_name) and clean_address=comp_address and jaroT(customer_name,Comp_name) ge 0.8
            then do;
            rc=hh.find();

            do while (r ne 0);

               dist2=jaroT(customer_name,Comp_name);
               hh.has_next(result:r);

               if r=0 & dist2 ge 0.8 then do;
                  h.add(key:customer_name,key:clean_address, data:customer_name, data:customer_key, data:clean_address, data:customer_phone);
                  hh.add();
                  output;return;
               end;

               else if r ne 0 & dist2 ge 0.8
                then rc=hh.find_next();

               else if dist2 < 0.8
            then leave;

            end;

         end;


         rc=hi.next();

      end;

      _customer_key+1;
      customer_key=_customer_key;
      h.add(key:customer_name,key:clean_address, data:customer_name, data:customer_key, data:clean_address, data:customer_phone);
      hh.add(key:customer_key, data:customer_key, data:customer_name, data:clean_address, data:customer_phone);

   end;

   output;
run;

Также ниже я публикую код для функции jaroT, чтобы другие могли использовать ее, если они захотят, я не написал эту функцию, и вы могли бы заменитьэто в моем коде выше с любым алгоритмом сравнения, который вы хотите (не забудьте также изменить пороговые числа):

FUNCTION jaroT(string_1 $,string_2 $);

if STRING_1=STRING_2 THEN return(1);
else do;
length1=length(string_1);
if length1>26 then length1=26;
length2=length(string_2);
if length2>26 then length2=26;
range=(int(max(length1,length2)/2)-1);
big=max(length1,length2);
short=min(length1,length2);
array String1{26} $ 1 _temporary_;
array String2{26} $ 1 _temporary_;
array String1Match{26} $ 1 _temporary_;
array String2Match{26} $ 1 _temporary_;

/*The following two do loops place the characters into arrays labelled string1 and string2. 
While we are here, we also set a second array of the same dimensions full of zeros.  This will
act as our match key, whereby values in the same relative position as those in the original string
will be set to 1 when we find a valid match candidate later on.*/

do i=1 to length1 by 1;
    String1{i}=substr(string_1,i,1);
    String1Match{i}='0';
end;

do i=1 to length2 by 1;
    String2{i}=substr(string_2,i,1);
    String2Match{i}='0';
end;

/*We introduce m, which will keep track of the number of matches */

m=0;

/*We set a loop to compare one string with the other.  We only need to loop the same number of
times as there are characters in one of our strings.  Hence "do while i<=length1".

We set the allowable search range for a character using pos and endpos, and set another loop to
search through this range.  We loop through until we find our first match, or until we hit
the end of our search range.  If the character in string 2 is already signed to a match, we move
on to searching the next character.  When we find a match, the match flag for that character in both
strings is set to 1.  Hopefully by the end of the loop, we have match flags for our two arrays set.

*/

do i=1 to length1 by 1;
pos=max(i-range,1);
endpos=min(range+i,length2);
    do while (pos<=endpos and String1Match{i}^='1');
    if String1{i}=String2{pos} and String2Match{pos}^='1' then do;
        m=m+1;
        String1Match{i}='1';
        String2Match{pos}='1';
        end;
    pos=pos+1;
    end;
end;

/* If there are no matching characters, we do not bother with any more work, and say the two strings are not alike at all */

IF m=0 then return(0);

else if m=1 then do;
t=0;
end;

/* If those three conditions all fail, then we move onto the heavy lifting.*/

else do;

/* We set i back to 1, ready for another looping run.  

c is a variable to track the position of the next valid transposition check.
j is a variable helping to keep track of matching characters found during the next loop inside string 1.
k is a variable helping to keep track of matching characters found during the next loop inside string 2.
t will be the number of tranpositions found.

*/

i=1;
c=1;
k=0;
j=0;
t=0;

/* We begin our loop.  These conditional loops within loops 
make several logical conclusions to arrive at the correct number of transpositions and matching characters 
at the beginning of a string.  At the end of this we should have every variable we need to calculate the winkler
score (and theoretically the jaro as well).  I'm not going to write out an explanation here, but if you're
interested all the extra variables are defined just above this comment, and I've already told you what the
string arrays are.  Work through a couple of examples with pen and paper, or in your head, to see how 
and why it works.*/

do while (j<m OR k<m);
    IF j<m then do;
        IF String1Match{i}='1' THEN DO;
            j=j+1;
            String1{j}=String1{i};
        end;
    end;
    IF k<m then do;
        IF String2Match{i}='1' THEN DO;
            k=k+1;
            String2{k}=String2{i};
        end;
    end;
    IF j>=c and k>=c then do;
        IF String1{c}^=String2{c} then t=t+1; 
    c=c+1;
    end;
i=i+1;
end;
end;

/* Finally, we do the calculation of the scores */

jaro=(1/3)*((m/length1)+(m/length2)+((m-(t/2))/m));

return(jaro);
end;

endsub;

FUNCTION fuzznum(num_1,num_2,diff,direction);
IF direction=-1 THEN DO;
    IF num_1-num_2<=diff AND num_1-num_2>=0 THEN RETURN(1);
    ELSE RETURN(0);
    END;
IF direction=1 THEN DO;
    IF num_1-num_2>=-(diff) AND num_1-num_2<=0 THEN RETURN(1);
    ELSE RETURN(0);
    END;
IF direction=0 THEN DO;
    IF ABS(num_1-num_2)<=diff THEN RETURN(1);
    ELSE RETURN(0);
    END;
endsub;
1 голос
/ 17 октября 2019

найдите «sas.exe-memsize 16G» в функции поиска Windows, чтобы получить новую версию программы sas, которая будет иметь 16G в памяти. Вы можете изменить номер до G тоже. Также убедитесь, что у вас достаточно места на диске. Приветствия.

...