''' Function to analyse strawberry mtg: Data extraction and plot '''
from __future__ import absolute_import
from __future__ import print_function
from ipaddress import summarize_address_range
import pandas as pd
from collections import OrderedDict
import matplotlib.pyplot as plt
from itertools import chain
from pandas.core.groupby.groupby import DataError
import numpy as np
from matplotlib.colors import to_rgb
import matplotlib.patches as mpatches
from matplotlib.ticker import MaxNLocator
import plotly.express as px
import plotly.graph_objs as go
from six.moves import map
from six.moves import range
from openalea.mtg.algo import orders
from openalea.mtg import algo, traversal
convert = dict(Stade='Stade',
Fleurs_ouverte='FLWRNUMBER_OPEN',
Fleurs_avorte='FLWRNUMBER_ABORTED',
Fleurs_total='FLWRNUMBER',
date='Sample_date',
Plante='Plant_ID',
)
[docs]def to_dataframe(g, vertices=[], f=None):
"""Convert an MTG into a full dataframe.
:param g: MTG graph
:type g: MTG
:param vertices: define the vertices you want to export, defaults to []
:type vertices: list, optional
:param f: v -> dict : function that returns a set of properties for any vertex, defaults to None
:type f: function, optional
:return: A dataframe that include all the MTG properties
:rtype: DataFrame
"""
# Recompute the properties for each vertices
if not vertices:
vertices = g.vertices()
if f is None:
f = lambda v : g[v]
d = dict()
for v in vertices:
props_v = f(v)
for k, value in props_v.items():
d.setdefault(k,{})[v] = value
parents = g._parent
complexes = {vid: g.complex(vid) for vid in g if g.complex(vid) is not None}
scales = g._scale
_orders = orders(g)
d['parent'] = {v: parents.get(v) for v in vertices}
d['complex'] = {v: complexes.get(v) for v in vertices}
d['scale'] = {v: scales.get(v) for v in vertices}
d['order'] = {v: _orders.get(v) for v in vertices}
dataframe = pd.DataFrame.from_dict(d)
return dataframe
[docs]def strawberry_dataframe(g):
"""Convert a strawberry MTG into a dataframe
:param g: the strawberry MTG
:type g: MTG
:return: A dataframe
:rtype: DataFrame
"""
vertices = [v for v in g.vertices_iter() if v !=0]
props = g.properties()
def my_f(v):
d = dict()
pid = g.complex_at_scale(v, scale=1)
pnid = g.node(pid)
nid = g.node(v)
# Set plant properties to each node
d['Genotype'] = pnid.Genotype
d['Plante'] = pnid.Plante
d['date'] = pnid.Sample_date
d['label'] = g.label(v)
# At all scale
d['Stage'] = nid.Stade
d['Foliar_type'] = nid.Foliar_type
#d['prelevement'] = nid.prelevement
#d['Fleurs_total']= nid.Fleurs_total
#d['Fleurs_ouverte'] = nid.Fleurs_ouverte
#d['Fleurs_avorte'] = nid.Fleurs_avorte
return d
return to_dataframe(g, vertices, my_f)
[docs]def write_sequences(seqs, variables, VertexIdentifiers):
"""Write Sequences into a txt file.
Example:
VertexIdentifiers = [['-'.join(str(orders[j]) for j in seq[0:i+1]) for i, vid in enumerate(seq)] for seq in seqs]
seqs = [ list(apparent_axis(g, vid)) for vid in vids]
:param seqs: A MTG in vector format (=sequence)
:type seqs: list
:param variables: dict of variables to extract as keys & their function as var
:type variables: OrderedDict
:param VertexIdentifiers: index or order succession
:type VertexIdentifiers: list
:return: A string with variable information extacted from MTG
:rtype: string
"""
sep = '\t'
txts = []
# header
header = "INDEX_PARAMETER : TIME # vertex_id"
txts.append(header)
txts.append('')
n = len(variables)
header = "%d VARIABLES"%n
txts.append(header)
txts.append('')
for i in range(1, n+1):
txts.append('VARIABLE %d: INT # %s'%(i, variables[i-1]))
txts.append('')
txts.append('')
txts.append('#Index'+sep+sep.join(variables))
for i, seq in enumerate(seqs):
n = len(seq)
for j, value in enumerate(seq):
vid = VertexIdentifiers[i][j]
record = [str(vid)] + list(map(str,value))
if j < n-1:
record.append('\\')
else:
# end of the sequence
record.append('')
record.append('#')
txt = sep.join(record)
txts.append(txt)
txts.append('')
return '\n'.join(txts)
def occurence_module_order_along_time(data, frequency_type):
"""Compute occurence of module order along time.
:param data: dataframe extracted at module scale
:type data: DataFrame
:param frequency_type: type of distribution frequency distribution (freq), probability distribution frequency (pdf) or cumulative frequency distribution (cdf)
:type frequency_type: string
:return: A dataframe with frequency, probability or cumulative frequency distribution for each module order along time
:rtype: DataFrame
"""
if frequency_type == "freq":
res = pd.crosstab(index= data["order"], columns= data["date"], margins = True)
if frequency_type == "pdf":
res = pd.crosstab(index= data["order"], columns= data["date"], normalize = "columns")
if frequency_type == "cdf":
res = pd.crosstab(index= data["order"], columns= data["date"], normalize = "columns").cumsum()
return res
[docs]def pointwisemean_plot(data_mean,data_sd,varieties, variable,title,ylab, expand=0):
"""Plot a pointwise mean of variables.
:param data_mean: panda dataframe containg mean values
:type data_mean: DataFrame
:param data_sd: panda dataframe containing standars error values
:type data_sd: DataFrame
:param varieties: names of varieties which are plot
:type varieties: list of string
:param variable: The variable to plot
:type variable: string
:param title: plot title
:type title: string
:param ylab: y axis label
:type ylab: string
:param expand: allows to change xlim, defaults to 0
:type expand: int, optional
"""
fig, pointwise_mean = plt.subplots()
cmap = plt.get_cmap('rainbow', len(varieties))
for i, varietie in enumerate(varieties):
pointwise_mean.errorbar(x=data_mean.loc[varietie].index,
y=data_mean.loc[varietie][variable],
yerr=data_sd.loc[varietie][variable],
color=cmap(i),
marker="p")
pointwise_mean.legend(labels=varieties,loc='center left', bbox_to_anchor=(1, 0.5))
pointwise_mean.set_title(title)
pointwise_mean.set_ylabel(ylab)
pointwise_mean.set_xlim(left=-expand, right= max(data_mean.loc[varietie].index)+expand)
plt.show()
[docs]def crowntype_distribution(data, varieties, crown_type, plot=True,expand=0):
"""Create a dataframe containing relative frequency values by genotype and order for extension and branch crown and a relative frequency distribution plot
:param data: panda dataframe issue from extraction of data at module scale
:type data: pd.DataFrame
:param varieties: names of varieties which are plot
:type varieties: list of string
:param crown_type: type of branch crown (extension_crown or branch_crown)
:type crown_type: str
:param plot: is ploted, defaults to True
:type plot: bool, optional
:param expand: allows to change xlim, defaults to 0
:type expand: int, optional
:return: the dataframe
:rtype: pd.DataFrame
"""
df= pd.crosstab(index= [data.Genotype, data.order],
columns= data.type_of_crown,
normalize="index")
df.columns=["Main", "extension_crown", "branch_crown"]
if plot:
cmap = plt.get_cmap('rainbow', len(varieties))
print(cmap)
for i, variety in enumerate(varieties):
df = df[df.index.get_level_values('order')!=0]
plt.plot(df.loc[variety][crown_type],
marker="p",
color = cmap(i))
plt.ylabel("relative frequency")
plt.xlabel("order")
plt.title("Relative frequency of " + crown_type)
plt.legend(labels=varieties,loc='center left', bbox_to_anchor=(1, 0.5))
plt.xlim(left=1-expand, right= max(df.loc[variety].index)+expand)
plt.ylim(bottom=0.1, top= 1.1)
return df
[docs]def property(g, name):
"""Change the name of the MTG properties without changing the code
:param g: The MTG to modify
:type g: MTG
:param name: The property name
:type name: string
:return: A MTG with the name of the property changed
:rtype: MTG
"""
return g.property(convert.get(name, name))
# Extraction at plant scale
def _plant_variables(g):
"""Generate a dict of variables name and extraction function at plant scale
:param g: The MTG
:type g: MTG
:return: A dict of keys=variables name, var=extraction functions
:rtype: OrderedDict
"""
plant_variables = OrderedDict()
plant_variables['no_visible_leaves'] = nb_visible_leaves # Nombre de feuille developpé
plant_variables['no_foliar_primodium']= nb_foliar_primordia # Nombre de feuille dans le bourgeons
#plant_variables['no_missing_leaves'] = missing_leaves #Nombre de feuille manquante
plant_variables['no_total_leaves'] = nb_total_leaves #Nombre total de feuille
plant_variables['no_open_flowers'] = nb_open_flowers #Nombre de fleurs ouverte
plant_variables['no_aborted_flowers'] = nb_aborted_flowers #Nombre de fleurs avorte
plant_variables['no_closed_flowers'] = nb_close_flowers #Nombre de fleurs fermé
plant_variables['no_total_flowers'] = nb_total_flowers #Nombre total de Fleurs
plant_variables['no_fruits'] = no_fruits # number of fruits
plant_variables['no_stolons'] = nb_stolons # Nombre de stolons
plant_variables['no_vegetative_bud'] = nb_vegetative_buds
plant_variables['no_initiated_bud'] = nb_initiated_buds
plant_variables['no_floral_bud'] = nb_floral_buds
plant_variables['no_inflorescences'] = nb_inflorescence
#plant_variables['type_of_crown'] = type_of_crown (1: Main Crown, 2:extension crown, 3: branch crown)
#plant_variables['crown_status'] = crown_status
return plant_variables
# Extraction at the module scale
def _module_variables(g):
"""Generate a dict of variables name and extraction function at plant module
:param g: The MTG
:type g: MTG
:return: A dict of keys=variables name, var=extraction functions
:rtype: OrderedDict
"""
module_variables = OrderedDict()
module_variables['no_visible_leaves'] = nb_visible_leaves # Nombre de feuille developpe
module_variables['no_foliar_primordia'] = nb_foliar_primordia #Nombre de feuille dans le bourgeons
module_variables['no_total_leaves'] = nb_total_leaves #Nombre total de feuille
module_variables['no_open_flowers'] = nb_open_flowers #Nombre de fleurs ouverte
module_variables['no_aborted_flowers'] = nb_aborted_flowers #Nombre de fleurs avorte
module_variables['no_total_flowers'] = nb_total_flowers #Nombre total de Fleurs
module_variables['no_fruits']= no_fruits # Number of fruits
module_variables['no_stolons']= nb_stolons # number of stolon
module_variables['no_vegetative_bud'] = nb_vegetative_buds # number of vegetative bud
module_variables['no_initiated_bud']= nb_initiated_buds
module_variables['no_floral_bud']= nb_floral_buds
module_variables['type_of_crown'] = type_of_crown # Type de crowns (Primary Crown:1, Branch crown:3 extension crown:2)
module_variables['crown_status'] = crown_status
module_variables['stage']= stage
module_variables['complete_module'] = complete #(True: complete, False: incomplete)
return module_variables
[docs]def visible_modules(g, vids=[]):
"""Give "visible" property to module
:param g: The MTG
:type g: MTG
:param vids: List of vids whose property will be "visible", defaults to []
:type vids: list, optional
"""
modules = [v for v in g.vertices_iter(scale=2)
if (g.complex(v) in vids)
and g.label(next(g.component_roots_iter(v))) == 'F']
_visible = {}
for m in modules:
_visible[m] = True
g.properties()['visible'] = _visible
[docs]def complete_module(g, vids=[]):
"""Return properties incomplete or complete module
Algorithm:
module are complete:
if module are visible and terminated by an Inflorescence (HT) (propertie=True)
else module are incomplete (all module terminated by ht or bt) (property=False)
:param g: the MTG module
:type g: MTG
:param vids: list of vids, defaults to []
:type vids: list, optional
"""
complete = {}
visible = g.property('visible')
for vid in visible:
if g.complex_at_scale(vid, scale=1) not in vids:
continue
comp = g.components(vid)
c = comp[0]
axis = [v for v in g.Axis(c) if v in comp]
last = axis[-1]
if g.label(last) == 'HT':
complete[vid] = True
g.properties()['complete'] = complete
[docs]def nb_visible_leaves(vid, g):
"""Return the number of visible leaves
:param vid: vid for which the function is applied
:type vid: int
:param g: MTG
:type g: MTG
:return: The number of visible leaves
:rtype: int
"""
return sum(1 for cid in g.components(vid) if g.label(cid)=='F')
[docs]def nb_foliar_primordia(vid, g):
"""Return the number of foliar primordia (f)
:param vid: vid for which the function is applied
:type vid: int
:param g: MTG
:type g: MTG
:return: The number of foliar primordia
:rtype: int
"""
return sum(1 for cid in g.components(vid) if g.label(cid)=='f')
[docs]def nb_total_leaves(vid, g):
"""Return the total number of leaves (f+F)
:param vid: vid for which the function is applied
:type vid: int
:param g: MTG
:type g: MTG
:return: The number of total leaves
:rtype: int
"""
return sum(1 for cid in g.components(vid) if g.label(cid) in ('f', 'F'))
[docs]def nb_stolons(v, g):
"""Return the number of stolons
:param v: vid for which the function is applied
:type v: int
:param g: MTG
:type g: MTG
:return: The number of stolon
:rtype: int
"""
def nb_stolon(vid, g=g):
return sum(1 for cid in g.components(vid) if g.label(cid)=='s')
return sum(nb_stolon(ch) for ch in g.children(v))
[docs]def nb_open_flowers(vid, g):
"""Return the number of open flowers
:param vid: vid for which the function is applied
:type vid: int
:param g: MTG
:type g: MTG
:return: The number of open flowers
:rtype: int
"""
flowers = property(g, 'Fleurs_ouverte')
return sum( flowers.get(cid,0) for cid in g.components(vid) if g.label(cid) in ('ht', 'HT'))
[docs]def no_fruits(vid, g):
"""Return the number of fruits
:param vid: vid for which the function is applied
:type vid: int
:param g: MTG
:type g: MTG
:return: The number of fruits
:rtype: int
"""
fruits = property(g, 'no_fruits')
return sum( fruits.get(cid,0) for cid in g.components(vid) if g.label(cid) in ('ht','HT'))
[docs]def nb_aborted_flowers(vid, g):
"""Return the number of aborted flowers
:param vid: vid for which the function is applied
:type vid: int
:param g: MTG
:type g: MTG
:return: The number of aborted flowers
:rtype: int
"""
flowers = property(g, 'Fleurs_aborted')
return sum( flowers.get(cid,0) for cid in g.components(vid) if g.label(cid) in ('ht', 'HT'))
[docs]def nb_close_flowers(vid, g):
"""Return the number of aborted flowers
:param vid: vid for which the function is applied
:type vid: int
:param g: MTG
:type g: MTG
:return: The number of aborted flowers
:rtype: int
"""
flowers = property(g, 'FLWRNUMBER_CLOSED')
return sum( flowers.get(cid,0) for cid in g.components(vid) if g.label(cid) in ('ht', 'HT'))
[docs]def nb_total_flowers(vid, g):
"""Return the number of total flowers
:param vid: vid for which the function is applied
:type vid: int
:param g: MTG
:type g: MTG
:return: The number of total flowers
:rtype: int
"""
flowers = property(g, 'Fleurs_total')
return sum( flowers.get(cid,0) for cid in g.components(vid) if g.label(cid) in ('ht', 'HT'))
[docs]def missing_leaves(vid,g):
"""Return the number of missing leaves
:param vid: vid for which the function is applied
:type vid: int
:param g: MTG
:type g: MTG
:return: The number of missing leaves
:rtype: int
"""
missing= property(g, 'Missing')
return sum(1 for cid in g.components(vid) if missing.get(cid)=="yes")
[docs]def nb_vegetative_buds(vid, g):
"""Return the number of vegetative buds
Algorithm:
if label is bt then stage is 17,18,19 or None
count number of bt and attach at the parent order
:param vid: vid for which the function is applied
:type vid: int
:param g: MTG
:type g: MTG
:return: The number of vegetative buds
:rtype: int
"""
stages= property(g, 'Stade')
def nb_vegetative(v):
cid = g.component_roots(v)[0]
return sum(1 for cid in g.components(v) if g.label(cid)=='bt' and stages.get(cid) in (None,'17','18','19'))
return sum(nb_vegetative(ch) for ch in g.children(vid))
[docs]def nb_initiated_buds(vid, g):
"""Return the number of initiated buds
:param vid: vid for which the function is applied
:type vid: int
:param g: MTG
:type g: MTG
:return: The number of initiated buds
:rtype: int
"""
stages= property(g, 'Stade')
def nb_init(v):
return sum(1 for cid in g.components(v) if (g.label(cid)=='bt') and (stages.get(cid)=='A'))
return sum(nb_init(ch) for ch in g.children(vid))
[docs]def nb_floral_buds (vid, g):
"""Return the number of floral buds
:param vid: vid for which the function is applied
:type vid: int
:param g: MTG
:type g: MTG
:return: The number of floral buds
:rtype: int
"""
visibles = property(g, 'visible')
def nb_floral(v):
return sum(1 for cid in g.components(v) if g.label(cid)=="ht" )
return sum(nb_floral(ch) for ch in g.children(vid) if ch not in visibles)
[docs]def type_of_crown(vid, g):
"""Returns the type of crown.
Definition of type of crown (1, 2, 3):
- principal crown (1): label == T
- branch_crown (3)
parent(component_roots()[0]) : if successor() == F
- extension_crown (2): contains(HT, ht, bt)
- error (4)
:param vid: vid for which the function is applied
:type vid: int
:param g: MTG
:type g: MTG
:return: The type of crown
:rtype: int
"""
if g.label(vid) == 'T':
return 1
else:
cid = next(g.component_roots_iter(vid))
pid = g.parent(cid)
sid = g.Successor(pid)
#print(sid)
if g.label(sid) in ('F', 'f'):
return 3
elif g.label(sid) in ('bt', 'ht', 'HT'):
return 2
else:
# ERROR !!!
# print((g[cid], g[g.complex_at_scale(cid, scale=1)]))
return 4
[docs]def crown_status(vid, g):
"""Returns the type of inflorescence
:Algorithms:
if label is bt then
- if stage is 17, 18, 19 or None, => vegetative (1)
- if stage is A => initiated (2)
- if stage is other => non defined (pourri, avorte, coupe) (-1)
- Terminal vegetative bud (1): label==bt g.property(Stade)== none or 17 or 18 or 19
- Terminal initiated bud (2): label== bt if g.property(Stade) == A
- Terminal Floral bud (3): label==ht
- Inflorescence Terminal (4): label== HT
- runner (5): label = s
:param vid: vid for which the function is applied
:type vid: int
:param g: MTG
:type g: MTG
:return: The crown status
:rtype: int
"""
stages = property(g,'Stade')
# select s, ht, HT et bt
for cid in g.components(vid):
if g.label(cid) in ('s', 'ht', 'HT', 'bt'):
label = g.label(cid)
if label == 'ht':
return 3
elif label == 'HT':
return 4
elif label == 'bt':
stage = stages.get(cid)
if stage == 'A':
return 2
elif stage in (None, '17', '18', '19'):
return 1
elif label == 's':
return 5
return -1
[docs]def nb_inflorescence (Vid, g):
"""Return the number of inflorescence
:param vid: vid for which the function is applied
:type vid: int
:param g: MTG
:type g: MTG
:return: The number of inflorescence
:rtype: int
"""
return sum(1 for cid in g.components(Vid) if g.label(cid)=='HT')
#TODO: Remove
[docs]def genotype(vid, g):
"""Return the genotype of the selected vids in the mtg
:param vid: vid selected
:type vid: int
:param g: MTG
:type g: MTG
:return: The genotype
:rtype: string
"""
cpx = g.complex_at_scale(vid, scale=1)
_genotype = property(g, 'Genotype')[cpx]
return _genotype
[docs]def plant(vid, g):
"""Return the plant id of the selected vid in the mtg
:param vid: vid selected
:type vid: int
:param g: MTG
:type g: MTG
:return: The plant id
:rtype: string
"""
cpx = g.complex_at_scale(vid, scale=1)
return property(g, 'Plante')[cpx]
[docs]def date(vid, g):
"""Return the date of the selected vid in the mtg
:param vid: vid selected
:type vid: int
:param g: MTG
:type g: MTG
:return: The date
:rtype: string
"""
cpx = g.complex_at_scale(vid, scale=1)
_date = property(g, 'date')[cpx]
return(_date)
[docs]def modality(vid, g):
"""Return the modality of the selected vid in the mtg
:param vid: vid selected
:type vid: int
:param g: MTG
:type g: MTG
:return: The modality
:rtype: string
"""
cpx = g.complex_at_scale(vid, scale=1)
_modality = property(g, 'Modality')[cpx]
return(_modality)
[docs]def compute_leaf_area(g, vids=[]):
"""Compute the leaf area of the selected MTG
:param g: MTG
:type g: MTG
:param vids: list of vids at scale 3, defaults to []
:type vids: list, optional
:return: A dict of leaf area (key is vid)
:rtype: dict
"""
_central= g.property("LFTLG_CENTRAL")
_left= g.property("LFTLG_LEFT")
_mean_leaf_area= g.property("LFAR")
for v in _central:
pid = g.complex_at_scale(v, scale=1)
if pid not in vids:
continue
central = _central.get(v)
left = _left.get(v)
if (central is None) or (left is None):
print(("DATA is missing on vertex %d for line %d"%(v, g.node(v)._line)))
continue
_mean_leaf_area[pid]= round(1.89 + (2.145 * (central/10) * (left/10)),2)
return _mean_leaf_area
[docs]def mean_leaf_area(vid,g):
"""Compute the mean leaf area
:param vid: selected vids
:type vid: int
:param g: MTG
:type g: MTG
:return: the mean leaf area of the selected vid
:rtype: float
"""
pid = g.complex_at_scale(vid, scale=1)
area = g.property("LFAR").get(pid, 0.)
return area
def complete(vid, g):
"""Returns the 'complete' state of the vid
:param vid: vid selected
:type vid: int
:param g: MTG
:type g: MTG
:return: 'complete' state
:rtype: boolean
"""
return g.property("complete").get(vid, False)
[docs]def stage(vid, g):
_stage = g.property('Stade')
return next((_stage[cid] for cid in g.components(vid) if cid in _stage), None)
# Data transformation
[docs]def occurence_module_order_along_time(data, frequency_type):
"""Compute the occurence module order along time
:param data: data extracted at module scale
:type data: pd.DataFrame
:param frequency_type: type of distribution frequency distribution (freq), probability distribution frequency (pbf) or cumulative frequency distribution (cdf)
:type frequency_type: str
:return: A dataframe with frequency, probability or cumulative frequency distribution for each module order along time
:rtype: pd.DataFrame
"""
if frequency_type == "freq":
res = pd.crosstab(index= data["order"], columns= data["date"], margins = True)
if frequency_type == "pdf":
res = pd.crosstab(index= data["order"], columns= data["date"], normalize = "columns")
if frequency_type == "cdf":
res = pd.crosstab(index= data["order"], columns= data["date"], normalize = "columns").cumsum()
return res
# Extraction on node scale
[docs]def my_bt(vid, g):
"""Return branching type on parent if branch crown correspond to Son vertex
:param vid: vid selected
:type vid: int
:param g: MTG
:type g: MTG
:return: the branching type on parent
:rtype: string
"""
for cid in g.Sons(vid, EdgeType='+'):
return str(branching_type(cid,g))
[docs]def complete(vid, g):
"""Add property complete or not on mtg
:param vid: vid selected
:type vid: int
:param g: MTG
:type g: MTG
:return: The 'complete' state of the vid
:rtype: boolean
"""
return g.property("complete").get(vid, False)
[docs]def my_complete(vid, g):
"""Return complete module, incomplete module or other (if not branch crown)
:param vid: vid selected
:type vid: int
:param g: MTG
:type g: MTG
:return: 'Complete' state
:rtype: string
"""
# scale = 2
_complete = g.property('complete')
if not complete:
complete_module(g)
_complete = g.property('complete')
# print(_complete)
res = 'other'
for cid in g.Sons(vid, EdgeType='+'):
cpx = g.complex(cid)
bt = branching_type(cid,g)
if bt == 6:
res = 'complete' if complete(cpx,g) else 'incomplete'
break
return res
[docs]def apparent_axis(g, vid):
"""Return apparent axis if module are visible
:param g: MTG
:type g: MTG
:param vid: vid selected
:type vid: int
:yield: generator of axis
:rtype: int
"""
visibles = g.property('visible')
v = vid
while v is not None:
yield v
vtx = v; v = None
for vid in g.children(vtx):
if (vid in visibles) and (not is_axis_root(g, vid)):
v = vid
[docs]def is_axis_root(g, vid):
"""Checks if axis is root
:param g: MTG
:type g: MTG
:param vid: vid selected
:type vid: int
:return: Is the axis a root
:rtype: boolean
"""
cid = next(g.component_roots_iter(vid))
pid = g.parent(cid)
sid = g.Successor(pid)
if g.label(sid) not in ('bt', 'ht', 'HT'):
return True
else:
return False
[docs]def branching_type(vid, g):
"""Returns the type of branching
:Algorithms:
if module is visible:
- branch crown (complex ramification):6
- inflorescence : 7
if module is invisible:
- stolon (s): 1,
- vegetative bud(bt, at stage None, 17,18,19):2,
- initiated bud, (bt, at stage A):3,
- aborted or roten or dried bud: 4
- floral bud(ht):5
:param vid: vid selected
:type vid: int
:param g: MTG
:type g: MTG
:return: The branching type
:rtype: int
"""
cpx = g.complex(vid)
nid = g.node(cpx)
if nid.visible:
if g.label(vid) == 'HT':
return 7
else:
return 6
# select s, ht, et bt
for cid in nid.components():
label = cid.label
if label in ('s', 'ht', 'bt'):
if label == 's':
return 1
elif label == 'bt':
stage = cid.Stade
if stage in (None, '17', '18', '19'):
return 2
elif stage == 'A':
return 3
elif stage in ('pourri', 'aborted', 'dried'):
return 4
elif label == 'ht':
return 5
else:
return -1
# print(('ERROR: ', cpx, nid.complex().Genotype, nid.properties()))
DEBUG = True
[docs]def module_tree(v, g):
"""Return the list of vid of module tree
:param v: selected vid
:type v: int
:param g: MTG
:type g: MTG
:return: List of module tree
:rtype: list
"""
# _complete = g.property('complete')
# if not complete:
# complete_module(g)
# _complete = g.property('complete')
for cid in g.Sons(v, EdgeType='+'):
cpx = g.complex(cid)
visibles = g.property('visible')
if DEBUG:
if cpx in visibles and (is_axis_root(g, cpx)):
return [m for m in traversal.pre_order2(g, cpx) if m in visibles]
else:
bt = branching_type(cid,g)
if bt == 6:
return [m for m in traversal.pre_order2(g, cpx) if m in visibles]
[docs]def nb_total_module_tree(v, g):
"""Compute the number of total module tree
:param v: selected vid
:type v: int
:param g: MTG
:type g: MTG
:return: total number of module tree
:rtype: int
"""
if not module_tree(v,g):
return 0
else:
return len(module_tree(v, g))
[docs]def nb_branching_tree(v, g):
"""Compute the number of branching tree
:param v: selected vid
:type v: int
:param g: MTG
:type g: MTG
:return: number of branching tree
:rtype: int
"""
if not module_tree(v, g):
return 0
else:
return sum(1 for m in module_tree(v, g) if is_axis_root(g, m))
[docs]def nb_branching_tree_weight(v, g):
"""Compute the number of branching tree weight
:param v: selected vid
:type v: int
:param g: MTG
:type g: MTG
:return: number of branching tree weight
:rtype: int
"""
if not module_tree(v, g):
return 0
else:
return sum(g.nb_components(m) for m in module_tree(v, g) if is_axis_root(g, m))
[docs]def nb_extension_tree(v, g):
"""Compute the number of extension tree
:param v: selected vid
:type v: int
:param g: MTG
:type g: MTG
:return: number of extension tree
:rtype: int
"""
if not module_tree(v, g):
return 0
else:
return sum(1 for m in module_tree(v, g) if not is_axis_root(g, m))
[docs]def nb_visible_leaves_tree(v, g):
"""Compute the number of visible leaves tree
:param v: selected vid
:type v: int
:param g: MTG
:type g: MTG
:return: number of visible leaves tree
:rtype: int
"""
if not module_tree(v, g):
return 0
else:
return sum(nb_visible_leaves(m,g) for m in module_tree(v, g))
[docs]def stage_tree(vid, g):
return list(stage(m,g) for m in module_tree(v, g))
[docs]def prob_axillary_production(g, order=None, vids=[],frequency=False):
"""Probability of axillary production as function of node rank
:param g: mtg
:type g: MTG
:param order: order selected (order= None all module orders are selected), defaults to None
:type order: str, optional
:param vids: vid selected, defaults to []
:type vids: list, optional
:param frequency: frequency selected, defaults to False
:type frequency: bool, optional
:return: A dataframe with the probability of axillary production for each node
:rtype: pd.DataFrame
"""
if not vids:
vids = g.vertices(scale=1)
df=extract_at_node_scale(g, vids=vids)
if order is not None:
df=df[df["order"]==order]
# Value conversion
df["branching_type"]= df["branching_type"].replace(["1","2","3","4","5","6","7"],["S","VB","IB","AB","FB","BC","Inflorescence"])
# pandas crosstab data
if frequency==True:
data= pd.crosstab(df["rank"],df["branching_type"])
else:
data=pd.crosstab(df["rank"],df["branching_type"],normalize="index")
return data
# Transformation of dataframe
[docs]def df2waffle(df, date, index, variable, order=None, aggfunc=None, crosstab=None, *args, **kwargs):
"""Transpose dataframe by variable with plant in columns and rank or order in index
This function are available for extraction at node scale (index='rank') and
extraction at module scale (index= 'order')
:param df: dataframe from extract function at differente scale (modules and nodes scale)
:type df: pd.DataFrame
:param date: date which must be processed
:type date: str
:param index: scale of the representation (node > 'rank', module > 'order')
:type index: str
:param variable: variable which must be processed
:type variable: str
:param order: at module scale, give a specific module to be processed , defaults to None
:type order: int, optional
:param aggfunc: required if the data need to be grouped quantitative: "mean", "median" | qualitative: lambda x: ' '.join(x), defaults to None
:type aggfunc: str, optional
:param crosstab: make a crosstab selection of the input data, defaults to None
:type crosstab: boolean, optional
:return: a dataframe in "waffle" shape: index=date, & columns=variable
:rtype: pd.DataFrame
"""
if order:
data=df[(df['date']==date) & (df['order']==order)]
else:
data=df[df['date']==date]
if index=='rank':
res = data.pivot(index='rank',columns='plant',values=variable)
elif index=='order':
if crosstab:
res = pd.crosstab(index=data['order'], columns=data[variable], normalize='index')
res=res*100
res = res.round(2)
else:
# Catch data error: when values are string and aggfunc compute numbers
try:
res= data.pivot_table(index='order',columns='plant',values=variable, aggfunc=aggfunc)
except DataError:
print("ERROR, the aggregate function does not handle the data type (float func on str?)")
return pd.DataFrame()
else:
res = data.pivot(index=index,columns='plant',values=variable)
# If use plotly heatmap -> comment "res = res.fillna('')"
if res.isnull().values.any():
res = res.fillna('')
res = res.sort_index(ascending=False)
return res
[docs]def plot_waffle_plotly_heatmap(df, layout={}, legend_name={}):
def df_to_plotly(df):
return {'z': df.values.tolist(),
'x': df.columns.tolist(),
'y': df.index.tolist()}
height = layout.get('height', 500)
width = layout.get('width', 500)
xlabel = layout.get('xlabel', 'Plant')
xticks = layout.get('xticks', range(0,len(df.columns)))
xticks_label = layout.get('xticks_label', list(df.columns))
ylabel = layout.get('ylabel', '')
yticks = layout.get('yticks', [l-1 for l in list(df.index)])
yticks_label = layout.get('yticks_label', list(range(0,len(df.index))))
title = layout.get('title', '')
hm_layout = go.Layout(plot_bgcolor='rgba(0,0,0,0)',
# xaxis=dict(zeroline=False),
# yaxis=dict(zeroline=False, ),
autosize=False,
width=width, height=height
)
data = go.Heatmap(df_to_plotly(df),
xgap=1,
ygap=1,
colorscale="aggrnyl"
)
fig = go.Figure(data=data, layout=hm_layout)
return fig
[docs]def plot_waffle_plotly_imshow(df, layout={}, legend_name={}):
colormap_used = plt.cm.coolwarm
values = list(set(df.values.flatten()))
if '' in values:
values.remove('')
try:
values.sort()
except TypeError:
values = [str(i) for i in values]
values.sort()
values.insert(0,'')
color_map = {val: colormap_used(i/len(values)) for i, val in enumerate(values)}
# Add the "empty" variable - and set its color as white
color_map[''] = (1., 1., 1., 1.)
data = np.array(df)
# Create an array where each cell is a colormap value RGBA
data_3d = np.ndarray(shape=(data.shape[0], data.shape[1], 4), dtype=float)
for i in range(0, data.shape[0]):
for j in range(0, data.shape[1]):
data_3d[i][j] = color_map[data[i][j]]
# drop the A
data_3d_rgb = np.array([[to_rgb([v for v in row]) for row in col] for col in data_3d], dtype=np.float64)
yticks = list(range(0,data.shape[0]))
yticks.reverse()
fig = px.imshow(data,
labels={'x':'Plant', 'y':'Node'},
x=list(range(1,data.shape[1]+1)),
y=yticks,
origin='lower',
color_continuous_scale='aggrnyl',
# colorbar={}
)
fig.update_layout(plot_bgcolor='rgba(0,0,0,0)',
)
return fig
[docs]def plot_waffle_matplotlib(df, layout={}, legend_name={}):
height = layout.get('height', 18.5)
width = layout.get('width', 10.5)
xlabel = layout.get('xlabel', 'Plant')
xticks = layout.get('xticks', range(0,len(df.columns)))
xticks_label = layout.get('xticks_label', list(df.columns))
ylabel = layout.get('ylabel', '')
yticks = layout.get('yticks', [l-1 for l in list(df.index)])
yticks_label = layout.get('yticks_label', list(range(0,len(df.index))))
title = layout.get('title', '')
colormap_used = plt.cm.coolwarm
# Sort the variables. When variables are int or float, remove the str('') (that replaced the NaN) before sorting
values = list(set(df.values.flatten()))
if '' in values:
values.remove('')
try:
values.sort()
except TypeError:
values = [str(i) for i in values]
values.sort()
values.insert(0,'')
w_height = len(df.index)
w_width = len(df.columns)
color_map = {val: colormap_used(i/len(values)) for i, val in enumerate(values)}
# Add the "empty" variable - and set its color as white
color_map[''] = (1., 1., 1., 1.)
data = np.array(df)
# Create an array where each cell is a colormap value RGBA
data_3d = np.ndarray(shape=(data.shape[0], data.shape[1], 4), dtype=float)
for i in range(0, data.shape[0]):
for j in range(0, data.shape[1]):
data_3d[i][j] = color_map[data[i][j]]
# display the plot
fig, ax = plt.subplots(1,1)
fig.set_size_inches(height, width)
fig = ax.imshow(data_3d)
# Get the axis.
ax = plt.gca()
# Minor ticks
ax.set_xticks(np.arange(-.5, (w_width), 1), minor=True);
ax.set_yticks(np.arange(-.5, (w_height), 1), minor=True);
# Gridlines based on minor ticks
ax.grid(which='minor', color='w', linestyle='-', linewidth=2)
# Manually constructing a legend solves your "catagorical" problem.
legend_handles = []
for i, val in enumerate(values):
if val!= "":
color_val = color_map[val]
legend_handles.append(mpatches.Patch(color=color_val, label=legend_name.get(val, val)))
# Add the legend.
plt.legend(handles=legend_handles, loc=(1,0))
plt.xlabel(xlabel)
plt.ylabel(ylabel)
plt.xticks(ticks=xticks, labels=xticks_label)
plt.yticks(ticks=yticks, labels=yticks_label)
plt.title(title)
plt.show()
return fig
[docs]def plot_waffle(df, layout={}, legend_name={}, savepath=None, plot_func='matplotlib'):
"""
Plot a dataframe in "waffle" shape
layout: dict of layout parameters:
height/width: size of the picture in inch
x/ylabel: label of the x/y axis
x/yticks: ticks of the x/y axis
x/yticks_labels: labels of the ticks on the x/y axis
title: title
plot_func: library used for the ploting:
matplotlib: matplotlib.pyplot.subplot.imshow
plotly.imshow: plotly.express.imshow
plotly.heatmap: plotly.graph_objs.heatmap
"""
## Axes not working - Plotly heatmap
if plot_func=='plotly.heatmap':
fig= plot_waffle_plotly_heatmap(df=df, layout=layout, legend_name=legend_name)
# Plotly imshow
elif plot_func=='plotly.imshow':
fig= plot_waffle_plotly_imshow(df=df, layout=layout, legend_name=legend_name)
# With matplotlib
elif plot_func=='matplotlib':
try:
fig= plot_waffle_matplotlib(df=df, layout=layout, legend_name=legend_name)
except ValueError:
fig={}
if savepath:
plt.savefig(savepath)
return fig
[docs]def plot_pie(df):
return px.pie(df, values=df.mean(axis=0), names=df.columns)