Skip to content

Commit 80e3d43

Browse files
Julien RousselJulien Roussel
authored andcommitted
reformatted
1 parent 076ecf9 commit 80e3d43

41 files changed

Lines changed: 402 additions & 1208 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ testpaths = ["tests"]
133133
norecursedirs = ["_build"]
134134

135135
[tool.ruff]
136-
line-length = 79
136+
line-length = 99
137137
fix = true
138138
indent-width = 4
139139
target-version = "py310"

qolmat/analysis/holes_characterization.py

Lines changed: 25 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,7 @@ def __init__(self, random_state: RandomSetting = None):
4545
self.rng = sku.check_random_state(random_state)
4646

4747
@abstractmethod
48-
def test(
49-
self, df: Union[pd.DataFrame, np.ndarray]
50-
) -> Union[float, Tuple[float, List[float]]]:
48+
def test(self, df: Union[pd.DataFrame, np.ndarray]) -> Union[float, Tuple[float, List[float]]]:
5149
"""Perform the MCAR test on the input data.
5250
5351
Parameters
@@ -99,8 +97,7 @@ def __init__(
9997
super().__init__()
10098
if imputer and imputer.model != "multinormal":
10199
raise AttributeError(
102-
"The ImputerEM model must be 'multinormal' "
103-
"to use the Little's test"
100+
"The ImputerEM model must be 'multinormal' " "to use the Little's test"
104101
)
105102
self.imputer = imputer
106103
self.random_state = random_state
@@ -131,22 +128,16 @@ def test(self, df: pd.DataFrame) -> float:
131128
# Iterate over the patterns
132129

133130
df_nan = df.notna()
134-
for tup_pattern, df_nan_pattern in df_nan.groupby(
135-
df_nan.columns.tolist()
136-
):
131+
for tup_pattern, df_nan_pattern in df_nan.groupby(df_nan.columns.tolist()):
137132
n_rows_pattern, _ = df_nan_pattern.shape
138133
ind_pattern = df_nan_pattern.index
139134
df_pattern = df.loc[ind_pattern, list(tup_pattern)]
140135
obs_mean = df_pattern.mean().to_numpy()
141136

142137
diff_means = obs_mean - ml_means[list(tup_pattern)]
143-
inv_sigma_pattern = np.linalg.inv(
144-
ml_cov[:, tup_pattern][tup_pattern, :]
145-
)
138+
inv_sigma_pattern = np.linalg.inv(ml_cov[:, tup_pattern][tup_pattern, :])
146139

147-
d0 += n_rows_pattern * np.dot(
148-
np.dot(diff_means, inv_sigma_pattern), diff_means.T
149-
)
140+
d0 += n_rows_pattern * np.dot(np.dot(diff_means, inv_sigma_pattern), diff_means.T)
150141
degree_f += tup_pattern.count(True)
151142

152143
return 1 - float(chi2.cdf(d0, degree_f))
@@ -242,9 +233,7 @@ def _encode_dataframe(self, df: pd.DataFrame) -> np.ndarray:
242233

243234
return self.encoder.fit_transform(df)
244235

245-
def _pklm_preprocessing(
246-
self, X: Union[pd.DataFrame, np.ndarray]
247-
) -> np.ndarray:
236+
def _pklm_preprocessing(self, X: Union[pd.DataFrame, np.ndarray]) -> np.ndarray:
248237
"""Preprocess the input DataFrame or ndarray for further processing.
249238
250239
Parameters
@@ -295,9 +284,7 @@ def _get_max_draw(p: int) -> int:
295284
"""
296285
return p * (2 ** (p - 1) - 1)
297286

298-
def _draw_features_and_target_indexes(
299-
self, X: np.ndarray
300-
) -> Tuple[List[int], int]:
287+
def _draw_features_and_target_indexes(self, X: np.ndarray) -> Tuple[List[int], int]:
301288
"""Randomly select features and a target from the dataframe.
302289
303290
This corresponds to the Ai and Bi projections of the paper.
@@ -320,9 +307,7 @@ def _draw_features_and_target_indexes(
320307
return features_idx.tolist(), target_idx
321308

322309
@staticmethod
323-
def _check_draw(
324-
X: np.ndarray, features_idx: List[int], target_idx: int
325-
) -> bool:
310+
def _check_draw(X: np.ndarray, features_idx: List[int], target_idx: int) -> bool:
326311
"""Check if the drawn features and target are valid.
327312
328313
Here we check
@@ -344,16 +329,12 @@ def _check_draw(
344329
True if the draw is valid, False otherwise.
345330
346331
"""
347-
target_values = X[~np.isnan(X[:, features_idx]).any(axis=1)][
348-
:, target_idx
349-
]
332+
target_values = X[~np.isnan(X[:, features_idx]).any(axis=1)][:, target_idx]
350333
is_nan = np.isnan(target_values).any()
351334
is_distinct_values = (~np.isnan(target_values)).any()
352335
return is_nan and is_distinct_values
353336

354-
def _generate_label_feature_combinations(
355-
self, X: np.ndarray
356-
) -> List[Tuple[List[int], int]]:
337+
def _generate_label_feature_combinations(self, X: np.ndarray) -> List[Tuple[List[int], int]]:
357338
"""Generate all valid combinations of features and labels.
358339
359340
Parameters
@@ -400,9 +381,7 @@ def _draw_projection(self, X: np.ndarray) -> Tuple[List[int], int]:
400381
"""
401382
is_checked = False
402383
while not is_checked:
403-
features_idx, target_idx = self._draw_features_and_target_indexes(
404-
X
405-
)
384+
features_idx, target_idx = self._draw_features_and_target_indexes(X)
406385
is_checked = self._check_draw(X, features_idx, target_idx)
407386
return features_idx, target_idx
408387

@@ -434,13 +413,9 @@ def _build_dataset(
434413
the target column.
435414
436415
"""
437-
X_features = X[~np.isnan(X[:, features_idx]).any(axis=1)][
438-
:, features_idx
439-
]
416+
X_features = X[~np.isnan(X[:, features_idx]).any(axis=1)][:, features_idx]
440417
y = np.where(
441-
np.isnan(
442-
X[~np.isnan(X[:, features_idx]).any(axis=1)][:, target_idx]
443-
),
418+
np.isnan(X[~np.isnan(X[:, features_idx]).any(axis=1)][:, target_idx]),
444419
1,
445420
0,
446421
)
@@ -479,9 +454,7 @@ def _build_label(
479454
"""
480455
return perm[~np.isnan(X[:, features_idx]).any(axis=1), target_idx]
481456

482-
def _get_oob_probabilities(
483-
self, X: np.ndarray, y: np.ndarray
484-
) -> np.ndarray:
457+
def _get_oob_probabilities(self, X: np.ndarray, y: np.ndarray) -> np.ndarray:
485458
"""Retrieve out-of-bag probabilities.
486459
487460
Train a RandomForestClassifier and retrieves out-of-bag (OOB)
@@ -547,26 +520,14 @@ def _U_hat(oob_probabilities: np.ndarray, labels: np.ndarray) -> float:
547520
if unique_labels.shape[0] == 1:
548521
if unique_labels[0] == 0:
549522
n0 = labels.shape[0]
550-
return (
551-
np.log(p0_0 / (1 - p0_0)).sum() / n0
552-
- np.log(p1_0 / (1 - p1_0)).sum() / n0
553-
)
523+
return np.log(p0_0 / (1 - p0_0)).sum() / n0 - np.log(p1_0 / (1 - p1_0)).sum() / n0
554524
else:
555525
n1 = labels.shape[0]
556-
return (
557-
np.log(p1_1 / (1 - p1_1)).sum() / n1
558-
- np.log(p0_1 / (1 - p0_1)).sum() / n1
559-
)
526+
return np.log(p1_1 / (1 - p1_1)).sum() / n1 - np.log(p0_1 / (1 - p0_1)).sum() / n1
560527

561528
n0, n1 = label_matrix.sum(axis=0)
562-
u_0 = (
563-
np.log(p0_0 / (1 - p0_0)).sum() / n0
564-
- np.log(p0_1 / (1 - p0_1)).sum() / n1
565-
)
566-
u_1 = (
567-
np.log(p1_1 / (1 - p1_1)).sum() / n1
568-
- np.log(p1_0 / (1 - p1_0)).sum() / n0
569-
)
529+
u_0 = np.log(p0_0 / (1 - p0_0)).sum() / n0 - np.log(p0_1 / (1 - p0_1)).sum() / n1
530+
u_1 = np.log(p1_1 / (1 - p1_1)).sum() / n1 - np.log(p1_0 / (1 - p1_0)).sum() / n0
570531

571532
return u_0 + u_1
572533

@@ -708,9 +669,7 @@ def _compute_partial_p_value(
708669

709670
return p_v_k / (self.nb_permutation + 1)
710671

711-
def test(
712-
self, X: Union[pd.DataFrame, np.ndarray]
713-
) -> Union[float, Tuple[float, List[float]]]:
672+
def test(self, X: Union[pd.DataFrame, np.ndarray]) -> Union[float, Tuple[float, List[float]]]:
714673
"""Apply the PKLM test over a real dataset.
715674
716675
Parameters
@@ -733,21 +692,15 @@ def test(
733692
if self._get_max_draw(n_cols) <= self.nb_projections_threshold:
734693
list_proj = self._generate_label_feature_combinations(X)
735694
else:
736-
list_proj = [
737-
self._draw_projection(X) for _ in range(self.nb_projections)
738-
]
695+
list_proj = [self._draw_projection(X) for _ in range(self.nb_projections)]
739696

740697
M = np.isnan(X).astype(int)
741-
list_perm = [
742-
self.rng.permutation(M) for _ in range(self.nb_permutation)
743-
]
698+
list_perm = [self.rng.permutation(M) for _ in range(self.nb_permutation)]
744699
U = 0.0
745700
list_U_sigma = [0.0 for _ in range(self.nb_permutation)]
746701

747702
parallel_results = Parallel(n_jobs=-1)(
748-
delayed(self._parallel_process_projection)(
749-
X, list_perm, features_idx, target_idx
750-
)
703+
delayed(self._parallel_process_projection)(X, list_perm, features_idx, target_idx)
751704
for features_idx, target_idx in list_proj
752705
)
753706

@@ -769,14 +722,9 @@ def test(
769722
return p_value
770723
else:
771724
B = self._build_B(list_proj, n_cols)
772-
U_matrix = np.array(
773-
[np.atleast_1d(item[0]) for item in parallel_results]
774-
)
775-
U_sigma = np.array(
776-
[np.atleast_1d(item[1]) for item in parallel_results]
777-
)
725+
U_matrix = np.array([np.atleast_1d(item[0]) for item in parallel_results])
726+
U_sigma = np.array([np.atleast_1d(item[1]) for item in parallel_results])
778727
p_values = [
779-
self._compute_partial_p_value(B, U_matrix, U_sigma, k)
780-
for k in range(n_cols)
728+
self._compute_partial_p_value(B, U_matrix, U_sigma, k) for k in range(n_cols)
781729
]
782730
return p_value, p_values

qolmat/benchmark/comparator.py

Lines changed: 15 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,11 @@ def get_errors(
8484
dict_errors = {}
8585
for name_metric in self.metrics:
8686
fun_metric = metrics.get_metric(name_metric)
87-
dict_errors[name_metric] = fun_metric(
88-
df_origin, df_imputed, df_mask
89-
)
87+
dict_errors[name_metric] = fun_metric(df_origin, df_imputed, df_mask)
9088
df_errors = pd.concat(dict_errors.values(), keys=dict_errors.keys())
9189
return df_errors
9290

93-
def process_split(
94-
self, split_data: Tuple[int, pd.DataFrame, pd.DataFrame]
95-
) -> pd.DataFrame:
91+
def process_split(self, split_data: Tuple[int, pd.DataFrame, pd.DataFrame]) -> pd.DataFrame:
9692
"""Process a split.
9793
9894
Parameters
@@ -119,15 +115,12 @@ def process_split(
119115
subset = self.generator_holes.subset
120116
if subset is None:
121117
raise ValueError(
122-
"HoleGenerator `subset` should be overwritten in split "
123-
"but it is none!"
118+
"HoleGenerator `subset` should be overwritten in split " "but it is none!"
124119
)
125120

126121
split_results = {}
127122
for imputer_name, imputer in self.dict_imputers.items():
128-
dict_config_opti_imputer = self.dict_config_opti.get(
129-
imputer_name, {}
130-
)
123+
dict_config_opti_imputer = self.dict_config_opti.get(imputer_name, {})
131124

132125
imputer_opti = hyperparameters.optimize(
133126
imputer,
@@ -140,9 +133,7 @@ def process_split(
140133
)
141134

142135
df_imputed = imputer_opti.fit_transform(df_with_holes)
143-
errors = self.get_errors(
144-
df_origin[subset], df_imputed[subset], df_mask[subset]
145-
)
136+
errors = self.get_errors(df_origin[subset], df_imputed[subset], df_mask[subset])
146137
split_results[imputer_name] = errors
147138

148139
return pd.concat(split_results, axis=1)
@@ -168,8 +159,7 @@ def process_imputer(
168159
subset = self.generator_holes.subset
169160
if subset is None:
170161
raise ValueError(
171-
"HoleGenerator `subset` should be overwritten in split "
172-
"but it is none!"
162+
"HoleGenerator `subset` should be overwritten in split " "but it is none!"
173163
)
174164

175165
dict_config_opti_imputer = self.dict_config_opti.get(imputer_name, {})
@@ -188,14 +178,10 @@ def process_imputer(
188178
df_with_holes = df_origin.copy()
189179
df_with_holes[df_mask] = np.nan
190180
df_imputed = imputer_opti.fit_transform(df_with_holes)
191-
errors = self.get_errors(
192-
df_origin[subset], df_imputed[subset], df_mask[subset]
193-
)
181+
errors = self.get_errors(df_origin[subset], df_imputed[subset], df_mask[subset])
194182
imputer_results.append(errors)
195183

196-
return imputer_name, pd.concat(imputer_results).groupby(
197-
level=[0, 1]
198-
).mean()
184+
return imputer_name, pd.concat(imputer_results).groupby(level=[0, 1]).mean()
199185

200186
def compare(
201187
self,
@@ -229,26 +215,17 @@ def compare(
229215
1-level index are the column names.
230216
231217
"""
232-
logging.info(
233-
f"Starting comparison for {len(self.dict_imputers)} imputers."
234-
)
218+
logging.info(f"Starting comparison for {len(self.dict_imputers)} imputers.")
235219

236220
all_splits = list(self.generator_holes.split(df_origin))
237221

238222
if parallel_over == "auto":
239-
parallel_over = (
240-
"splits"
241-
if len(all_splits) > len(self.dict_imputers)
242-
else "imputers"
243-
)
223+
parallel_over = "splits" if len(all_splits) > len(self.dict_imputers) else "imputers"
244224

245225
if use_parallel:
246226
logging.info(f"Parallelisation over: {parallel_over}...")
247227
if parallel_over == "splits":
248-
split_data = [
249-
(i, df_mask, df_origin)
250-
for i, df_mask in enumerate(all_splits)
251-
]
228+
split_data = [(i, df_mask, df_origin) for i, df_mask in enumerate(all_splits)]
252229
n_jobs = self.get_optimal_n_jobs(split_data, n_jobs)
253230
results = Parallel(n_jobs=n_jobs)(
254231
delayed(self.process_split)(data) for data in split_data
@@ -261,22 +238,16 @@ def compare(
261238
]
262239
n_jobs = self.get_optimal_n_jobs(imputer_data, n_jobs)
263240
results = Parallel(n_jobs=n_jobs)(
264-
delayed(self.process_imputer)(data)
265-
for data in imputer_data
241+
delayed(self.process_imputer)(data) for data in imputer_data
266242
)
267243
final_results = pd.concat(dict(results), axis=1)
268244
else:
269-
raise ValueError(
270-
"`parallel_over` should be `auto`, `splits` or `imputers`."
271-
)
245+
raise ValueError("`parallel_over` should be `auto`, `splits` or `imputers`.")
272246

273247
else:
274248
logging.info("Sequential treatment...")
275249
if parallel_over == "splits":
276-
split_data = [
277-
(i, df_mask, df_origin)
278-
for i, df_mask in enumerate(all_splits)
279-
]
250+
split_data = [(i, df_mask, df_origin) for i, df_mask in enumerate(all_splits)]
280251
results = [self.process_split(data) for data in split_data]
281252
final_results = pd.concat(results).groupby(level=[0, 1]).mean()
282253
elif parallel_over == "imputers":
@@ -287,9 +258,7 @@ def compare(
287258
results = [self.process_imputer(data) for data in imputer_data]
288259
final_results = pd.concat(dict(results), axis=1)
289260
else:
290-
raise ValueError(
291-
"`parallel_over` should be `auto`, `splits` or `imputers`."
292-
)
261+
raise ValueError("`parallel_over` should be `auto`, `splits` or `imputers`.")
293262

294263
logging.info("Comparison successfully terminated.")
295264
return final_results

qolmat/benchmark/hyperparameters.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,7 @@ def fun_obf(args: List[HyperValue]) -> float:
6262
df_imputed = imputer.fit_transform(df_corrupted)
6363
subset = generator.subset
6464
fun_metric = metrics.get_metric(metric)
65-
errors = fun_metric(
66-
df_origin[subset], df_imputed[subset], df_mask[subset]
67-
)
65+
errors = fun_metric(df_origin[subset], df_imputed[subset], df_mask[subset])
6866
list_errors.append(errors)
6967

7068
mean_errors = np.mean(errors)
@@ -120,9 +118,7 @@ def optimize(
120118
return imputer
121119
names_hyperparams = list(dict_config.keys())
122120
values_hyperparams = list(dict_config.values())
123-
imputer.imputer_params = tuple(
124-
set(imputer.imputer_params) | set(dict_config.keys())
125-
)
121+
imputer.imputer_params = tuple(set(imputer.imputer_params) | set(dict_config.keys()))
126122
if verbose and hasattr(imputer, "verbose"):
127123
setattr(imputer, "verbose", False)
128124
fun_obj = get_objective(imputer, df, generator, metric, names_hyperparams)

0 commit comments

Comments
 (0)