aspect-data 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.
- aspect/__init__.py +5 -0
- aspect/checkpoint_utils.py +95 -0
- aspect/cli_module/__init__.py +0 -0
- aspect/cli_module/cli.py +129 -0
- aspect/cli_module/featurize.py +187 -0
- aspect/cli_module/io.py +52 -0
- aspect/data.py +577 -0
- aspect/io.py +189 -0
- aspect/package_data.py +35 -0
- aspect/transform/__init__.py +9 -0
- aspect/transform/base.py +182 -0
- aspect/transform/deep_functions.py +143 -0
- aspect/transform/functions.py +333 -0
- aspect/transform/registry.py +17 -0
- aspect/typing.py +31 -0
- aspect_data-0.0.1.dist-info/METADATA +917 -0
- aspect_data-0.0.1.dist-info/RECORD +21 -0
- aspect_data-0.0.1.dist-info/WHEEL +5 -0
- aspect_data-0.0.1.dist-info/entry_points.txt +2 -0
- aspect_data-0.0.1.dist-info/licenses/LICENSE +21 -0
- aspect_data-0.0.1.dist-info/top_level.txt +1 -0
aspect/__init__.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
""""Utilities for loading and saving checkpoints."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Union
|
|
4
|
+
from tempfile import TemporaryDirectory
|
|
5
|
+
import os
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
from carabiner import print_err
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from datasets import Dataset, IterableDataset
|
|
12
|
+
else:
|
|
13
|
+
Dataset, IterableDataset = Any, Any
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _load_json(checkpoint: str, filename: str) -> Dict[str, Any]:
|
|
17
|
+
with open(os.path.join(checkpoint, filename), "r") as f:
|
|
18
|
+
obj = json.load(f)
|
|
19
|
+
return obj
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def save_json(obj, filename: str) -> None:
|
|
23
|
+
with open(filename, "w") as f:
|
|
24
|
+
try:
|
|
25
|
+
json.dump(obj, f, sort_keys=True, indent=4)
|
|
26
|
+
except TypeError as e:
|
|
27
|
+
print_err(f"{obj=}")
|
|
28
|
+
raise e
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _load_hf_dataset(checkpoint, filename) -> Union[Dataset, IterableDataset]:
|
|
33
|
+
from datasets import load_from_disk
|
|
34
|
+
return load_from_disk(os.path.join(checkpoint, filename))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
FILE_LOADING_CALLBACKS = {
|
|
38
|
+
"json": _load_json,
|
|
39
|
+
"hf-dataset": _load_hf_dataset,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def load_checkpoint_file(
|
|
44
|
+
checkpoint: str,
|
|
45
|
+
filename: str,
|
|
46
|
+
*args,
|
|
47
|
+
callback: Union[str, Callable] = "json",
|
|
48
|
+
none_on_error: bool = False,
|
|
49
|
+
cache_dir: Optional[str] = None,
|
|
50
|
+
**kwargs
|
|
51
|
+
) -> Union[Any, None]:
|
|
52
|
+
from huggingface_hub import snapshot_download
|
|
53
|
+
|
|
54
|
+
obj = None
|
|
55
|
+
if isinstance(callback, str):
|
|
56
|
+
try:
|
|
57
|
+
callback = FILE_LOADING_CALLBACKS[callback.casefold()]
|
|
58
|
+
except KeyError:
|
|
59
|
+
raise ValueError(
|
|
60
|
+
"""
|
|
61
|
+
File loading callback must be callable or name.
|
|
62
|
+
"""
|
|
63
|
+
)
|
|
64
|
+
if os.path.exists(checkpoint):
|
|
65
|
+
obj = callback(checkpoint, filename)
|
|
66
|
+
elif checkpoint.startswith("hf://"):
|
|
67
|
+
checkpoint = checkpoint.split("hf://")[-1]
|
|
68
|
+
if filename.endswith(".hf"):
|
|
69
|
+
filename_pattern = [filename + '/*.arrow', filename + '/*.json']
|
|
70
|
+
else:
|
|
71
|
+
filename_pattern = filename
|
|
72
|
+
with TemporaryDirectory() as tmpdirname:
|
|
73
|
+
try:
|
|
74
|
+
print_err(f"Looking up: {checkpoint} :: {filename}")
|
|
75
|
+
snapshot_download(
|
|
76
|
+
repo_id=checkpoint,
|
|
77
|
+
allow_patterns=filename_pattern,
|
|
78
|
+
local_dir=tmpdirname,
|
|
79
|
+
cache_dir=cache_dir,
|
|
80
|
+
*args, **kwargs
|
|
81
|
+
)
|
|
82
|
+
except Exception as e:
|
|
83
|
+
print_err(e)
|
|
84
|
+
if none_on_error:
|
|
85
|
+
return None
|
|
86
|
+
else:
|
|
87
|
+
raise e
|
|
88
|
+
else:
|
|
89
|
+
obj = callback(tmpdirname, filename)
|
|
90
|
+
if obj is not None:
|
|
91
|
+
return obj
|
|
92
|
+
else:
|
|
93
|
+
raise AttributeError(
|
|
94
|
+
f"Could not load anything from {checkpoint=}, {filename=} with {callback=}."
|
|
95
|
+
)
|
|
File without changes
|
aspect/cli_module/cli.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Command-line interface for aspect."""
|
|
2
|
+
|
|
3
|
+
from argparse import FileType
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from carabiner.cliutils import CLIOption, CLICommand, CLIApp
|
|
8
|
+
|
|
9
|
+
from .. import app_name, __version__
|
|
10
|
+
from .featurize import _featurize, _serialize
|
|
11
|
+
|
|
12
|
+
def main() -> None:
|
|
13
|
+
|
|
14
|
+
input_file = CLIOption(
|
|
15
|
+
'input_file',
|
|
16
|
+
type=FileType('r'),
|
|
17
|
+
default=sys.stdin,
|
|
18
|
+
nargs='?',
|
|
19
|
+
help='Input file. Default: STDIN',
|
|
20
|
+
)
|
|
21
|
+
input_filename = CLIOption(
|
|
22
|
+
'input_file',
|
|
23
|
+
type=str,
|
|
24
|
+
help='Input file.',
|
|
25
|
+
)
|
|
26
|
+
feature_cols = CLIOption(
|
|
27
|
+
'--features', '-x',
|
|
28
|
+
type=str,
|
|
29
|
+
nargs='*',
|
|
30
|
+
default=None,
|
|
31
|
+
help='Featurization spec: column_name[:transform[(kwargs)]:...][@output_name]...',
|
|
32
|
+
)
|
|
33
|
+
cache = CLIOption(
|
|
34
|
+
'--cache',
|
|
35
|
+
type=str,
|
|
36
|
+
default=None,
|
|
37
|
+
help='Where to cache data.',
|
|
38
|
+
)
|
|
39
|
+
_config = CLIOption(
|
|
40
|
+
'--config',
|
|
41
|
+
type=str,
|
|
42
|
+
default=None,
|
|
43
|
+
help='Load pipeline from this config or checkpoint. Default: do not use, process from scratch.',
|
|
44
|
+
)
|
|
45
|
+
_checkpoint = CLIOption(
|
|
46
|
+
'--checkpoint',
|
|
47
|
+
type=str,
|
|
48
|
+
default=None,
|
|
49
|
+
help='Save data at this checkpoint. Default: do not save checkpoint.',
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
output_name = CLIOption(
|
|
53
|
+
'--output', '-o',
|
|
54
|
+
type=str,
|
|
55
|
+
required=True,
|
|
56
|
+
help='Output filename.',
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# slice dataset
|
|
60
|
+
slice_start = CLIOption(
|
|
61
|
+
'--start',
|
|
62
|
+
type=int,
|
|
63
|
+
default=0,
|
|
64
|
+
help='First row of dataset to process.',
|
|
65
|
+
)
|
|
66
|
+
slice_end = CLIOption(
|
|
67
|
+
'--end',
|
|
68
|
+
type=int,
|
|
69
|
+
default=None,
|
|
70
|
+
help='Last row of dataset to process. Default: end of dataset.',
|
|
71
|
+
)
|
|
72
|
+
extra_cols = CLIOption(
|
|
73
|
+
'--extras',
|
|
74
|
+
type=str,
|
|
75
|
+
nargs="*",
|
|
76
|
+
default=None,
|
|
77
|
+
help='Extra columns to retain without transformation.',
|
|
78
|
+
)
|
|
79
|
+
random_seed = CLIOption(
|
|
80
|
+
'--seed', '-e',
|
|
81
|
+
type=int,
|
|
82
|
+
default=None,
|
|
83
|
+
help='Random seed. Default: determininstic.',
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
serialize = CLICommand(
|
|
87
|
+
"serialize",
|
|
88
|
+
description="Checkpoint a feature spec.",
|
|
89
|
+
options=[
|
|
90
|
+
output_name,
|
|
91
|
+
feature_cols,
|
|
92
|
+
extra_cols,
|
|
93
|
+
],
|
|
94
|
+
main=_serialize,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
featurize = CLICommand(
|
|
98
|
+
"featurize",
|
|
99
|
+
description="Featurize a table.",
|
|
100
|
+
options=[
|
|
101
|
+
input_filename,
|
|
102
|
+
output_name,
|
|
103
|
+
feature_cols,
|
|
104
|
+
extra_cols,
|
|
105
|
+
slice_start,
|
|
106
|
+
slice_end,
|
|
107
|
+
_config,
|
|
108
|
+
_checkpoint,
|
|
109
|
+
random_seed,
|
|
110
|
+
cache,
|
|
111
|
+
],
|
|
112
|
+
main=_featurize,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
app = CLIApp(
|
|
116
|
+
app_name,
|
|
117
|
+
description="Serializable featurization pipelines for ML/AI on chemistry, taxonomy, and general tabular data.",
|
|
118
|
+
version=__version__,
|
|
119
|
+
commands=[
|
|
120
|
+
serialize,
|
|
121
|
+
featurize,
|
|
122
|
+
],
|
|
123
|
+
)
|
|
124
|
+
app.run()
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
if __name__ == '__main__':
|
|
129
|
+
main()
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
from typing import Optional, Iterable, Union
|
|
2
|
+
|
|
3
|
+
from argparse import Namespace
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
from carabiner import print_err
|
|
7
|
+
from carabiner.cliutils import clicommand
|
|
8
|
+
|
|
9
|
+
from .io import _resolve_and_slice_data, _save_dataset
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _parse_feature_spec(x: str, i: int):
|
|
13
|
+
if ":" in x:
|
|
14
|
+
input_col, _, remainder = x.partition(":")
|
|
15
|
+
transforms, _, output_col = remainder.rpartition("@")
|
|
16
|
+
else:
|
|
17
|
+
input_col, _, output_col = x.partition("@")
|
|
18
|
+
transforms, remainder = "", ""
|
|
19
|
+
# print(x, " | ".join([input_col, remainder, transforms, output_col]))
|
|
20
|
+
if not output_col:
|
|
21
|
+
if not transforms:
|
|
22
|
+
output_col = input_col
|
|
23
|
+
extra = True
|
|
24
|
+
else:
|
|
25
|
+
output_col = f"col_{i:03d}"
|
|
26
|
+
else:
|
|
27
|
+
extra = False
|
|
28
|
+
transforms = transforms.split(":")
|
|
29
|
+
if transforms == [""]:
|
|
30
|
+
return extra, {output_col: (input_col, {"name": "identity"})}
|
|
31
|
+
parsed_transforms = []
|
|
32
|
+
for transform in transforms:
|
|
33
|
+
name, _, kwargs = transform.partition("(")
|
|
34
|
+
if not kwargs:
|
|
35
|
+
parsed_transforms.append({"name": name})
|
|
36
|
+
continue
|
|
37
|
+
kwargs = kwargs.removesuffix(")").split(",")
|
|
38
|
+
key_vals = [k.strip().split("=") for k in kwargs]
|
|
39
|
+
wrong_number = [k for k in key_vals if len(k) != 2]
|
|
40
|
+
if wrong_number:
|
|
41
|
+
raise ValueError(f"Badly formatted kwargs: {kwargs=}, parsed to {key_vals}")
|
|
42
|
+
kwargs = {
|
|
43
|
+
key.strip(): value.strip()
|
|
44
|
+
for key, value in key_vals
|
|
45
|
+
}
|
|
46
|
+
parsed_kwargs = {}
|
|
47
|
+
for k, v in kwargs.items():
|
|
48
|
+
if v.isdigit():
|
|
49
|
+
parsed_v = int(v)
|
|
50
|
+
elif v.casefold() == "true":
|
|
51
|
+
parsed_v = True
|
|
52
|
+
elif v.casefold() == "false":
|
|
53
|
+
parsed_v = False
|
|
54
|
+
else:
|
|
55
|
+
try:
|
|
56
|
+
parsed_v = float(v)
|
|
57
|
+
except:
|
|
58
|
+
parsed_v = v
|
|
59
|
+
parsed_kwargs[k] = parsed_v
|
|
60
|
+
parsed_transforms.append({
|
|
61
|
+
"name": name,
|
|
62
|
+
} | parsed_kwargs)
|
|
63
|
+
return extra, {output_col: (input_col, tuple(parsed_transforms))}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def parse_feature_specs(x: Union[str, Iterable[str]]):
|
|
67
|
+
if isinstance(x, str):
|
|
68
|
+
x = [x]
|
|
69
|
+
column_transforms = []
|
|
70
|
+
extra_cols = []
|
|
71
|
+
for i, _x in enumerate(x):
|
|
72
|
+
extra, column_transform = _parse_feature_spec(_x, i)
|
|
73
|
+
if extra:
|
|
74
|
+
extra_cols.append(_x)
|
|
75
|
+
else:
|
|
76
|
+
column_transforms.append(column_transform)
|
|
77
|
+
return extra_cols, {k: v for d in column_transforms for k, v in d.items()}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _common_feature_spec_routine(x: Union[str, Iterable[str]], args_extras=None):
|
|
81
|
+
extras, column_transforms = parse_feature_specs(x)
|
|
82
|
+
extras += (args_extras or [])
|
|
83
|
+
extras = sorted(set(extras))
|
|
84
|
+
|
|
85
|
+
print_err(
|
|
86
|
+
f"""
|
|
87
|
+
Parsed the feature spec:
|
|
88
|
+
- Column transforms: {column_transforms}
|
|
89
|
+
- Other columns to retain: {extras}
|
|
90
|
+
|
|
91
|
+
"""
|
|
92
|
+
)
|
|
93
|
+
return extras, column_transforms
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _validate_checkpoints(chk1, path):
|
|
97
|
+
from ..data import DataPipeline
|
|
98
|
+
print_err(f"[INFO] Validating checkpoint at {path}...", end="")
|
|
99
|
+
pipeline2 = DataPipeline().load_checkpoint(path)
|
|
100
|
+
if chk1 != pipeline2:
|
|
101
|
+
attr1, attr2 = vars(chk1), vars(pipeline2)
|
|
102
|
+
wrong_attributes = {
|
|
103
|
+
k: (attr1[k], "!=", v)
|
|
104
|
+
for k, v in attr2.items()
|
|
105
|
+
if v != attr1[k]
|
|
106
|
+
}
|
|
107
|
+
raise IOError(
|
|
108
|
+
f"Checkpoint at {path} does not recreate the same object. "
|
|
109
|
+
f"These attributes were different: {wrong_attributes}"
|
|
110
|
+
)
|
|
111
|
+
print_err(" all good!")
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@clicommand("Serializing featurization with the following parameters")
|
|
116
|
+
def _serialize(args: Namespace) -> None:
|
|
117
|
+
|
|
118
|
+
from ..data import DataPipeline
|
|
119
|
+
|
|
120
|
+
output = args.output
|
|
121
|
+
out_dir = os.path.dirname(output)
|
|
122
|
+
base = os.path.basename(output)
|
|
123
|
+
if len(out_dir) > 0:
|
|
124
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
125
|
+
|
|
126
|
+
extras, column_transforms = _common_feature_spec_routine(
|
|
127
|
+
args.features,
|
|
128
|
+
args.extras,
|
|
129
|
+
)
|
|
130
|
+
pipeline = DataPipeline(
|
|
131
|
+
column_transforms=column_transforms,
|
|
132
|
+
columns_to_keep=extras,
|
|
133
|
+
)
|
|
134
|
+
pipeline.save_checkpoint(output)
|
|
135
|
+
_validate_checkpoints(pipeline, output)
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@clicommand("Featurizing data with the following parameters")
|
|
140
|
+
def _featurize(args: Namespace) -> None:
|
|
141
|
+
|
|
142
|
+
from ..data import DataPipeline
|
|
143
|
+
|
|
144
|
+
if args.features:
|
|
145
|
+
extras, column_transforms = _common_feature_spec_routine(args.features)
|
|
146
|
+
pipeline = DataPipeline(
|
|
147
|
+
column_transforms=column_transforms,
|
|
148
|
+
columns_to_keep=extras,
|
|
149
|
+
cache_dir=args.cache,
|
|
150
|
+
)
|
|
151
|
+
elif args.config:
|
|
152
|
+
extras = args.extras or []
|
|
153
|
+
pipeline = DataPipeline(
|
|
154
|
+
cache_dir=args.cache,
|
|
155
|
+
).load_checkpoint(args.config)
|
|
156
|
+
else:
|
|
157
|
+
raise ValueError(f"One of --features or --config must be provided.")
|
|
158
|
+
|
|
159
|
+
ds = _resolve_and_slice_data(
|
|
160
|
+
args.input_file,
|
|
161
|
+
start=args.start,
|
|
162
|
+
end=args.end,
|
|
163
|
+
cache_dir=args.cache,
|
|
164
|
+
)
|
|
165
|
+
ds = pipeline(
|
|
166
|
+
ds,
|
|
167
|
+
keep_extra_columns=extras,
|
|
168
|
+
drop_unused_columns=True,
|
|
169
|
+
)
|
|
170
|
+
if args.checkpoint:
|
|
171
|
+
output = args.checkpoint
|
|
172
|
+
out_dir = os.path.dirname(output)
|
|
173
|
+
base = os.path.basename(output)
|
|
174
|
+
pipeline.save_checkpoint(output)
|
|
175
|
+
_validate_checkpoints(pipeline, output)
|
|
176
|
+
|
|
177
|
+
if args.output:
|
|
178
|
+
output = args.output
|
|
179
|
+
out_dir = os.path.dirname(output)
|
|
180
|
+
base = os.path.basename(output)
|
|
181
|
+
if len(out_dir) > 0:
|
|
182
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
183
|
+
_save_dataset(
|
|
184
|
+
ds,
|
|
185
|
+
output,
|
|
186
|
+
)
|
|
187
|
+
return None
|
aspect/cli_module/io.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from typing import Mapping, Optional, Union
|
|
2
|
+
|
|
3
|
+
from carabiner import print_err
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _resolve_and_slice_data(
|
|
7
|
+
data: str,
|
|
8
|
+
start: Optional[int] = None,
|
|
9
|
+
end: Optional[int] = None,
|
|
10
|
+
batch_size: int = 1024,
|
|
11
|
+
cache_dir: str = None
|
|
12
|
+
):
|
|
13
|
+
from ..io import AutoDataset
|
|
14
|
+
|
|
15
|
+
candidates_ds = AutoDataset.load(data, cache_dir)._dataset
|
|
16
|
+
nrows = candidates_ds.num_rows
|
|
17
|
+
skip = start or 0
|
|
18
|
+
take = (end or nrows) - skip
|
|
19
|
+
if (take - skip) < nrows:
|
|
20
|
+
print_err(f"[INFO] Reading dataset from row {skip} to row {take + skip} / {nrows}.")
|
|
21
|
+
return candidates_ds.skip(skip).take(take)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _save_dataset(
|
|
25
|
+
dataset,
|
|
26
|
+
output: str
|
|
27
|
+
) -> None:
|
|
28
|
+
print_err("[INFO] Saving dataset:\n" + str(dataset) + "\n" + f"at {output} as", end=" ")
|
|
29
|
+
if output.removesuffix(".gz").endswith((".csv", ".tsv", ".txt")):
|
|
30
|
+
print_err("CSV.")
|
|
31
|
+
dataset.to_csv(
|
|
32
|
+
output,
|
|
33
|
+
sep="," if output.removesuffix(".gz").endswith(".csv") else "\t",
|
|
34
|
+
compression='gzip' if output.endswith(".gz") else None,
|
|
35
|
+
)
|
|
36
|
+
elif output.endswith(".json"):
|
|
37
|
+
print_err("JSON.")
|
|
38
|
+
dataset.to_json(output)
|
|
39
|
+
elif output.endswith(".parquet"):
|
|
40
|
+
print_err("Parquet.")
|
|
41
|
+
dataset.to_parquet(output)
|
|
42
|
+
elif output.endswith(".sql"):
|
|
43
|
+
print_err("SQL.")
|
|
44
|
+
dataset.to_sql(output)
|
|
45
|
+
elif output.endswith(".hf"):
|
|
46
|
+
print_err("Hugging Face dataset.")
|
|
47
|
+
dataset.save_to_disk(output)
|
|
48
|
+
else:
|
|
49
|
+
print_err("Hugging Face dataset.")
|
|
50
|
+
dataset.save_to_disk(output + ".hf")
|
|
51
|
+
print_err(f"WARNING: Unsure what format to save as for filename {output}. Defaulted to Hugging Face dataset.")
|
|
52
|
+
return None
|