Skip to content

Commit 64a80ec

Browse files
committed
Ditch DataFrames.jl dependency, implements Tables.jl interface instead
1 parent 98f865b commit 64a80ec

22 files changed

Lines changed: 427 additions & 268 deletions

Project.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ version = "0.14.4"
55

66
[deps]
77
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
8-
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
98
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
109
DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"
1110
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
@@ -19,7 +18,6 @@ XLSX = "fdbf4ff8-1666-58a4-91e7-1b58723a45e0"
1918

2019
[compat]
2120
AbstractTrees = "0.4"
22-
DataFrames = "1"
2321
DelimitedFiles = "1"
2422
Graphs = "1"
2523
MetaGraphsNext = "0.5, 0.6, 0.7, 0.8"

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,15 @@ write_mtg("test.mtg",mtg)
5353

5454
## 3. Conversion
5555

56-
You can convert an MTG into a DataFrame and select the variables you need:
56+
You can expose an MTG as a `Tables.jl` source:
5757

5858
```julia
59-
DataFrame(mtg, [:length_mm, :XX])
59+
mtg_table(mtg)
60+
symbol_table(mtg, :Leaf)
6061
```
6162

63+
If you use `DataFrames.jl`, `DataFrame(mtg)` works out of the box through the `Tables.jl` interface.
64+
6265
Or convert it to a [MetaGraph](https://juliagraphs.org/MetaGraphsNext.jl/dev/):
6366

6467
```julia

docs/src/api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Notable attribute APIs:
77
- `attribute`, `attribute!`, `attributes`, `attribute_names`
88
- `add_column!`, `drop_column!`, `rename_column!`
99
- `descendants_strategy`, `descendants_strategy!` (choose automatic/direct/indexed descendants retrieval)
10-
- `symbol_table`, `mtg_table` (`Tables.jl` sources)
10+
- `to_table`, `symbol_table`, `mtg_table` (`Tables.jl` sources)
1111

1212
```@index
1313
```

docs/src/get_started.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,24 @@ Then you can compute new variables in the MTG using [`transform!`](@ref):
4242
transform!(mtg, :Length => (x -> isnothing(x) ? nothing : x * 1000.) => :length_mm)
4343
```
4444

45-
The design of [`transform!`](@ref) is heavily inspired from the eponym function from [`DataFrame.jl`](https://dataframes.juliadata.org/stable/), with little tweaks for MTGs.
45+
The design of [`transform!`](@ref) is heavily inspired from the eponym function from tabular workflows (notably [`DataFrames.jl`](https://dataframes.juliadata.org/stable/)), with little tweaks for MTGs.
4646

4747
You can see the newly-computed attributes using descendants like so:
4848

4949
```@example usepkg
5050
descendants(mtg, :length_mm)
5151
```
5252

53-
Or by transforming your MTG into a DataFrame:
53+
Or by getting a tabular view of your MTG:
5454

5555
```@example usepkg
56-
DataFrame(mtg, :length_mm)
56+
mtg_table(mtg)
57+
```
58+
59+
Or directly transforming the MTG into a DataFrame:
60+
61+
```@example usepkg
62+
DataFrame(mtg)
5763
```
5864

5965
Then you can write the MTG back to disk like so:

docs/src/tutorials/2.descendants_ancestors_filters.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,10 @@ If you start from another node you can retrieve the root node using [`get_root`]
8484
get_attributes(get_root(node_5))
8585
```
8686

87-
A more simple way to get all nodes and their attributes is to convert the MTG into a DataFrame like so:
87+
A simple way to get all nodes and their attributes is to use the unified table view:
8888

8989
```@example usepkg
90-
DataFrame(mtg)
90+
mtg_table(mtg)
9191
```
9292

9393
## Descendants

docs/src/tutorials/3.transform_mtg.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,10 @@ We can also get its values by using [`descendants`](@ref) on the root node:
108108
descendants(mtg, :length_m)
109109
```
110110

111-
We can also get the values in the form of a DataFrame instead:
111+
We can also inspect values through the MTG table view:
112112

113113
```@example usepkg
114-
DataFrame(mtg, :length_m)
114+
mtg_table(mtg)
115115
```
116116

117117
We can also provide several input variables if we need:
@@ -125,7 +125,7 @@ Here we provide the input attributes as a Vector of Symbols (could be String als
125125
Let's see the results:
126126

127127
```@example usepkg
128-
DataFrame(mtg, [:Length, :width, :volume_cm3])
128+
mtg_table(mtg)
129129
```
130130

131131
The new name of the attribute (the RHS) is optional though. We could write our first example as:
@@ -241,15 +241,15 @@ mtg_select = deepcopy(mtg)
241241
242242
select!(mtg_select, :Length => (x -> x / 10) => :length_m, :Width, ignore_nothing = true)
243243
244-
DataFrame(mtg_select)
244+
mtg_table(mtg_select)
245245
```
246246

247247
There is also a copy-returning version of the function (`select`, without `!`):
248248

249249
```@example usepkg
250250
mtg_select = select(mtg, :Length => (x -> x / 10) => :length_m, :Width, ignore_nothing = true)
251251
252-
DataFrame(mtg_select)
252+
mtg_table(mtg_select)
253253
```
254254

255255
## Traverse an MTG
@@ -298,7 +298,7 @@ This package provides an implementation of the pipe model, used as follows:
298298
first_cross_section = 0.34 # the initial cross-section of the plant
299299
300300
transform!(mtg, (node -> pipe_model!(node, first_cross_section)) => :cross_section_pipe)
301-
DataFrame(mtg, :cross_section_pipe)
301+
descendants(mtg, :cross_section_pipe)
302302
```
303303

304304
For more information about the implementation, you can check the documentation of the function: [`pipe_model!`](@ref).

docs/src/tutorials/4.convert_mtg.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,36 @@ mtg = read_mtg(file)
88

99
We can do a lot using the MTG format, but sometimes we want our data in another format.
1010

11-
That's why `MultiScaleTreeGraph.jl` provide functions to convert an MTG into a DataFrame or into a Graph.
11+
That's why `MultiScaleTreeGraph.jl` provides functions to expose an MTG as a `Tables.jl` source, or convert it into a graph.
1212

13-
## MTG to DataFrame
13+
## MTG to table
1414

15-
To convert an MTG into a DataFrame, you can simply use this command:
15+
To get a unified table view of the MTG, you can use:
1616

1717
```@example usepkg
18-
df = DataFrame(mtg, :Width)
18+
tbl = mtg_table(mtg)
1919
```
2020

21-
This will convert your MTG into a DataFrame along with the selected variable (here the Width). The node MTG is always reported in new columns:
21+
The unified table reports MTG topology columns plus all attributes:
2222

23-
- tree: a pretty-printing of the MTG
24-
- id: the unique ID of the node in the whole MTG
23+
- node_id: the unique ID of the node in the whole MTG
2524
- symbol: the node symbol
2625
- scale: the node scale
2726
- index: the node index
2827
- parent_id: the node's parent id
2928
- link: the link between the node and its parent
3029

31-
It is also possible to get several attributes as columns by passing their names as a vector:
30+
It is also possible to get a per-symbol table:
3231

3332
```@example usepkg
34-
DataFrame(mtg, [:Width, :Length])
33+
leaf_tbl = symbol_table(mtg, :Leaf)
34+
```
35+
36+
If you use `DataFrames.jl` in your own project, `DataFrame(mtg)` still works because MTGs implement the `Tables.jl` interface.
37+
38+
```julia
39+
using DataFrames
40+
df = DataFrame(mtg)
3541
```
3642

3743
## MTG to MetaGraph

docs/src/tutorials/6.add_remove_nodes.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ mtg
7070
And the attributes:
7171

7272
```@example usepkg
73-
DataFrame(mtg, get_attributes(mtg))
73+
mtg_table(mtg)
7474
```
7575

7676
### Inserting nodes
@@ -332,7 +332,7 @@ insert_siblings!(
332332
Let's see the results for the area of our leaves:
333333

334334
```@example usepkg
335-
DataFrame(mtg_5, :area)
335+
descendants(mtg_5, :area, self = true)
336336
```
337337

338338
### Write the MTG

src/MultiScaleTreeGraph.jl

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,14 @@ using OrderedCollections
88
import XLSX: readxlsx, sheetnames
99
import SHA: sha1 # for naming the cache variable
1010
import Base
11-
import DataFrames: DataFrame, insertcols!
12-
import DataFrames: transform!, transform # We define our own version for transforming the MTG
13-
import DataFrames: select!, select # We define our own version for transforming the MTG
14-
import DataFrames: rename! # We define our own version for renaming node attributes
1511
import Tables
1612
import MetaGraphsNext: MetaGraph, code_for, add_edge! # Transform to MetaGraph
1713
import Graphs.DiGraph
1814
import Dates: Date, @dateformat_str, format
1915

2016
include("types/AbstractNodeMTG.jl")
2117
include("types/Attributes.jl")
18+
include("types/ColumnTable.jl")
2219
include("types/Node.jl")
2320
include("read_MTG/read_MTG.jl")
2421
include("read_MTG/strip_comments.jl")
@@ -50,7 +47,6 @@ include("compute_MTG/mutation_helpers.jl")
5047
include("compute_MTG/nleaves.jl")
5148
include("compute_MTG/pipe_model.jl")
5249
include("compute_MTG/get_node.jl")
53-
include("conversion/DataFrame.jl")
5450
include("conversion/Tables.jl")
5551
include("conversion/MetaGraph.jl")
5652

@@ -68,7 +64,6 @@ export nextsibling, prevsibling, lastsibling
6864
export print
6965
export show
7066
export length
71-
export DataFrame
7267
export MetaGraph
7368
export iterate
7469
export siblings
@@ -102,6 +97,7 @@ export add_column!, drop_column!, rename_column!
10297
export descendants_strategy, descendants_strategy!
10398
export columnarize!
10499
export symbol_table, mtg_table
100+
export to_table
105101
export list_nodes
106102
export get_classes
107103
export get_description

src/compute_MTG/summary.jl

Lines changed: 47 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,24 @@ Compute the mtg classes based on its content. Usefull after having mutating the
66
function get_classes(mtg)
77
attributes = traverse(mtg, node -> (SYMBOL=symbol(node), SCALE=scale(node)), type=@NamedTuple{SYMBOL::Symbol, SCALE::Int64})
88
attributes = unique(attributes)
9-
df = DataFrame(attributes)
9+
n = length(attributes)
10+
symbols_ = Vector{Symbol}(undef, n)
11+
scales_ = Vector{Int}(undef, n)
12+
@inbounds for i in eachindex(attributes)
13+
symbols_[i] = attributes[i].SYMBOL
14+
scales_[i] = attributes[i].SCALE
15+
end
1016

11-
# Make everything to default values:
12-
df[!, :DECOMPOSITION] .= "FREE"
13-
df[!, :INDEXATION] .= "FREE"
14-
df[!, :DEFINITION] .= "IMPLICIT"
15-
return df
17+
ColumnTable(
18+
Symbol[:SYMBOL, :SCALE, :DECOMPOSITION, :INDEXATION, :DEFINITION],
19+
AbstractVector[
20+
symbols_,
21+
scales_,
22+
fill("FREE", n),
23+
fill("FREE", n),
24+
fill("IMPLICIT", n)
25+
]
26+
)
1627
end
1728

1829
"""
@@ -31,53 +42,40 @@ Compute the mtg features section based on its attributes. Usefull after having c
3142
in the mtg.
3243
"""
3344
function get_features(mtg)
34-
attributes = traverse(
35-
mtg,
36-
node -> (collect(keys(node_attributes(node))), [typeof(i) for i in values(node_attributes(node))]), type=Tuple{Vector{Symbol},Vector{DataType}}
37-
) |> unique
38-
39-
df = DataFrame(
40-
:NAME => vcat([i[1] for i in attributes]...),
41-
:TYPE => vcat([i[2] for i in attributes]...)
42-
)
43-
44-
# filter-out the attributes that have more than one value inside them (not compatible with
45-
# the mtg format yet):
46-
filter!(
47-
x -> !(x.TYPE <: Vector) &&
48-
!(x.TYPE <: Nothing) &&
49-
!in(x.NAME, [:description, :symbols, :scales]),
50-
df
51-
)
45+
names_ = Symbol[]
46+
types_ = String[]
47+
seen = Set{Tuple{Symbol,String}}()
5248

53-
# Remove repeated rows:
54-
unique!(df)
55-
56-
new_type = fill("", size(df)[1])
57-
for (index, value) in enumerate(df.TYPE)
58-
if value <: AbstractFloat
59-
new_type[index] = "REAL"
60-
elseif value <: Bool
61-
# we test booleans before integers because Bool <: Integer
62-
new_type[index] = "BOOLEAN"
63-
elseif value <: Integer
64-
new_type[index] = "INT"
65-
# elseif df.NAME[i] in () # Put reserved keywords here if needed in the future
66-
# new_type[index] = "ALPHA"
67-
elseif value <: Date
68-
new_type[index] = "DD/MM/YY"
69-
else
70-
# All others are parsed as string
71-
new_type[index] = "STRING"
49+
traverse!(mtg) do node
50+
for (name, value) in pairs(node_attributes(node))
51+
T = typeof(value)
52+
if (T <: AbstractVector) || (T <: Nothing) || (name in (:description, :symbols, :scales))
53+
continue
54+
end
55+
56+
typ =
57+
if T <: AbstractFloat
58+
"REAL"
59+
elseif T <: Bool
60+
"BOOLEAN"
61+
elseif T <: Integer
62+
"INT"
63+
elseif T <: Date
64+
"DD/MM/YY"
65+
else
66+
"STRING"
67+
end
68+
69+
row = (Symbol(name), typ)
70+
if !(row in seen)
71+
push!(seen, row)
72+
push!(names_, row[1])
73+
push!(types_, row[2])
74+
end
7275
end
7376
end
7477

75-
df.TYPE = new_type
76-
77-
# Remove repeated rows again after having changed the type (can have String and SubString for the same variable before):
78-
unique!(df)
79-
80-
return df
78+
ColumnTable(Symbol[:NAME, :TYPE], AbstractVector[names_, types_])
8179
end
8280

8381
"""

0 commit comments

Comments
 (0)