Общий доход от маршрута в R - PullRequest
5 голосов
/ 20 марта 2019

У меня есть набор данных с источником ("от"), пунктом назначения ("до") и ценой, как показано ниже:

from    to  price
A       B   28109
A       D   2356
A       E   4216
B       A   445789
B       D   123
D       A   45674
D       B   1979

Я хочу суммировать цену с учетом обратного маршрута. например, A - B состоит из следующих данных:

from    to  price
  A     B   28109
  B     A   445789

Затем возьмите сумму цены (28109 + 445789). Вывод будет таким:

route   total_price
A - B   473898
A - D   48030
A - E   4216
B - D   2102

Я думал запустить цикл for, но мой размер данных очень большой (800 тыс. Строк). Любая помощь будет высоко оценена. Заранее большое спасибо.

Ответы [ 4 ]

6 голосов
/ 20 марта 2019

Вы можете сделать это, отсортировав пары from-to, затем сгруппировав эту отсортированную пару и суммировав.

Редактировать: См. Ответ @JasonAizkalns для Tidyverse эквивалента

library(data.table)
setDT(df)

df[, .(total_price = sum(price))
   , by = .(route = paste(pmin(from, to), '-', pmax(from, to)))]

#    route total_price
# 1: A - B      473898
# 2: A - D       48030
# 3: A - E        4216
# 4: B - D        2102

@ Фрэнк отмечает, что этот результат скрывает тот факт, что маршрут "A - E" не является полным, в том смысле, что нет строки исходных данных с from == 'E' и to == 'A'. Он предложил хороший способ сбора этой информации (и многое другое), и я добавил несколько других ниже.

df[, .(total_price = sum(price), complete = .N > 1)
   , by = .(route = paste(pmin(from, to), '-', pmax(from, to)))]

#    route total_price complete
# 1: A - B      473898     TRUE
# 2: A - D       48030     TRUE
# 3: A - E        4216    FALSE
# 4: B - D        2102     TRUE

df[, .(total_price = sum(price), paths_counted = .(paste(from, '-', to)))
   , by = .(route = paste(pmin(from, to), '-', pmax(from, to)))]

#    route total_price paths_counted
# 1: A - B      473898   A - B,B - A
# 2: A - D       48030   A - D,D - A
# 3: A - E        4216         A - E
# 4: B - D        2102   B - D,D - B

Используемые данные

df <- fread('
from    to  price
A       B   28109
A       D   2356
A       E   4216
B       A   445789
B       D   123
D       A   45674
D       B   1979')
3 голосов
/ 20 марта 2019

Вы можете сделать самостоятельное объединение, и тогда все будет довольно просто:

library(tidyverse)

df <- readr::read_table("
from    to  price
A       B   28109
A       D   2356
A       E   4216
B       A   445789
B       D   123
D       A   45674
D       B   1979
")

df %>%
  inner_join(df, by = c("from" = "to")) %>%
  filter(to == from.y) %>%
  mutate(
    route = paste(from, "-", to),
    total_price = price.x + price.y
  )
#> # A tibble: 6 x 7
#>   from  to    price.x from.y price.y route total_price
#>   <chr> <chr>   <dbl> <chr>    <dbl> <chr>       <dbl>
#> 1 A     B       28109 B       445789 A - B      473898
#> 2 A     D        2356 D        45674 A - D       48030
#> 3 B     A      445789 A        28109 B - A      473898
#> 4 B     D         123 D         1979 B - D        2102
#> 5 D     A       45674 A         2356 D - A       48030
#> 6 D     B        1979 B          123 D - B        2102

Создано в 2019-03-20 пакетом представлением (v0.2.1)

Поскольку мне больше нравится ответ @ IceCreamToucan, вот эквивалент tidyverse:

df %>%
  group_by(route = paste(pmin(from, to), "-", pmax(from, to))) %>%
  summarise(total_price = sum(price))
2 голосов
/ 20 марта 2019

Также одна tidyverse возможность:

df %>%
 nest(from, to) %>%
 mutate(route = unlist(map(data, function(x) paste(sort(x), collapse = "_")))) %>%
 group_by(route) %>%
 summarise(total_price = sum(price))

  route total_price
  <chr>       <int>
1 A_B        473898
2 A_D         48030
3 A_E          4216
4 B_D          2102

В этом случае сначала создается список, состоящий из значений переменных from и to. Во-вторых, он сортирует элементы в списке и объединяет их вместе, отделяя _. Наконец, он группируется по объединенным элементам и получает сумму.

Или с преобразованием ширины в длину:

df %>%
 rowid_to_column() %>%
 gather(var, val, -c(rowid, price)) %>%
 arrange(rowid, val) %>%
 group_by(rowid) %>%
 summarise(route = paste(val, collapse = "_"),
           price = first(price)) %>%
 group_by(route) %>%
 summarise(total_price = sum(price))

Для этого он, во-первых, выполняет преобразование данных из широкой в ​​длинную, исключая идентификатор строки и «цену». Во-вторых, он упорядочивает данные по идентификатору строки и значениям, содержащимся в «от» и «до». В-третьих, он группируется по идентификатору строки, объединяет элементы вместе, разделенные _. Наконец, он группируется по этой переменной и получает сумму.

1 голос
/ 20 марта 2019

Я бы сделал ...

library(data.table)
setDT(df)

pts = df[, unique(c(from, to))]
rDT = CJ(P1 = pts, P2 = pts)[P1 < P2]

rDT[df, on=.(P1 = from, P2 = to), r12 := i.price]
rDT[df, on=.(P2 = from, P1 = to), r21 := i.price]
rDT[, r := r12 + r21]

   P1 P2   r12    r21      r
1:  A  B 28109 445789 473898
2:  A  D  2356  45674  48030
3:  A  E  4216     NA     NA
4:  B  D   123   1979   2102
5:  B  E    NA     NA     NA
6:  D  E    NA     NA     NA

Это даст понять, где данные неполные. ** Вы можете отфильтровать до rDT[!is.na(r)] только для полных записей.

** Об этом также говорится в @ JasonAizkalns's и ответы @ IceCreamToucan, но контрастирует с запрошенным выводом OP.

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