有没有一种简单的方法可以并行运行DataFrames :: by?

时间:2018-09-28 13:22:17

标签: dataframe julia

我有一个要并行计算的大型数据框。我要并行化的呼叫是

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)

2 个答案:

答案 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。