misata 0.3.0b0__py3-none-any.whl → 0.3.1b0__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.
- misata/__init__.py +1 -1
- misata/simulator.py +133 -12
- {misata-0.3.0b0.dist-info → misata-0.3.1b0.dist-info}/METADATA +1 -1
- {misata-0.3.0b0.dist-info → misata-0.3.1b0.dist-info}/RECORD +9 -9
- /misata/{generators.py → generators_legacy.py} +0 -0
- {misata-0.3.0b0.dist-info → misata-0.3.1b0.dist-info}/WHEEL +0 -0
- {misata-0.3.0b0.dist-info → misata-0.3.1b0.dist-info}/entry_points.txt +0 -0
- {misata-0.3.0b0.dist-info → misata-0.3.1b0.dist-info}/licenses/LICENSE +0 -0
- {misata-0.3.0b0.dist-info → misata-0.3.1b0.dist-info}/top_level.txt +0 -0
misata/__init__.py
CHANGED
misata/simulator.py
CHANGED
|
@@ -16,7 +16,9 @@ from typing import Any, Dict, List, Optional
|
|
|
16
16
|
import numpy as np
|
|
17
17
|
import pandas as pd
|
|
18
18
|
|
|
19
|
-
from misata.generators import TextGenerator
|
|
19
|
+
from misata.generators.base import TextGenerator as _FactoryTextGenerator # Generator factory version
|
|
20
|
+
# Use the original generators.py TextGenerator which supports seed
|
|
21
|
+
from misata.generators_legacy import TextGenerator
|
|
20
22
|
from misata.schema import Column, Relationship, ScenarioEvent, SchemaConfig
|
|
21
23
|
|
|
22
24
|
|
|
@@ -34,6 +36,10 @@ class DataSimulator:
|
|
|
34
36
|
rng: NumPy random generator for reproducibility
|
|
35
37
|
"""
|
|
36
38
|
|
|
39
|
+
# Performance constants
|
|
40
|
+
MAX_CONTEXT_ROWS = 50000 # Cap context storage for memory efficiency
|
|
41
|
+
TEXT_POOL_SIZE = 10000 # Size of text value pools for vectorized sampling
|
|
42
|
+
|
|
37
43
|
def __init__(self, config: SchemaConfig,
|
|
38
44
|
apply_semantic_fixes: bool = True, batch_size: int = 10_000,
|
|
39
45
|
smart_mode: bool = False, use_llm: bool = True):
|
|
@@ -57,6 +63,7 @@ class DataSimulator:
|
|
|
57
63
|
self._unique_pools: Dict[str, np.ndarray] = {} # Store pre-generated unique values
|
|
58
64
|
self._unique_counters: Dict[str, int] = {} # Track usage of unique pools
|
|
59
65
|
self._smart_pools: Dict[str, np.ndarray] = {} # Cache smart value pools
|
|
66
|
+
self._text_pools: Dict[str, np.ndarray] = {} # Cache text pools for vectorized sampling
|
|
60
67
|
|
|
61
68
|
# Apply semantic inference to fix column types
|
|
62
69
|
if apply_semantic_fixes:
|
|
@@ -199,10 +206,24 @@ class DataSimulator:
|
|
|
199
206
|
ctx_df = df[cols_to_store].copy()
|
|
200
207
|
|
|
201
208
|
if table_name not in self.context:
|
|
209
|
+
# First batch: store up to MAX_CONTEXT_ROWS
|
|
210
|
+
if len(ctx_df) > self.MAX_CONTEXT_ROWS:
|
|
211
|
+
ctx_df = ctx_df.sample(n=self.MAX_CONTEXT_ROWS, random_state=self.config.seed)
|
|
202
212
|
self.context[table_name] = ctx_df
|
|
203
213
|
else:
|
|
204
|
-
# Append to existing context
|
|
205
|
-
|
|
214
|
+
# Append to existing context, but cap at MAX_CONTEXT_ROWS
|
|
215
|
+
current_len = len(self.context[table_name])
|
|
216
|
+
if current_len >= self.MAX_CONTEXT_ROWS:
|
|
217
|
+
# Already at capacity, use reservoir sampling for randomness
|
|
218
|
+
# Replace some existing rows with new ones (probability-based)
|
|
219
|
+
return # Skip appending, we have enough IDs
|
|
220
|
+
|
|
221
|
+
remaining_space = self.MAX_CONTEXT_ROWS - current_len
|
|
222
|
+
rows_to_add = ctx_df.iloc[:remaining_space]
|
|
223
|
+
self.context[table_name] = pd.concat(
|
|
224
|
+
[self.context[table_name], rows_to_add],
|
|
225
|
+
ignore_index=True
|
|
226
|
+
)
|
|
206
227
|
|
|
207
228
|
def generate_column(
|
|
208
229
|
self,
|
|
@@ -225,6 +246,70 @@ class DataSimulator:
|
|
|
225
246
|
"""
|
|
226
247
|
params = column.distribution_params
|
|
227
248
|
|
|
249
|
+
# ========== CORRELATED COLUMN GENERATION ==========
|
|
250
|
+
# If this column depends on another column's value, use conditional distribution
|
|
251
|
+
if "depends_on" in params and table_data is not None:
|
|
252
|
+
parent_col = params["depends_on"]
|
|
253
|
+
mapping = params.get("mapping", {})
|
|
254
|
+
|
|
255
|
+
if parent_col in table_data.columns and mapping:
|
|
256
|
+
parent_values = table_data[parent_col].values
|
|
257
|
+
|
|
258
|
+
# Check if it's numeric or categorical mapping
|
|
259
|
+
first_val = next(iter(mapping.values()))
|
|
260
|
+
if isinstance(first_val, dict) and "mean" in first_val:
|
|
261
|
+
# Numeric conditional distribution (e.g., salary based on job_title)
|
|
262
|
+
# mapping = {"Intern": {"mean": 40000, "std": 5000}, "CTO": {"mean": 200000, "std": 30000}}
|
|
263
|
+
values = np.zeros(size)
|
|
264
|
+
for key, dist in mapping.items():
|
|
265
|
+
mask = parent_values == key
|
|
266
|
+
count = mask.sum()
|
|
267
|
+
if count > 0:
|
|
268
|
+
mean = dist.get("mean", 50000)
|
|
269
|
+
std = dist.get("std", mean * 0.1)
|
|
270
|
+
values[mask] = self.rng.normal(mean, std, count)
|
|
271
|
+
|
|
272
|
+
# Handle values that didn't match any key (use default)
|
|
273
|
+
default = params.get("default", {"mean": 50000, "std": 10000})
|
|
274
|
+
unmatched = ~np.isin(parent_values, list(mapping.keys()))
|
|
275
|
+
if unmatched.sum() > 0:
|
|
276
|
+
values[unmatched] = self.rng.normal(
|
|
277
|
+
default.get("mean", 50000),
|
|
278
|
+
default.get("std", 10000),
|
|
279
|
+
unmatched.sum()
|
|
280
|
+
)
|
|
281
|
+
return values
|
|
282
|
+
|
|
283
|
+
elif isinstance(first_val, list):
|
|
284
|
+
# Categorical conditional (e.g., state based on country)
|
|
285
|
+
# mapping = {"USA": ["CA", "TX", "NY"], "UK": ["England", "Scotland"]}
|
|
286
|
+
values = np.empty(size, dtype=object)
|
|
287
|
+
for key, choices in mapping.items():
|
|
288
|
+
mask = parent_values == key
|
|
289
|
+
count = mask.sum()
|
|
290
|
+
if count > 0:
|
|
291
|
+
values[mask] = self.rng.choice(choices, count)
|
|
292
|
+
|
|
293
|
+
# Default for unmatched
|
|
294
|
+
default_choices = params.get("default", ["Unknown"])
|
|
295
|
+
unmatched = values == None # noqa
|
|
296
|
+
if unmatched.sum() > 0:
|
|
297
|
+
values[unmatched] = self.rng.choice(default_choices, unmatched.sum())
|
|
298
|
+
return values
|
|
299
|
+
|
|
300
|
+
elif isinstance(first_val, (int, float)):
|
|
301
|
+
# Probability-based boolean (e.g., churn probability based on plan)
|
|
302
|
+
# mapping = {"free": 0.3, "pro": 0.1, "enterprise": 0.05}
|
|
303
|
+
values = np.zeros(size, dtype=bool)
|
|
304
|
+
for key, prob in mapping.items():
|
|
305
|
+
mask = parent_values == key
|
|
306
|
+
count = mask.sum()
|
|
307
|
+
if count > 0:
|
|
308
|
+
values[mask] = self.rng.random(count) < prob
|
|
309
|
+
return values
|
|
310
|
+
|
|
311
|
+
# ========== STANDARD COLUMN GENERATION ==========
|
|
312
|
+
|
|
228
313
|
# CATEGORICAL
|
|
229
314
|
if column.type == "categorical":
|
|
230
315
|
choices = params.get("choices", ["A", "B", "C"])
|
|
@@ -469,23 +554,59 @@ class DataSimulator:
|
|
|
469
554
|
return values
|
|
470
555
|
|
|
471
556
|
if text_type == "name":
|
|
472
|
-
|
|
557
|
+
pool_key = "text_name"
|
|
558
|
+
if pool_key not in self._text_pools:
|
|
559
|
+
pool_size = min(size, self.TEXT_POOL_SIZE)
|
|
560
|
+
self._text_pools[pool_key] = np.array([self.text_gen.name() for _ in range(pool_size)])
|
|
561
|
+
values = self.rng.choice(self._text_pools[pool_key], size=size)
|
|
473
562
|
elif text_type == "email":
|
|
474
|
-
|
|
563
|
+
pool_key = "text_email"
|
|
564
|
+
if pool_key not in self._text_pools:
|
|
565
|
+
pool_size = min(size, self.TEXT_POOL_SIZE)
|
|
566
|
+
self._text_pools[pool_key] = np.array([self.text_gen.email() for _ in range(pool_size)])
|
|
567
|
+
values = self.rng.choice(self._text_pools[pool_key], size=size)
|
|
475
568
|
elif text_type == "company":
|
|
476
|
-
|
|
569
|
+
pool_key = "text_company"
|
|
570
|
+
if pool_key not in self._text_pools:
|
|
571
|
+
pool_size = min(size, self.TEXT_POOL_SIZE)
|
|
572
|
+
self._text_pools[pool_key] = np.array([self.text_gen.company() for _ in range(pool_size)])
|
|
573
|
+
values = self.rng.choice(self._text_pools[pool_key], size=size)
|
|
477
574
|
elif text_type == "sentence":
|
|
478
|
-
|
|
575
|
+
pool_key = "text_sentence"
|
|
576
|
+
if pool_key not in self._text_pools:
|
|
577
|
+
pool_size = min(size, self.TEXT_POOL_SIZE)
|
|
578
|
+
self._text_pools[pool_key] = np.array([self.text_gen.sentence() for _ in range(pool_size)])
|
|
579
|
+
values = self.rng.choice(self._text_pools[pool_key], size=size)
|
|
479
580
|
elif text_type == "word":
|
|
480
|
-
|
|
581
|
+
pool_key = "text_word"
|
|
582
|
+
if pool_key not in self._text_pools:
|
|
583
|
+
pool_size = min(size, self.TEXT_POOL_SIZE)
|
|
584
|
+
self._text_pools[pool_key] = np.array([self.text_gen.word() for _ in range(pool_size)])
|
|
585
|
+
values = self.rng.choice(self._text_pools[pool_key], size=size)
|
|
481
586
|
elif text_type == "address":
|
|
482
|
-
|
|
587
|
+
pool_key = "text_address"
|
|
588
|
+
if pool_key not in self._text_pools:
|
|
589
|
+
pool_size = min(size, self.TEXT_POOL_SIZE)
|
|
590
|
+
self._text_pools[pool_key] = np.array([self.text_gen.full_address() for _ in range(pool_size)])
|
|
591
|
+
values = self.rng.choice(self._text_pools[pool_key], size=size)
|
|
483
592
|
elif text_type == "phone":
|
|
484
|
-
|
|
593
|
+
pool_key = "text_phone"
|
|
594
|
+
if pool_key not in self._text_pools:
|
|
595
|
+
pool_size = min(size, self.TEXT_POOL_SIZE)
|
|
596
|
+
self._text_pools[pool_key] = np.array([self.text_gen.phone_number() for _ in range(pool_size)])
|
|
597
|
+
values = self.rng.choice(self._text_pools[pool_key], size=size)
|
|
485
598
|
elif text_type == "url":
|
|
486
|
-
|
|
599
|
+
pool_key = "text_url"
|
|
600
|
+
if pool_key not in self._text_pools:
|
|
601
|
+
pool_size = min(size, self.TEXT_POOL_SIZE)
|
|
602
|
+
self._text_pools[pool_key] = np.array([self.text_gen.url() for _ in range(pool_size)])
|
|
603
|
+
values = self.rng.choice(self._text_pools[pool_key], size=size)
|
|
487
604
|
else:
|
|
488
|
-
|
|
605
|
+
pool_key = "text_sentence"
|
|
606
|
+
if pool_key not in self._text_pools:
|
|
607
|
+
pool_size = min(size, self.TEXT_POOL_SIZE)
|
|
608
|
+
self._text_pools[pool_key] = np.array([self.text_gen.sentence() for _ in range(pool_size)])
|
|
609
|
+
values = self.rng.choice(self._text_pools[pool_key], size=size)
|
|
489
610
|
|
|
490
611
|
return values
|
|
491
612
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
misata/__init__.py,sha256=
|
|
1
|
+
misata/__init__.py,sha256=IMFNFb00vkOctRozJJt5HjfPZokACGCBsNin5tqNj5I,3144
|
|
2
2
|
misata/api.py,sha256=Wq2H3iJzocNTsCzb9vhYJxDyag3Yiucvb-GVF0tdKhI,14999
|
|
3
3
|
misata/audit.py,sha256=4eUCHT2STptemfakWeNODbVuBRhyD8Q32LlB2eufvuw,12291
|
|
4
4
|
misata/benchmark.py,sha256=Y1-tuKegJyAlTneROQpPo276qnfmMmupGDbVDs9k5J8,12358
|
|
@@ -12,7 +12,7 @@ misata/customization.py,sha256=pw-BEsPKN091hyOrQWWQoRhTrlmQ9_PXXopm2FZSEvs,8551
|
|
|
12
12
|
misata/exceptions.py,sha256=C3IGMk8xAy9AmRVWeSAnLHHui7drv6rzgzvOmr6gh50,8335
|
|
13
13
|
misata/feedback.py,sha256=HBEsoKi_vdRqwRzMoVFVj_cjfzQ5SUAaGz40s1HMD50,13313
|
|
14
14
|
misata/formulas.py,sha256=KOTq5YN_19vv1ERd92bdzKot9yo9rrrwjOuWO13nFCg,11210
|
|
15
|
-
misata/
|
|
15
|
+
misata/generators_legacy.py,sha256=NrMF12i6CB7K6fUsqcqurmZBBQ382ZhVnYB9oMBIZCE,8844
|
|
16
16
|
misata/hybrid.py,sha256=5oopAdfOLWUYzdRWlc0plVeVEVg7Nu1CVGNNCDSjQt8,13104
|
|
17
17
|
misata/llm_parser.py,sha256=2SVozbKtb0kaPaR4ERz9FtIIxK5jQVaYJ8L_xC6gU10,20662
|
|
18
18
|
misata/noise.py,sha256=UO7MokzQ5Y5Vj7JaayDUG0JwCLnpHtnpQTcJ4UHWibo,10460
|
|
@@ -20,7 +20,7 @@ misata/profiles.py,sha256=0djys8wWvH8VP74KmGn6cGLuOb64h9Hk0g0bkXOfxP4,9578
|
|
|
20
20
|
misata/quality.py,sha256=VSntJfMnF1tVWJ05fvbVJOMcAPEB7QtuEg18k6aEwhA,11685
|
|
21
21
|
misata/schema.py,sha256=zMYDPCgPfcy_STgANiS-Ow3dUETpW3Ayo02G88jmBe0,8954
|
|
22
22
|
misata/semantic.py,sha256=0fauGWJ75wlbHVqT0hohYTN4m_nscdaMaVAIfkhTZXk,7087
|
|
23
|
-
misata/simulator.py,sha256=
|
|
23
|
+
misata/simulator.py,sha256=dLAJf_Ko_3b27OwcVk-d6n7fBVvYmY2v-B_Qscq-m6c,41085
|
|
24
24
|
misata/smart_values.py,sha256=8-TYBK5cVBst9tfGuQXXetOLSqgns_NKnIl14rpVrbk,35870
|
|
25
25
|
misata/story_parser.py,sha256=7N7so3KWisl2UxkOtENQwP-4hN2cs9vTKsPHVRZB2Mc,15964
|
|
26
26
|
misata/streaming.py,sha256=qbEnoFRfn9a7H_gWlq5C3TwbNUnP5U98OPo1EdU_cQ0,7578
|
|
@@ -29,9 +29,9 @@ misata/generators/__init__.py,sha256=V4I_1IucuywRJZH3cLxKvBd2Ib7kE0WIJ7tq8y4lkx8
|
|
|
29
29
|
misata/generators/base.py,sha256=iON9iAONMEQdbq2Fdric3V3bWn3caD1ITC16DTCK0Og,21329
|
|
30
30
|
misata/templates/__init__.py,sha256=0RcZz9d4bmCqLAr77h0gpMfHncqAPeZCguqsuGCz7rE,25245
|
|
31
31
|
misata/templates/library.py,sha256=eMex18ZKlzQqIkGFgs1uy9QGs7PmUN_VVL4txKvxynM,20930
|
|
32
|
-
misata-0.3.
|
|
33
|
-
misata-0.3.
|
|
34
|
-
misata-0.3.
|
|
35
|
-
misata-0.3.
|
|
36
|
-
misata-0.3.
|
|
37
|
-
misata-0.3.
|
|
32
|
+
misata-0.3.1b0.dist-info/licenses/LICENSE,sha256=oagkechmfr9iT214N871zCm7TnB0KTfPjAUWxHsYJ4I,1071
|
|
33
|
+
misata-0.3.1b0.dist-info/METADATA,sha256=A-4ymJEBCSo-yl3CM8sNYr0aCdJfUBgtg7S-bABtBkw,8114
|
|
34
|
+
misata-0.3.1b0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
35
|
+
misata-0.3.1b0.dist-info/entry_points.txt,sha256=k3SDuju7VnqB4AcY0Vufw-j1tWU3Ay612G3DGqoNs0U,43
|
|
36
|
+
misata-0.3.1b0.dist-info/top_level.txt,sha256=dpwR99XWKUAXqNg7WiNLu_XYd7WYGmZpJzrfQXbAZFs,7
|
|
37
|
+
misata-0.3.1b0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|