Добавить столбец на основе сопоставления нескольких условий и диапазонов дат из нескольких наборов данных - PullRequest
0 голосов
/ 06 мая 2018

Я изо всех сил пытался найти лучший способ решения этой проблемы.

Чтобы обобщить эту проблему и помочь другим, которым может понадобиться выполнить аналогичные задачи, я пытаюсь найти лучший способ добавления столбцов в один набор данных из третьего, основанного на сопоставлении в промежуточном наборе данных, И принадлежность в диапазоне дат третьего набора данных. Конечный результат будет возвращать совпадающие значения из третьего набора данных в первый.

Вот главы примеров фреймов данных, чтобы добавить немного ясности:

> head(SalesData, 10)
   sale_id sale_amt int_rate  sale_date sale_status
1        1     7000    10.71 2008-05-01  Fully Paid
2        2    10800    13.57 2009-11-01  Fully Paid
3        3     7500    10.08 2008-04-01  Fully Paid
4        4     3000    14.26 2009-09-01  Fully Paid
5        5     5600    14.96 2010-02-01 Charged Off
6        6     2800    11.49 2010-08-01  Fully Paid
7        7    10000     8.59 2009-10-01  Fully Paid
8        8    18000    10.39 2008-03-01  Fully Paid
9        9     5000    15.13 2008-04-01  Fully Paid
10      10     9600    12.29 2008-03-01  Fully Paid

> head(EmployeeSales, 10)
   sale_id empl_name empl_num
1        1    Dakota        4
2        2    Dakota        4
3        3      Kami        9
4        4      Adel        1
5        5      Adel        1
6        6     Farah        6
7        7      Kami        9
8        8      Kami        9
9        9       Ida        7
10      10      Kami        9

> head(EmployeeMap, 10)
   empl_num empl_name skill_lvl team start_date   end_date
1         1      Adel       Beg  Red 2007-06-01 2008-05-31
2         1      Adel       Int  Red 2008-06-01 2010-10-31
3         1      Adel       Adv  Red 2010-11-01 2999-12-12
4         2    Bailey       Beg Blue 2010-08-01 2011-04-30
5         2    Bailey       Beg  Red 2011-05-01 2999-12-12
6         3     Casey       Beg Blue 2010-08-01 2010-12-31
7         3     Casey       Int Blue 2011-01-01 2999-12-12
8         4    Dakota       Beg  Red 2007-06-01 2009-08-30
9         4    Dakota       Int  Red 2009-09-01 2010-08-30
10        4    Dakota       Adv  Red 2010-09-01 2011-08-30

Требуемый вывод будет добавлять empl_num, sales_team и skill_level из EmployeeMap в SalesData для каждого sale_id.

Пытаясь осмыслить шаги, я думаю об этом, но, возможно, есть лучший способ: Возьмите sale_id из SalesData, сопоставьте его с sale_id в Employee Sales и получите empl_num. Возьмите empl_num и сопоставьте его с empl_num на карте сотрудников. Теперь нам нужно взять sale_date из SalesData и найти, в какой диапазон «start_date, end_date» он попадает. Затем мы берем соответствующий уровень команды и навыка и добавляем его в SalesData.

См. Таблицу ниже:

 > head(df2,10)
    sale_id sale_amt int_rate  sale_date sale_status empl_num  team skill_lvl
 1        1     7000    10.71 2008-05-01  Fully Paid        4   Red       Beg
 2        2    10800    13.57 2009-11-01  Fully Paid        4   Red       Int
 3        3     7500    10.08 2008-04-01  Fully Paid        9  Blue       Beg
 4        4     3000    14.26 2009-09-01  Fully Paid        1   Red       Int
 5        5     5600    14.96 2010-02-01 Charged Off        1   Red       Int
 6        6     2800    11.49 2010-08-01  Fully Paid        6   Red       Beg
 7        7    10000     8.59 2009-10-01  Fully Paid        9  Blue       Int
 8        8    18000    10.39 2008-03-01  Fully Paid        9  Blue       Beg
 9        9     5000    15.13 2008-04-01  Fully Paid        7  Blue       Beg
 10      10     9600    12.29 2008-03-01  Fully Paid        9  Blue       Int

Что усложняет это для меня, так это то, что в EmployeeMap start_date и end_date сообщают нам дату, когда каждый сотрудник начинал и заканчивал принадлежать к определенному уровню квалификации и команде. Но каждый сотрудник изменил уровень квалификации и / или команду, поэтому у каждого сотрудника есть несколько рядов.

Например, в EmployeeMap для empl_id 1 мы можем видеть 3 строки, сообщающие нам свои start_date и end_date, когда они имели Beg, Int, Adv и skill_level, все в Red Team. Но некоторые, например empl_id 2, меняют команду, оставаясь на том же уровне квалификации. А другие меняют уровень квалификации и команды.

Буду признателен за любые ваши идеи о том, как лучше всего решить эту проблему.

Ответы [ 3 ]

0 голосов
/ 06 мая 2018

Попробуйте запустить merge дважды, а затем subset по датам. Ниже вложенные вызовы объединяются в одну длинную строку, но могут быть разбиты на отдельные строки. Кроме того, выходные данные меньше желаемого результата, так как ваши опубликованные данные являются образцами строк.

# MERGE TWICE AND SUBSET BY DATE
finaldf <- subset(merge(merge(SalesData, EmployeeSales, by="sale_id"), 
                        EmployeeMap, "empl_num", suffixes=c('', '_')),
                  sale_date >= start_date & sale_date <= end_date)

# SELECT NEEDED COLUMNS
finaldf <- finaldf[c("sale_id", "sale_amt", "int_rate", "sale_date", 
                     "sale_status", "empl_num", "team", "skill_lvl")]

# RE-ORDER BY SALE_ID AND RESET ROW NAMES
finaldf <- with(finaldf, finaldf[order(sale_id),])
row.names(finaldf) <- NULL

finaldf
#   sale_id sale_amt int_rate  sale_date sale_status empl_num team skill_lvl
# 1       1     7000    10.71 2008-05-01  Fully Paid        4  Red       Beg
# 2       2    10800    13.57 2009-11-01  Fully Paid        4  Red       Int
# 3       4     3000    14.26 2009-09-01  Fully Paid        1  Red       Int
# 4       5     5600    14.96 2010-02-01 Charged Off        1  Red       Int
0 голосов
/ 06 мая 2018

В терминах SQL это трехстороннее соединение. Это можно сделать в одном SQL-запросе, например:

library(sqldf)

sqldf("
  select s.*, es.empl_num, em.team, em.skill_lvl
  from SalesData s
  left join EmployeeSales es 
    using (sale_id)
  left join EmployeeMap em
    on es.empl_num = em.empl_num and s.sale_date between em.start_date and em.end_date
")

Используя данные в примечании в конце (на основе данных, показанных в вопросе), мы получаем следующее. В данных EmployeeMap, показанных в вопросе, присутствуют только первые 4 числа сотрудников, а левые объединения гарантируют, что мы получим значения NA для команды и уровень квалификации других вместо того, чтобы эти строки SalesData были отброшены из-за несоответствия.

   sale_id sale_amt int_rate  sale_date sale_status empl_num team skill_lvl
1        1     7000    10.71 2008-05-01  Fully Paid        4  Red       Beg
2        2    10800    13.57 2009-11-01  Fully Paid        4  Red       Int
3        3     7500    10.08 2008-04-01  Fully Paid        9 <NA>      <NA>
4        4     3000    14.26 2009-09-01  Fully Paid        1  Red       Int
5        5     5600    14.96 2010-02-01 Charged Off        1  Red       Int
6        6     2800    11.49 2010-08-01  Fully Paid        6 <NA>      <NA>
7        7    10000     8.59 2009-10-01  Fully Paid        9 <NA>      <NA>
8        8    18000    10.39 2008-03-01  Fully Paid        9 <NA>      <NA>
9        9     5000    15.13 2008-04-01  Fully Paid        7 <NA>      <NA>
10      10     9600    12.29 2008-03-01  Fully Paid        9 <NA>      <NA>

Примечание

Исходные данные в воспроизводимом виде:

SalesData <- structure(list(sale_id = 1:10, sale_amt = c(7000L, 10800L, 7500L, 
3000L, 5600L, 2800L, 10000L, 18000L, 5000L, 9600L), int_rate = c(10.71, 
13.57, 10.08, 14.26, 14.96, 11.49, 8.59, 10.39, 15.13, 12.29), 
    sale_date = structure(c(3L, 6L, 2L, 4L, 7L, 8L, 5L, 1L, 2L, 
    1L), .Label = c("2008-03-01", "2008-04-01", "2008-05-01", 
    "2009-09-01", "2009-10-01", "2009-11-01", "2010-02-01", "2010-08-01"
    ), class = "factor"), sale_status = structure(c(2L, 2L, 2L, 
    2L, 1L, 2L, 2L, 2L, 2L, 2L), .Label = c("Charged Off", "Fully Paid"
    ), class = "factor")), class = "data.frame", row.names = c("1", 
"2", "3", "4", "5", "6", "7", "8", "9", "10"))

EmployeeSales <-
structure(list(sale_id = 1:10, empl_name = structure(c(2L, 2L, 
5L, 1L, 1L, 3L, 5L, 5L, 4L, 5L), .Label = c("Adel", "Dakota", 
"Farah", "Ida", "Kami"), class = "factor"), empl_num = c(4L, 
4L, 9L, 1L, 1L, 6L, 9L, 9L, 7L, 9L)), class = "data.frame", row.names = c("1", 
"2", "3", "4", "5", "6", "7", "8", "9", "10"))

EmployeeMap <- structure(list(empl_num = c(1L, 1L, 1L, 2L, 2L, 3L, 3L, 4L, 4L, 
4L), empl_name = structure(c(1L, 1L, 1L, 2L, 2L, 3L, 3L, 4L, 
4L, 4L), .Label = c("Adel", "Bailey", "Casey", "Dakota"), class = "factor"), 
    skill_lvl = structure(c(2L, 3L, 1L, 2L, 2L, 2L, 3L, 2L, 3L, 
    1L), .Label = c("Adv", "Beg", "Int"), class = "factor"), 
    team = structure(c(2L, 2L, 2L, 1L, 2L, 1L, 1L, 2L, 2L, 2L
    ), .Label = c("Blue", "Red"), class = "factor"), start_date = structure(c(1L, 
    2L, 6L, 4L, 8L, 4L, 7L, 1L, 3L, 5L), .Label = c("2007-06-01", 
    "2008-06-01", "2009-09-01", "2010-08-01", "2010-09-01", "2010-11-01", 
    "2011-01-01", "2011-05-01"), class = "factor"), end_date = structure(c(1L, 
    4L, 8L, 6L, 8L, 5L, 8L, 2L, 3L, 7L), .Label = c("2008-05-31", 
    "2009-08-30", "2010-08-30", "2010-10-31", "2010-12-31", "2011-04-30", 
    "2011-08-30", "2999-12-12"), class = "factor")), class = "data.frame", 
    row.names = c("1", "2", "3", "4", "5", "6", "7", "8", "9", "10"))
0 голосов
/ 06 мая 2018

Пожалуй, самый простой способ сделать это с помощью двух SQL-подобных объединений (я предлагаю вам прочитать , как это , если вы не знакомы с объединениями / реляционной алгеброй).

Многие объединения могут выполняться с помощью функции merge в base R, и многие другие популярные пакеты (dplyr, data.table, sqldf, если назвать несколько) предлагают альтернативный синтаксис или расширенную функциональность в операциях соединения .

Первое из двух ваших соединений (между SalesData и EmployeeSales) может быть легко выполнено с помощью merge:

merge(SalesData, EmployeeSales, by = "sale_id")

#    sale_id sale_amt int_rate  sale_date sale_status empl_name empl_num
# 1        1     7000    10.71 2008-05-01  Fully Paid    Dakota        4
# 2        2    10800    13.57 2009-11-01  Fully Paid    Dakota        4
# 3        3     7500    10.08 2008-04-01  Fully Paid      Kami        9
# ...

Второе соединение, однако, является более сложным, поскольку оно не является типичным equi-join . Вместо этого логике соединения необходимо найти строки в EmployeeMap, где start_date меньше sale_date, а end date больше его (в дополнение к условию равенства в empl_num).

К счастью, вышеупомянутый пакет data.table предоставляет возможность применить указанную логику.

library(data.table)

# convert all three dataframes to data.table objects
setDT(SalesData) ; setDT(EmployeeSales) ; setDT(EmployeeMap)

EmployeeMap[SalesData[EmployeeSales[, c("sale_id","empl_num")],
                      on = "sale_id"], 
            on = .(empl_num, start_date <= sale_date, end_date >= sale_date)]

#    empl_num empl_name skill_lvl team start_date   end_date sale_id sale_amt int_rate sale_status
# 1:        4    Dakota       Beg  Red 2008-05-01 2008-05-01       1     7000    10.71  Fully Paid
# 2:        4    Dakota       Int  Red 2009-11-01 2009-11-01       2    10800    13.57  Fully Paid
# 3:        9        NA        NA   NA 2008-04-01 2008-04-01       3     7500    10.08  Fully Paid
# ...

Обратите внимание, что все три столбца даты должны быть типами Date, а не строками, чтобы сравнение работало. Также обратите внимание, что значения NA в выводе выше являются результатом снимка EmployeeMap, предоставленного в вопросе, который отображает только empl_num 1-4.

Я бы также предложил прочитать ответы на этот вопрос , чтобы узнать больше о том, как присоединиться к диапазону дат.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...