SankeyExcelParser 1.0.0b0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. SankeyExcelParser/__init__.py +0 -0
  2. SankeyExcelParser/io_excel.py +1867 -0
  3. SankeyExcelParser/io_excel_constants.py +811 -0
  4. SankeyExcelParser/sankey.py +3138 -0
  5. SankeyExcelParser/sankey_utils/__init__.py +0 -0
  6. SankeyExcelParser/sankey_utils/data.py +1118 -0
  7. SankeyExcelParser/sankey_utils/excel_source.py +31 -0
  8. SankeyExcelParser/sankey_utils/flux.py +344 -0
  9. SankeyExcelParser/sankey_utils/functions.py +278 -0
  10. SankeyExcelParser/sankey_utils/node.py +340 -0
  11. SankeyExcelParser/sankey_utils/protos/__init__.py +0 -0
  12. SankeyExcelParser/sankey_utils/protos/flux.py +84 -0
  13. SankeyExcelParser/sankey_utils/protos/node.py +386 -0
  14. SankeyExcelParser/sankey_utils/protos/sankey_object.py +135 -0
  15. SankeyExcelParser/sankey_utils/protos/tag_group.py +95 -0
  16. SankeyExcelParser/sankey_utils/sankey_object.py +165 -0
  17. SankeyExcelParser/sankey_utils/table_object.py +37 -0
  18. SankeyExcelParser/sankey_utils/tag.py +95 -0
  19. SankeyExcelParser/sankey_utils/tag_group.py +206 -0
  20. SankeyExcelParser/su_trace.py +239 -0
  21. SankeyExcelParser/tests/integration/__init__.py +0 -0
  22. SankeyExcelParser/tests/integration/test_base.py +356 -0
  23. SankeyExcelParser/tests/integration/test_run_check_input.py +100 -0
  24. SankeyExcelParser/tests/integration/test_run_conversions.py +96 -0
  25. SankeyExcelParser/tests/integration/test_run_load_input.py +94 -0
  26. SankeyExcelParser/tests/unit/__init__.py +0 -0
  27. SankeyExcelParser-1.0.0b0.data/scripts/run_parse_and_write_excel.py +155 -0
  28. SankeyExcelParser-1.0.0b0.data/scripts/run_parse_excel.py +115 -0
  29. SankeyExcelParser-1.0.0b0.dist-info/METADATA +113 -0
  30. SankeyExcelParser-1.0.0b0.dist-info/RECORD +32 -0
  31. SankeyExcelParser-1.0.0b0.dist-info/WHEEL +5 -0
  32. SankeyExcelParser-1.0.0b0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,31 @@
1
+ """
2
+ Author : Vincent LE DOZE
3
+ Date : 31/05/23
4
+
5
+ This file contains description for ExcelSource class
6
+
7
+ """
8
+
9
+ # External libs ----------------------------------------------------------------
10
+ import pandas as pd
11
+
12
+
13
+ # CLASS ----------------------------------------------------------------------------
14
+ class ExcelSource(object):
15
+ user_sheet_name: str
16
+ sheet_type: str
17
+ _table: pd.DataFrame
18
+
19
+ def __init__(
20
+ self,
21
+ user_sheet_name: str,
22
+ sheet_type: str,
23
+ ):
24
+ self.user_sheet_name = user_sheet_name
25
+ self.sheet_type = sheet_type
26
+
27
+ def save_origin_table(
28
+ self,
29
+ table: pd.DataFrame
30
+ ):
31
+ self.table = table
@@ -0,0 +1,344 @@
1
+ """
2
+ Author : Vincent LE DOZE
3
+ Date : 31/05/23
4
+
5
+ This file contains descriptions for Flux class
6
+
7
+ """
8
+
9
+ # Local modules -----------------------------------------------------
10
+ from SankeyExcelParser.sankey_utils.protos.flux import _ProtoFlux
11
+ from SankeyExcelParser.sankey_utils.data import Data
12
+ from SankeyExcelParser.sankey_utils.data import DataConstraint
13
+ from SankeyExcelParser.sankey_utils.data import DataMinMax
14
+ from SankeyExcelParser.sankey_utils.data import MCData
15
+ from SankeyExcelParser.sankey_utils.node import Node
16
+
17
+
18
+ # CLASS ----------------------------------------------------------------------------
19
+ class Flux(_ProtoFlux):
20
+ """
21
+ Define a flux.
22
+ Inherits from `_ProtoFlux`
23
+
24
+ Parameters
25
+ ----------
26
+ :param orig: Flux starting node
27
+ :type orig: Node
28
+
29
+ :param dest: Flux ending node
30
+ :type dest: Node
31
+
32
+ :param datas: All datas for the flux
33
+ :type datas: list [Data, ...]
34
+
35
+ :param results: All results for the flux
36
+ :type results: list [Data, ...]
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ orig: Node,
42
+ dest: Node,
43
+ **kwargs
44
+ ):
45
+ # Init super constructor
46
+ _ProtoFlux.__init__(self, orig, dest)
47
+ self._orig.add_output_flux(self)
48
+ self._dest.add_input_flux(self)
49
+ # Datas
50
+ self._datatags_combinations = []
51
+ self._datas = []
52
+ self._results = []
53
+ self._monte_carlo = None
54
+ # Init contraints values for all datas
55
+ self._min_max = DataMinMax(self)
56
+ self._max = None
57
+ self._constraints = {}
58
+ # Update values
59
+ self.update(**kwargs)
60
+
61
+ def update(
62
+ self,
63
+ **kwargs
64
+ ):
65
+ for key, value in kwargs.items():
66
+ setattr(self, key, value)
67
+
68
+ @property
69
+ def datas(self):
70
+ return self._datas
71
+
72
+ def add_data(self, _):
73
+ if type(_) is float:
74
+ data = Data(value=_)
75
+ self._datas.append(data)
76
+ data.flux = self
77
+ return
78
+ if type(_) is Data:
79
+ self._datas.append(_)
80
+ _.flux = self
81
+ return
82
+
83
+ def has_data(self):
84
+ return len(self._datas) > 0
85
+
86
+ def instanciate_all_datas(
87
+ self,
88
+ data_taggs={}
89
+ ):
90
+ """
91
+ Create data according to data tags presents in a table line.
92
+ Data can only have one tag for every given data tag group.
93
+ If multiple tag are given, then we must create one data for each one and link them
94
+ to the reference flux.
95
+
96
+ Parameters
97
+ ----------
98
+ :param data_taggs: List of data tags
99
+ :type data_taggs: dict as {key=TagGroup.name: value=TagGroup, ...} . (default={})
100
+
101
+ Returns
102
+ -------
103
+ :return: List of created datas
104
+ :rtype: list as [Data, ...]
105
+ """
106
+ # If no data tag -> Create only one data
107
+ if len(data_taggs) == 0:
108
+ # Add only one data
109
+ self.add_data(Data())
110
+ # Add empty datatags combinations
111
+ self._datatags_combinations.append([])
112
+ return
113
+ # Otherwise create data recursivly
114
+ self._recursive_instanciate_all_datas(
115
+ [list(_.tags.values()) for _ in data_taggs.values()])
116
+
117
+ def _recursive_instanciate_all_datas(
118
+ self,
119
+ data_tags_per_tagg,
120
+ data_tags_per_datas=[[]]
121
+ ):
122
+ """
123
+ Create data recursivly according to data tags presents in a table line.
124
+ Data can only have one tag for every given data tag group.
125
+ If multiple tag are given, then we must create one data for each one and link them
126
+ to the reference flux.
127
+
128
+ Parameters
129
+ ----------
130
+ :param data_tags_per_tagg: List of data tags regrouped per data tag groups.
131
+ :type data_tags_per_tagg: list as [list as [Tag, ...], ...]
132
+
133
+ :param data_tags_per_datas: Must not be touch. Used for recursivity
134
+ :type data_tags_per_datas: list (default=[[]])
135
+
136
+ Returns
137
+ -------
138
+ :return: List of created datas
139
+ :rtype: list as [Data, ...]
140
+ """
141
+ # Check if we arrived at the end of recursivity on data tags
142
+ if len(data_tags_per_tagg) == 0:
143
+ # Create unique data for each data tag per data tag groups
144
+ for data_tags in data_tags_per_datas:
145
+ # Create Data
146
+ data = Data()
147
+ # Add tags
148
+ for data_tag in data_tags:
149
+ data.add_tag(data_tag)
150
+ # Add data to flux
151
+ self.add_data(data)
152
+ # Save data_tags_per_datas as keys
153
+ self._datatags_combinations.append(data_tags.copy())
154
+ # Otherwise we continue to recurse
155
+ else:
156
+ # Get data tags related to datas to create and to a unique data tag group
157
+ data_tags = data_tags_per_tagg.pop()
158
+ # Create the list of unique data tags for each data
159
+ all_data_tags_per_datas = []
160
+ for data_tag in data_tags:
161
+ # data_tags_per_datas = [[tag1_1, tag2_1], [tag1_1, tag2_2], [tag1_2, tag2_1], ...]
162
+ # with data tag group 1 = (tag1_1, tag1_2)
163
+ # data tag group 2 = (tag2_1, tag2_2)
164
+ # So, if we have data tag group 3 = (tag3_1, tag3_2, ...)
165
+ # we must copy each list from data_tags_per_datas and append tag3_1
166
+ # then make anothers copies to append tag3_2, etc.
167
+ new_data_tags_per_datas = []
168
+ for data_tags_per_data in data_tags_per_datas:
169
+ new_data_tags_per_data = data_tags_per_data.copy()
170
+ new_data_tags_per_data.append(data_tag)
171
+ new_data_tags_per_datas.append(new_data_tags_per_data)
172
+ all_data_tags_per_datas += new_data_tags_per_datas
173
+ # We recurse to deal with data tag groups
174
+ self._recursive_instanciate_all_datas(
175
+ data_tags_per_tagg,
176
+ data_tags_per_datas=all_data_tags_per_datas)
177
+
178
+ def get_corresponding_datas_from_tags(
179
+ self,
180
+ datatags_to_match,
181
+ fluxtags_to_match=[]
182
+ ):
183
+ """
184
+ Get a list of data that correspond to the input list of tags
185
+
186
+ Parameters
187
+ ----------
188
+ :param tags: list of tags to check
189
+ :type tags: list[Tag, ...]
190
+
191
+ Returns
192
+ -------
193
+ :return: List of corresponding datas
194
+ :rtype: list[Data, ...]
195
+ """
196
+ # Init list of matched datas
197
+ matched_datas = []
198
+ # Match // datatags
199
+ for datatags, data in zip(self._datatags_combinations, self._datas):
200
+ ok_match_datatags = False
201
+ # If all current datatags are contained in datatags to match list
202
+ if set(datatags_to_match).issuperset(set(datatags)):
203
+ ok_match_datatags = True
204
+ # If all datatags to match are contained in current datatags list
205
+ if set(datatags).issuperset(set(datatags_to_match)):
206
+ ok_match_datatags = True
207
+ # Check flux tags also
208
+ if ok_match_datatags:
209
+ # If flux tags are related to curren data
210
+ if set(data.tags).issuperset(set(fluxtags_to_match)):
211
+ matched_datas.append(data)
212
+ # Output
213
+ return matched_datas
214
+
215
+ @property
216
+ def results(self):
217
+ return self._results
218
+
219
+ def add_result(self, _):
220
+ if type(_) is float:
221
+ result = Data(value=_)
222
+ self._results.append(result)
223
+ result.flux = self
224
+ return
225
+ if type(_) is Data:
226
+ self._results.append(_)
227
+ _.flux = self
228
+ return
229
+
230
+ def has_result(self):
231
+ return len(self._results) > 0
232
+
233
+ def reset_results(self):
234
+ # remove altergo links with datas
235
+ for result in self._results:
236
+ result.alterego = None
237
+ # Empty list
238
+ self._results = []
239
+
240
+ def get_corresponding_results_from_tags(self, tags):
241
+ """
242
+ Get a list of data that correspond to the input list of tags
243
+
244
+ Parameters
245
+ ----------
246
+ :param tag: tag
247
+ :type tag: str | Tag
248
+
249
+ Returns
250
+ -------
251
+ :return: Corresponding data if it exist, else None
252
+ :rtype: Data | None
253
+ """
254
+ results = set(self._results)
255
+ for tag in tags:
256
+ results &= set(tag.references)
257
+ return list(results)
258
+
259
+ @property
260
+ def monte_carlo(self):
261
+ return self._monte_carlo
262
+
263
+ def add_monte_carlo(
264
+ self,
265
+ starting_mean_value,
266
+ starting_sigma,
267
+ result_mean_value,
268
+ result_sigma,
269
+ result_min,
270
+ result_max
271
+ ):
272
+ self._monte_carlo = MCData(flux=self)
273
+ self._monte_carlo.starting_data = Data(
274
+ value=starting_mean_value,
275
+ sigma=starting_sigma)
276
+ self._monte_carlo.result_data = Data(
277
+ value=result_mean_value,
278
+ sigma=result_sigma)
279
+ self._monte_carlo.min = result_min
280
+ self._monte_carlo.max = result_max
281
+
282
+ @property
283
+ def min_max(self):
284
+ return self._min_max
285
+
286
+ @property
287
+ def min(self):
288
+ return self._min_max.min_val
289
+
290
+ @min.setter
291
+ def min(self, _):
292
+ self._min_max.min = _
293
+
294
+ @property
295
+ def max(self):
296
+ return self._min_max.max_val
297
+
298
+ @max.setter
299
+ def max(self, _):
300
+ self._min_max.max = _
301
+
302
+ @property
303
+ def constraints(self):
304
+ return self._constraints
305
+
306
+ def add_constraint(self, id_constraint, **kwargs):
307
+ # Create a new piece of constraint
308
+ constraint = DataConstraint(id_constraint, self, **kwargs)
309
+ # Update constraint for given id
310
+ if id_constraint in self._constraints.keys():
311
+ self._constraints[id_constraint].append(constraint)
312
+ else:
313
+ self._constraints[id_constraint] = [constraint]
314
+ # Return constraint
315
+ return constraint
316
+
317
+ def get_as_dict(self):
318
+ # Init output
319
+ output = {}
320
+ # Get values
321
+ output['orig'] = self.orig.name
322
+ output['dest'] = self.dest.name
323
+ output['datas'] = []
324
+ for data in self.datas:
325
+ output['datas'].append(data.get_as_dict())
326
+ return output
327
+
328
+ def __repr__(self):
329
+ if self.has_data():
330
+ if len(self.datas) > 1:
331
+ return '{0} --- [{2}, ...] ---> {1}'.format(
332
+ self._orig.name,
333
+ self._dest.name,
334
+ self.datas[0])
335
+ else:
336
+ return '{0} --- {2} ---> {1}'.format(
337
+ self._orig.name,
338
+ self._dest.name,
339
+ self.datas[0])
340
+ else:
341
+ return '{0} --- {2} ---> {1}'.format(
342
+ self._orig.name,
343
+ self._dest.name,
344
+ 'No data')
@@ -0,0 +1,278 @@
1
+ """
2
+ Author : Vincent LE DOZE
3
+ Date : 31/05/23
4
+
5
+ This file contains functions used in sankey_utils modules
6
+
7
+ """
8
+
9
+ # External libs ----------------------------------------------------------------
10
+ import pandas as pd
11
+ import re
12
+ import webcolors
13
+
14
+ # External modules ------------------------------------------------------------
15
+ from unidecode import unidecode
16
+
17
+
18
+ # FUNCTIONS -------------------------------------------------------------------
19
+ def _stdStr(s):
20
+ """
21
+ Returns standardize format for input string.
22
+
23
+ Parameters
24
+ ----------
25
+ :param s: input.
26
+ :type s: str | set | list
27
+
28
+ Returns
29
+ -------
30
+ :return: formatted string(s).
31
+ :rtype: same type as input
32
+ """
33
+ if type(s) is str:
34
+ return unidecode(s).strip().lower().replace('.0', '')
35
+ if type(s) is set:
36
+ new_s = set()
37
+ for _ in s:
38
+ new_s.add(_stdStr(_))
39
+ return s
40
+ if type(s) is list:
41
+ return [_stdStr(_) for _ in s]
42
+
43
+
44
+ def _getValueIfPresent(
45
+ line: pd.Series,
46
+ index: int,
47
+ default_value
48
+ ):
49
+ """
50
+ Extract the value from a table line for given column name.
51
+ If nothing is found, returns a default value.
52
+
53
+ Parameters
54
+ ----------
55
+ :param line: Line where the info should be find.
56
+ :type line: pd.Series
57
+
58
+ :param index: Column name under which we should find a value
59
+ :type index: int
60
+
61
+ :param default_value: If no value is found under the given column, this is the default output.
62
+
63
+ Returns
64
+ -------
65
+ :return: Found value or default value if nothing is found.
66
+ """
67
+ value = line[index] if index in line.index else default_value
68
+ return value
69
+
70
+
71
+ def _extractFluxFromMatrix(
72
+ table: pd.DataFrame,
73
+ origins: list,
74
+ destinations: list,
75
+ values: list
76
+ ):
77
+ """
78
+ Extract flux from a matrix as following:
79
+
80
+ +------+------+------+------+
81
+ | - | C4 | C5 | C6 |
82
+ +======+======+======+======+
83
+ | R3 | None | x | x |
84
+ +------+------+------+------+
85
+ | R4 | x | None | None |
86
+ +------+------+------+------+
87
+
88
+ Parameters
89
+ ----------
90
+ :param table: Matrix table (rows = Origins, cols = Destinations)
91
+ :type table: pd.DataFrame
92
+
93
+ :param origins: List of all origins
94
+ :type origins: list, modified
95
+
96
+ :param destinations: List of all destinations
97
+ :type destinations: list, modified
98
+
99
+ :param values: List of all values for flux
100
+ :type values: list, modified
101
+
102
+ """
103
+ for orig in table.index:
104
+ for dest in table.columns:
105
+ if orig == dest:
106
+ continue
107
+ v = table.loc[orig][dest]
108
+ ok = (v is not None) and (v != 0) and (v != "0")
109
+ if ok:
110
+ origins.append(orig)
111
+ destinations.append(dest)
112
+ if re.fullmatch('[xX]', str(v)) is not None:
113
+ values.append(None)
114
+ else:
115
+ values.append(float(v))
116
+
117
+
118
+ def _createMatrixFromFlux(
119
+ origins: list,
120
+ destinations: list,
121
+ transpose=False
122
+ ):
123
+ """
124
+ Create a matrix table (rows = Origins, cols = Destinations)
125
+
126
+ Parameters
127
+ ----------
128
+ :param origins: List of all origin nodes
129
+ :type origins: list[Node]
130
+
131
+ :param destinations: List of all destination nodes
132
+ :type destinations: list[Node]
133
+
134
+ :param transpose: Return matrix as transpose version
135
+ :type transpose: bool
136
+
137
+ Returns
138
+ -------
139
+ :return: matrix table (rows = Origins, cols = Destinations).
140
+ If "x" we have a flux from origin to destination
141
+ If None we dont have a flux
142
+ :rtype: list[list]
143
+ """
144
+ table = []
145
+ if transpose:
146
+ for destination in destinations:
147
+ # Init new row
148
+ row = []
149
+ # Get all nodes that are registred as a flux destination from origin
150
+ registered_origins = \
151
+ [flux.orig for flux in destination.input_flux]
152
+ # If we have such nodes
153
+ if len(registered_origins) > 0:
154
+ for origin in origins:
155
+ # 'x' if we have a flux origin -> destination
156
+ # None otherwise
157
+ if origin in registered_origins:
158
+ row.append('x')
159
+ else:
160
+ row.append(None)
161
+ else:
162
+ row += [None]*len(origins)
163
+ # Add row to table
164
+ table.append(row)
165
+ else:
166
+ for origin in origins:
167
+ # Init new row
168
+ row = []
169
+ # Get all nodes that are registred as a flux destination from origin
170
+ registered_destinations = \
171
+ [flux.dest for flux in origin.output_flux]
172
+ # If we have such nodes
173
+ if len(registered_destinations) > 0:
174
+ for destination in destinations:
175
+ # 'x' if we have a flux origin -> destination
176
+ # None otherwise
177
+ if destination in registered_destinations:
178
+ row.append('x')
179
+ else:
180
+ row.append(None)
181
+ else:
182
+ row += [None]*len(destinations)
183
+ # Add row to table
184
+ table.append(row)
185
+ # Create and return panda table
186
+ return table
187
+
188
+
189
+ def is_hex(s):
190
+ return re.fullmatch(r"^\# ?[0-9a-fA-F]+$", s or "") is not None
191
+
192
+
193
+ def _convertColorToHex(color, default_color=''):
194
+ """
195
+ Convert a color str to hex value.
196
+
197
+ Parameters
198
+ ----------
199
+ :param color: color to convert
200
+ :type color: str
201
+ """
202
+ if type(color) is str:
203
+ # is the color as hexa ?
204
+ if (re.fullmatch(r"^\# ?[0-9a-fA-F]+$", color) is not None) or (color == ""):
205
+ return color
206
+ else:
207
+ return webcolors.name_to_hex(color)
208
+ return default_color
209
+
210
+
211
+ def _reorderTable(
212
+ table: pd.DataFrame,
213
+ cols: list,
214
+ contents: list
215
+ ):
216
+ """
217
+ Reorder table lines accordlying to values presents in list of cols
218
+
219
+ Example :
220
+ -------
221
+ Col 1 | Col 2 | Col 3
222
+ ---------------------
223
+ a | 1 | 2
224
+ b | 2 | 2
225
+ c | 2 | 1
226
+ d | 1 | 2
227
+
228
+ Reordering as [Col2, Col3] gives
229
+ Col 1 | Col 2 | Col 3
230
+ ---------------------
231
+ a | 1 | 2
232
+ d | 1 | 2
233
+ c | 2 | 1
234
+ b | 2 | 2
235
+
236
+ Parameters
237
+ ----------
238
+ :param table: Table to reorder
239
+ :type table: panda.DataFrame
240
+
241
+ :param cols: List of ref columns for reordering
242
+ :type cols: list as [str, ...]
243
+
244
+ :param contents: List possible content per col. Must be ordered following this.
245
+ If content not in this list, then will be processed at the end.
246
+ :type contents: list as [list as [str, ...], ...]
247
+
248
+ Returns
249
+ -------
250
+ :return: Reordered table
251
+ :rtype: panda.DataFrame
252
+
253
+ """
254
+ # No more ordering cols -> return table
255
+ if len(cols) == 0:
256
+ return table
257
+ # Create new table to get block of sorted subtables
258
+ new_table = pd.DataFrame(columns=table.columns)
259
+ sorting_col = cols.pop()
260
+ sorting_contents = contents.pop()
261
+ present_contents = table[sorting_col].unique()
262
+ # Update sorting content with unexpected content
263
+ prio_sorting_contents = [_ for _ in sorting_contents if _ in present_contents]
264
+ other_sorting_contents = [_ for _ in present_contents if _ not in sorting_contents]
265
+ sorting_contents = prio_sorting_contents + other_sorting_contents
266
+ # Sort
267
+ for value in sorting_contents:
268
+ # Get subtable
269
+ if value is None:
270
+ sub_table = table.loc[table[sorting_col].isnull()]
271
+ else:
272
+ sub_table = table.loc[table[sorting_col] == value]
273
+ # Recursive filtering of sub_table
274
+ if len(cols) > 0:
275
+ sub_table = _reorderTable(sub_table, cols.copy(), contents.copy())
276
+ # Appending results
277
+ new_table = new_table._append(sub_table, ignore_index=True)
278
+ return new_table