按 julia 中的公共列值合并数组

Merge arrays by common column values in julia

假设我们在 Julia 中有以下 3 个数组:

5.0 3.5 6.0 3.6 7.0 3.0

5.0 4.5 6.0 4.7 8.0 3.0

5.0 4.0 6.0 3.2 8.0 4.0

我想将 3 个数组合并为一个数组,通过第一列的共同值,对第二列的值求和。结果必须是以下数组:

5.0 12 6.0 11.5 7.0 3.0 8.0 7.0

我尝试了 vcatreduce 但我没有得到假装的结果。有没有一种相对简单的方法来编写指令代码,避免耗时的代码?谢谢!

可能有很多方法可以做到。如果你想避免编码,你可以使用 DataFrames 包。这不是最快的解决方案,但它很短。

假设您将数组定义为变量:

x = [5.0  3.5
     6.0  3.6
     7.0  3.0]

y = [5.0  4.5
     6.0  4.7
     8.0  3.0]

z = [5.0  4.0
     6.0  3.2
     8.0  4.0]

那么你可以这样做:

using DataFrames
Matrix(aggregate(DataFrame(vcat(x,y,z)), :x1, sum))

:x1 部分是因为默认情况下 DataFrame 的第一列被称为 :x1 如果你没有给它一个明确的名称。在本节中,我们将矩阵转换为 DataFrame 聚合它们并将结果转换回矩阵。

如果没有额外的包,可能的解决方案可能是这样的

function aggregate(m::Array{<:Number,2}...)

    result=sortrows(vcat(m...))

    n = size(result,1)
    if n <= 1
        return result
    end 

    key_idx=1
    key=result[key_idx,1]

    for i in 2:n
      if key==result[i,1]
          result[key_idx,2:end] += result[i,2:end]
      else
          key = result[i,1]
          key_idx += 1
          result[key_idx,1]     = key 
          result[key_idx,2:end] = result[i,2:end]
      end
    end

    return result[1:key_idx,:]
end   

演示:

x = [5.0  3.5
     6.0  3.6
     7.0  3.0]

y = [5.0  4.5
     6.0  4.7
     8.0  3.0]

z = [5.0  4.0
     6.0  3.2
     8.0  4.0]

aggregate(x,y,z)

打印:

4×2 Array{Float64,2}:
 5.0  12.0
 6.0  11.5
 7.0   3.0
 8.0   7.0

注意:此解决方案也适用于任意数量的列

给出以下两个假设:

  1. 每个输入数组的第一列已排序,
  2. 每个输入数组的第一列都是唯一的,

然后对于大多数输入组合(即输入数组的数量、数组的大小),以下算法应该通过利用假设显着优于其他答案:

function f_ag(x::Matrix{T}...)::Matrix{T} where {T<:Number}
    isempty(x) && error("Empty input")
    any([ size(y,2) != 2 for y in x ]) && error("Input matrices must have two columns")
    length(x) == 1 && return copy(x[1]) #simple case shortcut
    nxmax = [ size(y,1) for y in x ]
    nxarrinds = find(nxmax .> 0)
    nxrowinds = ones(Int, length(nxarrinds))
    z = Tuple{T,T}[]
    while !isempty(nxarrinds)
        xmin = minimum(T[ x[nxarrinds[j]][nxrowinds[j], 1] for j = 1:length(nxarrinds) ])
        minarrinds = Int[ j for j = 1:length(nxarrinds) if x[nxarrinds[j]][nxrowinds[j], 1] == xmin ]
        rowsum = sum(T[ x[nxarrinds[k]][nxrowinds[k], 2] for k in minarrinds ])
        push!(z, (xmin, rowsum))
        for k in minarrinds
            nxrowinds[k] += 1
        end
        for j = length(nxarrinds):-1:1
            if nxrowinds[j] > nxmax[nxarrinds[j]]
                deleteat!(nxrowinds, j)
                deleteat!(nxarrinds, j)
            end
        end
    end
    return [ z[n][j] for n = 1:length(z), j = 1:2 ]
end

如果假设 2 被违反,即第一列不能保证是唯一的,您仍然可以利用排序顺序,但算法将再次变得更加复杂,因为您需要另外期待每个最小索引来检查重复项。我现在不会让自己经历那种痛苦。

另请注意,您可以调整以下行:

rowsum = sum(T[ x[nxarrinds[k]][nxrowinds[k], 2] for k in minarrinds ])

对此:

rowsum = input_func(T[ x[nxarrinds[k]][nxrowinds[k], 2:end] for k in minarrinds ])

现在您可以输入任何您喜欢的函数,并且在您的输入矩阵中还有任意数量的附加列。

可能还有一些额外的优化可以添加到这里,例如预分配 z、只有两个输入矩阵时的专用例程等,但我不会为它们操心。