FeatureRank 0.1.2__tar.gz → 0.1.4__tar.gz
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.
- {featurerank-0.1.2 → featurerank-0.1.4}/FeatureRank/GUI.py +181 -43
- {featurerank-0.1.2 → featurerank-0.1.4}/FeatureRank/__init__.py +1 -1
- {featurerank-0.1.2 → featurerank-0.1.4/FeatureRank.egg-info}/PKG-INFO +75 -30
- featurerank-0.1.4/FeatureRank.egg-info/requires.txt +11 -0
- featurerank-0.1.2/README.md → featurerank-0.1.4/PKG-INFO +87 -27
- featurerank-0.1.2/FeatureRank.egg-info/PKG-INFO → featurerank-0.1.4/README.md +71 -42
- {featurerank-0.1.2 → featurerank-0.1.4}/pyproject.toml +4 -3
- featurerank-0.1.2/FeatureRank.egg-info/requires.txt +0 -6
- {featurerank-0.1.2 → featurerank-0.1.4}/FeatureRank/__main__.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/FeatureRank.egg-info/SOURCES.txt +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/FeatureRank.egg-info/dependency_links.txt +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/FeatureRank.egg-info/entry_points.txt +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/FeatureRank.egg-info/top_level.txt +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/scripts/EvaluatePaperDimensionReduction.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/scripts/FeatureBlockDatasetTools.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/scripts/FeatureRank.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/scripts/Figure_Scripts/CreateAccuracyBoxplotFromList.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/scripts/Figure_Scripts/CreateClassificationFigure.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/scripts/Figure_Scripts/CreateClusterClassificationSummaryFigure.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/scripts/Figure_Scripts/CreateClusterFigure1.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/scripts/Figure_Scripts/CreateConfusionMatrixPanel.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/scripts/Figure_Scripts/CreatePrecisionRecallPanel.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/scripts/Figure_Scripts/CreateRegressionFigure2.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/scripts/Figure_Scripts/CreateRocPanel.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/scripts/GeneratePaperDimensionReduction.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/scripts/PaperDimensionReductionCommon.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/scripts/RunAutoencoder.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/scripts/RunBlockFeatureSelection.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/scripts/RunDimensionReduction.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/scripts/RunFeatureRankCV.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/scripts/Simple.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/scripts/__init__.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/setup.cfg +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/src/AutoencoderFeatureSelection.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/src/Classification.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/src/Clustering.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/src/Config.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/src/DataLoader.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/src/DivideCombine.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/src/Experiment.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/src/Models.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/src/OutputPaths.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/src/Preprocessing.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/src/Regression.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/src/Reporting.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/src/Runtime.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/src/Utils.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/src/Workflow.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/src/__init__.py +0 -0
- {featurerank-0.1.2 → featurerank-0.1.4}/tests/test_feature_rank_refactor.py +0 -0
|
@@ -19,6 +19,7 @@ from tkinter import filedialog, messagebox, ttk
|
|
|
19
19
|
|
|
20
20
|
PERCENT_VALUES = tuple(str(value) for value in range(10, 101, 10))
|
|
21
21
|
BLOCK_VALUES = ("5", "10", "20")
|
|
22
|
+
NO_DATASET_LABEL = "No Dataset"
|
|
22
23
|
MODE_LABELS = {
|
|
23
24
|
"GL": "GL — Global Feature Ranking",
|
|
24
25
|
"DC": "DC — Divide & Combine Feature Ranking",
|
|
@@ -40,9 +41,42 @@ def _dataset_choices() -> list[str]:
|
|
|
40
41
|
if not directory.exists():
|
|
41
42
|
continue
|
|
42
43
|
for data_file in sorted(directory.glob("*_data.csv")):
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
absolute_path = str(data_file.resolve())
|
|
45
|
+
if data_file.is_file() and absolute_path not in choices:
|
|
46
|
+
choices.append(absolute_path)
|
|
47
|
+
return choices or [NO_DATASET_LABEL]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _resolve_dataset_path(dataset: str) -> str:
|
|
51
|
+
"""Use an absolute path when the selected file exists locally."""
|
|
52
|
+
requested = Path(dataset).expanduser()
|
|
53
|
+
if requested.is_file():
|
|
54
|
+
return str(requested.resolve())
|
|
55
|
+
|
|
56
|
+
project_root = Path(__file__).resolve().parent.parent
|
|
57
|
+
candidates = (
|
|
58
|
+
Path.cwd() / requested,
|
|
59
|
+
Path.cwd() / "data" / "raw" / requested,
|
|
60
|
+
project_root / requested,
|
|
61
|
+
project_root / "data" / "raw" / requested,
|
|
62
|
+
)
|
|
63
|
+
for candidate in candidates:
|
|
64
|
+
if candidate.is_file():
|
|
65
|
+
return str(candidate.resolve())
|
|
66
|
+
return dataset
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _worker_python() -> Path:
|
|
70
|
+
"""Find a project virtual-environment interpreter before the system one."""
|
|
71
|
+
project_root = Path(__file__).resolve().parent.parent
|
|
72
|
+
executable_name = "python.exe" if os.name == "nt" else "python"
|
|
73
|
+
candidates = (
|
|
74
|
+
Path.cwd() / ".venv" / ("Scripts" if os.name == "nt" else "bin") / executable_name,
|
|
75
|
+
project_root / ".venv" / ("Scripts" if os.name == "nt" else "bin") / executable_name,
|
|
76
|
+
Path(sys.executable).with_name("python"),
|
|
77
|
+
Path(sys.executable),
|
|
78
|
+
)
|
|
79
|
+
return next((candidate for candidate in candidates if candidate.is_file()), candidates[-1])
|
|
46
80
|
|
|
47
81
|
|
|
48
82
|
def _open_folder(path: Path) -> None:
|
|
@@ -79,16 +113,27 @@ class FeatureRankGUI:
|
|
|
79
113
|
def __init__(self, root: tk.Tk) -> None:
|
|
80
114
|
self.root = root
|
|
81
115
|
self.root.title("FeatureRank")
|
|
82
|
-
|
|
83
|
-
self.root.
|
|
116
|
+
# Keep the first view compact; the log can still grow when resized.
|
|
117
|
+
self.root.geometry("760x680")
|
|
118
|
+
self.root.minsize(680, 600)
|
|
84
119
|
|
|
85
120
|
self.messages: queue.Queue[tuple[str, object]] = queue.Queue()
|
|
86
121
|
self.worker: threading.Thread | None = None
|
|
87
122
|
self.last_output_dir: Path | None = None
|
|
88
123
|
self.block_count = 10
|
|
89
124
|
self.blocks_seen = 0
|
|
125
|
+
self.progress_value = 0.0
|
|
126
|
+
self.progress_started_at: float | None = None
|
|
127
|
+
self.progress_job: str | None = None
|
|
128
|
+
self.progress_running = False
|
|
129
|
+
|
|
130
|
+
dataset_paths = _dataset_choices()
|
|
131
|
+
self.dataset_paths = {
|
|
132
|
+
Path(path).name: path for path in dataset_paths if path != NO_DATASET_LABEL
|
|
133
|
+
}
|
|
134
|
+
self.dataset_labels = [NO_DATASET_LABEL, *self.dataset_paths]
|
|
90
135
|
|
|
91
|
-
self.dataset_var = tk.StringVar(value=
|
|
136
|
+
self.dataset_var = tk.StringVar(value=NO_DATASET_LABEL)
|
|
92
137
|
self.percent_var = tk.StringVar(value="20")
|
|
93
138
|
self.task_var = tk.StringVar(value="Classification")
|
|
94
139
|
self.mode_var = tk.StringVar(value=MODE_LABELS["GL"])
|
|
@@ -121,16 +166,56 @@ class FeatureRankGUI:
|
|
|
121
166
|
style.theme_use("clam")
|
|
122
167
|
except tk.TclError:
|
|
123
168
|
pass
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
style.configure("
|
|
127
|
-
style.configure("
|
|
169
|
+
# A restrained palette gives the form a clean surface on all platforms.
|
|
170
|
+
self.root.configure(background="#f4f6f8")
|
|
171
|
+
style.configure("TFrame", background="#f4f6f8")
|
|
172
|
+
style.configure("TLabel", background="#ffffff", foreground="#1f2937")
|
|
173
|
+
style.configure("TLabelframe", background="#ffffff")
|
|
174
|
+
style.configure("TLabelframe.Label", background="#ffffff", foreground="#1f2937")
|
|
175
|
+
style.configure(
|
|
176
|
+
"Title.TLabel",
|
|
177
|
+
background="#f4f6f8",
|
|
178
|
+
font=("Helvetica", 20, "bold"),
|
|
179
|
+
foreground="#1f2937",
|
|
180
|
+
)
|
|
181
|
+
style.configure(
|
|
182
|
+
"Subtitle.TLabel",
|
|
183
|
+
background="#f4f6f8",
|
|
184
|
+
font=("Helvetica", 10),
|
|
185
|
+
foreground="#4b5563",
|
|
186
|
+
)
|
|
187
|
+
style.configure(
|
|
188
|
+
"Stage.TLabel",
|
|
189
|
+
background="#f4f6f8",
|
|
190
|
+
font=("Helvetica", 11, "bold"),
|
|
191
|
+
foreground="#1f4e79",
|
|
192
|
+
)
|
|
193
|
+
style.configure(
|
|
194
|
+
"Run.TButton",
|
|
195
|
+
background="#1f4e79",
|
|
196
|
+
foreground="#ffffff",
|
|
197
|
+
font=("Helvetica", 11, "bold"),
|
|
198
|
+
padding=(16, 6),
|
|
199
|
+
)
|
|
200
|
+
style.map(
|
|
201
|
+
"Run.TButton",
|
|
202
|
+
background=[("active", "#173a5c"), ("disabled", "#b9c3cf")],
|
|
203
|
+
foreground=[("disabled", "#eef2f6")],
|
|
204
|
+
)
|
|
205
|
+
style.configure(
|
|
206
|
+
"Horizontal.TProgressbar",
|
|
207
|
+
background="#1f4e79",
|
|
208
|
+
troughcolor="#e5e7eb",
|
|
209
|
+
bordercolor="#d1d5db",
|
|
210
|
+
lightcolor="#1f4e79",
|
|
211
|
+
darkcolor="#1f4e79",
|
|
212
|
+
)
|
|
128
213
|
|
|
129
214
|
def _build_window(self) -> None:
|
|
130
215
|
self.root.columnconfigure(0, weight=1)
|
|
131
216
|
self.root.rowconfigure(3, weight=1)
|
|
132
217
|
|
|
133
|
-
header = ttk.Frame(self.root, padding=(
|
|
218
|
+
header = ttk.Frame(self.root, padding=(24, 18, 24, 8))
|
|
134
219
|
header.grid(row=0, column=0, sticky="ew")
|
|
135
220
|
ttk.Label(header, text="FeatureRank", style="Title.TLabel").pack(anchor="w")
|
|
136
221
|
ttk.Label(
|
|
@@ -139,53 +224,55 @@ class FeatureRankGUI:
|
|
|
139
224
|
style="Subtitle.TLabel",
|
|
140
225
|
).pack(anchor="w", pady=(4, 0))
|
|
141
226
|
|
|
142
|
-
options = ttk.LabelFrame(self.root, text="Experiment", padding=
|
|
143
|
-
options.grid(row=1, column=0, padx=
|
|
227
|
+
options = ttk.LabelFrame(self.root, text="Experiment", padding=14)
|
|
228
|
+
options.grid(row=1, column=0, padx=24, pady=6, sticky="ew")
|
|
144
229
|
options.columnconfigure(1, weight=1)
|
|
145
230
|
|
|
146
|
-
ttk.Label(options, text="Dataset").grid(row=0, column=0, sticky="w", pady=
|
|
231
|
+
ttk.Label(options, text="Dataset").grid(row=0, column=0, sticky="w", pady=5)
|
|
147
232
|
self.dataset_combo = ttk.Combobox(
|
|
148
|
-
options, textvariable=self.dataset_var, values=
|
|
233
|
+
options, textvariable=self.dataset_var, values=self.dataset_labels, state="readonly"
|
|
149
234
|
)
|
|
150
|
-
self.dataset_combo.grid(row=0, column=1, sticky="ew", padx=(
|
|
235
|
+
self.dataset_combo.grid(row=0, column=1, sticky="ew", padx=(10, 6), pady=5)
|
|
151
236
|
ttk.Button(options, text="Browse…", command=self._browse_dataset).grid(
|
|
152
|
-
row=0, column=2, pady=
|
|
237
|
+
row=0, column=2, pady=5
|
|
153
238
|
)
|
|
154
239
|
|
|
155
|
-
ttk.Label(options, text="Task").grid(row=1, column=0, sticky="w", pady=
|
|
240
|
+
ttk.Label(options, text="Task").grid(row=1, column=0, sticky="w", pady=5)
|
|
156
241
|
ttk.Combobox(
|
|
157
242
|
options,
|
|
158
243
|
textvariable=self.task_var,
|
|
159
244
|
values=tuple(TASK_LABELS),
|
|
160
245
|
state="readonly",
|
|
161
|
-
).grid(row=1, column=1, sticky="ew", padx=(
|
|
246
|
+
).grid(row=1, column=1, sticky="ew", padx=(10, 6), pady=5)
|
|
162
247
|
|
|
163
|
-
ttk.Label(options, text="Feature Percent").grid(row=2, column=0, sticky="w", pady=
|
|
248
|
+
ttk.Label(options, text="Feature Percent").grid(row=2, column=0, sticky="w", pady=5)
|
|
164
249
|
ttk.Combobox(
|
|
165
250
|
options,
|
|
166
251
|
textvariable=self.percent_var,
|
|
167
252
|
values=PERCENT_VALUES,
|
|
168
253
|
state="readonly",
|
|
169
254
|
width=12,
|
|
170
|
-
).grid(row=2, column=1, sticky="w", padx=(
|
|
255
|
+
).grid(row=2, column=1, sticky="w", padx=(10, 6), pady=5)
|
|
171
256
|
|
|
172
|
-
ttk.Label(options, text="Mode").grid(row=3, column=0, sticky="w", pady=
|
|
257
|
+
ttk.Label(options, text="Mode").grid(row=3, column=0, sticky="w", pady=5)
|
|
173
258
|
self.mode_combo = ttk.Combobox(
|
|
174
259
|
options,
|
|
175
260
|
textvariable=self.mode_var,
|
|
176
261
|
values=tuple(MODE_LABELS.values()),
|
|
177
262
|
state="readonly",
|
|
178
263
|
)
|
|
179
|
-
self.mode_combo.grid(row=3, column=1, sticky="ew", padx=(
|
|
264
|
+
self.mode_combo.grid(row=3, column=1, sticky="ew", padx=(10, 6), pady=5)
|
|
180
265
|
self.mode_combo.bind("<<ComboboxSelected>>", self._mode_changed)
|
|
181
266
|
|
|
182
|
-
ttk.Label(options, text="Block Count").grid(row=4, column=0, sticky="w", pady=
|
|
267
|
+
ttk.Label(options, text="Block Count").grid(row=4, column=0, sticky="w", pady=5)
|
|
183
268
|
self.block_combo = ttk.Combobox(
|
|
184
269
|
options, textvariable=self.block_var, values=BLOCK_VALUES, state="disabled", width=12
|
|
185
270
|
)
|
|
186
|
-
self.block_combo.grid(row=4, column=1, sticky="w", padx=(
|
|
271
|
+
self.block_combo.grid(row=4, column=1, sticky="w", padx=(10, 6), pady=5)
|
|
187
272
|
|
|
188
|
-
|
|
273
|
+
# Keep the run controls in a shallow toolbar so START is not surrounded
|
|
274
|
+
# by a large empty band.
|
|
275
|
+
action_row = ttk.Frame(self.root, padding=(24, 2, 24, 2))
|
|
189
276
|
action_row.grid(row=2, column=0, sticky="ew")
|
|
190
277
|
action_row.columnconfigure(1, weight=1)
|
|
191
278
|
self.start_button = ttk.Button(
|
|
@@ -196,14 +283,14 @@ class FeatureRankGUI:
|
|
|
196
283
|
row=0, column=1, sticky="e", padx=(12, 0)
|
|
197
284
|
)
|
|
198
285
|
|
|
199
|
-
progress_frame = ttk.Frame(self.root, padding=(
|
|
286
|
+
progress_frame = ttk.Frame(self.root, padding=(24, 0, 24, 8))
|
|
200
287
|
progress_frame.grid(row=3, column=0, sticky="nsew")
|
|
201
288
|
progress_frame.columnconfigure(0, weight=1)
|
|
202
289
|
progress_frame.rowconfigure(2, weight=1)
|
|
203
290
|
self.progress = ttk.Progressbar(progress_frame, mode="determinate", maximum=100)
|
|
204
291
|
self.progress.grid(row=0, column=0, sticky="ew")
|
|
205
292
|
ttk.Label(progress_frame, textvariable=self.status_var, style="Subtitle.TLabel").grid(
|
|
206
|
-
row=1, column=0, sticky="w", pady=(
|
|
293
|
+
row=1, column=0, sticky="w", pady=(5, 6)
|
|
207
294
|
)
|
|
208
295
|
|
|
209
296
|
log_frame = ttk.LabelFrame(progress_frame, text="Log / Output", padding=8)
|
|
@@ -212,7 +299,7 @@ class FeatureRankGUI:
|
|
|
212
299
|
log_frame.rowconfigure(0, weight=1)
|
|
213
300
|
self.log_text = tk.Text(
|
|
214
301
|
log_frame,
|
|
215
|
-
height=
|
|
302
|
+
height=8,
|
|
216
303
|
wrap="word",
|
|
217
304
|
state="disabled",
|
|
218
305
|
background="#f8fafc",
|
|
@@ -226,19 +313,19 @@ class FeatureRankGUI:
|
|
|
226
313
|
scrollbar.grid(row=0, column=1, sticky="ns")
|
|
227
314
|
self.log_text.configure(yscrollcommand=scrollbar.set)
|
|
228
315
|
|
|
229
|
-
summary = ttk.LabelFrame(self.root, text="Summary", padding=
|
|
230
|
-
summary.grid(row=4, column=0, padx=
|
|
316
|
+
summary = ttk.LabelFrame(self.root, text="Summary", padding=12)
|
|
317
|
+
summary.grid(row=4, column=0, padx=24, pady=(0, 8), sticky="ew")
|
|
231
318
|
summary.columnconfigure(1, weight=1)
|
|
232
319
|
for row, (key, value) in enumerate(self.summary_vars.items()):
|
|
233
320
|
ttk.Label(summary, text=key).grid(row=row // 2, column=(row % 2) * 2, sticky="w")
|
|
234
321
|
ttk.Label(summary, textvariable=value, foreground="#1f4e79").grid(
|
|
235
|
-
row=row // 2, column=(row % 2) * 2 + 1, sticky="w", padx=(8,
|
|
322
|
+
row=row // 2, column=(row % 2) * 2 + 1, sticky="w", padx=(8, 22), pady=2
|
|
236
323
|
)
|
|
237
324
|
|
|
238
325
|
self.open_button = ttk.Button(
|
|
239
326
|
self.root, text="Open Results Folder", command=self._open_results, state="disabled"
|
|
240
327
|
)
|
|
241
|
-
self.open_button.grid(row=5, column=0, padx=
|
|
328
|
+
self.open_button.grid(row=5, column=0, padx=24, pady=(0, 14), sticky="w")
|
|
242
329
|
|
|
243
330
|
def _browse_dataset(self) -> None:
|
|
244
331
|
selected = filedialog.askopenfilename(
|
|
@@ -246,14 +333,20 @@ class FeatureRankGUI:
|
|
|
246
333
|
filetypes=[("CSV files", "*.csv"), ("Text files", "*.txt"), ("All files", "*.*")],
|
|
247
334
|
)
|
|
248
335
|
if selected:
|
|
249
|
-
|
|
336
|
+
# Keep the full path internally, but show only the file name in
|
|
337
|
+
# the form so the selected dataset remains readable.
|
|
338
|
+
self.dataset_paths[Path(selected).name] = selected
|
|
339
|
+
self.dataset_labels = [NO_DATASET_LABEL, *self.dataset_paths]
|
|
340
|
+
self.dataset_combo.configure(values=self.dataset_labels)
|
|
341
|
+
self.dataset_var.set(Path(selected).name)
|
|
250
342
|
|
|
251
343
|
def _mode_changed(self, _event: object = None) -> None:
|
|
252
344
|
is_dc = self.mode_var.get().startswith("DC")
|
|
253
345
|
self.block_combo.configure(state="readonly" if is_dc else "disabled")
|
|
254
346
|
|
|
255
347
|
def _start(self) -> None:
|
|
256
|
-
|
|
348
|
+
dataset_label = self.dataset_var.get().strip()
|
|
349
|
+
dataset = self.dataset_paths.get(dataset_label, "")
|
|
257
350
|
if not dataset:
|
|
258
351
|
messagebox.showerror("FeatureRank", "Please select a dataset.")
|
|
259
352
|
return
|
|
@@ -273,10 +366,13 @@ class FeatureRankGUI:
|
|
|
273
366
|
self.last_output_dir = None
|
|
274
367
|
self.open_button.configure(state="disabled")
|
|
275
368
|
self.start_button.configure(state="disabled")
|
|
276
|
-
self.
|
|
369
|
+
self.progress_value = 2.0
|
|
370
|
+
self.progress.configure(value=self.progress_value)
|
|
277
371
|
self.stage_var.set("Loading Dataset")
|
|
278
372
|
self.status_var.set("The experiment is running. You can follow progress in the log.")
|
|
279
373
|
self._clear_log()
|
|
374
|
+
self.progress_started_at = time.perf_counter()
|
|
375
|
+
self.progress_running = True
|
|
280
376
|
|
|
281
377
|
self.worker = threading.Thread(
|
|
282
378
|
target=self._run_experiment,
|
|
@@ -284,12 +380,14 @@ class FeatureRankGUI:
|
|
|
284
380
|
daemon=True,
|
|
285
381
|
)
|
|
286
382
|
self.worker.start()
|
|
383
|
+
self._animate_progress()
|
|
287
384
|
self.root.after(100, self._poll_messages)
|
|
288
385
|
|
|
289
386
|
def _run_experiment(
|
|
290
387
|
self, dataset: str, percent: float, task: str, mode: str, block_count: int
|
|
291
388
|
) -> None:
|
|
292
389
|
started_at = time.perf_counter()
|
|
390
|
+
output_lines: list[str] = []
|
|
293
391
|
try:
|
|
294
392
|
# Keep TensorFlow out of the Tk process. On macOS, loading a model
|
|
295
393
|
# from a GUI thread can terminate the native Python process. The
|
|
@@ -297,8 +395,9 @@ class FeatureRankGUI:
|
|
|
297
395
|
# stdout is streamed back into this window.
|
|
298
396
|
from src.Config import ExperimentConfig
|
|
299
397
|
|
|
398
|
+
dataset_path = _resolve_dataset_path(dataset)
|
|
300
399
|
config = ExperimentConfig(
|
|
301
|
-
dataset_name=
|
|
400
|
+
dataset_name=dataset_path,
|
|
302
401
|
task=task,
|
|
303
402
|
feature_percent=percent,
|
|
304
403
|
random_state=42,
|
|
@@ -308,15 +407,13 @@ class FeatureRankGUI:
|
|
|
308
407
|
cluster_k=None,
|
|
309
408
|
save_details=False,
|
|
310
409
|
)
|
|
311
|
-
python_executable =
|
|
312
|
-
if not python_executable.exists():
|
|
313
|
-
python_executable = Path(sys.executable)
|
|
410
|
+
python_executable = _worker_python()
|
|
314
411
|
command = [
|
|
315
412
|
str(python_executable),
|
|
316
413
|
"-m",
|
|
317
414
|
"scripts.FeatureRank",
|
|
318
415
|
"--dataset-name",
|
|
319
|
-
|
|
416
|
+
dataset_path,
|
|
320
417
|
"--task",
|
|
321
418
|
task,
|
|
322
419
|
"--feature-percent",
|
|
@@ -328,9 +425,18 @@ class FeatureRankGUI:
|
|
|
328
425
|
if mode == "dc":
|
|
329
426
|
command.extend(["--block-count", str(block_count)])
|
|
330
427
|
|
|
428
|
+
worker_environment = os.environ.copy()
|
|
429
|
+
worker_environment["PYTHONUNBUFFERED"] = "1"
|
|
430
|
+
package_root = str(Path(__file__).resolve().parent.parent)
|
|
431
|
+
existing_python_path = worker_environment.get("PYTHONPATH")
|
|
432
|
+
worker_environment["PYTHONPATH"] = os.pathsep.join(
|
|
433
|
+
path for path in (package_root, existing_python_path) if path
|
|
434
|
+
)
|
|
435
|
+
self.messages.put(("log", f"Worker: {python_executable}"))
|
|
331
436
|
process = subprocess.Popen(
|
|
332
437
|
command,
|
|
333
438
|
cwd=Path.cwd(),
|
|
439
|
+
env=worker_environment,
|
|
334
440
|
stdout=subprocess.PIPE,
|
|
335
441
|
stderr=subprocess.STDOUT,
|
|
336
442
|
text=True,
|
|
@@ -338,6 +444,7 @@ class FeatureRankGUI:
|
|
|
338
444
|
)
|
|
339
445
|
assert process.stdout is not None
|
|
340
446
|
for line in process.stdout:
|
|
447
|
+
output_lines.append(line.rstrip())
|
|
341
448
|
self.messages.put(("log", line))
|
|
342
449
|
return_code = process.wait()
|
|
343
450
|
if return_code != 0:
|
|
@@ -347,7 +454,9 @@ class FeatureRankGUI:
|
|
|
347
454
|
"Anaconda TensorFlow paketini guncelleyin veya uyumlu bir "
|
|
348
455
|
"Python yorumlayicisi kullanin."
|
|
349
456
|
)
|
|
350
|
-
|
|
457
|
+
recent_output = "\n".join(line for line in output_lines[-12:] if line.strip())
|
|
458
|
+
details = f"\n\nSon worker çıktısı:\n{recent_output}" if recent_output else ""
|
|
459
|
+
raise RuntimeError(f"FeatureRank worker stopped with code {return_code}.{details}")
|
|
351
460
|
|
|
352
461
|
average = 0.0
|
|
353
462
|
values: list[float] = []
|
|
@@ -489,11 +598,31 @@ class FeatureRankGUI:
|
|
|
489
598
|
elif "metrik" in lower or "metric" in lower or "tamamlandi" in lower:
|
|
490
599
|
self._set_progress("Calculating Results", 95)
|
|
491
600
|
|
|
601
|
+
def _animate_progress(self) -> None:
|
|
602
|
+
"""Advance the bar smoothly while the worker is still running.
|
|
603
|
+
|
|
604
|
+
Workflow messages move the bar to meaningful stages. This timer fills
|
|
605
|
+
the gaps between those messages based on elapsed time, then waits near
|
|
606
|
+
90% until the worker reports completion instead of pretending to know
|
|
607
|
+
the exact duration of every dataset.
|
|
608
|
+
"""
|
|
609
|
+
if not self.progress_running or self.progress_started_at is None:
|
|
610
|
+
return
|
|
611
|
+
elapsed = time.perf_counter() - self.progress_started_at
|
|
612
|
+
time_based_value = min(90.0, 2.0 + elapsed * 1.2)
|
|
613
|
+
if time_based_value > self.progress_value:
|
|
614
|
+
self.progress_value = time_based_value
|
|
615
|
+
self.progress.configure(value=self.progress_value)
|
|
616
|
+
self.status_var.set(f"The experiment is running. Elapsed time: {elapsed:.0f} s.")
|
|
617
|
+
self.progress_job = self.root.after(200, self._animate_progress)
|
|
618
|
+
|
|
492
619
|
def _set_progress(self, stage: str, value: int) -> None:
|
|
493
620
|
self.stage_var.set(stage)
|
|
494
|
-
self.
|
|
621
|
+
self.progress_value = max(self.progress_value, float(min(value, 99)))
|
|
622
|
+
self.progress.configure(value=self.progress_value)
|
|
495
623
|
|
|
496
624
|
def _completed(self, summary: dict[str, str]) -> None:
|
|
625
|
+
self._stop_progress_animation()
|
|
497
626
|
for key, value in summary.items():
|
|
498
627
|
self.summary_vars[key].set(value)
|
|
499
628
|
output = summary.get("Output Directory", "N/A")
|
|
@@ -506,13 +635,22 @@ class FeatureRankGUI:
|
|
|
506
635
|
self.start_button.configure(state="normal")
|
|
507
636
|
|
|
508
637
|
def _failed(self, error: str) -> None:
|
|
638
|
+
self._stop_progress_animation()
|
|
509
639
|
self.progress.configure(value=0)
|
|
640
|
+
self.progress_value = 0.0
|
|
510
641
|
self.stage_var.set("Failed")
|
|
511
642
|
self.status_var.set("The experiment could not be completed.")
|
|
512
643
|
self._append_log(f"ERROR: {error}")
|
|
513
644
|
self.start_button.configure(state="normal")
|
|
514
645
|
messagebox.showerror("FeatureRank", error)
|
|
515
646
|
|
|
647
|
+
def _stop_progress_animation(self) -> None:
|
|
648
|
+
self.progress_running = False
|
|
649
|
+
self.progress_started_at = None
|
|
650
|
+
if self.progress_job is not None:
|
|
651
|
+
self.root.after_cancel(self.progress_job)
|
|
652
|
+
self.progress_job = None
|
|
653
|
+
|
|
516
654
|
def _clear_log(self) -> None:
|
|
517
655
|
self.log_text.configure(state="normal")
|
|
518
656
|
self.log_text.delete("1.0", tk.END)
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: FeatureRank
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Autoencoder-based feature ranking and selection workflows
|
|
5
5
|
Author: Sercan
|
|
6
6
|
License: MIT
|
|
7
|
-
Requires-Python:
|
|
7
|
+
Requires-Python: <3.14,>=3.10
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
Requires-Dist: numpy>=2.0
|
|
10
10
|
Requires-Dist: pandas>=2.0
|
|
11
11
|
Requires-Dist: numexpr>=2.10.2
|
|
12
12
|
Requires-Dist: scikit-learn>=1.4
|
|
13
13
|
Requires-Dist: matplotlib>=3.8
|
|
14
|
-
Requires-Dist: tensorflow
|
|
14
|
+
Requires-Dist: tensorflow<2.22,>=2.21; platform_system != "Darwin" or platform_machine != "x86_64"
|
|
15
|
+
Requires-Dist: tensorflow<2.17,>=2.16.2; platform_system == "Darwin" and platform_machine == "x86_64" and python_version < "3.13"
|
|
15
16
|
|
|
16
17
|
# FeatureRank
|
|
17
18
|
|
|
@@ -28,23 +29,45 @@ The project supports two selection modes:
|
|
|
28
29
|
The selected features can be evaluated with classification, regression, or
|
|
29
30
|
unsupervised clustering.
|
|
30
31
|
|
|
31
|
-
##
|
|
32
|
+
## PyPI installation and GUI (end users)
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
FeatureRank supports 64-bit Python 3.10, 3.11, 3.12, and 3.13 on desktop
|
|
35
|
+
Windows, Linux, and macOS systems. Python 3.14 is not supported yet. Intel
|
|
36
|
+
macOS users should use Python 3.12 or earlier because newer TensorFlow releases
|
|
37
|
+
do not provide an Intel macOS wheel. Check the Python version first:
|
|
34
38
|
|
|
35
39
|
```bash
|
|
36
|
-
|
|
40
|
+
python3 --version
|
|
37
41
|
```
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
is `FeatureRank`. Verify the installation from Python (not directly in zsh):
|
|
43
|
+
Install the published package with:
|
|
41
44
|
|
|
42
45
|
```bash
|
|
43
|
-
|
|
46
|
+
pip install FeatureRank
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+

|
|
50
|
+
|
|
51
|
+
Open Python:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
python3
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+

|
|
58
|
+
|
|
59
|
+
Then import FeatureRank at the Python prompt:
|
|
60
|
+
|
|
61
|
+
```python
|
|
44
62
|
>>> import FeatureRank
|
|
45
|
-
>>> print(FeatureRank.__file__)
|
|
46
63
|
```
|
|
47
64
|
|
|
65
|
+

|
|
66
|
+
|
|
67
|
+
The import opens the desktop GUI automatically. Do not type `import
|
|
68
|
+
FeatureRank` directly in zsh; it is Python code and must be entered after
|
|
69
|
+
starting `python3`.
|
|
70
|
+
|
|
48
71
|
For local development, run these commands from the repository root instead:
|
|
49
72
|
|
|
50
73
|
```bash
|
|
@@ -58,10 +81,10 @@ GPU-specific dependencies are listed in
|
|
|
58
81
|
[`requirements-gpu.txt`](requirements-gpu.txt). Hardware notes are available in
|
|
59
82
|
[`GPU_SETUP.md`](GPU_SETUP.md).
|
|
60
83
|
|
|
61
|
-
The repository metadata is version `0.1.
|
|
84
|
+
The repository metadata is version `0.1.4`. Because PyPI releases cannot be
|
|
62
85
|
replaced in place, publish this version (or a newer one) to the existing
|
|
63
86
|
`FeatureRank` project when you release the refactor. If an environment still
|
|
64
|
-
shows
|
|
87
|
+
shows an older FeatureRank version, upgrade it with the command above or use the local
|
|
65
88
|
editable installation while developing.
|
|
66
89
|
|
|
67
90
|
After installation, the package GUI can be opened with:
|
|
@@ -96,12 +119,17 @@ in that interpreter's environment.
|
|
|
96
119
|
If you do not want to install the package, use the equivalent script directly:
|
|
97
120
|
|
|
98
121
|
```bash
|
|
99
|
-
|
|
122
|
+
python3 scripts/FeatureRank.py --help
|
|
100
123
|
```
|
|
101
124
|
|
|
102
|
-
##
|
|
125
|
+
## Optional Python API (developers)
|
|
126
|
+
|
|
127
|
+
The main entry point for regular users is the GUI shown above. The following
|
|
128
|
+
Python API is optional and is useful when another Python program needs to start
|
|
129
|
+
an experiment. It is not required for the normal `pip install` → `import
|
|
130
|
+
FeatureRank` workflow.
|
|
103
131
|
|
|
104
|
-
The main entry point is:
|
|
132
|
+
The main command-line entry point is:
|
|
105
133
|
|
|
106
134
|
```text
|
|
107
135
|
scripts/FeatureRank.py
|
|
@@ -136,28 +164,45 @@ The GUI is the primary end-user interface. Select a dataset from the dropdown
|
|
|
136
164
|
or choose a CSV/TXT file with **Browse…**, select a feature percentage, choose
|
|
137
165
|
`GL` or `DC`, and press **START**. `Block Count` is enabled only for `DC`.
|
|
138
166
|
|
|
167
|
+
When the GUI opens, the experiment form is ready for these choices:
|
|
168
|
+
|
|
169
|
+

|
|
170
|
+
|
|
171
|
+
If the required dataset is not listed, press **Browse…** and select its data
|
|
172
|
+
file. For paired datasets, keep the matching label file in the same directory:
|
|
173
|
+
|
|
174
|
+

|
|
175
|
+
|
|
139
176
|
While the model runs, the progress bar and log show stages such as loading,
|
|
140
177
|
feature ranking, block processing, combining, training, and result creation.
|
|
141
178
|
When the run finishes, the summary lists the selected feature count, metric,
|
|
142
179
|
execution time, and output directory. **Open Results Folder** opens that
|
|
143
180
|
directory in Finder, Explorer, or the Linux file manager.
|
|
144
181
|
|
|
182
|
+

|
|
183
|
+
|
|
145
184
|
The selected file can be a normal CSV containing a `target` column, or one of
|
|
146
185
|
the project's paired files (`*_data.csv` and `*_label.csv`).
|
|
147
186
|
|
|
148
|
-
##
|
|
187
|
+
## Local repository command-line mode (developers)
|
|
188
|
+
|
|
189
|
+
The GUI is the normal user interface. The commands in this section are optional
|
|
190
|
+
and are intended for developers, batch experiments, and reproducible research.
|
|
191
|
+
They bypass the GUI and run the existing workflow directly in the terminal.
|
|
192
|
+
They are available from a repository checkout; a normal PyPI user does not need
|
|
193
|
+
them.
|
|
149
194
|
|
|
150
|
-
|
|
151
|
-
|
|
195
|
+
Run these shell commands directly in Terminal. Do not first open the Python
|
|
196
|
+
prompt and do not type the `>>>` marker. To inspect the CLI options:
|
|
152
197
|
|
|
153
198
|
```bash
|
|
154
|
-
|
|
199
|
+
python3 scripts/FeatureRank.py --help
|
|
155
200
|
```
|
|
156
201
|
|
|
157
202
|
A normal classification run is:
|
|
158
203
|
|
|
159
204
|
```bash
|
|
160
|
-
|
|
205
|
+
python3 scripts/FeatureRank.py \
|
|
161
206
|
--dataset-name breast_cancer_data.csv \
|
|
162
207
|
--task classification \
|
|
163
208
|
--feature-percent 20 \
|
|
@@ -167,7 +212,7 @@ python scripts/FeatureRank.py \
|
|
|
167
212
|
The command can also be run through the compatibility wrapper:
|
|
168
213
|
|
|
169
214
|
```bash
|
|
170
|
-
|
|
215
|
+
python3 scripts/RunAutoencoder.py \
|
|
171
216
|
--dataset-name breast_cancer_data.csv \
|
|
172
217
|
--feature-percent 20
|
|
173
218
|
```
|
|
@@ -189,7 +234,7 @@ workflow is:
|
|
|
189
234
|
Run GLOBAL explicitly with:
|
|
190
235
|
|
|
191
236
|
```bash
|
|
192
|
-
|
|
237
|
+
python3 scripts/FeatureRank.py \
|
|
193
238
|
--dataset-name carcinom_data.csv \
|
|
194
239
|
--task classification \
|
|
195
240
|
--feature-percent 40 \
|
|
@@ -218,7 +263,7 @@ The steps are:
|
|
|
218
263
|
Run DC with ten blocks:
|
|
219
264
|
|
|
220
265
|
```bash
|
|
221
|
-
|
|
266
|
+
python3 scripts/FeatureRank.py \
|
|
222
267
|
--dataset-name arcene_data.csv \
|
|
223
268
|
--task classification \
|
|
224
269
|
--feature-percent 50 \
|
|
@@ -284,19 +329,19 @@ Examples:
|
|
|
284
329
|
|
|
285
330
|
```bash
|
|
286
331
|
# Classification
|
|
287
|
-
|
|
332
|
+
python3 scripts/FeatureRank.py \
|
|
288
333
|
--dataset-name breast_cancer_data.csv \
|
|
289
334
|
--task classification \
|
|
290
335
|
--feature-percent 20
|
|
291
336
|
|
|
292
337
|
# Regression
|
|
293
|
-
|
|
338
|
+
python3 scripts/FeatureRank.py \
|
|
294
339
|
--dataset-name air_data.csv \
|
|
295
340
|
--task regression \
|
|
296
341
|
--feature-percent 30
|
|
297
342
|
|
|
298
343
|
# Clustering
|
|
299
|
-
|
|
344
|
+
python3 scripts/FeatureRank.py \
|
|
300
345
|
--dataset-name codon_usage_data.csv \
|
|
301
346
|
--task clustering \
|
|
302
347
|
--feature-percent 60
|
|
@@ -322,7 +367,7 @@ The main user-facing parameters are:
|
|
|
322
367
|
For the complete, current list:
|
|
323
368
|
|
|
324
369
|
```bash
|
|
325
|
-
|
|
370
|
+
python3 scripts/FeatureRank.py --help
|
|
326
371
|
```
|
|
327
372
|
|
|
328
373
|
Model architecture, epochs, batch size, learning rate, early stopping, and
|
|
@@ -423,9 +468,9 @@ in [`requirements-gpu.txt`](requirements-gpu.txt).
|
|
|
423
468
|
Run the checks from the repository root:
|
|
424
469
|
|
|
425
470
|
```bash
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
471
|
+
python3 -m pytest -q
|
|
472
|
+
python3 -m pyflakes src scripts tests
|
|
473
|
+
python3 -m compileall -q src scripts tests
|
|
429
474
|
```
|
|
430
475
|
|
|
431
476
|
## Citation
|