sampling-mining-workflows-dsl 0.0.1__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.
Files changed (79) hide show
  1. sampling_mining_workflows_dsl/ __init__.py +0 -0
  2. sampling_mining_workflows_dsl/CompleteWorkflow.py +23 -0
  3. sampling_mining_workflows_dsl/Workflow.py +242 -0
  4. sampling_mining_workflows_dsl/WorkflowBuilder.py +11 -0
  5. sampling_mining_workflows_dsl/analysis/ChiSquareAnalysis.py +37 -0
  6. sampling_mining_workflows_dsl/analysis/CochranTest.py +44 -0
  7. sampling_mining_workflows_dsl/analysis/CochranWorkflowAnalysis.py +37 -0
  8. sampling_mining_workflows_dsl/analysis/CoverageTest.py +60 -0
  9. sampling_mining_workflows_dsl/analysis/DistributionWorkflowAnalysis.py +57 -0
  10. sampling_mining_workflows_dsl/analysis/HistAnalysis.py +201 -0
  11. sampling_mining_workflows_dsl/analysis/HistWorkflowAnalysis.py +72 -0
  12. sampling_mining_workflows_dsl/analysis/KSWorkflowAnalysis.py +217 -0
  13. sampling_mining_workflows_dsl/analysis/WorkflowAnalysis.py +7 -0
  14. sampling_mining_workflows_dsl/analysis/YamaneTest.py +16 -0
  15. sampling_mining_workflows_dsl/analysis/YamaneWorkflowAnalysis.py +27 -0
  16. sampling_mining_workflows_dsl/analysis/kolmogorov_smirnov.py +31 -0
  17. sampling_mining_workflows_dsl/constraint/BoolComparator.py +23 -0
  18. sampling_mining_workflows_dsl/constraint/BoolConstraint.py +50 -0
  19. sampling_mining_workflows_dsl/constraint/BoolConstraintString.py +66 -0
  20. sampling_mining_workflows_dsl/constraint/Comparator.py +16 -0
  21. sampling_mining_workflows_dsl/constraint/Constraint.py +23 -0
  22. sampling_mining_workflows_dsl/constraint/NaturalComparator.py +17 -0
  23. sampling_mining_workflows_dsl/element/Element.py +45 -0
  24. sampling_mining_workflows_dsl/element/Loader.py +17 -0
  25. sampling_mining_workflows_dsl/element/Repository.py +28 -0
  26. sampling_mining_workflows_dsl/element/Set.py +210 -0
  27. sampling_mining_workflows_dsl/element/Writer.py +9 -0
  28. sampling_mining_workflows_dsl/element/loader/CsvLoader.py +67 -0
  29. sampling_mining_workflows_dsl/element/loader/JsonLoader.py +84 -0
  30. sampling_mining_workflows_dsl/element/loader/LoaderFactory.py +14 -0
  31. sampling_mining_workflows_dsl/element/writer/CsvWriter.py +46 -0
  32. sampling_mining_workflows_dsl/element/writer/JsonWriter.py +58 -0
  33. sampling_mining_workflows_dsl/element/writer/WriterFactory.py +7 -0
  34. sampling_mining_workflows_dsl/exec_visualizer/WorkflowVisualizer.py +189 -0
  35. sampling_mining_workflows_dsl/github_seart/loader.py +17 -0
  36. sampling_mining_workflows_dsl/github_seart/metadata.py +106 -0
  37. sampling_mining_workflows_dsl/metadata/Metadata.py +117 -0
  38. sampling_mining_workflows_dsl/metadata/MetadataBoolean.py +14 -0
  39. sampling_mining_workflows_dsl/metadata/MetadataDate.py +75 -0
  40. sampling_mining_workflows_dsl/metadata/MetadataDict.py +33 -0
  41. sampling_mining_workflows_dsl/metadata/MetadataList.py +32 -0
  42. sampling_mining_workflows_dsl/metadata/MetadataNumber.py +29 -0
  43. sampling_mining_workflows_dsl/metadata/MetadataString.py +13 -0
  44. sampling_mining_workflows_dsl/metadata/MetadataValue.py +24 -0
  45. sampling_mining_workflows_dsl/operator/Operator.py +165 -0
  46. sampling_mining_workflows_dsl/operator/OperatorBuilder.py +191 -0
  47. sampling_mining_workflows_dsl/operator/OperatorFactory.py +76 -0
  48. sampling_mining_workflows_dsl/operator/clustering/GroupingOperator.py +36 -0
  49. sampling_mining_workflows_dsl/operator/clustering/SubWorkflowOperatorBuilder.py +76 -0
  50. sampling_mining_workflows_dsl/operator/selection/SelectionOperator.py +5 -0
  51. sampling_mining_workflows_dsl/operator/selection/filter/FilterOperator.py +30 -0
  52. sampling_mining_workflows_dsl/operator/selection/sampling/SamplingOperator.py +11 -0
  53. sampling_mining_workflows_dsl/operator/selection/sampling/automatic/AutomaticSamplingOperator.py +9 -0
  54. sampling_mining_workflows_dsl/operator/selection/sampling/automatic/RandomSelectionOperator.py +24 -0
  55. sampling_mining_workflows_dsl/operator/selection/sampling/automatic/RandomSelectionPartitionOperator.py +62 -0
  56. sampling_mining_workflows_dsl/operator/selection/sampling/automatic/SystematicRandomSelectionOperator.py +14 -0
  57. sampling_mining_workflows_dsl/operator/selection/sampling/automatic/SystematicSelectionOperator.py +31 -0
  58. sampling_mining_workflows_dsl/operator/selection/sampling/manual/InteractiveManualSamplingOperator.py +40 -0
  59. sampling_mining_workflows_dsl/operator/selection/sampling/manual/ManualSamplingOperator.py +28 -0
  60. sampling_mining_workflows_dsl/operator/set_algebra/ExternalSetOperator.py +33 -0
  61. sampling_mining_workflows_dsl/operator/set_algebra/InternalSetOperator.py +47 -0
  62. sampling_mining_workflows_dsl/operator/set_algebra/SetOperator.py +30 -0
  63. sampling_mining_workflows_dsl/operator/set_algebra/external_set_operator/DifferenceOperator.py +17 -0
  64. sampling_mining_workflows_dsl/operator/set_algebra/external_set_operator/IntersectionOperator.py +19 -0
  65. sampling_mining_workflows_dsl/operator/set_algebra/external_set_operator/UnionOperator.py +17 -0
  66. sampling_mining_workflows_dsl/operator/set_algebra/internal_set_operator/DifferenceOperator.py +17 -0
  67. sampling_mining_workflows_dsl/operator/set_algebra/internal_set_operator/IntersectionOperator.py +18 -0
  68. sampling_mining_workflows_dsl/operator/set_algebra/internal_set_operator/UnionOperator.py +17 -0
  69. sampling_mining_workflows_dsl/operator/set_algebra/set_operator/DifferenceOperator.py +15 -0
  70. sampling_mining_workflows_dsl/operator/set_algebra/set_operator/IntersectionOperator.py +18 -0
  71. sampling_mining_workflows_dsl/operator/set_algebra/set_operator/UnionOperator.py +17 -0
  72. sampling_mining_workflows_dsl/test/ __init__.py +0 -0
  73. sampling_mining_workflows_dsl/test/Workflow_simple.py +38 -0
  74. sampling_mining_workflows_dsl/test/input.json +401 -0
  75. sampling_mining_workflows_dsl/toolbox.py +42 -0
  76. sampling_mining_workflows_dsl-0.0.1.dist-info/METADATA +236 -0
  77. sampling_mining_workflows_dsl-0.0.1.dist-info/RECORD +79 -0
  78. sampling_mining_workflows_dsl-0.0.1.dist-info/WHEEL +4 -0
  79. sampling_mining_workflows_dsl-0.0.1.dist-info/licenses/LICENSE.txt +674 -0
@@ -0,0 +1,201 @@
1
+ import os
2
+ from collections import Counter
3
+ from typing import TYPE_CHECKING
4
+
5
+ import matplotlib.pyplot as plt
6
+ import matplotlib.ticker as ticker
7
+ import pandas as pd
8
+
9
+ from sampling_mining_workflows_dsl.element.Set import Set
10
+ from sampling_mining_workflows_dsl.metadata.Metadata import Metadata
11
+
12
+ if TYPE_CHECKING:
13
+ from sampling_mining_workflows_dsl.metadata import MetadataValue
14
+
15
+
16
+ class HistAnalysis:
17
+ def __init__(
18
+ self,
19
+ save_path: str,
20
+ metadata: Metadata,
21
+ top_x: int = -1,
22
+ category: bool = True,
23
+ sort: bool = False,
24
+ show: bool = False,
25
+ log_y: bool = False,
26
+ fixed_bins: int = None,
27
+ max_x_bound: float = None,
28
+ x_label: str = None,
29
+ fig_size=(10,6),
30
+ ):
31
+ self.metadata = metadata
32
+ # Wether data should be treated as categorical data or continous
33
+ self.category = category
34
+ self.top_x = top_x
35
+ self.sort = sort
36
+ self.save_path = save_path
37
+ os.makedirs(self.save_path, exist_ok=True)
38
+ self.show = show
39
+ self.log_y = log_y
40
+ self.fixed_bins = fixed_bins
41
+ self.max_x_bound = max_x_bound
42
+ self.x_label = x_label
43
+ self.fig_size = fig_size
44
+
45
+ def analyze(self, s: Set, file_name: str, op_info: str):
46
+ # From Set to List of Metadata values
47
+ try:
48
+ metadata_values = []
49
+ for element in s.get_elements():
50
+ if not isinstance(element, Set):
51
+ metadata_value: MetadataValue = element.get_metadata_value(
52
+ self.metadata
53
+ )
54
+ if self.metadata.type is list:
55
+ metadata_values.extend(metadata_value.get_value())
56
+ else:
57
+ metadata_values.append(metadata_value.get_value())
58
+
59
+ if self.top_x > 0:
60
+ # Count all and find top_x
61
+ counter = Counter(metadata_values)
62
+ most_common = dict(counter.most_common(self.top_x))
63
+ top_values = set(most_common.keys())
64
+
65
+ # Replace non-top values with 'Other'
66
+ metadata_values = [
67
+ val if val in top_values else "Other" for val in metadata_values
68
+ ]
69
+
70
+ fig, ax = self.create_histogram(metadata_values, op_info)
71
+ if self.show:
72
+ self.show_histogram()
73
+ self.save_histogram(fig, file_name)
74
+ except Exception as e:
75
+ print(f"Error analyzing {self.metadata.name}: {e}")
76
+ return
77
+
78
+ def create_histogram(self, data: list, op_info: str):
79
+ df = pd.DataFrame(data, columns=["value"])
80
+ series = df["value"]
81
+
82
+ # Apply max x bound filter if specified
83
+ if self.max_x_bound is not None and not self.category:
84
+ # Only filter for continuous data
85
+ filtered_data = [x for x in data if x <= self.max_x_bound]
86
+ df = pd.DataFrame(filtered_data, columns=["value"])
87
+ series = df["value"]
88
+ data = filtered_data
89
+
90
+
91
+ # Set style and create figure with better styling
92
+ plt.style.use('default') # Reset to default style
93
+ fig, ax = plt.subplots(figsize=self.fig_size)
94
+ fig.patch.set_facecolor('white')
95
+
96
+ if not self.category:
97
+ unique_values = series.nunique()
98
+ # Determine number of bins
99
+ if self.fixed_bins is not None:
100
+ bins = self.fixed_bins
101
+ else:
102
+ bins = min(10, unique_values)
103
+
104
+ # Create histogram with better styling
105
+ n, bins_edges, patches = ax.hist(
106
+ x=data,
107
+ bins=bins,
108
+ color="#000000", # Dark blue color
109
+ alpha=0.85,
110
+ edgecolor="#C9CCD3", # Very dark blue border
111
+ linewidth=1.2
112
+ )
113
+
114
+ if self.log_y:
115
+ ax.set_yscale('log')
116
+
117
+ # Set x-axis limit if max_x_bound is specified
118
+ if self.max_x_bound is not None:
119
+ ax.set_xlim(right=self.max_x_bound)
120
+ else:
121
+ if self.sort:
122
+ value_counts = series.value_counts(ascending=False, sort=True)
123
+ else:
124
+ value_counts = series.value_counts().sort_index(ascending=True)
125
+
126
+ # Create bar chart with better styling
127
+ value_counts.plot(
128
+ kind="bar",
129
+ ax=ax,
130
+ color="#000000", # Dark blue color
131
+ alpha=0.85,
132
+ edgecolor='#C9CCD3', # Very dark blue border
133
+ linewidth=1.2
134
+ )
135
+
136
+
137
+ # Set custom x-label or default
138
+ x_axis_label = self.x_label if self.x_label else "Category"
139
+ ax.set_xlabel(x_axis_label, fontsize=16, fontweight='bold')
140
+
141
+ # Rotate x-axis labels for better readability
142
+ plt.xticks(rotation=45, ha='right', fontsize=14)
143
+
144
+ # Enhanced styling
145
+ ax.set_title(op_info, fontsize=18, fontweight='bold', pad=20)
146
+
147
+ # Y-axis label with log scale indication
148
+ y_label = "Frequency"
149
+ if self.log_y:
150
+ y_label += " (Log Scale)"
151
+ ax.set_ylabel(y_label, fontsize=16, fontweight='bold')
152
+
153
+ # Set custom x-label for continuous data if provided
154
+ if not self.category and self.x_label:
155
+ ax.set_xlabel(self.x_label, fontsize=16, fontweight='bold')
156
+
157
+ # Grid styling
158
+ ax.grid(True, alpha=0.3, linestyle='-', linewidth=0.5)
159
+ ax.set_axisbelow(True)
160
+
161
+ # Spine styling
162
+ for spine in ax.spines.values():
163
+ spine.set_color('#333333')
164
+ spine.set_linewidth(1)
165
+
166
+ # Make axes more visible with intermediate ticks
167
+ ax.tick_params(axis='both', which='major', labelsize=14, colors='#333333')
168
+ ax.tick_params(axis='both', which='minor', labelsize=12, colors='#666666')
169
+
170
+ # Add minor ticks for Y axis for better readability
171
+ ax.yaxis.set_minor_locator(ticker.AutoMinorLocator())
172
+
173
+ # If log scale, use log minor locator for Y axis
174
+ if self.log_y:
175
+ ax.yaxis.set_minor_locator(ticker.LogLocator(base=10.0, subs='auto', numticks=4))
176
+
177
+ # Format axis numbers with separators
178
+ # Format x-axis with space separators for thousands
179
+ if not self.category:
180
+
181
+ ax.xaxis.set_major_formatter(ticker.FuncFormatter(lambda x, p: f"{x:,.0f}"))
182
+ # Format y-axis with space separators for thousands
183
+ ax.yaxis.set_major_formatter(ticker.FuncFormatter(lambda x, p: f"{x:,.0f}"))
184
+
185
+ plt.tight_layout()
186
+ return fig, ax
187
+
188
+ def save_histogram(self, fig, file_name: str):
189
+
190
+ if file_name:
191
+ # Create folder if needed
192
+ os.makedirs(self.save_path, exist_ok=True)
193
+ file_path = os.path.join(self.save_path, file_name)
194
+ fig.savefig(file_path)
195
+ else:
196
+ print("No save path provided, displaying histogram instead.")
197
+ self.show_histogram()
198
+ plt.close(fig)
199
+
200
+ def show_histogram(self):
201
+ plt.show()
@@ -0,0 +1,72 @@
1
+ from typing import TypeVar
2
+
3
+ from sampling_mining_workflows_dsl.analysis.HistAnalysis import HistAnalysis
4
+ from sampling_mining_workflows_dsl.analysis.WorkflowAnalysis import WorkflowAnalysis
5
+ from sampling_mining_workflows_dsl.metadata.Metadata import Metadata
6
+ from sampling_mining_workflows_dsl.operator.clustering.GroupingOperator import GroupingOperator
7
+
8
+ T = TypeVar("T")
9
+
10
+
11
+ class HistWorkflowAnalysis(WorkflowAnalysis):
12
+ def __init__(
13
+ self,
14
+ metadata: Metadata[T],
15
+ top_x: int = -1,
16
+ category: bool = True,
17
+ sort: bool = False,
18
+ output_path: str = "hist_analysis/",
19
+ log_y: bool = False,
20
+ fixed_bins: int = None,
21
+ max_x_bound: float = None,
22
+ x_label: str = None,
23
+ fig_size=(10,6)
24
+ ):
25
+ super().__init__()
26
+ self.category = category
27
+ self.sort = sort
28
+ self.metadata = metadata
29
+ self.file_path = output_path
30
+ self.top_x = top_x
31
+ self.log_y = log_y
32
+ self.fixed_bins = fixed_bins
33
+ self.max_x_bound = max_x_bound
34
+ self.x_label = x_label
35
+ self.fig_size = fig_size
36
+
37
+ def analyze(
38
+ self, workflow, workflow_name: str = "main workflow", op_number: int = 1
39
+ ):
40
+ op = workflow.get_root()
41
+ analysis = HistAnalysis(
42
+ self.file_path, self.metadata, self.top_x, self.category, self.sort,
43
+ show=False, log_y=self.log_y, fixed_bins=self.fixed_bins,
44
+ max_x_bound=self.max_x_bound, x_label=self.x_label,fig_size=self.fig_size
45
+ )
46
+ analysis.analyze(
47
+ op.get_input(),
48
+ f"{self.metadata.name}_{workflow_name}_op{op_number}_input.svg",
49
+ f"",
50
+ )
51
+
52
+ while op is not None:
53
+ if isinstance(op, GroupingOperator):
54
+ analysis.analyze(
55
+ op.get_merged_output(),
56
+ f"{self.metadata.name}_{workflow_name}_op{op_number}_output.svg",
57
+ f"",
58
+ )
59
+ for i, internal_w in enumerate(op.get_workflows(), start=1):
60
+ # Recursively analyze subworkflows
61
+ subworkflow_name = f"subworkflow {i}"
62
+ self.analyze(
63
+ internal_w, subworkflow_name, 1
64
+ ) # Reset operator numbering for subworkflows
65
+ else:
66
+ analysis.analyze(
67
+ op.get_output(),
68
+ f"{self.metadata.name}_{workflow_name}_op{op_number}_output.svg",
69
+ f"",
70
+ )
71
+ op = op.get_next_operator()
72
+ op_number += 1
@@ -0,0 +1,217 @@
1
+ from typing import TypeVar
2
+
3
+
4
+ from sampling_mining_workflows_dsl.Workflow import Workflow
5
+ from sampling_mining_workflows_dsl.analysis.HistAnalysis import HistAnalysis
6
+ from sampling_mining_workflows_dsl.analysis.WorkflowAnalysis import WorkflowAnalysis
7
+ from sampling_mining_workflows_dsl.analysis.kolmogorov_smirnov import kolmogorov_smirnov
8
+ from sampling_mining_workflows_dsl.metadata.Metadata import Metadata
9
+ from sampling_mining_workflows_dsl.operator.clustering.GroupingOperator import (
10
+ GroupingOperator,
11
+ )
12
+
13
+ T = TypeVar("T")
14
+
15
+
16
+ class KSWorkflowAnalysis(WorkflowAnalysis):
17
+ def __init__(
18
+ self,
19
+ metadata: Metadata[T],
20
+ output_path: str = "analysis",
21
+ ):
22
+ super().__init__()
23
+
24
+ self.metadata = metadata
25
+ self.file_path = output_path+"/ks_analysis.txt"
26
+
27
+ def analyze(
28
+ self, workflow
29
+ ):
30
+ import os
31
+ from datetime import datetime
32
+
33
+ # Create all directory if it doesn't exist
34
+ os.makedirs(os.path.dirname(self.file_path), exist_ok=True)
35
+ print(f"KS Analysis results will be saved in {self.file_path}")
36
+
37
+ # Store pairs that pass the test
38
+ passing_pairs = []
39
+ failing_pairs = []
40
+
41
+ # Open file for writing
42
+ with open(self.file_path, 'w', encoding='utf-8') as f:
43
+ # Write header
44
+ f.write(f"Kolmogorov-Smirnov Analysis Results\n")
45
+ f.write(f"{'='*50}\n")
46
+ f.write(f"Metadata: {self.metadata.name}\n")
47
+ f.write(f"{'='*50}\n\n")
48
+
49
+ #compute for all pair
50
+ tuples = self.get_all_set_from_workflow(workflow)
51
+
52
+ if len(tuples) < 2:
53
+ f.write("Warning: Less than 2 sets found. Cannot perform pairwise analysis.\n")
54
+ print("Warning: Less than 2 sets found. Cannot perform pairwise analysis.")
55
+ return
56
+
57
+ comparison_count = 0
58
+ for i in range(0, len(tuples)):
59
+ for j in range(i, len(tuples)):
60
+ set_1, op_1 = tuples[i]
61
+ set_2, op_2 = tuples[j]
62
+
63
+ comparison_count += 1
64
+
65
+ # Write comparison header
66
+ op_1_name = op_1.__class__.__name__ if op_1 else 'Initial set'
67
+ op_1_name = f"Set #{i}"+op_1_name
68
+ op_2_name = op_2.__class__.__name__ if op_2 else 'Initial Input'
69
+ op_2_name = f"Set #{j}"+op_2_name
70
+ f.write(f"Comparison #{comparison_count}\n")
71
+ f.write(f"Set 1: {op_1_name} (size: {set_1.size()})\n")
72
+ f.write(f"Set 2: {op_2_name} (size: {set_2.size()})\n")
73
+ f.write(f"{'-'*30}\n")
74
+
75
+ # Perform KS analysis
76
+ print(f"Analyzing sets {i} from {op_1_name} and {j} from {op_2_name}")
77
+
78
+ try:
79
+ ks_result = kolmogorov_smirnov(self.metadata).analyze(set_1, set_2)
80
+
81
+ # Extract results from KstestResult object
82
+ statistic = ks_result.statistic if hasattr(ks_result, 'statistic') else 'N/A'
83
+ p_value = ks_result.pvalue if hasattr(ks_result, 'pvalue') else 'N/A'
84
+
85
+ # Create interpretation
86
+ if p_value != 'N/A':
87
+ if p_value > 0.05:
88
+ interpretation = "No significant difference (p > 0.05)"
89
+ else:
90
+ interpretation = "Significant difference detected (p ≤ 0.05)"
91
+ else:
92
+ interpretation = "Unable to determine"
93
+
94
+ # Check if test passes (typically p_value > 0.05 means no significant difference)
95
+ test_passed = p_value != 'N/A' and float(p_value) > 0.05
96
+
97
+ pair_info = {
98
+ 'comparison': comparison_count,
99
+ 'set_1': op_1_name,
100
+ 'set_2': op_2_name,
101
+ 'statistic': statistic,
102
+ 'p_value': p_value,
103
+ 'interpretation': interpretation
104
+ }
105
+
106
+ if test_passed:
107
+ passing_pairs.append(pair_info)
108
+ f.write(f"✓ TEST PASSED - ")
109
+ else:
110
+ failing_pairs.append(pair_info)
111
+ f.write(f"✗ TEST FAILED - ")
112
+
113
+ # Write results to file
114
+ f.write(f"KS Statistic: {statistic}\n")
115
+ f.write(f"P-value: {p_value}\n")
116
+ f.write(f"Interpretation: {interpretation}\n")
117
+
118
+ # Print to console as well
119
+ status = "PASSED" if test_passed else "FAILED"
120
+ print(f" [{status}] KS Statistic: {statistic}, P-value: {p_value}")
121
+
122
+ except Exception as e:
123
+ error_msg = f"Error during analysis: {str(e)}"
124
+ f.write(f"{error_msg}\n")
125
+ print(f" {error_msg}")
126
+ failing_pairs.append({
127
+ 'comparison': comparison_count,
128
+ 'set_1': op_1_name,
129
+ 'set_2': op_2_name,
130
+ 'error': str(e)
131
+ })
132
+
133
+ f.write(f"\n")
134
+
135
+ # Write summary section
136
+ f.write(f"\n{'='*50}\n")
137
+ f.write(f"SUMMARY\n")
138
+ f.write(f"{'='*50}\n")
139
+ f.write(f"Total comparisons: {comparison_count}\n")
140
+ f.write(f"Tests passed: {len(passing_pairs)}\n")
141
+ f.write(f"Tests failed: {len(failing_pairs)}\n")
142
+ f.write(f"\nPassing pairs (no significant difference):\n")
143
+ f.write(f"{'-'*30}\n")
144
+
145
+ if passing_pairs:
146
+ for pair in passing_pairs:
147
+ f.write(f"• {pair['set_1']} vs {pair['set_2']} (p-value: {pair['p_value']})\n")
148
+ else:
149
+ f.write("None\n")
150
+
151
+ f.write(f"\nFailing pairs (significant difference detected):\n")
152
+ f.write(f"{'-'*30}\n")
153
+
154
+ if failing_pairs:
155
+ for pair in failing_pairs:
156
+ if 'error' in pair:
157
+ f.write(f"• {pair['set_1']} vs {pair['set_2']} (Error: {pair['error']})\n")
158
+ else:
159
+ f.write(f"• {pair['set_1']} vs {pair['set_2']} (p-value: {pair['p_value']})\n")
160
+ else:
161
+ f.write("None\n")
162
+
163
+ f.write(f"\nAnalysis completed at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
164
+
165
+ # Print summary to console
166
+ print(f"\n{'='*50}")
167
+ print(f"KS ANALYSIS SUMMARY")
168
+ print(f"{'='*50}")
169
+ print(f"Total comparisons: {comparison_count}")
170
+ print(f"Tests passed: {len(passing_pairs)}")
171
+ print(f"Tests failed: {len(failing_pairs)}")
172
+
173
+ if passing_pairs:
174
+ print(f"\nPassing pairs (no significant difference):")
175
+ for pair in passing_pairs:
176
+ print(f" ✓ {pair['set_1']} vs {pair['set_2']} (p-value: {pair['p_value']})")
177
+
178
+ if failing_pairs:
179
+ print(f"\nFailing pairs (significant difference detected):")
180
+ for pair in failing_pairs:
181
+ if 'error' in pair:
182
+ print(f" ✗ {pair['set_1']} vs {pair['set_2']} (Error)")
183
+ else:
184
+ print(f" ✗ {pair['set_1']} vs {pair['set_2']} (p-value: {pair['p_value']})")
185
+
186
+ print(f"\nResults saved to: {self.file_path}")
187
+
188
+ return {
189
+ 'passing_pairs': passing_pairs,
190
+ 'failing_pairs': failing_pairs,
191
+ 'total_comparisons': comparison_count
192
+ }
193
+
194
+
195
+
196
+
197
+
198
+ def get_all_set_from_workflow(self, workflow: Workflow, index=0):
199
+ sets = {}
200
+ if index==0:
201
+ sets[index] = (workflow._input,None)
202
+ index += 1
203
+
204
+ op = workflow.get_root()
205
+ while op is not None:
206
+ if not isinstance(op, GroupingOperator):
207
+ if hasattr(op, "_output") and op._output is not None:
208
+ sets[index] = (op._output,op)
209
+ index += 1
210
+ else:
211
+ for internal_w in op.get_workflows():
212
+ grouping_sets = self.get_all_set_from_workflow(internal_w, index)
213
+ index = index + len(grouping_sets)
214
+ sets.update(grouping_sets)
215
+ op = op.get_next_operator()
216
+
217
+ return sets
@@ -0,0 +1,7 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class WorkflowAnalysis(ABC):
5
+ @abstractmethod
6
+ def analyze(self, workflow):
7
+ pass
@@ -0,0 +1,16 @@
1
+ import math
2
+
3
+
4
+ class YamaneTest:
5
+ def __init__(self, population_size: int, margin_of_error: float):
6
+ self.population_size = population_size
7
+ self.margin_of_error = margin_of_error
8
+
9
+ def calculate_required_sample_size(self) -> int:
10
+ return math.ceil(
11
+ self.population_size / (1 + self.population_size * self.margin_of_error**2)
12
+ )
13
+
14
+ def is_representative(self, given_sample_size: int) -> bool:
15
+ required_sample_size = self.calculate_required_sample_size()
16
+ return given_sample_size >= required_sample_size
@@ -0,0 +1,27 @@
1
+ from sampling_mining_workflows_dsl.analysis.WorkflowAnalysis import WorkflowAnalysis
2
+ from sampling_mining_workflows_dsl.analysis.YamaneTest import YamaneTest
3
+
4
+
5
+ class YamaneWorkflowAnalysis(WorkflowAnalysis):
6
+ def __init__(self, margin_of_error: float = 0.05):
7
+ super().__init__()
8
+ self.margin_of_error = margin_of_error
9
+
10
+ def analyze(self, workflow):
11
+ pop_size = workflow.get_workflow_input().size()
12
+ yamane_test = YamaneTest(pop_size, self.margin_of_error)
13
+
14
+ required_sample_size = yamane_test.calculate_required_sample_size()
15
+ actual_sample_size = workflow.get_workflow_output().size()
16
+
17
+ print("Yamane's Test Analysis:")
18
+ print("Population Size (input size):", pop_size)
19
+ print("Required Sample Size:", required_sample_size)
20
+ print("Actual Sample Size:", actual_sample_size)
21
+
22
+ if yamane_test.is_representative(actual_sample_size):
23
+ print("The sample is representative based on Yamane's test.")
24
+ else:
25
+ print("The sample is not representative based on Yamane's test.")
26
+
27
+ print("------------------------------------------")
@@ -0,0 +1,31 @@
1
+
2
+ from scipy.stats import ks_2samp
3
+
4
+ from sampling_mining_workflows_dsl.element.Repository import Repository
5
+ from sampling_mining_workflows_dsl.element.Set import Set
6
+ from sampling_mining_workflows_dsl.metadata.Metadata import Metadata
7
+
8
+
9
+ class kolmogorov_smirnov:
10
+ def __init__(self, metadata: Metadata[int]):
11
+ self.metadata = metadata
12
+
13
+ def analyze(self, a: Set, sample: Set) -> float:
14
+ first_list = self.extract_list(a)
15
+ second_list = self.extract_list(sample)
16
+
17
+ # Perform the Kolmogorov-Smirnov test
18
+ res = ks_2samp(first_list, second_list)
19
+ return res
20
+
21
+ def extract_list(self, s: Set) -> list[int]:
22
+ metadata_values = []
23
+
24
+ for element in s.get_elements():
25
+ if isinstance(element, Repository):
26
+ repo: Repository = element
27
+ metadata_value = repo.get_metadata_value(self.metadata)
28
+ if metadata_value:
29
+ metadata_values.append(metadata_value.get_value())
30
+
31
+ return metadata_values
@@ -0,0 +1,23 @@
1
+ from collections.abc import Callable
2
+ from typing import TypeVar
3
+
4
+ from sampling_mining_workflows_dsl.constraint.Comparator import Comparator
5
+ from sampling_mining_workflows_dsl.element.Element import Element
6
+ from sampling_mining_workflows_dsl.metadata.Metadata import Metadata
7
+
8
+ T = TypeVar("T")
9
+
10
+
11
+ class BoolComparator[T](Comparator[T]):
12
+ def __init__(
13
+ self, targeted_metadata: Metadata[T], comparator: Callable[[T, T], T] = None
14
+ ):
15
+ super().__init__(targeted_metadata)
16
+ self.comparator = comparator
17
+
18
+ def compare(self, a: Element, b: Element) -> Element:
19
+ a_value = a.get_metadata_value(self.targeted_metadata).get_value()
20
+ b_value = b.get_metadata_value(self.targeted_metadata).get_value()
21
+ result = self.comparator(a_value, b_value)
22
+
23
+ return a if result == a_value else b
@@ -0,0 +1,50 @@
1
+ from collections.abc import Callable
2
+ from typing import TypeVar
3
+
4
+ from sampling_mining_workflows_dsl.constraint.Constraint import Constraint
5
+ from sampling_mining_workflows_dsl.element.Element import Element
6
+ from sampling_mining_workflows_dsl.metadata.Metadata import Metadata
7
+
8
+ T = TypeVar("T")
9
+
10
+
11
+ class BoolConstraint(Constraint[T]):
12
+ def __init__(
13
+ self,
14
+ workflow,
15
+ constraint: Callable[[tuple[T, ...]], bool],
16
+ *targeted_metadatas: tuple[Metadata[T], ...],
17
+ ):
18
+ super().__init__(workflow, *targeted_metadatas)
19
+ self.constraint = constraint
20
+ self.or_constraint: Constraint | None = None
21
+ self.and_constraint: Constraint | None = None
22
+
23
+ def is_satisfied(self, element: Element) -> bool:
24
+ if self.or_constraint is not None and self.and_constraint is not None:
25
+ raise RuntimeError("Both 'and' & 'or' constraints are defined")
26
+
27
+ value_objs = [
28
+ element.get_metadata_value(target_metadata).get_value()
29
+ for target_metadata in self.targeted_metadatas
30
+ ]
31
+ # TODO add type check
32
+ # if not isinstance(value_objs, self.targeted_metadatas.type):
33
+ # raise RuntimeError(f"Unexpected metadata type: {type(value_objs)}")
34
+
35
+ constraint_result = self.constraint(*value_objs)
36
+
37
+ if self.or_constraint is not None:
38
+ return constraint_result or self.or_constraint.is_satisfied(element)
39
+ if self.and_constraint is not None:
40
+ return constraint_result and self.and_constraint.is_satisfied(element)
41
+
42
+ return constraint_result
43
+
44
+ def or_(self, other: "BoolConstraint") -> "BoolConstraint":
45
+ self.or_constraint = other
46
+ return other
47
+
48
+ def and_(self, other: "BoolConstraint") -> "BoolConstraint":
49
+ self.and_constraint = other
50
+ return other