diff --git a/PyCTBN/PyCTBN/structure_graph/network_generator.py b/PyCTBN/PyCTBN/structure_graph/network_generator.py index 0a84669..3930607 100644 --- a/PyCTBN/PyCTBN/structure_graph/network_generator.py +++ b/PyCTBN/PyCTBN/structure_graph/network_generator.py @@ -3,6 +3,7 @@ from .network_graph import NetworkGraph from .conditional_intensity_matrix import ConditionalIntensityMatrix from .set_of_cims import SetOfCims import numpy as np +import pandas as pd import os import json @@ -105,44 +106,13 @@ class NetworkGenerator(object): return self._graph @property - def cims(self) -> NetworkGraph: + def cims(self) -> dict: return self._cims @property def dyn_str(self) -> list: - return [{"From": edge[0], "To": edge[1]} for edge in self._graph.edges] + return pd.DataFrame([[edge[0], edge[1]] for edge in self._graph.edges], columns = ["From", "To"]) @property def variables(self) -> list: - return [{"Name": l, "Value": self._vals[i]} for i, l in enumerate(self._labels)] - - """Restructure the CIMs object in order to fit the standard JSON file structure - """ - @property - def dyn_cims(self) -> dict: - dyn_cims = {} - - for i, l in enumerate(self._labels): - dyn_cims[l] = {} - parents = self._graph.get_ordered_by_indx_set_of_parents(l)[0] - for j, comb in enumerate(self._cims[l].p_combs): - comb_key = "" - if len(parents) != 0: - for k, val in enumerate(comb): - comb_key += parents[k] + "=" + str(val) - if k < len(comb) - 1: - comb_key += "," - else: - comb_key = l - - cim = self._cims[l].filter_cims_with_mask(np.array([True for p in parents]), comb) - if len(parents) == 1: - cim = cim[comb[0]].cim - elif len(parents) == 0: - cim = cim[0].cim - else: - cim = cim[0].cim - - dyn_cims[l][comb_key] = [dict([(str(i), val) for i, val in enumerate(row)]) for row in cim] - - return dyn_cims \ No newline at end of file + return pd.DataFrame([[l, self._vals[i]] for i, l in enumerate(self._labels)], columns = ["Name", "Value"]) \ No newline at end of file diff --git a/PyCTBN/PyCTBN/structure_graph/trajectory_generator.py b/PyCTBN/PyCTBN/structure_graph/trajectory_generator.py index 42fb095..50262a2 100644 --- a/PyCTBN/PyCTBN/structure_graph/trajectory_generator.py +++ b/PyCTBN/PyCTBN/structure_graph/trajectory_generator.py @@ -33,14 +33,14 @@ class TrajectoryGenerator(object): self._importer = importer - self._vnames = self._importer._df_variables.iloc[:, 0].to_list() if importer is not None else [v["Name"] for v in variables] + self._vnames = self._importer._df_variables.iloc[:, 0].to_list() if importer is not None else variables.iloc[:, 0].to_list() self._parents = {} for v in self._vnames: if importer is not None: self._parents[v] = self._importer._df_structure.where(self._importer._df_structure["To"] == v).dropna()["From"].tolist() else: - self._parents[v] = [edge["From"] for edge in dyn_str if edge["To"] == v] + self._parents[v] = dyn_str.where(dyn_str["To"] == v).dropna()["From"].tolist() self._cims = {} sampled_cims = self._importer._raw_data[0]["dyn.cims"] if importer is not None else dyn_cims @@ -56,8 +56,8 @@ class TrajectoryGenerator(object): sof = SetOfCims(node_id = v, parents_states_number = [self._importer._df_variables.where(self._importer._df_variables["Name"] == p)["Value"] for p in self._parents[v]], node_states_number = self._importer._df_variables.where(self._importer._df_variables["Name"] == v)["Value"], p_combs = np.array(p_combs), cims = v_cims) else: - sof = SetOfCims(node_id = v, parents_states_number = [[variable["Value"] for variable in variables if variable["Name"] == p][0] for p in self._parents[v]], - node_states_number = [variable for variable in variables if variable["Name"] == v][0]["Value"], p_combs = np.array(p_combs), cims = v_cims) + sof = SetOfCims(node_id = v, parents_states_number = [variables.where(variables["Name"] == p)["Value"] for p in self._parents[v]], + node_states_number = variables.where(variables["Name"] == v)["Value"], p_combs = np.array(p_combs), cims = v_cims) self._cims[v] = sof def CTBN_Sample(self, t_end = -1, max_tr = -1): diff --git a/PyCTBN/PyCTBN/utility/abstract_exporter.py b/PyCTBN/PyCTBN/utility/abstract_exporter.py new file mode 100644 index 0000000..1c7c237 --- /dev/null +++ b/PyCTBN/PyCTBN/utility/abstract_exporter.py @@ -0,0 +1,42 @@ +import json +import pandas as pd +import os +from abc import ABC, abstractmethod + +class AbstractExporter(ABC): + """Provides the methods to save in json format a network information + along with one or more trajectories generated basing on it + + :param _variables: Dataframe containing the nodes labels and cardinalities + :type _variables: pandas.DataFrame + :param _dyn_str: Dataframe containing the structure of the network (edges) + :type _dyn_str: pandas.DataFrame + :param _dyn_cims: It contains, for every variable (label is the key), the SetOfCims object related to it + :type _dyn_cims: dict + :param _trajectories: List of trajectories, that can be added subsequently + :type _trajectories: List + """ + + def __init__(self, variables: pd.DataFrame = None, dyn_str: pd.DataFrame = None, dyn_cims: dict = None): + self._variables = variables + self._dyn_str = dyn_str + self._dyn_cims = dyn_cims + self._trajectories = [] + + def add_trajectory(self, trajectory: list): + """Add a new trajectory to the current list + + :param trajectory: The trajectory to add + :type trajectory: pandas.DataFrame + """ + + self._trajectories.append(trajectory) + + @abstractmethod + def out_file(self, filename): + """Create a file in current directory and write on it the previously added data + (variables, dyn_str, dyn_cims and trajectories) + + :param filename: Name of the output file (it must include json extension) + :type filename: string + """ \ No newline at end of file diff --git a/PyCTBN/PyCTBN/utility/json_exporter.py b/PyCTBN/PyCTBN/utility/json_exporter.py index 688d6aa..43b11da 100644 --- a/PyCTBN/PyCTBN/utility/json_exporter.py +++ b/PyCTBN/PyCTBN/utility/json_exporter.py @@ -1,37 +1,31 @@ import json import pandas as pd +import numpy as np import os -class JsonExporter(object): +from .abstract_exporter import AbstractExporter + +class JsonExporter(AbstractExporter): """Provides the methods to save in json format a network information along with one or more trajectories generated basing on it - :param _variables: List of dictionaries, representing the variables in the network and their cardinality - :type _variables: List - :param _dyn_str: List of dictionaries, each of which represents an edge ({"From": "", "To": ""}) - :type _dyn_str: List - :param _dyn_cims: It contains, for every variable (label is the key), the CIM values related to it - :type _dyn_cims: Dict + :param _variables: Dataframe containing the nodes labels and cardinalities + :type _variables: pandas.DataFrame + :param _dyn_str: Dataframe containing the structure of the network (edges) + :type _dyn_str: pandas.DataFrame + :param _dyn_cims: It contains, for every variable (label is the key), the SetOfCims object related to it + :type _dyn_cims: dict :param _trajectories: List of trajectories, that can be added subsequently :type _trajectories: List """ - def __init__(self, variables, dyn_str, dyn_cims): + def __init__(self, variables: pd.DataFrame = None, dyn_str: pd.DataFrame = None, dyn_cims: dict = None): self._variables = variables self._dyn_str = dyn_str self._dyn_cims = dyn_cims self._trajectories = [] - def add_trajectory(self, trajectory: list): - """Add a new trajectory to the current list - - :param trajectory: The trajectory to add. It must already be in json form, and not as pandas.DataFrame - :type trajectory: List - """ - - self._trajectories.append(trajectory) - - def out_json(self, filename): + def out_file(self, filename): """Create a file in current directory and write on it the previously added data (variables, dyn_str, dyn_cims and trajectories) @@ -40,12 +34,42 @@ class JsonExporter(object): """ data = [{ - "dyn.str": self._dyn_str, - "variables": self._variables, - "dyn.cims": self._dyn_cims, - "samples": self._trajectories + "dyn.str": json.loads(self._dyn_str.to_json(orient="records")), + "variables": json.loads(self._variables.to_json(orient="records")), + "dyn.cims": self.cims_to_json(), + "samples": [json.loads(trajectory.to_json(orient="records")) for trajectory in self._trajectories] }] path = os.getcwd() with open(path + "/" + filename, "w") as json_file: - json.dump(data, json_file) \ No newline at end of file + json.dump(data, json_file) + + """Restructure the CIMs object in order to fit the standard JSON file structure + """ + def cims_to_json(self) -> dict: + json_cims = {} + + for i, l in enumerate(self._variables.iloc[:, 0].to_list()): + json_cims[l] = {} + parents = self._dyn_str.where(self._dyn_str["To"] == l).dropna()["From"].tolist() + for j, comb in enumerate(self._dyn_cims[l].p_combs): + comb_key = "" + if len(parents) != 0: + for k, val in enumerate(comb): + comb_key += parents[k] + "=" + str(val) + if k < len(comb) - 1: + comb_key += "," + else: + comb_key = l + + cim = self._dyn_cims[l].filter_cims_with_mask(np.array([True for p in parents]), comb) + if len(parents) == 1: + cim = cim[comb[0]].cim + elif len(parents) == 0: + cim = cim[0].cim + else: + cim = cim[0].cim + + json_cims[l][comb_key] = [dict([(str(i), val) for i, val in enumerate(row)]) for row in cim] + + return json_cims \ No newline at end of file diff --git a/example.py b/example.py index ee03f34..aa0ceeb 100644 --- a/example.py +++ b/example.py @@ -23,11 +23,11 @@ ng.generate_cims(cim_min, cim_max) # Trajectory Generation print(ng.dyn_str) -e1 = JsonExporter(ng.variables, ng.dyn_str, ng.dyn_cims) -tg = TrajectoryGenerator(variables = ng.variables, dyn_str = ng.dyn_str, dyn_cims = ng.dyn_cims) -sigma = tg.CTBN_Sample(max_tr = 100) -e1.add_trajectory(tg.to_json()) -e1.out_json("example.json") +e1 = JsonExporter(ng.variables, ng.dyn_str, ng.cims) +tg = TrajectoryGenerator(variables = ng.variables, dyn_str = ng.dyn_str, dyn_cims = e1.cims_to_json()) +sigma = tg.CTBN_Sample(max_tr = 30000) +e1.add_trajectory(sigma) +e1.out_file("example.json") # Network Estimation (Constraint Based) importer = JsonImporter(file_path="example.json", samples_label='samples',