Julia: How to create multiple columns with one function in DataFrames.jl - julia

Say I have a column
using DataFrames
df = DataFrame(var = "methodA_mean")
1×3 DataFrame
│ Row │ var │
│ │ String │
├─────┼──────────────┼
│ 1 │ methodA_mean │
│ 2 │ methodB_var │
│ 3 │ methodA_var │
and I would like to create two new columns by extracting A and mean var like so
3×3 DataFrame
│ Row │ var │ ab │ stat │
│ │ String │ String │ String │
├─────┼──────────────┼────────┼────────┤
│ 1 │ methodA_mean │ A │ mean │
│ 2 │ methodB_var │ B │ var │
│ 3 │ methodA_var │ A │ var │
I can write a regex extract "A" or "B" and "mean" and "var" from the var column. But how I output into multiple columns elegantly?
I tried the below and it works, but I feel there should more elegant way to create multiple columns
tmp = match.(r"method(?<ab>A|B)_(?<stat>mean|var)", df.var)
df.ab = getindex.(tmp, :ab)
df.stat = getindex.(tmp, :st)
3×3 DataFrame
│ Row │ var │ ab │ stat │
│ │ String │ SubStri… │ SubStri… │
├─────┼──────────────┼──────────┼──────────┤
│ 1 │ methodA_mean │ A │ mean │
│ 2 │ methodB_var │ B │ var │
│ 3 │ methodA_var │ A │ var │

I am not sure in which part for your code you are looking for an improvement as it seems normal and OK to me, but you could write e.g. this:
julia> insertcols!(df, :ab => last.(first.(df.var, 7), 1), :stat => chop.(df.var, head=8, tail=0))
3×3 DataFrame
│ Row │ var │ ab │ stat │
│ │ String │ String │ SubStri… │
├─────┼──────────────┼────────┼──────────┤
│ 1 │ methodA_mean │ A │ mean │
│ 2 │ methodB_var │ B │ var │
│ 3 │ methodA_var │ A │ var │

Related

How to add suffix or prefix for duplicate columns in julia?

I have a two df and both dfs have some common columns which are not included in on list. If I add makeunique parameter it creates new column with suffix of _n where. Is there anyway I can pass prefix values such as ['_left', '_right'] to the result df?
In pandas I can pass some argument lsuffix and rsuffix.
Sample Input:
Df1:
│ Row │ ID │ Name │
│ │ Int64 │ String │
├─────┼───────┼─────────┤
│ 1 │ 1 │ Mohamed │
│ 2 │ 2 │ Thasin │
Df2:
│ Row │ ID │ Job │ Name │
│ │ Int64 │ String │ String │
├─────┼───────┼────────┼────────┤
│ 1 │ 1 │ Tech │ Md │
│ 2 │ 2 │ Tech │ Tn │
│ 3 │ 3 │ Assist │ Rj │
│ 4 │ 4 │ Test │ Mi │
inner join result:
innerjoin(people, jobs, on = :ID, makeunique=true)
│ Row │ ID │ Name │ Job │ Name_1 │
│ │ Int64 │ String │ String │ String │
├─────┼───────┼─────────┼────────┼─────────┤
│ 1 │ 1 │ Mohamed │ Tech │ Md │
│ 2 │ 2 │ Thasin │ Tech │ Tn │
Expected Output:
| Row │ ID │ Name_left│ Job │ Name_right │
│ │ Int64 │ String │ String │ String │
├─────┼───────┼─────────┼────────┼─────────┤
│ 1 │ 1 │ Mohamed │ Tech │ Md │
│ 2 │ 2 │ Thasin │ Tech │ Tn │
This is not implemented yet. You can expect that it will be added this year. See https://github.com/JuliaData/DataFrames.jl/issues/1333.
What you can do for the time being is:
innerjoin(rename!(s -> s == "ID" ? "ID" : s*"_left", DataFrame!(people)),
rename!(s -> s == "ID" ? "ID" : s*"_right", DataFrame!(jobs)),
on = :ID)
If you do not care about efficiency and want a bit shorter code use:
innerjoin(rename(s -> s == "ID" ? "ID" : s*"_left", people),
rename(s -> s == "ID" ? "ID" : s*"_right", jobs),
on = :ID)

How to remove/drop rows of nothing and NaN in Julia dataframe?

I have a df which contains, nothing, NaN and missing. to remove rows which contains missing I can use dropmissing. Is there any methods to deal with NaN and nothing?
Sample df:
│ Row │ x │ y │
│ │ Union…? │ Char │
├─────┼─────────┼──────┤
│ 1 │ 1.0 │ 'a' │
│ 2 │ missing │ 'b' │
│ 3 │ 3.0 │ 'c' │
│ 4 │ │ 'd' │
│ 5 │ 5.0 │ 'e' │
│ 6 │ NaN │ 'f' │
Expected output:
│ Row │ x │ y │
│ │ Any │ Char │
├─────┼─────┼──────┤
│ 1 │ 1.0 │ 'a' │
│ 2 │ 3.0 │ 'c' │
│ 3 │ 5.0 │ 'e' │
What I have tried so far,
Based on my knowledge in Julia I tried this,
df.x = replace(df.x, NaN=>"something", missing=>"something", nothing=>"something")
print(df[df."x".!="something", :])
My code is working as I expected. I feel it's ineffective way of solving this issue.
Is there any separate method to deal with nothing and NaN?
You can do e.g. this:
julia> df = DataFrame(x=[1,missing,3,nothing,5,NaN], y='a':'f')
6×2 DataFrame
│ Row │ x │ y │
│ │ Union…? │ Char │
├─────┼─────────┼──────┤
│ 1 │ 1.0 │ 'a' │
│ 2 │ missing │ 'b' │
│ 3 │ 3.0 │ 'c' │
│ 4 │ │ 'd' │
│ 5 │ 5.0 │ 'e' │
│ 6 │ NaN │ 'f' │
julia> filter(:x => x -> !any(f -> f(x), (ismissing, isnothing, isnan)), df)
3×2 DataFrame
│ Row │ x │ y │
│ │ Union…? │ Char │
├─────┼─────────┼──────┤
│ 1 │ 1.0 │ 'a' │
│ 2 │ 3.0 │ 'c' │
│ 3 │ 5.0 │ 'e' │
Note that here the order of checks is important, as isnan should be last, because otherwise this check will fail for missing or nothing value.
You could also have written it more directly as:
julia> filter(:x => x -> !(ismissing(x) || isnothing(x) || isnan(x)), df)
3×2 DataFrame
│ Row │ x │ y │
│ │ Union…? │ Char │
├─────┼─────────┼──────┤
│ 1 │ 1.0 │ 'a' │
│ 2 │ 3.0 │ 'c' │
│ 3 │ 5.0 │ 'e' │
but I felt that the example with any is more extensible (you can then store the list of predicates to check in a variable).
The reason why only a function for removing missing is provided in DataFrames.jl is that this is what is normally considered to be a valid but desirable to remove value in data science pipelines.
Normally in Julia when you see nothing or NaN you probably want to handle them in a different way than missing as they most likely signal there is some error in the data or in processing of data (as opposed to missing which signals that the data was just not collected).

Repeat random data with Faker

I want to use Faker data for many rows. My current code only repeats whatever was generated by the Faker library at that moment:
Current output:
│ Row │ Identifier │
│ │ String │
├─────┼────────────┤
│ 1 │ 40D593 │
│ 2 │ 40D593 │
│ 3 │ 40D593 │
Desired outputs:
│ Row │ Digits │
│ │ String │
├─────┼────────┤
│ 1 │ 23K125 │
│ 2 │ 13K125 │
│ 3 │ 45K125 │
df2 = DataFrame(Identifier = repeat([Faker.bothify("##?###")], outer=[3]))
I thought I could do something like Faker.bothify("##?###") * 3. I suppose there may also be a way to apply it to a dataframe column that was already made, but I can't find a way just looking through the docs quickly.
The simple way is to use a comprehension:
df2 = DataFrame(Identifier=[Faker.bothify("##?###") for _ in 1:3])
an alternative is to use broadcasting (but for me a comprehension in this case is more natural to use):
df2 = DataFrame(Identifier=Faker.bothify.(Iterators.repeated("##?###", 3)))
(I assume this is what you want)
and this is the way to apply it to an existing column with String eltype. This operation is in-place:
julia> df = DataFrame(Identifier=Vector{String}(undef, 3))
3×1 DataFrame
│ Row │ Identifier │
│ │ String │
├─────┼────────────┤
│ 1 │ #undef │
│ 2 │ #undef │
│ 3 │ #undef │
julia> df.Identifier .= Faker.bothify.("##?###")
3-element Array{String,1}:
"12H314"
"56G992"
"23X588"
julia> df
3×1 DataFrame
│ Row │ Identifier │
│ │ String │
├─────┼────────────┤
│ 1 │ 12H314 │
│ 2 │ 56G992 │
│ 3 │ 23X588 │

Cumulative Returns

In R we can do:
cum.ret <- cumprod(1 + df$rets) - 1
I want to do the same thing with Julia here is some dummy data:
# Dummy Data
df = DataFrame(a = 1:10, b = 10*rand(10), Close = 10 * rand(10))
# Calculate Returns
Close = df[:Close]
Close = convert(Array, Close)
df[:Close_Rets] = [NaN; (Close[2:end] ./ Close[1:(end-1)] - 1)]
# Calculate Cumulative Returns
df[:Cum_Ret] = cumprod(((1 .+ df[:Close_Rets])-1),2)
With the output:
julia> head(df)
6×5 DataFrames.DataFrame
│ Row │ a │ b │ Close │ Close_Rets │ Cum_Ret │
├─────┼───┼─────────┼──────────┼────────────┼───────────┤
│ 1 │ 1 │ 6.15507 │ 3.6363 │ NaN │ NaN │
│ 2 │ 2 │ 7.73259 │ 0.98378 │ -0.729456 │ -0.729456 │
│ 3 │ 3 │ 3.64926 │ 7.94633 │ 7.07735 │ 7.07735 │
│ 4 │ 4 │ 5.15762 │ 0.744905 │ -0.906258 │ -0.906258 │
│ 5 │ 5 │ 9.49532 │ 8.51811 │ 10.4352 │ 10.4352 │
│ 6 │ 6 │ 6.14604 │ 5.02165 │ -0.410473 │ -0.410473 │
Anyway to make this work?

Multiply two data frame columns

So I tried this:
df[:new_col] = (df[:col_one ] .* [df[:col_two]])
It produces a wild result.
I then though to iterate row wise by access the data frame index:
v = Float64[]
for i in 1:nrow(df)
z = df[[i],[:col_one]] * df[[i],[:col_two]]
append!(v,z)
end
This however does not work. Any ideas?
What are my options from here? Pull the data from a data frame and make a vector?
** Update **
df = DataFrame(a = 1:10, b = 10*rand(10), c = 10 * rand(10))
df[:new_d] = df[:b] .* df[:c]
For output:
julia> head(df)
6×4 DataFrames.DataFrame
│ Row │ a │ b │ c │ new_d │
├─────┼───┼─────────┼─────────┼─────────┤
│ 1 │ 1 │ 6.67916 │ 8.38096 │ 55.9778 │
│ 2 │ 2 │ 7.50056 │ 5.26593 │ 39.4974 │
│ 3 │ 3 │ 7.76419 │ 3.54361 │ 27.5133 │
│ 4 │ 4 │ 2.86521 │ 8.41335 │ 24.1061 │
│ 5 │ 5 │ 3.7417 │ 8.10884 │ 30.3409 │
│ 6 │ 6 │ 7.52014 │ 2.61603 │ 19.6729 │

Resources