首页 > 解决方案 > Julia:DataFrame 的类型稳定性

问题描述

如何以类型稳定的方式访问 DataFrame 的列?

假设我有以下数据:

df = DataFrame(x = fill(1.0, 1000000), y = fill(1, 1000000), z = fill("1", 1000000))

现在我想做一些递归计算(所以我不能使用transform

function foo!(df::DataFrame)
    for i in 1:nrow(df)
        if (i > 1) df.x[i] += df.x[i-1] end
    end
end

这有可怕的表现:

julia> @time foo!(df)
  0.144921 seconds (6.00 M allocations: 91.529 MiB)

此简化示例中的快速修复如下:

function bar!(df::DataFrame)
    x::Vector{Float64} = df.x
    for i in length(x)
        if (i > 1) x[i] += x[i-1] end
    end
end
julia> @time bar!(df)
  0.000004 seconds

但是,我正在寻找一个通用的解决方案,例如当递归计算只是指定为一个函数时

function foo2!(df::DataFrame, fn::Function)
    for i in 1:nrow(df)
        if (i > 1) fn(df, i) end
    end
end

function my_fn(df::DataFrame, i::Int64)
    x::Vector{Float64} = df.x
    x[i] += x[i-1]
end

虽然这(几乎)没有分配,但它仍然很慢。

julia> @time foo2!(df, my_fn)
  0.050465 seconds (1 allocation: 16 bytes)

是否有一种高性能的方法允许这种灵活性/通用性?

编辑:我还应该提到,在实践中,不知道函数fn依赖于哪些列。即我正在寻找一种方法,允许高性能访问/更新内部的任意列fnfn如果需要,可以将所需的列与例如一起指定Vector{Symbol}

编辑2:我尝试使用如下屏障功能,但它不是高性能的

function foo3!(df::DataFrame, fn::Function, colnames::Vector{Symbol})
    cols = map(cname -> df[!,cname], colnames)
    for i in 1:nrow(df)
        if (i > 1) fn(cols..., i) end
    end
end

function my_fn1(x::Vector{Float64}, i::Int64)
    x[i] += x[i-1]
end

function my_fn2(x::Vector{Float64}, y::Vector{Int64}, i::Int64)
    x[i] += x[i-1] * y[i-1]
end
@time foo3!(df, my_fn1, [:x])
@time foo3!(df, my_fn2, [:x, :y])

标签: julia

解决方案


此问题旨在(避免对宽数据帧进行过度编译),如何处理它的方法在https://github.com/bkamins/Julia-DataFrames-Tutorial/blob/master/11_performance.ipynb中进行了说明。

一般来说,您应该减少索引到数据框的次数。所以在这种情况下做:

julia> function foo3!(x::AbstractVector, fn::Function)
           for i in 2:length(x)
               fn(x, i)
           end
       end
foo3! (generic function with 1 method)

julia> function my_fn(x::AbstractVector, i::Int64)
           x[i] += x[i-1]
       end
my_fn (generic function with 1 method)

julia> @time foo3!(df.x, my_fn)
  0.010746 seconds (16.60 k allocations: 926.036 KiB)

julia> @time foo3!(df.x, my_fn)
  0.002301 seconds

(我正在使用您想要传递自定义函数的版本)


推荐阅读