我有一个要并行计算的大型数据框。我要并行化的呼叫是
df = by(df, [:Chromosome], some_func)
有没有一种方法可以轻松地并行化?最好不要复制。
此外,我猜测所使用的并行化类型应根据by创建的组的大小而有所不同。
在答案中使用的最小可重现示例:
using DataFrames, CSV, Pkg
iris = CSV.read(joinpath(Pkg.dir("DataFrames"), "test/data/iris.csv"))
iris_count = by(iris, [:Species], nrow)
答案 0 :(得分:3)
在Windows中,在控制台上运行(根据您拥有的内核/线程数进行调整):
$ set JULIA_NUM_THREADS=4
$ julia
在控制台上运行的Linux上:
$ export JULIA_NUM_THREADS=4
$ julia
现在检查是否有效:
julia> Threads.nthreads()
4
运行以下代码(我将更新您的代码以匹配Julia 1.0):
using CSV, DataFrames, BenchmarkTools
iris = CSV.read(joinpath(dirname(pathof(DataFrames)),"..","test/data/iris.csv"))
iris.PetalType = iris.PetalWidth .> 2; #add an additional column for testing
让我们定义一些在DataFrame
的一部分上运行的函数
function nrow2(df::AbstractDataFrame)
val = nrow(df)
#do something much more complicated...
val
end
现在,难题中最复杂的部分出现了:
function par_by(df::AbstractDataFrame,f::Function,cols::Symbol...;block_size=40)
#f needs to be precompiled - we precompile using the first row of the DataFrame.
#If try to do it within @thread macro
#Julia will crash in most ugly and unexpected ways
#if you comment out this line you can observe a different crash with every run
by(view(df,1:1),[cols...],f);
nr = nrow(df)
local dfs = DataFrame()
blocks = Int(ceil(nr/block_size))
s = Threads.SpinLock()
Threads.@threads for block in 1:blocks
startix = (block-1)*block_size+1
endix = min(block*block_size,nr)
rv= by(view(df,startix:endix), [cols...], f)
Threads.lock(s)
if nrow(dfs) == 0
dfs = rv
else
append!(dfs,rv)
end
Threads.unlock(s)
end
dfs
end
让我们测试一下并汇总结果
julia> res = par_by(iris,nrow2,:Species)
6×2 DataFrame
│ Row │ Species │ x1 │
│ │ String │ Int64 │
├─────┼────────────┼───────┤
│ 1 │ versicolor │ 20 │
│ 2 │ virginica │ 20 │
│ 3 │ setosa │ 10 │
│ 4 │ versicolor │ 30 │
│ 5 │ virginica │ 30 │
│ 6 │ setosa │ 40 │
julia> by(res, :Species) do df;DataFrame(x1=sum(df.x1));end
3×2 DataFrame
│ Row │ Species │ x1 │
│ │ String │ Int64 │
├─────┼────────────┼───────┤
│ 1 │ setosa │ 50 │
│ 2 │ versicolor │ 50 │
│ 3 │ virginica │ 50 │
par_by
还支持多列
julia> res = par_by(iris,nrow2,:Species,:PetalType)
8×3 DataFrame
│ Row │ Species │ PetalType │ x1 │
│ │ String │ Bool │ Int64 │
├─────┼───────────┼───────────┼───────┤
│ 1 │ setosa │ false │ 40 │
⋮
│ 7 │ virginica │ true │ 13 │
│ 8 │ virginica │ false │ 17 │
@BogumiłKamiński评论说,在线程化之前使用groupby()
是合理的。除非出于某些原因groupby
花费很高(需要全扫描),否则这是推荐的方法-使聚合更简单。
ress = DataFrame(Species=String[],count=Int[])
for group in groupby(iris,:Species)
r = par_by(group,nrow2,:Species,block_size=15)
push!(ress,[r.Species[1],sum(r.x1)])
end
julia> ress
3×2 DataFrame
│ Row │ Species │ count │
│ │ String │ Int64 │
├─────┼────────────┼───────┤
│ 1 │ setosa │ 50 │
│ 2 │ versicolor │ 50 │
│ 3 │ virginica │ 50 │
请注意,在上面的示例中,只有三个组,因此我们对每个组进行并行处理。但是,如果您有大量的组,则可以考虑运行:
function par_by2(df::AbstractDataFrame,f::Function,cols::Symbol...)
res = NamedTuple[]
s = Threads.SpinLock()
groups = groupby(df,[cols...])
f(view(groups[1],1:1));
Threads.@threads for g in 1:length(groups)
rv= f(groups[g])
Threads.lock(s)
key=tuple([groups[g][cc][1] for cc in cols]...)
push!(res,(key=key,val=rv))
Threads.unlock(s)
end
res
end
julia> iris.PetalType = iris.PetalWidth .> 2;
julia> par_by2(iris,nrow2,:Species,:PetalType)
4-element Array{NamedTuple,1}:
(key = ("setosa", false), val = 50)
(key = ("versicolor", false), val = 50)
(key = ("virginica", true), val = 23)
(key = ("virginica", false), val = 27)
让我知道它是否对您有用。 由于可能会有更多的人遇到类似的问题,因此我会将这段代码放入Julia包中(这就是为什么我保持这段代码非常笼统的原因)
答案 1 :(得分:0)
以julia -p 4
开始朱莉娅,然后运行
using CSV, DataFrames
iris = CSV.read(joinpath(dirname(pathof(DataFrames)),"..","test/data/iris.csv"))
g = groupby(iris, :Species)
pmap(nrow, [i for i in g])
这将并行运行groupby。