tabdat-synth 0.1.0__tar.gz → 0.2.0__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.
Files changed (28) hide show
  1. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/PKG-INFO +3 -3
  2. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/README.md +2 -2
  3. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/pyproject.toml +1 -1
  4. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/src/tabdat_synth/__init__.py +2 -0
  5. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/src/tabdat_synth/core/validation.py +61 -1
  6. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/src/tabdat_synth/domain/contracts.py +11 -0
  7. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/src/tabdat_synth/interfaces/bundles.py +35 -0
  8. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/src/tabdat_synth/interfaces/io.py +35 -0
  9. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/src/tabdat_synth/version.py +1 -1
  10. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/src/tabdat_synth/workflows/pipelines.py +43 -1
  11. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/src/tabdat_synth.egg-info/PKG-INFO +3 -3
  12. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/LICENSE +0 -0
  13. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/setup.cfg +0 -0
  14. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/src/tabdat_synth/core/__init__.py +0 -0
  15. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/src/tabdat_synth/core/evaluation.py +0 -0
  16. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/src/tabdat_synth/core/probability.py +0 -0
  17. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/src/tabdat_synth/core/sampling.py +0 -0
  18. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/src/tabdat_synth/core/statistics.py +0 -0
  19. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/src/tabdat_synth/domain/__init__.py +0 -0
  20. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/src/tabdat_synth/interfaces/__init__.py +0 -0
  21. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/src/tabdat_synth/interfaces/models.py +0 -0
  22. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/src/tabdat_synth/interfaces/reports.py +0 -0
  23. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/src/tabdat_synth/workflows/__init__.py +0 -0
  24. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/src/tabdat_synth/workflows/evaluation.py +0 -0
  25. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/src/tabdat_synth.egg-info/SOURCES.txt +0 -0
  26. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/src/tabdat_synth.egg-info/dependency_links.txt +0 -0
  27. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/src/tabdat_synth.egg-info/requires.txt +0 -0
  28. {tabdat_synth-0.1.0 → tabdat_synth-0.2.0}/src/tabdat_synth.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tabdat-synth
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Spec-driven tabular data synthesis with reusable fitted bundles.
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/SaehwanPark/tabdat-synth
@@ -54,7 +54,7 @@ Real tabular data often carries privacy, governance, licensing, or access constr
54
54
 
55
55
  - Spec-driven generation from YAML configuration.
56
56
  - Directed synthesis workflows with stable topological execution.
57
- - Empirical, truncated-normal, categorical-model, coefficient-sum, sigmoid-probability, and Bernoulli-response steps.
57
+ - Empirical, truncated-normal, categorical-model, bin-map, coefficient-sum, sigmoid-probability, and Bernoulli-response steps.
58
58
  - Built-in categorical backends for LightGBM and logistic regression.
59
59
  - Reusable fitted bundles for generating later without the original source file.
60
60
  - Evaluation reports for marginal quality and row-level disclosure-risk heuristics.
@@ -70,7 +70,7 @@ A synthesis run has five stages:
70
70
  4. Sample synthetic rows from the fitted artifacts.
71
71
  5. Evaluate the synthetic table against source data when appropriate.
72
72
 
73
- The DAG controls which generated columns are available to later steps. For categorical-model steps, declared parents define graph edges, and the fitted model receives the full incoming ancestor context in stable order.
73
+ The DAG controls which generated columns are available to later steps. For categorical-model steps, declared parents define graph edges, and the fitted model receives the full incoming ancestor context in stable order by default, or exactly the columns named in an optional `feature_columns` tuple. Bin-map steps convert a generated numeric column into a categorical column through fixed breaks and labels, with no fitting and no random draw.
74
74
 
75
75
  ## Installation
76
76
 
@@ -24,7 +24,7 @@ Real tabular data often carries privacy, governance, licensing, or access constr
24
24
 
25
25
  - Spec-driven generation from YAML configuration.
26
26
  - Directed synthesis workflows with stable topological execution.
27
- - Empirical, truncated-normal, categorical-model, coefficient-sum, sigmoid-probability, and Bernoulli-response steps.
27
+ - Empirical, truncated-normal, categorical-model, bin-map, coefficient-sum, sigmoid-probability, and Bernoulli-response steps.
28
28
  - Built-in categorical backends for LightGBM and logistic regression.
29
29
  - Reusable fitted bundles for generating later without the original source file.
30
30
  - Evaluation reports for marginal quality and row-level disclosure-risk heuristics.
@@ -40,7 +40,7 @@ A synthesis run has five stages:
40
40
  4. Sample synthetic rows from the fitted artifacts.
41
41
  5. Evaluate the synthetic table against source data when appropriate.
42
42
 
43
- The DAG controls which generated columns are available to later steps. For categorical-model steps, declared parents define graph edges, and the fitted model receives the full incoming ancestor context in stable order.
43
+ The DAG controls which generated columns are available to later steps. For categorical-model steps, declared parents define graph edges, and the fitted model receives the full incoming ancestor context in stable order by default, or exactly the columns named in an optional `feature_columns` tuple. Bin-map steps convert a generated numeric column into a categorical column through fixed breaks and labels, with no fitting and no random draw.
44
44
 
45
45
  ## Installation
46
46
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tabdat-synth"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "Spec-driven tabular data synthesis with reusable fitted bundles."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -3,6 +3,7 @@
3
3
  from tabdat_synth.version import __version__
4
4
  from tabdat_synth.domain.contracts import (
5
5
  BernoulliResponseSpec,
6
+ BinMapSpec,
6
7
  CategoricalModelStepArtifact,
7
8
  ColumnSpec,
8
9
  CoefficientSumSpec,
@@ -40,6 +41,7 @@ from tabdat_synth.workflows.pipelines import (
40
41
  __all__ = [
41
42
  "__version__",
42
43
  "BernoulliResponseSpec",
44
+ "BinMapSpec",
43
45
  "CategoricalModelStepArtifact",
44
46
  "ColumnSpec",
45
47
  "CoefficientSumSpec",
@@ -8,6 +8,7 @@ import polars as pl
8
8
 
9
9
  from tabdat_synth.domain.contracts import (
10
10
  BernoulliResponseSpec,
11
+ BinMapSpec,
11
12
  CoefficientSumSpec,
12
13
  ColumnSpec,
13
14
  DatasetSchema,
@@ -24,6 +25,7 @@ ALLOWED_MISSING_VALUE_POLICIES = {
24
25
  _SOURCE_STEP_KINDS = {"empirical", "truncated_normal", "categorical_model"}
25
26
  _FEATURE_STEP_KINDS = {"categorical_model", "coefficient_sum"}
26
27
  _INPUT_STEP_KINDS = {"sigmoid_probability", "bernoulli_response"}
28
+ _BIN_MAP_STEP_KINDS = {"bin_map"}
27
29
 
28
30
 
29
31
  def validate_generation_spec(spec: GenerationSpec) -> None:
@@ -61,6 +63,7 @@ def validate_generation_spec(spec: GenerationSpec) -> None:
61
63
  if dependency == step.output_column:
62
64
  raise ValueError(f"step '{step.name}' cannot depend on itself")
63
65
 
66
+ _validate_feature_columns(spec)
64
67
  _validate_terminal_bernoulli_steps(spec)
65
68
  topologically_sort_steps(spec)
66
69
 
@@ -163,12 +166,17 @@ def _validate_step_shape(
163
166
  elif step.source_column is not None:
164
167
  raise ValueError(f"{step.kind} step '{step.name}' cannot declare source_column")
165
168
 
166
- if step.kind in _INPUT_STEP_KINDS:
169
+ if step.kind in _INPUT_STEP_KINDS or step.kind in _BIN_MAP_STEP_KINDS:
167
170
  if step.input_column is None:
168
171
  raise ValueError(f"{step.kind} step '{step.name}' requires input_column")
169
172
  elif step.input_column is not None:
170
173
  raise ValueError(f"{step.kind} step '{step.name}' cannot declare input_column")
171
174
 
175
+ if step.kind == "bin_map":
176
+ _validate_bin_map_step(step, output_spec)
177
+ elif step.bin_map is not None:
178
+ raise ValueError(f"{step.kind} step '{step.name}' cannot declare bin_map")
179
+
172
180
  if step.kind == "categorical_model":
173
181
  if not step.parent_columns:
174
182
  raise ValueError(
@@ -183,6 +191,11 @@ def _validate_step_shape(
183
191
  elif step.parent_columns:
184
192
  raise ValueError(f"{step.kind} step '{step.name}' cannot declare parent_columns")
185
193
 
194
+ if step.feature_columns and step.kind != "categorical_model":
195
+ raise ValueError(
196
+ f"{step.kind} step '{step.name}' cannot declare feature_columns"
197
+ )
198
+
186
199
  if step.kind == "truncated_normal" and output_spec.data_type == "categorical":
187
200
  raise ValueError(
188
201
  f"truncated_normal step '{step.name}' cannot target a categorical column"
@@ -253,6 +266,53 @@ def _validate_response_spec(step_name: str, response: BernoulliResponseSpec) ->
253
266
  )
254
267
 
255
268
 
269
+ def _validate_feature_columns(spec: GenerationSpec) -> None:
270
+ for step in spec.steps:
271
+ if not step.feature_columns:
272
+ continue
273
+ if len(set(step.feature_columns)) != len(step.feature_columns):
274
+ raise ValueError(f"step '{step.name}' has duplicate feature_columns")
275
+ ancestors = resolve_incoming_ancestor_outputs(spec, step)
276
+ unknown = [column for column in step.feature_columns if column not in ancestors]
277
+ if unknown:
278
+ raise ValueError(
279
+ f"step '{step.name}' feature_columns must already be ancestor outputs; "
280
+ f"unknown feature columns: {unknown}"
281
+ )
282
+
283
+
284
+ def _validate_bin_map_step(step: GenerationStep, output_spec: ColumnSpec) -> None:
285
+ if step.source_column is not None:
286
+ raise ValueError(f"bin_map step '{step.name}' cannot declare source_column")
287
+ if step.parent_columns:
288
+ raise ValueError(f"bin_map step '{step.name}' cannot declare parent_columns")
289
+ if step.response is not None:
290
+ raise ValueError(f"bin_map step '{step.name}' cannot declare response")
291
+ if step.coefficient_sum is not None:
292
+ raise ValueError(f"bin_map step '{step.name}' cannot declare coefficient_sum")
293
+ if step.feature_columns:
294
+ raise ValueError(f"bin_map step '{step.name}' cannot declare feature_columns")
295
+ if output_spec.data_type != "categorical":
296
+ raise ValueError(f"bin_map step '{step.name}' requires categorical output")
297
+ if step.bin_map is None:
298
+ raise ValueError(f"bin_map step '{step.name}' requires bin_map")
299
+ _validate_bin_map_spec(step.name, step.bin_map)
300
+
301
+
302
+ def _validate_bin_map_spec(step_name: str, bin_map: BinMapSpec) -> None:
303
+ breaks = bin_map.breaks
304
+ labels = bin_map.labels
305
+
306
+ if not breaks:
307
+ raise ValueError(f"bin_map step '{step_name}' requires at least one break")
308
+ if len(labels) != len(breaks) + 1:
309
+ raise ValueError(
310
+ f"bin_map step '{step_name}' requires len(labels) == len(breaks) + 1"
311
+ )
312
+ if any(right <= left for left, right in zip(breaks, breaks[1:])):
313
+ raise ValueError(f"bin_map step '{step_name}' requires strictly increasing breaks")
314
+
315
+
256
316
  def _validate_forbidden_specs(
257
317
  step: GenerationStep,
258
318
  *,
@@ -16,6 +16,7 @@ GenerationStepKind = Literal[
16
16
  "coefficient_sum",
17
17
  "sigmoid_probability",
18
18
  "bernoulli_response",
19
+ "bin_map",
19
20
  ]
20
21
  BackendParamValue: TypeAlias = str | int | float | bool
21
22
  BackendScalarValue: TypeAlias = str | int | float
@@ -65,6 +66,14 @@ class BernoulliResponseSpec:
65
66
  slope: float = 1.0
66
67
 
67
68
 
69
+ @dataclass(frozen=True)
70
+ class BinMapSpec:
71
+ """Typed deterministic binning configuration for one numeric input column."""
72
+
73
+ breaks: tuple[float, ...] = field(default_factory=tuple)
74
+ labels: tuple[str, ...] = field(default_factory=tuple)
75
+
76
+
68
77
  @dataclass(frozen=True)
69
78
  class ColumnSpec:
70
79
  """Declarative schema metadata for one source or generated column."""
@@ -103,6 +112,8 @@ class GenerationStep:
103
112
  mean_shift: float = 0.0
104
113
  coefficient_sum: CoefficientSumSpec | None = None
105
114
  response: BernoulliResponseSpec | None = None
115
+ feature_columns: tuple[str, ...] = field(default_factory=tuple)
116
+ bin_map: BinMapSpec | None = None
106
117
 
107
118
 
108
119
  @dataclass(frozen=True)
@@ -13,6 +13,7 @@ import yaml # type: ignore[import-untyped]
13
13
 
14
14
  from tabdat_synth.domain.contracts import (
15
15
  BernoulliResponseSpec,
16
+ BinMapSpec,
16
17
  CategoricalModelStepArtifact,
17
18
  CoefficientSumSpec,
18
19
  CoefficientTerm,
@@ -330,6 +331,10 @@ def _load_generation_step(raw_step: object) -> GenerationStep:
330
331
  mean_shift=float(raw_step.get("mean_shift", 0.0)),
331
332
  coefficient_sum=_load_coefficient_sum(raw_step.get("coefficient_sum")),
332
333
  response=_load_response(raw_step.get("response")),
334
+ feature_columns=tuple(
335
+ str(column) for column in raw_step.get("feature_columns", [])
336
+ ),
337
+ bin_map=_load_bin_map(raw_step.get("bin_map")),
333
338
  )
334
339
 
335
340
 
@@ -351,6 +356,10 @@ def _generation_step_to_dict(step: GenerationStep) -> dict[str, object]:
351
356
  payload["coefficient_sum"] = _coefficient_sum_to_dict(step.coefficient_sum)
352
357
  if step.response is not None:
353
358
  payload["response"] = _response_to_dict(step.response)
359
+ if step.feature_columns:
360
+ payload["feature_columns"] = list(step.feature_columns)
361
+ if step.bin_map is not None:
362
+ payload["bin_map"] = _bin_map_to_dict(step.bin_map)
354
363
  return payload
355
364
 
356
365
 
@@ -421,5 +430,31 @@ def _response_to_dict(response: BernoulliResponseSpec) -> dict[str, object]:
421
430
  }
422
431
 
423
432
 
433
+ def _load_bin_map(raw_bin_map: object) -> BinMapSpec | None:
434
+ if raw_bin_map is None:
435
+ return None
436
+ if not isinstance(raw_bin_map, dict):
437
+ raise ValueError("bundle step bin_map must be a mapping")
438
+
439
+ raw_breaks = raw_bin_map.get("breaks", [])
440
+ raw_labels = raw_bin_map.get("labels", [])
441
+ if not isinstance(raw_breaks, list):
442
+ raise ValueError("bundle step bin_map.breaks must be a list")
443
+ if not isinstance(raw_labels, list):
444
+ raise ValueError("bundle step bin_map.labels must be a list")
445
+
446
+ return BinMapSpec(
447
+ breaks=tuple(float(value) for value in raw_breaks),
448
+ labels=tuple(str(value) for value in raw_labels),
449
+ )
450
+
451
+
452
+ def _bin_map_to_dict(bin_map: BinMapSpec) -> dict[str, object]:
453
+ return {
454
+ "breaks": list(bin_map.breaks),
455
+ "labels": list(bin_map.labels),
456
+ }
457
+
458
+
424
459
  def _safe_token(value: str) -> str:
425
460
  return re.sub(r"[^A-Za-z0-9_.-]+", "_", value)
@@ -12,6 +12,7 @@ import yaml # type: ignore[import-untyped]
12
12
  from tabdat_synth.core.sampling import MISSING_CATEGORY_LABEL
13
13
  from tabdat_synth.domain.contracts import (
14
14
  BernoulliResponseSpec,
15
+ BinMapSpec,
15
16
  CoefficientSumSpec,
16
17
  CoefficientTerm,
17
18
  ColumnSpec,
@@ -294,6 +295,10 @@ def _load_generation_steps(raw_steps: object) -> tuple[GenerationStep, ...]:
294
295
  mean_shift=float(cast(Any, raw_step.get("mean_shift", 0.0))),
295
296
  coefficient_sum=_load_coefficient_sum(raw_step.get("coefficient_sum")),
296
297
  response=_load_response(raw_step.get("response")),
298
+ feature_columns=tuple(
299
+ str(column) for column in raw_step.get("feature_columns", [])
300
+ ),
301
+ bin_map=_load_bin_map(raw_step.get("bin_map")),
297
302
  )
298
303
  )
299
304
 
@@ -369,6 +374,25 @@ def _load_response(raw_response: object) -> BernoulliResponseSpec | None:
369
374
  )
370
375
 
371
376
 
377
+ def _load_bin_map(raw_bin_map: object) -> BinMapSpec | None:
378
+ if raw_bin_map is None:
379
+ return None
380
+ if not isinstance(raw_bin_map, dict):
381
+ raise ValueError("bin_map must be a mapping when provided")
382
+
383
+ raw_breaks = raw_bin_map.get("breaks", [])
384
+ raw_labels = raw_bin_map.get("labels", [])
385
+ if not isinstance(raw_breaks, list):
386
+ raise ValueError("bin_map.breaks must be a list when provided")
387
+ if not isinstance(raw_labels, list):
388
+ raise ValueError("bin_map.labels must be a list when provided")
389
+
390
+ return BinMapSpec(
391
+ breaks=tuple(float(cast(Any, value)) for value in raw_breaks),
392
+ labels=tuple(str(value) for value in raw_labels),
393
+ )
394
+
395
+
372
396
  def _column_spec_to_dict(column: ColumnSpec) -> dict[str, object]:
373
397
  payload: dict[str, object] = {
374
398
  "name": column.name,
@@ -406,6 +430,10 @@ def _generation_step_to_dict(step: GenerationStep) -> dict[str, object]:
406
430
  payload["coefficient_sum"] = _coefficient_sum_to_dict(step.coefficient_sum)
407
431
  if step.response is not None:
408
432
  payload["response"] = _response_to_dict(step.response)
433
+ if step.feature_columns:
434
+ payload["feature_columns"] = list(step.feature_columns)
435
+ if step.bin_map is not None:
436
+ payload["bin_map"] = _bin_map_to_dict(step.bin_map)
409
437
  return payload
410
438
 
411
439
 
@@ -436,3 +464,10 @@ def _response_to_dict(response: BernoulliResponseSpec) -> dict[str, object]:
436
464
  "intercept": response.intercept,
437
465
  "slope": response.slope,
438
466
  }
467
+
468
+
469
+ def _bin_map_to_dict(bin_map: BinMapSpec) -> dict[str, object]:
470
+ return {
471
+ "breaks": list(bin_map.breaks),
472
+ "labels": list(bin_map.labels),
473
+ }
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.2.0"
@@ -186,7 +186,7 @@ def _fit_generation_step(
186
186
  if step.kind == "categorical_model":
187
187
  assert step.source_column is not None
188
188
  source_series = prepared_source_df[step.source_column]
189
- input_columns = resolve_incoming_ancestor_outputs(spec, step)
189
+ input_columns = _resolve_categorical_input_columns(spec, step)
190
190
  X_source = source_generated_df.select(list(input_columns))
191
191
  model = fit_categorical_model(
192
192
  X_source,
@@ -220,6 +220,14 @@ def _fit_generation_step(
220
220
  column_spec,
221
221
  )
222
222
 
223
+ if step.kind == "bin_map":
224
+ binned_values = _calculate_bin_map_series(step, source_generated_df)
225
+ return ConfiguredStepArtifact(step=step), _cast_output_series(
226
+ step.output_column,
227
+ binned_values,
228
+ column_spec,
229
+ )
230
+
223
231
  if step.kind == "sigmoid_probability":
224
232
  probability_values = _calculate_sigmoid_probability_series(
225
233
  step,
@@ -289,6 +297,10 @@ def _execute_fitted_step(
289
297
  values = _calculate_coefficient_sum_series(step, synth_df, n_samples)
290
298
  return _cast_output_series(step.output_column, values, column_spec)
291
299
 
300
+ if step.kind == "bin_map":
301
+ values = _calculate_bin_map_series(step, synth_df)
302
+ return _cast_output_series(step.output_column, values, column_spec)
303
+
292
304
  if step.kind == "sigmoid_probability":
293
305
  values = _calculate_sigmoid_probability_series(step, synth_df)
294
306
  return _cast_output_series(step.output_column, values, column_spec)
@@ -329,6 +341,36 @@ def _calculate_sigmoid_probability_series(
329
341
  return sigmoid(_input_values(step, frame))
330
342
 
331
343
 
344
+ def _calculate_bin_map_series(
345
+ step: GenerationStep,
346
+ frame: pl.DataFrame,
347
+ ) -> np.ndarray:
348
+ assert step.bin_map is not None
349
+ assert step.input_column is not None
350
+ breaks = list(step.bin_map.breaks)
351
+ labels = list(step.bin_map.labels)
352
+ binned = (
353
+ frame.select(
354
+ pl.col(step.input_column)
355
+ .cut(breaks=breaks, labels=labels)
356
+ .cast(pl.String)
357
+ .alias(step.output_column)
358
+ )
359
+ .to_series()
360
+ .to_numpy()
361
+ )
362
+ return binned
363
+
364
+
365
+ def _resolve_categorical_input_columns(
366
+ spec: GenerationSpec,
367
+ step: GenerationStep,
368
+ ) -> tuple[str, ...]:
369
+ if step.feature_columns:
370
+ return step.feature_columns
371
+ return resolve_incoming_ancestor_outputs(spec, step)
372
+
373
+
332
374
  def _input_values(step: GenerationStep, frame: pl.DataFrame) -> np.ndarray:
333
375
  assert step.input_column is not None
334
376
  return frame[step.input_column].cast(pl.Float64).to_numpy()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tabdat-synth
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Spec-driven tabular data synthesis with reusable fitted bundles.
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/SaehwanPark/tabdat-synth
@@ -54,7 +54,7 @@ Real tabular data often carries privacy, governance, licensing, or access constr
54
54
 
55
55
  - Spec-driven generation from YAML configuration.
56
56
  - Directed synthesis workflows with stable topological execution.
57
- - Empirical, truncated-normal, categorical-model, coefficient-sum, sigmoid-probability, and Bernoulli-response steps.
57
+ - Empirical, truncated-normal, categorical-model, bin-map, coefficient-sum, sigmoid-probability, and Bernoulli-response steps.
58
58
  - Built-in categorical backends for LightGBM and logistic regression.
59
59
  - Reusable fitted bundles for generating later without the original source file.
60
60
  - Evaluation reports for marginal quality and row-level disclosure-risk heuristics.
@@ -70,7 +70,7 @@ A synthesis run has five stages:
70
70
  4. Sample synthetic rows from the fitted artifacts.
71
71
  5. Evaluate the synthetic table against source data when appropriate.
72
72
 
73
- The DAG controls which generated columns are available to later steps. For categorical-model steps, declared parents define graph edges, and the fitted model receives the full incoming ancestor context in stable order.
73
+ The DAG controls which generated columns are available to later steps. For categorical-model steps, declared parents define graph edges, and the fitted model receives the full incoming ancestor context in stable order by default, or exactly the columns named in an optional `feature_columns` tuple. Bin-map steps convert a generated numeric column into a categorical column through fixed breaks and labels, with no fitting and no random draw.
74
74
 
75
75
  ## Installation
76
76
 
File without changes
File without changes