chromaquant 0.3.1__py3-none-any.whl → 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chromaquant/__init__.py +9 -2
- chromaquant/data/__init__.py +14 -0
- chromaquant/data/breakdown.py +430 -0
- chromaquant/data/dataset.py +195 -0
- chromaquant/data/table.py +412 -0
- chromaquant/data/value.py +215 -0
- chromaquant/formula/__init__.py +13 -0
- chromaquant/formula/base_formulas.py +168 -0
- chromaquant/formula/formula.py +507 -0
- chromaquant/import_local_packages.py +55 -0
- chromaquant/logging_and_handling.py +76 -0
- chromaquant/match/__init__.py +13 -0
- chromaquant/match/match.py +184 -0
- chromaquant/match/match_config.py +296 -0
- chromaquant/match/match_tools.py +154 -0
- chromaquant/{Handle → results}/__init__.py +2 -2
- chromaquant/results/reporting_tools.py +190 -0
- chromaquant/results/results.py +250 -0
- chromaquant/utils/__init__.py +14 -0
- chromaquant/utils/categories.py +127 -0
- chromaquant/utils/chemical_formulas.py +104 -0
- chromaquant/utils/dataframe_processing.py +222 -0
- chromaquant/utils/file_tools.py +100 -0
- chromaquant/utils/formula_tools.py +119 -0
- chromaquant-0.5.0.dist-info/METADATA +61 -0
- chromaquant-0.5.0.dist-info/RECORD +29 -0
- {chromaquant-0.3.1.dist-info → chromaquant-0.5.0.dist-info}/WHEEL +1 -1
- {chromaquant-0.3.1.dist-info → chromaquant-0.5.0.dist-info}/licenses/LICENSE.txt +1 -1
- chromaquant-0.5.0.dist-info/licenses/LICENSES_bundled.txt +251 -0
- chromaquant/Handle/handleDirectories.py +0 -89
- chromaquant/Manual/HydroUI.py +0 -418
- chromaquant/Manual/QuantUPP.py +0 -373
- chromaquant/Manual/Quantification.py +0 -1305
- chromaquant/Manual/__init__.py +0 -10
- chromaquant/Manual/duplicateMatch.py +0 -211
- chromaquant/Manual/fpm_match.py +0 -798
- chromaquant/Manual/label-type.py +0 -179
- chromaquant/Match/AutoFpmMatch.py +0 -1133
- chromaquant/Match/__init__.py +0 -12
- chromaquant/Quant/AutoQuantification.py +0 -1329
- chromaquant/Quant/__init__.py +0 -12
- chromaquant/__main__.py +0 -493
- chromaquant/properties.json +0 -4
- chromaquant-0.3.1.dist-info/METADATA +0 -189
- chromaquant-0.3.1.dist-info/RECORD +0 -22
- chromaquant-0.3.1.dist-info/entry_points.txt +0 -2
- chromaquant-0.3.1.dist-info/licenses/LICENSES_bundled.txt +0 -1035
chromaquant/__init__.py
CHANGED
|
@@ -2,9 +2,16 @@
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
|
|
4
4
|
"""
|
|
5
|
-
|
|
5
|
+
chromaquant.utils package initialization
|
|
6
6
|
|
|
7
7
|
Julia Hancock
|
|
8
8
|
Created 10-20-2024
|
|
9
9
|
|
|
10
|
-
"""
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .results import Results
|
|
13
|
+
from .formula import Formula
|
|
14
|
+
from .match import MatchConfig
|
|
15
|
+
from .data import Table
|
|
16
|
+
from .data import Value
|
|
17
|
+
from .data import Breakdown
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
COPYRIGHT STATEMENT:
|
|
5
|
+
|
|
6
|
+
ChromaQuant – A quantification software for complex gas chromatographic data
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026, by Julia Hancock
|
|
9
|
+
Affiliation: Dr. Julie Elaine Rorrer
|
|
10
|
+
URL: https://www.rorrerlab.com/
|
|
11
|
+
|
|
12
|
+
License: BSD 3-Clause License
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
CLASS DEFINITION FOR BREAKDOWNS
|
|
17
|
+
|
|
18
|
+
Julia Hancock
|
|
19
|
+
Started 1-28-2026
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
from openpyxl.utils import get_column_letter
|
|
25
|
+
from pandas import DataFrame
|
|
26
|
+
from typing import Any, TYPE_CHECKING
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from ..results import Results
|
|
29
|
+
from .dataset import DataSet
|
|
30
|
+
from .table import Table
|
|
31
|
+
from ..logging_and_handling import setup_logger, setup_error_logging
|
|
32
|
+
|
|
33
|
+
""" LOGGING AND HANDLING """
|
|
34
|
+
|
|
35
|
+
# Create a logger
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
# Format the logger
|
|
39
|
+
logger = setup_logger(logger)
|
|
40
|
+
|
|
41
|
+
# Get an error logging decorator
|
|
42
|
+
error_logging = setup_error_logging(logger)
|
|
43
|
+
|
|
44
|
+
""" CLASS"""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# Class definition for Breakdown
|
|
48
|
+
class Breakdown(DataSet):
|
|
49
|
+
|
|
50
|
+
# Initialize
|
|
51
|
+
def __init__(self,
|
|
52
|
+
start_cell: str = '',
|
|
53
|
+
sheet: str = '',
|
|
54
|
+
conditional_aggregate: str = 'SUMIFS',
|
|
55
|
+
header: str = '',
|
|
56
|
+
results: Results = None):
|
|
57
|
+
|
|
58
|
+
# Run DataSet initialization
|
|
59
|
+
super().__init__(data=DataFrame(),
|
|
60
|
+
start_cell=start_cell,
|
|
61
|
+
sheet=sheet,
|
|
62
|
+
header=header,
|
|
63
|
+
results=results)
|
|
64
|
+
|
|
65
|
+
# Create attributes
|
|
66
|
+
self._data = DataFrame()
|
|
67
|
+
self.allowed_conditional_aggregates = ['SUMIFS',
|
|
68
|
+
'COUNTIFS',
|
|
69
|
+
'AVERAGEIFS',
|
|
70
|
+
'MINIFS',
|
|
71
|
+
'MAXIFS']
|
|
72
|
+
self._breakdown_cache = {}
|
|
73
|
+
self.length = len(self._data)
|
|
74
|
+
|
|
75
|
+
# If the conditional aggregate is not equal to an expected value...
|
|
76
|
+
if conditional_aggregate not in self.allowed_conditional_aggregates:
|
|
77
|
+
# Raise an error
|
|
78
|
+
raise ValueError(('Conditional aggregate not recognized: '
|
|
79
|
+
f'{conditional_aggregate}'))
|
|
80
|
+
# Otherwise, assign to an attribute
|
|
81
|
+
else:
|
|
82
|
+
self.conditional_aggregate = conditional_aggregate
|
|
83
|
+
|
|
84
|
+
""" PROPERTIES """
|
|
85
|
+
# Data properties - GETTER ONLY
|
|
86
|
+
# Getter
|
|
87
|
+
@property
|
|
88
|
+
def data(self) -> Any:
|
|
89
|
+
return self._data
|
|
90
|
+
|
|
91
|
+
# Setter
|
|
92
|
+
@data.setter
|
|
93
|
+
def data(self, value: DataFrame | None):
|
|
94
|
+
self._data = value
|
|
95
|
+
|
|
96
|
+
# Deleter
|
|
97
|
+
@data.deleter
|
|
98
|
+
def data(self):
|
|
99
|
+
del self._data
|
|
100
|
+
self._data = DataFrame()
|
|
101
|
+
|
|
102
|
+
# Sheet properties
|
|
103
|
+
# Getter
|
|
104
|
+
@property
|
|
105
|
+
def sheet(self) -> str:
|
|
106
|
+
return self._sheet
|
|
107
|
+
|
|
108
|
+
# Setter
|
|
109
|
+
@sheet.setter
|
|
110
|
+
def sheet(self, value: str):
|
|
111
|
+
if value == '':
|
|
112
|
+
raise ValueError('Breakdown sheet cannot be an empty string.')
|
|
113
|
+
self._sheet = value
|
|
114
|
+
if self._mediator is not None:
|
|
115
|
+
self._mediator.update_datasets()
|
|
116
|
+
|
|
117
|
+
# Deleter
|
|
118
|
+
@sheet.deleter
|
|
119
|
+
def sheet(self):
|
|
120
|
+
del self._sheet
|
|
121
|
+
|
|
122
|
+
# Start cell properties
|
|
123
|
+
# Getter
|
|
124
|
+
@property
|
|
125
|
+
def start_cell(self) -> str:
|
|
126
|
+
return self._start_cell
|
|
127
|
+
|
|
128
|
+
# Setter
|
|
129
|
+
@start_cell.setter
|
|
130
|
+
def start_cell(self, value: str):
|
|
131
|
+
# Try...
|
|
132
|
+
try:
|
|
133
|
+
# Get the cell's absolute indices
|
|
134
|
+
self.start_column, self.start_row = self.get_cell_indices(value)
|
|
135
|
+
# Set the starting cell
|
|
136
|
+
self._start_cell = value
|
|
137
|
+
# If an exception occurs...
|
|
138
|
+
except Exception as e:
|
|
139
|
+
raise ValueError(f'Passed start cell is not valid: {e}')
|
|
140
|
+
if self._mediator is not None:
|
|
141
|
+
self._mediator.update_datasets()
|
|
142
|
+
|
|
143
|
+
# Deleter
|
|
144
|
+
@start_cell.deleter
|
|
145
|
+
def start_cell(self):
|
|
146
|
+
del self._start_cell
|
|
147
|
+
del self.start_row, self.start_column
|
|
148
|
+
|
|
149
|
+
""" METHODS """
|
|
150
|
+
# Method to create a conditional aggregate formula based on criteria
|
|
151
|
+
def create_conditional_aggregate_formula(self,
|
|
152
|
+
table: Table,
|
|
153
|
+
criteria: dict[str, str],
|
|
154
|
+
summarize_column: str = ''
|
|
155
|
+
):
|
|
156
|
+
|
|
157
|
+
# Create a blank formula template
|
|
158
|
+
formula_template = ''
|
|
159
|
+
|
|
160
|
+
# If the conditional aggregate is COUNTIFS...
|
|
161
|
+
if self.conditional_aggregate == 'COUNTIFS':
|
|
162
|
+
|
|
163
|
+
# Create a COUNTIFS-specific inner formula template
|
|
164
|
+
formula_template = ''
|
|
165
|
+
|
|
166
|
+
# Otherwise, if summarize column is not empty...
|
|
167
|
+
elif summarize_column != '':
|
|
168
|
+
# Create an inner formula template
|
|
169
|
+
formula_template = f'{table.reference[summarize_column]['range']}'
|
|
170
|
+
|
|
171
|
+
# Otherwise, raise an error
|
|
172
|
+
else:
|
|
173
|
+
raise ValueError(('Argument summarize_column cannot be blank for'
|
|
174
|
+
'conditional aggregates besides COUNTIFS'))
|
|
175
|
+
|
|
176
|
+
# For every key-value pair in criteria...
|
|
177
|
+
for cell_or_range in criteria:
|
|
178
|
+
|
|
179
|
+
# Add criteria range to formula_template
|
|
180
|
+
formula_template += f', {cell_or_range}'
|
|
181
|
+
# Add criteria to formula_template
|
|
182
|
+
formula_template += f', {criteria[cell_or_range]}'
|
|
183
|
+
|
|
184
|
+
# Get the final formula by wrapping in the passed conditional aggregate
|
|
185
|
+
formula = f'={self.conditional_aggregate}({formula_template})'
|
|
186
|
+
|
|
187
|
+
return formula
|
|
188
|
+
|
|
189
|
+
# Method to create a one-dimensional breakdown
|
|
190
|
+
def create_1D(self,
|
|
191
|
+
table: Table,
|
|
192
|
+
group_by_column: str,
|
|
193
|
+
summarize_column: str,
|
|
194
|
+
groups_to_summarize: list[str] = None):
|
|
195
|
+
|
|
196
|
+
# If groups_to_summarize is not none...
|
|
197
|
+
if groups_to_summarize is not None:
|
|
198
|
+
# Set unique_groups to a shallow copy of groups_to_summarize
|
|
199
|
+
unique_groups = groups_to_summarize[:]
|
|
200
|
+
|
|
201
|
+
# Otherwise...
|
|
202
|
+
else:
|
|
203
|
+
# Get a list of unique values in Table in the group by column
|
|
204
|
+
# NOTE: IGNORES NONE
|
|
205
|
+
unique_groups = [group for group in
|
|
206
|
+
table.data[group_by_column].unique()
|
|
207
|
+
if group is not None]
|
|
208
|
+
|
|
209
|
+
# Sort the list of unique groups
|
|
210
|
+
unique_groups.sort()
|
|
211
|
+
|
|
212
|
+
# Create an empty 1D DataFrame using the unique groups
|
|
213
|
+
self._data = DataFrame({group: [None] for group in unique_groups})
|
|
214
|
+
|
|
215
|
+
# Create an index for the current header cell
|
|
216
|
+
header_cell_index = 0
|
|
217
|
+
|
|
218
|
+
# Get the start cell's absolute indices
|
|
219
|
+
absolute_start_col, absolute_start_row = \
|
|
220
|
+
self.get_cell_indices(self.start_cell)
|
|
221
|
+
|
|
222
|
+
# For every group...
|
|
223
|
+
for group in unique_groups:
|
|
224
|
+
|
|
225
|
+
# Get the current header's column and row
|
|
226
|
+
start_col = \
|
|
227
|
+
get_column_letter(absolute_start_col + 1 + header_cell_index)
|
|
228
|
+
start_row = \
|
|
229
|
+
absolute_start_row + 1
|
|
230
|
+
|
|
231
|
+
# Get the current header cell
|
|
232
|
+
header_cell = f'{start_col}${start_row}'
|
|
233
|
+
|
|
234
|
+
# Define the criteria dictionary
|
|
235
|
+
criteria = {table.reference[group_by_column]['range']: header_cell}
|
|
236
|
+
|
|
237
|
+
# Get a formula string
|
|
238
|
+
formula_string = \
|
|
239
|
+
self.create_conditional_aggregate_formula(table,
|
|
240
|
+
criteria,
|
|
241
|
+
summarize_column
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# Add the formula string to the current group entry
|
|
245
|
+
self._data[group] = formula_string
|
|
246
|
+
|
|
247
|
+
# Iterate the header cell index
|
|
248
|
+
header_cell_index += 1
|
|
249
|
+
|
|
250
|
+
# Set the data length
|
|
251
|
+
self.length = len(self._data)
|
|
252
|
+
|
|
253
|
+
# Set the breakdown cache
|
|
254
|
+
self._breakdown_cache = {'function': self.create_1D,
|
|
255
|
+
'arguments': {'table':
|
|
256
|
+
table,
|
|
257
|
+
'group_by_column':
|
|
258
|
+
group_by_column,
|
|
259
|
+
'summarize_column':
|
|
260
|
+
summarize_column,
|
|
261
|
+
'groups_to_summarize':
|
|
262
|
+
groups_to_summarize}}
|
|
263
|
+
|
|
264
|
+
return None
|
|
265
|
+
|
|
266
|
+
# Method to create a two-dimensional breakdown
|
|
267
|
+
def create_2D(self,
|
|
268
|
+
table: Table,
|
|
269
|
+
group_by_col_1: str,
|
|
270
|
+
group_by_col_2: str,
|
|
271
|
+
summarize_column: str,
|
|
272
|
+
groups_to_summarize: dict[str, list[str]] = None):
|
|
273
|
+
|
|
274
|
+
# Create an empty dictionary to contain lists of unique groups
|
|
275
|
+
unique_groups = {group_by_col_1: [], group_by_col_2: []}
|
|
276
|
+
|
|
277
|
+
# If groups_to_summarize is not none...
|
|
278
|
+
if groups_to_summarize is not None:
|
|
279
|
+
|
|
280
|
+
# Get a list of booleans based on whether each key
|
|
281
|
+
# in groups_to_summarize matches a group_by_col
|
|
282
|
+
check_groups_list = [True if group_col in
|
|
283
|
+
[group_by_col_1, group_by_col_2]
|
|
284
|
+
else False for group_col in
|
|
285
|
+
groups_to_summarize]
|
|
286
|
+
|
|
287
|
+
# If groups to summarize is longer than two or contains a string
|
|
288
|
+
# that is not equal to either column string...
|
|
289
|
+
if len(groups_to_summarize) > 2 or not all(check_groups_list):
|
|
290
|
+
|
|
291
|
+
# Raise an error
|
|
292
|
+
raise ValueError('Unexpected keys in groups_to_summarize.')
|
|
293
|
+
|
|
294
|
+
# Otherwise, pass
|
|
295
|
+
else:
|
|
296
|
+
pass
|
|
297
|
+
|
|
298
|
+
# Create an empty dictionary if groups_to_summarize is None
|
|
299
|
+
groups_to_summarize = \
|
|
300
|
+
groups_to_summarize if groups_to_summarize is not None else {}
|
|
301
|
+
|
|
302
|
+
# For every entry in unique_groups...
|
|
303
|
+
for group_col in unique_groups:
|
|
304
|
+
|
|
305
|
+
# If the group column is present in groups_to_summarize...
|
|
306
|
+
if group_col in groups_to_summarize:
|
|
307
|
+
|
|
308
|
+
# Set unique_groups[group_col] to a shallow
|
|
309
|
+
# copy of groups_to_summarize entry
|
|
310
|
+
unique_groups[group_col] = groups_to_summarize[group_col][:]
|
|
311
|
+
|
|
312
|
+
# Otherwise...
|
|
313
|
+
else:
|
|
314
|
+
# Set unique_groups[group_col] to a list of unique values
|
|
315
|
+
# in the group by column
|
|
316
|
+
unique_groups[group_col] = [group for group in
|
|
317
|
+
table.data[group_col].unique()
|
|
318
|
+
if group is not None]
|
|
319
|
+
|
|
320
|
+
# Sort the list
|
|
321
|
+
unique_groups[group_col].sort()
|
|
322
|
+
|
|
323
|
+
# Create a new column for group_2 (row headers)
|
|
324
|
+
self._data[group_by_col_2] = \
|
|
325
|
+
[group for group in unique_groups[group_by_col_2]]
|
|
326
|
+
|
|
327
|
+
# Get the start cell's absolute indices
|
|
328
|
+
absolute_start_col, absolute_start_row = \
|
|
329
|
+
self.get_cell_indices(self.start_cell)
|
|
330
|
+
|
|
331
|
+
# Create column index to track current column
|
|
332
|
+
column_index = 0
|
|
333
|
+
|
|
334
|
+
# For every group in unique_groups group_by_1 (treat as columns)...
|
|
335
|
+
for group_1 in unique_groups[group_by_col_1]:
|
|
336
|
+
|
|
337
|
+
# Get the current column header's column and row
|
|
338
|
+
column_start_col = \
|
|
339
|
+
get_column_letter(absolute_start_col + 2 +
|
|
340
|
+
column_index)
|
|
341
|
+
column_start_row = \
|
|
342
|
+
absolute_start_row + 2
|
|
343
|
+
|
|
344
|
+
# Get the current column header cell
|
|
345
|
+
column_header_cell = f'{column_start_col}${column_start_row}'
|
|
346
|
+
|
|
347
|
+
# Create row index to track current row
|
|
348
|
+
row_index = 0
|
|
349
|
+
|
|
350
|
+
# For every group in unique_groups group_by_2 (treat as rows)...
|
|
351
|
+
for group_2 in unique_groups[group_by_col_2]:
|
|
352
|
+
|
|
353
|
+
# Get the current row header's column and row
|
|
354
|
+
row_start_col = \
|
|
355
|
+
get_column_letter(absolute_start_col + 1)
|
|
356
|
+
row_start_row = \
|
|
357
|
+
absolute_start_row + 3 + row_index
|
|
358
|
+
|
|
359
|
+
# Get the current row header cell
|
|
360
|
+
row_header_cell = f'${row_start_col}{row_start_row}'
|
|
361
|
+
|
|
362
|
+
# Define the criteria dictionary
|
|
363
|
+
criteria = {table.reference[group_by_col_1]['range']:
|
|
364
|
+
column_header_cell,
|
|
365
|
+
table.reference[group_by_col_2]['range']:
|
|
366
|
+
row_header_cell}
|
|
367
|
+
|
|
368
|
+
# Get a formula string
|
|
369
|
+
formula_string = \
|
|
370
|
+
self.create_conditional_aggregate_formula(table,
|
|
371
|
+
criteria,
|
|
372
|
+
summarize_column
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
# Add the formula string to the current group entry
|
|
376
|
+
self._data.at[row_index, group_1] = formula_string
|
|
377
|
+
|
|
378
|
+
# Iterate the row cell index
|
|
379
|
+
row_index += 1
|
|
380
|
+
|
|
381
|
+
# Iterate the header cell index
|
|
382
|
+
column_index += 1
|
|
383
|
+
|
|
384
|
+
# Set the data length
|
|
385
|
+
self.length = len(self._data)
|
|
386
|
+
|
|
387
|
+
# Set the breakdown cache
|
|
388
|
+
self._breakdown_cache = {'function': self.create_2D,
|
|
389
|
+
'arguments': {'table':
|
|
390
|
+
table,
|
|
391
|
+
'group_by_col_1':
|
|
392
|
+
group_by_col_1,
|
|
393
|
+
'group_by_col_2':
|
|
394
|
+
group_by_col_2,
|
|
395
|
+
'summarize_column':
|
|
396
|
+
summarize_column,
|
|
397
|
+
'groups_to_summarize':
|
|
398
|
+
groups_to_summarize}}
|
|
399
|
+
|
|
400
|
+
return None
|
|
401
|
+
|
|
402
|
+
def merge_1D(self,
|
|
403
|
+
breakdown_list):
|
|
404
|
+
|
|
405
|
+
return None
|
|
406
|
+
|
|
407
|
+
def merge_2D(self,
|
|
408
|
+
breakdown_list):
|
|
409
|
+
|
|
410
|
+
return None
|
|
411
|
+
|
|
412
|
+
""" STATIC METHODS """
|
|
413
|
+
|
|
414
|
+
# Base conditional aggregate method
|
|
415
|
+
@staticmethod
|
|
416
|
+
def _wrap_conditional_aggregate(inner, outer):
|
|
417
|
+
|
|
418
|
+
# If the inner starts with an equals sign...
|
|
419
|
+
if inner[0] == '=':
|
|
420
|
+
# Remove the equals sign
|
|
421
|
+
inner = inner[1:]
|
|
422
|
+
|
|
423
|
+
# Otherwise, pass
|
|
424
|
+
else:
|
|
425
|
+
pass
|
|
426
|
+
|
|
427
|
+
# Get the formula string
|
|
428
|
+
formula_string = f"={outer}({inner})"
|
|
429
|
+
|
|
430
|
+
return formula_string
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
COPYRIGHT STATEMENT:
|
|
5
|
+
|
|
6
|
+
ChromaQuant – A quantification software for complex gas chromatographic data
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026, by Julia Hancock
|
|
9
|
+
Affiliation: Dr. Julie Elaine Rorrer
|
|
10
|
+
URL: https://www.rorrerlab.com/
|
|
11
|
+
|
|
12
|
+
License: BSD 3-Clause License
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
CLASS DEFINITION FOR DATASETS
|
|
17
|
+
|
|
18
|
+
Julia Hancock
|
|
19
|
+
Started 1-12-2026
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
from openpyxl.utils.cell import coordinate_from_string, \
|
|
25
|
+
column_index_from_string
|
|
26
|
+
from typing import Any, TYPE_CHECKING
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from ..results import Results
|
|
29
|
+
import uuid
|
|
30
|
+
from ..logging_and_handling import setup_logger, setup_error_logging
|
|
31
|
+
|
|
32
|
+
""" LOGGING AND HANDLING """
|
|
33
|
+
|
|
34
|
+
# Create a logger
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
# Format the logger
|
|
38
|
+
logger = setup_logger(logger)
|
|
39
|
+
|
|
40
|
+
# Get an error logging decorator
|
|
41
|
+
error_logging = setup_error_logging(logger)
|
|
42
|
+
|
|
43
|
+
""" CLASS """
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Define the DataSet class
|
|
47
|
+
class DataSet:
|
|
48
|
+
|
|
49
|
+
# Initialize
|
|
50
|
+
def __init__(self,
|
|
51
|
+
data: Any = float('nan'),
|
|
52
|
+
start_cell: str = '',
|
|
53
|
+
sheet: str = '',
|
|
54
|
+
type: str = 'DataSet',
|
|
55
|
+
header: str = '',
|
|
56
|
+
results: Results = None):
|
|
57
|
+
|
|
58
|
+
# Initialize instance attributes
|
|
59
|
+
self.type = type
|
|
60
|
+
self._start_cell = start_cell if start_cell != '' else '$A$1'
|
|
61
|
+
self._sheet = sheet if sheet != '' else 'Sheet1'
|
|
62
|
+
self._header = header
|
|
63
|
+
self._reference: dict[str, Any] = {}
|
|
64
|
+
self.start_column, self.start_row = \
|
|
65
|
+
self.get_cell_indices(self._start_cell)
|
|
66
|
+
|
|
67
|
+
# Initialize data attribute
|
|
68
|
+
self._data = data
|
|
69
|
+
|
|
70
|
+
# Create unique ID for the instance
|
|
71
|
+
self.id = str(uuid.uuid4())
|
|
72
|
+
|
|
73
|
+
# Add a mediator attribute
|
|
74
|
+
self._mediator = results
|
|
75
|
+
|
|
76
|
+
# Define the object representation by including its data
|
|
77
|
+
def __repr__(self):
|
|
78
|
+
return f"Contents of chromaquant.{self.type} Object:\n{self._data}"
|
|
79
|
+
|
|
80
|
+
""" PROPERTIES """
|
|
81
|
+
# Define the reference property, ONLY DEFINE GETTER
|
|
82
|
+
@property
|
|
83
|
+
def reference(self) -> dict[str, Any]:
|
|
84
|
+
return self._reference
|
|
85
|
+
|
|
86
|
+
# Data properties
|
|
87
|
+
# Getter
|
|
88
|
+
@property
|
|
89
|
+
def data(self) -> Any:
|
|
90
|
+
return self._data
|
|
91
|
+
|
|
92
|
+
# Setter
|
|
93
|
+
@data.setter
|
|
94
|
+
def data(self, value: Any) -> None:
|
|
95
|
+
self._data = value
|
|
96
|
+
|
|
97
|
+
# Deleter
|
|
98
|
+
@data.deleter
|
|
99
|
+
def data(self) -> None:
|
|
100
|
+
del self._data
|
|
101
|
+
|
|
102
|
+
# Sheet properties
|
|
103
|
+
# Getter
|
|
104
|
+
@property
|
|
105
|
+
def sheet(self) -> str:
|
|
106
|
+
return self._sheet
|
|
107
|
+
|
|
108
|
+
# Setter
|
|
109
|
+
@sheet.setter
|
|
110
|
+
def sheet(self, value: str):
|
|
111
|
+
if value == '':
|
|
112
|
+
raise ValueError('Table sheet cannot be an empty string.')
|
|
113
|
+
self._sheet = value
|
|
114
|
+
if self._mediator is not None:
|
|
115
|
+
self._mediator.update_datasets()
|
|
116
|
+
|
|
117
|
+
# Deleter
|
|
118
|
+
@sheet.deleter
|
|
119
|
+
def sheet(self):
|
|
120
|
+
del self._sheet
|
|
121
|
+
|
|
122
|
+
# Start cell properties
|
|
123
|
+
# Getter
|
|
124
|
+
@property
|
|
125
|
+
def start_cell(self) -> str:
|
|
126
|
+
return self._start_cell
|
|
127
|
+
|
|
128
|
+
# Setter
|
|
129
|
+
@start_cell.setter
|
|
130
|
+
def start_cell(self, value: str):
|
|
131
|
+
try:
|
|
132
|
+
# Get the cell's absolute indices
|
|
133
|
+
self.start_column, self.start_row = self.get_cell_indices(value)
|
|
134
|
+
# Set the starting cell
|
|
135
|
+
self._start_cell = value
|
|
136
|
+
except Exception as e:
|
|
137
|
+
raise ValueError(f'Passed start cell is not valid: {e}')
|
|
138
|
+
if self._mediator is not None:
|
|
139
|
+
self._mediator.update_datasets()
|
|
140
|
+
|
|
141
|
+
# Deleter
|
|
142
|
+
@start_cell.deleter
|
|
143
|
+
def start_cell(self):
|
|
144
|
+
del self._start_cell
|
|
145
|
+
del self.start_row, self.start_column
|
|
146
|
+
|
|
147
|
+
# Header properties
|
|
148
|
+
# Getter
|
|
149
|
+
@property
|
|
150
|
+
def header(self) -> str:
|
|
151
|
+
return self._header
|
|
152
|
+
|
|
153
|
+
# Setter
|
|
154
|
+
@header.setter
|
|
155
|
+
def header(self, value: str):
|
|
156
|
+
self._header = value
|
|
157
|
+
if self._mediator is not None:
|
|
158
|
+
self._mediator.update_datasets()
|
|
159
|
+
|
|
160
|
+
# Deleter
|
|
161
|
+
@header.deleter
|
|
162
|
+
def header(self):
|
|
163
|
+
del self._header
|
|
164
|
+
if self._mediator is not None:
|
|
165
|
+
self._mediator.update_datasets()
|
|
166
|
+
|
|
167
|
+
# Mediator properties
|
|
168
|
+
# Getter
|
|
169
|
+
@property
|
|
170
|
+
def mediator(self) -> str:
|
|
171
|
+
return self._mediator
|
|
172
|
+
|
|
173
|
+
# Setter
|
|
174
|
+
@mediator.setter
|
|
175
|
+
def mediator(self, mediator: Results):
|
|
176
|
+
self._mediator = mediator
|
|
177
|
+
|
|
178
|
+
""" STATIC METHODS """
|
|
179
|
+
|
|
180
|
+
# Static method to get the absolute indices of a cell
|
|
181
|
+
@staticmethod
|
|
182
|
+
def get_cell_indices(cell: str) -> tuple[int, int]:
|
|
183
|
+
|
|
184
|
+
# Split the coordinate
|
|
185
|
+
column_index, row_index = \
|
|
186
|
+
coordinate_from_string(cell)
|
|
187
|
+
|
|
188
|
+
# Get the absolute row by subtracting one
|
|
189
|
+
row_index = row_index - 1
|
|
190
|
+
|
|
191
|
+
# Get the column index from the string, adjusting to get absolute
|
|
192
|
+
column_index = \
|
|
193
|
+
column_index_from_string(column_index) - 1
|
|
194
|
+
|
|
195
|
+
return column_index, row_index
|