Capítulo 4. Emparejamiento probabilístico

Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com

En el Capítulo 3, exploramos cómo utilizar técnicas de concordancia aproximada para medir el grado de similitud entre los valores de los atributos. Establecimos un umbral por encima del cual declaramos la equivalencia y luego combinamos estas características de coincidencia, con igual peso, para concluir que dos registros se referían a la misma entidad cuando ambos coincidían. Evaluamos nuestro rendimiento sólo con las coincidencias exactas.

En este capítulo, examinaremos cómo utilizar las técnicas basadas en la probabilidad para calcular la ponderación óptima de cada atributo equivalente al calcular la probabilidad de una coincidencia global de entidades. Este enfoque basado en la probabilidad nos permite declarar una coincidencia cuando los atributos estadísticamente más significativos son equivalentes (exactos o aproximados), pero los menos significativos no son suficientemente similares. También nos permite graduar nuestra confianza en la declaración de una coincidencia y aplicar umbrales de coincidencia adecuados. El modelo que se presentará en esta sección se conoce como modelo Fellegi-Sunter (FS).

También presentaremos un marco probabilístico de resolución de entidades, Splink, que utilizaremos para ayudarnos a calcular estas métricas y resolver nuestras entidades juntas.

Ejemplo de problema

Volvamos a nuestros resultados de coincidencia exacta del final del Capítulo 2. Abriendo el cuaderno Capítulo4.ipynb, volvemos a cargar los conjuntos de datos normalizados de los sitios web Wikipedia y TheyWorkForYou. Como en el Capítulo 3, empezamos por calculando el producto cartesiano, o cruzado, de los dos conjuntos de datos como:

cross = df_w.merge(df_t, how='cross', suffixes=('_w', '_t'))

Esto nos da una población total de 650 × 650 = 422.500 pares de registros: un par por cada combinación de nombres entre los conjuntos de datos de Wikipedia y TheyWorkForYou.

A lo largo de este capítulo, utilizaremos varias veces las coincidencias exactas entre los campos Firstname, Lastname y Constituency de cada uno de estos pares de registros. Por tanto, es más eficiente calcular estas coincidencias una vez y almacenarlas como columnas de características adicionales:

cross['Fmatch']= (cross['Firstname_w']==cross['Firstname_t'])
cross['Lmatch']= (cross['Lastname_w']==cross['Lastname_t'])
cross['Cmatch']= (cross['Constituency_w']==cross['Constituency_t'])

También calculamos el número total de columnas coincidentes, que utilizaremos más adelante:

cross['Tmatch'] =
    sum([cross['Fmatch'],cross['Lmatch'],cross['Cmatch']]) 

Basándonos en nuestra exploración de los datos en el Capítulo 2, sabemos que dentro de nuestra población total de 422.500 combinaciones tenemos 637 pares de registros que tienen una coincidencia exacta en circunscripción y nombre o apellidos. Ésta es nuestra población match:

match = cross[cross['Cmatch'] & (cross['Fmatch'] |
   cross['Lmatch'])]

El resto, nuestra población notmatch, se extrae como la inversa:

notmatch = cross[(~cross['Cmatch']) | (~cross['Fmatch'] &
    ~cross['Lmatch'])]

Estas combinaciones se resumen en la Tabla 4-1.

Tabla 4-1. Combinaciones coincidentes y no coincidentes
Población coincidente/no coincidente Coincidencia de circunscripción Coincidencia de nombre Coincidencia de apellidos
No coincide No No No
No coincide No No
No coincide No No
No coincide No
No coincide No No
Partido No
Partido No
Partido

A continuación examinaremos hasta qué punto la equivalencia entre nombre y apellidos, tanto individualmente como en conjunto, puede predecir si un registro individual pertenece a la población de match o de notmatch.

Probabilidad de coincidencia de un solo atributo

Empecemos considerando si la equivalencia del nombre de pila por sí sola es un buen indicador de que dos entidades dentro de un par de registros se refieren a la misma persona. Examinaremos las poblaciones match y notmatch y estableceremos, dentro de cada uno de esos subconjuntos, cuántos nombres de pila coinciden y cuántos no.

Convención de nomenclatura

Como trabajamos a través de varios subconjuntos de estas poblaciones, es útil adoptar una convención de nomenclatura estándar para que podamos ver de un vistazo cómo se seleccionó cada población de registros. A medida que seleccionamos registros, añadimos los criterios de selección al nombre de la población, de derecha a izquierda, por ejemplo, first_match debe leerse como que primero seleccionamos los registros que forman parte de la población match y, dentro de ese subconjunto de la población, seleccionamos además sólo las filas en las que los nombres de pila son equivalentes.

Probabilidad de coincidencia del nombre

Partiendo de la población match podemos seleccionar aquellos registros en los que los nombres de pila sean equivalentes para obtener nuestra población first_match:

first_match = match[match['Fmatch']]

len(first_match)
632

Repitiendo esto para las otras tres combinaciones de coincidencia/no coincidencia, y equivalencia o no del nombre de pila, podemos elaborar un mapa de población, como se muestra en la Figura 4-1.

Figura 4-1. Mapa de población con nombre de pila

Por tanto, basándonos sólo en la equivalencia del nombre, tenemos:

T r u e p o s i t i v e m a t c h e s ( T P ) = 632

F a l s e p o s i t i v e m a t c h e s ( F P ) = 2052

T r u e n e g a t i v e m a t c h e s ( T N ) = 419811

F a l s e n e g a t i v e m a t c h e s ( F N ) = 5

Ahora podemos calcular algunos valores de probabilidad. En primer lugar, la probabilidad de que un par de registros cuyos nombres de pila son equivalentes sea realmente una coincidencia positiva verdadera puede calcularse como el número de pares dentro de la población match cuyos nombres de pila coinciden dividido por el número de pares cuyos nombres de pila coinciden en las poblaciones match y notmatch:

p r o b _ m a t c h _ f i r s t = len(first_match) (len(first_match)+len(first_notmatch)) = 632 (632+2052) 0 . 2355

De esto se deduce que, con sólo un 23%, la equivalencia del nombre de pila por sí sola no es un buen indicador de la coincidencia entre dos registros. Este valor es una probabilidad condicional, es decir, es la probabilidad de una coincidencia positiva verdadera condicionada a que el nombre coincida. Se puede escribir como

P ( m a t c h | f i r s t )

donde el carácter pipa (|) se lee como "dado que".

Apellido Probabilidad de coincidencia

Aplicando los mismos cálculos al apellido, podemos dibujar un segundo mapa de población, como se muestra en la Figura 4-2.

Figura 4-2. Mapa de población con apellidos

Al igual que para el nombre, la probabilidad de que un par de registros cuyos apellidos son equivalentes coincida realmente puede calcularse como el número de pares dentro de la población match cuyos apellidos coinciden dividido por el número de pares cuyos apellidos coinciden en las poblaciones match y notmatch.

p r o b _ m a t c h _ l a s t = len(last_match) (len(last_match)+len(last_notmatch)) = 633 (633+349) 0 . 6446

Para estos registros, la equivalencia del apellido es claramente un mejor predictor de una coincidencia verdadera que el nombre, lo que instintivamente tiene sentido.

De nuevo, esto puede escribirse como:

P ( m a t c h | l a s t )

Probabilidad de coincidencia de atributos múltiples

Ahora, si tenemos en cuenta tanto la equivalencia del nombre como la del apellido, podemos subdividir aún más nuestro mapa de población. Partiendo de nuestro mapa de nombres de pila y subdividiendo cada categoría de nombres en equivalencia de apellidos y no, podemos ver nuestra población como se muestra en la Figura 4-3.

Figura 4-3. Mapa de población con nombre y apellidos

Ampliando nuestro cálculo a las coincidencias exactas tanto del nombre como del apellido, podemos calcular la probabilidad de una coincidencia positiva verdadera dada la equivalencia tanto del nombre como del apellido como:

p r o b _ m a t c h _ l a s t _ f i r s t = len(last_first_match) (len(last_first_match)+len(last_first_notmatch) = 628 (628+0) = 1 . 0

Si el nombre coincide pero el apellido no, ¿cuál es la probabilidad de que coincida?

p r o b _ m a t c h _ n o t l a s t _ f i r s t = len(notlast_first_match) (len(notlast_first_match)+len(notlast_first_notmatch)) = 4 (4+2052) 0 . 0019

Si el nombre no coincide pero el apellido sí, ¿cuál es la probabilidad de que coincida?

p r o b _ m a t c h _ l a s t _ n o t f i r s t = len(last_notfirst_match) (len(last_notfirst_match)+len(last_notfirst_notmatch)) = 5 (5+349) 0 . 0141

Como esperábamos, si el nombre o el apellido no coinciden exactamente, la probabilidad de una coincidencia positiva verdadera es baja, pero una coincidencia de apellido nos da más confianza que una de nombre.

Si no coinciden ni el nombre ni el apellido, la probabilidad de que coincida es:

p r o b _ m a t c h _ n o t l a s t _ n o t f i r s t =

len(notlast_notfirst_match) (len(notlast_notfirst_match)+len(notlast_notfirst_notmatch)) = 0 (0+419462) = 0

Esto no es sorprendente, ya que definimos las coincidencias positivas verdaderas como los registros con una coincidencia exacta en la circunscripción electoral y el nombre o los apellidos.

En conclusión, podemos utilizar estas probabilidades para decidir si es probable que tengamos una coincidencia positiva verdadera o no. En este ejemplo, daríamos más peso a una coincidencia de apellidos que a una de nombres. Esto supone una mejora respecto a nuestro método del Capítulo 3, en el que les dábamos la misma ponderación (y exigíamos que ambos fueran equivalentes) para declarar una coincidencia.

Pero espera, tenemos un problema. En el ejemplo anterior, empezamos con una población conocida de coincidencias que utilizamos para calcular las probabilidades de que la equivalencia entre nombre y apellidos equivalga a una coincidencia. Sin embargo, en la mayoría de las situaciones no disponemos de una población conocida de match; ¡de lo contrario no necesitaríamos realizar el cotejo en primer lugar! ¿Cómo superamos esto? Para ello, tenemos que replantear un poco nuestro cálculo y luego emplear algunas técnicas de estimación inteligentes.

Modelos probabilísticos

En la sección anterior, aprendimos que algunos atributos son más informativos que otros; es decir, tienen más poder predictivo para ayudarnos a decidir si es probable que una coincidencia sea correcta. En esta sección, examinamos cómo calcular estas contribuciones y cómo combinarlas para evaluar la probabilidad global de una coincidencia.

Empezaremos con un poco de teoría estadística (utilizando la equivalencia del nombre de pila como ejemplo) antes de generalizar para modelar lo que podemos implementar a escala.

Teorema de Bayes

El teorema de Bayes, llamado así por Thomas Bayes, afirma que la probabilidad condicional de un suceso, basada en la ocurrencia de otro suceso, es igual a la probabilidad del segundo suceso dado el primero, multiplicada por la probabilidad del primero.

Considera la probabilidad de que dos registros elegidos al azar sean una coincidencia positiva verdadera, P(coincidencia), multiplicada por la probabilidad de que dentro de esas coincidencias coincidan los primeros nombres, P (primera|comparación):

P ( f i r s t | m a t c h ) × P ( m a t c h )

Igualmente, podríamos calcular el mismo valor en el orden inverso, empezando por la probabilidad de que el primer nombre coincida, P(primero) multiplicada por la probabilidad de que los registros dentro de esta población sean una coincidencia positiva verdadera:

P ( m a t c h | f i r s t ) × P ( f i r s t )

Igualando estas probabilidades, tenemos

P ( m a t c h | f i r s t ) × P ( f i r s t ) = P ( f i r s t | m a t c h ) × P ( m a t c h )

Reordenando podemos calcular:

P ( m a t c h | f i r s t ) = P(first|match)×P(match) P(first)

Podemos calcular P(primero) como la suma de las probabilidades de las poblaciones match y notmatch:

P ( f i r s t ) = ( P ( f i r s t | m a t c h ) × P ( m a t c h ) + P ( f i r s t | n o t m a t c h ) × P ( n o t m a t c h ) )

Sustituyendo en la ecuación anterior, tenemos:

P ( m a t c h | f i r s t ) = P(first|match)×P(match) P(first|match)×P(match)+P(first|notmatch)×P(notmatch)

Alternativamente, podemos reordenar esto como:

P ( m a t c h | f i r s t ) = 1 - (1+P(first|match) P(first|notmatch)×P(match) P(notmatch)) -1

Si podemos estimar los valores de esta ecuación, podemos determinar la probabilidad de que si un nombre de pila es equivalente, entonces el par de registros realmente coincide.

Examinemos estos valores con un poco más de detalle, simplificando la notación a medida que avanzamos.

m Valor

La probabilidad condicional de que un atributo sea equivalente en el conjunto de la población match se conoce como valor m. Utilizando nuestro ejemplo Firstname, podemos denotarlo como:

m f = P ( f i r s t | m a t c h )

En un conjunto de datos perfecto, todos los nombres de pila de la población match serían exactamente equivalentes y el valor m sería 1. Por tanto, este valor puede considerarse una medida de la calidad de los datos, es decir, de la variabilidad que existe en la forma en que se ha capturado un atributo en los conjuntos de datos. Un valor más alto indica un atributo de mejor calidad.

u Valor

La probabilidad condicional de que un atributo sea equivalente en el conjunto de la población notmatch se conoce como valor u. De nuevo, utilizando nuestro ejemplo de Firstname, podemos denotarlo como:

u f = P ( f i r s t | n o t m a t c h )

Este valor refleja el grado de coincidencia de este atributo en los conjuntos de datos. Un valor más bajo indica un atributo menos común y más distintivo que, si se encontrara equivalente en un caso concreto, nos llevaría a cuestionar si pertenece a la población de notmatch y es realmente una coincidencia. Por el contrario, un valor u más alto nos indica que este atributo concreto no es tan valioso para determinar las coincidencias globales.

Un buen ejemplo de valor u es el atributo mes de nacimiento, que, suponiendo que la población se distribuye por igual a lo largo del año, tendrá un valor u de 1 12 .

Lambda ( λ ) Valor

El valor lambda de, λ también llamado prior, es la probabilidad de que dos registros elegidos al azar coincidan.

λ = P ( m a t c h )

A diferencia de los valores m y u, el λ es un valor a nivel de registro no asociado a ningún atributo concreto. Este valor es una medida de cuánta duplicación hay en el conjunto de datos en general y es el punto de partida para nuestros cálculos de probabilidad.

La inversa, la probabilidad de que dos registros elegidos al azar no coincidan, puede escribirse así:

1 - λ = P ( n o t m a t c h )

Factor de Bayes

Sustituyendo estas notaciones compactas podemos obtener lo siguiente:

P ( m a t c h | f i r s t ) = 1 - (1+m f u f ×λ (1-λ)) -1

La relación m f u f también se conoce como factor de Bayes, en este caso del parámetro Firstname. El factor Bayes, como combinación de los valores m y u, da una medida de la importancia que debemos atribuir al hecho de que los valores Firstname fueran equivalentes.

Modelo Fellegi-Sunter

El modelo Fellegi-Sunter, llamado así por Ivan P. Fellegi y Alan B. Sunter,1 describe cómo podemos ampliar nuestro sencillo enfoque bayesiano, combinando la contribución de múltiples atributos, para calcular la probabilidad global de una coincidencia. Se basa en el supuesto simplificador de independencia condicional entre atributos, también conocido como naive Bayes.

Utilizando el modelo FS, podemos combinar los factores de Bayes asociados a cada atributo de nuestro registro simplemente multiplicándolos entre sí. Tomando nuestro ejemplo de Firstname y ampliándolo para considerar cuando el Lastname también es equivalente, tenemos:

P ( m a t c h | l a s t | f i r s t ) = 1 - (1+m f u f ×m l u l ×λ (1-λ)) -1

Cuando un atributo no es equivalente, el factor de Bayes se calcula como el inverso, (1-m l ) (1-u l ) . Por tanto, cuando el Firstname es equivalente pero el Lastname no lo es, calculamos la probabilidad de una coincidencia global como:

P ( m a t c h | n o t l a s t | f i r s t ) = 1 - (1+m f u f ×(1-m l ) (1-u l )×λ (1-λ)) -1

Una vez que podamos calcular los valores m y u de cada atributo, y la λ del conjunto de datos, podemos calcular fácilmente las probabilidades de cada par de registros. Simplemente determinamos la equivalencia de cada atributo (exacta o aproximada según convenga), seleccionamos los factores de Bayes adecuados y los multiplicamos entre sí mediante la fórmula anterior para calcular la probabilidad global de ese par de registros.

Por tanto, para nuestro sencillo ejemplo, nuestros factores de Bayes se calculan como se muestra en la Tabla 4-2.

Tabla 4-2. Firstname, Lastname cálculos del factor de coincidencia
Firstname equivalencia Lastname equivalencia Firstname Factor de Bayes Lastname Factor de Bayes Factor de Bayes combinado
No No (1-m f ) (1-u f ) (1-m l ) (1-u l ) (1-m f ) (1-u f ) × (1-m l ) (1-u l )
No (1-m f ) (1-u f ) m l u l (1-m f ) (1-u f ) × m l u l
No m f u f (1-m l ) (1-u l ) m f u f × (1-m l ) (1-u l )
m f u f m l u l m f u f × m l u l

Peso del partido

Para hacer más intuitivos los cálculos globales de las coincidencias, a veces se utiliza el logaritmo de los factores de Bayes, de modo que puedan sumarse en lugar de multiplicarse. De este modo es fácil visualizar la contribución relativa de cada atributo a la puntuación global.

Para nuestro sencillo ejemplo de equivalencia entre nombre y apellidos, el peso logarítmico de coincidencia podría calcularse (utilizando base 2) como:

M a t c h W e i g h t = l o g 2 m f u f + l o g 2 m l u l + l o g 2 λ (1-λ)

Podemos calcular la probabilidad a partir del peso del partido como:

P r o b a b i l i t y = 1 - (1+2 MatchWeight ) -1

Ahora que sabemos cómo combinar nuestras probabilidades de atributos individuales, o ponderaciones de coincidencia, consideremos cómo estimar nuestra λ y nuestros valores m y u para cada atributo cuando no tenemos una población match conocida. Una técnica que podemos utilizar se llama algoritmo de maximización de expectativas (EM).

Algoritmo de maximización de expectativas

El algoritmo de maximización de expectativas utiliza un enfoque iterativo para aproximar la λ y los valores m y u. Veamos una forma simplificada de esto en acción aplicada a nuestro problema de ejemplo.

Iteración 1

Para la primera iteración hacemos la suposición inicial de que los pares de registros en los que la mayoría de las columnas de características son equivalentes son coincidentes:

it1_match = cross[cross['Tmatch']>=2]
it1_notmatch = cross[cross['Tmatch']<2]

len(it1_match)
637

Esto nos da una pseudopoblación match, it1_match, de 637 registros. Además de las 628 coincidencias perfectas que encontramos en el Capítulo 2, también tenemos 9 coincidencias en las que o bien Firstname o bien Lastname (pero no ambas) no coinciden, como vemos en la Figura 4-4:

it1_match[~it1_match['Fmatch'] | ~it1_match['Lmatch']]
   [['Constituency_w','Firstname_w','Firstname_t',
      'Lastname_w','Lastname_t']]
Figura 4-4. Coincidencias adicionales de la iteración 1 de la maximización de expectativas

Nuestra inicial λ inicial es por tanto

λ 1 = 637 650×650 0 . 0015

( 1 - λ 1 ) = ( 1 - 0 . 0015 ) 0 . 9985

Por tanto, nuestra ponderación de coincidencia previa inicial es l o g 2 λ 1 (1-λ 1 ) - 9 . 371 .

Por tanto, como punto de partida, es extremadamente improbable que dos registros coincidan. Ahora calculemos nuestros valores m y u para poder actualizar nuestra probabilidad por registro.

Como tenemos una pseudopoblación match y notmatch, es sencillo calcular nuestros valores m y u como la proporción de cada población con un atributo equivalente. Para Firstname, Lastname, y Constituency utilizamos:

mfi1 = len(it1_match[it1_match['Fmatch']])/len(it1_match)
mli1 = len(it1_match[it1_match['Lmatch']])/len(it1_match)
mci1 = len(it1_match[it1_match['Cmatch']])/len(it1_match)

ufi1 = len(it1_notmatch[it1_notmatch['Fmatch']])/len(it1_notmatch)
uli1 = len(it1_notmatch[it1_notmatch['Lmatch']])/len(it1_notmatch)
uci1 = len(it1_notmatch[it1_notmatch['Cmatch']])/len(it1_notmatch)

La Tabla 4-3 muestra estos valores y los valores de peso de coincidencia resultantes por atributo.

Tabla 4-3. Valores m y u de la iteración 1
Atributo m valor u valor Factor Bayes de coincidencia Peso del partido No coincide con el factor de Bayes No coincide con el peso
Firstname 0.9921 0.0049 203.97 7.67 0.0079 -6.98
Lastname 0.9937 0.0008 1201.19 10.23 0.0063 -7.31
Constituency 1.0 0.0 0 -

No hay pares de registros en los que la circunscripción sea equivalente en la población notmatch, por lo que su valor u es 0 y, por tanto, su peso match es matemáticamente infinito y su peso notmatch es infinito negativo.

Ahora podemos utilizar estos valores en el modelo Fellegi-Sunter para calcular la probabilidad de coincidencia de cada par de registros de la población completa. Utilizamos una función de ayuda para calcular estas probabilidades basándonos en los valores de las características de coincidencia Constituency, Firstname y Lastname:

def match_prb(Fmatch,Lmatch,Cmatch,mf1,ml1,mc1,uf1,ul1,uc1, lmbda):
    if (Fmatch==1):
        mf = mf1
        uf = uf1
    else:
        mf = (1-mf1)
        uf = (1-uf1)
    if (Lmatch==1):
        ml = ml1
        ul = ul1
    else:
        ml = (1-ml1)
        ul = (1-ul1)
    if (Cmatch==1):
        mc = mc1
        uc = uc1
    else:
        mc = (1-mc1)
        uc = (1-uc1)
    prob = (lmbda * ml * mf * mc) / (lmbda * ml * mf * mc +
           (1-lmbda) * ul * uf * uc)
    return(prob)

Aplicamos esta función a toda la población con:

cross['prob'] = cross.apply(lambda x: match_prb(
      x.Fmatch,x.Lmatch,x.Cmatch,
      mfi1,mli1,mci1,
      ufi1,uli1,uci1,
      lmbda), axis=1)

Una vez calculados estos valores, podemos iterar de nuevo, resegmentando nuestra población en poblaciones match y notmatch en función de las probabilidades de coincidencia calculadas.

Iteración 2

A título ilustrativo, utilizamos una probabilidad de coincidencia global superior a 0,99 para definir nuestra nueva población supuesta match y asignamos cualquier registro con una probabilidad igual o inferior a nuestra población notmatch:

it2_match = cross[cross['prob']>0.99]
it2_notmatch = cross[cross['prob']<=0.99]

len(it2_match)
633

La aplicación de este umbral de 0,99 nos da una población match ligeramente reducida de 633 habitantes. Veamos por qué. Si seleccionamos los registros situados justo por debajo del umbral, podemos ver:

it2_notmatch[it2_notmatch['prob']>0.9]
   [['Constituency_w', 'Lastname_w','Lastname_t','prob']]
Figura 4-5. Registros de la iteración 2 con umbrales de coincidencia por debajo de la línea

Como vemos en la Figura 4-5, si el Lastname no es equivalente, la nueva probabilidad de coincidencia cae justo por debajo de nuestro umbral de 0,99. Utilizando estas nuevas poblaciones match y notmatch podemos revisar nuestra λ m y u e iterar de nuevo, recalculando las probabilidades de coincidencia de cada par de registros.

En este caso, nuestra λ no cambia materialmente:

λ 2 = 633 650×650 0 . 0015

Sólo cambian ligeramente los valores de Lastname, como se muestra en la Tabla 4-4.

Tabla 4-4. Valores m y u de la iteración 2
Atributo m valor u valor Factor Bayes de coincidencia Peso del partido No coincide con el factor de Bayes No coincide con el peso
Firstname 0.9921 0.0049 203.97 7.67 0.0079 -6.98
Lastname 1.0 0.0008 1208.79 10.24 0 -
Constituency 1.0 0.0 0 -

Iteración 3

En este sencillo ejemplo, esta siguiente iteración no cambia la población de match, que sigue siendo 633, porque el algoritmo EM ya ha convergido.

Esto nos da unos valores finales de los parámetros de

λ 0 . 0015

m f = P ( f i r s t | m a t c h ) 0 . 9921

m l = P ( l a s t | m a t c h ) 1 . 0

m c = P ( c o n s t i t u e n c y | m a t c h ) 1 . 0

u f = P ( f i r s t | n o t m a t c h ) 0 . 0049

u l = P ( l a s t | n o t m a t c h ) 0 . 0008

u c = P ( c o n s t i t u e n c y | n o t m a t c h ) 0

Esto nos parece instintivamente correcto. Sabemos que una coincidencia siempre tendrá una circunscripción equivalente y que coincidirá el nombre o el apellido, siendo ligeramente más probable que el apellido sea equivalente que el nombre (cinco de cada nueve frente a cuatro de cada nueve en la muestra anterior).

Del mismo modo, sabemos que la circunscripción nunca será la misma en un par de registros de notmatch y es muy poco probable que el nombre o el apellido coincidan accidentalmente (siendo ligeramente más probable el nombre).

Podemos convertir estos valores estimados en probabilidades de coincidencia utilizando las ecuaciones de la sección anterior:

P ( m a t c h | l a s t | f i r s t ) = 1 - (1+m f u f ×m l u l ×λ (1-λ)) -1 = 1 . 0

P ( m a t c h | n o t l a s t | f i r s t ) = 1 - (1+m f u f ×(1-m l ) (1-u l )×λ (1-λ)) -1 0 . 0019

P ( m a t c h | n o t f i r s t | l a s t ) = 1 - (1+(1-m f ) (1-u f )×m l u l ×λ (1-λ)) -1 0 . 0141

P ( m a t c h | n o t f i r s t | n o t l a s t ) = 1 - (1+(1-m f ) (1-u f )×(1-m l ) (1-u l )×λ (1-λ)) -1 = 0

Como era de esperar, estas probabilidades coinciden con los valores que calculamos utilizando los mapas de probabilidad de la Figura 4-3 cuando conocíamos de antemano la población de match y notmatch.

En conclusión, ahora podemos estimar las probabilidades de coincidencia para todas las permutaciones diferentes de equivalencia de atributos sin tener que conocer de antemano la población match. Este enfoque probabilístico es potente y escalable a grandes conjuntos de datos con múltiples atributos. Para ayudarnos a aplicar estas técnicas con mayor facilidad, en la siguiente sección presentamos una biblioteca de código abierto eficaz y fácil de usar, Splink.

Presentación de Splink

Splink es un paquete de Python para la resolución probabilística de entidades. Splink implementa el modelo Fellegi-Sunter e incluye diversos resultados interactivos para ayudar a los usuarios a comprender los modelos y diagnosticar los problemas de vinculación.

Splink admite varios backends para realizar los cálculos de coincidencia. Para empezar, utilizaremos DuckDB, un sistema de gestión de bases de datos SQL en proceso, que podemos ejecutar localmente en nuestro portátil.

Configurar Splink

Para importar Splink a nuestro bloc de notas, utilizamos:

import splink

Splink requiere una columna ID única en cada conjunto de datos, así que tenemos que crearlas copiando sus respectivos índices DataFrame:

df_w['unique_id'] = df_w.index
df_t['unique_id'] = df_t.index

Splink también necesita que las mismas columnas estén presentes en ambos conjuntos de datos. Por lo tanto, tenemos que crear columnas en blanco cuando éstas sólo estén presentes en un conjunto de registros y, a continuación, eliminar las columnas innecesarias:

df_w['Flink'] = None
df_t['Notes'] = None

df_w = df_w[['Firstname','Lastname','Constituency','Flink','Notes',
   'unique_id']]
df_t = df_t[['Firstname','Lastname','Constituency','Flink','Notes',
   'unique_id']]

Nuestro siguiente paso es configurar los ajustes de Splink:

from splink.duckdb.linker import DuckDBLinker
from splink.duckdb import comparison_library as cl

settings = {
   "link_type": "link_only", "comparisons": [
       cl.exact_match("Firstname"),
       cl.exact_match("Lastname"),
       cl.exact_match("Constituency"),
   ],
}
linker = DuckDBLinker([df_w, df_t], settings)

Splink admite tanto la deduplicación de registros dentro de un único conjunto de datos como la vinculación entre uno o más conjuntos de datos independientes. Aquí establecemos link_type en link_only para indicar a Splink que sólo queremos coincidencias, no deduplicaciones, entre nuestros dos conjuntos de datos. También indicamos a Splink las comparaciones que deseamos utilizar, en este caso coincidencias exactas entre nuestros tres atributos. Por último, instanciamos el enlazador con estos ajustes y nuestros DataFrames fuente.

Para ayudarnos a comprender nuestros conjuntos de datos, Splink proporciona una visualización de la distribución de las columnas que se van a emparejar:

linker.profile_columns(['Firstname','Lastname','Constituency'])

Los gráficos que vemos en la Figura 4-6 nos muestran la población combinada de ambos conjuntos de datos.

Empezando por la distribución de los nombres de pila, podemos ver en la parte inferior derecha del gráfico que, dentro de la población de 352 nombres distintos, aproximadamente el 35% sólo aparecen dos veces, muy probablemente una vez en cada conjunto de datos. Luego, moviéndonos de derecha a izquierda, vemos un aumento gradual de la frecuencia hasta el nombre más popular, con 32 ocurrencias. Si observamos los 10 valores principales por recuento de valores, vemos que John es el nombre más popular, seguido de Andrew, David, etc. Esto nos dice que Firstname es un atributo razonable para buscar coincidencias, pero si se utiliza solo, dará lugar a algunos falsos positivos.

En cuanto a los apellidos, el patrón es más marcado, con una población mayor de 574 nombres distintos, de los que casi el 80% aparecen sólo dos veces. Si nos fijamos en los 10 valores principales, los apellidos más comunes, Smith y Jones, aparecen 18 veces, casi la mitad que el nombre más popular. Como era de esperar, esto nos dice que Lastname es un atributo más rico que Firstname y, por tanto, su equivalencia es un mejor predictor de la coincidencia de entidades.

Como era de esperar, las circunscripciones están emparejadas de forma única en los dos conjuntos de datos, por lo que todos los valores aparecen exactamente dos veces.

Figura 4-6. Perfiles de columna Splink

A efectos de este sencillo ejemplo, vamos a pedir a Splink que calcule todos los parámetros del modelo utilizando el algoritmo de maximización de expectativas que hemos introducido antes. El parámetro inicial True indica a Splink que compare todos los registros de ambos conjuntos de datos sin bloquearlos (lo veremos en el próximo capítulo). También le decimos a Splink que recalcule los valores de u en cada iteración estableciendo fix_u_probabilities en False. Si establecemos fix_probability_two_random_records_match en False, la λ (la probabilidad general de coincidencia entre los dos conjuntos de datos) se recalculará en cada iteración. Por último, le decimos a Splink que utilice el valor actualizado de λ actualizado al calcular las probabilidades de los pares de registros.:

em_session = linker.estimate_parameters_using_expectation_maximisation(
   'True',
   fix_u_probabilities=False,
   fix_probability_two_random_records_match=False,
   populate_probability_two_random_records_match_from_trained_values
     =True)

Rendimiento de Splink

El modelo EM de converge tras tres iteraciones. Splink produce un gráfico interactivo que muestra la progresión iterativa de los valores relativos de los pesos de coincidencia:

em_session.match_weights_interactive_history_chart()
Figura 4-7. Pesos de coincidencia de Splink

La Figura 4-7 muestra los pesos finales de coincidencia que Splink ha calculado tras la tercera iteración. En primer lugar, tenemos el peso de coincidencia previo (inicial), que es una medida de la probabilidad de que coincidan dos registros elegidos al azar. Si pasas el ratón por encima de las barras de peso de coincidencia, podrás ver el valor calculado del peso de coincidencia junto con los parámetros m y u subyacentes. Éstos se calculan del siguiente modo:

P r i o r ( s t a r t i n g ) m a t c h w e i g h t = l o g 2 λ (1-λ) - 9 . 38

F i r s t n a m e m a t c h w e i g h t ( e x a c t m a t c h ) = l o g 2 m f u f 7 . 67

F i r s t n a m e m a t c h w e i g h t ( n o t e x a c t m a t c h ) = l o g 2 (1-m f ) (1-u f ) - 6 . 98

L a s t n a m e m a t c h w e i g h t ( e x a c t m a t c h ) = l o g 2 m l u l 10 . 23

L a s t n a m e m a t c h w e i g h t ( n o t e x a c t m a t c h ) = l o g 2 (1-m l ) (1-u l ) - 7 . 32

C o n s t i t u e n c y m a t c h w e i g h t ( e x a c t m a t c h ) = l o g 2 m c u c 14 . 98

A efectos ilustrativos, Splink aproxima el peso de la coincidencia no exacta de Constituency como infinito negativo y lo muestra en un color diferente. Esto se debe a que no hay casos en los que los atributos Firstname y Lastname coincidan pero el Constituency no.

Podemos ver los valores discretos que Splink ha calculado utilizando:

linker.save_settings_to_json("Chapter4_Splink_Settings.json",
   overwrite=True)
{'link_type': 'link_only',
'comparisons': [{'output_column_name': 'Firstname',
   'comparison_levels': [{'sql_condition': '"Firstname_l" IS NULL OR
       "Firstname_r" IS NULL',
     'label_for_charts': 'Null',
     'is_null_level': True},
    {'sql_condition': '"Firstname_l" = "Firstname_r"',
     'label_for_charts': 'Exact match',
     'm_probability': 0.992118804074688,
     'u_probability': 0.004864290128404288},
    {'sql_condition': 'ELSE',
     'label_for_charts': 'All other comparisons',
     'm_probability': 0.007881195925311958,
     'u_probability': 0.9951357098715956}],
   'comparison_description': 'Exact match vs. anything else'},
  {'output_column_name': 'Lastname',
   'comparison_levels': [{'sql_condition': '"Lastname_l" IS NULL OR
       "Lastname_r" IS NULL',
     'label_for_charts': 'Null',
     'is_null_level': True},
    {'sql_condition': '"Lastname_l" = "Lastname_r"',
     'label_for_charts': 'Exact match',
     'm_probability': 0.9937726043638647,
     'u_probability': 0.00082730840955421},
    {'sql_condition': 'ELSE',
     'label_for_charts': 'All other comparisons',
     'm_probability': 0.006227395636135347,
     'u_probability': 0.9991726915904457}],
   'comparison_description': 'Exact match vs. anything else'},
  {'output_column_name': 'Constituency',
   'comparison_levels': [{'sql_condition': '"Constituency_l" IS NULL OR
       "Constituency_r" IS NULL',
     'label_for_charts': 'Null',
     'is_null_level': True},
    {'sql_condition': '"Constituency_l" = "Constituency_r"',
     'label_for_charts': 'Exact match',
     'm_probability': 0.9999999403661186,
     'u_probability': 3.092071473132138e-05},
    {'sql_condition': 'ELSE',
     'label_for_charts': 'All other comparisons',
     'm_probability': 5.963388147277392e-08,
     'u_probability': 0.9999690792852688}],
   'comparison_description': 'Exact match vs. anything else'}],
'retain_intermediate_calculation_columns': True,
'retain_matching_columns': True,
'sql_dialect': 'duckdb',
'linker_uid': 'adm20und',
'probability_two_random_records_match': 0.0015075875293170335}

Las probabilidades m y u coinciden con las que calculamos manualmente utilizando el algoritmo de maximización de expectativas anteriormente en el capítulo.

Por último, como antes, aplicamos un umbral de probabilidad de coincidencia y seleccionamos el par de registros por encima del umbral:

pres = linker.predict(threshold_match_probability =
   0.99).as_pandas_dataframe()

len(pres)
633

El análisis de estas predicciones muestra que las 633 son verdaderos positivos, quedando los 13 verdaderos negativos de las elecciones parciales y 4 falsos negativos. Podemos ver los 4 falsos negativos con:

m_outer = match.merge(
   pres,
   left_on=['Constituency_t'],
   right_on=['Constituency_l'],
   how='outer')

m_outer[m_outer['Constituency_t']!=m_outer['Constituency_l']]
   [['Constituency_w','Lastname_w','Lastname_t']]

El resultado, mostrado en la Figura 4-8, muestra que la falta de coincidencia en Lastname es la razón por la que estas entidades quedan por debajo del umbral de coincidencia.

Figura 4-8. Splink por debajo del umbral debido al desajuste de Lastname

En comparación con los resultados no ponderados del Capítulo 3, Splink declara una coincidencia para "Liz Truss" frente a "Elizabeth Truss", pero no empareja "Anne Marie Morris" con "Anne Morris", ni "Martin Docherty-Hughes" con "Martin Docherty". Esto se debe a que está más influenciado por una falta de coincidencia en Lastname, que estadísticamente es un mejor predictor negativo, que por una falta de coincidencia en Firstname.

Resumen

Para recapitular, tomamos dos conjuntos de registros y los combinamos en un conjunto de datos compuesto que contenía cada combinación de pares de registros. A continuación, calculamos las características de coincidencia exacta entre campos equivalentes y luego combinamos esas características, ponderadas según la frecuencia con que se daban tanto en la población coincidente como en la no coincidente, para determinar la probabilidad global de coincidencia.

Hemos visto cómo utilizar la teoría de la probabilidad para calcular los pesos de coincidencia utilizando el algoritmo iterativo de maximización de expectativas cuando no tenemos poblaciones conocidas de match.

Por último, introdujimos el marco de resolución probabilística de entidades Splink, que simplificó enormemente los cálculos al combinar múltiples atributos y nos ayudó a visualizar y comprender los resultados de nuestras coincidencias.

Ahora que hemos trabajado con un ejemplo a pequeña escala, aplicaremos las técnicas de emparejamiento aproximado y probabilístico a mayor escala.

1 El documento original está disponible en Internet.

Get Resolución práctica de entidades now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.