Mam dwa data.frame
sz wielu wspólnych kolumnach (tu: date
, city
, ctry
, oraz ( other_
) number
).
Chciałbym teraz scalić je w powyższych kolumnach, ale toleruję pewien poziom różnicy:
threshold.numbers <- 3
threshold.date <- 5 # in days
Jeśli różnica między date
wpisami wynosi > threshold.date
(w dniach) lub > threshold.numbers
, nie chcę, aby linie zostały scalone. Podobnie, jeśli wpis w city
jest podciągiem df
wpisu drugiej osoby w city
kolumnie, chcę, aby linie zostały scalone. [Jeśli ktoś ma lepszy pomysł do testowania rzeczywistej nazwy miast podobieństwa, byłbym szczęśliwy, aby usłyszeć o tym.] (I utrzymać pierwsze df
„s wpisy date
, city
a country
jednak obie ( other_
) number
kolumny i wszystkie inne kolumny w df
.
Rozważ następujący przykład:
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"))
Teraz chciałbym scalić data.frames
i otrzymać miejsce, w df
którym linie zostaną scalone, jeśli zostaną spełnione powyższe warunki.
(Pierwsza kolumna jest tylko dla twojej wygody: za pierwszą cyfrą, która wskazuje oryginalny przypadek, pokazuje, czy linie zostały scalone ( .
), czy też linie są od df1
( 1
) lub 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 #
Próbowałem różnych implementacji ich łączenia, ale nie udało mi się zrealizować progu.
EDIT Przeprosiny za niejasne sformułowanie - Chciałbym zachować wszystkie wiersze i otrzymać informację, czy wiersz jest dopasowany, niedopasowany i od df1, czy niedopasowany i od df2.
pseudo-kod to:
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```
.
?Odpowiedzi:
Oto rozwiązanie, które wykorzystuje mój pakiet safejoin , owijając w tym przypadku pakiet fuzzyjoin .
Możemy użyć
by
argumentu do określenia złożonego warunku, używając funkcjiX()
do uzyskania wartościdf1
iY()
do uzyskania wartościdf2
.Jeśli twoje prawdziwe stoły są duże, może to być powolne lub niemożliwe, ponieważ robi to produkt kartezjański, ale tutaj działa dobrze.
To, czego chcemy, to pełne sprzężenie (zachowaj wszystkie wiersze i połącz to, co można połączyć), a my chcemy zachować pierwszą wartość, kiedy się łączą, i weźmy drugą, to oznacza, że chcemy poradzić sobie z konfliktem kolumny nazwane identycznie przez łączenie, więc używamy argumentu
conflict = dplyr::coalesce
wynik :
Utworzono 13.11.2019 przez pakiet reprezentx (v0.3.0)
Niestety fuzzyjoin wymusza wszystkie kolumny w macierzy podczas łączenia wielu, a safejoin otacza fuzzyjoin, więc musimy przekonwertować zmienne na odpowiedni typ wewnątrz argumentu, wyjaśnia to pierwsze wiersze
by
argumentu.Więcej informacji o safejoin : https://github.com/moodymudskipper/safejoin
źródło
Najpierw zamieniłem nazwy miast na wektory znaków, ponieważ (jeśli dobrze zrozumiałem) chcesz dołączyć nazwy miast, które są zawarte w df2.
Następnie scal je według kraju:
Biblioteka
stringr
pozwoli ci zobaczyć, czy city.x znajduje się w obrębie city.y tutaj (patrz ostatnia kolumna):Następnie możesz uzyskać różnicę w dniach między datami:
i różnica w liczbach:
Oto jak wygląda wynikowa ramka danych:
Ale chcemy upuścić rzeczy tam, gdzie nie znaleziono city.x w mieście. Y, gdzie różnica dni jest większa niż 5 lub różnica liczb jest większa niż 3:
Pozostały trzy wiersze, które miałeś powyżej (które zawierały kropki w kolumnie 1).
Teraz możemy upuścić trzy kolumny, które utworzyliśmy oraz datę i miasto z df2:
źródło
Krok 1: Scal dane na podstawie „miasta” i „ctry”:
Krok 2: Usuń wiersze, jeśli różnica między wpisami daty wynosi> próg.data (w dniach):
Krok 3: Usuń wiersze, jeśli różnica między liczbami wynosi> threshhold.number:
Dane powinny zostać scalone przed zastosowaniem warunków, w przypadku gdy wiersze nie są zgodne.
źródło
Opcja wykorzystująca
data.table
(objaśnienia w linii):wynik:
źródło
Możesz przetestować
city
dopasowaniegrepl
i zactry
pomocą==
. Dla tych, którzy pasują do tego momentu, możesz obliczyć różnicę dat, konwertując nadate
użycieas.Date
i porównując ją zdifftime
.number
Różnica odbywa się w ten sam sposób.źródło
Oto elastyczne podejście, które pozwala określić dowolną kolekcję wybranych kryteriów scalania.
Przygotuj pracę
Upewniłem się, że wszystkie ciągi
df1
idf2
były ciągami, a nie czynnikami (jak zauważono w kilku innych odpowiedziach). Zapakowałem też daty,as.Date
aby były prawdziwymi datami.Określ kryteria scalania
Utwórz listę list. Każdy element głównej listy jest jednym kryterium; członkami kryterium są
final.col.name
: nazwa kolumny, którą chcemy w końcowej tabelicol.name.1
: nazwa kolumny wdf1
col.name.2
: nazwa kolumny wdf2
exact
: boolean; czy powinniśmy wykonać dokładne dopasowanie w tej kolumnie?threshold
: próg (jeśli nie dopasowujemy dokładnie)match.function
: funkcja, która zwraca, czy wiersze są zgodne (w szczególnych przypadkach, takich jak użyciegrepl
do dopasowywania ciągów; należy pamiętać, że ta funkcja musi być wektoryzowana)Funkcja łączenia
Ta funkcja przyjmuje trzy argumenty: dwie ramki danych, które chcemy scalić, oraz listę kryteriów dopasowania. Postępuje następująco:
Zastosuj funkcję i gotowe
źródło