#r #permutation
#r #перестановка
Вопрос:
У меня есть набор данных с исходным кодом («откуда»), пунктом назначения («кому») и ценой, как показано ниже:
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 тыс. строк). Любая помощь будет высоко оценена. Заранее большое спасибо.
Ответ №1:
Вы можете сделать это путем сортировки пар «от-до», затем группировки по этой отсортированной паре и суммирования.
Редактировать: Смотрите ответ @ 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
@Frank отмечает, что этот результат скрывает тот факт, что маршрут "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')
Ответ №2:
Вы могли бы выполнить самосоединение, и тогда все было бы довольно просто:
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 пакетом reprex (версия 0.2.1)
Поскольку мне больше нравится ответ @ IceCreamToucan, вот tidyverse
эквивалент:
df %>%
group_by(route = paste(pmin(from, to), "-", pmax(from, to))) %>%
summarise(total_price = sum(price))
Ответ №3:
Также одна 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))
Для этого сначала выполняется преобразование широких данных в длинные, исключая идентификатор строки и «цену». Во-вторых, он упорядочивает данные в соответствии с идентификатором строки и значениями, содержащимися в «from» и «to». В-третьих, он группирует по идентификатору строки, объединяет элементы вместе, разделенные _
. Наконец, он группируется по этой переменной и получает сумму.
Ответ №4:
Я бы сделал…
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 и @IceCreamToucan, но отличается от запрошенного вывода OP.