Skip to main content

dynamic - Soft-Match String Comparison


I have a list of names in which I would like to check for duplicates. However, within the list, the duplicates may not show up as exact duplicates of each other - for instance,


{Barack Obama, Barack H. Obama, Barack Hussein Obama, Obama Barack Hussein}


are all considered likely candidates to be duplicates of each other. Furthermore, the data may contain slight mistakes - for instance, the following and other variants would be considered (near) duplicates too.


{Barack Obama, barack Obama, Barrack Obama}


What I would like to do is to automatically highlight these duplicates (for example, by changing their font size or color as displayed on screen) so that I can later go through the list manually and confirm whether or not the duplicates are indeed duplicates.


My current approach is as such:


nlsplit = 
StringSplit /@ namelist; (*splitting the names at the whitespace*)

nlsplitsort =
Map[Sort, nlsplit,
2] ; (*sorting the names of each person alphabetically - for
example, John Anderson Doe becomes {Anderson, Doe, John}*)
nlsplitsortpad = Transpose[Map[PadRight[#, 5] &, nlsplitsort, 2]][[1]];
(*pad each person's name with zeros so that Mathematica doesn't sort
based on list length but on name*)
Sort[nlsplitsortpad]

This question may be linked to mine, but I feel it's sufficiently different to merit a post. Mr Wizard also informed me that a question of Soft-Match String Comparison may have been asked before and the link is here as Sjoerd C. de Vries points out.



Edit: It would be great if the following could be done, but from my knowledge of Mathematica, I'm not sure whether it's worth the effort.



  1. The process of highlighting duplicates does not change the ordering of the original list, but simply highlights them, and

  2. I could simply hover over a duplicate candidate where using Tooltip we display the possible matches identified as similar to this duplicate candidate.

  3. By choosing from a list of options I could do one of the following:

    • select the current duplicate candidate as the entry I want to keep (and remove all other similar candidates from the main list)

    • skip to one of the other matches displayed by the Tooltip and choose that as the entry I want to keep (removing all other similar candidates from the main list)

    • confirm that the current candidate is not in fact a duplicate, and remove it from the list of any candidate of duplicates.






Answer



Start by making some similarity measure of sentences, here I use one that takes number of words in common divided by number of words in longest sentence.


The measure is then used to connect sentences that are similar enough in a graph and extracts the connected components:


strs = {"Barack Obama", "Barack H. Obama", "Barack Hussein Obama", 
"Obama Barack Hussein", "Barrack Obma", "Some other", "Strings",
"That are not duplicates","NotReally Barrack Obama"};
sameWordQ[w1_, w2_] := EditDistance[w1, w2, IgnoreCase->True] < 2;
similarity[str1_, str2_] := Module[{

l1 = StringSplit[str1],
l2 = StringSplit[str2]},
Length@Intersection[l1, l2,SameTest->sameWordQ]/Max[Length[l1], Length[l2]]
]
findDupes[lst_] := ConnectedComponents@Graph@Flatten@MapIndexed[
Function[{str, ind},
Thread[str \[UndirectedEdge] Select[lst[[First@ind ;;]], similarity[str, #] >= 2/3 &]]
], lst]



findDupes[strs]
(* {{"Barack Obama", "Barack H. Obama", "Barack Hussein Obama",
"Obama Barack Hussein", "Barrack Obma", "NotReally Barrack Obama"},
{"Some other"}, {"Strings"}, {"That are not duplicates"}} *)

To actually answer the question and visually verify the output and remove false positives:


For each group returned take away all groups of length 1. Then for each name create a button that lets you remove a name if it is considered a false positive.


Options[verifyDupes] = {HoldFirst};
(* When you click save the result is stored in the first argument *)
verifyDupes[result_Symbol, dupes_] :=

Module[{
groups = Select[dupes, Length@# > 1 &],
isDupe = Blue, notDupe = Black, styles},

(*Creata a list of same size where each element is marked a duplicate*)
styles = Map[isDupe &, groups, {2}];

Column@{
MapIndexed[Function[{str, ind},
Button[Dynamic[Style[str, styles[[Sequence @@ ind]]]],

(*Switch style when a name is clicked*)
styles[[Sequence @@ ind]] = If[styles[[Sequence @@ ind]] === isDupe, notDupe,isDupe],
Appearance -> None]], groups, {2}],
Button["Save",
(*Remove all names that have been deselected*)
result = MapIndexed[
If[styles[[Sequence @@ #2]] === isDupe, #1, Unevaluated@Sequence[]] &,
groups, {2}]]}
]


strs = {"Barack Obama", "Barack H. Obama", "Barack Hussein Obama",
"Obama Barack Hussein", "barrack obma", "Some other", "Strings",
"That are not duplicates", "NotReally Barack Obama",
"John Anderson Doe", "John A. Doe", "John Doe"};

Clear@result
verifyDupes[result,findDupes[strs]]

nodupe


Comments

Popular posts from this blog

plotting - How to draw lines between specified dots on ListPlot?

I would like to create a plot where I have unconnected dots and some connected. So far, I have figured out how to draw the dots. My code is the following: ListPlot[{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {1, 4}, {2, 5}, {3, 6}, {4, 7}, {1, 7}, {2, 8}, {3, 9}, {4, 10}, {1, 10}, {2, 11}, {3, 12}, {4,13}, {2.5, 7}}, Ticks -> {{1, 2, 3, 4}, None}, AxesStyle -> Thin, TicksStyle -> Directive[Black, Bold, 12], Mesh -> Full] I have thought using ListLinePlot command, but I don't know how to specify to the command to draw only selected lines between the dots. Do have any suggestions/hints on how to do that? Thank you. Answer One possibility would be to use Epilog with Line : ListPlot[ {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {1, 4}, {2, 5}, {3, 6}, {4, 7}, {1, 7}, {2, 8}, {3, 9}, {4, 10}, {1, 10}, {2, 11}, {3, 12}, {4, 13}, {2.5, 7}}, Ticks -> {{1, 2, 3, 4}, None}, AxesStyle -> Thin, TicksStyle -> Directive[Black, Bold, 12], Mesh -> Full, Epilog -> { Line[ ...

dynamic - How can I make a clickable ArrayPlot that returns input?

I would like to create a dynamic ArrayPlot so that the rectangles, when clicked, provide the input. Can I use ArrayPlot for this? Or is there something else I should have to use? Answer ArrayPlot is much more than just a simple array like Grid : it represents a ranged 2D dataset, and its visualization can be finetuned by options like DataReversed and DataRange . These features make it quite complicated to reproduce the same layout and order with Grid . Here I offer AnnotatedArrayPlot which comes in handy when your dataset is more than just a flat 2D array. The dynamic interface allows highlighting individual cells and possibly interacting with them. AnnotatedArrayPlot works the same way as ArrayPlot and accepts the same options plus Enabled , HighlightCoordinates , HighlightStyle and HighlightElementFunction . data = {{Missing["HasSomeMoreData"], GrayLevel[ 1], {RGBColor[0, 1, 1], RGBColor[0, 0, 1], GrayLevel[1]}, RGBColor[0, 1, 0]}, {GrayLevel[0], GrayLevel...

functions - What best practices or performance considerations are there for choosing between Cases, Position, Pick and Select?

Cases , Select , Pick and Position each have different syntaxes and purposes, but there are times when you can express the same calculation equivalently using either of them. So with this input: test = RandomInteger[{-25, 25}, {20, 2}] {{-15, 13}, {-8, 16}, {-8, -19}, {7, 6}, {-21, 9}, {-3, -25}, {21, -18}, {4, 4}, {2, -2}, {-24, 8}, {-17, -8}, {4, -18}, {22, -24}, {-4, -3}, {21, 0}, {19, 18}, {-23, -8}, {23, -25}, {14, -2}, {-1, -13}} You can get the following equivalent results: Cases[test, {_, _?Positive}] {{-15, 13}, {-8, 16}, {7, 6}, {-21, 9}, {4, 4}, {-24, 8}, {19, 18}} Select[test, #[[2]] > 0 &] {{-15, 13}, {-8, 16}, {7, 6}, {-21, 9}, {4, 4}, {-24, 8}, {19, 18}} Pick[test, Sign[test[[All, 2]] ], 1] {{-15, 13}, {-8, 16}, {7, 6}, {-21, 9}, {4, 4}, {-24, 8}, {19, 18}} test[[Flatten@Position[test[[All, 2]], _?Positive] ]] {{-15, 13}, {-8, 16}, {7, 6}, {-21, 9}, {4, 4}, {-24, 8}, {19, 18}} Are there performance or other considerations that should guide which you shou...