Julia中的向量化“in”函数是什么?

15
我经常需要遍历一个长数组或数据框的一列,对于每个项目,查看它是否是另一个数组的成员。与其这样做:
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来实现类似的功能,但不知道基本库中是否有类似的东西。

6个回答

14

indexin函数可以做到与您想要的类似的功能:

indexin(a, b)

返回一个向量,其中包含b中每个值的最高索引,这些值是a中属于b的成员。输出向量在a不是b成员的地方包含0。

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

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

实现indexin非常简单,并且指出了如果不关心b中的索引,可以如何进行优化:

function vectorin(a, b)
    bset = Set(b)
    [i in bset for i in a]
end

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


1
这在DataArrays中运行得很好,尽管它与NAs不兼容(幸运的是,这对我来说并不重要)。 - ARM

9

这个问题有几种现代解决方案(即Julia v1.0):

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

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

通过广播中缀 (\inTAB)运算符同样可以实现相同的结果:

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一起使用可能会导致最短、最干净的代码。

4

在Julia v0.6中,您可以使用统一广播语法轻松地将in向量化。

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

请注意,通过使用一个元素的元组来标量化good_letters。或者,您可以使用像StaticArrays.jl中介绍的Scalar类型。
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

我觉得我在这里漏掉了什么:上面的命令以及几个throw错误的变体在0.5.0中。 - ARM
@ARM:啊,抱歉,原来我一直在0.6.0上测试,所以确切的命令可以运行。在0.5.0中,你需要一个零维数组(或者使用“Scalar”类型)。 - Fengyang Wang
@ARM 我已经更新了我的解决方案,并提供了一个在0.5中也能工作的答案。 - Fengyang Wang
注意:这适用于数组,但不适用于0.6版本的DataArrays。Matt B.的答案仍然适用于两者。 - ARM

1

绩效评估

其他答案忽略了一个重要方面 - 绩效。因此,让我简要回顾一下。为了使这个更加现实,我创建了两个包含100,000个元素的Integer向量。

using StatsBase

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

为了知道一个良好的性能表现是什么,我在 R 中做了同样的事情,导致中位数性能为 4.4 毫秒
# 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)

Slower than R, but not by much. The syntax, however, could really use some improvement.

低效的解决方案

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)

比起 R 几乎慢了 800 倍(几乎比 R 慢了 1000 倍),这真的不值得一提。在我看来,这三个解决方案的语法也不是很好,但至少第一个解决方案看起来比“高性能解决方案”更好。

非解决方案

这个是这样的

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

1

findin()不能给你一个布尔掩码,但是你可以很容易地使用它来对包含在另一个数组中的值进行子集筛选:

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

注意,findin在0.7版本中已被弃用,并在1.0版本中删除。请使用findall(in(...), ...)代替。 - Milan Bouchet-Valat

0

在 Julia 论坛上刚刚发现了另一个解决方案,它使用了我认为更加简洁的语法:

in(giant_list).(good_letters)

这段代码会给你一个BitVector。要检查序列之间是否具有通用匹配,可以使用any()函数:

any(in(giant_list).(good_letters)) # true

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接