Tables and DataFrames
Tables.jl provides an ecosystem-wide interface to tabular data in Julia, ensuring interoperability with DataFrames.jl, CSV.jl, and hundreds of other packages that implement the standard.
Dimensional data are tables
DimensionalData.jl implements the Tables.jl interface for AbstractDimArray and AbstractDimStack. DimStack layers are unrolled so they are all the same size, and dimensions loop to match the length of the largest layer.
Columns are given the name of the array or stack layer, and the result of DD.name(dimension) for Dimension columns.
Looping of dimensions and stack layers is done lazily, and does not allocate unless collected.
Materializing tables to DimArray or DimStack
DimArray and DimStack have fallback methods to materialize any Tables.jl-compatible table.
By default, it will treat columns such as X, Y, Z, and Band as dimensions, and other columns as data. Pass a name keyword argument to determine which column(s) are used.
You have full control over which columns are dimensions - and what those dimensions look like exactly. If you pass a Tuple of Symbol or dimension types (e.g. X) as the second argument, those columns are treated as dimensions. Passing a Tuple of dimensions preserves these dimensions - with values matched to the corresponding columns.
Materializing tables will worked even if the table is not ordered, and can handle missing values.
Example
using DimensionalData
using Dates
using DataFramesDefine some dimensions:
julia> x, y, c = X(1:10), Y(1:10), Dim{:category}('a':'z')(↓ X 1:10,
→ Y 1:10,
↗ category 'a':1:'z')julia> A = rand(x, y, c; name=:data)┌ 10×10×26 DimArray{Float64, 3} data ┐
├────────────────────────────────────┴────────────── dims ┐
↓ X Sampled{Int64} 1:10 ForwardOrdered Regular Points,
→ Y Sampled{Int64} 1:10 ForwardOrdered Regular Points,
↗ category Categorical{Char} 'a':1:'z' ForwardOrdered
└─────────────────────────────────────────────────────────┘
[:, :, 1]
↓ → 1 2 3 … 8 9 10
1 0.910424 0.427826 0.862728 0.248024 0.293925 0.0595812
2 0.826802 0.512368 0.938823 0.451784 0.33373 0.675824
3 0.944751 0.80788 0.004129 0.132997 0.228297 0.530553
⋮ ⋱ ⋮
7 0.798656 0.471819 0.88707 0.944636 0.751848 0.441872
8 0.673027 0.021849 0.121545 0.249602 0.775614 0.779541
9 0.0522006 0.933603 0.228074 0.289268 0.788237 0.548612
10 0.94177 0.490656 0.906648 … 0.380685 0.859582 0.484276Converting to DataFrame
Arrays will have columns for each dimension, and only one data column
julia> DataFrame(A)2600×4 DataFrame
Row │ X Y category data
│ Int64 Int64 Char Float64
──────┼──────────────────────────────────
1 │ 1 1 a 0.910424
2 │ 2 1 a 0.826802
3 │ 3 1 a 0.944751
4 │ 4 1 a 0.159458
5 │ 5 1 a 0.614656
6 │ 6 1 a 0.977066
7 │ 7 1 a 0.798656
8 │ 8 1 a 0.673027
⋮ │ ⋮ ⋮ ⋮ ⋮
2594 │ 4 10 z 0.945291
2595 │ 5 10 z 0.245243
2596 │ 6 10 z 0.19857
2597 │ 7 10 z 0.694279
2598 │ 8 10 z 0.206362
2599 │ 9 10 z 0.842866
2600 │ 10 10 z 0.615094
2585 rows omittedConverting to CSV
We can also write arrays and stacks directly to CSV.jl, or any other data type supporting the Tables.jl interface.
using CSV
CSV.write("dimstack.csv", st)
readlines("dimstack.csv")2601-element Vector{String}:
"X,Y,category,data1,data2"
"1,1,a,0.9839310335062353,0.06519322052560106"
"2,1,a,0.07793648494589822,0.41204657699507596"
"3,1,a,0.219303664905647,0.2266498078450272"
"4,1,a,0.808552693598595,0.5768042809962604"
"5,1,a,0.8648363898272214,0.46048147930842187"
"6,1,a,0.48731263856378304,0.5536732497242794"
"7,1,a,0.8233218406562792,0.019251524697068256"
"8,1,a,0.9850534629525454,0.9111228873149807"
"9,1,a,0.6069051545554277,0.25452792253903866"
⋮
"2,10,z,0.5511954692677369,0.10709603432852566"
"3,10,z,0.32667356092800415,0.6974507495526762"
"4,10,z,0.135546552256131,0.8683059751916264"
"5,10,z,0.38759915964649516,0.7455409589828204"
"6,10,z,0.014672957560823985,0.15993758094186106"
"7,10,z,0.8899166255093396,0.6103859695911225"
"8,10,z,0.882300131522941,0.1987435419890986"
"9,10,z,0.33428559384872114,0.12120693020730788"
"10,10,z,0.7642325543665806,0.4543024424366052"Converting a DataFrame to a DimArray or DimStack
The Dataframe we use will have 5 columns: X, Y, category, data1, and data2
julia> df = DataFrame(st)2600×5 DataFrame
Row │ X Y category data1 data2
│ Int64 Int64 Char Float64 Float64
──────┼──────────────────────────────────────────────
1 │ 1 1 a 0.983931 0.0651932
2 │ 2 1 a 0.0779365 0.412047
3 │ 3 1 a 0.219304 0.22665
4 │ 4 1 a 0.808553 0.576804
5 │ 5 1 a 0.864836 0.460481
6 │ 6 1 a 0.487313 0.553673
7 │ 7 1 a 0.823322 0.0192515
8 │ 8 1 a 0.985053 0.911123
⋮ │ ⋮ ⋮ ⋮ ⋮ ⋮
2594 │ 4 10 z 0.135547 0.868306
2595 │ 5 10 z 0.387599 0.745541
2596 │ 6 10 z 0.014673 0.159938
2597 │ 7 10 z 0.889917 0.610386
2598 │ 8 10 z 0.8823 0.198744
2599 │ 9 10 z 0.334286 0.121207
2600 │ 10 10 z 0.764233 0.454302
2585 rows omittedConverting this DataFrame to a DimArray without other arguments will read the category columns as data and ignore data1 and data2:
julia> DimArray(df)┌ 10×10 DimArray{Char, 2} category ┐
├──────────────────────────────────┴────────────────── dims ┐
↓ X Sampled{Int64} 1:1:10 ForwardOrdered Regular Points,
→ Y Sampled{Int64} 1:1:10 ForwardOrdered Regular Points
└───────────────────────────────────────────────────────────┘
↓ → 1 2 3 4 5 6 7 8 9 10
1 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z'
2 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z'
3 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z'
4 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z'
⋮ ⋮ ⋮
7 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z'
8 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z'
9 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z'
10 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z' 'z'Specify dimenion names to ensure these get treated as dimensions. Now data1 is read in instead.
julia> DimArray(df, (X,Y,:category))┌ 10×10×26 DimArray{Float64, 3} data1 ┐
├─────────────────────────────────────┴──────────────── dims ┐
↓ X Sampled{Int64} 1:1:10 ForwardOrdered Regular Points,
→ Y Sampled{Int64} 1:1:10 ForwardOrdered Regular Points,
↗ category Categorical{Char} ['a', …, 'z'] ForwardOrdered
└────────────────────────────────────────────────────────────┘
[:, :, 1]
↓ → 1 2 3 … 8 9 10
1 0.983931 0.336887 0.353286 0.684799 0.802893 0.688119
2 0.0779365 0.284432 0.828376 0.101372 0.16707 0.551195
3 0.219304 0.615316 0.449789 0.142896 0.429108 0.326674
⋮ ⋱ ⋮
7 0.823322 0.433021 0.995956 0.578948 0.553472 0.889917
8 0.985053 0.235447 0.518961 0.079027 0.901092 0.8823
9 0.606905 0.132421 0.665932 0.313258 0.831805 0.334286
10 0.900526 0.69021 0.284993 … 0.340798 0.665965 0.764233You can also pass in the actual dimensions.
julia> DimArray(df, dims(st))┌ 10×10×26 DimArray{Float64, 3} data1 ┐
├─────────────────────────────────────┴───────────── dims ┐
↓ X Sampled{Int64} 1:10 ForwardOrdered Regular Points,
→ Y Sampled{Int64} 1:10 ForwardOrdered Regular Points,
↗ category Categorical{Char} 'a':1:'z' ForwardOrdered
└─────────────────────────────────────────────────────────┘
[:, :, 1]
↓ → 1 2 3 … 8 9 10
1 0.983931 0.336887 0.353286 0.684799 0.802893 0.688119
2 0.0779365 0.284432 0.828376 0.101372 0.16707 0.551195
3 0.219304 0.615316 0.449789 0.142896 0.429108 0.326674
⋮ ⋱ ⋮
7 0.823322 0.433021 0.995956 0.578948 0.553472 0.889917
8 0.985053 0.235447 0.518961 0.079027 0.901092 0.8823
9 0.606905 0.132421 0.665932 0.313258 0.831805 0.334286
10 0.900526 0.69021 0.284993 … 0.340798 0.665965 0.764233Pass in a name argument to read in data2 instead.
julia> DimArray(df, dims(st); name = :data2)┌ 10×10×26 DimArray{Float64, 3} data2 ┐
├─────────────────────────────────────┴───────────── dims ┐
↓ X Sampled{Int64} 1:10 ForwardOrdered Regular Points,
→ Y Sampled{Int64} 1:10 ForwardOrdered Regular Points,
↗ category Categorical{Char} 'a':1:'z' ForwardOrdered
└─────────────────────────────────────────────────────────┘
[:, :, 1]
↓ → 1 2 3 … 8 9 10
1 0.0651932 0.799726 0.60486 0.444635 0.0741017 0.529658
2 0.412047 0.290115 0.728298 0.975869 0.215829 0.862897
3 0.22665 0.873043 0.533987 0.16958 0.948835 0.998753
⋮ ⋱ ⋮
7 0.0192515 0.477097 0.935915 0.14575 0.0860152 0.651334
8 0.911123 0.909241 0.655165 0.135973 0.961394 0.802388
9 0.254528 0.283612 0.352911 0.174855 0.236336 0.382086
10 0.785481 0.971546 0.963417 … 0.291397 0.963681 0.855054