按 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
我尝试了 vcat
和 reduce
但我没有得到假装的结果。有没有一种相对简单的方法来编写指令代码,避免耗时的代码?谢谢!
可能有很多方法可以做到。如果你想避免编码,你可以使用 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
注意:此解决方案也适用于任意数量的列
给出以下两个假设:
- 每个输入数组的第一列已排序,
- 每个输入数组的第一列都是唯一的,
然后对于大多数输入组合(即输入数组的数量、数组的大小),以下算法应该通过利用假设显着优于其他答案:
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
、只有两个输入矩阵时的专用例程等,但我不会为它们操心。
假设我们在 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
我尝试了 vcat
和 reduce
但我没有得到假装的结果。有没有一种相对简单的方法来编写指令代码,避免耗时的代码?谢谢!
可能有很多方法可以做到。如果你想避免编码,你可以使用 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
注意:此解决方案也适用于任意数量的列
给出以下两个假设:
- 每个输入数组的第一列已排序,
- 每个输入数组的第一列都是唯一的,
然后对于大多数输入组合(即输入数组的数量、数组的大小),以下算法应该通过利用假设显着优于其他答案:
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
、只有两个输入矩阵时的专用例程等,但我不会为它们操心。