julia 中的向量化 "in" 函数?

Vectorized "in" function in julia?

我经常想遍历一个长数组或数据框的一列,并且对于每个项目,看看它是否是另一个数组的成员。而不是做

giant_list = ["a", "c", "j"]
good_letters = ["a", "b"]
isin = falses(size(giant_list,1))
for i=1:size(giant_list,1)
    isin[i] = giant_list[i] in good_letters
end

在 julia 中是否有任何矢量化(双重矢量化?)的方式来做到这一点?与基本运算符类比,我想做类似

的事情
isin = giant_list .in good_letters

我知道这可能是不可能的,但我只是想确保我没有遗漏任何东西。我知道我可能可以使用 DataStructures 中的 DefaultDict 来做类似的事情,但不知道 base 中的任何内容。

indexin 函数执行与您想要的类似的操作:

indexin(a, b)

Returns a vector containing the highest index in b for each value in a that is a member of b. The output vector contains 0 wherever a is not a member of b.

由于您希望 giant_list 中的每个元素都有一个布尔值(而不是 good_letters 中的索引),您可以简单地执行以下操作:

julia> indexin(giant_list, good_letters) .> 0
3-element BitArray{1}:
  true
 false
 false

implementation of indexin 非常简单,并指出了如果您不关心 b:

中的索引可以如何优化它的方法
function vectorin(a, b)
    bset = Set(b)
    [i in bset for i in a]
end

只有有限的一组名称可以用作中缀运算符,因此不能将其用作中缀运算符。

您可以在 Julia v0.6 中使用 unified broadcasting syntax.

非常轻松地矢量化 in
julia> in.(giant_list, (good_letters,))
3-element Array{Bool,1}:
  true
 false
 false

注意 good_lettersscalarification 使用单元素元组。或者,您可以使用 Scalar 类型,例如 StaticArrays.jl.

中介绍的类型

Julia v0.5 支持相同的语法,但需要专门的标量函数(或前面提到的 Scalar 类型):

scalar(x) = setindex!(Array{typeof(x)}(), x)

之后

julia> in.(giant_list, scalar(good_letters))
3-element Array{Bool,1}:
  true
 false
 false

findin() 不会给你一个布尔掩码,但你可以很容易地使用它来为另一个数组中包含的值设置 array/DataFrame 的子集:

julia> giant_list[findin(giant_list, good_letters)]
1-element Array{String,1}:
 "a"

这个问题有一些现代的(即 Julia v1.0)解决方案:

首先,更新标量策略。可以使用 Ref 对象来实现标量广播,而不是使用 1 元素元组或数组:

julia> in.(giant_list, Ref(good_letters))
3-element BitArray{1}:
  true
 false
 false

同样的结果可以通过广播中缀 (\inTAB) operator:

julia> giant_list .∈ Ref(good_letters)
3-element BitArray{1}:
  true
 false
 false

此外,使用一个参数调用 in 会创建一个 Base.Fix2,稍后可以通过广播调用应用。不过,与简单地定义一个函数相比,这似乎没有什么好处。

julia> is_good1 = in(good_letters);
       is_good2(x) = x in good_letters;

julia> is_good1.(giant_list)
3-element BitArray{1}:
  true
 false
 false

julia> is_good2.(giant_list)
3-element BitArray{1}:
  true
 false
 false

总而言之,使用 .∈Ref 可能会导致最短、最干净的代码。

绩效评估

其他答案都忽略了一个重要方面——性能。那么,让我简要回顾一下。为了实现这一点,我创建了两个 Integer 向量,每个向量包含 100,000 个元素。

using StatsBase

a = sample(1:1_000_000, 100_000)
b = sample(1:1_000_000, 100_000)

为了了解什么是体面的表现,我在 R 中做了同样的事情,导致中等表现 4.4 ms:

# R code

a <- sample.int(1000000, 100000)
b <- sample.int(1000000, 100000)

microbenchmark::microbenchmark(a %in% b)

Unit: milliseconds
     expr     min       lq     mean   median       uq      max neval
 a %in% b 4.09538 4.191653 5.517475 4.376034 5.765283 65.50126   100

高性能解决方案

findall(in(b),a)

5.039 ms (27 allocations: 3.63 MiB)

R 慢,但相差不大。然而,语法确实需要一些改进。

性能不佳的解决方案

a .∈ Ref(b)
in.(a,Ref(b))
findall(x -> x in b, a)

3.879468 seconds (6 allocations: 16.672 KiB)
3.866001 seconds (6 allocations: 16.672 KiB)
3.936978 seconds (178.88 k allocations: 5.788 MiB)

慢 800 倍(比 R 慢近 1000 倍)——这真的没什么好写的。在我看来,这三个的语法也不是很好,但至少第一个解决方案对我来说比 'performant solution'.

更好。

is-not-a 解决方案

这里是这个

indexin(a,b)

5.287 ms (38 allocations: 6.53 MiB)

性能很好,但对我来说这不是解决方案。它包含 nothing 个元素,其中该元素不在另一个向量中。在我看来,主要应用是对向量进行子集化,这不适用于此解决方案。

a[indexin(b,a)]

ERROR: ArgumentError: unable to check bounds for indices of type Nothing