data-manipulation-utilities 0.2.7__py3-none-any.whl → 0.2.8.dev720__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.
- {data_manipulation_utilities-0.2.7.dist-info → data_manipulation_utilities-0.2.8.dev720.dist-info}/METADATA +669 -42
- data_manipulation_utilities-0.2.8.dev720.dist-info/RECORD +45 -0
- {data_manipulation_utilities-0.2.7.dist-info → data_manipulation_utilities-0.2.8.dev720.dist-info}/WHEEL +1 -2
- data_manipulation_utilities-0.2.8.dev720.dist-info/entry_points.txt +8 -0
- dmu/generic/hashing.py +34 -8
- dmu/generic/utilities.py +164 -11
- dmu/logging/log_store.py +34 -2
- dmu/logging/messages.py +96 -0
- dmu/ml/cv_classifier.py +3 -3
- dmu/ml/cv_diagnostics.py +3 -0
- dmu/ml/cv_performance.py +58 -0
- dmu/ml/cv_predict.py +149 -46
- dmu/ml/train_mva.py +482 -100
- dmu/ml/utilities.py +29 -10
- dmu/pdataframe/utilities.py +28 -3
- dmu/plotting/fwhm.py +2 -2
- dmu/plotting/matrix.py +1 -1
- dmu/plotting/plotter.py +23 -3
- dmu/plotting/plotter_1d.py +96 -32
- dmu/plotting/plotter_2d.py +5 -0
- dmu/rdataframe/utilities.py +54 -3
- dmu/rfile/ddfgetter.py +102 -0
- dmu/stats/fit_stats.py +129 -0
- dmu/stats/fitter.py +55 -22
- dmu/stats/gof_calculator.py +7 -0
- dmu/stats/model_factory.py +153 -62
- dmu/stats/parameters.py +100 -0
- dmu/stats/utilities.py +443 -12
- dmu/stats/wdata.py +187 -0
- dmu/stats/zfit.py +17 -0
- dmu/stats/zfit_plotter.py +147 -36
- dmu/testing/utilities.py +102 -24
- dmu/workflow/__init__.py +0 -0
- dmu/workflow/cache.py +266 -0
- data_manipulation_utilities-0.2.7.data/scripts/publish +0 -89
- data_manipulation_utilities-0.2.7.dist-info/RECORD +0 -69
- data_manipulation_utilities-0.2.7.dist-info/entry_points.txt +0 -6
- data_manipulation_utilities-0.2.7.dist-info/top_level.txt +0 -3
- dmu_data/ml/tests/diagnostics_from_file.yaml +0 -13
- dmu_data/ml/tests/diagnostics_from_model.yaml +0 -10
- dmu_data/ml/tests/diagnostics_multiple_methods.yaml +0 -10
- dmu_data/ml/tests/diagnostics_overlay.yaml +0 -33
- dmu_data/ml/tests/train_mva.yaml +0 -58
- dmu_data/ml/tests/train_mva_with_diagnostics.yaml +0 -82
- dmu_data/plotting/tests/2d.yaml +0 -24
- dmu_data/plotting/tests/fig_size.yaml +0 -13
- dmu_data/plotting/tests/high_stat.yaml +0 -22
- dmu_data/plotting/tests/legend.yaml +0 -12
- dmu_data/plotting/tests/name.yaml +0 -14
- dmu_data/plotting/tests/no_bounds.yaml +0 -12
- dmu_data/plotting/tests/normalized.yaml +0 -9
- dmu_data/plotting/tests/plug_fwhm.yaml +0 -24
- dmu_data/plotting/tests/plug_stats.yaml +0 -19
- dmu_data/plotting/tests/simple.yaml +0 -9
- dmu_data/plotting/tests/stats.yaml +0 -9
- dmu_data/plotting/tests/styling.yaml +0 -11
- dmu_data/plotting/tests/title.yaml +0 -14
- dmu_data/plotting/tests/weights.yaml +0 -13
- dmu_data/text/transform.toml +0 -4
- dmu_data/text/transform.txt +0 -6
- dmu_data/text/transform_set.toml +0 -8
- dmu_data/text/transform_set.txt +0 -6
- dmu_data/text/transform_trf.txt +0 -12
- dmu_scripts/git/publish +0 -89
- dmu_scripts/physics/check_truth.py +0 -121
- dmu_scripts/rfile/compare_root_files.py +0 -299
- dmu_scripts/rfile/print_trees.py +0 -35
- dmu_scripts/ssh/coned.py +0 -168
- dmu_scripts/text/transform_text.py +0 -46
- {dmu_data → dmu}/__init__.py +0 -0
dmu/ml/cv_predict.py
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
'''
|
2
2
|
Module holding CVPredict class
|
3
3
|
'''
|
4
|
-
from typing import Optional
|
5
|
-
|
6
4
|
import pandas as pnd
|
7
5
|
import numpy
|
8
6
|
import tqdm
|
@@ -21,41 +19,107 @@ class CVPredict:
|
|
21
19
|
Class used to get classification probabilities from ROOT
|
22
20
|
dataframe and a set of models. The models were trained with CVClassifier
|
23
21
|
'''
|
24
|
-
def __init__(
|
22
|
+
def __init__(
|
23
|
+
self,
|
24
|
+
rdf : RDataFrame,
|
25
|
+
models : list[CVClassifier]):
|
25
26
|
'''
|
26
27
|
Will take a list of CVClassifier models and a ROOT dataframe
|
27
|
-
'''
|
28
|
-
|
29
|
-
if models is None:
|
30
|
-
raise ValueError('No list of models passed')
|
31
|
-
|
32
|
-
if rdf is None:
|
33
|
-
raise ValueError('No ROOT dataframe passed')
|
34
28
|
|
29
|
+
rdf : ROOT dataframe where features will be extracted
|
30
|
+
models: List of models, one per fold
|
31
|
+
'''
|
35
32
|
self._l_model = models
|
36
33
|
self._rdf = rdf
|
34
|
+
self._nrows : int
|
35
|
+
self._l_column : list[str]
|
37
36
|
self._d_nan_rep : dict[str,str]
|
38
37
|
|
39
|
-
|
38
|
+
# Value of score used when no score has been assigned
|
39
|
+
self._dummy_score = -1.0
|
40
|
+
|
41
|
+
# name of column in ROOT dataframe where 1s will prevent prediction
|
42
|
+
self._skip_index_column = 'skip_mva_prediction'
|
43
|
+
|
44
|
+
# name of attribute of features dataframe where array of indices to skip are stored
|
45
|
+
self._index_skip = 'skip_mva_prediction'
|
40
46
|
# --------------------------------------------
|
41
47
|
def _initialize(self):
|
48
|
+
self._rdf = self._remove_periods(self._rdf)
|
42
49
|
self._rdf = self._define_columns(self._rdf)
|
43
50
|
self._d_nan_rep = self._get_nan_replacements()
|
51
|
+
self._l_column = [ name.c_str() for name in self._rdf.GetColumnNames() ]
|
52
|
+
self._nrows = self._rdf.Count().GetValue()
|
53
|
+
# ----------------------------------
|
54
|
+
def _remove_periods(self, rdf : RDataFrame) -> RDataFrame:
|
55
|
+
'''
|
56
|
+
This will redefine all columns associated to friend trees as:
|
57
|
+
|
58
|
+
friend_preffix.branch_name -> friend_preffix.branch_name
|
59
|
+
'''
|
60
|
+
l_col = [ col.c_str() for col in rdf.GetColumnNames() ]
|
61
|
+
l_col = [ col for col in l_col if '.' in col ]
|
62
|
+
|
63
|
+
if len(l_col) == 0:
|
64
|
+
return rdf
|
65
|
+
|
66
|
+
log.debug(60 * '-')
|
67
|
+
log.debug('Renaming dotted columns')
|
68
|
+
log.debug(60 * '-')
|
69
|
+
for col in l_col:
|
70
|
+
new = col.replace('.', '_')
|
71
|
+
log.debug(f'{col:<50}{"->":10}{new:<20}')
|
72
|
+
rdf = rdf.Define(new, col)
|
73
|
+
|
74
|
+
return rdf
|
75
|
+
# --------------------------------------------
|
76
|
+
def _get_definitions(self) -> dict[str,str]:
|
77
|
+
'''
|
78
|
+
This method will search in the configuration the definitions used
|
79
|
+
on the dataframe before the dataframe was used to train the model.
|
80
|
+
'''
|
81
|
+
cfg = self._l_model[0].cfg
|
82
|
+
d_def = {}
|
83
|
+
if 'define' in cfg['dataset']:
|
84
|
+
d_def_gen = cfg['dataset']['define'] # get generic definitions
|
85
|
+
d_def.update(d_def_gen)
|
86
|
+
|
87
|
+
sig_name = 'sig'
|
88
|
+
try:
|
89
|
+
# Get sample specific definitions. This will be taken from the signal section
|
90
|
+
# because predicted scores should come from features defined as for the signal.
|
91
|
+
d_def_sam = cfg['dataset']['samples'][sig_name]['definitions']
|
92
|
+
except KeyError:
|
93
|
+
log.debug(f'No sample specific definitions were found in: {sig_name}')
|
94
|
+
return d_def
|
95
|
+
|
96
|
+
log.info('Adding sample dependent definitions')
|
97
|
+
d_def.update(d_def_sam)
|
98
|
+
|
99
|
+
return d_def
|
44
100
|
# --------------------------------------------
|
45
101
|
def _define_columns(self, rdf : RDataFrame) -> RDataFrame:
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
log.debug('No define section found in config, will not define extra columns')
|
102
|
+
d_def = self._get_definitions()
|
103
|
+
if len(d_def) == 0:
|
104
|
+
log.info('No definitions found')
|
50
105
|
return self._rdf
|
51
106
|
|
52
|
-
|
107
|
+
dexc = None
|
53
108
|
log.debug(60 * '-')
|
54
109
|
log.info('Defining columns in RDF before evaluating classifier')
|
55
110
|
log.debug(60 * '-')
|
56
111
|
for name, expr in d_def.items():
|
112
|
+
expr = expr.replace('.', '_')
|
113
|
+
|
57
114
|
log.debug(f'{name:<20}{"<---":20}{expr:<100}')
|
58
|
-
|
115
|
+
try:
|
116
|
+
rdf = rdf.Define(name, expr)
|
117
|
+
except TypeError as exc:
|
118
|
+
log.error(f'Cannot define {name}={expr}')
|
119
|
+
dexc = exc
|
120
|
+
|
121
|
+
if dexc is not None:
|
122
|
+
raise TypeError('Could not define at least one column') from dexc
|
59
123
|
|
60
124
|
return rdf
|
61
125
|
# --------------------------------------------
|
@@ -68,21 +132,25 @@ class CVPredict:
|
|
68
132
|
|
69
133
|
return cfg['dataset']['nan']
|
70
134
|
# --------------------------------------------
|
71
|
-
def _replace_nans(self,
|
135
|
+
def _replace_nans(self, df_ft : pnd.DataFrame) -> pnd.DataFrame:
|
136
|
+
'''
|
137
|
+
Funtion replaces nans in user specified columns with user specified values
|
138
|
+
These NaNs are expected
|
139
|
+
'''
|
72
140
|
if len(self._d_nan_rep) == 0:
|
73
141
|
log.debug('Not doing any NaN replacement')
|
74
|
-
return
|
142
|
+
return df_ft
|
75
143
|
|
76
144
|
log.info(60 * '-')
|
77
145
|
log.info('Doing NaN replacements')
|
78
146
|
log.info(60 * '-')
|
79
147
|
for var, val in self._d_nan_rep.items():
|
80
148
|
log.info(f'{var:<20}{"--->":20}{val:<20.3f}')
|
81
|
-
|
149
|
+
df_ft[var] = df_ft[var].fillna(val)
|
82
150
|
|
83
|
-
return
|
151
|
+
return df_ft
|
84
152
|
# --------------------------------------------
|
85
|
-
def _get_df(self):
|
153
|
+
def _get_df(self) -> pnd.DataFrame:
|
86
154
|
'''
|
87
155
|
Will make ROOT rdf into dataframe and return it
|
88
156
|
'''
|
@@ -90,11 +158,11 @@ class CVPredict:
|
|
90
158
|
l_ft = model.features
|
91
159
|
d_data= self._rdf.AsNumpy(l_ft)
|
92
160
|
df_ft = pnd.DataFrame(d_data)
|
93
|
-
df_ft = self._replace_nans(df_ft)
|
94
|
-
df_ft =
|
95
|
-
|
96
|
-
|
97
|
-
|
161
|
+
df_ft = self._replace_nans(df_ft=df_ft)
|
162
|
+
df_ft = self._tag_skipped(df_ft=df_ft)
|
163
|
+
df_ft = ut.tag_nans(
|
164
|
+
df = df_ft,
|
165
|
+
indexes = self._index_skip)
|
98
166
|
|
99
167
|
nfeat = len(l_ft)
|
100
168
|
log.info(f'Found {nfeat} features')
|
@@ -103,6 +171,24 @@ class CVPredict:
|
|
103
171
|
|
104
172
|
return df_ft
|
105
173
|
# --------------------------------------------
|
174
|
+
def _tag_skipped(self, df_ft : pnd.DataFrame) -> pnd.DataFrame:
|
175
|
+
'''
|
176
|
+
Will drop rows with features where column with name _skip_name (currently "_skip_mva_prediction") has values of 1
|
177
|
+
'''
|
178
|
+
if self._skip_index_column not in self._l_column:
|
179
|
+
log.debug(f'Not dropping any rows through: {self._skip_index_column}')
|
180
|
+
return df_ft
|
181
|
+
|
182
|
+
log.info(f'Dropping rows through: {self._skip_index_column}')
|
183
|
+
arr_drop = self._rdf.AsNumpy([self._skip_index_column])[self._skip_index_column]
|
184
|
+
|
185
|
+
if self._index_skip in df_ft.attrs:
|
186
|
+
raise ValueError(f'Feature dataframe already contains attribute: {self._index_skip}')
|
187
|
+
|
188
|
+
df_ft.attrs[self._index_skip] = numpy.where(arr_drop == 1)[0]
|
189
|
+
|
190
|
+
return df_ft
|
191
|
+
# --------------------------------------------
|
106
192
|
def _non_overlapping_hashes(self, model, df_ft):
|
107
193
|
'''
|
108
194
|
Will return True if hashes of model and data do not overlap
|
@@ -147,8 +233,8 @@ class CVPredict:
|
|
147
233
|
'''
|
148
234
|
Evaluate the dataset for one of the folds, by taking the model and the full dataset
|
149
235
|
'''
|
150
|
-
s_dat_hash = set(df_ft.index)
|
151
|
-
s_mod_hash = model.hashes
|
236
|
+
s_dat_hash : set[str] = set(df_ft.index)
|
237
|
+
s_mod_hash : set[str] = model.hashes
|
152
238
|
|
153
239
|
s_dif_hash = s_dat_hash - s_mod_hash
|
154
240
|
|
@@ -164,19 +250,29 @@ class CVPredict:
|
|
164
250
|
d_prob = dict(zip(l_hash, l_prob))
|
165
251
|
nfeat = len(df_ft_group)
|
166
252
|
nprob = len(l_prob)
|
167
|
-
|
253
|
+
|
254
|
+
if nfeat != nprob:
|
255
|
+
raise ValueError(f'Number of features and probabilities do not agree: {nfeat} != {nprob}')
|
168
256
|
|
169
257
|
return d_prob
|
170
258
|
# --------------------------------------------
|
171
|
-
def
|
172
|
-
|
173
|
-
|
259
|
+
def _predict_signal_probabilities(
|
260
|
+
self,
|
261
|
+
model : CVClassifier,
|
262
|
+
df_ft : pnd.DataFrame) -> numpy.ndarray:
|
263
|
+
'''
|
264
|
+
Takes model and features dataframe, returns array of signal probabilities
|
265
|
+
'''
|
266
|
+
if self._non_overlapping_hashes(model, df_ft):
|
267
|
+
log.debug('No intersecting hashes found between model and data')
|
268
|
+
arr_prb = model.predict_proba(df_ft)
|
269
|
+
else:
|
270
|
+
log.info('Intersecting hashes found between model and data')
|
271
|
+
arr_prb = self._predict_with_overlap(df_ft)
|
174
272
|
|
175
|
-
|
176
|
-
log.warning(f'Patching {nentries} probabilities with -1')
|
177
|
-
arr_prb[self._arr_patch] = -1
|
273
|
+
arr_sig_prb = arr_prb.T[1]
|
178
274
|
|
179
|
-
return
|
275
|
+
return arr_sig_prb
|
180
276
|
# --------------------------------------------
|
181
277
|
def predict(self) -> numpy.ndarray:
|
182
278
|
'''
|
@@ -187,15 +283,22 @@ class CVPredict:
|
|
187
283
|
df_ft = self._get_df()
|
188
284
|
model = self._l_model[0]
|
189
285
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
286
|
+
arr_keep = None
|
287
|
+
arr_skip = None
|
288
|
+
if self._index_skip in df_ft.attrs:
|
289
|
+
arr_skip = df_ft.attrs[self._index_skip]
|
290
|
+
df_ft = df_ft.drop(arr_skip)
|
291
|
+
arr_keep = df_ft.index.to_numpy()
|
292
|
+
|
293
|
+
arr_sig_prb = self._predict_signal_probabilities(
|
294
|
+
model = model,
|
295
|
+
df_ft = df_ft)
|
296
|
+
|
297
|
+
if arr_skip is None:
|
298
|
+
return arr_sig_prb
|
196
299
|
|
197
|
-
|
198
|
-
|
300
|
+
arr_all_sig_prb = numpy.full(self._nrows, self._dummy_score)
|
301
|
+
arr_all_sig_prb[arr_keep] = arr_sig_prb
|
199
302
|
|
200
|
-
return
|
303
|
+
return arr_all_sig_prb
|
201
304
|
# ---------------------------------------
|