Changed scripts to not infer field purposes from CSV values

Note there's a bit of subtlety here, field _types_ are still infered,
but the intention of the fields, i.e. if the field contains data vs
row name/other properties, must be unambiguous in the scripts.

There is still a _tiny_ bit of inference. For most scripts only one
of --by or --fields is strictly needed, since this makes the purpose of
the other fields unambiguous.

The reason for this change is so the scripts are a bit more reliable,
but also because this simplifies the data parsing/inference a bit.

Oh, and this also changes field inference to use the csv.DictReader's
fieldnames field instead of only inspecting the returned dicts. This
should also save a bit of O(n) overhead when parsing CSV files.
This commit is contained in:
Christopher Haster
2023-11-04 15:24:18 -05:00
parent 2be3ff57c5
commit d0a6ef0c89
12 changed files with 187 additions and 200 deletions
+48 -41
View File
@@ -46,11 +46,15 @@ def dat(x):
def collect(csv_paths, renames=[], defines=[]): def collect(csv_paths, renames=[], defines=[]):
# collect results from CSV files # collect results from CSV files
fields = []
results = [] results = []
for path in csv_paths: for path in csv_paths:
try: try:
with openio(path) as f: with openio(path) as f:
reader = csv.DictReader(f, restval='') reader = csv.DictReader(f, restval='')
fields.extend(
k for k in reader.fieldnames
if k not in fields)
for r in reader: for r in reader:
# apply any renames # apply any renames
if renames: if renames:
@@ -69,15 +73,15 @@ def collect(csv_paths, renames=[], defines=[]):
except FileNotFoundError: except FileNotFoundError:
pass pass
return results return fields, results
def main(csv_paths, output, *, def main(csv_paths, output, *,
amor=False, amor=False,
per=False, per=False,
by=None,
meas=None, meas=None,
iter=None, iter=None,
size=None, size=None,
by=None,
fields=None, fields=None,
defines=[]): defines=[]):
# default to amortizing and per-byte results if size is present # default to amortizing and per-byte results if size is present
@@ -95,40 +99,43 @@ def main(csv_paths, output, *,
if fields is not None: if fields is not None:
fields = [k for k, _ in fields] fields = [k for k, _ in fields]
if by is None and fields is None:
print("error: needs --by or --fields to figure out fields")
sys.exit(-1)
# collect results from csv files # collect results from csv files
results = collect(csv_paths, renames, defines) fields_, results = collect(csv_paths, renames, defines)
# if fields not specified, try to guess from data # if by not specified, guess it's anything not in
if fields is None: # iter/size/fields/renames/defines
fields = co.OrderedDict()
for r in results:
for k, v in r.items():
if k not in (by or []) and k != iter and v.strip():
try:
dat(v)
fields[k] = True
except ValueError:
fields[k] = False
fields = list(k for k,v in fields.items() if v)
# if by not specified, guess it's anything not in iter/fields and not a
# source of a rename
if by is None: if by is None:
by = co.OrderedDict() by = [
for r in results: k for k in fields_
# also ignore None keys, these are introduced by csv.DictReader if k != iter
# when header + row mismatch and k != size
by.update((k, True) for k in r.keys() and k not in (fields or [])
if k is not None and not any(k == old_k for _, old_k in renames)
and not any(k == k_ for k_, _ in defines)]
# if fields not specified, guess it's anything not in
# by/iter/size/renames/defines
if fields is None:
fields = [
k for k in fields_
if k not in (by or [])
and k != iter and k != iter
and k not in fields and k != size
and not any(k == old_k for _, old_k in renames)) and not any(k == old_k for _, old_k in renames)
by = list(by.keys()) and not any(k == k_ for k_, _ in defines)]
# add meas to by if it isn't already present
if meas is not None and meas not in by:
by.append(meas)
# convert iter/fields to ints/floats # convert iter/fields to ints/floats
for r in results: for r in results:
for k in {iter} | set(fields) | ({size} if size is not None else {}): for k in it.chain([iter], [size] if size is not None else [], fields):
if k in r: if k in r and isinstance(r[k], str):
r[k] = dat(r[k]) if r[k].strip() else 0 r[k] = dat(r[k]) if r[k].strip() else 0
# organize by 'by' values # organize by 'by' values
@@ -141,7 +148,7 @@ def main(csv_paths, output, *,
# for each key compute the amortized results # for each key compute the amortized results
amors = [] amors = []
for key, rs in results.items(): for key, rs in results.items():
# keep a running sum for each fied # keep a running sum for each field
sums = {f: 0 for f in fields} sums = {f: 0 for f in fields}
size_ = 0 size_ = 0
for j, (i, r) in enumerate(sorted( for j, (i, r) in enumerate(sorted(
@@ -171,7 +178,7 @@ def main(csv_paths, output, *,
# write results to CSV # write results to CSV
with openio(output, 'w') as f: with openio(output, 'w') as f:
writer = csv.DictWriter(f, writer = csv.DictWriter(f,
by + ([meas] if meas not in by else []) + [iter] + fields) by + [iter] + ([size] if size is not None else []) + fields)
writer.writeheader() writer.writeheader()
for r in amors: for r in amors:
writer.writerow(r) writer.writerow(r)
@@ -199,6 +206,16 @@ if __name__ == "__main__":
'--per', '--per',
action='store_true', action='store_true',
help="Compute per-byte results.") help="Compute per-byte results.")
parser.add_argument(
'-b', '--by',
action='append',
type=lambda x: (
lambda k, vs=None: (
k.strip(),
tuple(v.strip() for v in vs.split(','))
if vs is not None else ())
)(*x.split('=', 1)),
help="Group by this field. Can rename fields with new_name=old_name.")
parser.add_argument( parser.add_argument(
'-m', '--meas', '-m', '--meas',
help="Optional name of measurement name field. If provided, the name " help="Optional name of measurement name field. If provided, the name "
@@ -210,16 +227,6 @@ if __name__ == "__main__":
parser.add_argument( parser.add_argument(
'-n', '--size', '-n', '--size',
help="Optional name of size field.") help="Optional name of size field.")
parser.add_argument(
'-b', '--by',
action='append',
type=lambda x: (
lambda k, vs=None: (
k.strip(),
tuple(v.strip() for v in vs.split(','))
if vs is not None else ())
)(*x.split('=', 1)),
help="Group by this field. Can rename fields with new_name=old_name.")
parser.add_argument( parser.add_argument(
'-f', '--field', '-f', '--field',
dest='fields', dest='fields',
+38 -35
View File
@@ -46,11 +46,15 @@ def dat(x):
def collect(csv_paths, renames=[], defines=[]): def collect(csv_paths, renames=[], defines=[]):
# collect results from CSV files # collect results from CSV files
fields = []
results = [] results = []
for path in csv_paths: for path in csv_paths:
try: try:
with openio(path) as f: with openio(path) as f:
reader = csv.DictReader(f, restval='') reader = csv.DictReader(f, restval='')
fields.extend(
k for k in reader.fieldnames
if k not in fields)
for r in reader: for r in reader:
# apply any renames # apply any renames
if renames: if renames:
@@ -69,7 +73,7 @@ def collect(csv_paths, renames=[], defines=[]):
except FileNotFoundError: except FileNotFoundError:
pass pass
return results return fields, results
def main(csv_paths, output, *, def main(csv_paths, output, *,
sum=False, sum=False,
@@ -81,8 +85,8 @@ def main(csv_paths, output, *,
stddev=False, stddev=False,
gmean=False, gmean=False,
gstddev=False, gstddev=False,
meas=None,
by=None, by=None,
meas=None,
seeds=None, seeds=None,
fields=None, fields=None,
defines=[]): defines=[]):
@@ -113,40 +117,41 @@ def main(csv_paths, output, *,
if fields is not None: if fields is not None:
fields = [k for k, _ in fields] fields = [k for k, _ in fields]
if by is None and fields is None:
print("error: needs --by or --fields to figure out fields")
sys.exit(-1)
# collect results from csv files # collect results from csv files
results = collect(csv_paths, renames, defines) fields_, results = collect(csv_paths, renames, defines)
# if fields not specified, try to guess from data # if by not specified, guess it's anything not in
if fields is None: # seeds/fields/renames/defines
fields = co.OrderedDict()
for r in results:
for k, v in r.items():
if k not in (by or []) and k not in (seeds or []) and v.strip():
try:
dat(v)
fields[k] = True
except ValueError:
fields[k] = False
fields = list(k for k,v in fields.items() if v)
# if by not specified, guess it's anything not in seeds/fields and not a
# source of a rename
if by is None: if by is None:
by = co.OrderedDict() by = [
for r in results: k for k in fields_
# also ignore None keys, these are introduced by csv.DictReader if k not in (seeds or [])
# when header + row mismatch and k not in (fields or [])
by.update((k, True) for k in r.keys() and not any(k == old_k for _, old_k in renames)
if k is not None and not any(k == k_ for k_, _ in defines)]
# if fields not specified, guess it's anything not in
# by/seeds/renames/defines
if fields is None:
fields = [
k for k in fields_
if k not in (by or [])
and k not in (seeds or []) and k not in (seeds or [])
and k not in fields and not any(k == old_k for _, old_k in renames)
and not any(k == old_k for _, old_k in renames)) and not any(k == k_ for k_, _ in defines)]
by = list(by.keys())
# add meas to by if it isn't already present
if meas is not None and meas not in by:
by.append(meas)
# convert fields to ints/floats # convert fields to ints/floats
for r in results: for r in results:
for k in fields: for k in fields:
if k in r: if k in r and isinstance(r[k], str):
r[k] = dat(r[k]) if r[k].strip() else 0 r[k] = dat(r[k]) if r[k].strip() else 0
# organize by 'by' values # organize by 'by' values
@@ -162,7 +167,6 @@ def main(csv_paths, output, *,
vs = {f: [] for f in fields} vs = {f: [] for f in fields}
meas__ = None meas__ = None
for r in rs: for r in rs:
if all(k in r and r[k] == v for k, v in zip(by, key)):
for f in fields: for f in fields:
vs[f].append(r.get(f, 0)) vs[f].append(r.get(f, 0))
if meas is not None and meas in r: if meas is not None and meas in r:
@@ -197,8 +201,7 @@ def main(csv_paths, output, *,
# write results to CSVS # write results to CSVS
with openio(output, 'w') as f: with openio(output, 'w') as f:
writer = csv.DictWriter(f, writer = csv.DictWriter(f, by + fields)
by + ([meas] if meas not in by else []) + fields)
writer.writeheader() writer.writeheader()
for r in avgs: for r in avgs:
writer.writerow(r) writer.writerow(r)
@@ -254,10 +257,6 @@ if __name__ == "__main__":
'--gstddev', '--gstddev',
action='store_true', action='store_true',
help="Compute the geometric standard deviation.") help="Compute the geometric standard deviation.")
parser.add_argument(
'-m', '--meas',
help="Optional name of measurement name field. If provided, the name "
"will be modified with +amor or +per.")
parser.add_argument( parser.add_argument(
'-b', '--by', '-b', '--by',
action='append', action='append',
@@ -268,6 +267,10 @@ if __name__ == "__main__":
if vs is not None else ()) if vs is not None else ())
)(*x.split('=', 1)), )(*x.split('=', 1)),
help="Group by this field. Can rename fields with new_name=old_name.") help="Group by this field. Can rename fields with new_name=old_name.")
parser.add_argument(
'-m', '--meas',
help="Optional name of measurement name field. If provided, the name "
"will be modified with +amor or +per.")
parser.add_argument( parser.add_argument(
'-s', '--seed', '-s', '--seed',
dest='seeds', dest='seeds',
+1 -4
View File
@@ -315,10 +315,7 @@ def collect(obj_paths, *,
return results return results
def fold(Result, results, *, def fold(Result, results, by=None, defines=[]):
by=None,
defines=[],
**_):
if by is None: if by is None:
by = Result._by by = Result._by
+1 -4
View File
@@ -297,10 +297,7 @@ def collect(gcda_paths, *,
return results return results
def fold(Result, results, *, def fold(Result, results, by=None, defines=[]):
by=None,
defines=[],
**_):
if by is None: if by is None:
by = Result._by by = Result._by
+1 -4
View File
@@ -315,10 +315,7 @@ def collect(obj_paths, *,
return results return results
def fold(Result, results, *, def fold(Result, results, by=None, defines=[]):
by=None,
defines=[],
**_):
if by is None: if by is None:
by = Result._by by = Result._by
+1 -4
View File
@@ -627,10 +627,7 @@ def collect(perf_paths, *,
return results return results
def fold(Result, results, *, def fold(Result, results, by=None, defines=[]):
by=None,
defines=[],
**_):
if by is None: if by is None:
by = Result._by by = Result._by
+1 -4
View File
@@ -593,10 +593,7 @@ def collect(obj_path, trace_paths, *,
return results return results
def fold(Result, results, *, def fold(Result, results, by=None, defines=[]):
by=None,
defines=[],
**_):
if by is None: if by is None:
by = Result._by by = Result._by
+29 -25
View File
@@ -445,11 +445,15 @@ class Plot:
def collect(csv_paths, renames=[], defines=[]): def collect(csv_paths, renames=[], defines=[]):
# collect results from CSV files # collect results from CSV files
fields = []
results = [] results = []
for path in csv_paths: for path in csv_paths:
try: try:
with openio(path) as f: with openio(path) as f:
reader = csv.DictReader(f, restval='') reader = csv.DictReader(f, restval='')
fields.extend(
k for k in reader.fieldnames
if k not in fields)
for r in reader: for r in reader:
# apply any renames # apply any renames
if renames: if renames:
@@ -468,7 +472,7 @@ def collect(csv_paths, renames=[], defines=[]):
except FileNotFoundError: except FileNotFoundError:
pass pass
return results return fields, results
def fold(results, by=None, x=None, y=None, defines=[]): def fold(results, by=None, x=None, y=None, defines=[]):
# filter by matching defines # filter by matching defines
@@ -479,29 +483,16 @@ def fold(results, by=None, x=None, y=None, defines=[]):
results_.append(r) results_.append(r)
results = results_ results = results_
# if y not specified, try to guess from data
if not y:
y = co.OrderedDict()
for r in results:
for k, v in r.items():
if (not by or k not in by) and v.strip():
try:
dat(v)
y[k] = True
except ValueError:
y[k] = False
y = list(k for k,v in y.items() if v)
if by: if by:
# find all 'by' values # find all 'by' values
ks = set() keys = set()
for r in results: for r in results:
ks.add(tuple(r.get(k, '') for k in by)) keys.add(tuple(r.get(k, '') for k in by))
ks = sorted(ks) keys = sorted(keys)
# collect all datasets # collect all datasets
datasets = co.OrderedDict() datasets = co.OrderedDict()
for ks_ in (ks if by else [()]): for key in (keys if by else [()]):
for x_ in (x if x else [None]): for x_ in (x if x else [None]):
for y_ in y: for y_ in y:
# organize by 'by', x, and y # organize by 'by', x, and y
@@ -511,7 +502,7 @@ def fold(results, by=None, x=None, y=None, defines=[]):
# filter by 'by' # filter by 'by'
if by and not all( if by and not all(
k in r and r[k] == v k in r and r[k] == v
for k, v in zip(by, ks_)): for k, v in zip(by, key)):
continue continue
# find xs # find xs
@@ -542,8 +533,8 @@ def fold(results, by=None, x=None, y=None, defines=[]):
# hide x/y if there is only one field # hide x/y if there is only one field
k_x = x_ if len(x or []) > 1 else '' k_x = x_ if len(x or []) > 1 else ''
k_y = y_ if len(y or []) > 1 or (not ks_ and not k_x) else '' k_y = y_ if len(y or []) > 1 or (not key and not k_x) else ''
datasets[ks_ + (k_x, k_y)] = dataset datasets[key + (k_x, k_y)] = dataset
return datasets return datasets
@@ -904,13 +895,17 @@ def main(csv_paths, *,
all_defines = sorted(all_defines.items()) all_defines = sorted(all_defines.items())
# separate out renames # separate out renames
renames = list(it.chain.from_iterable( all_renames = list(it.chain.from_iterable(
((k, v) for v in vs) ((k, v) for v in vs)
for k, vs in it.chain(all_by, all_x, all_y))) for k, vs in it.chain(all_by, all_x, all_y)))
all_by = [k for k, _ in all_by] all_by = [k for k, _ in all_by]
all_x = [k for k, _ in all_x] all_x = [k for k, _ in all_x]
all_y = [k for k, _ in all_y] all_y = [k for k, _ in all_y]
if not all_by and not all_y:
print("error: needs --by or -y to figure out fields")
sys.exit(-1)
# create a grid of subplots # create a grid of subplots
grid = Grid.fromargs(**subplot, subplots=subplots) grid = Grid.fromargs(**subplot, subplots=subplots)
@@ -994,10 +989,19 @@ def main(csv_paths, *,
f.writeln = writeln f.writeln = writeln
# first collect results from CSV files # first collect results from CSV files
results = collect(csv_paths, renames, all_defines) fields_, results = collect(csv_paths, all_renames, all_defines)
# if y not specified, guess it's anything not in by/defines/x/renames
all_y_ = all_y
if not all_y:
all_y_ = [
k for k in fields_
if k not in all_by
and not any(k == k_ for k_, _ in all_defines)
and not any(k == old_k for _, old_k in all_renames)]
# then extract the requested datasets # then extract the requested datasets
datasets_ = fold(results, all_by, all_x, all_y) datasets_ = fold(results, all_by, all_x, all_y_)
# figure out colors/chars here so that subplot defines # figure out colors/chars here so that subplot defines
# don't change them later, that'd be bad # don't change them later, that'd be bad
@@ -1143,7 +1147,7 @@ def main(csv_paths, *,
# data can be constrained by subplot-specific defines, # data can be constrained by subplot-specific defines,
# so re-extract for each plot # so re-extract for each plot
subdatasets = fold(results, all_by, all_x, all_y, define_) subdatasets = fold(results, all_by, all_x, all_y_, define_)
# filter by subplot x/y # filter by subplot x/y
subdatasets = co.OrderedDict([(name, dataset) subdatasets = co.OrderedDict([(name, dataset)
+26 -23
View File
@@ -191,11 +191,15 @@ def dat(x):
def collect(csv_paths, renames=[], defines=[]): def collect(csv_paths, renames=[], defines=[]):
# collect results from CSV files # collect results from CSV files
fields = []
results = [] results = []
for path in csv_paths: for path in csv_paths:
try: try:
with openio(path) as f: with openio(path) as f:
reader = csv.DictReader(f, restval='') reader = csv.DictReader(f, restval='')
fields.extend(
k for k in reader.fieldnames
if k not in fields)
for r in reader: for r in reader:
# apply any renames # apply any renames
if renames: if renames:
@@ -214,7 +218,7 @@ def collect(csv_paths, renames=[], defines=[]):
except FileNotFoundError: except FileNotFoundError:
pass pass
return results return fields, results
def fold(results, by=None, x=None, y=None, defines=[]): def fold(results, by=None, x=None, y=None, defines=[]):
# filter by matching defines # filter by matching defines
@@ -225,29 +229,16 @@ def fold(results, by=None, x=None, y=None, defines=[]):
results_.append(r) results_.append(r)
results = results_ results = results_
# if y not specified, try to guess from data
if not y:
y = co.OrderedDict()
for r in results:
for k, v in r.items():
if (not by or k not in by) and v.strip():
try:
dat(v)
y[k] = True
except ValueError:
y[k] = False
y = list(k for k,v in y.items() if v)
if by: if by:
# find all 'by' values # find all 'by' values
ks = set() keys = set()
for r in results: for r in results:
ks.add(tuple(r.get(k, '') for k in by)) keys.add(tuple(r.get(k, '') for k in by))
ks = sorted(ks) keys = sorted(keys)
# collect all datasets # collect all datasets
datasets = co.OrderedDict() datasets = co.OrderedDict()
for ks_ in (ks if by else [()]): for key in (keys if by else [()]):
for x_ in (x if x else [None]): for x_ in (x if x else [None]):
for y_ in y: for y_ in y:
# organize by 'by', x, and y # organize by 'by', x, and y
@@ -257,7 +248,7 @@ def fold(results, by=None, x=None, y=None, defines=[]):
# filter by 'by' # filter by 'by'
if by and not all( if by and not all(
k in r and r[k] == v k in r and r[k] == v
for k, v in zip(by, ks_)): for k, v in zip(by, key)):
continue continue
# find xs # find xs
@@ -288,8 +279,8 @@ def fold(results, by=None, x=None, y=None, defines=[]):
# hide x/y if there is only one field # hide x/y if there is only one field
k_x = x_ if len(x or []) > 1 else '' k_x = x_ if len(x or []) > 1 else ''
k_y = y_ if len(y or []) > 1 or (not ks_ and not k_x) else '' k_y = y_ if len(y or []) > 1 or (not key and not k_x) else ''
datasets[ks_ + (k_x, k_y)] = dataset datasets[key + (k_x, k_y)] = dataset
return datasets return datasets
@@ -746,15 +737,27 @@ def main(csv_paths, output, *,
all_defines = sorted(all_defines.items()) all_defines = sorted(all_defines.items())
# separate out renames # separate out renames
renames = list(it.chain.from_iterable( all_renames = list(it.chain.from_iterable(
((k, v) for v in vs) ((k, v) for v in vs)
for k, vs in it.chain(all_by, all_x, all_y))) for k, vs in it.chain(all_by, all_x, all_y)))
all_by = [k for k, _ in all_by] all_by = [k for k, _ in all_by]
all_x = [k for k, _ in all_x] all_x = [k for k, _ in all_x]
all_y = [k for k, _ in all_y] all_y = [k for k, _ in all_y]
if not all_by and not all_y:
print("error: needs --by or -y to figure out fields")
sys.exit(-1)
# first collect results from CSV files # first collect results from CSV files
results = collect(csv_paths, renames, all_defines) fields_, results = collect(csv_paths, all_renames, all_defines)
# if y not specified, guess it's anything not in by/defines/x/renames
if not all_y:
all_y = [
k for k in fields_
if k not in all_by
and not any(k == k_ for k_, _ in all_defines)
and not any(k == old_k for _, old_k in all_renames)]
# then extract the requested datasets # then extract the requested datasets
datasets_ = fold(results, all_by, all_x, all_y) datasets_ = fold(results, all_by, all_x, all_y)
+1 -4
View File
@@ -273,10 +273,7 @@ def collect(ci_paths, *,
return results return results
def fold(Result, results, *, def fold(Result, results, by=None, defines=[]):
by=None,
defines=[],
**_):
if by is None: if by is None:
by = Result._by by = Result._by
+1 -4
View File
@@ -264,10 +264,7 @@ def collect(obj_paths, *,
return results return results
def fold(Result, results, *, def fold(Result, results, by=None, defines=[]):
by=None,
defines=[],
**_):
if by is None: if by is None:
by = Result._by by = Result._by
+33 -42
View File
@@ -251,11 +251,15 @@ def openio(path, mode='r', buffering=-1):
def collect(csv_paths, renames=[], defines=[]): def collect(csv_paths, renames=[], defines=[]):
# collect results from CSV files # collect results from CSV files
fields = []
results = [] results = []
for path in csv_paths: for path in csv_paths:
try: try:
with openio(path) as f: with openio(path) as f:
reader = csv.DictReader(f, restval='') reader = csv.DictReader(f, restval='')
fields.extend(
k for k in reader.fieldnames
if k not in fields)
for r in reader: for r in reader:
# apply any renames # apply any renames
if renames: if renames:
@@ -274,49 +278,34 @@ def collect(csv_paths, renames=[], defines=[]):
except FileNotFoundError: except FileNotFoundError:
pass pass
return results return fields, results
def infer(results, *, def infer(fields_, results,
by=None, by=None,
fields=None, fields=None,
types={}, types={},
ops={}, ops={},
renames=[], renames=[],
**_): defines=[]):
# if fields not specified, try to guess from data # if by not specified, guess it's anything not in fields/renames/defines
if fields is None:
fields = co.OrderedDict()
for r in results:
for k, v in r.items():
if (by is None or k not in by) and v.strip():
types_ = []
for t in fields.get(k, TYPES.values()):
try:
t(v)
types_.append(t)
except ValueError:
pass
fields[k] = types_
fields = list(k for k, v in fields.items() if v)
# deduplicate fields
fields = list(co.OrderedDict.fromkeys(fields).keys())
# if by not specified, guess it's anything not in fields and not a
# source of a rename
if by is None: if by is None:
by = co.OrderedDict() by = [
for r in results: k for k in fields_
# also ignore None keys, these are introduced by csv.DictReader if k not in (fields or [])
# when header + row mismatch and not any(k == old_k for _, old_k in renames)
by.update((k, True) for k in r.keys() and not any(k == k_ for k_, _ in defines)]
if k is not None
and k not in fields
and not any(k == old_k for _, old_k in renames))
by = list(by.keys())
# deduplicate fields # if fields not specified, guess it's anything not in by/renames/defines
if fields is None:
fields = [
k for k in fields_
if k not in (by or [])
and not any(k == old_k for _, old_k in renames)
and not any(k == k_ for k_, _ in defines)]
# deduplicate by/fields
by = list(co.OrderedDict.fromkeys(by).keys()) by = list(co.OrderedDict.fromkeys(by).keys())
fields = list(co.OrderedDict.fromkeys(fields).keys())
# find best type for all fields # find best type for all fields
types_ = {} types_ = {}
@@ -381,10 +370,7 @@ def infer(results, *,
}) })
def fold(Result, results, *, def fold(Result, results, by=None, defines=[]):
by=None,
defines=[],
**_):
if by is None: if by is None:
by = Result._by by = Result._by
@@ -634,16 +620,21 @@ def main(csv_paths, *,
ops_[new_k] = ops[old_k] ops_[new_k] = ops[old_k]
ops.update(ops_) ops.update(ops_)
if by is None and fields is None:
print("error: needs --by or --fields to figure out fields")
sys.exit(-1)
# find CSV files # find CSV files
results = collect(csv_paths, renames=renames, defines=defines) fields_, results = collect(csv_paths, renames, defines)
# homogenize # homogenize
Result = infer(results, Result = infer(fields_, results,
by=by, by=by,
fields=fields, fields=fields,
types=types, types=types,
ops=ops, ops=ops,
renames=renames) renames=renames,
defines=defines)
results_ = [] results_ = []
for r in results: for r in results:
if not any(k in r and r[k].strip() if not any(k in r and r[k].strip()
@@ -682,7 +673,7 @@ def main(csv_paths, *,
# find previous results? # find previous results?
if args.get('diff'): if args.get('diff'):
diff_results = collect([args['diff']], renames=renames, defines=defines) _, diff_results = collect([args['diff']], renames, defines)
diff_results_ = [] diff_results_ = []
for r in diff_results: for r in diff_results:
if not any(k in r and r[k].strip() if not any(k in r and r[k].strip()