Мне нужно применить функцию оптимизации к миллионам строк данных, которая возвращает число на основе двух входных переменных. Применение функции с помощью mapply занимает вечность. Поскольку в данных имеется ограниченный диапазон значений с большим количеством повторов, я решил, что будет быстрее создать таблицу поиска для всего диапазона возможных входных комбинаций, а затем просто использовать соединение из dplyr для заполнения данных. Мои попытки не увенчались успехом из-за какого-то странного поведения в моей таблице поиска (на самом деле это фрейм данных). Ниже приведен повторяемый пример. Я пытаюсь понять, почему я не могу ссылаться на некоторые значения в таблице поиска, но другие работают нормально.
## build a lookup table with three columns
lookup <- transform(expand.grid(0:180, seq(0, 35, by=0.1)), v=0)
colnames(lookup) <- c("d", "s", "v")
lookup$v <- 1:nrow(lookup)
## generate some fake data with random values
set.seed(42) ## for sake of reproducibility
data <- data.frame(d=sample(lookup$d, 1000, replace=TRUE),
s=round(runif (n=1000, min=0, max=35), 1))
## left join gives a lot of NAs
joined <- dplyr::left_join(x=data, y=lookup, by=c('d', 's'))
joined[1:20, ]
# d s v
# 1 53 5.3 NA
# 2 124 20.2 NA
# 3 172 21.5 39088
# 4 137 5.3 NA
# 5 52 18.8 34081
# 6 67 20.0 36268
# 7 87 21.1 38279
# 8 64 29.4 NA
# 9 97 18.5 33583
# 10 159 8.6 15726
# 11 40 10.4 18865
# 12 138 29.6 53715
# 13 33 15.7 NA
# 14 171 24.4 NA
# 15 126 33.2 60219
# 16 120 20.1 36502
# 17 15 17.3 31329
# 18 104 17.4 NA
# 19 34 3.3 NA
# 20 92 33.6 60909
## unable to reference certain values...
lookup$v[which(lookup$d == 177 & lookup$s == 8.2)]
#integer(0)
## ...even though they exist
lookup[15020, ]
# d s v
#15020 177 8.2 15020
## other values are OK
lookup$v[which(lookup$d == 177 & lookup$s == 8.3)]
#[1] 15201





Вы имеете дело с точностью с плавающей запятой и, следовательно, не получаете того, чего ожидаете. Начнем с вашего второго вопроса.
Вместо
> lookup[15020, ]
d s v
15020 177 8.2 15020
>
> lookup$s[15020] == 8.2
[1] FALSE
пытаться
> tol <- .Machine$double.eps^0.5
> lookup$s[15020] - 8.2 < tol
[1] TRUE
> lookup$v[which(lookup$d == 177 & abs(lookup$s - 8.2) < tol)]
[1] 15020
Подробнее об этом читайте в этом ответе.
Это также может относиться к вашему первому вопросу о том, что у merge есть проблемы.
Вместо этого, используя merge (или dplyr::*join), мы могли бы расширить логику tolerance.
> f <- \(x, y, tol=.Machine$double.eps^0.5) {
+ which(
+ colSums(
+ abs(t(lookup[, c('d', 's')]) - c(x, y)) < rep_len(tol, 2),
+ ) == 2L
+ )
+ }
> res1 <- data |> transform(v=lookup$v[mapply(f, data$d, data$s)])
> res1 |> head(20)
d s v
1 53 5.3 9647
2 124 20.2 36687
3 172 21.5 39088
4 137 5.3 9731
5 52 18.8 34081
6 67 20.0 36268
7 87 21.1 38279
8 64 29.4 53279
9 97 18.5 33583
10 159 8.6 15726
11 40 10.4 18865
12 138 29.6 53715
13 33 15.7 28451
14 171 24.4 44336
15 126 33.2 60219
16 120 20.1 36502
17 15 17.3 31329
18 104 17.4 31599
19 34 3.3 6008
20 92 33.6 60909
Поскольку это медленно, мы могли бы использовать Rcpp.
> Rcpp::cppFunction('
+ IntegerVector match_tol(NumericMatrix lookup, NumericMatrix data) {
+ int n = data.nrow();
+ IntegerVector res(n);
+ double tol = sqrt(std::numeric_limits<double>::epsilon());
+ for (int i = 0; i < n; ++i) {
+ double d = data(i, 0); // first data column
+ double s = data(i, 1); // second
+ for (int j = 0; j < lookup.nrow(); ++j) {
+ if (lookup(j, 0) == d && std::abs(lookup(j, 1) - s) < tol) {
+ res[i] = lookup(j, 2);
+ break;
+ }
+ }
+ }
+ return res;
+ }
+ ')
> res2 <- data |> transform(v=match_tol(as.matrix(lookup), as.matrix(data)))
> res2 |> head(20)
d s v
1 53 5.3 9647
2 124 20.2 36687
3 172 21.5 39088
4 137 5.3 9731
5 52 18.8 34081
6 67 20.0 36268
7 87 21.1 38279
8 64 29.4 53279
9 97 18.5 33583
10 159 8.6 15726
11 40 10.4 18865
12 138 29.6 53715
13 33 15.7 28451
14 171 24.4 44336
15 126 33.2 60219
16 120 20.1 36502
17 15 17.3 31329
18 104 17.4 31599
19 34 3.3 6008
20 92 33.6 60909
Другие решения, в том числе base::merge(., sort=FALSE) в числовом или символьном формате, могут по-прежнему иметь проблемы*:
> joined1 <- merge(data, lookup, all.x=TRUE, sort=FALSE)
> joined1 |> head(20)
d s v
1 53 5.3 9647
2 124 20.2 36687
3 172 21.5 39088
4 137 5.3 9731
5 52 18.8 34081
6 67 20.0 36268
7 87 21.1 38279
8 64 29.4 53279
9 97 18.5 33583
10 159 8.6 15726
11 40 10.4 18865
12 138 29.6 53715
13 33 15.7 28451
14 171 24.4 44336
15 126 33.2 60219
16 120 20.1 36502
17 15 17.3 31329
18 104 17.4 31599 ## *
19 104 17.4 31599 ## *
20 34 3.3 6008
> joined2 <- merge(
+ sapply(data, as.character),
+ sapply(lookup, as.character),
+ all.x=TRUE, sort=FALSE) |>
+ sapply(as.numeric) |>
+ as.data.frame() ## optional
> joined2 |> head(20)
d s v
1 53 5.3 9647
2 124 20.2 36687
3 172 21.5 39088
4 137 5.3 9731
5 52 18.8 34081
6 67 20.0 36268
7 87 21.1 38279
8 64 29.4 53279
9 97 18.5 33583
10 159 8.6 15726
11 40 10.4 18865
12 138 29.6 53715
13 33 15.7 28451
14 171 24.4 44336
15 126 33.2 60219
16 120 20.1 36502
17 15 17.3 31329
18 104 17.4 31599 ## *
19 104 17.4 31599 ## *
20 34 3.3 6008
Сравнивать:
> data |> head(20)
d s
1 53 5.3
2 124 20.2
3 172 21.5
4 137 5.3
5 52 18.8
6 67 20.0
7 87 21.1
8 64 29.4
9 97 18.5
10 159 8.6
11 40 10.4
12 138 29.6
13 33 15.7
14 171 24.4
15 126 33.2
16 120 20.1
17 15 17.3
18 104 17.4
19 34 3.3
20 92 33.6
Данные:
> lookup <- expand.grid(d=0:180, s=seq(0, 35, by=0.1)) |>
+ transform(v=seq_len(35/.1 + 1))
> set.seed(42)
> data <- data.frame(d=sample(lookup$d, 1000, replace=TRUE),
+ s=round(runif (n=1000, min=0, max=35), 1))
Спасибо @jay.sf. К сожалению, у слияния та же проблема: ~~~ merged <- base::merge(x=data, y=lookup, all.x=T, sort =F) merged[1:20,] # d s v # 1 179 4.6 NA # 2 45 12,9 23395 # 3 162 28,1 51024 # 4 25 11,2 NA # 5 65 14,4 26130 # 6 64 32,5 58890 # 7 95 9,3 16929 # 8 70 31,6 57267 # 9 92 2 6,4 НП № 10 129 12,0 21850 № 11 7 11,0 19918 # 12 59 34,6 62686 # 13 3 8,1 14665 # 14 146 3,0 5577 # 15 16 26,8 48525 # 16 15 27,5 49791 # 17 136 6,2 11359 # 18 70 6,5 11836 # 19 103 0,4 828 # 20 99 22,6 41006 ~~~
@birdy448 Вы использовали all.x=TRUE,, поэтому включены data, которых нет в lookup.
Блин, это форматирование комментариев просто чудовище. Ничего не работает...
Я думал, что это может быть как-то связано с точностью с плавающей запятой, но я не знал о настройках допуска. Может стоит попробовать. Также можно попробовать заменить цифры на символы.
@birdy448 Вы можете добавить информацию в виде редактирования к своему вопросу.
@birdy448 Использование персонажа — НЕ очень хорошая идея. Пожалуйста, смотрите редактирование.
Итак, подход слияния, похоже, теперь работает. По крайней мере, я не получаю НС. Повторяющиеся значения, которые вы получаете (@jay.sf), вполне ожидаемы, хотя при 'sort = FALSE' странно (для меня), что они постоянно оказываются рядом друг с другом. Я добавил последовательность 1 к nrow() к данным, чтобы отслеживать исходный порядок. Затем я применяю слияние и сортировку фрейма данных, чтобы восстановить исходный порядок строк.
@birdy448 Отлично, какое из трех решений вы в итоге использовали?
@birdy448 Birdy448 См. решение Rcpp в редактировании.
Может быть, вы могли бы умножить на десять, чтобы работать только с целыми числами?