In Julia, one can draw a boxplot using StatsPlots.jl. Assuming There is a DataFrame named df, we can draw a boxplot for one of its columns named a by this:
julia> #df df boxplot(["a"], :a, fillalpha=0.75, linewidth=2)
I want to put the same structure in a function:
julia> function BoxPlotColumn(col::Union{Symbol, String}, df::DataFrame)
if isa(col, String)
#df df boxplot([col], Symbol(col), fillalpha=0.75, linewidth=2)
else
#df df boxplot([String(col)], col, fillalpha=0.75, linewidth=2)
end
end
BoxPlotColumn (generic function with 1 method)
Then, if I say BoxPlotColumn("a", df), Julia throws an error:
ERROR: Cannot convert Symbol to series data for plotting
Stacktrace:
[1] error(s::String)
# Base .\error.jl:35
[2] _prepare_series_data(x::Symbol)
# RecipesPipeline C:\Users\Shayan\.julia\packages\RecipesPipeline\OXGmH\src\series.jl:8
[3] _series_data_vector(x::Symbol, plotattributes::Dict{Symbol, Any})
# RecipesPipeline C:\Users\Shayan\.julia\packages\RecipesPipeline\OXGmH\src\series.jl:35
[4] macro expansion
# C:\Users\Shayan\.julia\packages\RecipesPipeline\OXGmH\src\series.jl:135 [inlined]
[5] apply_recipe(plotattributes::AbstractDict{Symbol, Any}, #unused#::Type{RecipesPipeline.SliceIt}, x::Any, y::Any, z::Any)
# RecipesPipeline C:\Users\Shayan\.julia\packages\RecipesBase\qpxEX\src\RecipesBase.jl:289
[6] _process_userrecipes!(plt::Any, plotattributes::Any, args::Any)
# RecipesPipeline C:\Users\Shayan\.julia\packages\RecipesPipeline\OXGmH\src\user_recipe.jl:36
[7] recipe_pipeline!(plt::Any, plotattributes::Any, args::Any)
# RecipesPipeline C:\Users\Shayan\.julia\packages\RecipesPipeline\OXGmH\src\RecipesPipeline.jl:70
[8] _plot!(plt::Plots.Plot, plotattributes::Any, args::Any)
# Plots C:\Users\Shayan\.julia\packages\Plots\lW9ll\src\plot.jl:209
[9] #plot#145
# C:\Users\Shayan\.julia\packages\Plots\lW9ll\src\plot.jl:91 [inlined]
[10] boxplot(::Any, ::Vararg{Any}; kw::Base.Pairs{Symbol, V, Tuple{Vararg{Symbol, N}}, NamedTuple{names, T}} where {V, N, names, T<:Tuple{Vararg{Any, N}}})
# Plots C:\Users\Shayan\.julia\packages\RecipesBase\qpxEX\src\RecipesBase.jl:410
[11] add_label(::Vector{String}, ::typeof(boxplot), ::Vector{String}, ::Vararg{Any}; kwargs::Base.Pairs{Symbol, Real, Tuple{Symbol, Symbol}, NamedTuple{(:fillalpha, :linewidth), Tuple{Float64, Int64}}}) # StatsPlots C:\Users\Shayan\.julia\packages\StatsPlots\faFN5\src\df.jl:153
[12] (::var"#33#34"{String})(349::DataFrame)
# Main .\none:0
[13] BoxPlotColumn(col::String, df::DataFrame)
# Main c:\Users\Shayan\Documents\Python Scripts\test2.jl:15
[14] top-level scope
# c:\Users\Shayan\Documents\Python Scripts\test2.jl:22
Which is because of this : #df df boxplot([col], Symbol(col), fillalpha=0.75, linewidth=2)
How can I fix this? Why does this happen? I wrote the same thing just in a function.
I wrote the same thing just in a function.
You have not written the same thing. In your original code you use string and Symbol literals, and in function you pass a variable. This is the key difference.
To fix this I recommend you to use #with from DataFramesMeta.jl:
BoxPlotColumn(col::Union{Symbol, String}, df::DataFrame) =
#with df boxplot([string(col)], $col, fillalpha=0.75, linewidth=2)
which does what you want, as #with supports working with column names programmatically with $.
EDIT
Why Julia doesn't operate when we say boxplot(..., col, ...)
It does not operate because both #df and #which are macros. Since they are macros they transform code into other code that is only later executed. These macros are designed in a way that when they see a symbol literal, e.g. :a they treat it in a special way and consider it to be a column of a data frame. When they see a variable col they cannot know that this variable points to a symbol as the macro is executed before code is evaluated (remember - macro is a method to transform code into other code before this code is executed). See https://docs.julialang.org/en/v1/manual/metaprogramming/#man-macros
MethodError: no method matching isfinite(::String15)
Most likely you have a column with strings not numbers, instead write e.g. names(df, Real) to only get a list of columns that store real numbers (without missing). If you want to allow missing then write names(df, Union{Missing,Real}).
Related
I'm trying to call this macro from within a method where the parameters to the macro are passed in to the method. It works fine when I call it directly but there is something about the macro expansion which is preventing the variables lat and lon from being correctly used in the macro.
The macro I'm calling is #select here: https://github.com/Alexander-Barth/NCDatasets.jl/blob/4e35e843a53cdcff7f7ef66ebc3ceab1ee1e860b/src/select.jl#L54-L168
and here is the function where the lat and lon variables are not being expanded correctly
function data_for_lat_lon(ds, region, lat_lon_pair)
println("have latlon piar ", lat_lon_pair)
lat = lat_lon_pair[1]
lon = lat_lon_pair[2]
data = []
if(ArchGDAL.contains(region[1], ArchGDAL.createpoint(lon, lat)))
println(lat, " ", lon)
#the below call fails when called in this way
single_lat_lon = NCDatasets.#select(ds, latitude==$lat && longitude==$lon)
for (varname, var) in single_lat_lon
if varname in ["latitude", "longitude", "time"]
continue
end
push!(var_names, varname)
push!(data, Array[single_lat_lon[varname]][1][:])
end
return reduce(hcat, data)'
end
end
This is the error & stack trace I get when calling it:
MethodError: no method matching (::NCDatasets.var"#154#155")(::Float64)
The applicable method may be too new: running in world age 32645, while current world is 32646.
Closest candidates are:
(::NCDatasets.var"#154#155")(::Any) at none:0 (method too new to be called from this world context.)
Stacktrace:
[1] _broadcast_getindex_evalf
# .\broadcast.jl:670 [inlined]
[2] _broadcast_getindex
# .\broadcast.jl:643 [inlined]
[3] getindex
# .\broadcast.jl:597 [inlined]
[4] copy
# .\broadcast.jl:899 [inlined]
[5] materialize
# .\broadcast.jl:860 [inlined]
[6] findall(testf::NCDatasets.var"#154#155", A::Vector{Float64})
# Base .\array.jl:2311
[7] macro expansion
# C:\Users\scott\.julia\packages\NCDatasets\sLdiM\src\select.jl:242 [inlined]
[8] data_for_lat_lon(ds::NCDatasets.MFDataset{DeferDataset, 1, String, NCDatasets.DeferAttributes, NCDatasets.DeferDimensions, NCDatasets.DeferGroups}, region::DataFrameRow{DataFrame, DataFrames.Index}, lat_lon_pair::Tuple{Float64, Float64})
# Main .\In[46]:8
[9] top-level scope
# .\In[50]:3
[10] eval
# .\boot.jl:368 [inlined]
[11] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
# Base .\loading.jl:1428
I'm fairly confident this has something to do with the concept of "hygiene" and variable expansion with a macro but I'm new enough to julia to not understand what needs to be done in my calling function to resolve this. I have reveiwed this question but am not sure it applies to this case: How to pass variable value to a macro in julia?
Thanks!
Turns out the issue was unrelated and was a known issue in julia: https://discourse.julialang.org/t/how-to-bypass-the-world-age-problem/7012
I am having trouble plotting in Julia. After uploading an excel file into a data frame and plotting, I the error: 'Cannot Convert Dataframe to series data for plotting'.
For reference the simplest code to trigger error:
using Plots, XLSX, DataFrames
Colbalt = DataFrame(XLSX.readtable("/Users/jjtan/Downloads/statisticsExport.xlsx", "Sheet 1"))
plot(Colbalt, x = :Year, y = :Total)
and the stack trace:
ERROR: Cannot convert DataFrame to series data for plotting
Stacktrace:
[1] error(s::String)
# Base ./error.jl:35
[2] _prepare_series_data(x::DataFrame)
# RecipesPipeline ~/.julia/packages/RecipesPipeline/F2mWY/src/series.jl:8
[3] _series_data_vector(x::DataFrame, plotattributes::Dict{Symbol, Any})
# RecipesPipeline ~/.julia/packages/RecipesPipeline/F2mWY/src/series.jl:27
[4] macro expansion
# ~/.julia/packages/RecipesPipeline/F2mWY/src/series.jl:127 [inlined]
[5] apply_recipe(plotattributes::AbstractDict{Symbol, Any}, #unused#::Type{RecipesPipeline.SliceIt}, x::Any, y::Any, z::Any)
# RecipesPipeline ~/.julia/packages/RecipesBase/apcHH/src/RecipesBase.jl:290
[6] _process_userrecipes!(plt::Any, plotattributes::Any, args::Any)
# RecipesPipeline ~/.julia/packages/RecipesPipeline/F2mWY/src/user_recipe.jl:36
[7] recipe_pipeline!(plt::Any, plotattributes::Any, args::Any)
# RecipesPipeline ~/.julia/packages/RecipesPipeline/F2mWY/src/RecipesPipeline.jl:70
[8] _plot!(plt::Plots.Plot, plotattributes::Any, args::Any)
# Plots ~/.julia/packages/Plots/W75kY/src/plot.jl:232
[9] #plot#149
# ~/.julia/packages/Plots/W75kY/src/plot.jl:107 [inlined]
[10] top-level scope
# ~/Documents/Research/Grants/CDFA-ANALYSIS/##Packgaes.jl:9
Where did you find that syntax? As the error says, you cannot directly plot a DataFrame object. Instead, pass the columns you want to plot as vectors:
plot(Colbalt.Year, Colbalt.Total)
The StatsPlots extention of Plots offers some convenience macros for plotting DataFrames, here are some examples from the Readme:
using DataFrames
df = DataFrame(a = 1:10, b = 10 .* rand(10), c = 10 .* rand(10))
#df df plot(:a, [:b :c], colour = [:red :blue])
#df df scatter(:a, :b, markersize = 4 .* log.(:c .+ 0.1))
I have a simple simulation and I want to plot errors on 3 distinct figures. To speed things up I wanted to introduce a little bit of parallel computing.
Threads.#threads for i in 1:3
plt = plot(t, err[:, i], linecolor=:blue, label=[""], linewidth=3)
hline!(plt, [0], linestyle=:dash, linecolor=:black, label=[""])
xlabel!(L"t")
ylabel!(L"e_%$i")
savefig(plt, "fig/Staubli_Slotine_Li$i.pdf")
end
Everything stops working when I try running julia with -t3 flag
ERROR: LoadError: TaskFailedException
Stacktrace:
[1] wait
# ./task.jl:334 [inlined]
[2] threading_run(func::Function)
# Base.Threads ./threadingconstructs.jl:38
[3] top-level scope
# ./threadingconstructs.jl:97
nested task error: KeyError: key :annotations not found
Stacktrace:
[1] pop!(h::Dict{Symbol, Any}, key::Symbol)
# Base ./dict.jl:587
[2] pop_kw!(dd::RecipesPipeline.DefaultsDict, k::Symbol)
# RecipesPipeline ~/.julia/packages/RecipesPipeline/F2mWY/src/utils.jl:57
[3] _update_subplot_args(plt::Plots.Plot{Plots.PGFPlotsXBackend}, sp::Plots.Subplot{Plots.PGFPlotsXBackend}, plotattributes_in::Dict{Symbol, Any}, subplot_index::Int64, remove_pair::Bool)
# Plots ~/.julia/packages/Plots/nzdhU/src/args.jl:2058
[4] _subplot_setup(plt::Plots.Plot{Plots.PGFPlotsXBackend}, plotattributes::Dict{Symbol, Any}, kw_list::Vector{Dict{Symbol, Any}})
# Plots ~/.julia/packages/Plots/nzdhU/src/pipeline.jl:277
[5] plot_setup!(plt::Plots.Plot{Plots.PGFPlotsXBackend}, plotattributes::Dict{Symbol, Any}, kw_list::Vector{Dict{Symbol, Any}})
# Plots ~/.julia/packages/Plots/nzdhU/src/pipeline.jl:138
[6] recipe_pipeline!(plt::Any, plotattributes::Any, args::Any)
# RecipesPipeline ~/.julia/packages/RecipesPipeline/F2mWY/src/RecipesPipeline.jl:87
[7] _plot!(plt::Plots.Plot, plotattributes::Any, args::Any)
# Plots ~/.julia/packages/Plots/nzdhU/src/plot.jl:208
[8] plot!(::Plots.Plot; kw::Base.Pairs{Symbol, V, Tuple{Vararg{Symbol, N}}, NamedTuple{names, T}} where {V, N, names, T<:Tuple{Vararg{Any, N}}})
# Plots ~/.julia/packages/Plots/nzdhU/src/plot.jl:198
[9] plot!(; kw::Base.Pairs{Symbol, V, Tuple{Vararg{Symbol, N}}, NamedTuple{names, T}} where {V, N, names, T<:Tuple{Vararg{Any, N}}})
# Plots ~/.julia/packages/Plots/nzdhU/src/plot.jl:188
[10] #xlabel!#484
# ~/.julia/packages/Plots/nzdhU/src/shorthands.jl:416 [inlined]
[11] xlabel!
# ~/.julia/packages/Plots/nzdhU/src/shorthands.jl:416 [inlined]
[12] macro expansion
# ~/Documents/studia/master_thesis/master_thesis_code/sym_scripts/Staubli_Slotine_Li.jl:29 [inlined]
[13] (::var"#88#threadsfor_fun#1"{UnitRange{Int64}})(onethread::Bool)
# Main ./threadingconstructs.jl:85
[14] (::var"#88#threadsfor_fun#1"{UnitRange{Int64}})()
# Main ./threadingconstructs.jl:52
in expression starting at /home/jcebulsk/Documents/studia/master_thesis/master_thesis_code/sym_scripts/Staubli_Slotine_Li.jl:26
If I comment out hline! the script runs without any issue.
It looks like I can't have both hline and parallel operation.
On my machine I am getting an EXCEPTION_ACCESS_VIOLATION which is even uglier. For sure in your code xlabel!(L"t") and ylabel!(L"e_%$i") mutate the global state which is very bad for any kind parallelism and should be xlabel!(plt, L"t") and ylabel!(plt, L"e_%$i").
However, in many scenarios this might still not work because Plots.jl is also maintaining it's global state and it might be not thread safe. Hence the best way to go forward is through distributed computing:
using Distributed
addprocs(3)
#everywhere using Plots, LaTeXStrings
Distributed.#distributed for i in 1:3
plt = plot(t, err[:, i], linecolor=:blue, label=[""], linewidth=3)
hline!(plt, [0], linestyle=:dash, linecolor=:black, label=[""])
xlabel!(L"t")
ylabel!(L"e_%$i")
savefig(plt, "fig/Staubli_Slotine_Li$i.pdf")
end
Note that you will have a better performance if the plots are very complex as each of those subprocesses will experience the "Julia time to first plot" issue.
Hence before going multi-process you might want to parametrize the GR backend and see if the performance is sufficient:
using Plots
ENV["GKSwstype"]="nul" # this parameter significantly speeds up generation of GR-based plots
gr()
I'm learning julia from https://docs.juliaplots.org/latest/basics/
Here is the julia code
using Plots,UnicodePlots,RDatasets
unicodeplots()
v=dataset("Ecdat","Airline")
typeof(v)
plot(v, :Cost) # this line will produce the error
plot(v) # this line will produce the error, too
I got
ERROR: Cannot convert DataFrame to series data for plotting
Stacktrace:
[1] prepareSeriesData(::DataFrame) at /home/dlin/.julia/packages/Plots/qZHsp/src/series.jl:14
[2] convertToAnyVector(::DataFrame, ::Dict{Symbol,Any}) at /home/dlin/.julia/packages/Plots/qZHsp/src/series.jl:26
[3] macro expansion at /home/dlin/.julia/packages/Plots/qZHsp/src/series.jl:130 [inlined]
[4] apply_recipe(::Dict{Symbol,Any}, ::Type{Plots.SliceIt}, ::Nothing, ::DataFrame, ::Nothing) at /home/dlin/.julia/packages/RecipesBase/zBoFG/src/RecipesBase.jl:275
[5] _process_userrecipes(::Plots.Plot{Plots.GRBackend}, ::Dict{Symbol,Any}, ::Tuple{DataFrame}) at /home/dlin/.julia/packages/Plots/qZHsp/src/pipeline.jl:83
[6] _plot!(::Plots.Plot{Plots.GRBackend}, ::Dict{Symbol,Any}, ::Tuple{DataFrame}) at /home/dlin/.julia/packages/Plots/qZHsp/src/plot.jl:178
[7] #plot#138(::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}, ::typeof(plot), ::DataFrame) at /home/dlin/.julia/packages/Plots/qZHsp/src/plot.jl:57
[8] plot(::DataFrame) at /home/dlin/.julia/packages/Plots/qZHsp/src/plot.jl:51
[9] top-level scope at REPL[4]:1
In the code of series.jl, it is
prepareSeriesData(x) = error("Cannot convert $(typeof(x)) to series data for plotting")
How to correct this?
What about simply using plot(v.Cost):
julia> plot(v.Cost)
┌────────────────────────────────────────────────────────────┐
4.88870026e6 │⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⣠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ y1
│⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⠀⠀⠀⠀⠀⠀⢰⢹⠀⠀⠀⠀⠀⠀⠀⠀⢀⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⠀⠀⠀⠀⠀⠀⡎⢸⠀⠀⠀⠀⠀⠀⠀⠀⡸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⠀⠀⠀⠀⢀⠎⠀⢸⠀⠀⠀⠀⠀⠀⠀⢠⠃⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⠀⠀⠀⠀⢸⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⡇⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⠀⠀⠀⠀⡎⠀⠀⢸⠀⠀⠀⠀⠀⠀⢸⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⠀⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⠀⠀⠀⡎⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⠀⠀⠀⢰⠁⠀⠀⢸⠀⠀⠀⠀⠀⠀⡇⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⠀⠀⠀⡸⠀⠀⠀⢸⠀⠀⠀⠀⠀⢰⠁⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⠀⠀⢀⠇⠀⠀⠀⢸⠀⠀⠀⠀⠀⢸⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⠀⠀⡼⠀⠀⠀⠀⠈⡆⠀⠀⠀⠀⡇⠀⠀⠀⢣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⠀⡰⠁⠀⠀⠀⠀⠀⡇⠀⠀⠀⢰⠁⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⡰⠁⠀⠀⠀⠀⠀⠀⡇⠀⠀⢠⠇⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⡰⠁⠀⠀⠀⠀⠀⠀⠀⡇⠀⢀⠇⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⣀⠀⠀⠀⠀⠀⠀⠀⢀⠜⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⡎⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⢀⠜⠊⢸⠀⠀⠀⠀⠀⠀⢀⠎⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡄⠀│
│⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⡼⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⢠⠋⠀⠀⢸⠀⠀⠀⠀⠀⠀⡎⠀⠀⢱⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡜⠀⠀│
│⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠋⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⡠⠒⠁⠀⠀⠀⠀⡇⠀⠀⠀⢀⠜⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⡠⠔⢲⠀⠀⠀⠀⠀⠀⠀⡔⠁⠀⠀│
│⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠒⠊⠀⠀⠀⠀⠀⠀⠀⡇⡠⠖⠉⠁⠀⠀⠀⠀⢸⠀⠀⠀⢀⡠⠒⠁⠀⠈⡆⠀⠀⠀⠀⡠⠎⠀⠀⠀⠀│
-71402.26000000001 │⠤⡧⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠭⠤⠤⠤⠤⠤⠤⠤⠤⠼⠶⠾⠭⠥⠤⠤⠤⠤⠤⠷⠶⠾⠭⠭⠤⠤⠤⠤⠤⠤│
└────────────────────────────────────────────────────────────┘
-1.67 92.67
That part of the docs might be outdated. You have to use StatsPlots and the #df macro to get the desired behavior.
julia> using StatsPlots
julia> #df v plot(:Cost)
┌────────────────────────────────────────────────────────────┐
4.88870026e6 │⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⣠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│ y1
│⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⠀⠀⠀⠀⠀⠀⢰⢹⠀⠀⠀⠀⠀⠀⠀⠀⢀⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⠀⠀⠀⠀⠀⠀⡎⢸⠀⠀⠀⠀⠀⠀⠀⠀⡸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⠀⠀⠀⠀⢀⠎⠀⢸⠀⠀⠀⠀⠀⠀⠀⢠⠃⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⠀⠀⠀⠀⢸⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⡇⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⠀⠀⠀⠀⡎⠀⠀⢸⠀⠀⠀⠀⠀⠀⢸⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⠀⠀⠀⠀⡇⠀⠀⢸⠀⠀⠀⠀⠀⠀⡎⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⠀⠀⠀⢰⠁⠀⠀⢸⠀⠀⠀⠀⠀⠀⡇⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⠀⠀⠀⡸⠀⠀⠀⢸⠀⠀⠀⠀⠀⢰⠁⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⠀⠀⢀⠇⠀⠀⠀⢸⠀⠀⠀⠀⠀⢸⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⠀⠀⡼⠀⠀⠀⠀⠈⡆⠀⠀⠀⠀⡇⠀⠀⠀⢣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⠀⡰⠁⠀⠀⠀⠀⠀⡇⠀⠀⠀⢰⠁⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⠀⡰⠁⠀⠀⠀⠀⠀⠀⡇⠀⠀⢠⠇⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡇⡰⠁⠀⠀⠀⠀⠀⠀⠀⡇⠀⢀⠇⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⣀⠀⠀⠀⠀⠀⠀⠀⢀⠜⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⡎⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⢀⠜⠊⢸⠀⠀⠀⠀⠀⠀⢀⠎⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡄⠀│
│⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⡼⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⢠⠋⠀⠀⢸⠀⠀⠀⠀⠀⠀⡎⠀⠀⢱⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡜⠀⠀│
│⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠋⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⡠⠒⠁⠀⠀⠀⠀⡇⠀⠀⠀⢀⠜⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⡠⠔⢲⠀⠀⠀⠀⠀⠀⠀⡔⠁⠀⠀│
│⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠒⠊⠀⠀⠀⠀⠀⠀⠀⡇⡠⠖⠉⠁⠀⠀⠀⠀⢸⠀⠀⠀⢀⡠⠒⠁⠀⠈⡆⠀⠀⠀⠀⡠⠎⠀⠀⠀⠀│
-71402.26000000001 │⠤⡧⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠭⠤⠤⠤⠤⠤⠤⠤⠤⠼⠶⠾⠭⠥⠤⠤⠤⠤⠤⠷⠶⠾⠭⠭⠤⠤⠤⠤⠤⠤│
└────────────────────────────────────────────────────────────┘
-1.67 92.67
This is explained in later sections of the manual: https://docs.juliaplots.org/latest/input_data/#dataframes-support
I want to use the hasmethod function to find if an object t::T supports t[!, something] syntax.
The key is something can be of many types and I don't want to check them all, I just want a way to express that hasmethod(getindex, Tuple{T, typeof{!}, S}) regardless of what S is.
How do I do that?
I think the way to get the list of methods is:
methods(getindex, Tuple{Any, typeof(!), Any})
The only problem is that if the second argument allowed by getindex is a supertype of typeof(!) it will be also listed. I do not think it can be avoided though, as you cannot rule out that such getindex definition actually allows ! to be passed as a first argument.
For instance if no packages are loaded the following is the result of the call above:
julia> methods(getindex, Tuple{Any, typeof(!), Any})
# 10 methods for generic function "getindex":
[1] getindex(md::Markdown.MD, args...) in Markdown at C:\cygwin\home\Administrator\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.2\Markdown\src\parse\parse.jl:24
[2] getindex(r::Distributed.Future, args...) in Distributed at C:\cygwin\home\Administrator\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.2\Distributed\src\remotecall.jl:624
[3] getindex(::Type{Any}, vals...) in Base at array.jl:357
[4] getindex(::Type{T}, x, y) where T in Base at array.jl:353
[5] getindex(::Type{T}, vals...) where T in Base at array.jl:344
[6] getindex(A::SparseArrays.SparseMatrixCSC, i, ::Colon) in SparseArrays at C:\cygwin\home\Administrator\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.2\SparseArrays\src\sparsematrix.jl:1879
[7] getindex(A::AbstractArray, I...) in Base at abstractarray.jl:979
[8] getindex(t::AbstractDict, k1, k2, ks...) in Base at abstractdict.jl:476
[9] getindex(itr::Base.SkipMissing, I...) in Base at missing.jl:232
[10] getindex(r::Distributed.RemoteChannel, args...) in Distributed at C:\cygwin\home\Administrator\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.2\Distributed\src\remotecall.jl:626
All of the cases are the ones where getindex does not put restrictions on the second argument so you cannot rule out that they could possibly allow ! as a valid value.
But if e.g. you load DataFrames.jl and restrict the first argument to AbstractDataFrame you get:
julia> methods(getindex, Tuple{AbstractDataFrame, typeof(!), Any})
# 5 methods for generic function "getindex":
[1] getindex(df::DataFrame, ::typeof(!), col_ind::Symbol) in DataFrames at D:\AppData\.julia\packages\DataFrames\yH0f6\src\dataframe\dataframe.jl:367
[2] getindex(df::DataFrame, ::typeof(!), col_ind::Union{Signed, Unsigned}) in DataFrames at D:\AppData\.julia\packages\DataFrames\yH0f6\src\dataframe\dataframe.jl:358
[3] getindex(df::DataFrame, row_ind::typeof(!), col_inds::Union{Colon, Regex, AbstractArray{T,1} where T, All, Between, InvertedIndex}) in DataFrames at D:\AppData\.julia\packages\DataFrames\yH0f6\src\dataframe\dataframe.jl:405
[4] getindex(sdf::SubDataFrame, ::typeof(!), colind::Union{Signed, Symbol, Unsigned}) in DataFrames at D:\AppData\.julia\packages\DataFrames\yH0f6\src\subdataframe\subdataframe.jl:127
[5] getindex(df::SubDataFrame, row_ind::typeof(!), col_inds::Union{Colon, Regex, AbstractArray{T,1} where T, All, Between, InvertedIndex}) in DataFrames at D:\AppData\.julia\packages\DataFrames\yH0f6\src\subdataframe\subdataframe.jl:137
which is now more informative, because in DataFrames.jl we try to be careful not to leave indexing arguments free (i.e. allow them to be Any and only internally check what is valid).
Additionally you can use methodswith to check which methods accept ! explicitly and filter only getindex instances. Here is the result after loading DataFrames.jl:
julia> filter(x -> x.name == :getindex, methodswith(typeof(!)))
[1] getindex(df::DataFrame, ::typeof(!), col_ind::Symbol) in DataFrames at D:\AppData\.julia\packages\DataFrames\yH0f6\src\dataframe\dataframe.jl:367
[2] getindex(df::DataFrame, ::typeof(!), col_ind::Union{Signed, Unsigned}) in DataFrames at D:\AppData\.julia\packages\DataFrames\yH0f6\src\dataframe\dataframe.jl:358
[3] getindex(df::DataFrame, row_ind::typeof(!), col_inds::Union{Colon, Regex, AbstractArray{T,1} where T, All, Between, InvertedIndex}) in DataFrames at D:\AppData\.julia\packages\DataFrames\yH0f6\src\dataframe\dataframe.jl:405
[4] getindex(sdf::SubDataFrame, ::typeof(!), colind::Union{Signed, Symbol, Unsigned}) in DataFrames at D:\AppData\.julia\packages\DataFrames\yH0f6\src\subdataframe\subdataframe.jl:127
[5] getindex(df::SubDataFrame, row_ind::typeof(!), col_inds::Union{Colon, Regex, AbstractArray{T,1} where T, All, Between, InvertedIndex}) in DataFrames at D:\AppData\.julia\packages\DataFrames\yH0f6\src\subdataframe\subdataframe.jl:137
Finally note that x[!, y] syntax can also mean a view (if preceded by e.g. #view) or a setindex! operation (if it appears on LHS of assignment) so you might wan to check also these functions if they accept ! (and in DataFrames.jl they actually do).
You can do that in theory, if you get the syntax right:
julia> hasmethod(getindex, Tuple{Vector{Int}, typeof(!), Any})
true
This should work since Tuples are covariant.
But its returning true is obviously nonsense:
julia> getindex([1], !, 1)
ERROR: ArgumentError: invalid index: ! of type typeof(!)
Stacktrace:
[1] to_index(::Function) at ./indices.jl:270
[2] to_index(::Array{Int64,1}, ::Function) at ./indices.jl:247
[3] to_indices(::Array{Int64,1}, ::Tuple{Base.OneTo{Int64}}, ::Tuple{typeof(!),Int64}) at ./indices.jl:298
[4] to_indices at ./indices.jl:294 [inlined]
[5] getindex(::Array{Int64,1}, ::Function, ::Int64) at ./abstractarray.jl:981
[6] top-level scope at REPL[26]:1
The reason is that it delegates to other methods internally.
I think it's better to constrain T to some abstract type(s) for which this kind of indexing is known to work. Or, as a last resort, use try.