Eu tenho dois data.frame
s com várias colunas comuns (aqui: date
, city
, ctry
, e ( other_
) number
).
Gostaria agora de mesclá-los nas colunas acima, mas tolero algum nível de diferença:
threshold.numbers <- 3
threshold.date <- 5 # in days
Se a diferença entre as date
entradas for > threshold.date
(em dias) ou > threshold.numbers
, não quero que as linhas sejam mescladas. Da mesma forma, se a entrada in city
for uma substring da entrada do outro df
na city
coluna, desejo que as linhas sejam mescladas. [Se alguém tiver uma idéia melhor para testar a semelhança de nomes de cidades reais, ficaria feliz em saber sobre isso.] (E mantenha as primeiras df
entradas de date
, city
e country
mas as duas ( other_
) number
colunas e todas as outras colunas na df
.
Considere o seguinte exemplo:
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"))
Agora, gostaria de mesclar o data.frames
e receber um df
onde as linhas são mescladas se as condições acima forem atendidas.
(A primeira coluna é apenas para sua conveniência: atrás do primeiro dígito, que indica o caso original, mostra se as linhas foram mescladas ( .
) ou se as linhas são 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 #
Tentei implementações diferentes de mesclá-los, mas não consigo obter o limite implementado.
EDIT Desculpas por formulações pouco claras - gostaria de manter todas as linhas e receber um indicador se a linha é correspondida, sem correspondência e do df1 ou sem correspondência e do df2.
o pseudo-código é:
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```
.
?Respostas:
Aqui está uma solução que usa meu pacote safejoin , envolvendo neste caso o pacote fuzzyjoin .
Podemos usar o
by
argumento para especificar uma condição complexa, usando a funçãoX()
para obter o valor dedf1
eY()
para obter o valordf2
.Se suas tabelas reais são grandes, isso pode ser lento ou impossível, como acontece com um produto cartesiano, mas aqui funciona bem.
O que queremos é uma junção completa (mantenha todas as linhas e junte o que pode ser unido), e queremos manter o primeiro valor quando eles se unirem e tomar o próximo por outro, isso significa que queremos lidar com o conflito de colunas nomeadas identicamente por coalescência, então usamos o argumento
conflict = dplyr::coalesce
resultado :
Criado em 2019-11-13 pelo pacote reprex (v0.3.0)
Infelizmente, o fuzzyjoin coage todas as colunas em uma matriz ao fazer uma junção múltipla e o safejoin envolve o fuzzyjoin; portanto, devemos converter as variáveis para o tipo apropriado dentro do argumento by, isso explica as primeiras linhas do
by
argumento.Mais sobre safejoin : https://github.com/moodymudskipper/safejoin
fonte
Primeiro, transformei os nomes das cidades em vetores de caracteres, pois (se entendi corretamente) você deseja incluir nomes de cidades contidos no df2.
Em seguida, mescle-os por país:
A biblioteca
stringr
permitirá que você veja se city.x está dentro de city.y aqui (consulte a última coluna):Então você pode obter a diferença de dias entre as datas:
e a diferença em números:
Aqui está a aparência do dataframe resultante:
Mas queremos descartar itens onde city.x não foi encontrado em city.y, em que a diferença de dia é maior que 5 ou a diferença de número é maior que 3:
O que resta são as três linhas que você tinha acima (que continha pontos na coluna 1).
Agora podemos remover as três colunas que criamos e a data e a cidade do df2:
fonte
Etapa 1: mesclar os dados com base em "cidade" e "ctry":
Etapa 2: remova as linhas se a diferença entre as entradas da data for> threshold.date (em dias):
Etapa 3: Remova as linhas se a diferença entre os números for> threshhold.number:
Os dados devem ser mesclados antes da aplicação das condições, caso as linhas não correspondam.
fonte
Uma opção usando
data.table
(explicações em linha):resultado:
fonte
Você pode testar a
city
partida comgrepl
ectry
simples com==
. Para aqueles que correspondem até aqui, é possível calcular a diferença de data convertendo-a paradate
usoas.Date
e comparando-a com adifftime
. Anumber
diferença é feita da mesma maneira.fonte
Aqui está uma abordagem flexível que permite especificar qualquer coleção de critérios de mesclagem escolhidos.
Trabalho de preparação
Eu assegurei que todas as seqüências de caracteres em
df1
edf2
fossem seqüências de caracteres, não fatores (conforme observado em várias das outras respostas). Também envolvi as datasas.Date
para torná-las reais.Especifique os critérios de mesclagem
Crie uma lista de listas. Cada elemento da lista principal é um critério; os membros de um critério são
final.col.name
: o nome da coluna que queremos na mesa finalcol.name.1
: o nome da coluna emdf1
col.name.2
: o nome da coluna emdf2
exact
: boleano; devemos fazer a correspondência exata nesta coluna?threshold
: threshold (se não estivermos fazendo a correspondência exata)match.function
: uma função que retorna se as linhas correspondem ou não (para casos especiais, como usargrepl
a correspondência de cadeias; observe que essa função deve ser vetorizada)Função para mesclar
Essa função usa três argumentos: os dois quadros de dados que queremos mesclar e a lista de critérios de correspondência. Procede da seguinte maneira:
Aplique a função e pronto
fonte