From 46e57685ae1731db3069712eff7b06dabe0bdd64 Mon Sep 17 00:00:00 2001 From: Pietro Vertechi Date: Thu, 24 Aug 2017 12:32:33 +0100 Subject: [PATCH 1/9] added given macro to plot dataframes --- README.md | 6 ++++-- src/StatPlots.jl | 2 ++ src/given.jl | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 src/given.jl diff --git a/README.md b/README.md index a56a96f..cb74104 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,12 @@ df = DataFrame(a = 1:10, b = 10*rand(10), c = 10 * rand(10)) plot(df, :a, [:b :c]) scatter(df, :a, :b, markersize = :(4 * log(:c + 0.1))) ``` -If you find an operation not supported by DataFrames, please open an issue. An alternative approach to the `StatPlots` syntax is to use the [DataFramesMeta](https://github.com/JuliaStats/DataFramesMeta.jl) macro `@with`. Symbols not referring to DataFrame columns must be escaped by `^()` e.g. +If you find an operation not supported by DataFrames, please open an issue. An alternative approach to the above syntax is to use the macro `@given`. Symbols not referring to DataFrame columns must be escaped by `^()` wherever this could cause ambiguity e.g. ```julia using DataFramesMeta -@with(df, plot(:a, [:b :c], colour = ^([:red :blue]))) +@given df plot(:a, [:b :c], colour = [:red :blue]) +df[:red] = rand(10) +@given df plot(:a, [:b :c], colour = ^([:red :blue])) ``` --- diff --git a/src/StatPlots.jl b/src/StatPlots.jl index c8fcceb..2359229 100644 --- a/src/StatPlots.jl +++ b/src/StatPlots.jl @@ -17,7 +17,9 @@ import Loess export groupapply export get_groupederror +export @given +include("given.jl") include("dataframes.jl") include("corrplot.jl") include("cornerplot.jl") diff --git a/src/given.jl b/src/given.jl new file mode 100644 index 0000000..1a1a74f --- /dev/null +++ b/src/given.jl @@ -0,0 +1,39 @@ +""" + `@given d x` + +Convert every symbol in the expression `x` with the respective column in `d` if it exists. + +If you want to avoid replacing the symbol, escape it with `^`. + +`NA` values are replaced with `NaN` for columns of `Float64` and `""` or `Symbol()` +for strings and symbols respectively. +""" +macro given(d, x) + esc(_given(d,x)) +end + +_given(d, x) = x + +function _given(d, x::Expr) + (x.head == :quote) && return :(StatPlots.select_column($d, $x)) + (x.head == :call) && x.args[1] == :^ && length(x.args) == 2 && return x.args[2] + return Expr(x.head, _given.(d, x.args)...) +end + + +select_column(df, s) = haskey(df, s) ? convert_column(df[s]) : s + + +convert_column(col) = col + +function convert_column(col::AbstractDataArray{T}) where T + try + convert(Array, col) + catch + error("Missing data of type $T is not supported") + end +end + +convert_column(col::AbstractDataArray{String}) = convert(Array, col, "") +convert_column(col::AbstractDataArray{Symbol}) = convert(Array, col, Symbol()) +convert_column(col::AbstractDataArray{Float64}) = convert(Array, col, NaN) From 5bdd2a29134ffe5e996c37eb6ec030f62caf5a92 Mon Sep 17 00:00:00 2001 From: Pietro Vertechi Date: Sun, 27 Aug 2017 19:12:35 +0100 Subject: [PATCH 2/9] rename to df --- README.md | 18 +++++++++++------- src/StatPlots.jl | 4 ++-- src/{given.jl => df.jl} | 20 +++++++++----------- 3 files changed, 22 insertions(+), 20 deletions(-) rename src/{given.jl => df.jl} (64%) diff --git a/README.md b/README.md index cb74104..83c7a73 100644 --- a/README.md +++ b/README.md @@ -25,19 +25,23 @@ using StatPlots gr(size=(400,300)) ``` -The `DataFrames` support allows passing `DataFrame` columns as symbols. Operations on DataFrame column can be specified using quoted expressions, e.g. +`DataFrames` are supported thanks to the macro `@df` which allows passing `DataFrame` columns as symbols. Those columns can then be manipulated inside the `plot` call, like normal `Arrays`: ```julia using DataFrames df = DataFrame(a = 1:10, b = 10*rand(10), c = 10 * rand(10)) -plot(df, :a, [:b :c]) -scatter(df, :a, :b, markersize = :(4 * log(:c + 0.1))) +@df df plot(:a, [:b :c], colour = [:red :blue]) +@df df scatter(:a, :b, markersize = 4 * log.(:c + 0.1)) ``` -If you find an operation not supported by DataFrames, please open an issue. An alternative approach to the above syntax is to use the macro `@given`. Symbols not referring to DataFrame columns must be escaped by `^()` wherever this could cause ambiguity e.g. + +In case of ambiguity, symbols not referring to `DataFrame` columns must be escaped by `^()`: ```julia -using DataFramesMeta -@given df plot(:a, [:b :c], colour = [:red :blue]) df[:red] = rand(10) -@given df plot(:a, [:b :c], colour = ^([:red :blue])) +@df df plot(:a, [:b :c], colour = ^([:red :blue])) +``` + +The old syntax, passing the `DataFrame` as the first argument to the `plot` call is still supported, but has several limitations (the most important being incompatibility with user recipes): +```julia +plot(df, :a, [:b :c], colour = [:red :blue]) ``` --- diff --git a/src/StatPlots.jl b/src/StatPlots.jl index 2359229..357cb05 100644 --- a/src/StatPlots.jl +++ b/src/StatPlots.jl @@ -17,9 +17,9 @@ import Loess export groupapply export get_groupederror -export @given +export @df -include("given.jl") +include("df.jl") include("dataframes.jl") include("corrplot.jl") include("cornerplot.jl") diff --git a/src/given.jl b/src/df.jl similarity index 64% rename from src/given.jl rename to src/df.jl index 1a1a74f..8799da8 100644 --- a/src/given.jl +++ b/src/df.jl @@ -1,5 +1,5 @@ """ - `@given d x` + `@df d x` Convert every symbol in the expression `x` with the respective column in `d` if it exists. @@ -8,22 +8,20 @@ If you want to avoid replacing the symbol, escape it with `^`. `NA` values are replaced with `NaN` for columns of `Float64` and `""` or `Symbol()` for strings and symbols respectively. """ -macro given(d, x) - esc(_given(d,x)) +macro df(d, x) + esc(_df(d,x)) end -_given(d, x) = x +_df(d, x) = x -function _given(d, x::Expr) +function _df(d, x::Expr) (x.head == :quote) && return :(StatPlots.select_column($d, $x)) (x.head == :call) && x.args[1] == :^ && length(x.args) == 2 && return x.args[2] - return Expr(x.head, _given.(d, x.args)...) + return Expr(x.head, _df.(d, x.args)...) end - select_column(df, s) = haskey(df, s) ? convert_column(df[s]) : s - convert_column(col) = col function convert_column(col::AbstractDataArray{T}) where T @@ -34,6 +32,6 @@ function convert_column(col::AbstractDataArray{T}) where T end end -convert_column(col::AbstractDataArray{String}) = convert(Array, col, "") -convert_column(col::AbstractDataArray{Symbol}) = convert(Array, col, Symbol()) -convert_column(col::AbstractDataArray{Float64}) = convert(Array, col, NaN) +convert_column(col::AbstractDataArray{<:AbstractString}) = convert(Array, col, "") +convert_column(col::AbstractDataArray{Symbol}) = convert(Array, col, Symbol()) +convert_column(col::AbstractDataArray{<:Real}) = convert(Array, convert(DataArray{Float64}, col), NaN) From a63fcf281df19dea151a816dbef7bb66f6a237ad Mon Sep 17 00:00:00 2001 From: Pietro Vertechi Date: Mon, 28 Aug 2017 21:26:35 +0100 Subject: [PATCH 3/9] added cols updated readme --- README.md | 23 ++++++++++++++++++----- src/df.jl | 39 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 83c7a73..b7a645d 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ plot(df, :a, [:b :c], colour = [:red :blue]) ```julia using RDatasets iris = dataset("datasets","iris") -marginalhist(iris, :PetalLength, :PetalWidth) +@df iris marginalhist(:PetalLength, :PetalWidth) ``` ![](https://cloud.githubusercontent.com/assets/933338/19213780/a82e34a6-8d42-11e6-8846-80c9f4c48b9c.png) @@ -59,6 +59,17 @@ marginalhist(iris, :PetalLength, :PetalWidth) ## corrplot and cornerplot +```julia +@df iris corrplot([:SepalLength :SepalWidth :PetalLength :PetalWidth]) +``` +or also: +```julia +@df iris corrplot(cols(1:4)) +``` + +![iris](https://user-images.githubusercontent.com/6333339/29792101-3daa6cfe-8c37-11e7-97e2-d1763d3b95c3.png) + +A correlation plot may also be produced from a matrix: ```julia M = randn(1000,4) @@ -89,8 +100,10 @@ cornerplot(M, compact=true) ```julia import RDatasets singers = RDatasets.dataset("lattice","singer") -violin(singers,:VoicePart,:Height,marker=(0.2,:blue,stroke(0))) -boxplot!(singers,:VoicePart,:Height,marker=(0.3,:orange,stroke(2))) +@df singers begin + violin(:VoicePart,:Height,marker=(0.2,:blue,stroke(0))) + boxplot!(:VoicePart,:Height,marker=(0.3,:orange,stroke(2))) +end ``` ![](https://juliaplots.github.io/examples/img/pyplot/pyplot_example_30.png) @@ -100,8 +113,8 @@ Asymmetric violin plots can be created using the `side` keyword (`:both` - defau ```julia singers_moscow = deepcopy(singers) singers_moscow[:Height] = singers_moscow[:Height]+5 -myPlot = violin(singers,:VoicePart,:Height, side=:right, marker=(0.2,:blue,stroke(0)), label="Scala") -violin!(singers_moscow,:VoicePart,:Height, side=:left, marker=(0.2,:red,stroke(0)), label="Moscow") +@df singers violin(:VoicePart,:Height, side=:right, marker=(0.2,:blue,stroke(0)), label="Scala") +@df singers_moscow violin!(:VoicePart,:Height, side=:left, marker=(0.2,:red,stroke(0)), label="Moscow") ``` ![](https://cloud.githubusercontent.com/assets/2077159/26156938/22ccf0d4-3b18-11e7-9f34-555005437e6c.png) diff --git a/src/df.jl b/src/df.jl index 8799da8..aae7e48 100644 --- a/src/df.jl +++ b/src/df.jl @@ -9,17 +9,52 @@ If you want to avoid replacing the symbol, escape it with `^`. for strings and symbols respectively. """ macro df(d, x) - esc(_df(d,x)) + argnames = _argnames(d, x) + plot_call = _df(d,x) + for i in 1:length(argnames) + if isa(argnames[i], Expr) + push!(plot_call.args, Expr(:kw, :label, argnames[i])) + else + push!(plot_call.args, Expr(:kw, kw_list[i], argnames[i])) + end + end + esc(plot_call) end _df(d, x) = x function _df(d, x::Expr) (x.head == :quote) && return :(StatPlots.select_column($d, $x)) - (x.head == :call) && x.args[1] == :^ && length(x.args) == 2 && return x.args[2] + if x.head == :call + x.args[1] == :^ && length(x.args) == 2 && return x.args[2] + x.args[1] == :cols && return :(hcat((convert_column($d[i]) for i in $(x.args[2]))...)) + end return Expr(x.head, _df.(d, x.args)...) end +const kw_list = [:xlabel, :ylabel, :zlabel] + +macro argnames(d, x) + esc(_argnames(d, x)) +end + +not_kw(x) = true +not_kw(x::Expr) = !(x.head in [:kw, :parameters]) + +arg2string(x) = string(x) +function arg2string(d, x) + if isa(x, Expr) && x.head == :call && x.args[1] == :cols + return :(reshape([(DataFrames.names($d)[i]) for i in $(x.args[2])], 1, :)) + else + return filter(t -> t != ':', string(x)) + end +end + +function _argnames(d, x::Expr) + [arg2string(d, s) for s in x.args[2:end] if not_kw(s)] +end + + select_column(df, s) = haskey(df, s) ? convert_column(df[s]) : s convert_column(col) = col From 27d8347d42c104ee067786f28a8d03ce0d81343c Mon Sep 17 00:00:00 2001 From: Pietro Vertechi Date: Tue, 29 Aug 2017 16:04:14 +0100 Subject: [PATCH 4/9] set labels --- src/df.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/df.jl b/src/df.jl index aae7e48..4fc09c5 100644 --- a/src/df.jl +++ b/src/df.jl @@ -12,7 +12,7 @@ macro df(d, x) argnames = _argnames(d, x) plot_call = _df(d,x) for i in 1:length(argnames) - if isa(argnames[i], Expr) + if isa(argnames[i], Expr) || isa(argnames[i], AbstractArray) push!(plot_call.args, Expr(:kw, :label, argnames[i])) else push!(plot_call.args, Expr(:kw, kw_list[i], argnames[i])) @@ -45,6 +45,10 @@ arg2string(x) = string(x) function arg2string(d, x) if isa(x, Expr) && x.head == :call && x.args[1] == :cols return :(reshape([(DataFrames.names($d)[i]) for i in $(x.args[2])], 1, :)) + elseif isa(x, Expr) && x.head == :call && x.args[1] == :hcat + return hcat.((filter(t -> t != ':', string(s)) for s in x.args[2:end])...) + elseif isa(x, Expr) && x.head == :hcat + return hcat.((filter(t -> t != ':', string(s)) for s in x.args)...) else return filter(t -> t != ':', string(x)) end From ddd792b2d5ea53bdd6db75056db38e4655eaf0f9 Mon Sep 17 00:00:00 2001 From: Pietro Vertechi Date: Tue, 29 Aug 2017 16:25:43 +0100 Subject: [PATCH 5/9] wip add labels --- src/df.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/df.jl b/src/df.jl index 4fc09c5..9d82111 100644 --- a/src/df.jl +++ b/src/df.jl @@ -13,9 +13,9 @@ macro df(d, x) plot_call = _df(d,x) for i in 1:length(argnames) if isa(argnames[i], Expr) || isa(argnames[i], AbstractArray) - push!(plot_call.args, Expr(:kw, :label, argnames[i])) + insert_kw!(plot_call, :label, argnames[i]) else - push!(plot_call.args, Expr(:kw, kw_list[i], argnames[i])) + insert_kw!(plot_call, kw_list[i], argnames[i]) end end esc(plot_call) @@ -32,16 +32,16 @@ function _df(d, x::Expr) return Expr(x.head, _df.(d, x.args)...) end -const kw_list = [:xlabel, :ylabel, :zlabel] - -macro argnames(d, x) - esc(_argnames(d, x)) +function insert_kw!(x::Expr, s::Symbol, v) + index = x.args[2].head == :parameters ? 3 : 2 + x.args = vcat(x.args[1:index-1], Expr(:kw, s, v), x.args[index:end]) end +const kw_list = [:xlabel, :ylabel, :zlabel] + not_kw(x) = true not_kw(x::Expr) = !(x.head in [:kw, :parameters]) -arg2string(x) = string(x) function arg2string(d, x) if isa(x, Expr) && x.head == :call && x.args[1] == :cols return :(reshape([(DataFrames.names($d)[i]) for i in $(x.args[2])], 1, :)) From 7e03043aa7e0f54857152e731a782063f73604f3 Mon Sep 17 00:00:00 2001 From: Pietro Vertechi Date: Tue, 29 Aug 2017 16:36:22 +0100 Subject: [PATCH 6/9] code cleanup --- src/df.jl | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/df.jl b/src/df.jl index 9d82111..cdc8a8f 100644 --- a/src/df.jl +++ b/src/df.jl @@ -39,24 +39,27 @@ end const kw_list = [:xlabel, :ylabel, :zlabel] +function _argnames(d, x::Expr) + [_arg2string(d, s) for s in x.args[2:end] if not_kw(s)] +end + not_kw(x) = true not_kw(x::Expr) = !(x.head in [:kw, :parameters]) -function arg2string(d, x) - if isa(x, Expr) && x.head == :call && x.args[1] == :cols +_arg2string(d, x) = stringify(x) +function _arg2string(d, x::Expr) + if x.head == :call && x.args[1] == :cols return :(reshape([(DataFrames.names($d)[i]) for i in $(x.args[2])], 1, :)) - elseif isa(x, Expr) && x.head == :call && x.args[1] == :hcat - return hcat.((filter(t -> t != ':', string(s)) for s in x.args[2:end])...) - elseif isa(x, Expr) && x.head == :hcat - return hcat.((filter(t -> t != ':', string(s)) for s in x.args)...) + elseif x.head == :call && x.args[1] == :hcat + return hcat(stringify.(x.args[2:end])...) + elseif x.head == :hcat + return hcat(stringify.(x.args)...) else - return filter(t -> t != ':', string(x)) + return stringify(x) end end -function _argnames(d, x::Expr) - [arg2string(d, s) for s in x.args[2:end] if not_kw(s)] -end +stringify(x) = filter(t -> t != ':', string(x)) select_column(df, s) = haskey(df, s) ? convert_column(df[s]) : s From d13dbbeea81278992e9ea6f0860499ba5371f504 Mon Sep 17 00:00:00 2001 From: Pietro Vertechi Date: Tue, 29 Aug 2017 16:45:44 +0100 Subject: [PATCH 7/9] updated README --- README.md | 13 ++++++------- src/df.jl | 3 +-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b7a645d..587713a 100644 --- a/README.md +++ b/README.md @@ -60,14 +60,15 @@ iris = dataset("datasets","iris") ## corrplot and cornerplot ```julia -@df iris corrplot([:SepalLength :SepalWidth :PetalLength :PetalWidth]) +@df iris corrplot([:SepalLength :SepalWidth :PetalLength :PetalWidth], grid = false) ``` or also: ```julia -@df iris corrplot(cols(1:4)) +@df iris corrplot(cols(1:4), grid = false) ``` -![iris](https://user-images.githubusercontent.com/6333339/29792101-3daa6cfe-8c37-11e7-97e2-d1763d3b95c3.png) +![corrplot](https://user-images.githubusercontent.com/6333339/29830744-fd1fe282-8cda-11e7-86aa-ff5e8033a693.png) + A correlation plot may also be produced from a matrix: @@ -100,10 +101,8 @@ cornerplot(M, compact=true) ```julia import RDatasets singers = RDatasets.dataset("lattice","singer") -@df singers begin - violin(:VoicePart,:Height,marker=(0.2,:blue,stroke(0))) - boxplot!(:VoicePart,:Height,marker=(0.3,:orange,stroke(2))) -end +@df singers violin(:VoicePart,:Height,marker=(0.2,:blue,stroke(0))) +@df singers boxplot!(:VoicePart,:Height,marker=(0.3,:orange,stroke(2))) ``` ![](https://juliaplots.github.io/examples/img/pyplot/pyplot_example_30.png) diff --git a/src/df.jl b/src/df.jl index cdc8a8f..79dd1ac 100644 --- a/src/df.jl +++ b/src/df.jl @@ -37,7 +37,7 @@ function insert_kw!(x::Expr, s::Symbol, v) x.args = vcat(x.args[1:index-1], Expr(:kw, s, v), x.args[index:end]) end -const kw_list = [:xlabel, :ylabel, :zlabel] +const kw_list = [:xguide, :yguide, :zguide] function _argnames(d, x::Expr) [_arg2string(d, s) for s in x.args[2:end] if not_kw(s)] @@ -61,7 +61,6 @@ end stringify(x) = filter(t -> t != ':', string(x)) - select_column(df, s) = haskey(df, s) ? convert_column(df[s]) : s convert_column(col) = col From 973dc72aec5ac9f02799d5370881395ed1b6d651 Mon Sep 17 00:00:00 2001 From: Pietro Vertechi Date: Wed, 30 Aug 2017 12:24:13 +0100 Subject: [PATCH 8/9] removed labelling --- README.md | 8 ++++---- src/df.jl | 18 +----------------- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 587713a..2a9e017 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ iris = dataset("datasets","iris") @df iris marginalhist(:PetalLength, :PetalWidth) ``` -![](https://cloud.githubusercontent.com/assets/933338/19213780/a82e34a6-8d42-11e6-8846-80c9f4c48b9c.png) +![marginalhist](https://user-images.githubusercontent.com/6333339/29869938-fbe08d02-8d7c-11e7-9409-ca47ee3aaf35.png) --- @@ -67,7 +67,7 @@ or also: @df iris corrplot(cols(1:4), grid = false) ``` -![corrplot](https://user-images.githubusercontent.com/6333339/29830744-fd1fe282-8cda-11e7-86aa-ff5e8033a693.png) +![corrplot](https://user-images.githubusercontent.com/6333339/29870023-7b07b010-8d7d-11e7-901c-3ef9a6af78bb.png) A correlation plot may also be produced from a matrix: @@ -105,7 +105,7 @@ singers = RDatasets.dataset("lattice","singer") @df singers boxplot!(:VoicePart,:Height,marker=(0.3,:orange,stroke(2))) ``` -![](https://juliaplots.github.io/examples/img/pyplot/pyplot_example_30.png) +![violin](https://user-images.githubusercontent.com/6333339/29870077-b4242e32-8d7d-11e7-9b18-40a57360936d.png) Asymmetric violin plots can be created using the `side` keyword (`:both` - default,`:right` or `:left`), e.g.: @@ -116,7 +116,7 @@ singers_moscow[:Height] = singers_moscow[:Height]+5 @df singers_moscow violin!(:VoicePart,:Height, side=:left, marker=(0.2,:red,stroke(0)), label="Moscow") ``` -![](https://cloud.githubusercontent.com/assets/2077159/26156938/22ccf0d4-3b18-11e7-9f34-555005437e6c.png) +![2violin](https://user-images.githubusercontent.com/6333339/29870110-d90ed468-8d7d-11e7-8ebb-008323dff8b8.png) --- diff --git a/src/df.jl b/src/df.jl index 79dd1ac..37acb9c 100644 --- a/src/df.jl +++ b/src/df.jl @@ -9,16 +9,7 @@ If you want to avoid replacing the symbol, escape it with `^`. for strings and symbols respectively. """ macro df(d, x) - argnames = _argnames(d, x) - plot_call = _df(d,x) - for i in 1:length(argnames) - if isa(argnames[i], Expr) || isa(argnames[i], AbstractArray) - insert_kw!(plot_call, :label, argnames[i]) - else - insert_kw!(plot_call, kw_list[i], argnames[i]) - end - end - esc(plot_call) + esc(_df(d,x)) end _df(d, x) = x @@ -32,13 +23,6 @@ function _df(d, x::Expr) return Expr(x.head, _df.(d, x.args)...) end -function insert_kw!(x::Expr, s::Symbol, v) - index = x.args[2].head == :parameters ? 3 : 2 - x.args = vcat(x.args[1:index-1], Expr(:kw, s, v), x.args[index:end]) -end - -const kw_list = [:xguide, :yguide, :zguide] - function _argnames(d, x::Expr) [_arg2string(d, s) for s in x.args[2:end] if not_kw(s)] end From 0f0ed3765607cc20ebbf0643d427b2d87bb0209c Mon Sep 17 00:00:00 2001 From: Pietro Vertechi Date: Wed, 30 Aug 2017 12:53:46 +0100 Subject: [PATCH 9/9] fix cols --- src/df.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/df.jl b/src/df.jl index 37acb9c..fdb9b24 100644 --- a/src/df.jl +++ b/src/df.jl @@ -18,7 +18,7 @@ function _df(d, x::Expr) (x.head == :quote) && return :(StatPlots.select_column($d, $x)) if x.head == :call x.args[1] == :^ && length(x.args) == 2 && return x.args[2] - x.args[1] == :cols && return :(hcat((convert_column($d[i]) for i in $(x.args[2]))...)) + x.args[1] == :cols && return :(hcat((StatPlots.convert_column($d[i]) for i in $(x.args[2]))...)) end return Expr(x.head, _df.(d, x.args)...) end