diff --git a/benchmark/README.md b/benchmark/README.md new file mode 100644 index 0000000..d8a3bd2 --- /dev/null +++ b/benchmark/README.md @@ -0,0 +1,55 @@ +# Benchmarking DataTracer +This directory contains code for benchmarking the performance of `DataTracer` +on user-supplied datasets. The datasets for benchmarking can be found in the +`s3://tracer-data` bucket. + +

+ +

+ +Each benchmark - `primary`, `foreign`, and `column` - can be executed by +running the following command + +> datatracer-benchmark --csv /path/to/results.csv + +which will (optionally) generate a CSV file with the benchmark results. + +## Primary Key +Primary key detection is evaluated by: + + - Accuracy. The percent of tables where the primary key was correctly identified. + - Inference time. The amount of time to infer the primary key for all tables. + +We will use leave-one-out validation and report the test performance on each dataset +in the S3 bucket. + +## Foreign Key +Foreign key detection is evaluated by: + + - F1. + - Recall. + - Precision. + - Inference time. + +Note that this assumes that the foreign key primitive returns a set of foreign keys; +in other words, for models that return a score for each candidate foreign key, this +assumes that thresholding is done. + +We will use leave-one-out validation and report the test performance on each dataset +in the S3 bucket. + +## Column Map +Column map detection is evaluated by: + + - F1. + - Recall. + - Precision. + - Inference time. + +Note that this assumes that the column map primitive returns a set of columns that it +thinks contributed to the target derived column. Since each dataset can have multiple +derived columns, this will report a F1/recall/precision/time tuple for each derived +column in the dataset. + +We will use leave-one-out validation and report the test performance on each dataset +in the S3 bucket. diff --git a/benchmark/benchmark.gif b/benchmark/benchmark.gif new file mode 100644 index 0000000..578f0a9 Binary files /dev/null and b/benchmark/benchmark.gif differ diff --git a/benchmark/benchmark.py b/benchmark/benchmark.py new file mode 100644 index 0000000..2b869d9 --- /dev/null +++ b/benchmark/benchmark.py @@ -0,0 +1,345 @@ +import argparse +import os +from io import BytesIO +from time import time +from urllib.parse import urljoin +from urllib.request import urlopen +from zipfile import ZipFile + +import boto3 +import dask +import pandas as pd +from dask.diagnostics import ProgressBar + +from datatracer import DataTracer, load_datasets + +BUCKET_NAME = 'tracer-data' +DATA_URL = 'http://{}.s3.amazonaws.com/'.format(BUCKET_NAME) + + +def download(data_dir): + """Download benchmark datasets from S3. + + This downloads the benchmark datasets from S3 into the target folder in an + uncompressed format. It skips datasets that have already been downloaded. + + Args: + data_dir: The directory to download the datasets to. + + Returns: + A DataFrame describing the downloaded datasets. + + Raises: + NoCredentialsError: If AWS S3 credentials are not found. + """ + rows = [] + client = boto3.client('s3') + for dataset in client.list_objects(Bucket=BUCKET_NAME)['Contents']: + rows.append(dataset) + dataset_name = dataset['Key'].replace(".zip", "") + dataset_path = os.path.join(data_dir, dataset_name) + if os.path.exists(dataset_path): + dataset["Status"] = "Skipped" + print("Skipping %s" % dataset_name) + else: + dataset["Status"] = "Downloaded" + print("Downloading %s" % dataset_name) + with urlopen(urljoin(DATA_URL, dataset['Key'])) as fp: + with ZipFile(BytesIO(fp.read())) as zipfile: + zipfile.extractall(dataset_path) + return pd.DataFrame(rows) + + +@dask.delayed +def primary_key(solver, target, datasets): + """Benchmark the primary key solver on the target dataset. + + Args: + solver: The name of the primary key pipeline. + target: The name of the target dataset. + datases: A dictionary mapping dataset names to (metadata, tables) tuples. + + Returns: + A dictionary mapping metric names to values. + """ + datasets = datasets.copy() + metadata, tables = datasets.pop(target) + + tracer = DataTracer(solver) + tracer.fit(datasets) + + y_true = {} + for table in metadata.get_tables(): + if "primary_key" not in table: + continue # Skip tables without primary keys + if not isinstance(table["primary_key"], str): + continue # Skip tables with composite primary keys + y_true[table["name"]] = table["primary_key"] + + if len(y_true) == 0: + return {} # Skip dataset, no primary keys found. + + correct, total = 0, 0 + start = time() + y_pred = tracer.solve(tables) + end = time() + for table_name, primary_key in y_true.items(): + if y_pred.get(table_name) == primary_key: + correct += 1 + total += 1 + accuracy = correct / total + + return { + "accuracy": accuracy, + "inference_time": end - start + } + + +def benchmark_primary_key(data_dir, solver="datatracer.primary_key.basic"): + """Benchmark the primary key solver. + + This uses leave-one-out validation and evaluates the performance of the + solver on the specified datasets. + + Args: + data_dir: The directory containing the datasets. + solver: The name of the primary key pipeline. + + Returns: + A DataFrame containing the benchmark resuls. + """ + datasets = load_datasets(data_dir) + dataset_names = list(datasets.keys()) + datasets = dask.delayed(datasets) + dataset_to_metrics = {} + for dataset_name in dataset_names: + dataset_to_metrics[dataset_name] = primary_key( + solver=solver, target=dataset_name, datasets=datasets) + + with ProgressBar(): + results = dask.compute(dataset_to_metrics)[0] + for dataset_name, metrics in results.items(): + metrics["dataset"] = dataset_name + return pd.DataFrame(list(results.values())) + + +@dask.delayed +def foreign_key(solver, target, datasets): + """Benchmark the foreign key solver on the target dataset. + + Args: + solver: The name of the foreign key pipeline. + target: The name of the target dataset. + datasets: A dictionary mapping dataset names to (metadata, tables) tuples. + + Returns: + A dictionary mapping metric names to values. + """ + datasets = datasets.copy() + metadata, tables = datasets.pop(target) + + tracer = DataTracer(solver) + tracer.fit(datasets) + + y_true = set() + for fk in metadata.get_foreign_keys(): + if not isinstance(fk["field"], str): + continue # Skip composite foreign keys + y_true.add((fk["table"], fk["field"], fk["ref_table"], fk["ref_field"])) + + start = time() + fk_pred = tracer.solve(tables) + end = time() + + y_pred = set() + for fk in fk_pred: + y_pred.add((fk["table"], fk["field"], fk["ref_table"], fk["ref_field"])) + + if len(y_pred) == 0 or len(y_true) == 0 or \ + len(y_true.intersection(y_pred)) == 0: + return { + "precision": 0.0, + "recall": 0.0, + "f1": 0.0, + "inference_time": end - start + } + + precision = len(y_true.intersection(y_pred)) / len(y_pred) + recall = len(y_true.intersection(y_pred)) / len(y_true) + f1 = 2.0 * precision * recall / (precision + recall) + + return { + "precision": precision, + "recall": recall, + "f1": f1, + "inference_time": end - start + } + + +def benchmark_foreign_key(data_dir, solver="datatracer.foreign_key.standard"): + """Benchmark the foreign key solver. + + This uses leave-one-out validation and evaluates the performance of the + solver on the specified datasets. + + Args: + data_dir: The directory containing the datasets. + solver: The name of the foreign key pipeline. + + Returns: + A DataFrame containing the benchmark resuls. + """ + datasets = load_datasets(data_dir) + dataset_names = list(datasets.keys()) + datasets = dask.delayed(datasets) + dataset_to_metrics = {} + for dataset_name in dataset_names: + dataset_to_metrics[dataset_name] = foreign_key( + solver=solver, target=dataset_name, datasets=datasets) + + with ProgressBar(): + results = dask.compute(dataset_to_metrics)[0] + for dataset_name, metrics in results.items(): + metrics["dataset"] = dataset_name + return pd.DataFrame(list(results.values())) + + +@dask.delayed +def column_map(solver, target, datasets): + """Benchmark the column map solver on the target dataset. + + Args: + solver: The name of the column map pipeline. + target: The name of the target dataset. + datases: A dictionary mapping dataset names to (metadata, tables) tuples. + + Returns: + A list of dictionaries mapping metric names to values for each deived column. + """ + datasets = datasets.copy() + metadata, tables = datasets.pop(target) + if not metadata.data.get("constraints"): + return {} # Skip dataset, no constraints found. + + tracer = DataTracer(solver) + tracer.fit(datasets) + + list_of_metrics = [] + for constraint in metadata.data["constraints"]: + field = constraint["fields_under_consideration"][0] + related_fields = constraint["related_fields"] + + y_true = set() + for related_field in related_fields: + y_true.add((related_field["table"], related_field["field"])) + + start = time() + y_pred = tracer.solve(tables, target_table=field["table"], target_field=field["field"]) + y_pred = {field for field, score in y_pred.items() if score > 0.0} + end = time() + + precision = len(y_true.intersection(y_pred)) / len(y_pred) + recall = len(y_true.intersection(y_pred)) / len(y_true) + f1 = 2.0 * precision * recall / (precision + recall) + + list_of_metrics.append({ + "table": field["table"], + "field": field["field"], + "precision": precision, + "recall": recall, + "f1": f1, + "inference_time": end - start + }) + + return list_of_metrics + + +def benchmark_column_map(data_dir, solver="datatracer.column_map.basic"): + """Benchmark the column map solver. + + This uses leave-one-out validation and evaluates the performance of the + solver on the specified datasets. + + Args: + data_dir: The directory containing the datasets. + solver: The name of the column map pipeline. + + Returns: + A DataFrame containing the benchmark resuls. + """ + datasets = load_datasets(data_dir) + dataset_names = list(datasets.keys()) + datasets = dask.delayed(datasets) + dataset_to_metrics = {} + for dataset_name in dataset_names: + dataset_to_metrics[dataset_name] = column_map( + solver=solver, target=dataset_name, datasets=datasets) + + rows = [] + with ProgressBar(): + results = dask.compute(dataset_to_metrics)[0] + for dataset_name, list_of_metrics in results.items(): + for metrics in list_of_metrics: + metrics["dataset"] = dataset_name + rows.append(metrics) + return pd.DataFrame(rows) + + +def _get_parser(): + shared_args = argparse.ArgumentParser(add_help=False) + shared_args.add_argument('--data_dir', type=str, + default=os.path.expanduser("~/tracer_data"), required=False, + help='Path to the benchmark datasets.') + shared_args.add_argument('--csv', type=str, required=False, + help='Path to the CSV file where the report will be written.') + + parser = argparse.ArgumentParser( + prog='datatracer-benchmark', + description='DataTracer Benchmark CLI' + ) + + command = parser.add_subparsers(title='command', help='Command to execute') + parser.set_defaults(benchmark=None) + + subparser = command.add_parser( + 'download', + parents=[shared_args], + help='Download datasets from S3.' + ) + subparser.set_defaults(command=download) + + subparser = command.add_parser( + 'primary', + parents=[shared_args], + help='Primary key benchmark.' + ) + subparser.set_defaults(command=benchmark_primary_key) + + subparser = command.add_parser( + 'foreign', + parents=[shared_args], + help='Foreign key benchmark.' + ) + subparser.set_defaults(command=benchmark_foreign_key) + + subparser = command.add_parser( + 'column', + parents=[shared_args], + help='Column map benchmark.' + ) + subparser.set_defaults(command=benchmark_column_map) + + return parser + + +def main(): + parser = _get_parser() + args = parser.parse_args() + df = args.command(args.data_dir) + if args.csv: + df.to_csv(args.csv, index=False) + print(df) + + +if __name__ == "__main__": + main() diff --git a/datatracer/column_map/base.py b/datatracer/column_map/base.py index 435bbf3..a257ce5 100644 --- a/datatracer/column_map/base.py +++ b/datatracer/column_map/base.py @@ -4,15 +4,15 @@ class ColumnMapSolver: """Base Solver for the data lineage problem of column dependency.""" - def fit(self, list_of_databases): + def fit(self, dict_of_databases): """Fit this solver. Args: - list_of_databases (list): - List of tuples containing ``MetaData`` instnces and table dictinaries, - which contain table names as input and ``pandas.DataFrames`` as values. + dict_of_databases (dict): + Map from database names to tuples containing ``MetaData`` + instances and table dictionaries, which contain table names + as input and ``pandas.DataFrames`` as values. """ - pass def solve(self, tables, foreign_keys, target_table, target_field): """Find the fields which contributed to the target_field the most. diff --git a/datatracer/core.py b/datatracer/core.py index 58c2f7b..fcc4104 100644 --- a/datatracer/core.py +++ b/datatracer/core.py @@ -52,15 +52,13 @@ def fit(self, datasets): """Fit the pipeline to the given data. Args: - datasets (list or dict): - List or dict of tuples containing a MetaData instance and a dict - with the tables of the dataset loaded as DataFrames. + datasets (dict): + Dict mapping dataset names to tuples containing a MetaData + instance and a dict with the tables of the dataset loaded + as DataFrames. """ - if isinstance(datasets, dict): - datasets = list(datasets.values()) - self._mlpipeline = self._get_mlpipeline() - self._mlpipeline.fit(list_of_databases=datasets, tables={}) + self._mlpipeline.fit(dict_of_databases=datasets, tables={}) def solve(self, tables, **kwargs): """Solve the data lineage problem. diff --git a/datatracer/foreign_key/base.py b/datatracer/foreign_key/base.py index d31a96e..82a336e 100644 --- a/datatracer/foreign_key/base.py +++ b/datatracer/foreign_key/base.py @@ -3,15 +3,15 @@ class ForeignKeySolver(): - def fit(self, list_of_databases): + def fit(self, dict_of_databases): """Fit this solver. Args: - list_of_databases (list): - List of tuples containing ``MetaData`` instnces and table dictinaries, - which contain table names as input and ``pandas.DataFrames`` as values. + dict_of_databases (dict): + Map from database names to tuples containing ``MetaData`` + instances and table dictionaries, which contain table names + as input and ``pandas.DataFrames`` as values. """ - pass def solve(self, tables, primary_keys=None): """Solve the foreign key detection problem. diff --git a/datatracer/foreign_key/standard.py b/datatracer/foreign_key/standard.py index 1210d59..adb5690 100644 --- a/datatracer/foreign_key/standard.py +++ b/datatracer/foreign_key/standard.py @@ -50,20 +50,24 @@ def _feature_vector(self, parent_col, child_col): 1.0 if child_col.dtype == "object" else 0.0, ] - def fit(self, list_of_databases): + def fit(self, dict_of_databases): """Fit this solver. Args: - list_of_databases (list): - List of tuples containing ``MetaData`` instnces and table dictinaries, - which contain table names as input and ``pandas.DataFrames`` as values. + dict_of_databases (dict): + Map from database names to tuples containing ``MetaData`` + instances and table dictionaries, which contain table names + as input and ``pandas.DataFrames`` as values. """ X, y = [], [] - for metadata, tables in tqdm(list_of_databases, "extracting features"): - fks = set() - for fk in metadata.get_foreign_keys(): - if isinstance(fk["field"], str): - fks.add((fk["table"], fk["field"], fk["ref_table"], fk["ref_field"])) + iterator = tqdm(dict_of_databases.items()) + for database_name, (metadata, tables) in iterator: + iterator.set_description("Extracting features from %s" % database_name) + fks = metadata.get_foreign_keys() + fks = set([ + (fk["table"], fk["field"], fk["ref_table"], fk["ref_field"]) + for fk in fks + ]) for t1, t2 in permutations(tables.keys(), r=2): for c1 in tables[t1].columns: diff --git a/datatracer/jsons/primitives/datatracer.foreign_key.StandardForeignKeySolver.json b/datatracer/jsons/primitives/datatracer.foreign_key.StandardForeignKeySolver.json index 51d9518..87ae0a7 100644 --- a/datatracer/jsons/primitives/datatracer.foreign_key.StandardForeignKeySolver.json +++ b/datatracer/jsons/primitives/datatracer.foreign_key.StandardForeignKeySolver.json @@ -6,7 +6,7 @@ "method": "fit", "args": [ { - "name": "list_of_databases", + "name": "dict_of_databases", "type": "list" } ] diff --git a/datatracer/jsons/primitives/datatracer.primary_key.BasicPrimaryKeySolver.json b/datatracer/jsons/primitives/datatracer.primary_key.BasicPrimaryKeySolver.json index ff81725..c387f9f 100644 --- a/datatracer/jsons/primitives/datatracer.primary_key.BasicPrimaryKeySolver.json +++ b/datatracer/jsons/primitives/datatracer.primary_key.BasicPrimaryKeySolver.json @@ -6,7 +6,7 @@ "method": "fit", "args": [ { - "name": "list_of_databases", + "name": "dict_of_databases", "type": "list" } ] diff --git a/datatracer/primary_key/base.py b/datatracer/primary_key/base.py index f033f8f..df316d2 100644 --- a/datatracer/primary_key/base.py +++ b/datatracer/primary_key/base.py @@ -3,15 +3,15 @@ class PrimaryKeySolver(): - def fit(self, list_of_databases): + def fit(self, dict_of_databases): """Fit this solver. Args: - list_of_databases (list): - List of tuples containing ``MetaData`` instnces and table dictinaries, - which contain table names as input and ``pandas.DataFrames`` as values. + dict_of_databases (dict): + Map from database names to tuples containing ``MetaData`` + instances and table dictionaries, which contain table names + as input and ``pandas.DataFrames`` as values. """ - pass def solve(self, tables): """Solve the primary key detection problem. diff --git a/datatracer/primary_key/basic.py b/datatracer/primary_key/basic.py index ea46aaf..b0c657a 100644 --- a/datatracer/primary_key/basic.py +++ b/datatracer/primary_key/basic.py @@ -2,6 +2,7 @@ import numpy as np from sklearn.ensemble import RandomForestClassifier +from tqdm import tqdm from datatracer.primary_key.base import PrimaryKeySolver @@ -27,16 +28,19 @@ def _feature_vector(self, table, column_name): 1.0 if column.dtype == "object" else 0.0, ] - def fit(self, list_of_databases): + def fit(self, dict_of_databases): """Fit this solver. Args: - list_of_databases (list): - List of tuples containing ``MetaData`` instnces and table dictinaries, - which contain table names as input and ``pandas.DataFrames`` as values. + dict_of_databases (dict): + Map from database names to tuples containing ``MetaData`` + instances and table dictionaries, which contain table names + as input and ``pandas.DataFrames`` as values. """ X, y = [], [] - for metadata, tables in list_of_databases: + iterator = tqdm(dict_of_databases.items()) + for database_name, (metadata, tables) in iterator: + iterator.set_description("Extracting features from %s" % database_name) for table in metadata.get_tables(): if "primary_key" not in table: continue diff --git a/setup.py b/setup.py index 691ae45..9385f01 100644 --- a/setup.py +++ b/setup.py @@ -12,6 +12,7 @@ history = history_file.read() install_requires = [ + 'boto3>=1.13,<2', 'pandas>=0.23.4,<0.25', 'scikit-learn>=0.20.0,<0.21', 'numpy<1.17,>=1.15.2', @@ -20,7 +21,7 @@ 'falcon>=2.0.0,<3', 'hug>=2.6.1,<3', 'pyyaml>=5.3.1,<6', - 'tqdm>=4.46.1,<5', + 'tqdm>=4,<5', ] setup_requires = [ @@ -62,6 +63,10 @@ # Advanced testing 'coverage>=4.5.1,<6', 'tox>=2.9.1,<4', + + # benchmarking + 'dask>=2.15,<3', + 'distributed>=2.15,<3', ] setup( @@ -84,7 +89,8 @@ 'pipelines=datatracer:MLBLOCKS_PIPELINES' ], 'console_scripts': [ - 'datatracer=datatracer.__main__:main' + 'datatracer=datatracer.__main__:main', + 'datatracer-benchmark=benchmark.benchmark:main' ], }, extras_require={ @@ -100,11 +106,11 @@ keywords='datatracer data-tracer Data Tracer', name='datatracer', packages=find_packages(include=['datatracer', 'datatracer.*']), - python_requires='>=3.5,<3.8', + python_requires='>=3.5', setup_requires=setup_requires, test_suite='tests', tests_require=tests_require, - url='https://github.com/HDI-Project/DataTracer', + url='https://github.com/data-dev/DataTracer', version='0.0.6.dev0', zip_safe=False, ) diff --git a/tutorials/DataTracer Quickstart.ipynb b/tutorials/DataTracer Quickstart.ipynb index 26a1840..fb5037f 100644 --- a/tutorials/DataTracer Quickstart.ipynb +++ b/tutorials/DataTracer Quickstart.ipynb @@ -33,7 +33,9 @@ { "cell_type": "code", "execution_count": 1, - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [ { "name": "stdout", @@ -83,7 +85,7 @@ { "data": { "text/plain": [ - "dict_keys(['mutagenesis', 'Chess', 'classicmodels', 'university', 'Bupa', 'trains', 'SameGen', 'NBA', 'pubs'])" + "dict_keys(['posts', 'NBA', 'university', 'pubs', 'Chess', 'classicmodels', 'mutagenesis', 'Bupa', 'trains', 'SameGen'])" ] }, "execution_count": 3, @@ -395,15 +397,6 @@ "the dataset that we just explored, using the rest of the datasets as our training data." ] }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "training_datasets = list(datasets.values())" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -423,7 +416,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -435,7 +428,7 @@ " 'datatracer.primary_key.basic']" ] }, - "execution_count": 11, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -467,7 +460,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -486,11 +479,19 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Extracting features from SameGen: 100%|██████████| 9/9 [00:00<00:00, 145.54it/s]\n" + ] + } + ], "source": [ - "dtr.fit(training_datasets)" + "dtr.fit(datasets)" ] }, { @@ -503,7 +504,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -519,7 +520,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -535,7 +536,7 @@ " 'products': 'productCode'}" ] }, - "execution_count": 15, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -557,14 +558,14 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "extracting features: 100%|██████████| 8/8 [00:00<00:00, 19.07it/s]\n" + "Extracting features from SameGen: 100%|██████████| 9/9 [00:01<00:00, 8.46it/s] \n" ] } ], @@ -572,7 +573,7 @@ "from datatracer import DataTracer\n", "\n", "dtr = DataTracer('datatracer.foreign_key.standard')\n", - "dtr.fit(training_datasets)\n", + "dtr.fit(datasets)\n", "foreign_keys = dtr.solve(tables)" ] }, @@ -586,7 +587,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "metadata": {}, "outputs": [ { @@ -618,7 +619,7 @@ " 'ref_field': 'officeCode'}]" ] }, - "execution_count": 17, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -649,14 +650,14 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "extracting features: 100%|██████████| 8/8 [00:00<00:00, 18.00it/s]\n" + "Extracting features from SameGen: 100%|██████████| 9/9 [00:01<00:00, 8.10it/s] \n" ] } ], @@ -664,7 +665,7 @@ "from datatracer import DataTracer\n", "\n", "dtr = DataTracer('datatracer.column_map.basic')\n", - "dtr.fit(training_datasets)\n", + "dtr.fit(datasets)\n", "column_map = dtr.solve(\n", " tables,\n", " target_table='orderdetails',\n", @@ -682,18 +683,18 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{('orderdetails', 'orderNumber'): 0.3854540808399761,\n", - " ('orderdetails', 'priceEach'): 0.43313416486749556,\n", - " ('orderdetails', 'orderLineNumber'): 0.18141175429252837}" + "{('orderdetails', 'orderNumber'): 0.3831749750863929,\n", + " ('orderdetails', 'priceEach'): 0.4397562935716433,\n", + " ('orderdetails', 'orderLineNumber'): 0.1770687313419638}" ] }, - "execution_count": 19, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -719,7 +720,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ @@ -735,7 +736,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 20, "metadata": {}, "outputs": [], "source": [ @@ -748,19 +749,19 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{('orderdetails', 'orderNumber'): 0.00022471076974350297,\n", - " ('orderdetails', 'priceEach'): 0.00015177917546761553,\n", - " ('orderdetails', 'orderLineNumber'): 0.00014925873321498974,\n", - " ('orderdetails', 'quantityOrdered_x2'): 0.9994742513215739}" + "{('orderdetails', 'orderNumber'): 0.00019290156505023432,\n", + " ('orderdetails', 'priceEach'): 0.00014624354192835704,\n", + " ('orderdetails', 'orderLineNumber'): 9.105599151739842e-05,\n", + " ('orderdetails', 'quantityOrdered_x2'): 0.9995697989015039}" ] }, - "execution_count": 22, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } diff --git a/tutorials/Introducing DataTracer.ipynb b/tutorials/Introducing DataTracer.ipynb index 9555b19..2598548 100644 --- a/tutorials/Introducing DataTracer.ipynb +++ b/tutorials/Introducing DataTracer.ipynb @@ -338,12 +338,12 @@ { "data": { "text/plain": [ - "{('users', 'id'): 0.0,\n", - " ('users', 'birthyear'): 0.9999980097244106,\n", - " ('users', 'height'): 5.180246597799326e-07,\n", + "{('users', 'id'): 5.046102180418218e-07,\n", + " ('users', 'birthyear'): 0.9999978458447893,\n", + " ('users', 'height'): 0.0,\n", " ('users', 'nb_posts'): 0.0,\n", - " ('posts', 'uid'): 6.40004263964262e-07,\n", - " ('posts', 'id'): 2.8343526963126543e-07}" + " ('posts', 'uid'): 0.0,\n", + " ('posts', 'id'): 7.225373384175289e-07}" ] }, "execution_count": 7, @@ -383,8 +383,8 @@ " ('users', 'age'): 0.0,\n", " ('users', 'birthyear'): 0.0,\n", " ('users', 'height'): 0.0,\n", - " ('posts', 'uid'): 0.4785790560832036,\n", - " ('posts', 'id'): 0.5136442420361127}" + " ('posts', 'uid'): 0.5263822157542845,\n", + " ('posts', 'id'): 0.46999603701224835}" ] }, "execution_count": 8,