fieldwork 0.1.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.
- fieldwork-0.1.0/LICENSE +21 -0
- fieldwork-0.1.0/NOTICE +6 -0
- fieldwork-0.1.0/PKG-INFO +86 -0
- fieldwork-0.1.0/README.pypi.md +62 -0
- fieldwork-0.1.0/benchmarks/discovery.py +32 -0
- fieldwork-0.1.0/benchmarks/foundation.py +125 -0
- fieldwork-0.1.0/docs/.gitkeep +0 -0
- fieldwork-0.1.0/docs/algorithms.md +145 -0
- fieldwork-0.1.0/docs/architecture.md +67 -0
- fieldwork-0.1.0/docs/assets/README.pypi.md +62 -0
- fieldwork-0.1.0/docs/assets/availability-topology.html +1 -0
- fieldwork-0.1.0/docs/assets/availability-topology.json +2127 -0
- fieldwork-0.1.0/docs/assets/availability-topology.png +0 -0
- fieldwork-0.1.0/docs/assets/availability-topology.svg +1 -0
- fieldwork-0.1.0/docs/assets/availability.html +1 -0
- fieldwork-0.1.0/docs/assets/availability.json +4524 -0
- fieldwork-0.1.0/docs/assets/availability.png +0 -0
- fieldwork-0.1.0/docs/assets/availability.svg +1 -0
- fieldwork-0.1.0/docs/assets/census.html +59 -0
- fieldwork-0.1.0/docs/assets/census.json +98 -0
- fieldwork-0.1.0/docs/assets/census.png +0 -0
- fieldwork-0.1.0/docs/assets/census.svg +1 -0
- fieldwork-0.1.0/docs/assets/grain.html +59 -0
- fieldwork-0.1.0/docs/assets/grain.json +408 -0
- fieldwork-0.1.0/docs/assets/grain.png +0 -0
- fieldwork-0.1.0/docs/assets/grain.svg +1 -0
- fieldwork-0.1.0/docs/assets/manifest.json +26 -0
- fieldwork-0.1.0/docs/assets/paths.html +1 -0
- fieldwork-0.1.0/docs/assets/paths.json +194 -0
- fieldwork-0.1.0/docs/assets/paths.png +0 -0
- fieldwork-0.1.0/docs/assets/paths.svg +1 -0
- fieldwork-0.1.0/docs/contracts.md +149 -0
- fieldwork-0.1.0/docs/development.md +53 -0
- fieldwork-0.1.0/docs/index.md +18 -0
- fieldwork-0.1.0/docs/investigation.md +114 -0
- fieldwork-0.1.0/docs/releases.md +73 -0
- fieldwork-0.1.0/examples/investigation.ipynb +140 -0
- fieldwork-0.1.0/examples/investigation.py +73 -0
- fieldwork-0.1.0/examples/observed_grain_graph.py +61 -0
- fieldwork-0.1.0/pyproject.toml +43 -0
- fieldwork-0.1.0/scripts/fonts/Lato-Bold.ttf +0 -0
- fieldwork-0.1.0/scripts/fonts/Lato-Regular.ttf +0 -0
- fieldwork-0.1.0/scripts/fonts/OFL.txt +93 -0
- fieldwork-0.1.0/scripts/generate_assets.py +101 -0
- fieldwork-0.1.0/src/fieldwork/__init__.py +47 -0
- fieldwork-0.1.0/src/fieldwork/_explore/__init__.py +27 -0
- fieldwork-0.1.0/src/fieldwork/_explore/_kernels.py +64 -0
- fieldwork-0.1.0/src/fieldwork/_explore/census.py +479 -0
- fieldwork-0.1.0/src/fieldwork/_explore/encoding.py +264 -0
- fieldwork-0.1.0/src/fieldwork/_explore/grain.py +245 -0
- fieldwork-0.1.0/src/fieldwork/_explore/grain_graph.py +179 -0
- fieldwork-0.1.0/src/fieldwork/_explore/graphics.py +733 -0
- fieldwork-0.1.0/src/fieldwork/_explore/orchestration.py +166 -0
- fieldwork-0.1.0/src/fieldwork/_explore/relations.py +405 -0
- fieldwork-0.1.0/src/fieldwork/_explore/render.py +450 -0
- fieldwork-0.1.0/src/fieldwork/_explore/resolved.py +125 -0
- fieldwork-0.1.0/src/fieldwork/_explore/result.py +75 -0
- fieldwork-0.1.0/src/fieldwork/_explore/roles.py +103 -0
- fieldwork-0.1.0/src/fieldwork/_explore/visual_data.py +299 -0
- fieldwork-0.1.0/src/fieldwork/availability.py +367 -0
- fieldwork-0.1.0/src/fieldwork/discovery.py +251 -0
- fieldwork-0.1.0/src/fieldwork/evidence.py +483 -0
- fieldwork-0.1.0/src/fieldwork/families.py +73 -0
- fieldwork-0.1.0/src/fieldwork/navigation.py +360 -0
- fieldwork-0.1.0/src/fieldwork/patterns.py +189 -0
- fieldwork-0.1.0/src/fieldwork/presentation.py +530 -0
- fieldwork-0.1.0/src/fieldwork/py.typed +0 -0
- fieldwork-0.1.0/src/fieldwork/workflow.py +199 -0
- fieldwork-0.1.0/tests/discovery/test_context_adapter.py +149 -0
- fieldwork-0.1.0/tests/discovery/test_examples.py +19 -0
- fieldwork-0.1.0/tests/discovery/test_journeys.py +365 -0
- fieldwork-0.1.0/tests/discovery/test_workflow.py +433 -0
- fieldwork-0.1.0/tests/foundation/__init__.py +1 -0
- fieldwork-0.1.0/tests/foundation/oracle.py +43 -0
- fieldwork-0.1.0/tests/foundation/test_census.py +50 -0
- fieldwork-0.1.0/tests/foundation/test_contracts.py +92 -0
- fieldwork-0.1.0/tests/foundation/test_differential.py +54 -0
- fieldwork-0.1.0/tests/foundation/test_grain_graph.py +136 -0
- fieldwork-0.1.0/tests/foundation/test_grain_relations.py +79 -0
- fieldwork-0.1.0/tests/foundation/test_graphics.py +226 -0
- fieldwork-0.1.0/tests/foundation/test_joint_counts.py +41 -0
- fieldwork-0.1.0/tests/foundation/test_render.py +293 -0
- fieldwork-0.1.0/tests/foundation/test_resolved.py +212 -0
- fieldwork-0.1.0/tests/foundation/test_scopes.py +108 -0
- fieldwork-0.1.0/uv.lock +789 -0
fieldwork-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Beatrice Brown-Mulry
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
fieldwork-0.1.0/NOTICE
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
Fieldwork exploration foundation, tests and benchmark extracted from bea-tools
|
|
2
|
+
by Beatrice Brown-Mulry, declared MIT in its pyproject.toml.
|
|
3
|
+
Source: https://github.com/beatrice-b-m/bea-tools
|
|
4
|
+
Commit: 4e4f1704cbca91a314652b0c99f86d8f4a2c4f90
|
|
5
|
+
The source repository contained no standalone license text at extraction.
|
|
6
|
+
No DICOM, sampling code, or compatibility accessors were migrated.
|
fieldwork-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fieldwork
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Explore unfamiliar data through patterns, evidence, and reproducible investigations
|
|
5
|
+
Author: Beatrice Brown-Mulry
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
License-File: NOTICE
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Requires-Dist: numpy>=1.26
|
|
16
|
+
Requires-Dist: pandas>=2.2.3
|
|
17
|
+
Requires-Dist: wcwidth>=0.2 ; extra == 'unicode'
|
|
18
|
+
Requires-Python: >=3.11, <3.15
|
|
19
|
+
Project-URL: Source, https://github.com/beatrice-b-m/fieldwork
|
|
20
|
+
Project-URL: Documentation, https://fieldwork.beabm.dev
|
|
21
|
+
Project-URL: Issues, https://github.com/beatrice-b-m/fieldwork/issues
|
|
22
|
+
Provides-Extra: unicode
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# Fieldwork
|
|
26
|
+
|
|
27
|
+
Explore unfamiliar data through patterns, source-row evidence, and reproducible
|
|
28
|
+
investigations. Fieldwork is a Python toolkit for researchers working with pandas
|
|
29
|
+
dataframes: find availability families, examine dependencies and candidate grains,
|
|
30
|
+
then choose a useful census path through the data.
|
|
31
|
+
|
|
32
|
+
**Alpha release.** Python 3.11–3.14; pandas and NumPy are the only required runtime
|
|
33
|
+
dependencies.
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install fieldwork
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
import pandas as pd
|
|
43
|
+
import fieldwork as fw
|
|
44
|
+
|
|
45
|
+
df = pd.DataFrame({
|
|
46
|
+
"site": ["North", "North", "South", "South"],
|
|
47
|
+
"exam": [1, 1, 2, 2],
|
|
48
|
+
"image": [10, 11, 20, 21],
|
|
49
|
+
"report": ["ok", "ok", "ok", None],
|
|
50
|
+
})
|
|
51
|
+
overview = fw.explore(df)
|
|
52
|
+
availability = fw.missingness(df, entity="exam", min_implication=0.75)
|
|
53
|
+
paths = fw.suggest_paths(df, features=["site", "exam"])
|
|
54
|
+
tree = paths.best.census(df)
|
|
55
|
+
print(tree)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Use `result.to_frame()` for discovery tables and `result.inspect(df, finding_id,
|
|
59
|
+
exceptions=True)` for the saved example rows. Duplicate indexes are supported;
|
|
60
|
+
inspection checks the ordered source dataset. Use `result.select(df, finding_id)`
|
|
61
|
+
to recover the complete matching population as a `Scope`, then pass that scope to
|
|
62
|
+
`missingness` or `suggest_paths`. `paths.best.census(df)` preserves the selected
|
|
63
|
+
population and sentinel conventions. Choose `unit="entities"` with `entity=` for
|
|
64
|
+
equal entity weights, or keep the default `unit="rows"`. Browse connected feature
|
|
65
|
+
evidence with `overview.relationships("image")`. Save results with `to_dict()`, export
|
|
66
|
+
with `render_svg()` or `render_html()`, and reapply a `Recipe` to later deliveries.
|
|
67
|
+
|
|
68
|
+

|
|
69
|
+
|
|
70
|
+
The single-table workflow includes independent levels, contextual pair summaries,
|
|
71
|
+
joint counts and absence, exact grain graphs, bounded approximate dependency
|
|
72
|
+
discovery, five census-path objectives, availability signatures and entity summaries,
|
|
73
|
+
string and numeric patterns, scoped investigations, and delivery comparisons.
|
|
74
|
+
Topology-only exports retain structure while suppressing quantitative evidence.
|
|
75
|
+
Automatic related-table discovery is a later extension.
|
|
76
|
+
|
|
77
|
+
- [User documentation source](https://github.com/beatrice-b-m/fieldwork-docs)
|
|
78
|
+
- [Developer documentation](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/docs/index.md): architecture, contracts, algorithms, releases
|
|
79
|
+
- [Investigation journey and units](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/docs/investigation.md)
|
|
80
|
+
- [Executable investigation](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/examples/investigation.py) and [notebook](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/examples/investigation.ipynb)
|
|
81
|
+
- [MIT license](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/LICENSE) and [extraction provenance](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/NOTICE)
|
|
82
|
+
|
|
83
|
+
For development: `uv sync --locked`, `uv run pytest`, and `uv build`. Documentation
|
|
84
|
+
images are generated from executable examples using the public renderers. Run
|
|
85
|
+
`uv run python scripts/generate_assets.py` after behavior or styling changes;
|
|
86
|
+
CI checks concurrence and release builds regenerate the assets.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Fieldwork
|
|
2
|
+
|
|
3
|
+
Explore unfamiliar data through patterns, source-row evidence, and reproducible
|
|
4
|
+
investigations. Fieldwork is a Python toolkit for researchers working with pandas
|
|
5
|
+
dataframes: find availability families, examine dependencies and candidate grains,
|
|
6
|
+
then choose a useful census path through the data.
|
|
7
|
+
|
|
8
|
+
**Alpha release.** Python 3.11–3.14; pandas and NumPy are the only required runtime
|
|
9
|
+
dependencies.
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install fieldwork
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import pandas as pd
|
|
19
|
+
import fieldwork as fw
|
|
20
|
+
|
|
21
|
+
df = pd.DataFrame({
|
|
22
|
+
"site": ["North", "North", "South", "South"],
|
|
23
|
+
"exam": [1, 1, 2, 2],
|
|
24
|
+
"image": [10, 11, 20, 21],
|
|
25
|
+
"report": ["ok", "ok", "ok", None],
|
|
26
|
+
})
|
|
27
|
+
overview = fw.explore(df)
|
|
28
|
+
availability = fw.missingness(df, entity="exam", min_implication=0.75)
|
|
29
|
+
paths = fw.suggest_paths(df, features=["site", "exam"])
|
|
30
|
+
tree = paths.best.census(df)
|
|
31
|
+
print(tree)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Use `result.to_frame()` for discovery tables and `result.inspect(df, finding_id,
|
|
35
|
+
exceptions=True)` for the saved example rows. Duplicate indexes are supported;
|
|
36
|
+
inspection checks the ordered source dataset. Use `result.select(df, finding_id)`
|
|
37
|
+
to recover the complete matching population as a `Scope`, then pass that scope to
|
|
38
|
+
`missingness` or `suggest_paths`. `paths.best.census(df)` preserves the selected
|
|
39
|
+
population and sentinel conventions. Choose `unit="entities"` with `entity=` for
|
|
40
|
+
equal entity weights, or keep the default `unit="rows"`. Browse connected feature
|
|
41
|
+
evidence with `overview.relationships("image")`. Save results with `to_dict()`, export
|
|
42
|
+
with `render_svg()` or `render_html()`, and reapply a `Recipe` to later deliveries.
|
|
43
|
+
|
|
44
|
+

|
|
45
|
+
|
|
46
|
+
The single-table workflow includes independent levels, contextual pair summaries,
|
|
47
|
+
joint counts and absence, exact grain graphs, bounded approximate dependency
|
|
48
|
+
discovery, five census-path objectives, availability signatures and entity summaries,
|
|
49
|
+
string and numeric patterns, scoped investigations, and delivery comparisons.
|
|
50
|
+
Topology-only exports retain structure while suppressing quantitative evidence.
|
|
51
|
+
Automatic related-table discovery is a later extension.
|
|
52
|
+
|
|
53
|
+
- [User documentation source](https://github.com/beatrice-b-m/fieldwork-docs)
|
|
54
|
+
- [Developer documentation](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/docs/index.md): architecture, contracts, algorithms, releases
|
|
55
|
+
- [Investigation journey and units](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/docs/investigation.md)
|
|
56
|
+
- [Executable investigation](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/examples/investigation.py) and [notebook](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/examples/investigation.ipynb)
|
|
57
|
+
- [MIT license](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/LICENSE) and [extraction provenance](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/NOTICE)
|
|
58
|
+
|
|
59
|
+
For development: `uv sync --locked`, `uv run pytest`, and `uv build`. Documentation
|
|
60
|
+
images are generated from executable examples using the public renderers. Run
|
|
61
|
+
`uv run python scripts/generate_assets.py` after behavior or styling changes;
|
|
62
|
+
CI checks concurrence and release builds regenerate the assets.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Measure end-to-end discovery and strict JSON result size on seeded wide data."""
|
|
2
|
+
import argparse
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
|
+
import fieldwork as fw
|
|
10
|
+
|
|
11
|
+
parser = argparse.ArgumentParser()
|
|
12
|
+
parser.add_argument('--rows', type=int, default=2000)
|
|
13
|
+
parser.add_argument('--columns', type=int, default=24)
|
|
14
|
+
parser.add_argument('--output', type=Path, required=True)
|
|
15
|
+
args = parser.parse_args()
|
|
16
|
+
rng = np.random.default_rng(721)
|
|
17
|
+
frame = pd.DataFrame({f'field_{i}': np.where(rng.random(args.rows) < .7, np.nan,
|
|
18
|
+
rng.integers(0, 8, args.rows)) for i in range(args.columns)})
|
|
19
|
+
frame['entity'] = np.arange(args.rows)//4
|
|
20
|
+
records = []
|
|
21
|
+
for name, run in [('missingness', lambda: fw.missingness(frame, entity='entity')),
|
|
22
|
+
('dependencies', lambda: fw.discover_dependencies(frame, max_candidates=12)),
|
|
23
|
+
('paths', lambda: fw.suggest_paths(frame, max_candidates=60))]:
|
|
24
|
+
start = time.perf_counter()
|
|
25
|
+
result = run()
|
|
26
|
+
elapsed = time.perf_counter()-start
|
|
27
|
+
encoded = json.dumps(result.to_dict(), allow_nan=False).encode()
|
|
28
|
+
records.append({'operation': name, 'seconds': elapsed, 'result_bytes': len(encoded),
|
|
29
|
+
'coverage': result['coverage']})
|
|
30
|
+
args.output.write_text(json.dumps({'rows': args.rows, 'columns': len(frame.columns),
|
|
31
|
+
'results': records}, indent=2)+'\n')
|
|
32
|
+
print(args.output.read_text())
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Reproducible synthetic end-to-end benchmark for the hierarchy explorer."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import hashlib
|
|
7
|
+
import json
|
|
8
|
+
import platform
|
|
9
|
+
import resource
|
|
10
|
+
import statistics
|
|
11
|
+
import time
|
|
12
|
+
import tracemalloc
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
import pandas as pd
|
|
17
|
+
|
|
18
|
+
from fieldwork import explore, grain, levels, render_plaintext
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def fixture(name: str, rows: int, seed: int) -> pd.DataFrame:
|
|
22
|
+
rng = np.random.default_rng(seed)
|
|
23
|
+
cardinalities = {
|
|
24
|
+
"low3": (2, 4, 8),
|
|
25
|
+
"low6": (4, 4, 4, 4, 4, 4),
|
|
26
|
+
"high6": (100, 100, 100, 100, 100, 100),
|
|
27
|
+
"mixed6": (17_000, 50_000, 2, 4, 8, 10_000),
|
|
28
|
+
}[name]
|
|
29
|
+
return pd.DataFrame(
|
|
30
|
+
{
|
|
31
|
+
f"c{index}": np.char.add("v", rng.integers(0, cardinality, rows).astype(str))
|
|
32
|
+
for index, cardinality in enumerate(cardinalities)
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def workload(frame: pd.DataFrame, name: str):
|
|
38
|
+
columns = frame.columns.tolist()
|
|
39
|
+
if name == "s1":
|
|
40
|
+
return levels(frame, columns, top_n=5)
|
|
41
|
+
if name == "s3":
|
|
42
|
+
return grain(frame, columns[:2])
|
|
43
|
+
result = explore(
|
|
44
|
+
frame,
|
|
45
|
+
columns[:6],
|
|
46
|
+
candidate_keys=columns[:2],
|
|
47
|
+
top_n=5,
|
|
48
|
+
max_nodes=10_000,
|
|
49
|
+
include_pairs=True,
|
|
50
|
+
)
|
|
51
|
+
if name == "render":
|
|
52
|
+
return render_plaintext(result, max_lines=200)
|
|
53
|
+
return json.dumps(result.to_dict(), allow_nan=False, separators=(",", ":"))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def main() -> None:
|
|
57
|
+
parser = argparse.ArgumentParser()
|
|
58
|
+
parser.add_argument("--suite", default="acceptance", choices=["smoke", "acceptance"])
|
|
59
|
+
parser.add_argument("--fixture", default="low6", choices=["low3", "low6", "high6", "mixed6"])
|
|
60
|
+
parser.add_argument("--workload", default="explore", choices=["s1", "s3", "explore", "render"])
|
|
61
|
+
parser.add_argument("--rows", type=int)
|
|
62
|
+
parser.add_argument("--seed", type=int, default=721)
|
|
63
|
+
parser.add_argument("--repeats", type=int, default=7)
|
|
64
|
+
parser.add_argument("--output", type=Path, required=True)
|
|
65
|
+
args = parser.parse_args()
|
|
66
|
+
rows = args.rows or (10_000 if args.suite == "smoke" else 150_000)
|
|
67
|
+
frame = fixture(args.fixture, rows, args.seed)
|
|
68
|
+
workload(frame, args.workload) # warmup
|
|
69
|
+
times = []
|
|
70
|
+
baseline_rss = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
|
|
71
|
+
for _ in range(args.repeats):
|
|
72
|
+
start = time.perf_counter()
|
|
73
|
+
workload(frame, args.workload)
|
|
74
|
+
times.append(time.perf_counter() - start)
|
|
75
|
+
# Allocation tracing is a separate run because it changes timings.
|
|
76
|
+
tracemalloc.start()
|
|
77
|
+
traced_output = workload(frame, args.workload)
|
|
78
|
+
_, traced_peak = tracemalloc.get_traced_memory()
|
|
79
|
+
peak_rss = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
|
|
80
|
+
tracemalloc.stop()
|
|
81
|
+
canonical = (
|
|
82
|
+
traced_output
|
|
83
|
+
if isinstance(traced_output, str)
|
|
84
|
+
else json.dumps(traced_output.to_dict(), allow_nan=False)
|
|
85
|
+
)
|
|
86
|
+
record = {
|
|
87
|
+
"fixture": args.fixture,
|
|
88
|
+
"workload": args.workload,
|
|
89
|
+
"rows": rows,
|
|
90
|
+
"columns": len(frame.columns),
|
|
91
|
+
"observed_cardinalities": {
|
|
92
|
+
column: int(frame[column].nunique(dropna=False)) for column in frame.columns
|
|
93
|
+
},
|
|
94
|
+
"seed": args.seed,
|
|
95
|
+
"repeats": args.repeats,
|
|
96
|
+
"seconds": {
|
|
97
|
+
"median": statistics.median(times),
|
|
98
|
+
"minimum": min(times),
|
|
99
|
+
"maximum": max(times),
|
|
100
|
+
"spread": max(times) - min(times),
|
|
101
|
+
"raw": times,
|
|
102
|
+
},
|
|
103
|
+
"memory": {
|
|
104
|
+
"baseline_peak_rss_platform_units": baseline_rss,
|
|
105
|
+
"absolute_peak_rss_platform_units": peak_rss,
|
|
106
|
+
"incremental_peak_rss_platform_units": max(0, peak_rss - baseline_rss),
|
|
107
|
+
"tracemalloc_peak_bytes": traced_peak,
|
|
108
|
+
},
|
|
109
|
+
"output_bytes": len(canonical.encode()),
|
|
110
|
+
"output_sha256": hashlib.sha256(canonical.encode()).hexdigest(),
|
|
111
|
+
"environment": {
|
|
112
|
+
"python": platform.python_version(),
|
|
113
|
+
"platform": platform.platform(),
|
|
114
|
+
"pandas": pd.__version__,
|
|
115
|
+
"numpy": np.__version__,
|
|
116
|
+
},
|
|
117
|
+
}
|
|
118
|
+
args.output.mkdir(parents=True, exist_ok=True)
|
|
119
|
+
destination = args.output / f"{args.fixture}-{args.workload}-{args.seed}.json"
|
|
120
|
+
destination.write_text(json.dumps(record, indent=2, allow_nan=False) + "\n")
|
|
121
|
+
print(destination)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
if __name__ == "__main__":
|
|
125
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Discovery algorithms and budgets
|
|
2
|
+
|
|
3
|
+
## Availability
|
|
4
|
+
|
|
5
|
+
Features are encoded once per operation. Row presence or explicit any/all entity
|
|
6
|
+
aggregation provides the analysis masks. Repeated boolean availability signatures
|
|
7
|
+
are ranked by descending analysis-unit count with lexical signature ties. `max_signatures=50`
|
|
8
|
+
limits stored signatures, with omitted row mass reported. Identical masks form
|
|
9
|
+
families even for always-missing or always-present columns.
|
|
10
|
+
|
|
11
|
+
Pairs are enumerated in input-column combination order, bounded by `max_pairs=200`.
|
|
12
|
+
For A and B, presence Jaccard is both-present / either-present. If neither is ever
|
|
13
|
+
present, it is undefined, never perfect similarity. Agreement additionally includes
|
|
14
|
+
co-absence and is reported separately. A implies B has conditional presence
|
|
15
|
+
both-present / A-present, an exception rate, and B's baseline presence. No antecedent
|
|
16
|
+
support means no implication finding. Similarity and implication thresholds default
|
|
17
|
+
to 0.8 and 0.9. Mutually exclusive pairs require each field to have observed support
|
|
18
|
+
and no co-presence; exact families let users interpret exclusive field groups.
|
|
19
|
+
|
|
20
|
+
Context groups are joint combinations in first-observed order, bounded by
|
|
21
|
+
`max_contexts=32`. Per-feature entity counts operate over all eligible distinct
|
|
22
|
+
keys, without a display truncation affecting their denominator.
|
|
23
|
+
|
|
24
|
+
## Dependencies and candidate grain
|
|
25
|
+
|
|
26
|
+
Enumerate determinants by size then input-column order, up to `max_key_size=2` and
|
|
27
|
+
`max_candidates=100`. Report the combinatorial candidate space and tested count.
|
|
28
|
+
Every selected non-key feature is a target; all rows are evaluated. User-specified
|
|
29
|
+
contexts add up to `max_contexts=32` conditional populations plus the global population.
|
|
30
|
+
|
|
31
|
+
Modal accuracy = 1 − minimum rows needing target-value repair / evaluated rows.
|
|
32
|
+
Within each determinant group, choose its most frequent target; canonical code order
|
|
33
|
+
breaks ties. Nonmodal rows are representative exceptions. Also retain all violating
|
|
34
|
+
groups, affected rows, group violation rate, and repeated-group support. This
|
|
35
|
+
separates approximate mapping quality from coverage and singleton effects.
|
|
36
|
+
`min_accuracy=0.95` controls finding emission, not which tests are computed.
|
|
37
|
+
|
|
38
|
+
Candidates report groups, uniqueness, repeated groups/rows, complete-case exclusions,
|
|
39
|
+
and exactly determined targets. A unique row ID therefore remains distinguishable
|
|
40
|
+
from a useful repeated entity grouping. Conditional dependencies use their own
|
|
41
|
+
populations. The embedded foundation grain graph admits exact dependencies only,
|
|
42
|
+
collapses equivalent candidates, retains cross-cutting structure, and checks
|
|
43
|
+
population compatibility. Graph work is additionally bounded by the same candidate
|
|
44
|
+
set; pairwise candidate comparisons may dominate runtime.
|
|
45
|
+
|
|
46
|
+
## Census paths
|
|
47
|
+
|
|
48
|
+
`max_features=20` bounds the eligible input features (steering columns survive the
|
|
49
|
+
budget), and `max_pairs=200` bounds pair nesting inference. Constants are omitted
|
|
50
|
+
unless required. Equivalent pairs are retained as aliases. If fine determines coarse
|
|
51
|
+
but coarse does not determine fine, the soft nesting edge is coarse → fine.
|
|
52
|
+
|
|
53
|
+
A deterministic beam search extends ordered prefixes; it defaults to four dimensions,
|
|
54
|
+
beam width 12, 200 evaluated extensions, three returned paths, and display budget 40.
|
|
55
|
+
Input order defines feature coverage; lexical path order breaks score ties. If the
|
|
56
|
+
search budget ends, shorter valid paths may be returned. Coverage records returned
|
|
57
|
+
and requested depth; unsatisfied required columns can yield no recommendation.
|
|
58
|
+
|
|
59
|
+
For each path, count observed distinct prefixes at every depth. Base score is:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
sum(prefix_counts) / display_budget
|
|
63
|
+
+ sum(max(0, prefix_count - display_budget)) / display_budget
|
|
64
|
+
+ 2 * redundant_steps
|
|
65
|
+
+ 3 * equivalent_pair_steps
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
A redundant step leaves the prefix count unchanged. Lower scores rank first.
|
|
69
|
+
Structure and context objectives add eight per reversed supported nesting edge.
|
|
70
|
+
Compact uses the base score. Target adds twelve times summed within-prefix modal
|
|
71
|
+
impurity of the target values. Availability adds twelve times the same impurity for
|
|
72
|
+
full boolean signatures. Impurity is rows outside each group's mode / input rows.
|
|
73
|
+
The target itself is excluded from browsing candidates unless explicitly required.
|
|
74
|
+
|
|
75
|
+
`start_with` fixes the leading dimensions. `before` enforces acyclic precedence and
|
|
76
|
+
includes its referenced features; `exclude` removes candidates. Conflicting or
|
|
77
|
+
unfittable steering raises `ValueError`. Context requires `start_with`; target
|
|
78
|
+
requires `target`. These are observed-prefix heuristics, not an exhaustive optimizer
|
|
79
|
+
or an order-invariant joint-information score.
|
|
80
|
+
|
|
81
|
+
## Value patterns
|
|
82
|
+
|
|
83
|
+
String formats replace digit runs with `9` and ASCII letter runs with `A`; report
|
|
84
|
+
three-character prefixes and lengths. `max_patterns=10` bounds displayed counts.
|
|
85
|
+
Indexed-name families are explicitly name evidence, augmented by identical presence
|
|
86
|
+
when observed. Numeric summaries use finite values, observed minimum spacing, and
|
|
87
|
+
an allclose grid check. Offset and ratio checks require at least two finite paired
|
|
88
|
+
rows; ratios exclude zero denominators. Tolerances are rtol 1e-5 and atol 1e-8.
|
|
89
|
+
They are simple measured relationships, not fitted latent models. Context constancy
|
|
90
|
+
reports how many populated context groups have a single populated target value.
|
|
91
|
+
|
|
92
|
+
### Population-compatible grain views
|
|
93
|
+
|
|
94
|
+
Discovery builds one view per distinct, nonempty candidate complete-case mask.
|
|
95
|
+
Each view includes candidates whose supported rows contain that mask. The anchor
|
|
96
|
+
candidate guarantees their common complete-case population is exactly that mask;
|
|
97
|
+
the foundation still recomputes and checks every combined relationship on it.
|
|
98
|
+
Views are ordered by descending population, then candidate enumeration order.
|
|
99
|
+
`exact_grain` is the first view; `grain_views` retains every view, candidate IDs,
|
|
100
|
+
source positions and population accounting. No relation is composed across views.
|
|
101
|
+
All candidates remain in `candidates`, with view membership; unsupported candidates
|
|
102
|
+
also appear in `graph_selection.excluded` with `no_evaluated_support`. With
|
|
103
|
+
`dropna=False`, all candidates share the scoped population.
|
|
104
|
+
|
|
105
|
+
Presentation ranks supported repeated groupings before unique identifiers,
|
|
106
|
+
constants, and candidates without evaluated support. Within each class, more exact
|
|
107
|
+
determined targets and repeated rows rank first, with shorter keys and lexical
|
|
108
|
+
order breaking ties. A one-group candidate is a constant, including a singleton
|
|
109
|
+
population. Ranking changes presentation only; the complete evaluated set remains
|
|
110
|
+
available in enumeration order. Conditional findings carry typed context predicates
|
|
111
|
+
in `structure.context`; readable statements name the feature, value and scalar type.
|
|
112
|
+
Topology retains these predicates while suppressing measurements and selectors.
|
|
113
|
+
|
|
114
|
+
### Recommendation reasons and diversity
|
|
115
|
+
|
|
116
|
+
Every path stores `reasons` for observed prefix branching/overflow, supported
|
|
117
|
+
nesting, redundant dimensions and equivalent partitions, and availability
|
|
118
|
+
separation. Target searches also report target separation. Separation is the
|
|
119
|
+
within-prefix nonmodal row fraction at each depth; explanations quote these
|
|
120
|
+
measured values and the actual feature names. Numerical reasons stay in full
|
|
121
|
+
presentations; topology retains only structural path and alias findings.
|
|
122
|
+
|
|
123
|
+
At each search depth the beam keeps the best order per selected feature set,
|
|
124
|
+
reserving slots for different browsing choices. Future extensions depend on that
|
|
125
|
+
set, while accumulated prefix costs retain the ordering evidence. Returned paths
|
|
126
|
+
also collapse alias substitutions. Fewer than `n_paths` are returned when no
|
|
127
|
+
meaningfully different evaluated feature sets exist; reverse permutations are not
|
|
128
|
+
advertised as alternatives. Explicit start order and precedence remain binding.
|
|
129
|
+
|
|
130
|
+
### Connected feature evidence
|
|
131
|
+
|
|
132
|
+
The overview includes value-pattern discovery and a `feature_network` with
|
|
133
|
+
feature nodes, typed relationships, and connected components. Availability
|
|
134
|
+
identity/similarity/implication/exclusion, indexed names, equivalent value
|
|
135
|
+
partitions and exact/approximate dependencies remain separate relationship types.
|
|
136
|
+
A connected component means reachability through this evidence, not equivalence
|
|
137
|
+
or a composed functional dependency. Composite determinants and typed context
|
|
138
|
+
predicates remain explicit. Each relationship links to its section finding,
|
|
139
|
+
overview finding, counting unit and population reference. Pair-specific FD
|
|
140
|
+
populations stay in the referenced finding; the network does not replace the
|
|
141
|
+
compatible exact grain views. `overview.relationships(feature, kinds=[...])`
|
|
142
|
+
returns a dataframe for filtering and following evidence to `inspect`/`select`.
|
|
143
|
+
Saved HTML provides feature disclosures and links to the supporting findings.
|
|
144
|
+
Topology removes evidence pointers and populations while retaining relation types,
|
|
145
|
+
direction and context. Its relationship order is canonical.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
Fieldwork is a dataframe-in/result-out library. Runtime dependencies are pandas
|
|
4
|
+
and NumPy. Optional rendering tools used by release builds are development
|
|
5
|
+
requirements only. Python 3.11–3.14 are supported, with a committed universal uv lock.
|
|
6
|
+
|
|
7
|
+
## Follow one investigation
|
|
8
|
+
|
|
9
|
+
`explore(df)` calls path search, availability analysis, and a bounded single-key
|
|
10
|
+
dependency search, plus bounded value-pattern analysis. It returns an overview containing individual saved results and
|
|
11
|
+
a census preview. Its compact text view presents families, signatures, candidate
|
|
12
|
+
grains and recommended paths together. `explore(df, dimensions)` delegates to the original composition
|
|
13
|
+
API; explicit dimension order remains authoritative. Common context settings are
|
|
14
|
+
applied to a private normalized frame and source accounting is retained in all
|
|
15
|
+
derived scopes; incompatible search options are rejected. `discovery={...}` steers the
|
|
16
|
+
implicit path search; common `features`, `scope`, `missing`, and `table_id` parameters
|
|
17
|
+
also flow into overview evidence.
|
|
18
|
+
|
|
19
|
+
Discovery modules call `evidence.prepare` to identify the ordered dataset, apply a
|
|
20
|
+
scope, encode values, and compute native/sentinel availability without modifying
|
|
21
|
+
source values. Findings carry table-qualified feature references and bounded source
|
|
22
|
+
positions. Presentation consumes saved evidence, never the original dataframe.
|
|
23
|
+
|
|
24
|
+
## Module map
|
|
25
|
+
|
|
26
|
+
| Module | Responsibility |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| `_explore/encoding.py`, `_kernels.py` | Canonical scalar identity and counting |
|
|
29
|
+
| `_explore/census.py` | Independent levels, bounded ordered observed prefixes |
|
|
30
|
+
| `_explore/grain.py`, `grain_graph.py` | Exact FDs, scope compatibility, equivalence, DAG |
|
|
31
|
+
| `_explore/relations.py`, `roles.py` | Pair contexts/absence, joint counts, schema suggestions |
|
|
32
|
+
| `_explore/result.py`, `resolved.py` | Foundation result model and readable references |
|
|
33
|
+
| `_explore/visual_data.py`, `render.py`, `graphics.py` | Foundation projections and renderers |
|
|
34
|
+
| `evidence.py` | Discovery results, scopes, fingerprints, source inspection |
|
|
35
|
+
| `availability.py` | Presence signatures, families, implications, entity summaries |
|
|
36
|
+
| `discovery.py` | Supplied search bounds, exact/approximate/conditional FDs |
|
|
37
|
+
| `navigation.py` | Deterministic beam search and objective-specific prefix costs |
|
|
38
|
+
| `families.py` | Typed feature relationships linked to section findings |
|
|
39
|
+
| `patterns.py` | Populated strings/numbers, indexed families, context constancy |
|
|
40
|
+
| `workflow.py` | Composition, recipes, delivery comparisons |
|
|
41
|
+
| `presentation.py` | Discovery projections and dispatch to foundation renderers |
|
|
42
|
+
|
|
43
|
+
## Extension boundaries
|
|
44
|
+
|
|
45
|
+
New patterns should add named measurements, population accounting, an example
|
|
46
|
+
selection limit, and a presentation test for full and topology exports. Add an
|
|
47
|
+
operation to the explicit Recipe allowlist only when its arguments serialize to
|
|
48
|
+
JSON and can be safely reapplied to a new frame. No arbitrary Python evaluation is
|
|
49
|
+
used for recipes.
|
|
50
|
+
|
|
51
|
+
Related-table automatic discovery, adaptive branch-specific census orders, cached
|
|
52
|
+
sessions, and external discovery engines remain future extensions. Table identity
|
|
53
|
+
is present in new feature references; foundation references are local to the
|
|
54
|
+
source's `table_id`. The current public APIs analyze one dataframe at a time.
|
|
55
|
+
|
|
56
|
+
## Extraction
|
|
57
|
+
|
|
58
|
+
The foundation, regression tests, benchmark and grain example came from bea-tools
|
|
59
|
+
commit `4e4f1704cbca91a314652b0c99f86d8f4a2c4f90`; see `NOTICE`. Imports were
|
|
60
|
+
rewritten, accessors omitted, branding updated, and repeated interpretive disclaimers
|
|
61
|
+
removed. Quantitative evidence, populations, and topology disclosure behavior remain.
|
|
62
|
+
No DICOM, sampling dependencies, or compatibility shims were migrated.
|
|
63
|
+
|
|
64
|
+
Overview discovery also accepts `by`, `entity`, `unit`, and `entity_presence`.
|
|
65
|
+
Context columns flow to availability, dependency and value-pattern sections.
|
|
66
|
+
Entity settings apply to availability; census path scores and exact/approximate
|
|
67
|
+
value dependencies continue to count rows. Each linked finding identifies its unit.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Fieldwork
|
|
2
|
+
|
|
3
|
+
Explore unfamiliar data through patterns, source-row evidence, and reproducible
|
|
4
|
+
investigations. Fieldwork is a Python toolkit for researchers working with pandas
|
|
5
|
+
dataframes: find availability families, examine dependencies and candidate grains,
|
|
6
|
+
then choose a useful census path through the data.
|
|
7
|
+
|
|
8
|
+
**Alpha release.** Python 3.11–3.14; pandas and NumPy are the only required runtime
|
|
9
|
+
dependencies.
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install fieldwork
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import pandas as pd
|
|
19
|
+
import fieldwork as fw
|
|
20
|
+
|
|
21
|
+
df = pd.DataFrame({
|
|
22
|
+
"site": ["North", "North", "South", "South"],
|
|
23
|
+
"exam": [1, 1, 2, 2],
|
|
24
|
+
"image": [10, 11, 20, 21],
|
|
25
|
+
"report": ["ok", "ok", "ok", None],
|
|
26
|
+
})
|
|
27
|
+
overview = fw.explore(df)
|
|
28
|
+
availability = fw.missingness(df, entity="exam", min_implication=0.75)
|
|
29
|
+
paths = fw.suggest_paths(df, features=["site", "exam"])
|
|
30
|
+
tree = paths.best.census(df)
|
|
31
|
+
print(tree)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Use `result.to_frame()` for discovery tables and `result.inspect(df, finding_id,
|
|
35
|
+
exceptions=True)` for the saved example rows. Duplicate indexes are supported;
|
|
36
|
+
inspection checks the ordered source dataset. Use `result.select(df, finding_id)`
|
|
37
|
+
to recover the complete matching population as a `Scope`, then pass that scope to
|
|
38
|
+
`missingness` or `suggest_paths`. `paths.best.census(df)` preserves the selected
|
|
39
|
+
population and sentinel conventions. Choose `unit="entities"` with `entity=` for
|
|
40
|
+
equal entity weights, or keep the default `unit="rows"`. Browse connected feature
|
|
41
|
+
evidence with `overview.relationships("image")`. Save results with `to_dict()`, export
|
|
42
|
+
with `render_svg()` or `render_html()`, and reapply a `Recipe` to later deliveries.
|
|
43
|
+
|
|
44
|
+

|
|
45
|
+
|
|
46
|
+
The single-table workflow includes independent levels, contextual pair summaries,
|
|
47
|
+
joint counts and absence, exact grain graphs, bounded approximate dependency
|
|
48
|
+
discovery, five census-path objectives, availability signatures and entity summaries,
|
|
49
|
+
string and numeric patterns, scoped investigations, and delivery comparisons.
|
|
50
|
+
Topology-only exports retain structure while suppressing quantitative evidence.
|
|
51
|
+
Automatic related-table discovery is a later extension.
|
|
52
|
+
|
|
53
|
+
- [User documentation source](https://github.com/beatrice-b-m/fieldwork-docs)
|
|
54
|
+
- [Developer documentation](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/docs/index.md): architecture, contracts, algorithms, releases
|
|
55
|
+
- [Investigation journey and units](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/docs/investigation.md)
|
|
56
|
+
- [Executable investigation](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/examples/investigation.py) and [notebook](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/examples/investigation.ipynb)
|
|
57
|
+
- [MIT license](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/LICENSE) and [extraction provenance](https://github.com/beatrice-b-m/fieldwork/blob/v0.1.0/NOTICE)
|
|
58
|
+
|
|
59
|
+
For development: `uv sync --locked`, `uv run pytest`, and `uv build`. Documentation
|
|
60
|
+
images are generated from executable examples using the public renderers. Run
|
|
61
|
+
`uv run python scripts/generate_assets.py` after behavior or styling changes;
|
|
62
|
+
CI checks concurrence and release builds regenerate the assets.
|