Fuzzy matching of postal addresses [1/1]
Andrew McLean
spam-trap-095 at at-andros.demon.co.uk
Sun Jan 23 19:45:34 EST 2005
In article <1106517571.768306.23430 at f14g2000cwb.googlegroups.com>, John
Machin <sjmachin at lexicon.net> writes
>Andrew McLean wrote:
>> In case anyone is interested, here is the latest.
>
>> def insCost(tokenList, indx, pos):
>> """The cost of inserting a specific token at a specific
>normalised position along the sequence."""
>> if containsNumber(tokenList[indx]):
>> return INSERT_TOKEN_WITH_NUMBER + POSITION_WEIGHT * (1 - pos)
>> elif indx > 0 and containsNumber(tokenList[indx-1]):
>> return INSERT_TOKEN_AFTER_NUMBER + POSITION_WEIGHT * (1 -
>pos)
>> elif tokenList[indx][0] in minorTokenList:
>> return INSERT_MINOR_TOKEN
>> else:
>> return INSERT_TOKEN + POSITION_WEIGHT * (1 - pos)
>>
>> def delCost(tokenList, indx, pos):
>> """The cost of deleting a specific token at a specific normalised
>position along the sequence.
>> This is exactly the same cost as inserting a token."""
>> return insCost(tokenList, indx, pos)
>
>Functions are first-class citizens of Pythonia -- so just do this:
>
>delCost = insCost
>
Actually, the code used to look like that. I think I changed it so that
it would look clearer. But perhaps that was a bad idea.
>Re speed generally: (1) How many addresses in each list and how long is
>it taking? On what sort of configuration? (2) Have you considered using
>pysco -- if not running on x86 architecture, consider exporting your
>files to a grunty PC and doing the match there. (3) Have you considered
>some relatively fast filter to pre-qualify pairs of addresses before
>you pass the pair to your relatively slow routine?
There are approx. 50,000 addresses in each list.
At the moment the processing assumes all the postcodes are correct, and
only compares addresses with matching postcodes. This makes it a lot
faster. But may miss some cases of mismatched postcodes.
Also it does two passes. One looking for exact matches of token
sequences. This deals with about half the cases. Only then do I employ
the, more expensive, edit distance technique.
Overall, the program runs in less than half an hour. Specifically it
takes about 60s per thousand addresses, which requires an average of
about 8 calls to editDistance per address. Psyco.full() reduced the 60s
to 45s.
I'll only try optimisation if I need to use it much more.
>Soundex?? To put it bluntly, the _only_ problem to which soundex is the
>preferred solution is genealogy searching in the US census records, and
>even then one needs to know what varieties of the algorithm were in use
>at what times. I thought you said your addresses came from
>authoritative sources. You have phonetic errors? Can you give some
>examples of pairs of tokens that illustrate the problem you are trying
>to overcome with soundex?
I'm sure that in retrospect Soundex might not be a good choice. The
misspellings tend to be minor, e.g.
Kitwhistle and KITTWHISTLE
Tythe and TITHE
I was tempted by an edit distance technique on the tokens, but would
prefer a hash based method for efficiency reasons.
>Back to speed again: When you look carefully at the dynamic programming
>algorithm for edit distance, you will note that it is _not_ necessary
>to instantiate the whole NxM matrix -- it only ever refers to the
>current row and the previous row. What does space saving have to do
>with speed, you ask? Well, Python is not FORTRAN; it takes considerable
>effort to evaluate d[i][j]. A relatively simple trick is to keep 2 rows
>and swap (the pointers to) them each time around the outer loop. At the
>expense of a little more complexity, one can reduce this to one row and
>3 variables (north, northwest, and west) corresponding to d[i-1][j],
>d[i-1][j-1], and d[i][j-1] -- but I'd suggest the simple way first.
>Hope some of this helps,
Thanks for that. The first edit distance algorithm I looked at did it
that way. But I based my code on a version of the algorithm I could
actually follow ;-). Getting the concatenation bit right was non-trivial
and it was useful to store all of "d" for debugging purposes.
As to Python not being Fortran. You've found me out. The three languages
I am most comfortable with are Fortran, Matlab and Python. It did occur
to me that numarray might be a more efficient way of dealing with a
4-dimensional array, but the arrays aren't very big, so the overhead in
setting them up might be significant.
The simplest optimisation would be to replace the two indices used to
deal with concatenation by four explicit variables. And then, as you
said, I could just store the three last rows, and avoid any multiple
indexing.
As with all these potential optimisations, you don't know until you try
them.
--
Andrew McLean
More information about the Python-list
mailing list