Julia 朱莉娅:从表达式创建数据帧列?

Julia 朱莉娅:从表达式创建数据帧列?,julia,dataframesmeta.jl,Julia,Dataframesmeta.jl,鉴于此: dict = Dict(("y" => ":x / 2")) df = DataFrame(x = [1, 2, 3, 4]) df 4×1 DataFrame │ Row │ x │ │ │ Int64 │ ├─────┼───────┤ │ 1 │ 1 │ │ 2 │ 2 │ │ 3 │ 3 │ │ 4 │ 4 │ 我想说: 4×2 DataFrame │ Row │

鉴于此:

dict = Dict(("y" => ":x / 2"))

df = DataFrame(x = [1, 2, 3, 4])

df
4×1 DataFrame
│ Row │ x     │
│     │ Int64 │
├─────┼───────┤
│ 1   │ 1     │
│ 2   │ 2     │
│ 3   │ 3     │
│ 4   │ 4     │
我想说:

4×2 DataFrame
│ Row │ x     │ y       │
│     │ Int64 │ Float64 │
├─────┼───────┼─────────┤
│ 1   │ 1     │ 0.5     │
│ 2   │ 2     │ 1.0     │
│ 3   │ 3     │ 1.5     │
│ 4   │ 4     │ 2.0     │
这似乎是
DataFramesMeta
的完美应用程序,无论是
@with
还是
@eachrow
,但我无法在存在
:x
的环境中使表达式按预期进行计算

基本上,我希望能够在
dict
中迭代
(k,v)
对,并为每个
符号(k)
创建一个新列,其中包含相应的值
eval(Meta.parse(v))
,或者类似的内容,在进行评估时,存在
符号
:x

我没想到这会起作用,它也不会:

[df[Symbol(k)] = eval(Meta.parse(v)) for (k, v) in dict]

ERROR: MethodError: no method matching /(::Symbol, ::Int64)
但这说明了问题:我需要在表达式包含符号的环境中对表达式求值

但是,使用将其移动到
@中不起作用:

using DataFramesMeta

@with(df, [eval(Meta.parse(v)) for (k, v) in dict])

ERROR: MethodError: no method matching /(::Symbol, ::Int64)
使用
@eachrow
失败的方式相同:

using DataFramesMeta

@eachrow df begin
           for (k, v) in dict
               @newcol tmp::Vector{Float32}
               tmp = eval(Meta.parse(v))
           end
       end

ERROR: MethodError: no method matching /(::Symbol, ::Int64)
我猜我还不清楚
DataFramesMeta
是如何在数据帧中创建环境的。我也不必使用
DataFramesMeta
为此,任何合理简洁的选项都可以使用,因为我可以将其封装在一个包函数中

注意:我控制要解析为表达式的字符串的格式,但我希望避免复杂性,例如在字符串中指定DataFrame对象的名称,或广播每个操作。我希望初始字符串中的表达式语法对非Julia程序员来说相当清晰

更新:我在对这个问题的评论中尝试了所有三种解决方案,但它们都有一个问题:它们在函数内部不工作

dict = Dict(("y" => ":x / 2"))

data = DataFrame(x = [1, 2, 3, 4])


function transform_from_dict(df, dict)

    new = eval(Meta.parse("@transform(df, " * join(join.(collect(dict), " = "), ", ") * ")"))

    return new

end

transform_from_dict(data, dict)

ERROR: UndefVarError: df not defined
或:


好的,把所有评论者的答案结合起来是有效的

using DataFrames
using DataFramesMeta

dict = Dict(("y" => ":x / 2"))

data = DataFrame(x = [1, 2, 3, 4])
@与
一起使用的方法:

# using @with
function transform_from_dict1(df, dict)

    global df

    [df[!, Symbol(k)] = eval(:(@with(df, $(Meta.parse(v))))) for (k, v) in dict]

    return df

end

transform_from_dict1(data, dict)
# 4×2 DataFrame
# │ Row │ x     │ y       │
# │     │ Int64 │ Float64 │
# ├─────┼───────┼─────────┤
# │ 1   │ 1     │ 0.5     │
# │ 2   │ 2     │ 1.0     │
# │ 3   │ 3     │ 1.5     │
# │ 4   │ 4     │ 2.0     │
和的方法使用
@transform

# using @transform
function transform_from_dict2(df, dict)

    global df

    new_df = eval(Meta.parse("@transform(df, " * join(join.(collect(dict), " = "), ", ") * ")"))

    return new_df

end

transform_from_dict2(data, dict)
# 4×2 DataFrame
# │ Row │ x     │ y       │
# │     │ Int64 │ Float64 │
# ├─────┼───────┼─────────┤
# │ 1   │ 1     │ 0.5     │
# │ 2   │ 2     │ 1.0     │
# │ 3   │ 3     │ 1.5     │
# │ 4   │ 4     │ 2.0     │
两者都包含使用
global
的修复程序

请注意,第二个表单使用的内存大约是第一个表单的2.5倍,这可能是由于创建了第二个
数据帧

julia> @allocated transform_from_dict1(data, dict)
853948

julia> @allocated transform_from_dict2(data, dict)
22009111
我还认为第一种形式更清晰一些,所以这就是我在内部使用的


请注意,如果您的转换中有逻辑运算符,您可能需要广播这些运算符,并且像往常一样,您需要提前处理任何丢失的数据问题。

我与@Ajar并行处理了这个答案,没有从该答案复制任何内容,我也不知道它。我对Julia完全是新手,所以我不得不安装它(因为我认为在线编译器甚至不知道数据帧),后来我明白了这些包无论如何都必须在开始时调用,无论是在线还是离线。我已经添加了初学者可能需要知道的软件包信息

using Pkg 
Pkg.add("DataFrames")
Pkg.add("DataFramesMeta")

using DataFrames
using DataFramesMeta 
dict = Dict(("y" => ":x / 2"))
df = DataFrame(x = [1, 2, 3, 4])
解决方案:

julia> function transform_from_dict!(k, v)
           global df
           df[!, Symbol(k)] = eval(:(@with(df, $(Meta.parse(v)))))
           return nothing
       end
julia> function transform_from_dict(df, dict)
           global new
           new = eval(Meta.parse("@transform(df, " * join(join.(collect(dict), " = "), ", ") * ")"))

           return new

       end
@transform解决方案:

julia> function transform_from_dict!(k, v)
           global df
           df[!, Symbol(k)] = eval(:(@with(df, $(Meta.parse(v)))))
           return nothing
       end
julia> function transform_from_dict(df, dict)
           global new
           new = eval(Meta.parse("@transform(df, " * join(join.(collect(dict), " = "), ", ") * ")"))

           return new

       end

感谢其他评论员,在@Ajar的答案中列出了一些基本的想法。

尝试
[df[!,Symbol(k)]=eval(DataFramesMeta.with_helper(df,Meta.parse(v)))for dict中的(k,v)
[df[!,Symbol(k)]=eval(:(@with(df,$(Meta.parse(v);))))for dict中的(k,v)
。但两者都很棘手。关键是你不想直接
eval
v
,而是让
DataFramesMeta
用它的魔力来评估它。一般来说,我不建议做这样的操作,因为它们是不安全的。如果您确实需要这样做,例如,这将起作用
eval(Meta.parse(@transform(df),*join(join.(collect(dict),“=”,”,“*”)))
。它们将在函数内部失败,因为
eval
是在模块的全局范围内计算的。DataFramesMeta.jl不创建
eval
环境,但其宏在编译时而不是在运行时解析。在脚本开头添加
global new
,并在函数开头(函数内部)添加
global new
。这是Pyton中的一个通用技巧,适用于任何需要在运行时读取并且仍然随时间增长的对象。看见也许它在朱莉娅身上也起作用。然后df可以扩展到任何列表/目录理解。未经测试:可能它还可以与eval()+Meta.parse()结合使用。顺便说一句:我没有看到他是茱莉亚而不是Python,请把这个想法转给茱莉亚。你接受我的回答太公平了。谢谢你,你也得到了明确的赞成票,因为你不能指望我回答,因为我对朱莉娅来说是全新的。谢谢你的公平竞争。再次投票支持时间测试。
1-element Array{Nothing,1}:
 nothing
julia> df
4×2 DataFrame
 Row │ x      y
     │ Int64  Float64
─────┼────────────────
   1 │     1      0.5
   2 │     2      1.0
   3 │     3      1.5
   4 │     4      2.0
julia> function transform_from_dict(df, dict)
           global new
           new = eval(Meta.parse("@transform(df, " * join(join.(collect(dict), " = "), ", ") * ")"))

           return new

       end
transform_from_dict (generic function with 1 method)
julia>

julia> transform_from_dict(data, dict)
4×2 DataFrame
 Row │ x      y
     │ Int64  Float64
─────┼────────────────
   1 │     1      0.5
   2 │     2      1.0
   3 │     3      1.5
   4 │     4      2.0