J'ai deux data.frame
s avec plusieurs colonnes communes (ici: date
, city
, ctry
et ( other_
) number
).
Je voudrais maintenant les fusionner dans les colonnes ci-dessus mais tolérer un certain niveau de différence:
threshold.numbers <- 3
threshold.date <- 5 # in days
Si la différence entre les date
entrées est > threshold.date
(en jours) ou > threshold.numbers
, je ne veux pas que les lignes soient fusionnées. De même, si l'entrée dans city
est une sous-chaîne de l' df
entrée de l'autre dans la city
colonne, je veux que les lignes soient fusionnées. [Si quelqu'un a une meilleure idée pour tester la similitude des noms de villes réels, je serais heureux d'en entendre parler.] (Et conservez les premières df
entrées de date
, city
et country
mais les deux ( other_
) number
colonnes et toutes les autres colonnes dans le df
.
Prenons l'exemple suivant:
df1 <- data.frame(date = c("2003-08-29", "1999-06-12", "2000-08-29", "1999-02-24", "2001-04-17",
"1999-06-30", "1999-03-16", "1999-07-16", "2001-08-29", "2002-07-30"),
city = c("Berlin", "Paris", "London", "Rome", "Bern",
"Copenhagen", "Warsaw", "Moscow", "Tunis", "Vienna"),
ctry = c("Germany", "France", "UK", "Italy", "Switzerland",
"Denmark", "Poland", "Russia", "Tunisia", "Austria"),
number = c(10, 20, 30, 40, 50, 60, 70, 80, 90, 100),
col = c("apple", "banana", "pear", "banana", "lemon", "cucumber", "apple", "peach", "cherry", "cherry"))
df2 <- data.frame(date = c("2003-08-29", "1999-06-12", "2000-08-29", "1999-02-24", "2001-04-17", # all identical to df1
"1999-06-29", "1999-03-14", "1999-07-17", # all 1-2 days different
"2000-01-29", "2002-07-01"), # all very different (> 2 weeks)
city = c("Berlin", "East-Paris", "near London", "Rome", # same or slight differences
"Zurich", # completely different
"Copenhagen", "Warsaw", "Moscow", "Tunis", "Vienna"), # same
ctry = c("Germany", "France", "UK", "Italy", "Switzerland", # all the same
"Denmark", "Poland", "Russia", "Tunisia", "Austria"),
other_number = c(13, 17, 3100, 45, 51, 61, 780, 85, 90, 101), # slightly different to very different
other_col = c("yellow", "green", "blue", "red", "purple", "orange", "blue", "red", "black", "beige"))
Maintenant, je voudrais fusionner le data.frames
et recevoir un df
où les lignes sont fusionnées si les conditions ci-dessus sont remplies.
(La première colonne est uniquement pour votre commodité: derrière le premier chiffre, qui indique le cas d'origine, elle indique si les lignes ont été fusionnées ( .
) ou si les lignes proviennent de df1
( 1
) ou df2
( 2
).
date city ctry number other_col other_number other_col2 #comment
1. 2003-08-29 Berlin Germany 10 apple 13 yellow # matched on date, city, number
2. 1999-06-12 Paris France 20 banana 17 green # matched on date, city similar, number - other_number == threshold.numbers
31 2000-08-29 London UK 30 pear <NA> <NA> # not matched: number - other_number > threshold.numbers
32 2000-08-29 near London UK <NA> <NA> 3100 blue #
41 1999-02-24 Rome Italy 40 banana <NA> <NA> # not matched: number - other_number > threshold.numbers
42 1999-02-24 Rome Italy <NA> <NA> 45 red #
51 2001-04-17 Bern Switzerland 50 lemon <NA> <NA> # not matched: cities different (dates okay, numbers okay)
52 2001-04-17 Zurich Switzerland <NA> <NA> 51 purple #
6. 1999-06-30 Copenhagen Denmark 60 cucumber 61 orange # matched: date difference < threshold.date (cities okay, dates okay)
71 1999-03-16 Warsaw Poland 70 apple <NA> <NA> # not matched: number - other_number > threshold.numbers (dates okay)
72 1999-03-14 Warsaw Poland <NA> <NA> 780 blue #
81 1999-07-16 Moscow Russia 80 peach <NA> <NA> # not matched: number - other_number > threshold.numbers (dates okay)
82 1999-07-17 Moscow Russia <NA> <NA> 85 red #
91 2001-08-29 Tunis Tunisia 90 cherry <NA> <NA> # not matched: date difference < threshold.date (cities okay, dates okay)
92 2000-01-29 Tunis Tunisia <NA> <NA> 90 black #
101 2002-07-30 Vienna Austria 100 cherry <NA> <NA> # not matched: date difference < threshold.date (cities okay, dates okay)
102 2002-07-01 Vienna Austria <NA> <NA> 101 beige #
J'ai essayé différentes implémentations pour les fusionner, mais je n'arrive pas à implémenter le seuil.
EDIT Excuses pour la formulation peu claire - Je voudrais conserver toutes les lignes et recevoir un indicateur si la ligne est appariée, sans correspondance et de df1 ou sans correspondance et de df2.
le pseudo-code est:
if there is a case where abs("date_df2" - "date_df1") <= threshold.date:
if "ctry_df2" == "ctry_df1":
if "city_df2" ~ "city_df1":
if abs("number_df2" - "number_df1") <= threshold.numbers:
merge and go to next row in df2
else:
add row to df1```
.
?Réponses:
Voici une solution qui utilise mon package safejoin , enveloppant dans ce cas le package fuzzyjoin .
Nous pouvons utiliser l'
by
argument pour spécifier une condition complexe, en utilisant la fonctionX()
pour obtenir la valeurdf1
etY()
la valeurdf2
.Si vos vraies tables sont grandes, cela peut être lent ou impossible car c'est un produit cartésien, mais ici, cela fonctionne bien.
Ce que nous voulons, c'est une jointure complète (conserver toutes les lignes et joindre ce qui peut être joint), et nous voulons conserver la première valeur quand ils se joignent, et prendre la suivante de l'autre, cela signifie que nous voulons faire face au conflit de colonnes nommées de manière identique par coalescence, nous utilisons donc l'argument
conflict = dplyr::coalesce
production :
Créé le 2019-11-13 par le package reprex (v0.3.0)
Malheureusement fuzzyjoin contraint toutes les colonnes dans une matrice lorsque vous faites un à plusieurs rejoindre, et safejoin enveloppements fuzzyjoin donc nous devons convertir les variables du type approprié à l' intérieur du par l' argument, ce qui explique les premières lignes de l'
by
argument.En savoir plus sur safejoin : https://github.com/moodymudskipper/safejoin
la source
J'ai d'abord transformé les noms de villes en vecteurs de caractères, car (si j'ai bien compris), vous souhaitez inclure les noms de villes contenus dans df2.
Fusionnez-les ensuite par pays:
La bibliothèque
stringr
vous permettra de voir si city.x est dans city.y ici (voir dernière colonne):Ensuite, vous pouvez obtenir la différence en jours entre les dates:
et la différence en nombre:
Voici à quoi ressemble la trame de données résultante:
Mais nous voulons supprimer les choses où city.x n'a pas été trouvé dans city.y, où la différence de jour est supérieure à 5 ou la différence de nombre est supérieure à 3:
Ce qui reste sont les trois lignes que vous aviez au-dessus (qui contenaient des points dans la colonne 1).
Maintenant, nous pouvons supprimer les trois colonnes que nous avons créées, ainsi que la date et la ville de df2:
la source
Étape 1: fusionner les données basées sur "ville" et "pays":
Étape 2: supprimez les lignes si la différence entre les entrées de date est> seuil.date (en jours):
Étape 3: supprimez les lignes si la différence entre les nombres est> threshhold.number:
Les données doivent être fusionnées avant d'appliquer des conditions, au cas où les lignes ne correspondent pas.
la source
Une option utilisant
data.table
(explications en ligne):production:
la source
Vous pouvez tester le
city
match avecgrepl
etctry
simple avec==
. Pour ceux qui correspondent jusqu'ici, vous pouvez calculer la différence de date en la convertissant endate
utilisantas.Date
et en la comparant à adifftime
. Lanumber
différence se fait de la même manière.la source
Voici une approche flexible qui vous permet de spécifier n'importe quelle collection de critères de fusion que vous choisissez.
Travail préparatoire
Je me suis assuré que toutes les chaînes étaient
df1
etdf2
étaient des chaînes, pas des facteurs (comme indiqué dans plusieurs des autres réponses). J'ai également enveloppé les datesas.Date
pour en faire de vraies dates.Spécifiez les critères de fusion
Créez une liste de listes. Chaque élément de la liste principale est un critère; les membres d'un critère sont
final.col.name
: le nom de la colonne que nous voulons dans le tableau finalcol.name.1
: le nom de la colonne dansdf1
col.name.2
: le nom de la colonne dansdf2
exact
: booléen; devrions-nous faire une correspondance exacte sur cette colonne?threshold
: seuil (si nous ne faisons pas de correspondance exacte)match.function
: une fonction qui retourne si les lignes correspondent ou non (pour des cas particuliers tels que l'utilisationgrepl
pour la correspondance de chaînes; notez que cette fonction doit être vectorisée)Fonction de fusion
Cette fonction prend trois arguments: les deux trames de données que nous voulons fusionner et la liste des critères de correspondance. Il procède comme suit:
Appliquez la fonction, et nous avons terminé
la source