5.3 Ingeniería de características
En aprendizaje máquina la ingeniería de características (Feature engineering) consiste en dotar al modelo de variables o características sintéticas que aporten información extra. Ya lo hemos hecho en el capítulo 4 con la variable ‘sexo’ para el enlazado probabilístico.
5.3.1 Nombre completo
En el nombre completo “JOSE CARLOS VICENTE PEREZ” ¿Cuáles son el nombre, primer apellido y segundo apellido?
Es fácil confundirse, ya que tanto CARLOS como VICENTE pueden ser tanto nombres propios como apellidos. En el caso de nombres extranjeros, la probabilidad de confusión es mayor.
En estos casos comparar con el nombre completo (nombre + primer apellido + segundo apellido) es mejor. Añadimos una variable con el nombre completo.
muestra <- muestra %>%
mutate(nombre_completo.x =
paste_3(bdc_nombre.x, bdc_apellido_1.x, bdc_apellido_2.x))
muestra <- muestra %>%
mutate(nombre_completo.y =
paste_3(bdc_nombre.y, bdc_apellido_1.y, bdc_apellido_2.y))
muestra %>%
filter(nombre_completo.x == nombre_completo.y & bdc_nombre.x != bdc_nombre.y) %>%
sample_n(4) %>%
select(bdc_nombre.x, bdc_apellido_1.x, bdc_apellido_2.x, nombre_completo.x) %>%
pulcro()
bdc_nombre.x | bdc_apellido_1.x | bdc_apellido_2.x | nombre_completo.x | |
---|---|---|---|---|
15 | LYUDMIL VALERIEV | HADZHIEV | NA | LYUDMIL VALERIEV HADZHIEV |
28 | MAGDALENA SERGEEVA | ZELEVA | NA | MAGDALENA SERGEEVA ZELEVA |
21 | LORA MIRCHEVA | BOSHNAKOVA | NA | LORA MIRCHEVA BOSHNAKOVA |
11 | LYUBOVKA | SIMEONOVA | BUYUKLIYSKA | LYUBOVKA SIMEONOVA BUYUKLIYSKA |
5.3.2 Distancias
La característica principal para la clasificación en enlazado de datos es la ‘similitud’ entre las variables a comparar. De hecho, en la comparativa de resultados de esta prueba de concepto no las consideramos características sintéticas.
Añadimos una variable con la distancia entre las posibles variables a comparar.
muestra <- muestra %>%
mutate(id_oficial.dis = stringdist(bdc_id_oficial.x, bdc_id_oficial.y)) %>%
mutate(nombre.dis = stringdist(bdc_nombre.x, bdc_nombre.y)) %>%
mutate(apellido_1.dis = stringdist(bdc_apellido_1.x, bdc_apellido_1.y)) %>%
mutate(apellido_2.dis = stringdist(bdc_apellido_2.x, bdc_apellido_2.y)) %>%
mutate(fecha_nacimiento.dis =
stringdist(bdc_fecha_nacimiento.x, bdc_fecha_nacimiento.y)) %>%
mutate(sexo.dis = stringdist(bdc_sexo.x, bdc_sexo.y)) %>%
mutate(municipio_domicilio.dis =
stringdist(bdc_municipio_domicilio.x, bdc_municipio_domicilio.y)) %>%
mutate(nombre_completo.dis = stringdist(nombre_completo.x, nombre_completo.y))
Muestra:
muestra %>%
filter(id_oficial.dis > 0 & id_oficial.dis < 4) %>%
tail(10) %>%
select(bdc_id_oficial.x, bdc_id_oficial.y, id_oficial.dis) %>%
pulcro()
bdc_id_oficial.x | bdc_id_oficial.y | id_oficial.dis | |
---|---|---|---|
1348 | C1026361 | X0102636X | 3 |
1349 | X11836Z | X0011836Z | 2 |
1350 | X560673W | X0560673W | 1 |
1351 | X117395A | X0117395A | 1 |
1352 | A07042425 | PA07042425 | 1 |
1353 | Y246596G | Y0246596G | 1 |
1354 | 15850725E | 15807025E | 2 |
1355 | X550777L | X0550777L | 1 |
1356 | 73601823Y | 73608723Y | 2 |
1357 | 15601838H | 15601631H | 2 |
5.3.3 Frecuencia de los nombres
¿Qué es más probable, que dos registros con el nombre “FRANCISCO JAVIER GARCIA” o que dos registros con el nombre “AURELIANO BUENDIA” correspondan a la misma persona?
Entre los residentes en Navarra es más probable el segundo caso, ya que el nombre AURELIANO y el apellido BUENDIA son mucho menos comunes que el nombre FRANCISCO JAVIER y el apellido GARCIA.
Añadimos una variable que indica lo comunes que son los nombres. Información proporcionada por el INE6.
ine_nombres <- get_ine_nombres()
ine_nombres %>% select(ine_nombre, ine_por_mil) %>% sample_n(10) %>%
arrange(desc(ine_por_mil)) %>%
pulcro()
ine_nombre | ine_por_mil |
---|---|
AMAIA | 6.353 |
JUAN ANTONIO | 3.119 |
ANTONIO LUIS | 0.135 |
LIBE | 0.111 |
BERNABE | 0.097 |
BADR | 0.047 |
NASSIRA | 0.034 |
LEONARDO DAVID | 0.019 |
GERMAN JOSE | 0.016 |
MIREN ARGIA | 0.015 |
muestra <- muestra %>%
left_join(select(ine_nombres, ine_nombre, ine_por_mil),
by = c("bdc_nombre.x" = "ine_nombre")) %>%
mutate(ine_por_mil.x = ine_por_mil) %>%
select(-ine_por_mil)
muestra <- muestra %>%
left_join(select(ine_nombres, ine_nombre, ine_por_mil),
by = c("bdc_nombre.y" = "ine_nombre")) %>%
mutate(ine_por_mil.y = ine_por_mil) %>%
select(-ine_por_mil)
Cruzamos esta tabla con la muestra para añadir las variables de frecuencia:
muestra %>% sample_n(10) %>%
select(bdc_nombre.x, ine_por_mil.x, bdc_nombre.y, ine_por_mil.y) %>%
pulcro()
bdc_nombre.x | ine_por_mil.x | bdc_nombre.y | ine_por_mil.y | |
---|---|---|---|---|
4506 | LUIS GUILLERMO | 0.028 | LUIS GUILLERMO | 0.028 |
10120 | LUISA MARIA | 0.231 | LUISA MARIA | 0.231 |
9245 | MAIDER BARDA | NA | MAIDER | 2.816 |
10218 | LIZ KARINA | NA | LIZ KARINA | NA |
8430 | MAITE | 5.608 | MAITE | 5.608 |
11534 | LUCIA | 6.076 | LUCIA | 6.076 |
8972 | LUIS | 7.039 | LUIS | 7.039 |
3328 | LUIS | 7.039 | LUIS | 7.039 |
11415 | LUIS MARIA | 2.589 | LUIS | 7.039 |
2133 | LOURDES | 1.812 | LOURDES | 1.812 |
Posible mejora: frecuncia sabiendo el sexo. El INE nos proporciona la frecuencia por sexo.
5.3.4 Id. nacional o extranjero
¿En qué casos dos registros con diferente identificador oficial pueden corresponder a la misma persona?
En el caso de los extranjeros que se nacionalizan, ya que pasan de tener un NIE a tener un DNI.
Añadimos una variable que, a partir del id oficial, crea un campo que indica si parece un id de un nacional o de un extranjero.
muestra <- muestra %>%
mutate(id_nacional_extranjero.x =
ifelse(is.na(bdc_id_oficial.x), NA,
ifelse(parece_nacional(bdc_id_oficial.x) |
parece_dni_sin_letra(bdc_id_oficial.x), "N",
ifelse(parece_extranjero(bdc_id_oficial.x) |
parece_extranjero_id_no_oficial(bdc_id_oficial.x), "E", NA))))
muestra <- muestra %>%
mutate(id_nacional_extranjero.y =
ifelse(is.na(bdc_id_oficial.y), NA,
ifelse(parece_nacional(bdc_id_oficial.y) |
parece_dni_sin_letra(bdc_id_oficial.y), "N",
ifelse(parece_extranjero(bdc_id_oficial.y) |
parece_extranjero_id_no_oficial(bdc_id_oficial.y), "E", NA))))
Muestra:
muestra %>%
filter(id_nacional_extranjero.x == "N" &
id_nacional_extranjero.y == "E") %>% sample_n(2) %>%
bind_rows(muestra %>% filter(id_nacional_extranjero.x == "E" &
id_nacional_extranjero.y == "N") %>%
sample_n(2)) %>%
bind_rows(muestra %>% filter(is.na(id_nacional_extranjero.x) |
is.na(id_nacional_extranjero.y)) %>%
sample_n(4)) %>%
select(bdc_id_oficial.x,id_nacional_extranjero.x,
bdc_id_oficial.y,id_nacional_extranjero.y) %>%
pulcro()
bdc_id_oficial.x | id_nacional_extranjero.x | bdc_id_oficial.y | id_nacional_extranjero.y |
---|---|---|---|
73121967E | N | M3105350M | E |
72708866R | N | X3814245V | E |
X6256266J | E | 25156637L | N |
X3754709M | E | 03754709M | N |
NA | NA | 10892581B | N |
X564020Z | NA | YB014284 | NA |
Y787477V | NA | Y0787477V | E |
18201469 | N | NA | NA |
5.3.5 Es menor de 14 años
Los menores de 14 años no están obligados a tener DNI. Es otro caso en el que dos registros con diferente identificador oficial pueden corresponder con la misma persona (uno vacío, el otro con un DNI)
Añadimos una variable que, a partir de la fecha de nacimiento y la fecha de la observación, crea un campo que indica si era menor de 14 años en el momento de la observación.
muestra <- muestra %>%
mutate(es_menor_de_14.x =
ifelse(is.na(bdc_fecha_nacimiento.x) | is.na(bdc_fecha_valor.x), NA,
difftime(as.Date(bdc_fecha_valor.x,"%Y-%m-%d"),
as.Date(bdc_fecha_nacimiento.x, "%Y-%m-%d")) < 365*14))
muestra <- muestra %>%
mutate(es_menor_de_14.y =
ifelse(is.na(bdc_fecha_nacimiento.y) | is.na(bdc_fecha_valor.y), NA,
difftime(as.Date(bdc_fecha_valor.y,"%Y-%m-%d"),
as.Date(bdc_fecha_nacimiento.y, "%Y-%m-%d")) < 365*14))
Muestra:
muestra %>%
filter(es_menor_de_14.x | es_menor_de_14.y) %>% head(4) %>%
bind_rows(muestra %>%
filter(is.na(es_menor_de_14.x) | is.na(es_menor_de_14.y)) %>%
head(4)) %>%
select(bdc_fecha_nacimiento.x,bdc_fecha_valor.x,es_menor_de_14.x) %>%
pulcro()
bdc_fecha_nacimiento.x | bdc_fecha_valor.x | es_menor_de_14.x |
---|---|---|
1979-12-15 | 2018-01-01 | FALSE |
1995-06-14 | 2018-01-01 | FALSE |
1996-02-13 | 2018-01-01 | FALSE |
1984-06-10 | 2018-01-01 | FALSE |
1991-05-14 | 2018-01-01 | FALSE |
1987-10-02 | 2018-01-01 | FALSE |
1937-02-25 | 2018-01-01 | FALSE |
1968-05-20 | 2018-01-01 | FALSE |
5.3.6 Subcadenas
En algunas variables, como el nombre y apellidos, puede ser más significativo que las distancia que la cadena más corta esté incluida en la cadena más larga:
## [1] 6
## [1] 4
Añadimos las variables nombre.incluido, apellido_1.incluido, apellido_2.incluido, municipio_domicilio.incluido, nombre_completo.incluido.
# https://www.rdocumentation.org/packages/stringr/versions/1.3.1/topics/str_detect
# Añado \\b para que compare con palabras completas,
# de manera que "LUIS" no esté 'incluido' en "LUISA" o "LUCIA" en "LUCIANA"
muestra <- muestra %>%
mutate(nombre.incluido =
ifelse(is.na(bdc_nombre.x) | is.na(bdc_nombre.y), NA,
str_detect(bdc_nombre.x, paste("\\b",bdc_nombre.y,"\\b",sep="")) |
str_detect(bdc_nombre.y, paste("\\b",bdc_nombre.x,"\\b",sep="")))) %>%
mutate(apellido_1.incluido =
ifelse(is.na(bdc_apellido_1.x) | is.na(bdc_apellido_1.y), NA,
str_detect(bdc_apellido_1.x, paste("\\b",bdc_apellido_1.y,"\\b",sep="")) |
str_detect(bdc_apellido_1.y, paste("\\b",bdc_apellido_1.x,"\\b",sep="")))) %>%
mutate(apellido_2.incluido =
ifelse(is.na(bdc_apellido_2.x) | is.na(bdc_apellido_2.y), NA,
str_detect(bdc_apellido_2.x, paste("\\b",bdc_apellido_2.y,"\\b",sep="")) |
str_detect(bdc_apellido_2.y, paste("\\b",bdc_apellido_2.x,"\\b",sep="")))) %>%
mutate(municipio_domicilio.incluido =
ifelse(is.na(bdc_municipio_domicilio.x) | is.na(bdc_municipio_domicilio.y), NA,
str_detect(bdc_municipio_domicilio.x,
paste("\\b",bdc_municipio_domicilio.y,"\\b",sep="")) |
str_detect(bdc_municipio_domicilio.y,
paste("\\b",bdc_municipio_domicilio.x,"\\b",sep="")))) %>%
mutate(nombre_completo.incluido =
ifelse(is.na(nombre_completo.x) | is.na(nombre_completo.y), NA,
str_detect(nombre_completo.x, paste("\\b",nombre_completo.y,"\\b",sep="")) |
str_detect(nombre_completo.y, paste("\\b",nombre_completo.x,"\\b",sep=""))))
Muestra:
muestra %>%
filter(bdc_nombre.x != bdc_nombre.y & (str_detect(bdc_nombre.x, bdc_nombre.y) |
str_detect(bdc_nombre.y, bdc_nombre.x) )) %>%
sample_n(10) %>%
select(bdc_nombre.x, bdc_nombre.y, nombre.dis, nombre.incluido) %>%
pulcro()
bdc_nombre.x | bdc_nombre.y | nombre.dis | nombre.incluido | |
---|---|---|---|---|
50 | LUIS | LUIS JUAN | 5 | TRUE |
543 | LUIS GERVASIO | LUIS | 9 | TRUE |
205 | LUIS | LUIS JOSE | 5 | TRUE |
115 | LUIS MIGUEL | LUIS | 7 | TRUE |
618 | MAGDALENA | MAGDALENA KRASIMIROV | 11 | TRUE |
116 | LUIS FRANCISCO | LUIS | 10 | TRUE |
48 | LUIS PEDRO | LUIS | 6 | TRUE |
383 | LUIS FERNANDO | LUIS | 9 | TRUE |
216 | LUIS MARIA | LUIS | 6 | TRUE |
136 | LUIS LEONCIO | LUIS | 8 | TRUE |
Fuera de la POC podríamos mejorar utilizando la frecuencia del nombre en la década de nacimiento de la persona, que es información que también proporciona el INE↩