Статьи

R: удаление петель

В моем последнем сообщении в блоге я показал перевод функции правдоподобия из Think Bayes в R, и в своей первой попытке использовать эту функцию я использовал несколько вложенных циклов for.

01
02
03
04
05
06
07
08
09
10
11
likelihoods = function(names, mixes, observations) {
  scores = rep(1, length(names))
  names(scores) = names
  
  for(name in names) {
      for(observation in observations) {
        scores[name] = scores[name] *  mixes[[name]][observation]     
      }
    
  return(scores)
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
Names = c("Bowl 1", "Bowl 2")
  
bowl1Mix = c(0.75, 0.25)
names(bowl1Mix) = c("vanilla", "chocolate")
bowl2Mix = c(0.5, 0.5)
names(bowl2Mix) = c("vanilla", "chocolate")
Mixes = list("Bowl 1" = bowl1Mix, "Bowl 2" = bowl2Mix)
Mixes
  
Observations = c("vanilla", "vanilla", "vanilla", "chocolate")
l = likelihoods(Names, Mixes, Observations)
  
> l / sum(l)
  Bowl 1   Bowl 2
0.627907 0.372093

Мы передаем вектор чаш, вложенный словарь, описывающий смеси печенья в каждой чаше и сделанные нами наблюдения. Эта функция сообщает нам, что вероятность того, что файлы cookie поступят из чаши 1, составляет почти 2/3, а из чаши 2 — чуть более 1/3.

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

Давайте начнем с избавления от внутреннего цикла for. Это можно заменить вызовом функции Reduce следующим образом:

1
2
3
4
5
6
7
8
9
likelihoods2 = function(names, mixes, observations) {
  scores = rep(0, length(names))
  names(scores) = names
  
  for(name in names) {
    scores[name] = Reduce(function(acc, observation) acc *  mixes[[name]][observation], Observations, 1)
  
  return(scores)
}
1
2
3
4
5
l2 = likelihoods2(Names, Mixes, Observations)
  
> l2 / sum(l2)
  Bowl 1   Bowl 2
0.627907 0.372093

Так что это хорошо, у нас все еще есть те же вероятности, что и раньше. Теперь избавимся от внешнего цикла for. Функция Map помогает нам здесь:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
likelihoods3 = function(names, mixes, observations) {
  scores = rep(0, length(names))
  names(scores) = names
  
  scores = Map(function(name)
    Reduce(function(acc, observation) acc *  mixes[[name]][observation], Observations, 1),
    names)
  
  return(scores)
}
  
l3 = likelihoods3(Names, Mixes, Observations)
> l3
$`Bowl 1`
  vanilla
0.1054688
  
$`Bowl 2`
vanilla
 0.0625

В итоге мы получаем список вместо вектора, который нам нужно исправить с помощью функции unlist :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
likelihoods3 = function(names, mixes, observations) {
  scores = rep(0, length(names))
  names(scores) = names
  
  scores = Map(function(name)
    Reduce(function(acc, observation) acc *  mixes[[name]][observation], Observations, 1),
    names)
  
  return(unlist(scores))
}
  
l3 = likelihoods3(Names, Mixes, Observations)
  
> l3 / sum(l3)
Bowl 1.vanilla Bowl 2.vanilla
      0.627907       0.372093

Теперь у нас просто есть эта раздражающая «ваниль» в названии. Это достаточно легко исправить:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
likelihoods3 = function(names, mixes, observations) {
  scores = rep(0, length(names))
  names(scores) = names
  
  scores = Map(function(name)
    Reduce(function(acc, observation) acc *  mixes[[name]][observation], Observations, 1),
    names)
  
  result = unlist(scores)
  names(result) = names
  
  return(result)
}
  
l3 = likelihoods3(Names, Mixes, Observations)
  
> l3 / sum(l3)
  Bowl 1   Bowl 2
0.627907 0.372093

Немного более чистая альтернатива использует функцию sapply :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
likelihoods3 = function(names, mixes, observations) {
  scores = rep(0, length(names))
  names(scores) = names
  
  scores = sapply(names, function(name)
    Reduce(function(acc, observation) acc *  mixes[[name]][observation], Observations, 1))
  names(scores) = names
  
  return(scores)
}
  
l3 = likelihoods3(Names, Mixes, Observations)
  
> l3 / sum(l3)
  Bowl 1   Bowl 2
0.627907 0.372093

Это лучшее, что у меня есть на данный момент, но мне интересно, могли бы мы как-нибудь написать версию с использованием матричных операций — но это в следующий раз!

Ссылка: Р: Удаление петель от нашего партнера по JCG Марка Нидхэма в блоге Марка Нидхэма .