Using DataFrames.jl you can do e.g.:
function bootstrap(;iters=1, data=nothing, statistic=nothing)
statArr = Float64.(empty(data)) # Init empty dataframe
for i in 1:iters
stat = statistic(data, rand(1:nrow(data), nrow(data)))
push!(statArr, stat) # push row to empty dataframe
end
return statArr
end;
# Statistic function for column means
meanmap(data, sel) = [mean(@view x[sel]) for x in eachcol(data)]
Which should be faster than R. The changes are:
- major: use views instead of copying everything in every iteration
- minor: do not create a data frame for each bootstrap replicate but rather a vector and
push! it instead of append!ing it (this saves time of creation and validation of data frame objects)
(I have made only the major optimizations of the code; there are some additional minor optimizations that could be made, but they should not affect the run time in a significant way)
Also note that you are close to the maximum execution speed as:
julia> x = rand(1:nrow(df), nrow(df));
julia> y = df[!, 1];
julia> f(y, x) = mean(@view y[x]);
julia> g(y, x) = [f(y, x) for _ in 1:9999*1000];
julia> @time g(y, x);
Is roughly the lower bound of execution time you can expect to have and it is not much faster than the code above (it is faster of course, by around 25%-30%, as it does less work and is more CPU cache friendly).
As a small comment showing how details matter in such cases (I think it is interesting, although it is a minor optimization, so I left it out).
Instead of rand(1:nrow(data), nrow(data)) if you use sort!(rand(1:nrow(data), nrow(data))) you save an additional 1 second. The reason is that in this way you ensure that you access data sequentially when you calculate mean (which is more CPU cache friendly and mean is unaffected by observation order).
A second comment like this is that on a multi-CPU machine (and started Julia with -t switch selecting to use more than one thread) one could use threading to speed things up like this (again - I did not optimize things out here to the very last possible tweak, but rather wanted to show the main idea):
function bootstrap(;iters=1, data=nothing, statistic=nothing)
statArr = Float64.(empty(data)) # Init empty dataframe
tmp = Vector{Any}(undef, iters)
Threads.@threads for i in 1:iters
stat = statistic(data, rand(1:nrow(data), nrow(data)))
tmp[i] = stat
end
for v in tmp
push!(statArr, v) # push dataframe to empty dataframe
end
return statArr
end
This is much faster and easy to do in Julia (while doable, but not so easy in R).
Regarding views you can read about them here.