Best library for visualizing time series

Hello,
I was wondering what is the best library for visualizing time series? I’m coming from Python, where I’m using matplotlib and seaborn. Tried Gadfly, but it’s not very prone customization – I couldn’t figure out how to format dates on the x-axis differently, or change the legend to show lines instead of coloured squares.

A popular choice for plotting is Makie.jl Plotting functions or Plots.jl Home · Plots (maybe together with GitHub - JuliaPlots/StatsPlots.jl: Statistical plotting recipes for Plots.jl )

I often use Makie, which could for sure do the tasks you talked about, see

1 Like

For Maike note that it’s not easy to have a date axis Date axis in Makie

Also see Implement time axis by SimonDanisch · Pull Request #1347 · JuliaPlots/Makie.jl · GitHub

Yes my main reason for not switching to Makie is that most of my data has a time dimensions and it’s not very convenient for that. Plots works generally well if you’ve got weekly/monthly/yearly data, and it’s also easy to just do something like xticks = Date(1990):Month(6):Date(2000) if you need specific ticks.

There’s PyPlot.jl and Seaborn.jl wrapping those, with Julia API. I’ve not used the latter (both use PyCall), in case something is missing in the wrapper(s), you can use PythonCall.jl (Seaborn is mentioned in its docs) to get all both with the Python API. That said, you may want to use Plots.jl which has matplotlib/PyPlot.jl as one of its backends, and Makie.jl which is likely the future of plotting in Julia. There’s a way to make it (or any plotting package) start up very fast.

Yes, that’s what I realized – date axes in Makie are not straightforward. Gadfly worked best for me, but formatting is a pain (I wanted dates with specific format “yyyymm” also at 45 degree angle). Defining x-ticks is OK, but I have to do it individually for every plot which is more annoying than just using Seaborn.

For some reason, I’m unable to make seaborn work with Julia data frames:

lineplot(data=slice, x=:date, y=:TVL_Share)
ERROR: (in a Julia function called from Python)
JULIA: AbstractDataFrame is not iterable. Use eachrow(df) to get a row iterator or eachcol(df) to get a column iterator
Stacktrace:
  [1] error(s::String)
    @ Base .\error.jl:35
  [2] iterate(#unused#::DataFrame)
    @ DataFrames C:\Users\mazoi\.julia\packages\DataFrames\zqFGs\src\abstractdataframe\iteration.jl:23
  [3] jlwrap_iterator(o::DataFrame)
    @ PyCall C:\Users\mazoi\.julia\packages\PyCall\ygXW2\src\pyiterator.jl:144
  [4] pyjlwrap_getiter(self_::Ptr{PyCall.PyObject_struct})
    @ PyCall C:\Users\mazoi\.julia\packages\PyCall\ygXW2\src\pyiterator.jl:125
  [5] macro expansion
    @ C:\Users\mazoi\.julia\packages\PyCall\ygXW2\src\exception.jl:95 [inlined]
  [6] #107
    @ C:\Users\mazoi\.julia\packages\PyCall\ygXW2\src\pyfncall.jl:43 [inlined]
  [7] disable_sigint
    @ .\c.jl:473 [inlined]
  [8] __pycall!
    @ C:\Users\mazoi\.julia\packages\PyCall\ygXW2\src\pyfncall.jl:42 [inlined]
  [9] _pycall!(ret::PyCall.PyObject, o::PyCall.PyObject, args::Tuple{}, nargs::Int64, kw::PyCall.PyObject)
    @ PyCall C:\Users\mazoi\.julia\packages\PyCall\ygXW2\src\pyfncall.jl:29
 [10] _pycall!(ret::PyCall.PyObject, o::PyCall.PyObject, args::Tuple{}, kwargs::Base.Pairs{Symbol, Any, Tuple{Symbol, Symbol, Symbol}, NamedTuple{(:data, :x, :y), Tuple{DataFrame, Symbol, Symbol}}})
    @ PyCall C:\Users\mazoi\.julia\packages\PyCall\ygXW2\src\pyfncall.jl:11
 [11] #_#114
    @ C:\Users\mazoi\.julia\packages\PyCall\ygXW2\src\pyfncall.jl:86 [inlined]
 [12] lineplot(; kwargs::Base.Pairs{Symbol, Any, Tuple{Symbol, Symbol, Symbol}, NamedTuple{(:data, :x, :y), Tuple{DataFrame, Symbol, Symbol}}})
    @ Seaborn C:\Users\mazoi\.julia\packages\Seaborn\ionLX\src\Seaborn.jl:112
 [13] top-level scope
    @ REPL[4]:1
 [14] eval
    @ .\boot.jl:368 [inlined]
 [15] eval
    @ .\Base.jl:65 [inlined]
 [16] repleval(m::Module, code::Expr, #unused#::String)
    @ VSCodeServer c:\Users\mazoi\.vscode\extensions\julialang.language-julia-1.7.6\scripts\packages\VSCodeServer\src\repl.jl:222
 [17] (::VSCodeServer.var"#107#109"{Module, Expr, REPL.LineEditREPL, REPL.LineEdit.Prompt})()
    @ VSCodeServer c:\Users\mazoi\.vscode\extensions\julialang.language-julia-1.7.6\scripts\packages\VSCodeServer\src\repl.jl:186
 [18] with_logstate(f::Function, logstate::Any)
    @ Base.CoreLogging .\logging.jl:511
 [19] with_logger
    @ .\logging.jl:623 [inlined]
 [20] (::VSCodeServer.var"#106#108"{Module, Expr, REPL.LineEditREPL, REPL.LineEdit.Prompt})()
    @ VSCodeServer c:\Users\mazoi\.vscode\extensions\julialang.language-julia-1.7.6\scripts\packages\VSCodeServer\src\repl.jl:187
 [21] #invokelatest#2
    @ .\essentials.jl:729 [inlined]
 [22] invokelatest(::Any)
    @ Base .\essentials.jl:726
 [23] macro expansion
    @ c:\Users\mazoi\.vscode\extensions\julialang.language-julia-1.7.6\scripts\packages\VSCodeServer\src\eval.jl:34 [inlined]
 [24] (::VSCodeServer.var"#61#62")()
    @ VSCodeServer .\task.jl:484
Stacktrace:
  [1] pyerr_check
    @ C:\Users\mazoi\.julia\packages\PyCall\ygXW2\src\exception.jl:62 [inlined]
  [2] pyerr_check
    @ C:\Users\mazoi\.julia\packages\PyCall\ygXW2\src\exception.jl:66 [inlined]
  [3] _handle_error(msg::String)
    @ PyCall C:\Users\mazoi\.julia\packages\PyCall\ygXW2\src\exception.jl:83
  [4] macro expansion
    @ C:\Users\mazoi\.julia\packages\PyCall\ygXW2\src\exception.jl:97 [inlined]
  [5] #107
    @ C:\Users\mazoi\.julia\packages\PyCall\ygXW2\src\pyfncall.jl:43 [inlined]
  [6] disable_sigint
    @ .\c.jl:473 [inlined]
  [7] __pycall!
    @ C:\Users\mazoi\.julia\packages\PyCall\ygXW2\src\pyfncall.jl:42 [inlined]
  [8] _pycall!(ret::PyCall.PyObject, o::PyCall.PyObject, args::Tuple{}, nargs::Int64, kw::PyCall.PyObject)
    @ PyCall C:\Users\mazoi\.julia\packages\PyCall\ygXW2\src\pyfncall.jl:29
  [9] _pycall!(ret::PyCall.PyObject, o::PyCall.PyObject, args::Tuple{}, kwargs::Base.Pairs{Symbol, Any, Tuple{Symbol, Symbol, Symbol}, NamedTuple{(:data, :x, :y), Tuple{DataFrame, Symbol, Symbol}}})
    @ PyCall C:\Users\mazoi\.julia\packages\PyCall\ygXW2\src\pyfncall.jl:11
 [10] #_#114
    @ C:\Users\mazoi\.julia\packages\PyCall\ygXW2\src\pyfncall.jl:86 [inlined]
 [11] lineplot(; kwargs::Base.Pairs{Symbol, Any, Tuple{Symbol, Symbol, Symbol}, NamedTuple{(:data, :x, :y), Tuple{DataFrame, Symbol, Symbol}}})
    @ Seaborn C:\Users\mazoi\.julia\packages\Seaborn\ionLX\src\Seaborn.jl:112
 [12] top-level scope
    @ REPL[4]:1

I would rather try seaborn directly with PythonCall.jl (see its docs). [E.g. PyPlot and] PyCall do not know anything about [Abstract]DataFrame type.

PythonCall seems to support it; because it depends on the Tables.jl interface package. See: “PyTable is an abstract type. See PyPandasDataFrame for a concrete example.”

I would, by now, always look at using the newer PythonCall rather than PyCall package. For packages that depend on PyCall, such as PyPlot, even though I mentioned it, I would look at alternatives. I see Seaborn.jl depends on Pandas.jl (an alternative to DataFrames.jl).

AbstractDataFrame is defined in DataFrames.jl (and Tables.jl is somewhat related). I did look up the error related to it, but as I said I would rather first look into PythonCall.

I often have this at the top of my (Plots-using) scripts:

# Function to generate Date x-axis labels
xts(start, stop, interval) = (start:interval:stop, Dates.format.(start:interval:stop, "u-Y"))

Then I can just do xticks = xts(Year(1990), Year(2000), Month(3)), xrot = 30 to get something decent looking very quickly

2 Likes

If you also want interactivity along with visualization take a look at plotly: Plotly Open Source Graphing Libraries

I’m looking into your issue, since I’m curious, but I’m not in a good position to test stuff out right now. Note I’ve been editing my previous answer, that was hopefully helpful. You could also look into the AbstractDataFrame error itself and the sugested solution in the error message itself or:

Thanks! It didn’t really work for me, I keep getting the same error. Tried the eachrow()/eachcolumn() to no avail. I couldn’t find any example syntax for seaborn.jl online…

No I mean with PythonCall.jl you would use seaborn, not the Seaborn.jl wrapper, so more or less exactly as you’re used to, hopefully with success. [I suppose Seaborn.jl should work too, assuming you use it correctly, I’m just not sure if you are, but I couldn’t rule out a bug in the wrapper.]