From e1a71c29d9179c9d583ea9c0997c65f151f0c341 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Mon, 4 Mar 2024 23:55:12 +0000 Subject: [PATCH 01/33] add pipeline from config --- pipeline_lib/__init__.py | 4 + pipeline_lib/core/data_container.py | 5 - pipeline_lib/core/pipeline.py | 98 +++++++++++++++---- pipeline_lib/core/steps/augment.py | 5 +- pipeline_lib/core/steps/base.py | 6 +- pipeline_lib/core/steps/calculate_features.py | 5 +- pipeline_lib/core/steps/calculate_metrics.py | 5 +- pipeline_lib/core/steps/calculate_reports.py | 5 +- pipeline_lib/core/steps/encode.py | 5 +- .../core/steps/explainer_dashboard.py | 5 +- pipeline_lib/core/steps/fit_encoders.py | 5 +- pipeline_lib/core/steps/fit_model.py | 5 +- pipeline_lib/core/steps/input_scaling.py | 5 +- pipeline_lib/core/steps/predict.py | 5 +- pipeline_lib/core/steps/tabular_split.py | 7 +- pipeline_lib/core/steps/target_scaling.py | 10 +- .../tabular/xgboost/fit_model.py | 9 +- 17 files changed, 145 insertions(+), 44 deletions(-) diff --git a/pipeline_lib/__init__.py b/pipeline_lib/__init__.py index e69de29..a6c537f 100644 --- a/pipeline_lib/__init__.py +++ b/pipeline_lib/__init__.py @@ -0,0 +1,4 @@ +from .core.pipeline import Pipeline + +Pipeline.auto_register_steps_from_package("pipeline_lib.core.steps") +Pipeline.auto_register_steps_from_package("pipeline_lib.implementation.tabular.xgboost") diff --git a/pipeline_lib/core/data_container.py b/pipeline_lib/core/data_container.py index e07c240..5e0c3ed 100644 --- a/pipeline_lib/core/data_container.py +++ b/pipeline_lib/core/data_container.py @@ -21,17 +21,12 @@ class DataContainer: A dictionary to store data items. """ - GENERATE_CONFIGS = "generate_configs" - CLEAN_CONFIGS = "clean_configs" - SPLIT_CONFIGS = "split_configs" - TARGET_SCALING_CONFIGS = "target_scaling_configs" RAW = "raw" CLEAN = "clean" TRAIN = "train" VALIDATION = "validation" TEST = "test" MODEL = "model" - MODEL_CONFIGS = "model_configs" MODEL_INPUT = "model_input" MODEL_OUTPUT = "model_output" METRICS = "metrics" diff --git a/pipeline_lib/core/pipeline.py b/pipeline_lib/core/pipeline.py index 3e6358c..29922d3 100644 --- a/pipeline_lib/core/pipeline.py +++ b/pipeline_lib/core/pipeline.py @@ -1,45 +1,107 @@ from __future__ import annotations +import importlib +import json import logging -from abc import ABC, abstractmethod +import pkgutil from typing import Optional from pipeline_lib.core.data_container import DataContainer from pipeline_lib.core.steps import PipelineStep -class Pipeline(ABC): +class Pipeline: """Base class for pipelines.""" + _step_registry = {} + def __init__(self, initial_data: Optional[DataContainer] = None): - self.steps = self.define_steps() + self.steps = [] if not all(isinstance(step, PipelineStep) for step in self.steps): raise TypeError("All steps must be instances of PipelineStep") self.initial_data = initial_data + self.init_logger() + + def init_logger(self) -> None: + """Initialize the logger.""" + self.logger = logging.getLogger(self.__class__.__name__) + self.logger.debug(f"{self.__class__.__name__} initialized") + + @classmethod + def register_step(cls, step_class): + """Register a step class using its class name.""" + step_name = step_class.__name__ + if not issubclass(step_class, PipelineStep): + raise ValueError(f"{step_class} must be a subclass of PipelineStep") + cls._step_registry[step_name] = step_class + + @classmethod + def get_step_class(cls, step_name): + """Retrieve a step class by name.""" + if step_name in cls._step_registry: + return cls._step_registry[step_name] + else: + raise ValueError(f"Step class '{step_name}' not found in registry.") + + def add_steps(self, steps: list[PipelineStep]): + """Add steps to the pipeline.""" + self.steps.extend(steps) + + @classmethod + def auto_register_steps_from_package(cls, package_name): + """ + Automatically registers all step classes found within a specified package. + """ + package = importlib.import_module(package_name) + prefix = package.__name__ + "." + for importer, modname, ispkg in pkgutil.walk_packages(package.__path__, prefix): + module = importlib.import_module(modname) + for name in dir(module): + attribute = getattr(module, name) + if ( + isinstance(attribute, type) + and issubclass(attribute, PipelineStep) + and attribute is not PipelineStep + ): + cls.register_step(attribute) - def run(self, data: Optional[DataContainer] = None) -> DataContainer: + def run(self) -> DataContainer: """Run the pipeline on the given data.""" - if data is None: - if self.initial_data is None: - raise ValueError("No data given and no initial data set") - self.logger.debug("No data given, using initial data") - data = self.initial_data + + data = DataContainer() for i, step in enumerate(self.steps): self.logger.info(f"Running {step.__class__.__name__} - {i + 1} / {len(self.steps)}") data = step.execute(data) return data - @abstractmethod - def define_steps(self) -> list[PipelineStep]: - """ - Subclasses should implement this method to define their specific steps. - """ + @classmethod + def from_json(cls, path: str) -> Pipeline: + """Load a pipeline from a JSON file.""" + # check file is a json file + if not path.endswith(".json"): + raise ValueError(f"File {path} is not a JSON file") - def init_logger(self) -> None: - """Initialize the logger.""" - self.logger = logging.getLogger(self.__class__.__name__) - self.logger.debug(f"{self.__class__.__name__} initialized") + with open(path, "r") as config_file: + config = json.load(config_file) + + pipeline = Pipeline() # Assuming you have a default or base Pipeline class + steps = [] + + for step_config in config["pipeline"]["steps"]: + step_type = step_config["step_type"] + parameters = step_config.get("parameters", {}) + + pipeline.logger.info( + f"Creating step {step_type} with parameters: \n {json.dumps(parameters, indent=4)}" + ) + + step_class = Pipeline.get_step_class(step_type) + step = step_class(config=parameters) + steps.append(step) + + pipeline.add_steps(steps) + return pipeline def __str__(self) -> str: step_names = [f"{i + 1}. {step.__class__.__name__}" for i, step in enumerate(self.steps)] diff --git a/pipeline_lib/core/steps/augment.py b/pipeline_lib/core/steps/augment.py index e92e029..895d5c8 100644 --- a/pipeline_lib/core/steps/augment.py +++ b/pipeline_lib/core/steps/augment.py @@ -1,3 +1,5 @@ +from typing import Optional + from pipeline_lib.core import DataContainer from .base import PipelineStep @@ -6,8 +8,9 @@ class AugmentStep(PipelineStep): """Augment the data.""" - def __init__(self) -> None: + def __init__(self, config: Optional[dict] = None) -> None: """Initialize AugmentStep.""" + super().__init__(config=config) self.init_logger() def execute(self, data: DataContainer) -> DataContainer: diff --git a/pipeline_lib/core/steps/base.py b/pipeline_lib/core/steps/base.py index cfa57ce..9530368 100644 --- a/pipeline_lib/core/steps/base.py +++ b/pipeline_lib/core/steps/base.py @@ -1,12 +1,16 @@ import logging from abc import ABC, abstractmethod +from typing import Optional -from pipeline_lib.core.data_container import DataContainer +from pipeline_lib.core import DataContainer class PipelineStep(ABC): """Base class for pipeline steps.""" + def __init__(self, config: Optional[dict] = None) -> None: + self.config = config + @abstractmethod def execute(self, data: DataContainer) -> DataContainer: """Abstract method for executing the step.""" diff --git a/pipeline_lib/core/steps/calculate_features.py b/pipeline_lib/core/steps/calculate_features.py index 588ccc9..eb145c1 100644 --- a/pipeline_lib/core/steps/calculate_features.py +++ b/pipeline_lib/core/steps/calculate_features.py @@ -1,3 +1,5 @@ +from typing import Optional + from pipeline_lib.core import DataContainer from .base import PipelineStep @@ -6,8 +8,9 @@ class CalculateFeaturesStep(PipelineStep): """Calculate features.""" - def __init__(self) -> None: + def __init__(self, config: Optional[dict] = None) -> None: """Initialize CalculateFeaturesStep.""" + super().__init__(config=config) self.init_logger() def execute(self, data: DataContainer) -> DataContainer: diff --git a/pipeline_lib/core/steps/calculate_metrics.py b/pipeline_lib/core/steps/calculate_metrics.py index 29fe5f5..ccbef26 100644 --- a/pipeline_lib/core/steps/calculate_metrics.py +++ b/pipeline_lib/core/steps/calculate_metrics.py @@ -1,3 +1,5 @@ +from typing import Optional + import numpy as np from sklearn.metrics import mean_absolute_error, mean_squared_error @@ -9,8 +11,9 @@ class CalculateMetricsStep(PipelineStep): """Calculate metrics.""" - def __init__(self) -> None: + def __init__(self, config: Optional[dict] = None) -> None: """Initialize CalculateMetricsStep.""" + super().__init__(config=config) self.init_logger() def execute(self, data: DataContainer) -> DataContainer: diff --git a/pipeline_lib/core/steps/calculate_reports.py b/pipeline_lib/core/steps/calculate_reports.py index 0eee829..75129cd 100644 --- a/pipeline_lib/core/steps/calculate_reports.py +++ b/pipeline_lib/core/steps/calculate_reports.py @@ -1,3 +1,5 @@ +from typing import Optional + from pipeline_lib.core import DataContainer from .base import PipelineStep @@ -6,8 +8,9 @@ class CalculateReportsStep(PipelineStep): """Calculate reports.""" - def __init__(self) -> None: + def __init__(self, config: Optional[dict] = None) -> None: """Initialize CalculateReportsStep.""" + super().__init__(config=config) self.init_logger() def execute(self, data: DataContainer) -> DataContainer: diff --git a/pipeline_lib/core/steps/encode.py b/pipeline_lib/core/steps/encode.py index f770131..5e07ceb 100644 --- a/pipeline_lib/core/steps/encode.py +++ b/pipeline_lib/core/steps/encode.py @@ -1,3 +1,5 @@ +from typing import Optional + from pipeline_lib.core import DataContainer from .base import PipelineStep @@ -6,8 +8,9 @@ class EncodeStep(PipelineStep): """Encode the data.""" - def __init__(self) -> None: + def __init__(self, config: Optional[dict] = None) -> None: """Initialize EncodeStep.""" + super().__init__(config=config) self.init_logger() def execute(self, data: DataContainer) -> DataContainer: diff --git a/pipeline_lib/core/steps/explainer_dashboard.py b/pipeline_lib/core/steps/explainer_dashboard.py index bd48da6..98bf397 100644 --- a/pipeline_lib/core/steps/explainer_dashboard.py +++ b/pipeline_lib/core/steps/explainer_dashboard.py @@ -1,3 +1,5 @@ +from typing import Optional + from pipeline_lib.core import DataContainer from .base import PipelineStep @@ -6,8 +8,9 @@ class ExplainerDashboardStep(PipelineStep): """Explainer Dashboard.""" - def __init__(self) -> None: + def __init__(self, config: Optional[dict] = None) -> None: """Initialize ExplainerDashboardStep.""" + super().__init__(config=config) self.init_logger() def execute(self, data: DataContainer) -> DataContainer: diff --git a/pipeline_lib/core/steps/fit_encoders.py b/pipeline_lib/core/steps/fit_encoders.py index 192b2cf..87f352e 100644 --- a/pipeline_lib/core/steps/fit_encoders.py +++ b/pipeline_lib/core/steps/fit_encoders.py @@ -1,3 +1,5 @@ +from typing import Optional + from pipeline_lib.core import DataContainer from .base import PipelineStep @@ -6,8 +8,9 @@ class FitEncodersStep(PipelineStep): """Fit encoders.""" - def __init__(self) -> None: + def __init__(self, config: Optional[dict] = None) -> None: """Initialize FitEncodersStep.""" + super().__init__(config=config) self.init_logger() def execute(self, data: DataContainer) -> DataContainer: diff --git a/pipeline_lib/core/steps/fit_model.py b/pipeline_lib/core/steps/fit_model.py index 248c128..6103a8d 100644 --- a/pipeline_lib/core/steps/fit_model.py +++ b/pipeline_lib/core/steps/fit_model.py @@ -1,3 +1,5 @@ +from typing import Optional + from pipeline_lib.core import DataContainer from .base import PipelineStep @@ -6,8 +8,9 @@ class FitModelStep(PipelineStep): """Fit the model.""" - def __init__(self) -> None: + def __init__(self, config: Optional[dict] = None) -> None: """Initialize FitModelStep.""" + super().__init__(config=config) self.init_logger() def execute(self, data: DataContainer) -> DataContainer: diff --git a/pipeline_lib/core/steps/input_scaling.py b/pipeline_lib/core/steps/input_scaling.py index 5f04f7f..128b0cd 100644 --- a/pipeline_lib/core/steps/input_scaling.py +++ b/pipeline_lib/core/steps/input_scaling.py @@ -1,3 +1,5 @@ +from typing import Optional + from pipeline_lib.core import DataContainer from .base import PipelineStep @@ -6,8 +8,9 @@ class InputScalingStep(PipelineStep): """Scale the input.""" - def __init__(self) -> None: + def __init__(self, config: Optional[dict] = None) -> None: """Initialize InputScalingStep.""" + super().__init__(config=config) self.init_logger() def execute(self, data: DataContainer) -> DataContainer: diff --git a/pipeline_lib/core/steps/predict.py b/pipeline_lib/core/steps/predict.py index 82bee7d..a8b5a79 100644 --- a/pipeline_lib/core/steps/predict.py +++ b/pipeline_lib/core/steps/predict.py @@ -1,3 +1,5 @@ +from typing import Optional + from pipeline_lib.core import DataContainer from .base import PipelineStep @@ -6,8 +8,9 @@ class PredictStep(PipelineStep): """Obtain the predictions.""" - def __init__(self) -> None: + def __init__(self, config: Optional[dict] = None) -> None: """Initialize Predict Step.""" + super().__init__(config=config) self.init_logger() def execute(self, data: DataContainer) -> DataContainer: diff --git a/pipeline_lib/core/steps/tabular_split.py b/pipeline_lib/core/steps/tabular_split.py index 72568ca..11e0450 100644 --- a/pipeline_lib/core/steps/tabular_split.py +++ b/pipeline_lib/core/steps/tabular_split.py @@ -1,4 +1,4 @@ -from typing import Tuple +from typing import Optional, Tuple import pandas as pd from sklearn.model_selection import train_test_split @@ -11,8 +11,9 @@ class TabularSplitStep(PipelineStep): """Split the data.""" - def __init__(self) -> None: + def __init__(self, config: Optional[dict] = None) -> None: """Initialize SplitStep.""" + super().__init__(config=config) self.init_logger() def _id_based_split( @@ -75,7 +76,7 @@ def execute(self, data: DataContainer) -> DataContainer: """Execute the split based on IDs.""" self.logger.info("Splitting tabular data...") - split_configs = data[DataContainer.SPLIT_CONFIGS] + split_configs = self.config if split_configs is None: self.logger.info("No split_configs found. No splitting will be performed.") diff --git a/pipeline_lib/core/steps/target_scaling.py b/pipeline_lib/core/steps/target_scaling.py index f77ce13..3dad235 100644 --- a/pipeline_lib/core/steps/target_scaling.py +++ b/pipeline_lib/core/steps/target_scaling.py @@ -1,3 +1,5 @@ +from typing import Optional + from pipeline_lib.core import DataContainer from .base import PipelineStep @@ -6,14 +8,14 @@ class TargetScalingStep(PipelineStep): """Scale the target.""" - def __init__(self) -> None: + def __init__(self, config: Optional[dict] = None) -> None: """Initialize TargetScalingStep.""" + super().__init__(config=config) self.init_logger() def execute(self, data: DataContainer) -> DataContainer: - target_scaling_configs = data.get(DataContainer.TARGET_SCALING_CONFIGS) - - if target_scaling_configs is None: + """Execute the step.""" + if not self.config: self.logger.info("No target scaling configs found. Skipping target scaling.") return data diff --git a/pipeline_lib/implementation/tabular/xgboost/fit_model.py b/pipeline_lib/implementation/tabular/xgboost/fit_model.py index f0ad06c..8dd8c7a 100644 --- a/pipeline_lib/implementation/tabular/xgboost/fit_model.py +++ b/pipeline_lib/implementation/tabular/xgboost/fit_model.py @@ -17,18 +17,21 @@ def execute(self, data: DataContainer) -> DataContainer: start_time = time.time() - model_configs = data[DataContainer.MODEL_CONFIGS] + model_configs = self.config + + if model_configs is None: + raise ValueError("No model configs found") target = model_configs.get("target") if target is None: raise ValueError("Target column not found in model_configs.") - drop_columns: list[str] = model_configs.get("drop_columns") - df_train = data[DataContainer.TRAIN] df_valid = data[DataContainer.VALIDATION] + drop_columns = model_configs.get("drop_columns") + if drop_columns: df_train = df_train.drop(columns=drop_columns) df_valid = df_valid.drop(columns=drop_columns) From 74369b161a9e9f0b725d34f31dad0417d4606518 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Tue, 5 Mar 2024 12:59:47 +0000 Subject: [PATCH 02/33] enable custom step registration via folder path in config.json --- pipeline_lib/core/pipeline.py | 61 +++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/pipeline_lib/core/pipeline.py b/pipeline_lib/core/pipeline.py index 29922d3..ba39dc6 100644 --- a/pipeline_lib/core/pipeline.py +++ b/pipeline_lib/core/pipeline.py @@ -3,6 +3,7 @@ import importlib import json import logging +import os import pkgutil from typing import Optional @@ -14,18 +15,13 @@ class Pipeline: """Base class for pipelines.""" _step_registry = {} + logger = logging.getLogger("Pipeline") def __init__(self, initial_data: Optional[DataContainer] = None): self.steps = [] if not all(isinstance(step, PipelineStep) for step in self.steps): raise TypeError("All steps must be instances of PipelineStep") self.initial_data = initial_data - self.init_logger() - - def init_logger(self) -> None: - """Initialize the logger.""" - self.logger = logging.getLogger(self.__class__.__name__) - self.logger.debug(f"{self.__class__.__name__} initialized") @classmethod def register_step(cls, step_class): @@ -65,13 +61,56 @@ def auto_register_steps_from_package(cls, package_name): ): cls.register_step(attribute) + @staticmethod + def load_and_register_custom_steps(custom_steps_path: str) -> None: + """ + Dynamically loads and registers step classes found in the specified directory. + + This method scans a specified directory for Python files (excluding __init__.py), + dynamically imports these files as modules, and registers all classes derived from + PipelineStep found within these modules. + + Parameters + ---------- + custom_steps_path : str + The path to the directory containing custom step implementation files. + + Returns + ------- + NoReturn + This function does not return anything. + """ + Pipeline.logger.debug(f"Loading custom steps from: {custom_steps_path}") + for filename in os.listdir(custom_steps_path): + if filename.endswith(".py") and not filename.startswith("__"): + filepath = os.path.join(custom_steps_path, filename) + module_name = os.path.splitext(filename)[0] + spec = importlib.util.spec_from_file_location(module_name, filepath) + module = importlib.util.module_from_spec(spec) + + try: + spec.loader.exec_module(module) + Pipeline.logger.debug(f"Successfully loaded module: {module_name}") + + for attribute_name in dir(module): + attribute = getattr(module, attribute_name) + if ( + isinstance(attribute, type) + and issubclass(attribute, PipelineStep) + and attribute is not PipelineStep + ): + Pipeline.register_step(attribute) + Pipeline.logger.debug(f"Registered step class: {attribute_name}") + except Exception as e: + Pipeline.logger.error(f"Failed to load module: {module_name}. Error: {e}") + def run(self) -> DataContainer: """Run the pipeline on the given data.""" data = DataContainer() for i, step in enumerate(self.steps): - self.logger.info(f"Running {step.__class__.__name__} - {i + 1} / {len(self.steps)}") + Pipeline.logger.info(f"Running {step.__class__.__name__} - {i + 1} / {len(self.steps)}") data = step.execute(data) return data @@ -85,14 +124,18 @@ def from_json(cls, path: str) -> Pipeline: with open(path, "r") as config_file: config = json.load(config_file) - pipeline = Pipeline() # Assuming you have a default or base Pipeline class + custom_steps_path = config.get("custom_steps_path") + if custom_steps_path: + Pipeline.load_and_register_custom_steps(custom_steps_path) + + pipeline = Pipeline() steps = [] for step_config in config["pipeline"]["steps"]: step_type = step_config["step_type"] parameters = step_config.get("parameters", {}) - pipeline.logger.info( + Pipeline.logger.info( f"Creating step {step_type} with parameters: \n {json.dumps(parameters, indent=4)}" ) From 93d304dcb45d4fd810b71faecf805621e5ad299d Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Tue, 5 Mar 2024 13:07:20 +0000 Subject: [PATCH 03/33] change load and register function to classmethod --- pipeline_lib/core/pipeline.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/pipeline_lib/core/pipeline.py b/pipeline_lib/core/pipeline.py index ba39dc6..617ad53 100644 --- a/pipeline_lib/core/pipeline.py +++ b/pipeline_lib/core/pipeline.py @@ -61,8 +61,8 @@ def auto_register_steps_from_package(cls, package_name): ): cls.register_step(attribute) - @staticmethod - def load_and_register_custom_steps(custom_steps_path: str) -> None: + @classmethod + def load_and_register_custom_steps(cls, custom_steps_path: str) -> None: """ Dynamically loads and registers step classes found in the specified directory. @@ -74,11 +74,6 @@ def load_and_register_custom_steps(custom_steps_path: str) -> None: ---------- custom_steps_path : str The path to the directory containing custom step implementation files. - - Returns - ------- - NoReturn - This function does not return anything. """ Pipeline.logger.debug(f"Loading custom steps from: {custom_steps_path}") for filename in os.listdir(custom_steps_path): From 3e63edf971319122ee08030dad3f5da9665dc562 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Tue, 5 Mar 2024 17:41:46 +0000 Subject: [PATCH 04/33] add load and save in json --- pipeline_lib/core/data_container.py | 2 ++ pipeline_lib/core/pipeline.py | 12 +++++++++++- pipeline_lib/core/steps/calculate_metrics.py | 7 ++++--- .../implementation/tabular/xgboost/fit_model.py | 2 ++ .../implementation/tabular/xgboost/predict.py | 9 +++++---- 5 files changed, 24 insertions(+), 8 deletions(-) diff --git a/pipeline_lib/core/data_container.py b/pipeline_lib/core/data_container.py index 5e0c3ed..b7f1a9c 100644 --- a/pipeline_lib/core/data_container.py +++ b/pipeline_lib/core/data_container.py @@ -33,6 +33,7 @@ class DataContainer: PREDICTIONS = "predictions" EXPLAINER = "explainer" TUNING_PARAMS = "tuning_params" + TARGET = "target" def __init__(self, initial_data: Optional[dict] = None): """ @@ -184,6 +185,7 @@ def save(self, file_path: str, keys: Optional[Union[str, list[str]]] = None): with open(file_path, "wb") as file: file.write(serialized_data) + self.logger.info( f"{self.__class__.__name__} serialized and saved to {file_path}. Size:" f" {data_size_mb:.2f} MB" diff --git a/pipeline_lib/core/pipeline.py b/pipeline_lib/core/pipeline.py index 617ad53..5b4e63c 100644 --- a/pipeline_lib/core/pipeline.py +++ b/pipeline_lib/core/pipeline.py @@ -22,6 +22,8 @@ def __init__(self, initial_data: Optional[DataContainer] = None): if not all(isinstance(step, PipelineStep) for step in self.steps): raise TypeError("All steps must be instances of PipelineStep") self.initial_data = initial_data + self.save_path = None + self.load_path = None @classmethod def register_step(cls, step_class): @@ -102,11 +104,15 @@ def load_and_register_custom_steps(cls, custom_steps_path: str) -> None: def run(self) -> DataContainer: """Run the pipeline on the given data.""" - data = DataContainer() + data = DataContainer.from_pickle(self.load_path) if self.load_path else DataContainer() for i, step in enumerate(self.steps): Pipeline.logger.info(f"Running {step.__class__.__name__} - {i + 1} / {len(self.steps)}") data = step.execute(data) + + if self.save_path: + data.save(self.save_path) + return data @classmethod @@ -124,6 +130,10 @@ def from_json(cls, path: str) -> Pipeline: Pipeline.load_and_register_custom_steps(custom_steps_path) pipeline = Pipeline() + + pipeline.load_path = config.get("load_path") + pipeline.save_path = config.get("save_path") + steps = [] for step_config in config["pipeline"]["steps"]: diff --git a/pipeline_lib/core/steps/calculate_metrics.py b/pipeline_lib/core/steps/calculate_metrics.py index ccbef26..e7c67ea 100644 --- a/pipeline_lib/core/steps/calculate_metrics.py +++ b/pipeline_lib/core/steps/calculate_metrics.py @@ -19,11 +19,11 @@ def __init__(self, config: Optional[dict] = None) -> None: def execute(self, data: DataContainer) -> DataContainer: self.logger.debug("Starting metric calculation") model_output = data[DataContainer.MODEL_OUTPUT] - model_configs = data[DataContainer.MODEL_CONFIGS] - target_column_name = model_configs.get("target") + + target_column_name = data.get(DataContainer.TARGET) if target_column_name is None: - raise ValueError("Target column not found in model_configs.") + raise ValueError("Target column not found on any configuration.") true_values = model_output[target_column_name] predictions = model_output[DataContainer.PREDICTIONS] @@ -32,5 +32,6 @@ def execute(self, data: DataContainer) -> DataContainer: rmse = np.sqrt(mean_squared_error(true_values, predictions)) results = {"MAE": str(mae), "RMSE": str(rmse)} + self.logger.info(results) data[DataContainer.METRICS] = results return data diff --git a/pipeline_lib/implementation/tabular/xgboost/fit_model.py b/pipeline_lib/implementation/tabular/xgboost/fit_model.py index 8dd8c7a..dc16efe 100644 --- a/pipeline_lib/implementation/tabular/xgboost/fit_model.py +++ b/pipeline_lib/implementation/tabular/xgboost/fit_model.py @@ -27,6 +27,8 @@ def execute(self, data: DataContainer) -> DataContainer: if target is None: raise ValueError("Target column not found in model_configs.") + data[DataContainer.TARGET] = target + df_train = data[DataContainer.TRAIN] df_valid = data[DataContainer.VALIDATION] diff --git a/pipeline_lib/implementation/tabular/xgboost/predict.py b/pipeline_lib/implementation/tabular/xgboost/predict.py index c8104d5..f82323e 100644 --- a/pipeline_lib/implementation/tabular/xgboost/predict.py +++ b/pipeline_lib/implementation/tabular/xgboost/predict.py @@ -19,14 +19,15 @@ def execute(self, data: DataContainer) -> DataContainer: if not isinstance(model_input, pd.DataFrame): raise ValueError("model_input must be a pandas DataFrame.") - model_configs = data.get(DataContainer.MODEL_CONFIGS) - if model_configs: - drop_columns: list[str] = model_configs.get("drop_columns") + if self.config: + drop_columns = self.config.get("drop_columns") if drop_columns: model_input = model_input.drop(columns=drop_columns) - target = model_configs.get("target") + + target = self.config.get("target") if target is None: raise ValueError("Target column not found in model_configs.") + data[DataContainer.TARGET] = target predictions = model.predict(model_input.drop(columns=[target])) else: From aa670ec161a4b827a5b830c96917cd3a3e601136 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Tue, 5 Mar 2024 17:46:52 +0000 Subject: [PATCH 05/33] add simple readme --- README.md | 174 ++++++++++++++++++++++-------------------------------- 1 file changed, 70 insertions(+), 104 deletions(-) diff --git a/README.md b/README.md index 38ab043..f5e0588 100644 --- a/README.md +++ b/README.md @@ -1,104 +1,70 @@ -# Repo Template - -Kick off a project with the right foot. - -A repository template for easily setting up a well behaved development environment for a smooth -collaboration experience. - -This template takes care of setting up and configuring: - -- A **virtual environment** -- **Formatting and linting** tools -- Some shared default **VSCode settings** -- A **Pull Request template** -- A **GitHub Action** that runs formatting and linting checks - -Any of these configurations and features can be disabled/modified freely after set up if the team -chooses to. - -Note: [pyenv](https://github.com/pyenv/pyenv#installation) and -[poetry](https://python-poetry.org/docs/#installation) are used for setting up a virtual environment -with the correct python version. Make sure both of those are installed correctly in your machine. - -# Usage - -1. Click the `Use this template` button at the top of this repo's home page to spawn a new repo - from this template. - -2. Clone the new repo to your local environment. - -3. Run `sh init.sh `. - - Note that: - - - the project's accepted python versions will be set to `^` - feel free - to change this manually in the `pyproject.toml` file after running the script. - - your project's source code should be placed in the newly-created folder with your project's - name, so that absolute imports (`from my_project.my_module import func`) work everywhere. - -4. Nuke this readme and the `init.sh` file. - -5. Add to git the changes made by the init script, such as the newly created `poetry.toml`, - `poetry.lock` and `.python-version` files. - -6. Commit and push your changes - your project is all set up. - -7. [Recommended] Set up the following in your GitHub project's `Settings` tab: - - Enable branch protection for the `main` branch in the `Branches` menu to prevent non-reviewed - pushes/merges to it. - - Enable `Automatically delete head branches` in the `General` tab for feature branches to be - cleaned up when merged. - -# For ongoing projects - -If you want to improve the current configs of an existing project, these files are the ones you'll -probably want to steal some content from: - -- [VSCode settings](.vscode/settings.json) -- [Flake8 config](.flake8) -- [Black and iSort configs](pyproject.toml) -- [Style check GitHub Action](.github/workflows/style-checks.yaml) - -Additionally, you might want to check the -[project's source code is correctly installed via Poetry](https://stackoverflow.com/questions/66586856/how-can-i-make-my-project-available-in-the-poetry-environment) -for intra-project imports to work as expected across the board. - -# For developers of this template - -To test new changes made to this template: - -1. Run the template in test mode with `test=true sh init.sh `, - which will not delete the [project_base/test.py](project_base/test.py) file from the source - directory. - -2. Use that file to check everything works as expected (see details in its docstring). - -3. Make sure not to version any of the files created by the script. `git reset --hard` + manually - deleting the created files not yet added to versioning works, for example. - -# Issues and suggestions - -Feel free to report issues or propose improvements to this template via GitHub issues or through the -`#team-tech-meta` channel in Slack. - -# Can I use it without Poetry? - -This template currently sets up your virtual environment via poetry only. - -If you want to use a different dependency manager, you'll have to manually do the following: - -1. Remove the `.venv` environment and the `pyproject.toml` and `poetry.lock` files. -2. Create a new environment with your dependency manager of choice. -3. Install flake, black and isort as dev dependencies. -4. Install the current project's source. -5. Set the path to your new environment's python in the `python.pythonPath` and - `python.defaultInterpreterPath` in [vscode settings](.vscode/settings.json). - -Disclaimer: this has not been tested, additional steps may be needed. - -# Troubleshooting - -### pyenv not picking up correct python version from .python-version - -Make sure the `PYENV_VERSION` env var isn't set in your current shell -(and if it is, run `unset PYENV_VERSION`). +# Pipeline Library + +The purpose of this library is to create pipelines for ML as simple as possible. At the moment we support XGBoost models, but we are working to support more models. + +This is an example of how to use the library to run an XGBoost pipeline: + +We create a `train.json` file with the following content: + +```json +{ + "custom_steps_path": "examples/ocf/", + "save_path": "runs/xgboost_train.pkl", + "pipeline": { + "name": "XGBoostTrainingPipeline", + "description": "Training pipeline for XGBoost models.", + "steps": [ + { + "step_type": "OCFGenerateStep", + "parameters": { + "path": "examples/ocf/data/trainset_new.parquet" + } + }, + { + "step_type": "OCFCleanStep", + "parameters": {} + }, + { + "step_type": "TabularSplitStep", + "parameters": { + "id_column": "ss_id", + "train_percentage": 0.95 + } + }, + { + "step_type": "XGBoostFitModelStep", + "parameters": { + "target": "average_power_kw", + "drop_columns": [ + "ss_id" + ], + "xgb_params": { + "max_depth": 12, + "eta": 0.12410097733370863, + "objective": "reg:squarederror", + "eval_metric": "mae", + "n_jobs": -1, + "n_estimators": 2, + "min_child_weight": 7, + "subsample": 0.8057743223537057, + "colsample_bytree": 0.6316852278944352 + }, + "save_model": true + } + } + ] + } +} +``` + +The user can define custom steps to generate and clean their own data and use them in the pipeline. Then we can run the pipeline with the following code: + +```python +import logging + +from pipeline_lib.core import Pipeline + +logging.basicConfig(level=logging.INFO) + +Pipeline.from_json("train.json").run() +``` \ No newline at end of file From c5210f19a5eb41902f1707e4a7eaff9ce96e9695 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Tue, 5 Mar 2024 22:49:38 +0000 Subject: [PATCH 06/33] add feature importance to fit step --- pipeline_lib/core/data_container.py | 1 + pipeline_lib/implementation/tabular/xgboost/fit_model.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/pipeline_lib/core/data_container.py b/pipeline_lib/core/data_container.py index b7f1a9c..a0393e6 100644 --- a/pipeline_lib/core/data_container.py +++ b/pipeline_lib/core/data_container.py @@ -34,6 +34,7 @@ class DataContainer: EXPLAINER = "explainer" TUNING_PARAMS = "tuning_params" TARGET = "target" + IMPORTANCE = "importance" def __init__(self, initial_data: Optional[dict] = None): """ diff --git a/pipeline_lib/implementation/tabular/xgboost/fit_model.py b/pipeline_lib/implementation/tabular/xgboost/fit_model.py index dc16efe..2a8c78b 100644 --- a/pipeline_lib/implementation/tabular/xgboost/fit_model.py +++ b/pipeline_lib/implementation/tabular/xgboost/fit_model.py @@ -75,6 +75,10 @@ def execute(self, data: DataContainer) -> DataContainer: # Save the model to the data container data[DataContainer.MODEL] = model + importance = model.get_booster().get_score(importance_type="gain") + importance = dict(sorted(importance.items(), key=lambda item: item[1], reverse=True)) + data[DataContainer.IMPORTANCE] = importance + end_time = time.time() elapsed_time = end_time - start_time minutes = int(elapsed_time // 60) From 5f273be5f7862321fa807f4d00093215c85a86a3 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Tue, 5 Mar 2024 22:57:42 +0000 Subject: [PATCH 07/33] fix optuna storage default param --- pipeline_lib/implementation/tabular/xgboost/fit_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipeline_lib/implementation/tabular/xgboost/fit_model.py b/pipeline_lib/implementation/tabular/xgboost/fit_model.py index 2a8c78b..c2c834c 100644 --- a/pipeline_lib/implementation/tabular/xgboost/fit_model.py +++ b/pipeline_lib/implementation/tabular/xgboost/fit_model.py @@ -138,7 +138,7 @@ def optuna_logging_callback(study, trial): self.logger.info(f"Optimizing XGBoost hyperparameters with {optuna_trials} trials.") study_name = optuna_params.get("study_name", "xgboost_optimization") - storage = optuna_params.get("storage", "I") + storage = optuna_params.get("storage", "sqlite:///db.sqlite3") study = optuna.create_study( direction="minimize", From a00981fe9786bc991658ffa2eb99bd4548a67d35 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Tue, 5 Mar 2024 23:54:18 +0000 Subject: [PATCH 08/33] add extras for better pip installation --- poetry.lock | 34 +++++----------------------------- pyproject.toml | 5 ++++- 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/poetry.lock b/poetry.lock index 9aff08c..5350e58 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1476,26 +1476,6 @@ markupsafe = "*" [package.extras] email = ["email-validator"] -[[package]] -name = "xgboost" -version = "2.0.3" -description = "XGBoost Python Package" -category = "main" -optional = false -python-versions = ">=3.8" - -[package.dependencies] -numpy = "*" -scipy = "*" - -[package.extras] -dask = ["dask", "distributed", "pandas"] -datatable = ["datatable"] -pandas = ["pandas"] -plotting = ["graphviz", "matplotlib"] -pyspark = ["cloudpickle", "pyspark", "scikit-learn"] -scikit-learn = ["scikit-learn"] - [[package]] name = "zipp" version = "3.17.0" @@ -1508,10 +1488,14 @@ python-versions = ">=3.8" docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +[extras] +all_models = [] +xgboost = [] + [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "3c451f451ab29518ed6a0cdff853fcdf88e20e24281e21f1982bedbc81ec4682" +content-hash = "101c905d710792e87138fead4f2f28fef8562fa1106445c24f77015ec71a6a18" [metadata.files] alembic = [ @@ -2937,14 +2921,6 @@ WTForms = [ {file = "wtforms-3.1.2-py3-none-any.whl", hash = "sha256:bf831c042829c8cdbad74c27575098d541d039b1faa74c771545ecac916f2c07"}, {file = "wtforms-3.1.2.tar.gz", hash = "sha256:f8d76180d7239c94c6322f7990ae1216dae3659b7aa1cee94b6318bdffb474b9"}, ] -xgboost = [ - {file = "xgboost-2.0.3-py3-none-macosx_10_15_x86_64.macosx_11_0_x86_64.macosx_12_0_x86_64.whl", hash = "sha256:b21b2bb188b162c615fce468db93e3f995f3690e6184aadc7743b58466dc7f13"}, - {file = "xgboost-2.0.3-py3-none-macosx_12_0_arm64.whl", hash = "sha256:722d5b9351dfdf61973490dfd28abd42844db1cc469d07ed9b0cde9d1ffcdb32"}, - {file = "xgboost-2.0.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:2315a57b1883221e2f78dd514559aa9797e6c272d995d22e45495a04adac93cc"}, - {file = "xgboost-2.0.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:30bd5f789fad467fd49e04e5d19e04238b931682c3951a514da5c2410b3bf59c"}, - {file = "xgboost-2.0.3-py3-none-win_amd64.whl", hash = "sha256:462f131d7bfb1bc42f67c57fa5aa3e57d2b5755b1573a6e0d2c7e8895164e0fc"}, - {file = "xgboost-2.0.3.tar.gz", hash = "sha256:505955b5d770f8217a049beecce79e04a93787371c06dfb4b2414fec9d496bf3"}, -] zipp = [ {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, diff --git a/pyproject.toml b/pyproject.toml index 5d77fee..a18261f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,10 +21,13 @@ pandas = "^2.2.1" scikit-learn = "^1.4.1.post1" explainerdashboard = "^0.4.5" optuna = "^3.5.0" -xgboost = "^2.0.3" pyarrow = "^15.0.0" PyYAML = "^6.0.1" +[tool.poetry.extras] +xgboost = ["xgboost>=2.0.0,<2.1.0"] +all_models = ["xgboost>=2.0.0,<2.1.0"] + [tool.poetry.group.dev.dependencies] flake8 = "^7.0.0" black = "^23.12.1" From e4ddec5ec879ef41393d2d2d1625691e9066e270 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Wed, 6 Mar 2024 17:28:10 +0000 Subject: [PATCH 09/33] save xgboost model to joblib --- pipeline_lib/core/pipeline.py | 2 -- .../implementation/tabular/xgboost/fit_model.py | 10 ++++++++++ .../implementation/tabular/xgboost/predict.py | 14 +++++++++++--- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/pipeline_lib/core/pipeline.py b/pipeline_lib/core/pipeline.py index 5b4e63c..258890e 100644 --- a/pipeline_lib/core/pipeline.py +++ b/pipeline_lib/core/pipeline.py @@ -19,8 +19,6 @@ class Pipeline: def __init__(self, initial_data: Optional[DataContainer] = None): self.steps = [] - if not all(isinstance(step, PipelineStep) for step in self.steps): - raise TypeError("All steps must be instances of PipelineStep") self.initial_data = initial_data self.save_path = None self.load_path = None diff --git a/pipeline_lib/implementation/tabular/xgboost/fit_model.py b/pipeline_lib/implementation/tabular/xgboost/fit_model.py index c2c834c..606e534 100644 --- a/pipeline_lib/implementation/tabular/xgboost/fit_model.py +++ b/pipeline_lib/implementation/tabular/xgboost/fit_model.py @@ -2,6 +2,7 @@ import optuna import xgboost as xgb +from joblib import dump from optuna.pruners import MedianPruner from sklearn.metrics import mean_absolute_error @@ -79,6 +80,15 @@ def execute(self, data: DataContainer) -> DataContainer: importance = dict(sorted(importance.items(), key=lambda item: item[1], reverse=True)) data[DataContainer.IMPORTANCE] = importance + # save model to disk + save_path = model_configs.get("save_path") + + if save_path: + if not save_path.endswith(".joblib"): + raise ValueError("Only joblib format is supported for saving the model.") + self.logger.info(f"Saving the model to {save_path}") + dump(model, save_path) + end_time = time.time() elapsed_time = end_time - start_time minutes = int(elapsed_time // 60) diff --git a/pipeline_lib/implementation/tabular/xgboost/predict.py b/pipeline_lib/implementation/tabular/xgboost/predict.py index f82323e..b0cd492 100644 --- a/pipeline_lib/implementation/tabular/xgboost/predict.py +++ b/pipeline_lib/implementation/tabular/xgboost/predict.py @@ -1,4 +1,5 @@ import pandas as pd +from joblib import load from pipeline_lib.core import DataContainer from pipeline_lib.core.steps import PredictStep @@ -10,9 +11,16 @@ class XGBoostPredictStep(PredictStep): def execute(self, data: DataContainer) -> DataContainer: self.logger.debug("Obtaining predictions for XGBoost model.") - model = data[DataContainer.MODEL] - if model is None: - raise Exception("Model not trained yet.") + if not self.config: + raise ValueError("No prediction configs found.") + + load_path = self.config.get("load_path") + if not load_path: + raise ValueError("No load path found in model_configs.") + + if not load_path.endswith(".joblib"): + raise ValueError("Only joblib format is supported for loading the model.") + model = load(load_path) model_input = data[DataContainer.CLEAN] From 871bbec07412e091adb4d5fdfb9223328852a159 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Wed, 6 Mar 2024 17:37:20 +0000 Subject: [PATCH 10/33] adjust poetry extras --- poetry.lock | 40 ++++++++++++++++++++++++++++++++++------ pyproject.toml | 5 +++-- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5350e58..2be3ece 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1086,7 +1086,7 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyparsing" -version = "3.1.1" +version = "3.1.2" description = "pyparsing module - Classes and methods to define and execute parsing grammars" category = "main" optional = false @@ -1476,6 +1476,26 @@ markupsafe = "*" [package.extras] email = ["email-validator"] +[[package]] +name = "xgboost" +version = "2.0.3" +description = "XGBoost Python Package" +category = "main" +optional = true +python-versions = ">=3.8" + +[package.dependencies] +numpy = "*" +scipy = "*" + +[package.extras] +dask = ["dask", "distributed", "pandas"] +datatable = ["datatable"] +pandas = ["pandas"] +plotting = ["graphviz", "matplotlib"] +pyspark = ["cloudpickle", "pyspark", "scikit-learn"] +scikit-learn = ["scikit-learn"] + [[package]] name = "zipp" version = "3.17.0" @@ -1489,13 +1509,13 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.link testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [extras] -all_models = [] -xgboost = [] +all_models = ["xgboost"] +xgboost = ["xgboost"] [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "101c905d710792e87138fead4f2f28fef8562fa1106445c24f77015ec71a6a18" +content-hash = "1569b3419d8a6c81d4ac0603e9d20e605f68f339527079764611a2454dee0d5b" [metadata.files] alembic = [ @@ -2538,8 +2558,8 @@ Pygments = [ {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, ] pyparsing = [ - {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, - {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, + {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, + {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, ] pytest = [ {file = "pytest-8.0.2-py3-none-any.whl", hash = "sha256:edfaaef32ce5172d5466b5127b42e0d6d35ebbe4453f0e3505d96afd93f6b096"}, @@ -2921,6 +2941,14 @@ WTForms = [ {file = "wtforms-3.1.2-py3-none-any.whl", hash = "sha256:bf831c042829c8cdbad74c27575098d541d039b1faa74c771545ecac916f2c07"}, {file = "wtforms-3.1.2.tar.gz", hash = "sha256:f8d76180d7239c94c6322f7990ae1216dae3659b7aa1cee94b6318bdffb474b9"}, ] +xgboost = [ + {file = "xgboost-2.0.3-py3-none-macosx_10_15_x86_64.macosx_11_0_x86_64.macosx_12_0_x86_64.whl", hash = "sha256:b21b2bb188b162c615fce468db93e3f995f3690e6184aadc7743b58466dc7f13"}, + {file = "xgboost-2.0.3-py3-none-macosx_12_0_arm64.whl", hash = "sha256:722d5b9351dfdf61973490dfd28abd42844db1cc469d07ed9b0cde9d1ffcdb32"}, + {file = "xgboost-2.0.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:2315a57b1883221e2f78dd514559aa9797e6c272d995d22e45495a04adac93cc"}, + {file = "xgboost-2.0.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:30bd5f789fad467fd49e04e5d19e04238b931682c3951a514da5c2410b3bf59c"}, + {file = "xgboost-2.0.3-py3-none-win_amd64.whl", hash = "sha256:462f131d7bfb1bc42f67c57fa5aa3e57d2b5755b1573a6e0d2c7e8895164e0fc"}, + {file = "xgboost-2.0.3.tar.gz", hash = "sha256:505955b5d770f8217a049beecce79e04a93787371c06dfb4b2414fec9d496bf3"}, +] zipp = [ {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, diff --git a/pyproject.toml b/pyproject.toml index a18261f..ba300a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,10 +23,11 @@ explainerdashboard = "^0.4.5" optuna = "^3.5.0" pyarrow = "^15.0.0" PyYAML = "^6.0.1" +xgboost = { version = "^2.0.3", optional = true } [tool.poetry.extras] -xgboost = ["xgboost>=2.0.0,<2.1.0"] -all_models = ["xgboost>=2.0.0,<2.1.0"] +xgboost = ["xgboost"] +all_models = ["xgboost"] [tool.poetry.group.dev.dependencies] flake8 = "^7.0.0" From b447fc96524531ff5231d0c83277f340acdf0839 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Wed, 13 Mar 2024 16:29:57 -0300 Subject: [PATCH 11/33] change config dict in init to **params --- pipeline_lib/core/pipeline.py | 2 +- pipeline_lib/core/steps/calculate_metrics.py | 4 +- pipeline_lib/core/steps/tabular_split.py | 59 +++++-------- .../tabular/xgboost/fit_model.py | 87 ++++++++++--------- .../implementation/tabular/xgboost/predict.py | 45 +++++----- 5 files changed, 93 insertions(+), 104 deletions(-) diff --git a/pipeline_lib/core/pipeline.py b/pipeline_lib/core/pipeline.py index 258890e..bcea4fe 100644 --- a/pipeline_lib/core/pipeline.py +++ b/pipeline_lib/core/pipeline.py @@ -143,7 +143,7 @@ def from_json(cls, path: str) -> Pipeline: ) step_class = Pipeline.get_step_class(step_type) - step = step_class(config=parameters) + step = step_class(**parameters) steps.append(step) pipeline.add_steps(steps) diff --git a/pipeline_lib/core/steps/calculate_metrics.py b/pipeline_lib/core/steps/calculate_metrics.py index e7c67ea..902a42f 100644 --- a/pipeline_lib/core/steps/calculate_metrics.py +++ b/pipeline_lib/core/steps/calculate_metrics.py @@ -11,9 +11,9 @@ class CalculateMetricsStep(PipelineStep): """Calculate metrics.""" - def __init__(self, config: Optional[dict] = None) -> None: + def __init__(self) -> None: """Initialize CalculateMetricsStep.""" - super().__init__(config=config) + super().__init__() self.init_logger() def execute(self, data: DataContainer) -> DataContainer: diff --git a/pipeline_lib/core/steps/tabular_split.py b/pipeline_lib/core/steps/tabular_split.py index 11e0450..44fc8a2 100644 --- a/pipeline_lib/core/steps/tabular_split.py +++ b/pipeline_lib/core/steps/tabular_split.py @@ -11,10 +11,19 @@ class TabularSplitStep(PipelineStep): """Split the data.""" - def __init__(self, config: Optional[dict] = None) -> None: + def __init__( + self, + train_percentage: float, + id_column: str, + train_ids: Optional[list[str]] = None, + validation_ids: Optional[list[str]] = None, + ) -> None: """Initialize SplitStep.""" - super().__init__(config=config) self.init_logger() + self.train_percentage = train_percentage + self.id_column_name = id_column + self.train_ids = train_ids + self.validation_ids = validation_ids def _id_based_split( self, @@ -76,50 +85,24 @@ def execute(self, data: DataContainer) -> DataContainer: """Execute the split based on IDs.""" self.logger.info("Splitting tabular data...") - split_configs = self.config - - if split_configs is None: - self.logger.info("No split_configs found. No splitting will be performed.") - return data - df = data[DataContainer.CLEAN] - id_column_name = split_configs.get("id_column") - if not id_column_name: - raise ValueError("ID column name must be specified in split_configs.") - - # check if both train_percentage and train_ids are provided - if "train_percentage" in split_configs and "train_ids" in split_configs: - raise ValueError( - "Both train_percentage and train_ids cannot be provided in split_configs." - ) - - # check if either train_percentage or train_ids are provided - if "train_percentage" not in split_configs and "train_ids" not in split_configs: - raise ValueError( - "Either train_percentage or train_ids must be provided in split_configs." - ) - if "train_percentage" in split_configs: - train_percentage = split_configs.get("train_percentage") - if train_percentage is None or train_percentage <= 0 or train_percentage >= 1: + if self.train_percentage: + if ( + self.train_percentage is None + or self.train_percentage <= 0 + or self.train_percentage >= 1 + ): raise ValueError("train_percentage must be between 0 and 1.") train_ids, validation_ids = self._percentage_based_id_split( - df, train_percentage, id_column_name + df, self.train_percentage, self.id_column_name ) - else: - train_ids = split_configs.get("train_ids") - validation_ids = split_configs.get("validation_ids") - if not train_ids or not validation_ids: - raise ValueError( - "Both train_ids and validation_ids must be provided in split_configs unless" - " train_percentage is specified." - ) self.logger.info(f"Number of train ids: {len(train_ids)}") self.logger.info(f"Number of validation ids: {len(validation_ids)}") train_df, validation_df = self._id_based_split( - df, train_ids, validation_ids, id_column_name + df, train_ids, validation_ids, self.id_column_name ) train_rows = len(train_df) @@ -134,7 +117,9 @@ def execute(self, data: DataContainer) -> DataContainer: f" {validation_rows/total_rows:.2%}" ) - left_ids = df[~df[id_column_name].isin(train_ids + validation_ids)][id_column_name].unique() + left_ids = df[~df[self.id_column_name].isin(train_ids + validation_ids)][ + self.id_column_name + ].unique() self.logger.info(f"Number of IDs left from total df: {len(left_ids)}") self.logger.debug(f"IDs left from total df: {left_ids}") diff --git a/pipeline_lib/implementation/tabular/xgboost/fit_model.py b/pipeline_lib/implementation/tabular/xgboost/fit_model.py index 606e534..b8446c5 100644 --- a/pipeline_lib/implementation/tabular/xgboost/fit_model.py +++ b/pipeline_lib/implementation/tabular/xgboost/fit_model.py @@ -6,6 +6,8 @@ from optuna.pruners import MedianPruner from sklearn.metrics import mean_absolute_error +from typing import Optional + from pipeline_lib.core import DataContainer from pipeline_lib.core.steps import FitModelStep @@ -13,54 +15,61 @@ class XGBoostFitModelStep(FitModelStep): """Fit the model with XGBoost.""" - def execute(self, data: DataContainer) -> DataContainer: - self.logger.debug("Starting model fitting with XGBoost") + def __init__( + self, + target: str, + drop_columns: Optional[list[str]] = None, + xgb_params: Optional[dict] = None, + optuna_params: Optional[dict] = None, + save_path: Optional[str] = None, + ) -> None: + self.init_logger() - start_time = time.time() + if target is None: + raise ValueError("Target column not found in the parameters.") - model_configs = self.config + self.target = target + self.drop_columns = drop_columns - if model_configs is None: - raise ValueError("No model configs found") + if optuna_params and xgb_params: + raise ValueError("Both optuna_params and xgb_params are defined. Please choose one.") - target = model_configs.get("target") + if not optuna_params and not xgb_params: + raise ValueError( + "No parameters defined. Please define either optuna_params or xgb_params." + ) - if target is None: - raise ValueError("Target column not found in model_configs.") + self.xgb_params = xgb_params + self.optuna_params = optuna_params + self.save_path = save_path - data[DataContainer.TARGET] = target + def execute(self, data: DataContainer) -> DataContainer: + self.logger.debug("Starting model fitting with XGBoost") + + start_time = time.time() + + data[DataContainer.TARGET] = self.target df_train = data[DataContainer.TRAIN] df_valid = data[DataContainer.VALIDATION] - drop_columns = model_configs.get("drop_columns") - - if drop_columns: - df_train = df_train.drop(columns=drop_columns) - df_valid = df_valid.drop(columns=drop_columns) + if self.drop_columns: + df_train = df_train.drop(columns=self.drop_columns) + df_valid = df_valid.drop(columns=self.drop_columns) # Prepare the data - X_train = df_train.drop(columns=[target]) - y_train = df_train[target] + X_train = df_train.drop(columns=[self.target]) + y_train = df_train[self.target] - X_valid = df_valid.drop(columns=[target]) - y_valid = df_valid[target] + X_valid = df_valid.drop(columns=[self.target]) + y_valid = df_valid[self.target] - optuna_params = model_configs.get("optuna_params") - xgb_params = model_configs.get("xgb_params") + params = self.xgb_params - if optuna_params and xgb_params: - raise ValueError("Both optuna_params and xgb_params are defined. Please choose one.") - - if not optuna_params and not xgb_params: - raise ValueError( - "No parameters defined. Please define either optuna_params or xgb_params." + if self.optuna_params: + params = self.optimize_with_optuna( + X_train, y_train, X_valid, y_valid, self.optuna_params ) - - params = xgb_params - - if optuna_params: - params = self.optimize_with_optuna(X_train, y_train, X_valid, y_valid, optuna_params) data[DataContainer.TUNING_PARAMS] = params model = xgb.XGBRegressor(**params) @@ -69,10 +78,15 @@ def execute(self, data: DataContainer) -> DataContainer: X_train, y_train, eval_set=[(X_valid, y_valid)], - early_stopping_rounds=model_configs.get("early_stopping_rounds", 100), verbose=True, ) + end_time = time.time() + elapsed_time = end_time - start_time + minutes = int(elapsed_time // 60) + seconds = int(elapsed_time % 60) + self.logger.info(f"XGBoost model fitting took {minutes} minutes and {seconds} seconds.") + # Save the model to the data container data[DataContainer.MODEL] = model @@ -81,7 +95,7 @@ def execute(self, data: DataContainer) -> DataContainer: data[DataContainer.IMPORTANCE] = importance # save model to disk - save_path = model_configs.get("save_path") + save_path = self.save_path if save_path: if not save_path.endswith(".joblib"): @@ -89,11 +103,6 @@ def execute(self, data: DataContainer) -> DataContainer: self.logger.info(f"Saving the model to {save_path}") dump(model, save_path) - end_time = time.time() - elapsed_time = end_time - start_time - minutes = int(elapsed_time // 60) - seconds = int(elapsed_time % 60) - self.logger.info(f"XGBoost model fitting took {minutes} minutes and {seconds} seconds.") return data def optimize_with_optuna(self, X_train, y_train, X_valid, y_valid, optuna_params): diff --git a/pipeline_lib/implementation/tabular/xgboost/predict.py b/pipeline_lib/implementation/tabular/xgboost/predict.py index b0cd492..3f2c56e 100644 --- a/pipeline_lib/implementation/tabular/xgboost/predict.py +++ b/pipeline_lib/implementation/tabular/xgboost/predict.py @@ -3,47 +3,42 @@ from pipeline_lib.core import DataContainer from pipeline_lib.core.steps import PredictStep +from typing import Optional class XGBoostPredictStep(PredictStep): """Obtain the predictions for XGBoost model.""" - def execute(self, data: DataContainer) -> DataContainer: - self.logger.debug("Obtaining predictions for XGBoost model.") - - if not self.config: - raise ValueError("No prediction configs found.") - - load_path = self.config.get("load_path") - if not load_path: - raise ValueError("No load path found in model_configs.") + def __init__( + self, + target: str, + load_path: str, + drop_columns: Optional[list[str]] = None, + ) -> None: + self.init_logger() if not load_path.endswith(".joblib"): raise ValueError("Only joblib format is supported for loading the model.") - model = load(load_path) - model_input = data[DataContainer.CLEAN] + self.target = target + self.load_path = load_path + self.drop_columns = drop_columns - if not isinstance(model_input, pd.DataFrame): - raise ValueError("model_input must be a pandas DataFrame.") + self.model = load(self.load_path) - if self.config: - drop_columns = self.config.get("drop_columns") - if drop_columns: - model_input = model_input.drop(columns=drop_columns) + def execute(self, data: DataContainer) -> DataContainer: + self.logger.debug("Obtaining predictions for XGBoost model.") - target = self.config.get("target") - if target is None: - raise ValueError("Target column not found in model_configs.") - data[DataContainer.TARGET] = target + model_input = data[DataContainer.CLEAN] - predictions = model.predict(model_input.drop(columns=[target])) - else: - predictions = model.predict(model_input) + if self.drop_columns: + model_input = model_input.drop(columns=self.drop_columns) + + predictions = self.model.predict(model_input.drop(columns=[self.target])) predictions_df = pd.DataFrame(predictions, columns=["prediction"]) model_input[DataContainer.PREDICTIONS] = predictions_df - data[DataContainer.MODEL_OUTPUT] = model_input + data[DataContainer.TARGET] = self.target return data From fef94d96f1283a19ad04ca5a5866e7691ceff01b Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Wed, 13 Mar 2024 16:56:31 -0300 Subject: [PATCH 12/33] split step_registry to new class --- pipeline_lib/__init__.py | 6 +- pipeline_lib/core/pipeline.py | 81 +------------- pipeline_lib/core/step_registry.py | 73 +++++++++++++ pipeline_lib/core/steps/calculate_metrics.py | 2 - pipeline_lib/core/steps/tabular_split.py | 101 ++---------------- .../tabular/xgboost/fit_model.py | 3 +- .../implementation/tabular/xgboost/predict.py | 3 +- 7 files changed, 92 insertions(+), 177 deletions(-) create mode 100644 pipeline_lib/core/step_registry.py diff --git a/pipeline_lib/__init__.py b/pipeline_lib/__init__.py index a6c537f..0c99341 100644 --- a/pipeline_lib/__init__.py +++ b/pipeline_lib/__init__.py @@ -1,4 +1,6 @@ from .core.pipeline import Pipeline -Pipeline.auto_register_steps_from_package("pipeline_lib.core.steps") -Pipeline.auto_register_steps_from_package("pipeline_lib.implementation.tabular.xgboost") +Pipeline.step_registry.auto_register_steps_from_package("pipeline_lib.core.steps") +Pipeline.step_registry.auto_register_steps_from_package( + "pipeline_lib.implementation.tabular.xgboost" +) diff --git a/pipeline_lib/core/pipeline.py b/pipeline_lib/core/pipeline.py index bcea4fe..b29f6ca 100644 --- a/pipeline_lib/core/pipeline.py +++ b/pipeline_lib/core/pipeline.py @@ -1,13 +1,11 @@ from __future__ import annotations -import importlib import json import logging -import os -import pkgutil from typing import Optional from pipeline_lib.core.data_container import DataContainer +from pipeline_lib.core.step_registry import StepRegistry from pipeline_lib.core.steps import PipelineStep @@ -16,6 +14,7 @@ class Pipeline: _step_registry = {} logger = logging.getLogger("Pipeline") + step_registry = StepRegistry() def __init__(self, initial_data: Optional[DataContainer] = None): self.steps = [] @@ -23,82 +22,10 @@ def __init__(self, initial_data: Optional[DataContainer] = None): self.save_path = None self.load_path = None - @classmethod - def register_step(cls, step_class): - """Register a step class using its class name.""" - step_name = step_class.__name__ - if not issubclass(step_class, PipelineStep): - raise ValueError(f"{step_class} must be a subclass of PipelineStep") - cls._step_registry[step_name] = step_class - - @classmethod - def get_step_class(cls, step_name): - """Retrieve a step class by name.""" - if step_name in cls._step_registry: - return cls._step_registry[step_name] - else: - raise ValueError(f"Step class '{step_name}' not found in registry.") - def add_steps(self, steps: list[PipelineStep]): """Add steps to the pipeline.""" self.steps.extend(steps) - @classmethod - def auto_register_steps_from_package(cls, package_name): - """ - Automatically registers all step classes found within a specified package. - """ - package = importlib.import_module(package_name) - prefix = package.__name__ + "." - for importer, modname, ispkg in pkgutil.walk_packages(package.__path__, prefix): - module = importlib.import_module(modname) - for name in dir(module): - attribute = getattr(module, name) - if ( - isinstance(attribute, type) - and issubclass(attribute, PipelineStep) - and attribute is not PipelineStep - ): - cls.register_step(attribute) - - @classmethod - def load_and_register_custom_steps(cls, custom_steps_path: str) -> None: - """ - Dynamically loads and registers step classes found in the specified directory. - - This method scans a specified directory for Python files (excluding __init__.py), - dynamically imports these files as modules, and registers all classes derived from - PipelineStep found within these modules. - - Parameters - ---------- - custom_steps_path : str - The path to the directory containing custom step implementation files. - """ - Pipeline.logger.debug(f"Loading custom steps from: {custom_steps_path}") - for filename in os.listdir(custom_steps_path): - if filename.endswith(".py") and not filename.startswith("__"): - filepath = os.path.join(custom_steps_path, filename) - module_name = os.path.splitext(filename)[0] - spec = importlib.util.spec_from_file_location(module_name, filepath) - module = importlib.util.module_from_spec(spec) - - try: - spec.loader.exec_module(module) - Pipeline.logger.debug(f"Successfully loaded module: {module_name}") - - for attribute_name in dir(module): - attribute = getattr(module, attribute_name) - if ( - isinstance(attribute, type) - and issubclass(attribute, PipelineStep) - and attribute is not PipelineStep - ): - Pipeline.register_step(attribute) - Pipeline.logger.debug(f"Registered step class: {attribute_name}") - except Exception as e: - Pipeline.logger.error(f"Failed to load module: {module_name}. Error: {e}") - def run(self) -> DataContainer: """Run the pipeline on the given data.""" @@ -125,7 +52,7 @@ def from_json(cls, path: str) -> Pipeline: custom_steps_path = config.get("custom_steps_path") if custom_steps_path: - Pipeline.load_and_register_custom_steps(custom_steps_path) + cls.step_registry.load_and_register_custom_steps(custom_steps_path) pipeline = Pipeline() @@ -142,7 +69,7 @@ def from_json(cls, path: str) -> Pipeline: f"Creating step {step_type} with parameters: \n {json.dumps(parameters, indent=4)}" ) - step_class = Pipeline.get_step_class(step_type) + step_class = cls.step_registry.get_step_class(step_type) step = step_class(**parameters) steps.append(step) diff --git a/pipeline_lib/core/step_registry.py b/pipeline_lib/core/step_registry.py new file mode 100644 index 0000000..f4c6f57 --- /dev/null +++ b/pipeline_lib/core/step_registry.py @@ -0,0 +1,73 @@ +import importlib +import logging +import os +import pkgutil + +from pipeline_lib.core.steps import PipelineStep + + +class StepRegistry: + """A helper class for managing the registry of pipeline steps.""" + + def __init__(self): + self._step_registry = {} + self.logger = logging.getLogger(StepRegistry.__name__) + + def register_step(self, step_class): + """Register a step class using its class name.""" + step_name = step_class.__name__ + if not issubclass(step_class, PipelineStep): + raise ValueError(f"{step_class} must be a subclass of PipelineStep") + self._step_registry[step_name] = step_class + + def get_step_class(self, step_name): + """Retrieve a step class by name.""" + if step_name in self._step_registry: + return self._step_registry[step_name] + else: + raise ValueError(f"Step class '{step_name}' not found in registry.") + + def auto_register_steps_from_package(self, package_name): + """ + Automatically registers all step classes found within a specified package. + """ + package = importlib.import_module(package_name) + prefix = package.__name__ + "." + for importer, modname, ispkg in pkgutil.walk_packages(package.__path__, prefix): + module = importlib.import_module(modname) + for name in dir(module): + attribute = getattr(module, name) + if ( + isinstance(attribute, type) + and issubclass(attribute, PipelineStep) + and attribute is not PipelineStep + ): + self.register_step(attribute) + + def load_and_register_custom_steps(self, custom_steps_path: str) -> None: + """ + Dynamically loads and registers step classes found in the specified directory. + """ + self.logger.debug(f"Loading custom steps from: {custom_steps_path}") + for filename in os.listdir(custom_steps_path): + if filename.endswith(".py") and not filename.startswith("__"): + filepath = os.path.join(custom_steps_path, filename) + module_name = os.path.splitext(filename)[0] + spec = importlib.util.spec_from_file_location(module_name, filepath) + module = importlib.util.module_from_spec(spec) + + try: + spec.loader.exec_module(module) + self.logger.debug(f"Successfully loaded module: {module_name}") + + for attribute_name in dir(module): + attribute = getattr(module, attribute_name) + if ( + isinstance(attribute, type) + and issubclass(attribute, PipelineStep) + and attribute is not PipelineStep + ): + self.register_step(attribute) + self.logger.debug(f"Registered step class: {attribute_name}") + except Exception as e: + self.logger.error(f"Failed to load module: {module_name}. Error: {e}") diff --git a/pipeline_lib/core/steps/calculate_metrics.py b/pipeline_lib/core/steps/calculate_metrics.py index 902a42f..85454d3 100644 --- a/pipeline_lib/core/steps/calculate_metrics.py +++ b/pipeline_lib/core/steps/calculate_metrics.py @@ -1,5 +1,3 @@ -from typing import Optional - import numpy as np from sklearn.metrics import mean_absolute_error, mean_squared_error diff --git a/pipeline_lib/core/steps/tabular_split.py b/pipeline_lib/core/steps/tabular_split.py index 44fc8a2..c6333cf 100644 --- a/pipeline_lib/core/steps/tabular_split.py +++ b/pipeline_lib/core/steps/tabular_split.py @@ -1,6 +1,3 @@ -from typing import Optional, Tuple - -import pandas as pd from sklearn.model_selection import train_test_split from pipeline_lib.core import DataContainer @@ -11,98 +8,22 @@ class TabularSplitStep(PipelineStep): """Split the data.""" - def __init__( - self, - train_percentage: float, - id_column: str, - train_ids: Optional[list[str]] = None, - validation_ids: Optional[list[str]] = None, - ) -> None: + def __init__(self, train_percentage: float) -> None: """Initialize SplitStep.""" self.init_logger() self.train_percentage = train_percentage - self.id_column_name = id_column - self.train_ids = train_ids - self.validation_ids = validation_ids - - def _id_based_split( - self, - df: pd.DataFrame, - train_ids: list[str], - validation_ids: list[str], - id_column_name: str, - ) -> Tuple[pd.DataFrame, pd.DataFrame]: - """ - Splits the DataFrame into training and validation sets based on specified IDs. - - Parameters - ---------- - df : pd.DataFrame - The DataFrame to split. - train_ids : List[str] - List of IDs for the training set. - validation_ids : List[str] - List of IDs for the validation set. - id_column_name : str - The name of the column in df that contains the IDs. - - Returns - ------- - Tuple[pd.DataFrame, pd.DataFrame] - A tuple containing the training set and the validation set. - """ - train_df = df[df[id_column_name].isin(train_ids)] - validation_df = df[df[id_column_name].isin(validation_ids)] - return train_df, validation_df - - def _percentage_based_id_split( - self, df: pd.DataFrame, train_percentage: float, id_column_name: str - ) -> Tuple[list[str], list[str]]: - """ - Splits the unique IDs into training and validation sets based on specified percentages. - Parameters - ---------- - df : pd.DataFrame - The DataFrame containing the IDs. - train_percentage : float - The percentage of IDs to include in the training set. - id_column_name : str - The name of the column containing the IDs. - - Returns - ------- - Tuple[List[str], List[str]] - A tuple containing lists of training and validation IDs. - """ - unique_ids = df[id_column_name].unique() - train_ids, validation_ids = train_test_split( - unique_ids, train_size=train_percentage, random_state=42 - ) - return train_ids.tolist(), validation_ids.tolist() + if self.train_percentage <= 0 or self.train_percentage >= 1: + raise ValueError("train_percentage must be between 0 and 1.") def execute(self, data: DataContainer) -> DataContainer: - """Execute the split based on IDs.""" + """Execute the random train-validation split.""" self.logger.info("Splitting tabular data...") df = data[DataContainer.CLEAN] - if self.train_percentage: - if ( - self.train_percentage is None - or self.train_percentage <= 0 - or self.train_percentage >= 1 - ): - raise ValueError("train_percentage must be between 0 and 1.") - train_ids, validation_ids = self._percentage_based_id_split( - df, self.train_percentage, self.id_column_name - ) - - self.logger.info(f"Number of train ids: {len(train_ids)}") - self.logger.info(f"Number of validation ids: {len(validation_ids)}") - - train_df, validation_df = self._id_based_split( - df, train_ids, validation_ids, self.id_column_name + train_df, validation_df = train_test_split( + df, train_size=self.train_percentage, random_state=42 ) train_rows = len(train_df) @@ -110,19 +31,13 @@ def execute(self, data: DataContainer) -> DataContainer: total_rows = train_rows + validation_rows self.logger.info( - f"Number of rows in training set: {len(train_df)} | {train_rows/total_rows:.2%}" + f"Number of rows in training set: {train_rows} | {train_rows/total_rows:.2%}" ) self.logger.info( - f"Number of rows in validation set: {len(validation_df)} |" + f"Number of rows in validation set: {validation_rows} |" f" {validation_rows/total_rows:.2%}" ) - left_ids = df[~df[self.id_column_name].isin(train_ids + validation_ids)][ - self.id_column_name - ].unique() - self.logger.info(f"Number of IDs left from total df: {len(left_ids)}") - self.logger.debug(f"IDs left from total df: {left_ids}") - data[DataContainer.TRAIN] = train_df data[DataContainer.VALIDATION] = validation_df diff --git a/pipeline_lib/implementation/tabular/xgboost/fit_model.py b/pipeline_lib/implementation/tabular/xgboost/fit_model.py index b8446c5..d0a538f 100644 --- a/pipeline_lib/implementation/tabular/xgboost/fit_model.py +++ b/pipeline_lib/implementation/tabular/xgboost/fit_model.py @@ -1,4 +1,5 @@ import time +from typing import Optional import optuna import xgboost as xgb @@ -6,8 +7,6 @@ from optuna.pruners import MedianPruner from sklearn.metrics import mean_absolute_error -from typing import Optional - from pipeline_lib.core import DataContainer from pipeline_lib.core.steps import FitModelStep diff --git a/pipeline_lib/implementation/tabular/xgboost/predict.py b/pipeline_lib/implementation/tabular/xgboost/predict.py index 3f2c56e..9dcd57f 100644 --- a/pipeline_lib/implementation/tabular/xgboost/predict.py +++ b/pipeline_lib/implementation/tabular/xgboost/predict.py @@ -1,9 +1,10 @@ +from typing import Optional + import pandas as pd from joblib import load from pipeline_lib.core import DataContainer from pipeline_lib.core.steps import PredictStep -from typing import Optional class XGBoostPredictStep(PredictStep): From 14e05b9415193b4ec20bb59ec5c54ceba1a04bd9 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Wed, 13 Mar 2024 17:15:03 -0300 Subject: [PATCH 13/33] refactor step registry --- pipeline_lib/core/step_registry.py | 45 +++++++++++++++++++----------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/pipeline_lib/core/step_registry.py b/pipeline_lib/core/step_registry.py index f4c6f57..9a1b800 100644 --- a/pipeline_lib/core/step_registry.py +++ b/pipeline_lib/core/step_registry.py @@ -6,43 +6,54 @@ from pipeline_lib.core.steps import PipelineStep +class StepClassNotFoundError(Exception): + pass + + class StepRegistry: """A helper class for managing the registry of pipeline steps.""" def __init__(self): self._step_registry = {} - self.logger = logging.getLogger(StepRegistry.__name__) + self.logger = logging.getLogger(__name__) - def register_step(self, step_class): + def register_step(self, step_class: type): """Register a step class using its class name.""" step_name = step_class.__name__ if not issubclass(step_class, PipelineStep): raise ValueError(f"{step_class} must be a subclass of PipelineStep") self._step_registry[step_name] = step_class - def get_step_class(self, step_name): + def get_step_class(self, step_name: str) -> type: """Retrieve a step class by name.""" if step_name in self._step_registry: return self._step_registry[step_name] else: - raise ValueError(f"Step class '{step_name}' not found in registry.") + raise StepClassNotFoundError(f"Step class '{step_name}' not found in registry.") + + def get_all_step_classes(self) -> dict: + """Retrieve all registered step classes.""" + return self._step_registry - def auto_register_steps_from_package(self, package_name): + def auto_register_steps_from_package(self, package_name: str): """ Automatically registers all step classes found within a specified package. """ - package = importlib.import_module(package_name) - prefix = package.__name__ + "." - for importer, modname, ispkg in pkgutil.walk_packages(package.__path__, prefix): - module = importlib.import_module(modname) - for name in dir(module): - attribute = getattr(module, name) - if ( - isinstance(attribute, type) - and issubclass(attribute, PipelineStep) - and attribute is not PipelineStep - ): - self.register_step(attribute) + try: + package = importlib.import_module(package_name) + prefix = package.__name__ + "." + for importer, modname, ispkg in pkgutil.walk_packages(package.__path__, prefix): + module = importlib.import_module(modname) + for name in dir(module): + attribute = getattr(module, name) + if ( + isinstance(attribute, type) + and issubclass(attribute, PipelineStep) + and attribute is not PipelineStep + ): + self.register_step(attribute) + except ImportError as e: + self.logger.error(f"Failed to import package: {package_name}. Error: {e}") def load_and_register_custom_steps(self, custom_steps_path: str) -> None: """ From 8d91815190ceb922db4ba0214ba5ef14ba5de5ba Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Wed, 13 Mar 2024 21:08:04 -0300 Subject: [PATCH 14/33] improve readme --- README.md | 49 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f5e0588..bd66caf 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,47 @@ # Pipeline Library -The purpose of this library is to create pipelines for ML as simple as possible. At the moment we support XGBoost models, but we are working to support more models. +The Pipeline Library is designed to simplify the creation of machine learning pipelines. Currently, it supports XGBoost models, with plans to expand support for more models in the future. -This is an example of how to use the library to run an XGBoost pipeline: +## Installation + +To install the Pipeline Library, you need to have Python 3.9 or higher and Poetry installed. Follow these steps: + +1. Clone the repository: + + ```bash + git clone https://github.com/tryolabs/pipeline-lib.git + ``` + +2. Navigate to the project directory: + + ```bash + cd pipeline-lib + ``` + +3. Install the dependencies using Poetry: + + ```bash + poetry install + ``` + + If you want to include optional dependencies, you can specify the extras: + + ```bash + poetry install --extras "xgboost" + ``` + + or + + ```bash + poetry install --extras "all_models" + ``` + +## Usage + +Here's an example of how to use the library to run an XGBoost pipeline: + +1. Create a `train.json` file with the following content: -We create a `train.json` file with the following content: ```json { @@ -57,7 +94,7 @@ We create a `train.json` file with the following content: } ``` -The user can define custom steps to generate and clean their own data and use them in the pipeline. Then we can run the pipeline with the following code: +2. Run the pipeline using the following code: ```python import logging @@ -67,4 +104,6 @@ from pipeline_lib.core import Pipeline logging.basicConfig(level=logging.INFO) Pipeline.from_json("train.json").run() -``` \ No newline at end of file +``` + +The library allows users to define custom steps for generating and cleaning their own data, which can be used in the pipeline. From 4819fadcdf9c5ce6642f15e57c7c3b3a6cfa62ba Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Thu, 14 Mar 2024 10:30:20 -0300 Subject: [PATCH 15/33] improve explainer dashboard --- pipeline_lib/core/data_container.py | 1 + pipeline_lib/core/steps/__init__.py | 1 + .../core/steps/explainer_dashboard.py | 47 +++++++++++++++---- .../tabular/xgboost/__init__.py | 1 - .../tabular/xgboost/explainer_dashboard.py | 40 ---------------- .../tabular/xgboost/fit_model.py | 1 + .../implementation/tabular/xgboost/predict.py | 2 + 7 files changed, 42 insertions(+), 51 deletions(-) delete mode 100644 pipeline_lib/implementation/tabular/xgboost/explainer_dashboard.py diff --git a/pipeline_lib/core/data_container.py b/pipeline_lib/core/data_container.py index a0393e6..8fe73c5 100644 --- a/pipeline_lib/core/data_container.py +++ b/pipeline_lib/core/data_container.py @@ -35,6 +35,7 @@ class DataContainer: TUNING_PARAMS = "tuning_params" TARGET = "target" IMPORTANCE = "importance" + DROP_COLUMNS = "drop_columns" def __init__(self, initial_data: Optional[dict] = None): """ diff --git a/pipeline_lib/core/steps/__init__.py b/pipeline_lib/core/steps/__init__.py index 9c8593c..56f63c1 100644 --- a/pipeline_lib/core/steps/__init__.py +++ b/pipeline_lib/core/steps/__init__.py @@ -11,3 +11,4 @@ from .predict import PredictStep # noqa: F401 from .tabular_split import TabularSplitStep # noqa: F401 from .target_scaling import TargetScalingStep # noqa: F401 +from .explainer_dashboard import ExplainerDashboardStep # noqa: F401 diff --git a/pipeline_lib/core/steps/explainer_dashboard.py b/pipeline_lib/core/steps/explainer_dashboard.py index 98bf397..3ebcf67 100644 --- a/pipeline_lib/core/steps/explainer_dashboard.py +++ b/pipeline_lib/core/steps/explainer_dashboard.py @@ -1,19 +1,46 @@ -from typing import Optional +import pandas as pd +from explainerdashboard import RegressionExplainer from pipeline_lib.core import DataContainer - -from .base import PipelineStep +from pipeline_lib.core.steps import PipelineStep class ExplainerDashboardStep(PipelineStep): - """Explainer Dashboard.""" - - def __init__(self, config: Optional[dict] = None) -> None: - """Initialize ExplainerDashboardStep.""" - super().__init__(config=config) + """Scale the target using Quantile Transformer.""" + def __init__( + self, + max_samples: int = 1000, + ) -> None: self.init_logger() + self.max_samples = max_samples def execute(self, data: DataContainer) -> DataContainer: - """Execute the step.""" - self.logger.info("Creating explainer dashboard.") + self.logger.debug("Starting explainer dashboard") + + model = data.get(DataContainer.MODEL) + if model is None: + raise ValueError("Model not found in data container.") + + target = data.get(DataContainer.TARGET) + if target is None: + raise ValueError("Target column not found in any parameter.") + + df = data.get(DataContainer.CLEAN) + + if len(df) > self.max_samples: + # Randomly sample a subset of data points if the dataset is larger than max_samples + self.logger.info(f"Sampling {self.max_samples} data points from the dataset.") + df = df.sample(n=self.max_samples, random_state=42) + + drop_columns = data.get("drop_columns") + if drop_columns: + df = df.drop(columns=drop_columns) + + X_test = df.drop(columns=[target]) + y_test = df[target] + + explainer = RegressionExplainer(model, X_test, y_test,) + + data[DataContainer.EXPLAINER] = explainer + return data diff --git a/pipeline_lib/implementation/tabular/xgboost/__init__.py b/pipeline_lib/implementation/tabular/xgboost/__init__.py index b299d0c..2887234 100644 --- a/pipeline_lib/implementation/tabular/xgboost/__init__.py +++ b/pipeline_lib/implementation/tabular/xgboost/__init__.py @@ -1,3 +1,2 @@ -from .explainer_dashboard import XGBoostExplainerDashboardStep # noqa: F401 from .fit_model import XGBoostFitModelStep # noqa: F401 from .predict import XGBoostPredictStep # noqa: F401 diff --git a/pipeline_lib/implementation/tabular/xgboost/explainer_dashboard.py b/pipeline_lib/implementation/tabular/xgboost/explainer_dashboard.py deleted file mode 100644 index f1b319d..0000000 --- a/pipeline_lib/implementation/tabular/xgboost/explainer_dashboard.py +++ /dev/null @@ -1,40 +0,0 @@ -from explainerdashboard import RegressionExplainer - -from pipeline_lib.core import DataContainer -from pipeline_lib.core.steps import ExplainerDashboardStep - - -class XGBoostExplainerDashboardStep(ExplainerDashboardStep): - """Scale the target using Quantile Transformer.""" - - def execute(self, data: DataContainer) -> DataContainer: - self.logger.debug("Starting explainer dashboard") - - model = data.get(DataContainer.MODEL) - if model is None: - raise ValueError("Model not found in data container.") - - val_df = data.get(DataContainer.VALIDATION) - if val_df is None: - raise ValueError("Validation data not found in data container.") - - model_configs = data[DataContainer.MODEL_CONFIGS] - if model_configs is None: - raise ValueError("Model configs not found in data container.") - - target = model_configs.get("target") - if target is None: - raise ValueError("Target column not found in model_configs.") - - drop_columns = model_configs.get("drop_columns") - if drop_columns: - val_df = val_df.drop(columns=drop_columns) - - X_test = val_df.drop(columns=[target]) - y_test = val_df[target] - - explainer = RegressionExplainer(model, X_test, y_test) - - data[DataContainer.EXPLAINER] = explainer - - return data diff --git a/pipeline_lib/implementation/tabular/xgboost/fit_model.py b/pipeline_lib/implementation/tabular/xgboost/fit_model.py index d0a538f..ea8e44f 100644 --- a/pipeline_lib/implementation/tabular/xgboost/fit_model.py +++ b/pipeline_lib/implementation/tabular/xgboost/fit_model.py @@ -48,6 +48,7 @@ def execute(self, data: DataContainer) -> DataContainer: start_time = time.time() data[DataContainer.TARGET] = self.target + data[DataContainer.DROP_COLUMNS] = self.drop_columns df_train = data[DataContainer.TRAIN] df_valid = data[DataContainer.VALIDATION] diff --git a/pipeline_lib/implementation/tabular/xgboost/predict.py b/pipeline_lib/implementation/tabular/xgboost/predict.py index 9dcd57f..ce9b62f 100644 --- a/pipeline_lib/implementation/tabular/xgboost/predict.py +++ b/pipeline_lib/implementation/tabular/xgboost/predict.py @@ -40,6 +40,8 @@ def execute(self, data: DataContainer) -> DataContainer: predictions_df = pd.DataFrame(predictions, columns=["prediction"]) model_input[DataContainer.PREDICTIONS] = predictions_df + data[DataContainer.MODEL] = self.model data[DataContainer.MODEL_OUTPUT] = model_input data[DataContainer.TARGET] = self.target + data[DataContainer.DROP_COLUMNS] = self.drop_columns return data From 548dac44fbcddf58dbad7f10a75923735dabc280 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Thu, 14 Mar 2024 15:05:17 -0300 Subject: [PATCH 16/33] fix flake8 errors --- pipeline_lib/core/steps/__init__.py | 1 - pipeline_lib/core/steps/explainer_dashboard.py | 12 ++++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pipeline_lib/core/steps/__init__.py b/pipeline_lib/core/steps/__init__.py index 56f63c1..9c8593c 100644 --- a/pipeline_lib/core/steps/__init__.py +++ b/pipeline_lib/core/steps/__init__.py @@ -11,4 +11,3 @@ from .predict import PredictStep # noqa: F401 from .tabular_split import TabularSplitStep # noqa: F401 from .target_scaling import TargetScalingStep # noqa: F401 -from .explainer_dashboard import ExplainerDashboardStep # noqa: F401 diff --git a/pipeline_lib/core/steps/explainer_dashboard.py b/pipeline_lib/core/steps/explainer_dashboard.py index 3ebcf67..41d3802 100644 --- a/pipeline_lib/core/steps/explainer_dashboard.py +++ b/pipeline_lib/core/steps/explainer_dashboard.py @@ -1,4 +1,3 @@ -import pandas as pd from explainerdashboard import RegressionExplainer from pipeline_lib.core import DataContainer @@ -7,6 +6,7 @@ class ExplainerDashboardStep(PipelineStep): """Scale the target using Quantile Transformer.""" + def __init__( self, max_samples: int = 1000, @@ -29,6 +29,10 @@ def execute(self, data: DataContainer) -> DataContainer: if len(df) > self.max_samples: # Randomly sample a subset of data points if the dataset is larger than max_samples + self.logger.info( + f"Dataset contains {len(df)} data points and max_samples is set to" + f" {self.max_samples}." + ) self.logger.info(f"Sampling {self.max_samples} data points from the dataset.") df = df.sample(n=self.max_samples, random_state=42) @@ -39,7 +43,11 @@ def execute(self, data: DataContainer) -> DataContainer: X_test = df.drop(columns=[target]) y_test = df[target] - explainer = RegressionExplainer(model, X_test, y_test,) + explainer = RegressionExplainer( + model, + X_test, + y_test, + ) data[DataContainer.EXPLAINER] = explainer From 67978af11c9f88534b935c2001440316df3a54f7 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Thu, 14 Mar 2024 15:06:52 -0300 Subject: [PATCH 17/33] remove importance in XGBoost Fit since we have Explainer Dashboard --- README.md | 1 - pipeline_lib/implementation/tabular/xgboost/fit_model.py | 4 ---- 2 files changed, 5 deletions(-) diff --git a/README.md b/README.md index bd66caf..44a3b56 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,6 @@ Here's an example of how to use the library to run an XGBoost pipeline: ```json { "custom_steps_path": "examples/ocf/", - "save_path": "runs/xgboost_train.pkl", "pipeline": { "name": "XGBoostTrainingPipeline", "description": "Training pipeline for XGBoost models.", diff --git a/pipeline_lib/implementation/tabular/xgboost/fit_model.py b/pipeline_lib/implementation/tabular/xgboost/fit_model.py index ea8e44f..3b43823 100644 --- a/pipeline_lib/implementation/tabular/xgboost/fit_model.py +++ b/pipeline_lib/implementation/tabular/xgboost/fit_model.py @@ -90,10 +90,6 @@ def execute(self, data: DataContainer) -> DataContainer: # Save the model to the data container data[DataContainer.MODEL] = model - importance = model.get_booster().get_score(importance_type="gain") - importance = dict(sorted(importance.items(), key=lambda item: item[1], reverse=True)) - data[DataContainer.IMPORTANCE] = importance - # save model to disk save_path = self.save_path From 86edc6570df54fa986456e8dfdcf8d929e50cbf5 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Mon, 18 Mar 2024 12:49:55 -0300 Subject: [PATCH 18/33] add generate and clean steps to core --- .gitignore | 4 + pipeline_lib/core/__init__.py | 1 - pipeline_lib/core/step_registry.py | 2 +- pipeline_lib/core/steps/__init__.py | 2 + pipeline_lib/core/steps/augment.py | 3 +- pipeline_lib/core/steps/base.py | 2 +- pipeline_lib/core/steps/calculate_features.py | 3 +- pipeline_lib/core/steps/calculate_metrics.py | 3 +- pipeline_lib/core/steps/calculate_reports.py | 3 +- pipeline_lib/core/steps/clean.py | 69 + pipeline_lib/core/steps/encode.py | 3 +- .../core/steps/explainer_dashboard.py | 2 +- pipeline_lib/core/steps/fit_encoders.py | 3 +- pipeline_lib/core/steps/fit_model.py | 3 +- pipeline_lib/core/steps/generate.py | 65 + pipeline_lib/core/steps/input_scaling.py | 3 +- pipeline_lib/core/steps/predict.py | 3 +- pipeline_lib/core/steps/tabular_split.py | 3 +- pipeline_lib/core/steps/target_scaling.py | 3 +- .../tabular/xgboost/fit_model.py | 17 +- poetry.lock | 2924 ++++++++--------- 21 files changed, 1572 insertions(+), 1549 deletions(-) create mode 100644 pipeline_lib/core/steps/clean.py create mode 100644 pipeline_lib/core/steps/generate.py diff --git a/.gitignore b/.gitignore index 2a7ca1a..912a3d9 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,10 @@ __pycache__/ # C extensions *.so +*.joblib +*.bin +*.json + # ignore examples folder examples/ diff --git a/pipeline_lib/core/__init__.py b/pipeline_lib/core/__init__.py index 3e73bca..eb20821 100644 --- a/pipeline_lib/core/__init__.py +++ b/pipeline_lib/core/__init__.py @@ -1,3 +1,2 @@ from .data_container import DataContainer # noqa: F401 -from .pipeline import Pipeline # noqa: F401 from .steps import PipelineStep # noqa: F401 diff --git a/pipeline_lib/core/step_registry.py b/pipeline_lib/core/step_registry.py index 9a1b800..e415eba 100644 --- a/pipeline_lib/core/step_registry.py +++ b/pipeline_lib/core/step_registry.py @@ -3,7 +3,7 @@ import os import pkgutil -from pipeline_lib.core.steps import PipelineStep +from pipeline_lib.core.steps.base import PipelineStep class StepClassNotFoundError(Exception): diff --git a/pipeline_lib/core/steps/__init__.py b/pipeline_lib/core/steps/__init__.py index 9c8593c..779f096 100644 --- a/pipeline_lib/core/steps/__init__.py +++ b/pipeline_lib/core/steps/__init__.py @@ -3,10 +3,12 @@ from .calculate_features import CalculateFeaturesStep # noqa: F401 from .calculate_metrics import CalculateMetricsStep # noqa: F401 from .calculate_reports import CalculateReportsStep # noqa: F401 +from .clean import CleanStep # noqa: F401 from .encode import EncodeStep # noqa: F401 from .explainer_dashboard import ExplainerDashboardStep # noqa: F401 from .fit_encoders import FitEncodersStep # noqa: F401 from .fit_model import FitModelStep # noqa: F401 +from .generate import GenerateStep # noqa: F401 from .input_scaling import InputScalingStep # noqa: F401 from .predict import PredictStep # noqa: F401 from .tabular_split import TabularSplitStep # noqa: F401 diff --git a/pipeline_lib/core/steps/augment.py b/pipeline_lib/core/steps/augment.py index 895d5c8..21708d7 100644 --- a/pipeline_lib/core/steps/augment.py +++ b/pipeline_lib/core/steps/augment.py @@ -1,8 +1,7 @@ from typing import Optional from pipeline_lib.core import DataContainer - -from .base import PipelineStep +from pipeline_lib.core.steps.base import PipelineStep class AugmentStep(PipelineStep): diff --git a/pipeline_lib/core/steps/base.py b/pipeline_lib/core/steps/base.py index 9530368..2381422 100644 --- a/pipeline_lib/core/steps/base.py +++ b/pipeline_lib/core/steps/base.py @@ -2,7 +2,7 @@ from abc import ABC, abstractmethod from typing import Optional -from pipeline_lib.core import DataContainer +from pipeline_lib.core.data_container import DataContainer class PipelineStep(ABC): diff --git a/pipeline_lib/core/steps/calculate_features.py b/pipeline_lib/core/steps/calculate_features.py index eb145c1..0316cb1 100644 --- a/pipeline_lib/core/steps/calculate_features.py +++ b/pipeline_lib/core/steps/calculate_features.py @@ -1,8 +1,7 @@ from typing import Optional from pipeline_lib.core import DataContainer - -from .base import PipelineStep +from pipeline_lib.core.steps.base import PipelineStep class CalculateFeaturesStep(PipelineStep): diff --git a/pipeline_lib/core/steps/calculate_metrics.py b/pipeline_lib/core/steps/calculate_metrics.py index 85454d3..8589283 100644 --- a/pipeline_lib/core/steps/calculate_metrics.py +++ b/pipeline_lib/core/steps/calculate_metrics.py @@ -2,8 +2,7 @@ from sklearn.metrics import mean_absolute_error, mean_squared_error from pipeline_lib.core import DataContainer - -from .base import PipelineStep +from pipeline_lib.core.steps.base import PipelineStep class CalculateMetricsStep(PipelineStep): diff --git a/pipeline_lib/core/steps/calculate_reports.py b/pipeline_lib/core/steps/calculate_reports.py index 75129cd..39cab38 100644 --- a/pipeline_lib/core/steps/calculate_reports.py +++ b/pipeline_lib/core/steps/calculate_reports.py @@ -1,8 +1,7 @@ from typing import Optional from pipeline_lib.core import DataContainer - -from .base import PipelineStep +from pipeline_lib.core.steps.base import PipelineStep class CalculateReportsStep(PipelineStep): diff --git a/pipeline_lib/core/steps/clean.py b/pipeline_lib/core/steps/clean.py new file mode 100644 index 0000000..0341f5a --- /dev/null +++ b/pipeline_lib/core/steps/clean.py @@ -0,0 +1,69 @@ +from typing import Optional + +from pipeline_lib.core import DataContainer +from pipeline_lib.core.steps.base import PipelineStep + + +class CleanStep(PipelineStep): + def __init__( + self, + fill_missing: Optional[dict] = None, + remove_outliers: Optional[dict] = None, + convert_dtypes: Optional[dict] = None, + ): + self.init_logger() + self.fill_missing = fill_missing + self.remove_outliers = remove_outliers + self.convert_dtypes = convert_dtypes + + def execute(self, data: DataContainer) -> DataContainer: + self.logger.info("Cleaning tabular data...") + + df = data[DataContainer.RAW] + + if self.fill_missing: + for column, fill_value in self.fill_missing.items(): + if column in df.columns: + df[column].fillna(fill_value, inplace=True) + self.logger.info( + f"Filled missing values in column '{column}' with {fill_value}" + ) + else: + self.logger.warning(f"Column '{column}' not found in the DataFrame") + + if self.remove_outliers: + for column, method in self.remove_outliers.items(): + if column in df.columns: + if method == "clip": + q1 = df[column].quantile(0.25) + q3 = df[column].quantile(0.75) + iqr = q3 - q1 + lower_bound = q1 - (1.5 * iqr) + upper_bound = q3 + (1.5 * iqr) + df[column] = df[column].clip(lower=lower_bound, upper=upper_bound) + self.logger.info(f"Clipped outliers in column '{column}'") + elif method == "drop": + q1 = df[column].quantile(0.25) + q3 = df[column].quantile(0.75) + iqr = q3 - q1 + lower_bound = q1 - (1.5 * iqr) + upper_bound = q3 + (1.5 * iqr) + outliers = (df[column] < lower_bound) | (df[column] > upper_bound) + df = df[~outliers] + self.logger.info(f"Dropped outliers in column '{column}'") + else: + self.logger.warning(f"Unsupported outlier removal method '{method}'") + else: + self.logger.warning(f"Column '{column}' not found in the DataFrame") + + if self.convert_dtypes: + for column, dtype in self.convert_dtypes.items(): + if column in df.columns: + df[column] = df[column].astype(dtype) + self.logger.info(f"Converted column '{column}' to {dtype}") + else: + self.logger.warning(f"Column '{column}' not found in the DataFrame") + + data[DataContainer.CLEAN] = df + + return data diff --git a/pipeline_lib/core/steps/encode.py b/pipeline_lib/core/steps/encode.py index 5e07ceb..c81951b 100644 --- a/pipeline_lib/core/steps/encode.py +++ b/pipeline_lib/core/steps/encode.py @@ -1,8 +1,7 @@ from typing import Optional from pipeline_lib.core import DataContainer - -from .base import PipelineStep +from pipeline_lib.core.steps.base import PipelineStep class EncodeStep(PipelineStep): diff --git a/pipeline_lib/core/steps/explainer_dashboard.py b/pipeline_lib/core/steps/explainer_dashboard.py index 41d3802..5a652c5 100644 --- a/pipeline_lib/core/steps/explainer_dashboard.py +++ b/pipeline_lib/core/steps/explainer_dashboard.py @@ -1,7 +1,7 @@ from explainerdashboard import RegressionExplainer from pipeline_lib.core import DataContainer -from pipeline_lib.core.steps import PipelineStep +from pipeline_lib.core.steps.base import PipelineStep class ExplainerDashboardStep(PipelineStep): diff --git a/pipeline_lib/core/steps/fit_encoders.py b/pipeline_lib/core/steps/fit_encoders.py index 87f352e..177949e 100644 --- a/pipeline_lib/core/steps/fit_encoders.py +++ b/pipeline_lib/core/steps/fit_encoders.py @@ -1,8 +1,7 @@ from typing import Optional from pipeline_lib.core import DataContainer - -from .base import PipelineStep +from pipeline_lib.core.steps.base import PipelineStep class FitEncodersStep(PipelineStep): diff --git a/pipeline_lib/core/steps/fit_model.py b/pipeline_lib/core/steps/fit_model.py index 6103a8d..4eaeef2 100644 --- a/pipeline_lib/core/steps/fit_model.py +++ b/pipeline_lib/core/steps/fit_model.py @@ -1,8 +1,7 @@ from typing import Optional from pipeline_lib.core import DataContainer - -from .base import PipelineStep +from pipeline_lib.core.steps.base import PipelineStep class FitModelStep(PipelineStep): diff --git a/pipeline_lib/core/steps/generate.py b/pipeline_lib/core/steps/generate.py new file mode 100644 index 0000000..6c5173b --- /dev/null +++ b/pipeline_lib/core/steps/generate.py @@ -0,0 +1,65 @@ +import os +from enum import Enum + +import pandas as pd + +from pipeline_lib.core import DataContainer +from pipeline_lib.core.steps.base import PipelineStep + + +class FileType(Enum): + CSV = ".csv" + PARQUET = ".parquet" + + +class GenerateStep(PipelineStep): + def __init__(self, path: str, **kwargs): + self.init_logger() + self.file_path = path + self.kwargs = kwargs + + def execute(self, data: DataContainer) -> DataContainer: + self.logger.info(f"Generating data from file: {self.file_path}") + + if not os.path.exists(self.file_path): + raise FileNotFoundError(f"File not found: {self.file_path}") + + file_type = self._infer_file_type() + + if file_type == FileType.CSV: + df = self._read_csv() + elif file_type == FileType.PARQUET: + df = self._read_parquet() + else: + raise ValueError(f"Unsupported file type: {file_type}") + + data[DataContainer.RAW] = df + + self.logger.info(f"Generated DataFrame with shape: {df.shape}") + + return data + + def _infer_file_type(self) -> FileType: + _, file_extension = os.path.splitext(self.file_path) + file_extension = file_extension.lower() + + try: + return FileType(file_extension) + except ValueError: + raise ValueError(f"Unsupported file extension: {file_extension}") + + def _read_csv(self) -> pd.DataFrame: + kwargs = self.kwargs.copy() + index_col = kwargs.pop("index", None) + df = pd.read_csv(self.file_path, **kwargs) + if index_col is not None: + df.set_index(index_col, inplace=True) + return df + + def _read_parquet(self) -> pd.DataFrame: + kwargs = self.kwargs.copy() + index_col = kwargs.pop("index", None) + df = pd.read_parquet(self.file_path, **kwargs) + if index_col is not None: + df.set_index(index_col, inplace=True) + return df diff --git a/pipeline_lib/core/steps/input_scaling.py b/pipeline_lib/core/steps/input_scaling.py index 128b0cd..18b826b 100644 --- a/pipeline_lib/core/steps/input_scaling.py +++ b/pipeline_lib/core/steps/input_scaling.py @@ -1,8 +1,7 @@ from typing import Optional from pipeline_lib.core import DataContainer - -from .base import PipelineStep +from pipeline_lib.core.steps.base import PipelineStep class InputScalingStep(PipelineStep): diff --git a/pipeline_lib/core/steps/predict.py b/pipeline_lib/core/steps/predict.py index a8b5a79..6da0a99 100644 --- a/pipeline_lib/core/steps/predict.py +++ b/pipeline_lib/core/steps/predict.py @@ -1,8 +1,7 @@ from typing import Optional from pipeline_lib.core import DataContainer - -from .base import PipelineStep +from pipeline_lib.core.steps.base import PipelineStep class PredictStep(PipelineStep): diff --git a/pipeline_lib/core/steps/tabular_split.py b/pipeline_lib/core/steps/tabular_split.py index c6333cf..ae5673b 100644 --- a/pipeline_lib/core/steps/tabular_split.py +++ b/pipeline_lib/core/steps/tabular_split.py @@ -1,8 +1,7 @@ from sklearn.model_selection import train_test_split from pipeline_lib.core import DataContainer - -from .base import PipelineStep +from pipeline_lib.core.steps.base import PipelineStep class TabularSplitStep(PipelineStep): diff --git a/pipeline_lib/core/steps/target_scaling.py b/pipeline_lib/core/steps/target_scaling.py index 3dad235..cb2f83a 100644 --- a/pipeline_lib/core/steps/target_scaling.py +++ b/pipeline_lib/core/steps/target_scaling.py @@ -1,8 +1,7 @@ from typing import Optional from pipeline_lib.core import DataContainer - -from .base import PipelineStep +from pipeline_lib.core.steps.base import PipelineStep class TargetScalingStep(PipelineStep): diff --git a/pipeline_lib/implementation/tabular/xgboost/fit_model.py b/pipeline_lib/implementation/tabular/xgboost/fit_model.py index 3b43823..6200ee0 100644 --- a/pipeline_lib/implementation/tabular/xgboost/fit_model.py +++ b/pipeline_lib/implementation/tabular/xgboost/fit_model.py @@ -40,6 +40,11 @@ def __init__( self.xgb_params = xgb_params self.optuna_params = optuna_params + + if save_path: + if not save_path.endswith(".joblib"): + raise ValueError("Only joblib format is supported for saving the model.") + self.save_path = save_path def execute(self, data: DataContainer) -> DataContainer: @@ -90,15 +95,9 @@ def execute(self, data: DataContainer) -> DataContainer: # Save the model to the data container data[DataContainer.MODEL] = model - # save model to disk - save_path = self.save_path - - if save_path: - if not save_path.endswith(".joblib"): - raise ValueError("Only joblib format is supported for saving the model.") - self.logger.info(f"Saving the model to {save_path}") - dump(model, save_path) - + if self.save_path: + self.logger.info(f"Saving the model to {self.save_path}") + dump(model, self.save_path) return data def optimize_with_optuna(self, X_train, y_train, X_valid, y_valid, optuna_params): diff --git a/poetry.lock b/poetry.lock index 2be3ece..b64460b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,15 @@ +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. + [[package]] name = "alembic" version = "1.13.1" description = "A database migration tool for SQLAlchemy." -category = "main" optional = false python-versions = ">=3.8" +files = [ + {file = "alembic-1.13.1-py3-none-any.whl", hash = "sha256:2edcc97bed0bd3272611ce3a98d98279e9c209e7186e43e75bbb1b2bdfdbcc43"}, + {file = "alembic-1.13.1.tar.gz", hash = "sha256:4932c8558bf68f2ee92b9bbcb8218671c627064d5b08939437af6d77dc05e595"}, +] [package.dependencies] Mako = "*" @@ -18,9 +23,12 @@ tz = ["backports.zoneinfo"] name = "ansi2html" version = "1.9.1" description = "Convert text with ANSI color codes to HTML or to LaTeX" -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "ansi2html-1.9.1-py3-none-any.whl", hash = "sha256:29ccdb1e83520d648ebdc9c9544059ea4d424ecc33d3ef723657f7f5a9ae5225"}, + {file = "ansi2html-1.9.1.tar.gz", hash = "sha256:5c6837a13ecc1903aab7a545353312049dfedfe5105362ad3a8d9d207871ec71"}, +] [package.extras] docs = ["mkdocs", "mkdocs-material", "mkdocs-material-extensions", "mkdocstrings", "mkdocstrings-python", "pymdown-extensions"] @@ -30,17 +38,23 @@ test = ["pytest", "pytest-cov"] name = "appnope" version = "0.1.4" description = "Disable App Nap on macOS >= 10.9" -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, + {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, +] [[package]] name = "asttokens" version = "2.4.1" description = "Annotate AST trees with source code positions" -category = "main" optional = false python-versions = "*" +files = [ + {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, + {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, +] [package.dependencies] six = ">=1.12.0" @@ -53,9 +67,32 @@ test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] name = "black" version = "23.12.1" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.8" +files = [ + {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, + {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, + {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"}, + {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"}, + {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"}, + {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"}, + {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"}, + {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"}, + {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"}, + {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"}, + {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"}, + {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"}, + {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"}, + {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"}, + {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"}, + {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"}, + {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"}, + {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"}, + {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"}, + {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"}, + {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"}, + {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"}, +] [package.dependencies] click = ">=8.0.0" @@ -76,25 +113,84 @@ uvloop = ["uvloop (>=0.15.2)"] name = "blinker" version = "1.7.0" description = "Fast, simple object-to-object and broadcast signaling" -category = "main" optional = false python-versions = ">=3.8" +files = [ + {file = "blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9"}, + {file = "blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"}, +] [[package]] name = "certifi" version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, +] [[package]] name = "cffi" version = "1.16.0" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = ">=3.8" +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] [package.dependencies] pycparser = "*" @@ -103,17 +199,111 @@ pycparser = "*" name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] [[package]] name = "click" version = "8.1.7" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -122,25 +312,34 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "cloudpickle" version = "3.0.0" description = "Pickler class to extend the standard pickle.Pickler functionality" -category = "main" optional = false python-versions = ">=3.8" +files = [ + {file = "cloudpickle-3.0.0-py3-none-any.whl", hash = "sha256:246ee7d0c295602a036e86369c77fecda4ab17b506496730f2f576d9016fd9c7"}, + {file = "cloudpickle-3.0.0.tar.gz", hash = "sha256:996d9a482c6fb4f33c1a35335cf8afd065d2a56e973270364840712d9131a882"}, +] [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] [[package]] name = "colorlog" version = "6.8.2" description = "Add colours to the output of Python's logging module." -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "colorlog-6.8.2-py3-none-any.whl", hash = "sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33"}, + {file = "colorlog-6.8.2.tar.gz", hash = "sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44"}, +] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} @@ -152,9 +351,12 @@ development = ["black", "flake8", "mypy", "pytest", "types-colorama"] name = "colour" version = "0.1.5" description = "converts and manipulates various color representation (HSL, RVB, web, X11, ...)" -category = "main" optional = false python-versions = "*" +files = [ + {file = "colour-0.1.5-py2.py3-none-any.whl", hash = "sha256:33f6db9d564fadc16e59921a56999b79571160ce09916303d35346dddc17978c"}, + {file = "colour-0.1.5.tar.gz", hash = "sha256:af20120fefd2afede8b001fbef2ea9da70ad7d49fafdb6489025dae8745c3aee"}, +] [package.extras] test = ["nose"] @@ -163,9 +365,12 @@ test = ["nose"] name = "comm" version = "0.2.1" description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." -category = "main" optional = false python-versions = ">=3.8" +files = [ + {file = "comm-0.2.1-py3-none-any.whl", hash = "sha256:87928485c0dfc0e7976fd89fc1e187023cf587e7c353e4a9b417555b44adf021"}, + {file = "comm-0.2.1.tar.gz", hash = "sha256:0bc91edae1344d39d3661dcbc36937181fdaddb304790458f8b044dbc064b89a"}, +] [package.dependencies] traitlets = ">=4" @@ -177,9 +382,54 @@ test = ["pytest"] name = "contourpy" version = "1.2.0" description = "Python library for calculating contours of 2D quadrilateral grids" -category = "main" optional = false python-versions = ">=3.9" +files = [ + {file = "contourpy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0274c1cb63625972c0c007ab14dd9ba9e199c36ae1a231ce45d725cbcbfd10a8"}, + {file = "contourpy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab459a1cbbf18e8698399c595a01f6dcc5c138220ca3ea9e7e6126232d102bb4"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fdd887f17c2f4572ce548461e4f96396681212d858cae7bd52ba3310bc6f00f"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d16edfc3fc09968e09ddffada434b3bf989bf4911535e04eada58469873e28e"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c203f617abc0dde5792beb586f827021069fb6d403d7f4d5c2b543d87edceb9"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b69303ceb2e4d4f146bf82fda78891ef7bcd80c41bf16bfca3d0d7eb545448aa"}, + {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:884c3f9d42d7218304bc74a8a7693d172685c84bd7ab2bab1ee567b769696df9"}, + {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4a1b1208102be6e851f20066bf0e7a96b7d48a07c9b0cfe6d0d4545c2f6cadab"}, + {file = "contourpy-1.2.0-cp310-cp310-win32.whl", hash = "sha256:34b9071c040d6fe45d9826cbbe3727d20d83f1b6110d219b83eb0e2a01d79488"}, + {file = "contourpy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:bd2f1ae63998da104f16a8b788f685e55d65760cd1929518fd94cd682bf03e41"}, + {file = "contourpy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd10c26b4eadae44783c45ad6655220426f971c61d9b239e6f7b16d5cdaaa727"}, + {file = "contourpy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c6b28956b7b232ae801406e529ad7b350d3f09a4fde958dfdf3c0520cdde0dd"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebeac59e9e1eb4b84940d076d9f9a6cec0064e241818bcb6e32124cc5c3e377a"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:139d8d2e1c1dd52d78682f505e980f592ba53c9f73bd6be102233e358b401063"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e9dc350fb4c58adc64df3e0703ab076f60aac06e67d48b3848c23647ae4310e"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18fc2b4ed8e4a8fe849d18dce4bd3c7ea637758c6343a1f2bae1e9bd4c9f4686"}, + {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:16a7380e943a6d52472096cb7ad5264ecee36ed60888e2a3d3814991a0107286"}, + {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8d8faf05be5ec8e02a4d86f616fc2a0322ff4a4ce26c0f09d9f7fb5330a35c95"}, + {file = "contourpy-1.2.0-cp311-cp311-win32.whl", hash = "sha256:67b7f17679fa62ec82b7e3e611c43a016b887bd64fb933b3ae8638583006c6d6"}, + {file = "contourpy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:99ad97258985328b4f207a5e777c1b44a83bfe7cf1f87b99f9c11d4ee477c4de"}, + {file = "contourpy-1.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:575bcaf957a25d1194903a10bc9f316c136c19f24e0985a2b9b5608bdf5dbfe0"}, + {file = "contourpy-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9e6c93b5b2dbcedad20a2f18ec22cae47da0d705d454308063421a3b290d9ea4"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:464b423bc2a009088f19bdf1f232299e8b6917963e2b7e1d277da5041f33a779"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68ce4788b7d93e47f84edd3f1f95acdcd142ae60bc0e5493bfd120683d2d4316"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d7d1f8871998cdff5d2ff6a087e5e1780139abe2838e85b0b46b7ae6cc25399"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e739530c662a8d6d42c37c2ed52a6f0932c2d4a3e8c1f90692ad0ce1274abe0"}, + {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:247b9d16535acaa766d03037d8e8fb20866d054d3c7fbf6fd1f993f11fc60ca0"}, + {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:461e3ae84cd90b30f8d533f07d87c00379644205b1d33a5ea03381edc4b69431"}, + {file = "contourpy-1.2.0-cp312-cp312-win32.whl", hash = "sha256:1c2559d6cffc94890b0529ea7eeecc20d6fadc1539273aa27faf503eb4656d8f"}, + {file = "contourpy-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:491b1917afdd8638a05b611a56d46587d5a632cabead889a5440f7c638bc6ed9"}, + {file = "contourpy-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5fd1810973a375ca0e097dee059c407913ba35723b111df75671a1976efa04bc"}, + {file = "contourpy-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:999c71939aad2780f003979b25ac5b8f2df651dac7b38fb8ce6c46ba5abe6ae9"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7caf9b241464c404613512d5594a6e2ff0cc9cb5615c9475cc1d9b514218ae8"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:266270c6f6608340f6c9836a0fb9b367be61dde0c9a9a18d5ece97774105ff3e"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbd50d0a0539ae2e96e537553aff6d02c10ed165ef40c65b0e27e744a0f10af8"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11f8d2554e52f459918f7b8e6aa20ec2a3bce35ce95c1f0ef4ba36fbda306df5"}, + {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ce96dd400486e80ac7d195b2d800b03e3e6a787e2a522bfb83755938465a819e"}, + {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6d3364b999c62f539cd403f8123ae426da946e142312a514162adb2addd8d808"}, + {file = "contourpy-1.2.0-cp39-cp39-win32.whl", hash = "sha256:1c88dfb9e0c77612febebb6ac69d44a8d81e3dc60f993215425b62c1161353f4"}, + {file = "contourpy-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:78e6ad33cf2e2e80c5dfaaa0beec3d61face0fb650557100ee36db808bfa6843"}, + {file = "contourpy-1.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:be16975d94c320432657ad2402f6760990cb640c161ae6da1363051805fa8108"}, + {file = "contourpy-1.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b95a225d4948b26a28c08307a60ac00fb8671b14f2047fc5476613252a129776"}, + {file = "contourpy-1.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d7e03c0f9a4f90dc18d4e77e9ef4ec7b7bbb437f7f675be8e530d65ae6ef956"}, + {file = "contourpy-1.2.0.tar.gz", hash = "sha256:171f311cb758de7da13fc53af221ae47a5877be5a0843a9fe150818c51ed276a"}, +] [package.dependencies] numpy = ">=1.20,<2.0" @@ -195,9 +445,12 @@ test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"] name = "cycler" version = "0.12.1" description = "Composable style cycles" -category = "main" optional = false python-versions = ">=3.8" +files = [ + {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, + {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, +] [package.extras] docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] @@ -205,11 +458,14 @@ tests = ["pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "dash" -version = "2.16.0" +version = "2.16.1" description = "A Python framework for building reactive web-apps. Developed by Plotly." -category = "main" optional = false python-versions = ">=3.8" +files = [ + {file = "dash-2.16.1-py3-none-any.whl", hash = "sha256:8a9d2a618e415113c0b2a4d25d5dc4df5cb921f733b33dde75559db2316b1df1"}, + {file = "dash-2.16.1.tar.gz", hash = "sha256:b2871d6b8d4c9dfd0a64f89f22d001c93292910b41d92d9ff2bb424a28283976"}, +] [package.dependencies] dash-core-components = "2.0.0" @@ -237,9 +493,12 @@ testing = ["beautifulsoup4 (>=4.8.2)", "cryptography (<3.4)", "dash-testing-stub name = "dash-auth" version = "2.2.1" description = "Dash Authorization Package." -category = "main" optional = false python-versions = ">=3.8" +files = [ + {file = "dash_auth-2.2.1-py3-none-any.whl", hash = "sha256:3109c126a862da5dfb9e48e816eb917223e5629acb87ad5634063a0e38e67cef"}, + {file = "dash_auth-2.2.1.tar.gz", hash = "sha256:cb821fdedbcd73e59e53b1c8247d81a66177769bc70cdaa16c6bb0b3639258bb"}, +] [package.dependencies] dash = ">=1.1.1" @@ -250,9 +509,12 @@ werkzeug = "*" name = "dash-bootstrap-components" version = "1.5.0" description = "Bootstrap themed components for use in Plotly Dash" -category = "main" optional = false python-versions = ">=3.7, <4" +files = [ + {file = "dash-bootstrap-components-1.5.0.tar.gz", hash = "sha256:083158c07434b9965e2d6c3e8ca72dbbe47dab23e676258cef9bf0ad47d2e250"}, + {file = "dash_bootstrap_components-1.5.0-py3-none-any.whl", hash = "sha256:b487fec1a85e3d6a8564fe04c0a9cd9e846f75ea9e563456ed3879592889c591"}, +] [package.dependencies] dash = ">=2.0.0" @@ -264,49 +526,87 @@ pandas = ["numpy", "pandas"] name = "dash-core-components" version = "2.0.0" description = "Core component suite for Dash" -category = "main" optional = false python-versions = "*" +files = [ + {file = "dash_core_components-2.0.0-py3-none-any.whl", hash = "sha256:52b8e8cce13b18d0802ee3acbc5e888cb1248a04968f962d63d070400af2e346"}, + {file = "dash_core_components-2.0.0.tar.gz", hash = "sha256:c6733874af975e552f95a1398a16c2ee7df14ce43fa60bb3718a3c6e0b63ffee"}, +] [[package]] name = "dash-html-components" version = "2.0.0" description = "Vanilla HTML components for Dash" -category = "main" optional = false python-versions = "*" +files = [ + {file = "dash_html_components-2.0.0-py3-none-any.whl", hash = "sha256:b42cc903713c9706af03b3f2548bda4be7307a7cf89b7d6eae3da872717d1b63"}, + {file = "dash_html_components-2.0.0.tar.gz", hash = "sha256:8703a601080f02619a6390998e0b3da4a5daabe97a1fd7a9cebc09d015f26e50"}, +] [[package]] name = "dash-table" version = "5.0.0" description = "Dash table" -category = "main" optional = false python-versions = "*" +files = [ + {file = "dash_table-5.0.0-py3-none-any.whl", hash = "sha256:19036fa352bb1c11baf38068ec62d172f0515f73ca3276c79dee49b95ddc16c9"}, + {file = "dash_table-5.0.0.tar.gz", hash = "sha256:18624d693d4c8ef2ddec99a6f167593437a7ea0bf153aa20f318c170c5bc7308"}, +] [[package]] name = "debugpy" version = "1.8.1" description = "An implementation of the Debug Adapter Protocol for Python" -category = "main" optional = false python-versions = ">=3.8" +files = [ + {file = "debugpy-1.8.1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:3bda0f1e943d386cc7a0e71bfa59f4137909e2ed947fb3946c506e113000f741"}, + {file = "debugpy-1.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dda73bf69ea479c8577a0448f8c707691152e6c4de7f0c4dec5a4bc11dee516e"}, + {file = "debugpy-1.8.1-cp310-cp310-win32.whl", hash = "sha256:3a79c6f62adef994b2dbe9fc2cc9cc3864a23575b6e387339ab739873bea53d0"}, + {file = "debugpy-1.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:7eb7bd2b56ea3bedb009616d9e2f64aab8fc7000d481faec3cd26c98a964bcdd"}, + {file = "debugpy-1.8.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:016a9fcfc2c6b57f939673c874310d8581d51a0fe0858e7fac4e240c5eb743cb"}, + {file = "debugpy-1.8.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd97ed11a4c7f6d042d320ce03d83b20c3fb40da892f994bc041bbc415d7a099"}, + {file = "debugpy-1.8.1-cp311-cp311-win32.whl", hash = "sha256:0de56aba8249c28a300bdb0672a9b94785074eb82eb672db66c8144fff673146"}, + {file = "debugpy-1.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:1a9fe0829c2b854757b4fd0a338d93bc17249a3bf69ecf765c61d4c522bb92a8"}, + {file = "debugpy-1.8.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3ebb70ba1a6524d19fa7bb122f44b74170c447d5746a503e36adc244a20ac539"}, + {file = "debugpy-1.8.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2e658a9630f27534e63922ebf655a6ab60c370f4d2fc5c02a5b19baf4410ace"}, + {file = "debugpy-1.8.1-cp312-cp312-win32.whl", hash = "sha256:caad2846e21188797a1f17fc09c31b84c7c3c23baf2516fed5b40b378515bbf0"}, + {file = "debugpy-1.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:edcc9f58ec0fd121a25bc950d4578df47428d72e1a0d66c07403b04eb93bcf98"}, + {file = "debugpy-1.8.1-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:7a3afa222f6fd3d9dfecd52729bc2e12c93e22a7491405a0ecbf9e1d32d45b39"}, + {file = "debugpy-1.8.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d915a18f0597ef685e88bb35e5d7ab968964b7befefe1aaea1eb5b2640b586c7"}, + {file = "debugpy-1.8.1-cp38-cp38-win32.whl", hash = "sha256:92116039b5500633cc8d44ecc187abe2dfa9b90f7a82bbf81d079fcdd506bae9"}, + {file = "debugpy-1.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:e38beb7992b5afd9d5244e96ad5fa9135e94993b0c551ceebf3fe1a5d9beb234"}, + {file = "debugpy-1.8.1-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:bfb20cb57486c8e4793d41996652e5a6a885b4d9175dd369045dad59eaacea42"}, + {file = "debugpy-1.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efd3fdd3f67a7e576dd869c184c5dd71d9aaa36ded271939da352880c012e703"}, + {file = "debugpy-1.8.1-cp39-cp39-win32.whl", hash = "sha256:58911e8521ca0c785ac7a0539f1e77e0ce2df753f786188f382229278b4cdf23"}, + {file = "debugpy-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:6df9aa9599eb05ca179fb0b810282255202a66835c6efb1d112d21ecb830ddd3"}, + {file = "debugpy-1.8.1-py2.py3-none-any.whl", hash = "sha256:28acbe2241222b87e255260c76741e1fbf04fdc3b6d094fcf57b6c6f75ce1242"}, + {file = "debugpy-1.8.1.zip", hash = "sha256:f696d6be15be87aef621917585f9bb94b1dc9e8aced570db1b8a6fc14e8f9b42"}, +] [[package]] name = "decorator" version = "5.1.1" description = "Decorators for Humans" -category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] [[package]] name = "dtreeviz" version = "2.2.2" description = "A Python 3 library for sci-kit learn, XGBoost, LightGBM, Spark, and TensorFlow decision tree visualization" -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "dtreeviz-2.2.2-py3-none-any.whl", hash = "sha256:e510d80857b29f15a12e8ebe2c7ab3fb667096739742ab47e5d8889cf78da8fe"}, + {file = "dtreeviz-2.2.2.tar.gz", hash = "sha256:a145ff91ffaa11bd2f59186fdcffaa384d90c1757517cbb27e66cc83922319ba"}, +] [package.dependencies] colour = "*" @@ -321,16 +621,19 @@ scikit-learn = "*" all = ["lightgbm", "pyspark", "tensorflow-decision-forests", "xgboost"] lightgbm = ["lightgbm"] pyspark = ["pyspark"] -tensorflow_decision_forests = ["tensorflow-decision-forests"] +tensorflow-decision-forests = ["tensorflow-decision-forests"] xgboost = ["xgboost"] [[package]] name = "exceptiongroup" version = "1.2.0" description = "Backport of PEP 654 (exception groups)" -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, +] [package.extras] test = ["pytest (>=6)"] @@ -339,9 +642,12 @@ test = ["pytest (>=6)"] name = "executing" version = "2.0.1" description = "Get the currently executing AST node of a frame, and other information" -category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, + {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, +] [package.extras] tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] @@ -350,9 +656,12 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth name = "explainerdashboard" version = "0.4.5" description = "Quickly build Explainable AI dashboards that show the inner workings of so-called \"blackbox\" machine learning models." -category = "main" optional = false python-versions = ">=3.8" +files = [ + {file = "explainerdashboard-0.4.5-py3-none-any.whl", hash = "sha256:ca2a1f43ebcdec44f33eb9dc49b3ea9789f05be9ed1d1bd0b6219dfa4338e937"}, + {file = "explainerdashboard-0.4.5.tar.gz", hash = "sha256:c489db883669c8c53bd12ef8548b16b3dee737e8e249959a2c3550fc604ed871"}, +] [package.dependencies] click = "*" @@ -376,9 +685,12 @@ waitress = "*" name = "flake8" version = "7.0.0" description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" optional = false python-versions = ">=3.8.1" +files = [ + {file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"}, + {file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"}, +] [package.dependencies] mccabe = ">=0.7.0,<0.8.0" @@ -386,12 +698,15 @@ pycodestyle = ">=2.11.0,<2.12.0" pyflakes = ">=3.2.0,<3.3.0" [[package]] -name = "Flask" +name = "flask" version = "3.0.2" description = "A simple framework for building complex web applications." -category = "main" optional = false python-versions = ">=3.8" +files = [ + {file = "flask-3.0.2-py3-none-any.whl", hash = "sha256:3232e0e9c850d781933cf0207523d1ece087eb8d87b23777ae38456e2fbe7c6e"}, + {file = "flask-3.0.2.tar.gz", hash = "sha256:822c03f4b799204250a7ee84b1eddc40665395333973dfb9deebfe425fefcb7d"}, +] [package.dependencies] blinker = ">=1.6.2" @@ -409,9 +724,12 @@ dotenv = ["python-dotenv"] name = "flask-simplelogin" version = "0.1.2" description = "Flask Simple Login - Login Extension for Flask" -category = "main" optional = false python-versions = ">=3.8,<4.0" +files = [ + {file = "flask_simplelogin-0.1.2-py3-none-any.whl", hash = "sha256:484845c10d6e48bc88d20e2fb19de565c2fe608a59bcbb902572f07b5437b188"}, + {file = "flask_simplelogin-0.1.2.tar.gz", hash = "sha256:a99b7ded371aa0f5ecf3271c160e3fd52653abd5d879acf9ae621c4825345a8f"}, +] [package.dependencies] click = ">=8.0.1,<9.0.0" @@ -423,12 +741,15 @@ WTForms = ">=2.1" docs = ["Sphinx (>=4.1.2,<5.0.0)", "recommonmark (>=0.7.1,<0.8.0)", "sphinx-markdown-tables (>=0.0.15,<0.0.16)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)"] [[package]] -name = "Flask-WTF" +name = "flask-wtf" version = "1.2.1" description = "Form rendering, validation, and CSRF protection for Flask with WTForms." -category = "main" optional = false python-versions = ">=3.8" +files = [ + {file = "flask_wtf-1.2.1-py3-none-any.whl", hash = "sha256:fa6793f2fb7e812e0fe9743b282118e581fb1b6c45d414b8af05e659bd653287"}, + {file = "flask_wtf-1.2.1.tar.gz", hash = "sha256:8bb269eb9bb46b87e7c8233d7e7debdf1f8b74bf90cc1789988c29b37a97b695"}, +] [package.dependencies] flask = "*" @@ -442,9 +763,52 @@ email = ["email-validator"] name = "fonttools" version = "4.49.0" description = "Tools to manipulate font files" -category = "main" optional = false python-versions = ">=3.8" +files = [ + {file = "fonttools-4.49.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d970ecca0aac90d399e458f0b7a8a597e08f95de021f17785fb68e2dc0b99717"}, + {file = "fonttools-4.49.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac9a745b7609f489faa65e1dc842168c18530874a5f5b742ac3dd79e26bca8bc"}, + {file = "fonttools-4.49.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ba0e00620ca28d4ca11fc700806fd69144b463aa3275e1b36e56c7c09915559"}, + {file = "fonttools-4.49.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdee3ab220283057e7840d5fb768ad4c2ebe65bdba6f75d5d7bf47f4e0ed7d29"}, + {file = "fonttools-4.49.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ce7033cb61f2bb65d8849658d3786188afd80f53dad8366a7232654804529532"}, + {file = "fonttools-4.49.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:07bc5ea02bb7bc3aa40a1eb0481ce20e8d9b9642a9536cde0218290dd6085828"}, + {file = "fonttools-4.49.0-cp310-cp310-win32.whl", hash = "sha256:86eef6aab7fd7c6c8545f3ebd00fd1d6729ca1f63b0cb4d621bccb7d1d1c852b"}, + {file = "fonttools-4.49.0-cp310-cp310-win_amd64.whl", hash = "sha256:1fac1b7eebfce75ea663e860e7c5b4a8831b858c17acd68263bc156125201abf"}, + {file = "fonttools-4.49.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:edc0cce355984bb3c1d1e89d6a661934d39586bb32191ebff98c600f8957c63e"}, + {file = "fonttools-4.49.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:83a0d9336de2cba86d886507dd6e0153df333ac787377325a39a2797ec529814"}, + {file = "fonttools-4.49.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36c8865bdb5cfeec88f5028e7e592370a0657b676c6f1d84a2108e0564f90e22"}, + {file = "fonttools-4.49.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33037d9e56e2562c710c8954d0f20d25b8386b397250d65581e544edc9d6b942"}, + {file = "fonttools-4.49.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8fb022d799b96df3eaa27263e9eea306bd3d437cc9aa981820850281a02b6c9a"}, + {file = "fonttools-4.49.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33c584c0ef7dc54f5dd4f84082eabd8d09d1871a3d8ca2986b0c0c98165f8e86"}, + {file = "fonttools-4.49.0-cp311-cp311-win32.whl", hash = "sha256:cbe61b158deb09cffdd8540dc4a948d6e8f4d5b4f3bf5cd7db09bd6a61fee64e"}, + {file = "fonttools-4.49.0-cp311-cp311-win_amd64.whl", hash = "sha256:fc11e5114f3f978d0cea7e9853627935b30d451742eeb4239a81a677bdee6bf6"}, + {file = "fonttools-4.49.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d647a0e697e5daa98c87993726da8281c7233d9d4ffe410812a4896c7c57c075"}, + {file = "fonttools-4.49.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f3bbe672df03563d1f3a691ae531f2e31f84061724c319652039e5a70927167e"}, + {file = "fonttools-4.49.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bebd91041dda0d511b0d303180ed36e31f4f54b106b1259b69fade68413aa7ff"}, + {file = "fonttools-4.49.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4145f91531fd43c50f9eb893faa08399816bb0b13c425667c48475c9f3a2b9b5"}, + {file = "fonttools-4.49.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ea329dafb9670ffbdf4dbc3b0e5c264104abcd8441d56de77f06967f032943cb"}, + {file = "fonttools-4.49.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c076a9e548521ecc13d944b1d261ff3d7825048c338722a4bd126d22316087b7"}, + {file = "fonttools-4.49.0-cp312-cp312-win32.whl", hash = "sha256:b607ea1e96768d13be26d2b400d10d3ebd1456343eb5eaddd2f47d1c4bd00880"}, + {file = "fonttools-4.49.0-cp312-cp312-win_amd64.whl", hash = "sha256:a974c49a981e187381b9cc2c07c6b902d0079b88ff01aed34695ec5360767034"}, + {file = "fonttools-4.49.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b85ec0bdd7bdaa5c1946398cbb541e90a6dfc51df76dfa88e0aaa41b335940cb"}, + {file = "fonttools-4.49.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:af20acbe198a8a790618ee42db192eb128afcdcc4e96d99993aca0b60d1faeb4"}, + {file = "fonttools-4.49.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d418b1fee41a1d14931f7ab4b92dc0bc323b490e41d7a333eec82c9f1780c75"}, + {file = "fonttools-4.49.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b44a52b8e6244b6548851b03b2b377a9702b88ddc21dcaf56a15a0393d425cb9"}, + {file = "fonttools-4.49.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7c7125068e04a70739dad11857a4d47626f2b0bd54de39e8622e89701836eabd"}, + {file = "fonttools-4.49.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:29e89d0e1a7f18bc30f197cfadcbef5a13d99806447c7e245f5667579a808036"}, + {file = "fonttools-4.49.0-cp38-cp38-win32.whl", hash = "sha256:9d95fa0d22bf4f12d2fb7b07a46070cdfc19ef5a7b1c98bc172bfab5bf0d6844"}, + {file = "fonttools-4.49.0-cp38-cp38-win_amd64.whl", hash = "sha256:768947008b4dc552d02772e5ebd49e71430a466e2373008ce905f953afea755a"}, + {file = "fonttools-4.49.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:08877e355d3dde1c11973bb58d4acad1981e6d1140711230a4bfb40b2b937ccc"}, + {file = "fonttools-4.49.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fdb54b076f25d6b0f0298dc706acee5052de20c83530fa165b60d1f2e9cbe3cb"}, + {file = "fonttools-4.49.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0af65c720520710cc01c293f9c70bd69684365c6015cc3671db2b7d807fe51f2"}, + {file = "fonttools-4.49.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f255ce8ed7556658f6d23f6afd22a6d9bbc3edb9b96c96682124dc487e1bf42"}, + {file = "fonttools-4.49.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d00af0884c0e65f60dfaf9340e26658836b935052fdd0439952ae42e44fdd2be"}, + {file = "fonttools-4.49.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:263832fae27481d48dfafcc43174644b6706639661e242902ceb30553557e16c"}, + {file = "fonttools-4.49.0-cp39-cp39-win32.whl", hash = "sha256:0404faea044577a01bb82d47a8fa4bc7a54067fa7e324785dd65d200d6dd1133"}, + {file = "fonttools-4.49.0-cp39-cp39-win_amd64.whl", hash = "sha256:b050d362df50fc6e38ae3954d8c29bf2da52be384649ee8245fdb5186b620836"}, + {file = "fonttools-4.49.0-py3-none-any.whl", hash = "sha256:af281525e5dd7fa0b39fb1667b8d5ca0e2a9079967e14c4bfe90fd1cd13e0f18"}, + {file = "fonttools-4.49.0.tar.gz", hash = "sha256:ebf46e7f01b7af7861310417d7c49591a85d99146fc23a5ba82fdb28af156321"}, +] [package.extras] all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] @@ -464,9 +828,12 @@ woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] name = "graphviz" version = "0.20.1" description = "Simple Python interface for Graphviz" -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "graphviz-0.20.1-py3-none-any.whl", hash = "sha256:587c58a223b51611c0cf461132da386edd896a029524ca61a1462b880bf97977"}, + {file = "graphviz-0.20.1.zip", hash = "sha256:8c58f14adaa3b947daf26c19bc1e98c4e0702cdc31cf99153e6f06904d492bf8"}, +] [package.extras] dev = ["flake8", "pep8-naming", "tox (>=3)", "twine", "wheel"] @@ -477,9 +844,68 @@ test = ["coverage", "mock (>=4)", "pytest (>=7)", "pytest-cov", "pytest-mock (>= name = "greenlet" version = "3.0.3" description = "Lightweight in-process concurrent programming" -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, + {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, + {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, + {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, + {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, + {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, + {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, + {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, + {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, + {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, + {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, + {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, + {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, +] [package.extras] docs = ["Sphinx", "furo"] @@ -489,56 +915,71 @@ test = ["objgraph", "psutil"] name = "idna" version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, +] [[package]] name = "importlib-metadata" -version = "7.0.1" +version = "7.0.2" description = "Read metadata from Python packages" -category = "main" optional = false python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-7.0.2-py3-none-any.whl", hash = "sha256:f4bc4c0c070c490abf4ce96d715f68e95923320370efb66143df00199bb6c100"}, + {file = "importlib_metadata-7.0.2.tar.gz", hash = "sha256:198f568f3230878cb1b44fbd7975f87906c22336dba2e4a7f05278c281fbd792"}, +] [package.dependencies] zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] [[package]] name = "importlib-resources" -version = "6.1.2" +version = "6.1.3" description = "Read resources from Python packages" -category = "main" optional = false python-versions = ">=3.8" +files = [ + {file = "importlib_resources-6.1.3-py3-none-any.whl", hash = "sha256:4c0269e3580fe2634d364b39b38b961540a7738c02cb984e98add8b4221d793d"}, + {file = "importlib_resources-6.1.3.tar.gz", hash = "sha256:56fb4525197b78544a3354ea27793952ab93f935bb4bf746b846bb1015020f2b"}, +] [package.dependencies] zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"] +testing = ["jaraco.collections", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"] [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] [[package]] name = "ipykernel" version = "6.29.3" description = "IPython Kernel for Jupyter" -category = "main" optional = false python-versions = ">=3.8" +files = [ + {file = "ipykernel-6.29.3-py3-none-any.whl", hash = "sha256:5aa086a4175b0229d4eca211e181fb473ea78ffd9869af36ba7694c947302a21"}, + {file = "ipykernel-6.29.3.tar.gz", hash = "sha256:e14c250d1f9ea3989490225cc1a542781b095a18a19447fcf2b5eaf7d0ac5bd2"}, +] [package.dependencies] appnope = {version = "*", markers = "platform_system == \"Darwin\""} @@ -546,7 +987,7 @@ comm = ">=0.1.1" debugpy = ">=1.6.5" ipython = ">=7.23.1" jupyter-client = ">=6.1.12" -jupyter-core = ">=4.12,<5.0.0 || >=5.1.0" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" matplotlib-inline = ">=0.1" nest-asyncio = "*" packaging = "*" @@ -566,9 +1007,12 @@ test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio name = "ipython" version = "8.18.1" description = "IPython: Productive Interactive Computing" -category = "main" optional = false python-versions = ">=3.9" +files = [ + {file = "ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"}, + {file = "ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27"}, +] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} @@ -594,15 +1038,18 @@ notebook = ["ipywidgets", "notebook"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] test = ["pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath"] -test_extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath", "trio"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath", "trio"] [[package]] name = "isort" version = "5.13.2" description = "A Python utility / library to sort Python imports." -category = "dev" optional = false python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] [package.extras] colors = ["colorama (>=0.4.6)"] @@ -611,17 +1058,23 @@ colors = ["colorama (>=0.4.6)"] name = "itsdangerous" version = "2.1.2" description = "Safely pass data to untrusted environments and back." -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, + {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, +] [[package]] name = "jedi" version = "0.19.1" description = "An autocompletion tool for Python that can be used for text editors." -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, + {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, +] [package.dependencies] parso = ">=0.8.3,<0.9.0" @@ -632,12 +1085,15 @@ qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] [[package]] -name = "Jinja2" +name = "jinja2" version = "3.1.3" description = "A very fast and expressive template engine." -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, + {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, +] [package.dependencies] MarkupSafe = ">=2.0" @@ -649,21 +1105,27 @@ i18n = ["Babel (>=2.7)"] name = "joblib" version = "1.3.2" description = "Lightweight pipelining with Python functions" -category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "joblib-1.3.2-py3-none-any.whl", hash = "sha256:ef4331c65f239985f3f2220ecc87db222f08fd22097a3dd5698f693875f8cbb9"}, + {file = "joblib-1.3.2.tar.gz", hash = "sha256:92f865e621e17784e7955080b6d042489e3b8e294949cc44c6eac304f59772b1"}, +] [[package]] name = "jupyter-client" -version = "8.6.0" +version = "8.6.1" description = "Jupyter protocol implementation and client libraries" -category = "main" optional = false python-versions = ">=3.8" +files = [ + {file = "jupyter_client-8.6.1-py3-none-any.whl", hash = "sha256:3b7bd22f058434e3b9a7ea4b1500ed47de2713872288c0d511d19926f99b459f"}, + {file = "jupyter_client-8.6.1.tar.gz", hash = "sha256:e842515e2bab8e19186d89fdfea7abd15e39dd581f94e399f00e2af5a1652d3f"}, +] [package.dependencies] importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} -jupyter-core = ">=4.12,<5.0.0 || >=5.1.0" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" python-dateutil = ">=2.8.2" pyzmq = ">=23.0" tornado = ">=6.2" @@ -675,11 +1137,14 @@ test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pyt [[package]] name = "jupyter-core" -version = "5.7.1" +version = "5.7.2" description = "Jupyter core package. A base package on which Jupyter projects rely." -category = "main" optional = false python-versions = ">=3.8" +files = [ + {file = "jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409"}, + {file = "jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9"}, +] [package.dependencies] platformdirs = ">=2.5" @@ -688,15 +1153,18 @@ traitlets = ">=5.3" [package.extras] docs = ["myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "traitlets"] -test = ["ipykernel", "pre-commit", "pytest", "pytest-cov", "pytest-timeout"] +test = ["ipykernel", "pre-commit", "pytest (<8)", "pytest-cov", "pytest-timeout"] [[package]] name = "jupyter-dash" version = "0.4.2" description = "Dash support for the Jupyter notebook interface" -category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "jupyter-dash-0.4.2.tar.gz", hash = "sha256:d546c7c25a2867c14c95a48af0ad572803b26915a5ce6052158c9dede4dbf48c"}, + {file = "jupyter_dash-0.4.2-py3-none-any.whl", hash = "sha256:b07d90ccf38d4dfb04efd630a2b2627f367b79fa4296ee3912d0c4e21e73e9b2"}, +] [package.dependencies] ansi2html = "*" @@ -715,1326 +1183,9 @@ dev = ["jupyter-server-proxy", "jupyterlab (>=2.0)", "notebook (>=6.0)"] name = "kiwisolver" version = "1.4.5" description = "A fast implementation of the Cassowary constraint solver" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "llvmlite" -version = "0.42.0" -description = "lightweight wrapper around basic LLVM functionality" -category = "main" -optional = false -python-versions = ">=3.9" - -[[package]] -name = "Mako" -version = "1.3.2" -description = "A super-fast templating language that borrows the best ideas from the existing templating languages." -category = "main" -optional = false -python-versions = ">=3.8" - -[package.dependencies] -MarkupSafe = ">=0.9.2" - -[package.extras] -babel = ["Babel"] -lingua = ["lingua"] -testing = ["pytest"] - -[[package]] -name = "MarkupSafe" -version = "2.1.5" -description = "Safely add untrusted strings to HTML/XML markup." -category = "main" optional = false python-versions = ">=3.7" - -[[package]] -name = "matplotlib" -version = "3.8.3" -description = "Python plotting package" -category = "main" -optional = false -python-versions = ">=3.9" - -[package.dependencies] -contourpy = ">=1.0.1" -cycler = ">=0.10" -fonttools = ">=4.22.0" -importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} -kiwisolver = ">=1.3.1" -numpy = ">=1.21,<2" -packaging = ">=20.0" -pillow = ">=8" -pyparsing = ">=2.3.1" -python-dateutil = ">=2.7" - -[[package]] -name = "matplotlib-inline" -version = "0.1.6" -description = "Inline Matplotlib backend for Jupyter" -category = "main" -optional = false -python-versions = ">=3.5" - -[package.dependencies] -traitlets = "*" - -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "nest-asyncio" -version = "1.6.0" -description = "Patch asyncio to allow nested event loops" -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "numba" -version = "0.59.0" -description = "compiling Python code using LLVM" -category = "main" -optional = false -python-versions = ">=3.9" - -[package.dependencies] -llvmlite = ">=0.42.0dev0,<0.43" -numpy = ">=1.22,<1.27" - -[[package]] -name = "numpy" -version = "1.26.4" -description = "Fundamental package for array computing in Python" -category = "main" -optional = false -python-versions = ">=3.9" - -[[package]] -name = "optuna" -version = "3.5.0" -description = "A hyperparameter optimization framework" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -alembic = ">=1.5.0" -colorlog = "*" -numpy = "*" -packaging = ">=20.0" -PyYAML = "*" -sqlalchemy = ">=1.3.0" -tqdm = "*" - -[package.extras] -benchmark = ["asv (>=0.5.0)", "botorch", "cma", "scikit-optimize", "virtualenv"] -checking = ["black", "blackdoc", "flake8", "isort", "mypy", "mypy-boto3-s3", "types-PyYAML", "types-redis", "types-setuptools", "types-tqdm", "typing-extensions (>=3.10.0.0)"] -document = ["ase", "botorch", "cma", "cmaes (>=0.10.0)", "distributed", "fvcore", "lightgbm", "matplotlib (!=3.6.0)", "mlflow", "pandas", "pillow", "plotly (>=4.9.0)", "scikit-learn", "scikit-optimize", "sphinx", "sphinx-copybutton", "sphinx-gallery", "sphinx-plotly-directive", "sphinx-rtd-theme (>=1.2.0)", "torch", "torchaudio", "torchvision"] -integration = ["botorch (>=0.4.0)", "catboost (>=0.26)", "catboost (>=0.26,<1.2)", "cma", "distributed", "lightgbm", "lightning", "mlflow", "pandas", "pytorch-ignite", "scikit-learn (>=0.24.2)", "scikit-optimize", "shap", "tensorflow", "torch", "torchaudio", "torchvision", "wandb", "xgboost"] -optional = ["boto3", "botorch", "cmaes (>=0.10.0)", "google-cloud-storage", "matplotlib (!=3.6.0)", "pandas", "plotly (>=4.9.0)", "redis", "scikit-learn (>=0.24.2)"] -test = ["coverage", "fakeredis[lua]", "kaleido", "moto", "pytest", "scipy (>=1.9.2)"] - -[[package]] -name = "oyaml" -version = "1.0" -description = "Ordered YAML: drop-in replacement for PyYAML which preserves dict ordering" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -pyyaml = "*" - -[[package]] -name = "packaging" -version = "23.2" -description = "Core utilities for Python packages" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "pandas" -version = "2.2.1" -description = "Powerful data structures for data analysis, time series, and statistics" -category = "main" -optional = false -python-versions = ">=3.9" - -[package.dependencies] -numpy = [ - {version = ">=1.22.4,<2", markers = "python_version < \"3.11\""}, - {version = ">=1.23.2,<2", markers = "python_version == \"3.11\""}, - {version = ">=1.26.0,<2", markers = "python_version >= \"3.12\""}, -] -python-dateutil = ">=2.8.2" -pytz = ">=2020.1" -tzdata = ">=2022.7" - -[package.extras] -all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] -aws = ["s3fs (>=2022.11.0)"] -clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] -compression = ["zstandard (>=0.19.0)"] -computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] -consortium-standard = ["dataframe-api-compat (>=0.1.7)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] -feather = ["pyarrow (>=10.0.1)"] -fss = ["fsspec (>=2022.11.0)"] -gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] -hdf5 = ["tables (>=3.8.0)"] -html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] -mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] -parquet = ["pyarrow (>=10.0.1)"] -performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] -plot = ["matplotlib (>=3.6.3)"] -postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] -pyarrow = ["pyarrow (>=10.0.1)"] -spss = ["pyreadstat (>=1.2.0)"] -sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] -test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.9.2)"] - -[[package]] -name = "parso" -version = "0.8.3" -description = "A Python Parser" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["docopt", "pytest (<6.0.0)"] - -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" -optional = false -python-versions = ">=3.8" - -[[package]] -name = "pexpect" -version = "4.9.0" -description = "Pexpect allows easy control of interactive console applications." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -ptyprocess = ">=0.5" - -[[package]] -name = "pillow" -version = "10.2.0" -description = "Python Imaging Library (Fork)" -category = "main" -optional = false -python-versions = ">=3.8" - -[package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] -fpx = ["olefile"] -mic = ["olefile"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] -typing = ["typing-extensions"] -xmp = ["defusedxml"] - -[[package]] -name = "platformdirs" -version = "4.2.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" -optional = false -python-versions = ">=3.8" - -[package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] - -[[package]] -name = "plotly" -version = "5.19.0" -description = "An open-source, interactive data visualization library for Python" -category = "main" -optional = false -python-versions = ">=3.8" - -[package.dependencies] -packaging = "*" -tenacity = ">=6.2.0" - -[[package]] -name = "pluggy" -version = "1.4.0" -description = "plugin and hook calling mechanisms for python" -category = "main" -optional = false -python-versions = ">=3.8" - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "prompt-toolkit" -version = "3.0.43" -description = "Library for building powerful interactive command lines in Python" -category = "main" -optional = false -python-versions = ">=3.7.0" - -[package.dependencies] -wcwidth = "*" - -[[package]] -name = "psutil" -version = "5.9.8" -description = "Cross-platform lib for process and system monitoring in Python." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" - -[package.extras] -test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] - -[[package]] -name = "ptyprocess" -version = "0.7.0" -description = "Run a subprocess in a pseudo terminal" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "pure-eval" -version = "0.2.2" -description = "Safely evaluate AST nodes without side effects" -category = "main" -optional = false -python-versions = "*" - -[package.extras] -tests = ["pytest"] - -[[package]] -name = "pyarrow" -version = "15.0.0" -description = "Python library for Apache Arrow" -category = "main" -optional = false -python-versions = ">=3.8" - -[package.dependencies] -numpy = ">=1.16.6,<2" - -[[package]] -name = "pycodestyle" -version = "2.11.1" -description = "Python style guide checker" -category = "dev" -optional = false -python-versions = ">=3.8" - -[[package]] -name = "pycparser" -version = "2.21" -description = "C parser in Python" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pyflakes" -version = "3.2.0" -description = "passive checker of Python programs" -category = "dev" -optional = false -python-versions = ">=3.8" - -[[package]] -name = "Pygments" -version = "2.17.2" -description = "Pygments is a syntax highlighting package written in Python." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -plugins = ["importlib-metadata"] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pyparsing" -version = "3.1.2" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "main" -optional = false -python-versions = ">=3.6.8" - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -name = "pytest" -version = "8.0.2" -description = "pytest: simple powerful testing with Python" -category = "main" -optional = false -python-versions = ">=3.8" - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=1.3.0,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} - -[package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "pytz" -version = "2024.1" -description = "World timezone definitions, modern and historical" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "pywin32" -version = "306" -description = "Python for Window Extensions" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "PyYAML" -version = "6.0.1" -description = "YAML parser and emitter for Python" -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "pyzmq" -version = "25.1.2" -description = "Python bindings for 0MQ" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -cffi = {version = "*", markers = "implementation_name == \"pypy\""} - -[[package]] -name = "requests" -version = "2.31.0" -description = "Python HTTP for Humans." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "retrying" -version = "1.3.4" -description = "Retrying" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -six = ">=1.7.0" - -[[package]] -name = "scikit-learn" -version = "1.4.1.post1" -description = "A set of python modules for machine learning and data mining" -category = "main" -optional = false -python-versions = ">=3.9" - -[package.dependencies] -joblib = ">=1.2.0" -numpy = ">=1.19.5,<2.0" -scipy = ">=1.6.0" -threadpoolctl = ">=2.0.0" - -[package.extras] -benchmark = ["matplotlib (>=3.3.4)", "memory-profiler (>=0.57.0)", "pandas (>=1.1.5)"] -docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.3.4)", "memory-profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)", "sphinx (>=6.0.0)", "sphinx-copybutton (>=0.5.2)", "sphinx-gallery (>=0.15.0)", "sphinx-prompt (>=1.3.0)", "sphinxext-opengraph (>=0.4.2)"] -examples = ["matplotlib (>=3.3.4)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)"] -tests = ["black (>=23.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.3)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "polars (>=0.19.12)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.0.272)", "scikit-image (>=0.17.2)"] - -[[package]] -name = "scipy" -version = "1.12.0" -description = "Fundamental algorithms for scientific computing in Python" -category = "main" -optional = false -python-versions = ">=3.9" - -[package.dependencies] -numpy = ">=1.22.4,<1.29.0" - -[package.extras] -dev = ["click", "cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] -doc = ["jupytext", "matplotlib (>2)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] -test = ["asv", "gmpy2", "hypothesis", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] - -[[package]] -name = "setuptools" -version = "69.1.1" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" -optional = false -python-versions = ">=3.8" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "shap" -version = "0.44.1" -description = "A unified approach to explain the output of any machine learning model." -category = "main" -optional = false -python-versions = ">=3.8" - -[package.dependencies] -cloudpickle = "*" -numba = "*" -numpy = "*" -packaging = ">20.9" -pandas = "*" -scikit-learn = "*" -scipy = "*" -slicer = "0.0.7" -tqdm = ">=4.27.0" - -[package.extras] -docs = ["ipython", "matplotlib", "myst-parser (==2.0.0)", "nbsphinx (==0.9.3)", "numpydoc", "requests", "sphinx (==7.2.6)", "sphinx-github-changelog (==1.2.1)", "sphinx-rtd-theme (==2.0.0)"] -others = ["lime"] -plots = ["ipython", "matplotlib"] -test = ["catboost", "gpboost", "lightgbm", "ngboost", "opencv-python", "protobuf (==3.20.3)", "pyod", "pyspark", "pytest", "pytest-cov", "pytest-mpl", "sentencepiece", "tensorflow", "torch", "torchvision", "transformers", "xgboost"] -test-core = ["pytest", "pytest-cov", "pytest-mpl"] -test_notebooks = ["datasets", "jupyter", "keras", "nbconvert", "nbformat", "nlp", "transformers"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "slicer" -version = "0.0.7" -description = "A small package for big slicing." -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "SQLAlchemy" -version = "2.0.28" -description = "Database Abstraction Library" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} -typing-extensions = ">=4.6.0" - -[package.extras] -aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] -aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] -asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)"] -mysql = ["mysqlclient (>=1.4.0)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx_oracle (>=8)"] -oracle-oracledb = ["oracledb (>=1.0.1)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.29.1)"] -postgresql-psycopg = ["psycopg (>=3.0.7)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] -pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3_binary"] - -[[package]] -name = "stack-data" -version = "0.6.3" -description = "Extract data from python stack frames and tracebacks for informative displays" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -asttokens = ">=2.1.0" -executing = ">=1.2.0" -pure-eval = "*" - -[package.extras] -tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] - -[[package]] -name = "tenacity" -version = "8.2.3" -description = "Retry code until it succeeds" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -doc = ["reno", "sphinx", "tornado (>=4.5)"] - -[[package]] -name = "threadpoolctl" -version = "3.3.0" -description = "threadpoolctl" -category = "main" -optional = false -python-versions = ">=3.8" - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "tornado" -version = "6.4" -description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." -category = "main" -optional = false -python-versions = ">= 3.8" - -[[package]] -name = "tqdm" -version = "4.66.2" -description = "Fast, Extensible Progress Meter" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[package.extras] -dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] -notebook = ["ipywidgets (>=6)"] -slack = ["slack-sdk"] -telegram = ["requests"] - -[[package]] -name = "traitlets" -version = "5.14.1" -description = "Traitlets Python configuration system" -category = "main" -optional = false -python-versions = ">=3.8" - -[package.extras] -docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] -test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<7.5)", "pytest-mock", "pytest-mypy-testing"] - -[[package]] -name = "typing-extensions" -version = "4.10.0" -description = "Backported and Experimental Type Hints for Python 3.8+" -category = "main" -optional = false -python-versions = ">=3.8" - -[[package]] -name = "tzdata" -version = "2024.1" -description = "Provider of IANA time zone data" -category = "main" -optional = false -python-versions = ">=2" - -[[package]] -name = "urllib3" -version = "2.2.1" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" -optional = false -python-versions = ">=3.8" - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "waitress" -version = "3.0.0" -description = "Waitress WSGI server" -category = "main" -optional = false -python-versions = ">=3.8.0" - -[package.extras] -docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.9)"] -testing = ["coverage (>=5.0)", "pytest", "pytest-cov"] - -[[package]] -name = "wcwidth" -version = "0.2.13" -description = "Measures the displayed width of unicode strings in a terminal" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "Werkzeug" -version = "3.0.1" -description = "The comprehensive WSGI web application library." -category = "main" -optional = false -python-versions = ">=3.8" - -[package.dependencies] -MarkupSafe = ">=2.1.1" - -[package.extras] -watchdog = ["watchdog (>=2.3)"] - -[[package]] -name = "WTForms" -version = "3.1.2" -description = "Form validation and rendering for Python web development." -category = "main" -optional = false -python-versions = ">=3.8" - -[package.dependencies] -markupsafe = "*" - -[package.extras] -email = ["email-validator"] - -[[package]] -name = "xgboost" -version = "2.0.3" -description = "XGBoost Python Package" -category = "main" -optional = true -python-versions = ">=3.8" - -[package.dependencies] -numpy = "*" -scipy = "*" - -[package.extras] -dask = ["dask", "distributed", "pandas"] -datatable = ["datatable"] -pandas = ["pandas"] -plotting = ["graphviz", "matplotlib"] -pyspark = ["cloudpickle", "pyspark", "scikit-learn"] -scikit-learn = ["scikit-learn"] - -[[package]] -name = "zipp" -version = "3.17.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" -optional = false -python-versions = ">=3.8" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] - -[extras] -all_models = ["xgboost"] -xgboost = ["xgboost"] - -[metadata] -lock-version = "1.1" -python-versions = "^3.9" -content-hash = "1569b3419d8a6c81d4ac0603e9d20e605f68f339527079764611a2454dee0d5b" - -[metadata.files] -alembic = [ - {file = "alembic-1.13.1-py3-none-any.whl", hash = "sha256:2edcc97bed0bd3272611ce3a98d98279e9c209e7186e43e75bbb1b2bdfdbcc43"}, - {file = "alembic-1.13.1.tar.gz", hash = "sha256:4932c8558bf68f2ee92b9bbcb8218671c627064d5b08939437af6d77dc05e595"}, -] -ansi2html = [ - {file = "ansi2html-1.9.1-py3-none-any.whl", hash = "sha256:29ccdb1e83520d648ebdc9c9544059ea4d424ecc33d3ef723657f7f5a9ae5225"}, - {file = "ansi2html-1.9.1.tar.gz", hash = "sha256:5c6837a13ecc1903aab7a545353312049dfedfe5105362ad3a8d9d207871ec71"}, -] -appnope = [ - {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, - {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, -] -asttokens = [ - {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, - {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, -] -black = [ - {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, - {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, - {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"}, - {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"}, - {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"}, - {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"}, - {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"}, - {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"}, - {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"}, - {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"}, - {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"}, - {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"}, - {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"}, - {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"}, - {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"}, - {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"}, - {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"}, - {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"}, - {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"}, - {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"}, - {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"}, - {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"}, -] -blinker = [ - {file = "blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9"}, - {file = "blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"}, -] -certifi = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, -] -cffi = [ - {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, - {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, - {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, - {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, - {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, - {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, - {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, - {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, - {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, - {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, - {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, - {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, - {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, - {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, - {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, -] -charset-normalizer = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, -] -click = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] -cloudpickle = [ - {file = "cloudpickle-3.0.0-py3-none-any.whl", hash = "sha256:246ee7d0c295602a036e86369c77fecda4ab17b506496730f2f576d9016fd9c7"}, - {file = "cloudpickle-3.0.0.tar.gz", hash = "sha256:996d9a482c6fb4f33c1a35335cf8afd065d2a56e973270364840712d9131a882"}, -] -colorama = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -colorlog = [ - {file = "colorlog-6.8.2-py3-none-any.whl", hash = "sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33"}, - {file = "colorlog-6.8.2.tar.gz", hash = "sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44"}, -] -colour = [ - {file = "colour-0.1.5-py2.py3-none-any.whl", hash = "sha256:33f6db9d564fadc16e59921a56999b79571160ce09916303d35346dddc17978c"}, - {file = "colour-0.1.5.tar.gz", hash = "sha256:af20120fefd2afede8b001fbef2ea9da70ad7d49fafdb6489025dae8745c3aee"}, -] -comm = [ - {file = "comm-0.2.1-py3-none-any.whl", hash = "sha256:87928485c0dfc0e7976fd89fc1e187023cf587e7c353e4a9b417555b44adf021"}, - {file = "comm-0.2.1.tar.gz", hash = "sha256:0bc91edae1344d39d3661dcbc36937181fdaddb304790458f8b044dbc064b89a"}, -] -contourpy = [ - {file = "contourpy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0274c1cb63625972c0c007ab14dd9ba9e199c36ae1a231ce45d725cbcbfd10a8"}, - {file = "contourpy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab459a1cbbf18e8698399c595a01f6dcc5c138220ca3ea9e7e6126232d102bb4"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fdd887f17c2f4572ce548461e4f96396681212d858cae7bd52ba3310bc6f00f"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d16edfc3fc09968e09ddffada434b3bf989bf4911535e04eada58469873e28e"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c203f617abc0dde5792beb586f827021069fb6d403d7f4d5c2b543d87edceb9"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b69303ceb2e4d4f146bf82fda78891ef7bcd80c41bf16bfca3d0d7eb545448aa"}, - {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:884c3f9d42d7218304bc74a8a7693d172685c84bd7ab2bab1ee567b769696df9"}, - {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4a1b1208102be6e851f20066bf0e7a96b7d48a07c9b0cfe6d0d4545c2f6cadab"}, - {file = "contourpy-1.2.0-cp310-cp310-win32.whl", hash = "sha256:34b9071c040d6fe45d9826cbbe3727d20d83f1b6110d219b83eb0e2a01d79488"}, - {file = "contourpy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:bd2f1ae63998da104f16a8b788f685e55d65760cd1929518fd94cd682bf03e41"}, - {file = "contourpy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd10c26b4eadae44783c45ad6655220426f971c61d9b239e6f7b16d5cdaaa727"}, - {file = "contourpy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c6b28956b7b232ae801406e529ad7b350d3f09a4fde958dfdf3c0520cdde0dd"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebeac59e9e1eb4b84940d076d9f9a6cec0064e241818bcb6e32124cc5c3e377a"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:139d8d2e1c1dd52d78682f505e980f592ba53c9f73bd6be102233e358b401063"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e9dc350fb4c58adc64df3e0703ab076f60aac06e67d48b3848c23647ae4310e"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18fc2b4ed8e4a8fe849d18dce4bd3c7ea637758c6343a1f2bae1e9bd4c9f4686"}, - {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:16a7380e943a6d52472096cb7ad5264ecee36ed60888e2a3d3814991a0107286"}, - {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8d8faf05be5ec8e02a4d86f616fc2a0322ff4a4ce26c0f09d9f7fb5330a35c95"}, - {file = "contourpy-1.2.0-cp311-cp311-win32.whl", hash = "sha256:67b7f17679fa62ec82b7e3e611c43a016b887bd64fb933b3ae8638583006c6d6"}, - {file = "contourpy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:99ad97258985328b4f207a5e777c1b44a83bfe7cf1f87b99f9c11d4ee477c4de"}, - {file = "contourpy-1.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:575bcaf957a25d1194903a10bc9f316c136c19f24e0985a2b9b5608bdf5dbfe0"}, - {file = "contourpy-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9e6c93b5b2dbcedad20a2f18ec22cae47da0d705d454308063421a3b290d9ea4"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:464b423bc2a009088f19bdf1f232299e8b6917963e2b7e1d277da5041f33a779"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68ce4788b7d93e47f84edd3f1f95acdcd142ae60bc0e5493bfd120683d2d4316"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d7d1f8871998cdff5d2ff6a087e5e1780139abe2838e85b0b46b7ae6cc25399"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e739530c662a8d6d42c37c2ed52a6f0932c2d4a3e8c1f90692ad0ce1274abe0"}, - {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:247b9d16535acaa766d03037d8e8fb20866d054d3c7fbf6fd1f993f11fc60ca0"}, - {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:461e3ae84cd90b30f8d533f07d87c00379644205b1d33a5ea03381edc4b69431"}, - {file = "contourpy-1.2.0-cp312-cp312-win32.whl", hash = "sha256:1c2559d6cffc94890b0529ea7eeecc20d6fadc1539273aa27faf503eb4656d8f"}, - {file = "contourpy-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:491b1917afdd8638a05b611a56d46587d5a632cabead889a5440f7c638bc6ed9"}, - {file = "contourpy-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5fd1810973a375ca0e097dee059c407913ba35723b111df75671a1976efa04bc"}, - {file = "contourpy-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:999c71939aad2780f003979b25ac5b8f2df651dac7b38fb8ce6c46ba5abe6ae9"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7caf9b241464c404613512d5594a6e2ff0cc9cb5615c9475cc1d9b514218ae8"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:266270c6f6608340f6c9836a0fb9b367be61dde0c9a9a18d5ece97774105ff3e"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbd50d0a0539ae2e96e537553aff6d02c10ed165ef40c65b0e27e744a0f10af8"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11f8d2554e52f459918f7b8e6aa20ec2a3bce35ce95c1f0ef4ba36fbda306df5"}, - {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ce96dd400486e80ac7d195b2d800b03e3e6a787e2a522bfb83755938465a819e"}, - {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6d3364b999c62f539cd403f8123ae426da946e142312a514162adb2addd8d808"}, - {file = "contourpy-1.2.0-cp39-cp39-win32.whl", hash = "sha256:1c88dfb9e0c77612febebb6ac69d44a8d81e3dc60f993215425b62c1161353f4"}, - {file = "contourpy-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:78e6ad33cf2e2e80c5dfaaa0beec3d61face0fb650557100ee36db808bfa6843"}, - {file = "contourpy-1.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:be16975d94c320432657ad2402f6760990cb640c161ae6da1363051805fa8108"}, - {file = "contourpy-1.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b95a225d4948b26a28c08307a60ac00fb8671b14f2047fc5476613252a129776"}, - {file = "contourpy-1.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d7e03c0f9a4f90dc18d4e77e9ef4ec7b7bbb437f7f675be8e530d65ae6ef956"}, - {file = "contourpy-1.2.0.tar.gz", hash = "sha256:171f311cb758de7da13fc53af221ae47a5877be5a0843a9fe150818c51ed276a"}, -] -cycler = [ - {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, - {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, -] -dash = [ - {file = "dash-2.16.0-py3-none-any.whl", hash = "sha256:2a06e3856d9389656e1c84d6afb086748db0bbb676f44abb899df117c2879606"}, - {file = "dash-2.16.0.tar.gz", hash = "sha256:7887998e278f6cc1b8200ec9d9d6f82b5cb599997a31723c926487a25ce52117"}, -] -dash-auth = [ - {file = "dash_auth-2.2.1-py3-none-any.whl", hash = "sha256:3109c126a862da5dfb9e48e816eb917223e5629acb87ad5634063a0e38e67cef"}, - {file = "dash_auth-2.2.1.tar.gz", hash = "sha256:cb821fdedbcd73e59e53b1c8247d81a66177769bc70cdaa16c6bb0b3639258bb"}, -] -dash-bootstrap-components = [ - {file = "dash-bootstrap-components-1.5.0.tar.gz", hash = "sha256:083158c07434b9965e2d6c3e8ca72dbbe47dab23e676258cef9bf0ad47d2e250"}, - {file = "dash_bootstrap_components-1.5.0-py3-none-any.whl", hash = "sha256:b487fec1a85e3d6a8564fe04c0a9cd9e846f75ea9e563456ed3879592889c591"}, -] -dash-core-components = [ - {file = "dash_core_components-2.0.0-py3-none-any.whl", hash = "sha256:52b8e8cce13b18d0802ee3acbc5e888cb1248a04968f962d63d070400af2e346"}, - {file = "dash_core_components-2.0.0.tar.gz", hash = "sha256:c6733874af975e552f95a1398a16c2ee7df14ce43fa60bb3718a3c6e0b63ffee"}, -] -dash-html-components = [ - {file = "dash_html_components-2.0.0-py3-none-any.whl", hash = "sha256:b42cc903713c9706af03b3f2548bda4be7307a7cf89b7d6eae3da872717d1b63"}, - {file = "dash_html_components-2.0.0.tar.gz", hash = "sha256:8703a601080f02619a6390998e0b3da4a5daabe97a1fd7a9cebc09d015f26e50"}, -] -dash-table = [ - {file = "dash_table-5.0.0-py3-none-any.whl", hash = "sha256:19036fa352bb1c11baf38068ec62d172f0515f73ca3276c79dee49b95ddc16c9"}, - {file = "dash_table-5.0.0.tar.gz", hash = "sha256:18624d693d4c8ef2ddec99a6f167593437a7ea0bf153aa20f318c170c5bc7308"}, -] -debugpy = [ - {file = "debugpy-1.8.1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:3bda0f1e943d386cc7a0e71bfa59f4137909e2ed947fb3946c506e113000f741"}, - {file = "debugpy-1.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dda73bf69ea479c8577a0448f8c707691152e6c4de7f0c4dec5a4bc11dee516e"}, - {file = "debugpy-1.8.1-cp310-cp310-win32.whl", hash = "sha256:3a79c6f62adef994b2dbe9fc2cc9cc3864a23575b6e387339ab739873bea53d0"}, - {file = "debugpy-1.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:7eb7bd2b56ea3bedb009616d9e2f64aab8fc7000d481faec3cd26c98a964bcdd"}, - {file = "debugpy-1.8.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:016a9fcfc2c6b57f939673c874310d8581d51a0fe0858e7fac4e240c5eb743cb"}, - {file = "debugpy-1.8.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd97ed11a4c7f6d042d320ce03d83b20c3fb40da892f994bc041bbc415d7a099"}, - {file = "debugpy-1.8.1-cp311-cp311-win32.whl", hash = "sha256:0de56aba8249c28a300bdb0672a9b94785074eb82eb672db66c8144fff673146"}, - {file = "debugpy-1.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:1a9fe0829c2b854757b4fd0a338d93bc17249a3bf69ecf765c61d4c522bb92a8"}, - {file = "debugpy-1.8.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3ebb70ba1a6524d19fa7bb122f44b74170c447d5746a503e36adc244a20ac539"}, - {file = "debugpy-1.8.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2e658a9630f27534e63922ebf655a6ab60c370f4d2fc5c02a5b19baf4410ace"}, - {file = "debugpy-1.8.1-cp312-cp312-win32.whl", hash = "sha256:caad2846e21188797a1f17fc09c31b84c7c3c23baf2516fed5b40b378515bbf0"}, - {file = "debugpy-1.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:edcc9f58ec0fd121a25bc950d4578df47428d72e1a0d66c07403b04eb93bcf98"}, - {file = "debugpy-1.8.1-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:7a3afa222f6fd3d9dfecd52729bc2e12c93e22a7491405a0ecbf9e1d32d45b39"}, - {file = "debugpy-1.8.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d915a18f0597ef685e88bb35e5d7ab968964b7befefe1aaea1eb5b2640b586c7"}, - {file = "debugpy-1.8.1-cp38-cp38-win32.whl", hash = "sha256:92116039b5500633cc8d44ecc187abe2dfa9b90f7a82bbf81d079fcdd506bae9"}, - {file = "debugpy-1.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:e38beb7992b5afd9d5244e96ad5fa9135e94993b0c551ceebf3fe1a5d9beb234"}, - {file = "debugpy-1.8.1-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:bfb20cb57486c8e4793d41996652e5a6a885b4d9175dd369045dad59eaacea42"}, - {file = "debugpy-1.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efd3fdd3f67a7e576dd869c184c5dd71d9aaa36ded271939da352880c012e703"}, - {file = "debugpy-1.8.1-cp39-cp39-win32.whl", hash = "sha256:58911e8521ca0c785ac7a0539f1e77e0ce2df753f786188f382229278b4cdf23"}, - {file = "debugpy-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:6df9aa9599eb05ca179fb0b810282255202a66835c6efb1d112d21ecb830ddd3"}, - {file = "debugpy-1.8.1-py2.py3-none-any.whl", hash = "sha256:28acbe2241222b87e255260c76741e1fbf04fdc3b6d094fcf57b6c6f75ce1242"}, - {file = "debugpy-1.8.1.zip", hash = "sha256:f696d6be15be87aef621917585f9bb94b1dc9e8aced570db1b8a6fc14e8f9b42"}, -] -decorator = [ - {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, - {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, -] -dtreeviz = [ - {file = "dtreeviz-2.2.2-py3-none-any.whl", hash = "sha256:e510d80857b29f15a12e8ebe2c7ab3fb667096739742ab47e5d8889cf78da8fe"}, - {file = "dtreeviz-2.2.2.tar.gz", hash = "sha256:a145ff91ffaa11bd2f59186fdcffaa384d90c1757517cbb27e66cc83922319ba"}, -] -exceptiongroup = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, -] -executing = [ - {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, - {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, -] -explainerdashboard = [ - {file = "explainerdashboard-0.4.5-py3-none-any.whl", hash = "sha256:ca2a1f43ebcdec44f33eb9dc49b3ea9789f05be9ed1d1bd0b6219dfa4338e937"}, - {file = "explainerdashboard-0.4.5.tar.gz", hash = "sha256:c489db883669c8c53bd12ef8548b16b3dee737e8e249959a2c3550fc604ed871"}, -] -flake8 = [ - {file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"}, - {file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"}, -] -Flask = [ - {file = "flask-3.0.2-py3-none-any.whl", hash = "sha256:3232e0e9c850d781933cf0207523d1ece087eb8d87b23777ae38456e2fbe7c6e"}, - {file = "flask-3.0.2.tar.gz", hash = "sha256:822c03f4b799204250a7ee84b1eddc40665395333973dfb9deebfe425fefcb7d"}, -] -flask-simplelogin = [ - {file = "flask_simplelogin-0.1.2-py3-none-any.whl", hash = "sha256:484845c10d6e48bc88d20e2fb19de565c2fe608a59bcbb902572f07b5437b188"}, - {file = "flask_simplelogin-0.1.2.tar.gz", hash = "sha256:a99b7ded371aa0f5ecf3271c160e3fd52653abd5d879acf9ae621c4825345a8f"}, -] -Flask-WTF = [ - {file = "flask_wtf-1.2.1-py3-none-any.whl", hash = "sha256:fa6793f2fb7e812e0fe9743b282118e581fb1b6c45d414b8af05e659bd653287"}, - {file = "flask_wtf-1.2.1.tar.gz", hash = "sha256:8bb269eb9bb46b87e7c8233d7e7debdf1f8b74bf90cc1789988c29b37a97b695"}, -] -fonttools = [ - {file = "fonttools-4.49.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d970ecca0aac90d399e458f0b7a8a597e08f95de021f17785fb68e2dc0b99717"}, - {file = "fonttools-4.49.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac9a745b7609f489faa65e1dc842168c18530874a5f5b742ac3dd79e26bca8bc"}, - {file = "fonttools-4.49.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ba0e00620ca28d4ca11fc700806fd69144b463aa3275e1b36e56c7c09915559"}, - {file = "fonttools-4.49.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdee3ab220283057e7840d5fb768ad4c2ebe65bdba6f75d5d7bf47f4e0ed7d29"}, - {file = "fonttools-4.49.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ce7033cb61f2bb65d8849658d3786188afd80f53dad8366a7232654804529532"}, - {file = "fonttools-4.49.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:07bc5ea02bb7bc3aa40a1eb0481ce20e8d9b9642a9536cde0218290dd6085828"}, - {file = "fonttools-4.49.0-cp310-cp310-win32.whl", hash = "sha256:86eef6aab7fd7c6c8545f3ebd00fd1d6729ca1f63b0cb4d621bccb7d1d1c852b"}, - {file = "fonttools-4.49.0-cp310-cp310-win_amd64.whl", hash = "sha256:1fac1b7eebfce75ea663e860e7c5b4a8831b858c17acd68263bc156125201abf"}, - {file = "fonttools-4.49.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:edc0cce355984bb3c1d1e89d6a661934d39586bb32191ebff98c600f8957c63e"}, - {file = "fonttools-4.49.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:83a0d9336de2cba86d886507dd6e0153df333ac787377325a39a2797ec529814"}, - {file = "fonttools-4.49.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36c8865bdb5cfeec88f5028e7e592370a0657b676c6f1d84a2108e0564f90e22"}, - {file = "fonttools-4.49.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33037d9e56e2562c710c8954d0f20d25b8386b397250d65581e544edc9d6b942"}, - {file = "fonttools-4.49.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8fb022d799b96df3eaa27263e9eea306bd3d437cc9aa981820850281a02b6c9a"}, - {file = "fonttools-4.49.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33c584c0ef7dc54f5dd4f84082eabd8d09d1871a3d8ca2986b0c0c98165f8e86"}, - {file = "fonttools-4.49.0-cp311-cp311-win32.whl", hash = "sha256:cbe61b158deb09cffdd8540dc4a948d6e8f4d5b4f3bf5cd7db09bd6a61fee64e"}, - {file = "fonttools-4.49.0-cp311-cp311-win_amd64.whl", hash = "sha256:fc11e5114f3f978d0cea7e9853627935b30d451742eeb4239a81a677bdee6bf6"}, - {file = "fonttools-4.49.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d647a0e697e5daa98c87993726da8281c7233d9d4ffe410812a4896c7c57c075"}, - {file = "fonttools-4.49.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f3bbe672df03563d1f3a691ae531f2e31f84061724c319652039e5a70927167e"}, - {file = "fonttools-4.49.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bebd91041dda0d511b0d303180ed36e31f4f54b106b1259b69fade68413aa7ff"}, - {file = "fonttools-4.49.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4145f91531fd43c50f9eb893faa08399816bb0b13c425667c48475c9f3a2b9b5"}, - {file = "fonttools-4.49.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ea329dafb9670ffbdf4dbc3b0e5c264104abcd8441d56de77f06967f032943cb"}, - {file = "fonttools-4.49.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c076a9e548521ecc13d944b1d261ff3d7825048c338722a4bd126d22316087b7"}, - {file = "fonttools-4.49.0-cp312-cp312-win32.whl", hash = "sha256:b607ea1e96768d13be26d2b400d10d3ebd1456343eb5eaddd2f47d1c4bd00880"}, - {file = "fonttools-4.49.0-cp312-cp312-win_amd64.whl", hash = "sha256:a974c49a981e187381b9cc2c07c6b902d0079b88ff01aed34695ec5360767034"}, - {file = "fonttools-4.49.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b85ec0bdd7bdaa5c1946398cbb541e90a6dfc51df76dfa88e0aaa41b335940cb"}, - {file = "fonttools-4.49.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:af20acbe198a8a790618ee42db192eb128afcdcc4e96d99993aca0b60d1faeb4"}, - {file = "fonttools-4.49.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d418b1fee41a1d14931f7ab4b92dc0bc323b490e41d7a333eec82c9f1780c75"}, - {file = "fonttools-4.49.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b44a52b8e6244b6548851b03b2b377a9702b88ddc21dcaf56a15a0393d425cb9"}, - {file = "fonttools-4.49.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7c7125068e04a70739dad11857a4d47626f2b0bd54de39e8622e89701836eabd"}, - {file = "fonttools-4.49.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:29e89d0e1a7f18bc30f197cfadcbef5a13d99806447c7e245f5667579a808036"}, - {file = "fonttools-4.49.0-cp38-cp38-win32.whl", hash = "sha256:9d95fa0d22bf4f12d2fb7b07a46070cdfc19ef5a7b1c98bc172bfab5bf0d6844"}, - {file = "fonttools-4.49.0-cp38-cp38-win_amd64.whl", hash = "sha256:768947008b4dc552d02772e5ebd49e71430a466e2373008ce905f953afea755a"}, - {file = "fonttools-4.49.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:08877e355d3dde1c11973bb58d4acad1981e6d1140711230a4bfb40b2b937ccc"}, - {file = "fonttools-4.49.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fdb54b076f25d6b0f0298dc706acee5052de20c83530fa165b60d1f2e9cbe3cb"}, - {file = "fonttools-4.49.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0af65c720520710cc01c293f9c70bd69684365c6015cc3671db2b7d807fe51f2"}, - {file = "fonttools-4.49.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f255ce8ed7556658f6d23f6afd22a6d9bbc3edb9b96c96682124dc487e1bf42"}, - {file = "fonttools-4.49.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d00af0884c0e65f60dfaf9340e26658836b935052fdd0439952ae42e44fdd2be"}, - {file = "fonttools-4.49.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:263832fae27481d48dfafcc43174644b6706639661e242902ceb30553557e16c"}, - {file = "fonttools-4.49.0-cp39-cp39-win32.whl", hash = "sha256:0404faea044577a01bb82d47a8fa4bc7a54067fa7e324785dd65d200d6dd1133"}, - {file = "fonttools-4.49.0-cp39-cp39-win_amd64.whl", hash = "sha256:b050d362df50fc6e38ae3954d8c29bf2da52be384649ee8245fdb5186b620836"}, - {file = "fonttools-4.49.0-py3-none-any.whl", hash = "sha256:af281525e5dd7fa0b39fb1667b8d5ca0e2a9079967e14c4bfe90fd1cd13e0f18"}, - {file = "fonttools-4.49.0.tar.gz", hash = "sha256:ebf46e7f01b7af7861310417d7c49591a85d99146fc23a5ba82fdb28af156321"}, -] -graphviz = [ - {file = "graphviz-0.20.1-py3-none-any.whl", hash = "sha256:587c58a223b51611c0cf461132da386edd896a029524ca61a1462b880bf97977"}, - {file = "graphviz-0.20.1.zip", hash = "sha256:8c58f14adaa3b947daf26c19bc1e98c4e0702cdc31cf99153e6f06904d492bf8"}, -] -greenlet = [ - {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, - {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, - {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, - {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, - {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, - {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, - {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, - {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, - {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, - {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, - {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, - {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, - {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, - {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, - {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, - {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, -] -idna = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, -] -importlib-metadata = [ - {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, - {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"}, -] -importlib-resources = [ - {file = "importlib_resources-6.1.2-py3-none-any.whl", hash = "sha256:9a0a862501dc38b68adebc82970140c9e4209fc99601782925178f8386339938"}, - {file = "importlib_resources-6.1.2.tar.gz", hash = "sha256:308abf8474e2dba5f867d279237cd4076482c3de7104a40b41426370e891549b"}, -] -iniconfig = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] -ipykernel = [ - {file = "ipykernel-6.29.3-py3-none-any.whl", hash = "sha256:5aa086a4175b0229d4eca211e181fb473ea78ffd9869af36ba7694c947302a21"}, - {file = "ipykernel-6.29.3.tar.gz", hash = "sha256:e14c250d1f9ea3989490225cc1a542781b095a18a19447fcf2b5eaf7d0ac5bd2"}, -] -ipython = [ - {file = "ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"}, - {file = "ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27"}, -] -isort = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, -] -itsdangerous = [ - {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, - {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, -] -jedi = [ - {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, - {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, -] -Jinja2 = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, -] -joblib = [ - {file = "joblib-1.3.2-py3-none-any.whl", hash = "sha256:ef4331c65f239985f3f2220ecc87db222f08fd22097a3dd5698f693875f8cbb9"}, - {file = "joblib-1.3.2.tar.gz", hash = "sha256:92f865e621e17784e7955080b6d042489e3b8e294949cc44c6eac304f59772b1"}, -] -jupyter-client = [ - {file = "jupyter_client-8.6.0-py3-none-any.whl", hash = "sha256:909c474dbe62582ae62b758bca86d6518c85234bdee2d908c778db6d72f39d99"}, - {file = "jupyter_client-8.6.0.tar.gz", hash = "sha256:0642244bb83b4764ae60d07e010e15f0e2d275ec4e918a8f7b80fbbef3ca60c7"}, -] -jupyter-core = [ - {file = "jupyter_core-5.7.1-py3-none-any.whl", hash = "sha256:c65c82126453a723a2804aa52409930434598fd9d35091d63dfb919d2b765bb7"}, - {file = "jupyter_core-5.7.1.tar.gz", hash = "sha256:de61a9d7fc71240f688b2fb5ab659fbb56979458dc66a71decd098e03c79e218"}, -] -jupyter-dash = [ - {file = "jupyter-dash-0.4.2.tar.gz", hash = "sha256:d546c7c25a2867c14c95a48af0ad572803b26915a5ce6052158c9dede4dbf48c"}, - {file = "jupyter_dash-0.4.2-py3-none-any.whl", hash = "sha256:b07d90ccf38d4dfb04efd630a2b2627f367b79fa4296ee3912d0c4e21e73e9b2"}, -] -kiwisolver = [ +files = [ {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af"}, {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3"}, {file = "kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4"}, @@ -2140,7 +1291,14 @@ kiwisolver = [ {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee"}, {file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"}, ] -llvmlite = [ + +[[package]] +name = "llvmlite" +version = "0.42.0" +description = "lightweight wrapper around basic LLVM functionality" +optional = false +python-versions = ">=3.9" +files = [ {file = "llvmlite-0.42.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3366938e1bf63d26c34fbfb4c8e8d2ded57d11e0567d5bb243d89aab1eb56098"}, {file = "llvmlite-0.42.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c35da49666a21185d21b551fc3caf46a935d54d66969d32d72af109b5e7d2b6f"}, {file = "llvmlite-0.42.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70f44ccc3c6220bd23e0ba698a63ec2a7d3205da0d848804807f37fc243e3f77"}, @@ -2163,11 +1321,33 @@ llvmlite = [ {file = "llvmlite-0.42.0-cp39-cp39-win_amd64.whl", hash = "sha256:43d65cc4e206c2e902c1004dd5418417c4efa6c1d04df05c6c5675a27e8ca90e"}, {file = "llvmlite-0.42.0.tar.gz", hash = "sha256:f92b09243c0cc3f457da8b983f67bd8e1295d0f5b3746c7a1861d7a99403854a"}, ] -Mako = [ + +[[package]] +name = "mako" +version = "1.3.2" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +optional = false +python-versions = ">=3.8" +files = [ {file = "Mako-1.3.2-py3-none-any.whl", hash = "sha256:32a99d70754dfce237019d17ffe4a282d2d3351b9c476e90d8a60e63f133b80c"}, {file = "Mako-1.3.2.tar.gz", hash = "sha256:2a0c8ad7f6274271b3bb7467dd37cf9cc6dab4bc19cb69a4ef10669402de698e"}, ] -MarkupSafe = [ + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["Babel"] +lingua = ["lingua"] +testing = ["pytest"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, @@ -2229,7 +1409,14 @@ MarkupSafe = [ {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] -matplotlib = [ + +[[package]] +name = "matplotlib" +version = "3.8.3" +description = "Python plotting package" +optional = false +python-versions = ">=3.9" +files = [ {file = "matplotlib-3.8.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cf60138ccc8004f117ab2a2bad513cc4d122e55864b4fe7adf4db20ca68a078f"}, {file = "matplotlib-3.8.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f557156f7116be3340cdeef7f128fa99b0d5d287d5f41a16e169819dcf22357"}, {file = "matplotlib-3.8.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f386cf162b059809ecfac3bcc491a9ea17da69fa35c8ded8ad154cd4b933d5ec"}, @@ -2259,23 +1446,73 @@ matplotlib = [ {file = "matplotlib-3.8.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5e431a09e6fab4012b01fc155db0ce6dccacdbabe8198197f523a4ef4805eb26"}, {file = "matplotlib-3.8.3.tar.gz", hash = "sha256:7b416239e9ae38be54b028abbf9048aff5054a9aba5416bef0bd17f9162ce161"}, ] -matplotlib-inline = [ + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} +kiwisolver = ">=1.3.1" +numpy = ">=1.21,<2" +packaging = ">=20.0" +pillow = ">=8" +pyparsing = ">=2.3.1" +python-dateutil = ">=2.7" + +[[package]] +name = "matplotlib-inline" +version = "0.1.6" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.5" +files = [ {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, ] -mccabe = [ + +[package.dependencies] +traitlets = "*" + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] -mypy-extensions = [ + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] -nest-asyncio = [ + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +description = "Patch asyncio to allow nested event loops" +optional = false +python-versions = ">=3.5" +files = [ {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, ] -numba = [ + +[[package]] +name = "numba" +version = "0.59.0" +description = "compiling Python code using LLVM" +optional = false +python-versions = ">=3.9" +files = [ {file = "numba-0.59.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d061d800473fb8fef76a455221f4ad649a53f5e0f96e3f6c8b8553ee6fa98fa"}, {file = "numba-0.59.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c086a434e7d3891ce5dfd3d1e7ee8102ac1e733962098578b507864120559ceb"}, {file = "numba-0.59.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9e20736bf62e61f8353fb71b0d3a1efba636c7a303d511600fc57648b55823ed"}, @@ -2298,7 +1535,18 @@ numba = [ {file = "numba-0.59.0-cp39-cp39-win_amd64.whl", hash = "sha256:32bd0a41525ec0b1b853da244808f4e5333867df3c43c30c33f89cf20b9c2b63"}, {file = "numba-0.59.0.tar.gz", hash = "sha256:12b9b064a3e4ad00e2371fc5212ef0396c80f41caec9b5ec391c8b04b6eaf2a8"}, ] -numpy = [ + +[package.dependencies] +llvmlite = "==0.42.*" +numpy = ">=1.22,<1.27" + +[[package]] +name = "numpy" +version = "1.26.4" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, @@ -2336,19 +1584,67 @@ numpy = [ {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, ] -optuna = [ + +[[package]] +name = "optuna" +version = "3.5.0" +description = "A hyperparameter optimization framework" +optional = false +python-versions = ">=3.7" +files = [ {file = "optuna-3.5.0-py3-none-any.whl", hash = "sha256:4c86bbcaeff9ad5b0758e87537793f66df8f9352246315d7121ea465724a44e8"}, {file = "optuna-3.5.0.tar.gz", hash = "sha256:ca9e1ce16aa6c6a5af0e1cc1d0cbcd98eb1c75b6a2f06be6bd9c0c5ab0698724"}, ] -oyaml = [ + +[package.dependencies] +alembic = ">=1.5.0" +colorlog = "*" +numpy = "*" +packaging = ">=20.0" +PyYAML = "*" +sqlalchemy = ">=1.3.0" +tqdm = "*" + +[package.extras] +benchmark = ["asv (>=0.5.0)", "botorch", "cma", "scikit-optimize", "virtualenv"] +checking = ["black", "blackdoc", "flake8", "isort", "mypy", "mypy-boto3-s3", "types-PyYAML", "types-redis", "types-setuptools", "types-tqdm", "typing-extensions (>=3.10.0.0)"] +document = ["ase", "botorch", "cma", "cmaes (>=0.10.0)", "distributed", "fvcore", "lightgbm", "matplotlib (!=3.6.0)", "mlflow", "pandas", "pillow", "plotly (>=4.9.0)", "scikit-learn", "scikit-optimize", "sphinx", "sphinx-copybutton", "sphinx-gallery", "sphinx-plotly-directive", "sphinx-rtd-theme (>=1.2.0)", "torch", "torchaudio", "torchvision"] +integration = ["botorch (>=0.4.0)", "catboost (>=0.26)", "catboost (>=0.26,<1.2)", "cma", "distributed", "lightgbm", "lightning", "mlflow", "pandas", "pytorch-ignite", "scikit-learn (>=0.24.2)", "scikit-optimize", "shap", "tensorflow", "torch", "torchaudio", "torchvision", "wandb", "xgboost"] +optional = ["boto3", "botorch", "cmaes (>=0.10.0)", "google-cloud-storage", "matplotlib (!=3.6.0)", "pandas", "plotly (>=4.9.0)", "redis", "scikit-learn (>=0.24.2)"] +test = ["coverage", "fakeredis[lua]", "kaleido", "moto", "pytest", "scipy (>=1.9.2)"] + +[[package]] +name = "oyaml" +version = "1.0" +description = "Ordered YAML: drop-in replacement for PyYAML which preserves dict ordering" +optional = false +python-versions = "*" +files = [ {file = "oyaml-1.0-py2.py3-none-any.whl", hash = "sha256:3a378747b7fb2425533d1ce41962d6921cda075d46bb480a158d45242d156323"}, {file = "oyaml-1.0.tar.gz", hash = "sha256:ed8fc096811f4763e1907dce29c35895d6d5936c4d0400fe843a91133d4744ed"}, ] -packaging = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + +[package.dependencies] +pyyaml = "*" + +[[package]] +name = "packaging" +version = "24.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] -pandas = [ + +[[package]] +name = "pandas" +version = "2.2.1" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.9" +files = [ {file = "pandas-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8df8612be9cd1c7797c93e1c5df861b2ddda0b48b08f2c3eaa0702cf88fb5f88"}, {file = "pandas-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0f573ab277252ed9aaf38240f3b54cfc90fff8e5cab70411ee1d03f5d51f3944"}, {file = "pandas-2.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f02a3a6c83df4026e55b63c1f06476c9aa3ed6af3d89b4f04ea656ccdaaaa359"}, @@ -2379,19 +1675,89 @@ pandas = [ {file = "pandas-2.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:1ba21b1d5c0e43416218db63037dbe1a01fc101dc6e6024bcad08123e48004ab"}, {file = "pandas-2.2.1.tar.gz", hash = "sha256:0ab90f87093c13f3e8fa45b48ba9f39181046e8f3317d3aadb2fffbb1b978572"}, ] -parso = [ + +[package.dependencies] +numpy = [ + {version = ">=1.22.4,<2", markers = "python_version < \"3.11\""}, + {version = ">=1.23.2,<2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0,<2", markers = "python_version >= \"3.12\""}, +] +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.7" + +[package.extras] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +pyarrow = ["pyarrow (>=10.0.1)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] + +[[package]] +name = "parso" +version = "0.8.3" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +files = [ {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, ] -pathspec = [ + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] -pexpect = [ + +[[package]] +name = "pexpect" +version = "4.9.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +files = [ {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, ] -pillow = [ + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pillow" +version = "10.2.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.8" +files = [ {file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"}, {file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"}, {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"}, @@ -2461,23 +1827,81 @@ pillow = [ {file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"}, {file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"}, ] -platformdirs = [ + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] + +[[package]] +name = "platformdirs" +version = "4.2.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.8" +files = [ {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, ] -plotly = [ + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] + +[[package]] +name = "plotly" +version = "5.19.0" +description = "An open-source, interactive data visualization library for Python" +optional = false +python-versions = ">=3.8" +files = [ {file = "plotly-5.19.0-py3-none-any.whl", hash = "sha256:906abcc5f15945765328c5d47edaa884bc99f5985fbc61e8cd4dc361f4ff8f5a"}, {file = "plotly-5.19.0.tar.gz", hash = "sha256:5ea91a56571292ade3e3bc9bf712eba0b95a1fb0a941375d978cc79432e055f4"}, ] -pluggy = [ + +[package.dependencies] +packaging = "*" +tenacity = ">=6.2.0" + +[[package]] +name = "pluggy" +version = "1.4.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, ] -prompt-toolkit = [ + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "prompt-toolkit" +version = "3.0.43" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.7.0" +files = [ {file = "prompt_toolkit-3.0.43-py3-none-any.whl", hash = "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"}, {file = "prompt_toolkit-3.0.43.tar.gz", hash = "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d"}, ] -psutil = [ + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "psutil" +version = "5.9.8" +description = "Cross-platform lib for process and system monitoring in Python." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"}, {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"}, {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"}, @@ -2495,85 +1919,199 @@ psutil = [ {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"}, {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"}, ] -ptyprocess = [ + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +files = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, ] -pure-eval = [ + +[[package]] +name = "pure-eval" +version = "0.2.2" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +files = [ {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, ] -pyarrow = [ - {file = "pyarrow-15.0.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:0a524532fd6dd482edaa563b686d754c70417c2f72742a8c990b322d4c03a15d"}, - {file = "pyarrow-15.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:60a6bdb314affa9c2e0d5dddf3d9cbb9ef4a8dddaa68669975287d47ece67642"}, - {file = "pyarrow-15.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66958fd1771a4d4b754cd385835e66a3ef6b12611e001d4e5edfcef5f30391e2"}, - {file = "pyarrow-15.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f500956a49aadd907eaa21d4fff75f73954605eaa41f61cb94fb008cf2e00c6"}, - {file = "pyarrow-15.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6f87d9c4f09e049c2cade559643424da84c43a35068f2a1c4653dc5b1408a929"}, - {file = "pyarrow-15.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:85239b9f93278e130d86c0e6bb455dcb66fc3fd891398b9d45ace8799a871a1e"}, - {file = "pyarrow-15.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:5b8d43e31ca16aa6e12402fcb1e14352d0d809de70edd185c7650fe80e0769e3"}, - {file = "pyarrow-15.0.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:fa7cd198280dbd0c988df525e50e35b5d16873e2cdae2aaaa6363cdb64e3eec5"}, - {file = "pyarrow-15.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8780b1a29d3c8b21ba6b191305a2a607de2e30dab399776ff0aa09131e266340"}, - {file = "pyarrow-15.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0ec198ccc680f6c92723fadcb97b74f07c45ff3fdec9dd765deb04955ccf19"}, - {file = "pyarrow-15.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:036a7209c235588c2f07477fe75c07e6caced9b7b61bb897c8d4e52c4b5f9555"}, - {file = "pyarrow-15.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2bd8a0e5296797faf9a3294e9fa2dc67aa7f10ae2207920dbebb785c77e9dbe5"}, - {file = "pyarrow-15.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:e8ebed6053dbe76883a822d4e8da36860f479d55a762bd9e70d8494aed87113e"}, - {file = "pyarrow-15.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:17d53a9d1b2b5bd7d5e4cd84d018e2a45bc9baaa68f7e6e3ebed45649900ba99"}, - {file = "pyarrow-15.0.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:9950a9c9df24090d3d558b43b97753b8f5867fb8e521f29876aa021c52fda351"}, - {file = "pyarrow-15.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:003d680b5e422d0204e7287bb3fa775b332b3fce2996aa69e9adea23f5c8f970"}, - {file = "pyarrow-15.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f75fce89dad10c95f4bf590b765e3ae98bcc5ba9f6ce75adb828a334e26a3d40"}, - {file = "pyarrow-15.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca9cb0039923bec49b4fe23803807e4ef39576a2bec59c32b11296464623dc2"}, - {file = "pyarrow-15.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:9ed5a78ed29d171d0acc26a305a4b7f83c122d54ff5270810ac23c75813585e4"}, - {file = "pyarrow-15.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6eda9e117f0402dfcd3cd6ec9bfee89ac5071c48fc83a84f3075b60efa96747f"}, - {file = "pyarrow-15.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9a3a6180c0e8f2727e6f1b1c87c72d3254cac909e609f35f22532e4115461177"}, - {file = "pyarrow-15.0.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:19a8918045993349b207de72d4576af0191beef03ea655d8bdb13762f0cd6eac"}, - {file = "pyarrow-15.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d0ec076b32bacb6666e8813a22e6e5a7ef1314c8069d4ff345efa6246bc38593"}, - {file = "pyarrow-15.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5db1769e5d0a77eb92344c7382d6543bea1164cca3704f84aa44e26c67e320fb"}, - {file = "pyarrow-15.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2617e3bf9df2a00020dd1c1c6dce5cc343d979efe10bc401c0632b0eef6ef5b"}, - {file = "pyarrow-15.0.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:d31c1d45060180131caf10f0f698e3a782db333a422038bf7fe01dace18b3a31"}, - {file = "pyarrow-15.0.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:c8c287d1d479de8269398b34282e206844abb3208224dbdd7166d580804674b7"}, - {file = "pyarrow-15.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:07eb7f07dc9ecbb8dace0f58f009d3a29ee58682fcdc91337dfeb51ea618a75b"}, - {file = "pyarrow-15.0.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:47af7036f64fce990bb8a5948c04722e4e3ea3e13b1007ef52dfe0aa8f23cf7f"}, - {file = "pyarrow-15.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93768ccfff85cf044c418bfeeafce9a8bb0cee091bd8fd19011aff91e58de540"}, - {file = "pyarrow-15.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6ee87fd6892700960d90abb7b17a72a5abb3b64ee0fe8db6c782bcc2d0dc0b4"}, - {file = "pyarrow-15.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:001fca027738c5f6be0b7a3159cc7ba16a5c52486db18160909a0831b063c4e4"}, - {file = "pyarrow-15.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:d1c48648f64aec09accf44140dccb92f4f94394b8d79976c426a5b79b11d4fa7"}, - {file = "pyarrow-15.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:972a0141be402bb18e3201448c8ae62958c9c7923dfaa3b3d4530c835ac81aed"}, - {file = "pyarrow-15.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:f01fc5cf49081426429127aa2d427d9d98e1cb94a32cb961d583a70b7c4504e6"}, - {file = "pyarrow-15.0.0.tar.gz", hash = "sha256:876858f549d540898f927eba4ef77cd549ad8d24baa3207cf1b72e5788b50e83"}, -] -pycodestyle = [ + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "pyarrow" +version = "15.0.1" +description = "Python library for Apache Arrow" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyarrow-15.0.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:c2ddb3be5ea938c329a84171694fc230b241ce1b6b0ff1a0280509af51c375fa"}, + {file = "pyarrow-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7543ea88a0ff72f8e6baaf9bfdbec2c62aeabdbede9e4a571c71cc3bc43b6302"}, + {file = "pyarrow-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1519e218a6941fc074e4501088d891afcb2adf77c236e03c34babcf3d6a0d1c7"}, + {file = "pyarrow-15.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28cafa86e1944761970d3b3fc0411b14ff9b5c2b73cd22aaf470d7a3976335f5"}, + {file = "pyarrow-15.0.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:be5c3d463e33d03eab496e1af7916b1d44001c08f0f458ad27dc16093a020638"}, + {file = "pyarrow-15.0.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:47b1eda15d3aa3f49a07b1808648e1397e5dc6a80a30bf87faa8e2d02dad7ac3"}, + {file = "pyarrow-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e524a31be7db22deebbbcf242b189063ab9a7652c62471d296b31bc6e3cae77b"}, + {file = "pyarrow-15.0.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:a476fefe8bdd56122fb0d4881b785413e025858803cc1302d0d788d3522b374d"}, + {file = "pyarrow-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:309e6191be385f2e220586bfdb643f9bb21d7e1bc6dd0a6963dc538e347b2431"}, + {file = "pyarrow-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83bc586903dbeb4365cbc72b602f99f70b96c5882e5dfac5278813c7d624ca3c"}, + {file = "pyarrow-15.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e652daac6d8b05280cd2af31c0fb61a4490ec6a53dc01588014d9fa3fdbee9"}, + {file = "pyarrow-15.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:abad2e08652df153a72177ce20c897d083b0c4ebeec051239e2654ddf4d3c996"}, + {file = "pyarrow-15.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cde663352bc83ad75ba7b3206e049ca1a69809223942362a8649e37bd22f9e3b"}, + {file = "pyarrow-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:1b6e237dd7a08482a8b8f3f6512d258d2460f182931832a8c6ef3953203d31e1"}, + {file = "pyarrow-15.0.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:7bd167536ee23192760b8c731d39b7cfd37914c27fd4582335ffd08450ff799d"}, + {file = "pyarrow-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7c08bb31eb2984ba5c3747d375bb522e7e536b8b25b149c9cb5e1c49b0ccb736"}, + {file = "pyarrow-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0f9c1d630ed2524bd1ddf28ec92780a7b599fd54704cd653519f7ff5aec177a"}, + {file = "pyarrow-15.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5186048493395220550bca7b524420471aac2d77af831f584ce132680f55c3df"}, + {file = "pyarrow-15.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:31dc30c7ec8958da3a3d9f31d6c3630429b2091ede0ecd0d989fd6bec129f0e4"}, + {file = "pyarrow-15.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3f111a014fb8ac2297b43a74bf4495cc479a332908f7ee49cb7cbd50714cb0c1"}, + {file = "pyarrow-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:a6d1f7c15d7f68f08490d0cb34611497c74285b8a6bbeab4ef3fc20117310983"}, + {file = "pyarrow-15.0.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:9ad931b996f51c2f978ed517b55cb3c6078272fb4ec579e3da5a8c14873b698d"}, + {file = "pyarrow-15.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:738f6b53ab1c2f66b2bde8a1d77e186aeaab702d849e0dfa1158c9e2c030add3"}, + {file = "pyarrow-15.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c1c3fc16bc74e33bf8f1e5a212938ed8d88e902f372c4dac6b5bad328567d2f"}, + {file = "pyarrow-15.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1fa92512128f6c1b8dde0468c1454dd70f3bff623970e370d52efd4d24fd0be"}, + {file = "pyarrow-15.0.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:b4157f307c202cbbdac147d9b07447a281fa8e63494f7fc85081da351ec6ace9"}, + {file = "pyarrow-15.0.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:b75e7da26f383787f80ad76143b44844ffa28648fcc7099a83df1538c078d2f2"}, + {file = "pyarrow-15.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:3a99eac76ae14096c209850935057b9e8ce97a78397c5cde8724674774f34e5d"}, + {file = "pyarrow-15.0.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:dd532d3177e031e9b2d2df19fd003d0cc0520d1747659fcabbd4d9bb87de508c"}, + {file = "pyarrow-15.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ce8c89848fd37e5313fc2ce601483038ee5566db96ba0808d5883b2e2e55dc53"}, + {file = "pyarrow-15.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:862eac5e5f3b6477f7a92b2f27e560e1f4e5e9edfca9ea9da8a7478bb4abd5ce"}, + {file = "pyarrow-15.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f0ea3a29cd5cb99bf14c1c4533eceaa00ea8fb580950fb5a89a5c771a994a4e"}, + {file = "pyarrow-15.0.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:bb902f780cfd624b2e8fd8501fadab17618fdb548532620ef3d91312aaf0888a"}, + {file = "pyarrow-15.0.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:4f87757f02735a6bb4ad2e1b98279ac45d53b748d5baf52401516413007c6999"}, + {file = "pyarrow-15.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:efd3816c7fbfcbd406ac0f69873cebb052effd7cdc153ae5836d1b00845845d7"}, + {file = "pyarrow-15.0.1.tar.gz", hash = "sha256:21d812548d39d490e0c6928a7c663f37b96bf764034123d4b4ab4530ecc757a9"}, +] + +[package.dependencies] +numpy = ">=1.16.6,<2" + +[[package]] +name = "pycodestyle" +version = "2.11.1" +description = "Python style guide checker" +optional = false +python-versions = ">=3.8" +files = [ {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, ] -pycparser = [ + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] -pyflakes = [ + +[[package]] +name = "pyflakes" +version = "3.2.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.8" +files = [ {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, ] -Pygments = [ + +[[package]] +name = "pygments" +version = "2.17.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, ] -pyparsing = [ + +[package.extras] +plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pyparsing" +version = "3.1.2" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.6.8" +files = [ {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, ] -pytest = [ - {file = "pytest-8.0.2-py3-none-any.whl", hash = "sha256:edfaaef32ce5172d5466b5127b42e0d6d35ebbe4453f0e3505d96afd93f6b096"}, - {file = "pytest-8.0.2.tar.gz", hash = "sha256:d4051d623a2e0b7e51960ba963193b09ce6daeb9759a451844a21e4ddedfc1bd"}, + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pytest" +version = "8.1.1" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"}, + {file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"}, ] -python-dateutil = [ + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.4,<2.0" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, ] -pytz = [ + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2024.1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, ] -pywin32 = [ + +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, @@ -2589,7 +2127,14 @@ pywin32 = [ {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, ] -PyYAML = [ + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, @@ -2642,7 +2187,14 @@ PyYAML = [ {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] -pyzmq = [ + +[[package]] +name = "pyzmq" +version = "25.1.2" +description = "Python bindings for 0MQ" +optional = false +python-versions = ">=3.6" +files = [ {file = "pyzmq-25.1.2-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:e624c789359f1a16f83f35e2c705d07663ff2b4d4479bad35621178d8f0f6ea4"}, {file = "pyzmq-25.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:49151b0efece79f6a79d41a461d78535356136ee70084a1c22532fc6383f4ad0"}, {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9a5f194cf730f2b24d6af1f833c14c10f41023da46a7f736f48b6d35061e76e"}, @@ -2737,15 +2289,52 @@ pyzmq = [ {file = "pyzmq-25.1.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:146b9b1f29ead41255387fb07be56dc29639262c0f7344f570eecdcd8d683314"}, {file = "pyzmq-25.1.2.tar.gz", hash = "sha256:93f1aa311e8bb912e34f004cf186407a4e90eec4f0ecc0efd26056bf7eda0226"}, ] -requests = [ + +[package.dependencies] +cffi = {version = "*", markers = "implementation_name == \"pypy\""} + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, ] -retrying = [ + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "retrying" +version = "1.3.4" +description = "Retrying" +optional = false +python-versions = "*" +files = [ {file = "retrying-1.3.4-py3-none-any.whl", hash = "sha256:8cc4d43cb8e1125e0ff3344e9de678fefd85db3b750b81b2240dc0183af37b35"}, {file = "retrying-1.3.4.tar.gz", hash = "sha256:345da8c5765bd982b1d1915deb9102fd3d1f7ad16bd84a9700b85f64d24e8f3e"}, ] -scikit-learn = [ + +[package.dependencies] +six = ">=1.7.0" + +[[package]] +name = "scikit-learn" +version = "1.4.1.post1" +description = "A set of python modules for machine learning and data mining" +optional = false +python-versions = ">=3.9" +files = [ {file = "scikit-learn-1.4.1.post1.tar.gz", hash = "sha256:93d3d496ff1965470f9977d05e5ec3376fb1e63b10e4fda5e39d23c2d8969a30"}, {file = "scikit_learn-1.4.1.post1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c540aaf44729ab5cd4bd5e394f2b375e65ceaea9cdd8c195788e70433d91bbc5"}, {file = "scikit_learn-1.4.1.post1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:4310bff71aa98b45b46cd26fa641309deb73a5d1c0461d181587ad4f30ea3c36"}, @@ -2768,7 +2357,26 @@ scikit-learn = [ {file = "scikit_learn-1.4.1.post1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a3ee19211ded1a52ee37b0a7b373a8bfc66f95353af058a210b692bd4cda0dd"}, {file = "scikit_learn-1.4.1.post1-cp39-cp39-win_amd64.whl", hash = "sha256:234b6bda70fdcae9e4abbbe028582ce99c280458665a155eed0b820599377d25"}, ] -scipy = [ + +[package.dependencies] +joblib = ">=1.2.0" +numpy = ">=1.19.5,<2.0" +scipy = ">=1.6.0" +threadpoolctl = ">=2.0.0" + +[package.extras] +benchmark = ["matplotlib (>=3.3.4)", "memory-profiler (>=0.57.0)", "pandas (>=1.1.5)"] +docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.3.4)", "memory-profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)", "sphinx (>=6.0.0)", "sphinx-copybutton (>=0.5.2)", "sphinx-gallery (>=0.15.0)", "sphinx-prompt (>=1.3.0)", "sphinxext-opengraph (>=0.4.2)"] +examples = ["matplotlib (>=3.3.4)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)"] +tests = ["black (>=23.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.3)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "polars (>=0.19.12)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.0.272)", "scikit-image (>=0.17.2)"] + +[[package]] +name = "scipy" +version = "1.12.0" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = ">=3.9" +files = [ {file = "scipy-1.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:78e4402e140879387187f7f25d91cc592b3501a2e51dfb320f48dfb73565f10b"}, {file = "scipy-1.12.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f5f00ebaf8de24d14b8449981a2842d404152774c1a1d880c901bf454cb8e2a1"}, {file = "scipy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e53958531a7c695ff66c2e7bb7b79560ffdc562e2051644c5576c39ff8efb563"}, @@ -2795,48 +2403,118 @@ scipy = [ {file = "scipy-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:b360f1b6b2f742781299514e99ff560d1fe9bd1bff2712894b52abe528d1fd1e"}, {file = "scipy-1.12.0.tar.gz", hash = "sha256:4bf5abab8a36d20193c698b0f1fc282c1d083c94723902c447e5d2f1780936a3"}, ] -setuptools = [ + +[package.dependencies] +numpy = ">=1.22.4,<1.29.0" + +[package.extras] +dev = ["click", "cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] +doc = ["jupytext", "matplotlib (>2)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] +test = ["asv", "gmpy2", "hypothesis", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + +[[package]] +name = "setuptools" +version = "69.1.1" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ {file = "setuptools-69.1.1-py3-none-any.whl", hash = "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56"}, {file = "setuptools-69.1.1.tar.gz", hash = "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"}, ] -shap = [ - {file = "shap-0.44.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:93a94961a355249855f13f1ed564466afa1c5fae84f868dd56e50e936f4f9b57"}, - {file = "shap-0.44.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d2919f2b255e31363182afb1627b374eb6c4724c90b0318719cbe90a316682f5"}, - {file = "shap-0.44.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ad3192026a0e4c3e399ef499da4f726ab01d62f388e03c804d8e10c4e61c8d1"}, - {file = "shap-0.44.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4ba4f3e84c3adf2c5a55e6d2becb7a4bb83e5784f7aa373316099a105ad593f"}, - {file = "shap-0.44.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1f19a3c531c6ceb0d7adea8262385580b52ef187981606342cd03288fc48cbfa"}, - {file = "shap-0.44.1-cp310-cp310-win_amd64.whl", hash = "sha256:d65a21a64f3c1e76e1fb5277a0cfbcce447c08a26eaef311b1cbae9a6efe0ea0"}, - {file = "shap-0.44.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:44ed7a80ae30ed3927f1bc7911f6f04bb32bb2d938a9e8a5794b2bb99e3b99cc"}, - {file = "shap-0.44.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e98bb13dc351317ad2420dfe2dbca5361237ab9bef511fbe28ce6c1dfb40ff8c"}, - {file = "shap-0.44.1-cp311-cp311-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fe63f1f4acf6bdfa3c85db85067d0fda2a29fe38be2d14a2811844f35290f43"}, - {file = "shap-0.44.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fa67a8810a042aa2366224afa612ded8a2a77b5a5a0d89ea60eba5eff3a0c1b"}, - {file = "shap-0.44.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dda26c39ac0ef17233deba09b0f313ceb4d5fa663dced2a1b77a494d48db96b8"}, - {file = "shap-0.44.1-cp311-cp311-win_amd64.whl", hash = "sha256:e1c3de8747fb451e59768dca11b054e395a2a1601c7482738897616a70679419"}, - {file = "shap-0.44.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c82efde41d2b2c65d707b678a0d057d77436faf72331a623545aa742ffe5b9f5"}, - {file = "shap-0.44.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:89f4492b406ec9908750560281c4616aff2a28c61f3ad2f6ac511270f44a0c5a"}, - {file = "shap-0.44.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:497bde2083b2a27e0a4b8ded09927c11c8803e80e76cb2e87e2136c49f1c93b2"}, - {file = "shap-0.44.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a466d8a9bb12e7e07f13e68ea2859e4deb1dc6d3953c3a88310bd77484943cd6"}, - {file = "shap-0.44.1-cp38-cp38-win_amd64.whl", hash = "sha256:6aede800f0ec9efa8acd35913d791304d46376d444e8c6e1be905606e626c5e2"}, - {file = "shap-0.44.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ab36e2aecd0c1ba3df58f2452aa4aa24832ec766cbb2a794e2411897eb728b30"}, - {file = "shap-0.44.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:668ebe97c60a12bdffe254dfb2c8598cf24416e202b4b662076846b12d10b4b7"}, - {file = "shap-0.44.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:947c7e25b96d37763948ef2edd1711c6b3098a161de8e97fa0d1fdad6e49848c"}, - {file = "shap-0.44.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62bb07b4748db004c78802494c29e5e70a2efff967a5c4d50209d2ee2ace16db"}, - {file = "shap-0.44.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:febd7f0ad6ac27f999ed857a897f0f9963d4a4d9eeb003ee2505cf6164590d9c"}, - {file = "shap-0.44.1-cp39-cp39-win_amd64.whl", hash = "sha256:3c873d26e4fdcdb9e6a83a1e05c1e9c8c8b48ac4365b0259d6e138afb3729c32"}, - {file = "shap-0.44.1.tar.gz", hash = "sha256:a21d5a622e12e7c3b4a58d6e93b70133b7e09b6342b19746071a0dc2d190b432"}, -] -six = [ + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "shap" +version = "0.45.0" +description = "A unified approach to explain the output of any machine learning model." +optional = false +python-versions = ">=3.9" +files = [ + {file = "shap-0.45.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5dab790083aadfdfebd7a74a8aae150b0049f5366f685cb0e9ac2e2c273d874f"}, + {file = "shap-0.45.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16a7e129e0708c4f6d288ca03974c317c9921869d577a2cba1e74ec644a78085"}, + {file = "shap-0.45.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea46a7798b2f9069bb38627011b321242ff722dd55c8689a063fcc424ab22ce6"}, + {file = "shap-0.45.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fb3ef4abf26f6510f6626c4690bfb00370622fc2d8ced47b9f385c9be64ef34"}, + {file = "shap-0.45.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eab61cc59da2dc3b874478166f392333de8d72f5289b5d050f5f65c859743616"}, + {file = "shap-0.45.0-cp310-cp310-win_amd64.whl", hash = "sha256:da173b30b23e967935815a5f45f7bf0dd3ba69738c1e9f91c15bf2d3dc721ff3"}, + {file = "shap-0.45.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:45f1bc767c717c78cc69dd177ab7e3c611bbaebbe39ae7ca55101c190b7cf66c"}, + {file = "shap-0.45.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c8c4dc44f9ae3d7358f82d9bbca6b1e85096b52a620e497e092bf79570aa86d1"}, + {file = "shap-0.45.0-cp311-cp311-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a513857dbe125856a0afefa8dd548403a9fe8808f680549ae93fa05dd55438b5"}, + {file = "shap-0.45.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64022a12a80c6f7e26db6cda11d77a7f376b4ae981a9ef27f09403996025429c"}, + {file = "shap-0.45.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d08703eeef912a483eae314cc533250499780d756ccfa17771bd340257c7f47b"}, + {file = "shap-0.45.0-cp311-cp311-win_amd64.whl", hash = "sha256:7bb7867e833c7b5735a9615de9a1e1f9d6c4cb9b05936b5bd7fe4eb592d705ea"}, + {file = "shap-0.45.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ee81bd44cd0de92b46e92175e40d8b91243278f6d7c9725dbca5a567ffda6e13"}, + {file = "shap-0.45.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:117f6f6f224b6e61925a6eec2cc196170c879d092a91ff817d91a0bfe4e85bf4"}, + {file = "shap-0.45.0-cp312-cp312-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdea57f9038d502aa72e6bacbcd3380e807960769796c4a273cbe23d132696de"}, + {file = "shap-0.45.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4c824152f41885f2b92d211afbabf990415b97bbca7285c7d96c086b22c53c7"}, + {file = "shap-0.45.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7fd0bc2a2215383af72e05061b4c4ff91a0ef0c55d272ae78ac77a8356d522f9"}, + {file = "shap-0.45.0-cp312-cp312-win_amd64.whl", hash = "sha256:9b37f22158e01fb6d8a6d4616ad759f11ea0524e2de0d6cc7d66177e23fe9e9f"}, + {file = "shap-0.45.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ee2a9188f08b643c789099a1ba9326691d10df8fff8feea1bde83142b9d5c98e"}, + {file = "shap-0.45.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2747a2e650572567f94361adfa1fad77c857f0203a923968a80257ee1fe095d6"}, + {file = "shap-0.45.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:751836bb1eae9e73be5cb59218cf8ac362097a1d1f39afdd098cfb35abe9121d"}, + {file = "shap-0.45.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e658f09e21d5d646483338ad497a10e8defc6f67986a223244ca8809f874a9e"}, + {file = "shap-0.45.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ca41a72773e5d155995b41abd95bbfff30922f3d85544ed0a4fc1df8079c7cc"}, + {file = "shap-0.45.0-cp39-cp39-win_amd64.whl", hash = "sha256:de151d7a27de90a20b31c389c17f1c8d591b8fc5b70837eaeb22a4fefef9c556"}, + {file = "shap-0.45.0.tar.gz", hash = "sha256:be8ffc213ba3da1ce7950784571356e6ca77bee94446ddfa9c63eb2c72f7ce9e"}, +] + +[package.dependencies] +cloudpickle = "*" +numba = "*" +numpy = "*" +packaging = ">20.9" +pandas = "*" +scikit-learn = "*" +scipy = "*" +slicer = "0.0.7" +tqdm = ">=4.27.0" + +[package.extras] +docs = ["ipython", "matplotlib", "myst-parser (==2.0.0)", "nbsphinx (==0.9.3)", "numpydoc", "requests", "sphinx (==7.2.6)", "sphinx-github-changelog (==1.2.1)", "sphinx-rtd-theme (==2.0.0)"] +others = ["lime"] +plots = ["ipython", "matplotlib"] +test = ["catboost", "gpboost", "lightgbm", "ngboost", "opencv-python", "protobuf (==3.20.3)", "pyod", "pyspark", "pytest", "pytest-cov", "pytest-mpl", "sentencepiece", "tensorflow", "torch", "torch (==2.2.0)", "torchvision", "transformers", "xgboost"] +test-core = ["pytest", "pytest-cov", "pytest-mpl"] +test-notebooks = ["datasets", "jupyter", "keras", "nbconvert", "nbformat", "nlp", "transformers"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -slicer = [ + +[[package]] +name = "slicer" +version = "0.0.7" +description = "A small package for big slicing." +optional = false +python-versions = ">=3.6" +files = [ {file = "slicer-0.0.7-py3-none-any.whl", hash = "sha256:0b94faa5251c0f23782c03f7b7eedda91d80144059645f452c4bc80fab875976"}, {file = "slicer-0.0.7.tar.gz", hash = "sha256:f5d5f7b45f98d155b9c0ba6554fa9770c6b26d5793a3e77a1030fb56910ebeec"}, ] -SQLAlchemy = [ + +[[package]] +name = "sqlalchemy" +version = "2.0.28" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0b148ab0438f72ad21cb004ce3bdaafd28465c4276af66df3b9ecd2037bf252"}, {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bbda76961eb8f27e6ad3c84d1dc56d5bc61ba8f02bd20fcf3450bd421c2fcc9c"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feea693c452d85ea0015ebe3bb9cd15b6f49acc1a31c28b3c50f4db0f8fb1e71"}, {file = "SQLAlchemy-2.0.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5da98815f82dce0cb31fd1e873a0cb30934971d15b74e0d78cf21f9e1b05953f"}, + {file = "SQLAlchemy-2.0.28-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a5adf383c73f2d49ad15ff363a8748319ff84c371eed59ffd0127355d6ea1da"}, {file = "SQLAlchemy-2.0.28-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56856b871146bfead25fbcaed098269d90b744eea5cb32a952df00d542cdd368"}, {file = "SQLAlchemy-2.0.28-cp310-cp310-win32.whl", hash = "sha256:943aa74a11f5806ab68278284a4ddd282d3fb348a0e96db9b42cb81bf731acdc"}, {file = "SQLAlchemy-2.0.28-cp310-cp310-win_amd64.whl", hash = "sha256:c6c4da4843e0dabde41b8f2e8147438330924114f541949e6318358a56d1875a"}, @@ -2857,42 +2535,123 @@ SQLAlchemy = [ {file = "SQLAlchemy-2.0.28-cp312-cp312-win32.whl", hash = "sha256:a921002be69ac3ab2cf0c3017c4e6a3377f800f1fca7f254c13b5f1a2f10022c"}, {file = "SQLAlchemy-2.0.28-cp312-cp312-win_amd64.whl", hash = "sha256:b4a2cf92995635b64876dc141af0ef089c6eea7e05898d8d8865e71a326c0385"}, {file = "SQLAlchemy-2.0.28-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e91b5e341f8c7f1e5020db8e5602f3ed045a29f8e27f7f565e0bdee3338f2c7"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45c7b78dfc7278329f27be02c44abc0d69fe235495bb8e16ec7ef1b1a17952db"}, {file = "SQLAlchemy-2.0.28-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3eba73ef2c30695cb7eabcdb33bb3d0b878595737479e152468f3ba97a9c22a4"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5df5d1dafb8eee89384fb7a1f79128118bc0ba50ce0db27a40750f6f91aa99d5"}, {file = "SQLAlchemy-2.0.28-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2858bbab1681ee5406650202950dc8f00e83b06a198741b7c656e63818633526"}, {file = "SQLAlchemy-2.0.28-cp37-cp37m-win32.whl", hash = "sha256:9461802f2e965de5cff80c5a13bc945abea7edaa1d29360b485c3d2b56cdb075"}, {file = "SQLAlchemy-2.0.28-cp37-cp37m-win_amd64.whl", hash = "sha256:a6bec1c010a6d65b3ed88c863d56b9ea5eeefdf62b5e39cafd08c65f5ce5198b"}, {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:843a882cadebecc655a68bd9a5b8aa39b3c52f4a9a5572a3036fb1bb2ccdc197"}, {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dbb990612c36163c6072723523d2be7c3eb1517bbdd63fe50449f56afafd1133"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7e4baf9161d076b9a7e432fce06217b9bd90cfb8f1d543d6e8c4595627edb9"}, {file = "SQLAlchemy-2.0.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0a5354cb4de9b64bccb6ea33162cb83e03dbefa0d892db88a672f5aad638a75"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fffcc8edc508801ed2e6a4e7b0d150a62196fd28b4e16ab9f65192e8186102b6"}, {file = "SQLAlchemy-2.0.28-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aca7b6d99a4541b2ebab4494f6c8c2f947e0df4ac859ced575238e1d6ca5716b"}, {file = "SQLAlchemy-2.0.28-cp38-cp38-win32.whl", hash = "sha256:8c7f10720fc34d14abad5b647bc8202202f4948498927d9f1b4df0fb1cf391b7"}, {file = "SQLAlchemy-2.0.28-cp38-cp38-win_amd64.whl", hash = "sha256:243feb6882b06a2af68ecf4bec8813d99452a1b62ba2be917ce6283852cf701b"}, {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc4974d3684f28b61b9a90fcb4c41fb340fd4b6a50c04365704a4da5a9603b05"}, {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87724e7ed2a936fdda2c05dbd99d395c91ea3c96f029a033a4a20e008dd876bf"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68722e6a550f5de2e3cfe9da6afb9a7dd15ef7032afa5651b0f0c6b3adb8815d"}, {file = "SQLAlchemy-2.0.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:328529f7c7f90adcd65aed06a161851f83f475c2f664a898af574893f55d9e53"}, + {file = "SQLAlchemy-2.0.28-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:df40c16a7e8be7413b885c9bf900d402918cc848be08a59b022478804ea076b8"}, {file = "SQLAlchemy-2.0.28-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:426f2fa71331a64f5132369ede5171c52fd1df1bd9727ce621f38b5b24f48750"}, {file = "SQLAlchemy-2.0.28-cp39-cp39-win32.whl", hash = "sha256:33157920b233bc542ce497a81a2e1452e685a11834c5763933b440fedd1d8e2d"}, {file = "SQLAlchemy-2.0.28-cp39-cp39-win_amd64.whl", hash = "sha256:2f60843068e432311c886c5f03c4664acaef507cf716f6c60d5fde7265be9d7b"}, {file = "SQLAlchemy-2.0.28-py3-none-any.whl", hash = "sha256:78bb7e8da0183a8301352d569900d9d3594c48ac21dc1c2ec6b3121ed8b6c986"}, {file = "SQLAlchemy-2.0.28.tar.gz", hash = "sha256:dd53b6c4e6d960600fd6532b79ee28e2da489322fcf6648738134587faf767b6"}, ] -stack-data = [ + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} +typing-extensions = ">=4.6.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3_binary"] + +[[package]] +name = "stack-data" +version = "0.6.3" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +files = [ {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, ] -tenacity = [ + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + +[[package]] +name = "tenacity" +version = "8.2.3" +description = "Retry code until it succeeds" +optional = false +python-versions = ">=3.7" +files = [ {file = "tenacity-8.2.3-py3-none-any.whl", hash = "sha256:ce510e327a630c9e1beaf17d42e6ffacc88185044ad85cf74c0a8887c6a0f88c"}, {file = "tenacity-8.2.3.tar.gz", hash = "sha256:5398ef0d78e63f40007c1fb4c0bff96e1911394d2fa8d194f77619c05ff6cc8a"}, ] -threadpoolctl = [ + +[package.extras] +doc = ["reno", "sphinx", "tornado (>=4.5)"] + +[[package]] +name = "threadpoolctl" +version = "3.3.0" +description = "threadpoolctl" +optional = false +python-versions = ">=3.8" +files = [ {file = "threadpoolctl-3.3.0-py3-none-any.whl", hash = "sha256:6155be1f4a39f31a18ea70f94a77e0ccd57dced08122ea61109e7da89883781e"}, {file = "threadpoolctl-3.3.0.tar.gz", hash = "sha256:5dac632b4fa2d43f42130267929af3ba01399ef4bd1882918e92dbc30365d30c"}, ] -tomli = [ + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -tornado = [ + +[[package]] +name = "tornado" +version = "6.4" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">= 3.8" +files = [ {file = "tornado-6.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:02ccefc7d8211e5a7f9e8bc3f9e5b0ad6262ba2fbb683a6443ecc804e5224ce0"}, {file = "tornado-6.4-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:27787de946a9cffd63ce5814c33f734c627a87072ec7eed71f7fc4417bb16263"}, {file = "tornado-6.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7894c581ecdcf91666a0912f18ce5e757213999e183ebfc2c3fdbf4d5bd764e"}, @@ -2905,43 +2664,148 @@ tornado = [ {file = "tornado-6.4-cp38-abi3-win_amd64.whl", hash = "sha256:10aeaa8006333433da48dec9fe417877f8bcc21f48dda8d661ae79da357b2a63"}, {file = "tornado-6.4.tar.gz", hash = "sha256:72291fa6e6bc84e626589f1c29d90a5a6d593ef5ae68052ee2ef000dfd273dee"}, ] -tqdm = [ + +[[package]] +name = "tqdm" +version = "4.66.2" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +files = [ {file = "tqdm-4.66.2-py3-none-any.whl", hash = "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9"}, {file = "tqdm-4.66.2.tar.gz", hash = "sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531"}, ] -traitlets = [ - {file = "traitlets-5.14.1-py3-none-any.whl", hash = "sha256:2e5a030e6eff91737c643231bfcf04a65b0132078dad75e4936700b213652e74"}, - {file = "traitlets-5.14.1.tar.gz", hash = "sha256:8585105b371a04b8316a43d5ce29c098575c2e477850b62b848b964f1444527e"}, + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + +[[package]] +name = "traitlets" +version = "5.14.2" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.8" +files = [ + {file = "traitlets-5.14.2-py3-none-any.whl", hash = "sha256:fcdf85684a772ddeba87db2f398ce00b40ff550d1528c03c14dbf6a02003cd80"}, + {file = "traitlets-5.14.2.tar.gz", hash = "sha256:8cdd83c040dab7d1dee822678e5f5d100b514f7b72b01615b26fc5718916fdf9"}, ] -typing-extensions = [ + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.1)", "pytest-mock", "pytest-mypy-testing"] + +[[package]] +name = "typing-extensions" +version = "4.10.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, ] -tzdata = [ + +[[package]] +name = "tzdata" +version = "2024.1" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, ] -urllib3 = [ + +[[package]] +name = "urllib3" +version = "2.2.1" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, ] -waitress = [ + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "waitress" +version = "3.0.0" +description = "Waitress WSGI server" +optional = false +python-versions = ">=3.8.0" +files = [ {file = "waitress-3.0.0-py3-none-any.whl", hash = "sha256:2a06f242f4ba0cc563444ca3d1998959447477363a2d7e9b8b4d75d35cfd1669"}, {file = "waitress-3.0.0.tar.gz", hash = "sha256:005da479b04134cdd9dd602d1ee7c49d79de0537610d653674cc6cbde222b8a1"}, ] -wcwidth = [ + +[package.extras] +docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.9)"] +testing = ["coverage (>=5.0)", "pytest", "pytest-cov"] + +[[package]] +name = "wcwidth" +version = "0.2.13" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, ] -Werkzeug = [ + +[[package]] +name = "werkzeug" +version = "3.0.1" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.8" +files = [ {file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"}, {file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"}, ] -WTForms = [ + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog (>=2.3)"] + +[[package]] +name = "wtforms" +version = "3.1.2" +description = "Form validation and rendering for Python web development." +optional = false +python-versions = ">=3.8" +files = [ {file = "wtforms-3.1.2-py3-none-any.whl", hash = "sha256:bf831c042829c8cdbad74c27575098d541d039b1faa74c771545ecac916f2c07"}, {file = "wtforms-3.1.2.tar.gz", hash = "sha256:f8d76180d7239c94c6322f7990ae1216dae3659b7aa1cee94b6318bdffb474b9"}, ] -xgboost = [ + +[package.dependencies] +markupsafe = "*" + +[package.extras] +email = ["email-validator"] + +[[package]] +name = "xgboost" +version = "2.0.3" +description = "XGBoost Python Package" +optional = true +python-versions = ">=3.8" +files = [ {file = "xgboost-2.0.3-py3-none-macosx_10_15_x86_64.macosx_11_0_x86_64.macosx_12_0_x86_64.whl", hash = "sha256:b21b2bb188b162c615fce468db93e3f995f3690e6184aadc7743b58466dc7f13"}, {file = "xgboost-2.0.3-py3-none-macosx_12_0_arm64.whl", hash = "sha256:722d5b9351dfdf61973490dfd28abd42844db1cc469d07ed9b0cde9d1ffcdb32"}, {file = "xgboost-2.0.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:2315a57b1883221e2f78dd514559aa9797e6c272d995d22e45495a04adac93cc"}, @@ -2949,7 +2813,39 @@ xgboost = [ {file = "xgboost-2.0.3-py3-none-win_amd64.whl", hash = "sha256:462f131d7bfb1bc42f67c57fa5aa3e57d2b5755b1573a6e0d2c7e8895164e0fc"}, {file = "xgboost-2.0.3.tar.gz", hash = "sha256:505955b5d770f8217a049beecce79e04a93787371c06dfb4b2414fec9d496bf3"}, ] -zipp = [ + +[package.dependencies] +numpy = "*" +scipy = "*" + +[package.extras] +dask = ["dask", "distributed", "pandas"] +datatable = ["datatable"] +pandas = ["pandas"] +plotting = ["graphviz", "matplotlib"] +pyspark = ["cloudpickle", "pyspark", "scikit-learn"] +scikit-learn = ["scikit-learn"] + +[[package]] +name = "zipp" +version = "3.17.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, ] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] + +[extras] +all-models = ["xgboost"] +xgboost = ["xgboost"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.9" +content-hash = "1569b3419d8a6c81d4ac0603e9d20e605f68f339527079764611a2454dee0d5b" From b85720fdd53f9bec87b916e3a1ef75a06827c4fa Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Mon, 18 Mar 2024 14:11:51 -0300 Subject: [PATCH 19/33] add calculate features and improve clean step --- pipeline_lib/core/data_container.py | 1 + pipeline_lib/core/steps/calculate_features.py | 53 ++++++++++++++++++- pipeline_lib/core/steps/clean.py | 46 ++++++++++++++++ pipeline_lib/core/steps/tabular_split.py | 6 ++- 4 files changed, 103 insertions(+), 3 deletions(-) diff --git a/pipeline_lib/core/data_container.py b/pipeline_lib/core/data_container.py index 8fe73c5..1f50718 100644 --- a/pipeline_lib/core/data_container.py +++ b/pipeline_lib/core/data_container.py @@ -36,6 +36,7 @@ class DataContainer: TARGET = "target" IMPORTANCE = "importance" DROP_COLUMNS = "drop_columns" + FEATURES = "features" def __init__(self, initial_data: Optional[dict] = None): """ diff --git a/pipeline_lib/core/steps/calculate_features.py b/pipeline_lib/core/steps/calculate_features.py index 0316cb1..2c52397 100644 --- a/pipeline_lib/core/steps/calculate_features.py +++ b/pipeline_lib/core/steps/calculate_features.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import List, Optional from pipeline_lib.core import DataContainer from pipeline_lib.core.steps.base import PipelineStep @@ -7,12 +7,61 @@ class CalculateFeaturesStep(PipelineStep): """Calculate features.""" - def __init__(self, config: Optional[dict] = None) -> None: + def __init__( + self, + datetime_columns: Optional[List[str]] = None, + features: Optional[List[str]] = None, + config: Optional[dict] = None, + ) -> None: """Initialize CalculateFeaturesStep.""" super().__init__(config=config) self.init_logger() + self.datetime_columns = datetime_columns + self.features = features def execute(self, data: DataContainer) -> DataContainer: """Execute the step.""" self.logger.info("Calculating features") + + df = data[DataContainer.CLEAN] + + if self.datetime_columns: + for column in self.datetime_columns: + if column in df.columns: + if self.features: + for feature in self.features: + if feature == "year": + df.loc[:, f"{column}_year"] = df[column].dt.year + elif feature == "month": + df.loc[:, f"{column}_month"] = df[column].dt.month + elif feature == "day": + df.loc[:, f"{column}_day"] = df[column].dt.day + elif feature == "hour": + df.loc[:, f"{column}_hour"] = df[column].dt.hour + elif feature == "minute": + df.loc[:, f"{column}_minute"] = df[column].dt.minute + elif feature == "second": + df.loc[:, f"{column}_second"] = df[column].dt.second + elif feature == "weekday": + df.loc[:, f"{column}_weekday"] = df[column].dt.weekday + elif feature == "dayofyear": + df.loc[:, f"{column}_dayofyear"] = df[column].dt.dayofyear + else: + self.logger.warning(f"Unsupported datetime feature: {feature}") + else: + self.logger.warning( + "No datetime features specified. Skipping feature extraction." + ) + else: + self.logger.warning(f"Datetime column '{column}' not found in the DataFrame") + else: + self.logger.warning("No datetime columns specified. Skipping feature extraction.") + + # drop original datetime columns + if self.datetime_columns: + df = df.drop(columns=self.datetime_columns) + self.logger.info(f"Dropped datetime columns: {self.datetime_columns}") + + data[DataContainer.FEATURES] = df + return data diff --git a/pipeline_lib/core/steps/clean.py b/pipeline_lib/core/steps/clean.py index 0341f5a..a52ee14 100644 --- a/pipeline_lib/core/steps/clean.py +++ b/pipeline_lib/core/steps/clean.py @@ -10,11 +10,16 @@ def __init__( fill_missing: Optional[dict] = None, remove_outliers: Optional[dict] = None, convert_dtypes: Optional[dict] = None, + drop_na_columns: Optional[list] = None, + drop_ids: Optional[dict] = None, + ): self.init_logger() self.fill_missing = fill_missing self.remove_outliers = remove_outliers self.convert_dtypes = convert_dtypes + self.drop_na_columns = drop_na_columns + self.drop_ids = drop_ids def execute(self, data: DataContainer) -> DataContainer: self.logger.info("Cleaning tabular data...") @@ -64,6 +69,47 @@ def execute(self, data: DataContainer) -> DataContainer: else: self.logger.warning(f"Column '{column}' not found in the DataFrame") + if self.drop_na_columns: + for column in self.drop_na_columns: + if column in df.columns: + initial_rows = len(df) + df.dropna(subset=[column], inplace=True) + dropped_rows = initial_rows - len(df) + self.logger.info( + f"Dropped {dropped_rows} rows with None values in column '{column}'" + ) + else: + self.logger.warning(f"Column '{column}' not found in the DataFrame") + + if self.drop_ids: + for column, ids in self.drop_ids.items(): + if column in df.columns: + initial_rows = len(df) + initial_ids = set(df[column].unique()) + + dropped_ids = set(ids) & initial_ids + not_found_ids = set(ids) - initial_ids + + if dropped_ids: + df = df.loc[~df[column].isin(dropped_ids)].copy() + dropped_rows = initial_rows - len(df) + percentage_dropped = (dropped_rows / initial_rows) * 100 # Calculate the percentage of rows dropped + self.logger.info( + f"Dropped {dropped_rows} rows ({percentage_dropped:.2f}%) with IDs {list(dropped_ids)} in column '{column}'" + ) + else: + self.logger.info( + f"No rows dropped for IDs {list(ids)} in column '{column}'" + ) + + if not_found_ids: + self.logger.warning( + f"IDs {list(not_found_ids)} not found in column '{column}'" + ) + else: + self.logger.warning(f"Column '{column}' not found in the DataFrame") + + data[DataContainer.CLEAN] = df return data diff --git a/pipeline_lib/core/steps/tabular_split.py b/pipeline_lib/core/steps/tabular_split.py index ae5673b..68b78ce 100644 --- a/pipeline_lib/core/steps/tabular_split.py +++ b/pipeline_lib/core/steps/tabular_split.py @@ -19,7 +19,11 @@ def execute(self, data: DataContainer) -> DataContainer: """Execute the random train-validation split.""" self.logger.info("Splitting tabular data...") - df = data[DataContainer.CLEAN] + df = ( + data[DataContainer.FEATURES] + if DataContainer.FEATURES in data + else data[DataContainer.CLEAN] + ) train_df, validation_df = train_test_split( df, train_size=self.train_percentage, random_state=42 From 3b3db935b4f9054d9a40406788645e258d634c1f Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Mon, 18 Mar 2024 20:46:48 -0300 Subject: [PATCH 20/33] improve efficiency and error handling in calculate features step --- pipeline_lib/core/steps/calculate_features.py | 75 ++++++++++++++----- pipeline_lib/core/steps/clean.py | 9 ++- 2 files changed, 60 insertions(+), 24 deletions(-) diff --git a/pipeline_lib/core/steps/calculate_features.py b/pipeline_lib/core/steps/calculate_features.py index 2c52397..d6e0227 100644 --- a/pipeline_lib/core/steps/calculate_features.py +++ b/pipeline_lib/core/steps/calculate_features.py @@ -1,9 +1,18 @@ from typing import List, Optional +import pandas as pd +from pandas.api.types import is_datetime64_any_dtype + from pipeline_lib.core import DataContainer from pipeline_lib.core.steps.base import PipelineStep +class UnsupportedFeatureError(Exception): + """Custom exception for unsupported features.""" + + pass + + class CalculateFeaturesStep(PipelineStep): """Calculate features.""" @@ -11,43 +20,67 @@ def __init__( self, datetime_columns: Optional[List[str]] = None, features: Optional[List[str]] = None, - config: Optional[dict] = None, ) -> None: """Initialize CalculateFeaturesStep.""" - super().__init__(config=config) + super().__init__() self.init_logger() self.datetime_columns = datetime_columns self.features = features + if self.datetime_columns and not isinstance(self.datetime_columns, list): + self.datetime_columns = [self.datetime_columns] + + self.feature_extractors = { + "year": lambda col: col.dt.year, + "month": lambda col: col.dt.month, + "day": lambda col: col.dt.day, + "hour": lambda col: col.dt.hour, + "minute": lambda col: col.dt.minute, + "second": lambda col: col.dt.second, + "weekday": lambda col: col.dt.weekday, + "dayofyear": lambda col: col.dt.dayofyear, + } + + # Validate features during initialization + if self.features: + unsupported_features = set(self.features) - set(self.feature_extractors.keys()) + if unsupported_features: + raise UnsupportedFeatureError( + f"Unsupported datetime features: {unsupported_features}" + ) + + def _convert_column_to_datetime(self, df: pd.DataFrame, column: str) -> None: + """Convert a column to datetime.""" + # Check if the column is already a datetime type + if not is_datetime64_any_dtype(df[column]): + try: + df.loc[:, column] = pd.to_datetime(df[column], errors="raise") + self.logger.info(f"Column '{column}' converted to datetime.") + except Exception as e: + self.logger.error(f"Error converting column '{column}' to datetime: {e}") + else: + self.logger.debug(f"Column '{column}' is already a datetime type.") + + def _extract_feature(self, df: pd.DataFrame, column: str, feature: str) -> None: + """Extract a single feature from a datetime column.""" + extractor = self.feature_extractors[feature] + df.loc[:, f"{column}_{feature}"] = extractor(df[column]) + def execute(self, data: DataContainer) -> DataContainer: """Execute the step.""" self.logger.info("Calculating features") df = data[DataContainer.CLEAN] + created_features = [] if self.datetime_columns: for column in self.datetime_columns: if column in df.columns: + self._convert_column_to_datetime(df, column) if self.features: for feature in self.features: - if feature == "year": - df.loc[:, f"{column}_year"] = df[column].dt.year - elif feature == "month": - df.loc[:, f"{column}_month"] = df[column].dt.month - elif feature == "day": - df.loc[:, f"{column}_day"] = df[column].dt.day - elif feature == "hour": - df.loc[:, f"{column}_hour"] = df[column].dt.hour - elif feature == "minute": - df.loc[:, f"{column}_minute"] = df[column].dt.minute - elif feature == "second": - df.loc[:, f"{column}_second"] = df[column].dt.second - elif feature == "weekday": - df.loc[:, f"{column}_weekday"] = df[column].dt.weekday - elif feature == "dayofyear": - df.loc[:, f"{column}_dayofyear"] = df[column].dt.dayofyear - else: - self.logger.warning(f"Unsupported datetime feature: {feature}") + self._extract_feature(df, column, feature) + created_features.append(f"{column}_{feature}") else: self.logger.warning( "No datetime features specified. Skipping feature extraction." @@ -62,6 +95,8 @@ def execute(self, data: DataContainer) -> DataContainer: df = df.drop(columns=self.datetime_columns) self.logger.info(f"Dropped datetime columns: {self.datetime_columns}") + self.logger.info(f"Created new features: {created_features}") + data[DataContainer.FEATURES] = df return data diff --git a/pipeline_lib/core/steps/clean.py b/pipeline_lib/core/steps/clean.py index a52ee14..64a1576 100644 --- a/pipeline_lib/core/steps/clean.py +++ b/pipeline_lib/core/steps/clean.py @@ -12,7 +12,6 @@ def __init__( convert_dtypes: Optional[dict] = None, drop_na_columns: Optional[list] = None, drop_ids: Optional[dict] = None, - ): self.init_logger() self.fill_missing = fill_missing @@ -93,9 +92,12 @@ def execute(self, data: DataContainer) -> DataContainer: if dropped_ids: df = df.loc[~df[column].isin(dropped_ids)].copy() dropped_rows = initial_rows - len(df) - percentage_dropped = (dropped_rows / initial_rows) * 100 # Calculate the percentage of rows dropped + percentage_dropped = ( + dropped_rows / initial_rows + ) * 100 # Calculate the percentage of rows dropped self.logger.info( - f"Dropped {dropped_rows} rows ({percentage_dropped:.2f}%) with IDs {list(dropped_ids)} in column '{column}'" + f"Dropped {dropped_rows} rows ({percentage_dropped:.2f}%) with IDs" + f" {list(dropped_ids)} in column '{column}'" ) else: self.logger.info( @@ -109,7 +111,6 @@ def execute(self, data: DataContainer) -> DataContainer: else: self.logger.warning(f"Column '{column}' not found in the DataFrame") - data[DataContainer.CLEAN] = df return data From 5d65358ddf37da0f6604c6887d051f03d1737af7 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Mon, 18 Mar 2024 21:04:08 -0300 Subject: [PATCH 21/33] improve readme and fix minor issues --- README.md | 133 ++++++++++++++++-- .../core/steps/explainer_dashboard.py | 6 +- .../implementation/tabular/xgboost/predict.py | 7 +- 3 files changed, 133 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 44a3b56..1066d54 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,17 @@ # Pipeline Library -The Pipeline Library is designed to simplify the creation of machine learning pipelines. Currently, it supports XGBoost models, with plans to expand support for more models in the future. +The Pipeline Library is a powerful and flexible tool designed to simplify the creation and management of machine learning pipelines. It provides a high-level interface for defining and executing pipelines, allowing users to focus on the core aspects of their machine learning projects. The library currently supports XGBoost models, with plans to expand support for more models in the future. + +## Features + +* Intuitive and easy-to-use API for defining pipeline steps and configurations +* Support for various data loading formats, including CSV and Parquet +* Flexible data preprocessing steps, such as data cleaning, feature calculation, and encoding +* Seamless integration with XGBoost for model training and prediction +* Hyperparameter optimization using Optuna for fine-tuning models +* Evaluation metrics calculation and reporting +* Explainable AI (XAI) dashboard for model interpretability +* Extensible architecture for adding custom pipeline steps ## Installation @@ -45,25 +56,48 @@ Here's an example of how to use the library to run an XGBoost pipeline: ```json { - "custom_steps_path": "examples/ocf/", "pipeline": { "name": "XGBoostTrainingPipeline", "description": "Training pipeline for XGBoost models.", "steps": [ { - "step_type": "OCFGenerateStep", + "step_type": "GenerateStep", "parameters": { "path": "examples/ocf/data/trainset_new.parquet" } }, { - "step_type": "OCFCleanStep", - "parameters": {} + "step_type": "CleanStep", + "parameters": { + "drop_na_columns": [ + "average_power_kw" + ], + "drop_ids": { + "ss_id": [ + 7759, + 7061 + ] + } + } + }, + { + "step_type": "CalculateFeaturesStep", + "parameters": { + "datetime_columns": [ + "date" + ], + "features": [ + "year", + "month", + "day", + "hour", + "minute" + ] + } }, { "step_type": "TabularSplitStep", "parameters": { - "id_column": "ss_id", "train_percentage": 0.95 } }, @@ -72,7 +106,9 @@ Here's an example of how to use the library to run an XGBoost pipeline: "parameters": { "target": "average_power_kw", "drop_columns": [ - "ss_id" + "ss_id", + "operational_at", + "total_energy_kwh" ], "xgb_params": { "max_depth": 12, @@ -80,12 +116,13 @@ Here's an example of how to use the library to run an XGBoost pipeline: "objective": "reg:squarederror", "eval_metric": "mae", "n_jobs": -1, - "n_estimators": 2, + "n_estimators": 672, "min_child_weight": 7, "subsample": 0.8057743223537057, - "colsample_bytree": 0.6316852278944352 + "colsample_bytree": 0.6316852278944352, + "early_stopping_rounds": 10 }, - "save_model": true + "save_path": "model.joblib" } } ] @@ -105,4 +142,78 @@ logging.basicConfig(level=logging.INFO) Pipeline.from_json("train.json").run() ``` -The library allows users to define custom steps for generating and cleaning their own data, which can be used in the pipeline. +3. Create a `predict.json` file with the pipeline configuration for prediction: + +```json +{ + "pipeline": { + "name": "XGBoostPredictionPipeline", + "description": "Prediction pipeline for XGBoost models.", + "steps": [ + { + "step_type": "GenerateStep", + "parameters": { + "path": "examples/ocf/data/testset_new.parquet" + } + }, + { + "step_type": "CleanStep", + "parameters": { + "drop_na_columns": [ + "average_power_kw" + ] + } + }, + { + "step_type": "CalculateFeaturesStep", + "parameters": { + "datetime_columns": [ + "date" + ], + "features": [ + "year", + "month", + "day", + "hour", + "minute" + ] + } + }, + { + "step_type": "XGBoostPredictStep", + "parameters": { + "target": "average_power_kw", + "drop_columns": [ + "ss_id", + "operational_at", + "total_energy_kwh" + ], + "load_path": "model.joblib" + } + }, + { + "step_type": "CalculateMetricsStep", + "parameters": {} + }, + { + "step_type": "ExplainerDashboardStep", + "parameters": { + "max_samples": 1000 + } + } + ] + } +} +``` + +4. Run the prediction pipeline: + +```python +Pipeline.from_json("predict.json").run() +``` + +The library allows users to define custom steps for data generation, cleaning, and preprocessing, which can be seamlessly integrated into the pipeline. + +## Contributing + +Contributions to the Pipeline Library are welcome! If you encounter any issues, have suggestions for improvements, or want to add new features, please open an issue or submit a pull request on the GitHub repository. \ No newline at end of file diff --git a/pipeline_lib/core/steps/explainer_dashboard.py b/pipeline_lib/core/steps/explainer_dashboard.py index 5a652c5..6863b95 100644 --- a/pipeline_lib/core/steps/explainer_dashboard.py +++ b/pipeline_lib/core/steps/explainer_dashboard.py @@ -25,7 +25,11 @@ def execute(self, data: DataContainer) -> DataContainer: if target is None: raise ValueError("Target column not found in any parameter.") - df = data.get(DataContainer.CLEAN) + df = ( + data[DataContainer.FEATURES] + if DataContainer.FEATURES in data + else data[DataContainer.CLEAN] + ) if len(df) > self.max_samples: # Randomly sample a subset of data points if the dataset is larger than max_samples diff --git a/pipeline_lib/implementation/tabular/xgboost/predict.py b/pipeline_lib/implementation/tabular/xgboost/predict.py index ce9b62f..e52e9a1 100644 --- a/pipeline_lib/implementation/tabular/xgboost/predict.py +++ b/pipeline_lib/implementation/tabular/xgboost/predict.py @@ -30,9 +30,14 @@ def __init__( def execute(self, data: DataContainer) -> DataContainer: self.logger.debug("Obtaining predictions for XGBoost model.") - model_input = data[DataContainer.CLEAN] + model_input = ( + data[DataContainer.FEATURES] + if DataContainer.FEATURES in data + else data[DataContainer.CLEAN] + ) if self.drop_columns: + self.logger.info(f"Dropping columns: {self.drop_columns}") model_input = model_input.drop(columns=self.drop_columns) predictions = self.model.predict(model_input.drop(columns=[self.target])) From 0ec5f16338d7fa4eac1048b7d9db8e2d65b2ad3d Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Tue, 19 Mar 2024 01:48:48 -0300 Subject: [PATCH 22/33] change static variable to getter and setter --- pipeline_lib/core/data_container.py | 332 +++++++++++++++++- pipeline_lib/core/steps/calculate_features.py | 4 +- pipeline_lib/core/steps/calculate_metrics.py | 8 +- pipeline_lib/core/steps/clean.py | 4 +- .../core/steps/explainer_dashboard.py | 12 +- pipeline_lib/core/steps/generate.py | 2 +- pipeline_lib/core/steps/tabular_split.py | 10 +- .../tabular/xgboost/fit_model.py | 12 +- .../implementation/tabular/xgboost/predict.py | 15 +- 9 files changed, 340 insertions(+), 59 deletions(-) diff --git a/pipeline_lib/core/data_container.py b/pipeline_lib/core/data_container.py index 1f50718..86c64e5 100644 --- a/pipeline_lib/core/data_container.py +++ b/pipeline_lib/core/data_container.py @@ -6,7 +6,7 @@ import logging import pickle import sys -from typing import Optional, Union +from typing import Any, Optional, Union import yaml @@ -21,23 +21,6 @@ class DataContainer: A dictionary to store data items. """ - RAW = "raw" - CLEAN = "clean" - TRAIN = "train" - VALIDATION = "validation" - TEST = "test" - MODEL = "model" - MODEL_INPUT = "model_input" - MODEL_OUTPUT = "model_output" - METRICS = "metrics" - PREDICTIONS = "predictions" - EXPLAINER = "explainer" - TUNING_PARAMS = "tuning_params" - TARGET = "target" - IMPORTANCE = "importance" - DROP_COLUMNS = "drop_columns" - FEATURES = "features" - def __init__(self, initial_data: Optional[dict] = None): """ Initialize the DataContainer with an empty dictionary or provided data. @@ -296,6 +279,319 @@ def from_yaml(cls, file_path: str) -> DataContainer: # The loaded data is used as the initial data for the DataContainer instance return cls(initial_data=data) + @property + def clean(self) -> Any: + """ + Get the clean data from the DataContainer. + + Returns + ------- + Any + The clean data stored in the DataContainer. + """ + return self["clean"] + + @clean.setter + def clean(self, value: Any): + """ + Set the clean data in the DataContainer. + + Parameters + ---------- + value + The clean data to be stored in the DataContainer. + """ + self["clean"] = value + + # create the same for raw + @property + def raw(self) -> Any: + """ + Get the raw data from the DataContainer. + + Returns + ------- + Any + The raw data stored in the DataContainer. + """ + return self["raw"] + + @raw.setter + def raw(self, value: Any): + """ + Set the raw data in the DataContainer. + + Parameters + ---------- + value + The raw data to be stored in the DataContainer. + """ + self["raw"] = value + + @property + def train(self) -> Any: + """ + Get the train data from the DataContainer. + + Returns + ------- + Any + The train data stored in the DataContainer. + """ + return self["train"] + + @train.setter + def train(self, value: Any): + """ + Set the train data in the DataContainer. + + Parameters + ---------- + value + The train data to be stored in the DataContainer. + """ + self["train"] = value + + @property + def validation(self) -> Any: + """ + Get the validation data from the DataContainer. + + Returns + ------- + Any + The validation data stored in the DataContainer. + """ + return self["validation"] + + @validation.setter + def validation(self, value: Any): + """ + Set the validation data in the DataContainer. + + Parameters + ---------- + value + The validation data to be stored in the DataContainer. + """ + self["validation"] = value + + @property + def model(self) -> Any: + """ + Get the model from the DataContainer. + + Returns + ------- + Any + The model stored in the DataContainer. + """ + return self["model"] + + @model.setter + def model(self, value: Any): + """ + Set the model in the DataContainer. + + Parameters + ---------- + value + The model to be stored in the DataContainer. + """ + self["model"] = value + + @property + def model_input(self) -> Any: + """ + Get the model input from the DataContainer. + + Returns + ------- + Any + The model input stored in the DataContainer. + """ + return self["model_input"] + + @model_input.setter + def model_input(self, value: Any): + """ + Set the model input in the DataContainer. + + Parameters + ---------- + value + The model input to be stored in the DataContainer. + """ + self["model_input"] = value + + @property + def model_output(self) -> Any: + """ + Get the model output from the DataContainer. + + Returns + ------- + Any + The model output stored in the DataContainer. + """ + return self["model_output"] + + @model_output.setter + def model_output(self, value: Any): + """ + Set the model output in the DataContainer. + + Parameters + ---------- + value + The model output to be stored in the DataContainer. + """ + self["model_output"] = value + + @property + def metrics(self) -> Any: + """ + Get the metrics from the DataContainer. + + Returns + ------- + Any + The metrics stored in the DataContainer. + """ + return self["metrics"] + + @metrics.setter + def metrics(self, value: Any): + """ + Set the metrics in the DataContainer. + + Parameters + ---------- + value + The metrics to be stored in the DataContainer. + """ + self["metrics"] = value + + @property + def predictions(self) -> Any: + """ + Get the predictions from the DataContainer. + + Returns + ------- + Any + The predictions stored in the DataContainer. + """ + return self["predictions"] + + @predictions.setter + def predictions(self, value: Any): + """ + Set the predictions in the DataContainer. + + Parameters + ---------- + value + The predictions to be stored in the DataContainer. + """ + self["predictions"] = value + + @property + def explainer(self) -> Any: + """ + Get the explainer from the DataContainer. + + Returns + ------- + Any + The explainer stored in the DataContainer. + """ + return self["explainer"] + + @explainer.setter + def explainer(self, value: Any): + """ + Set the explainer in the DataContainer. + + Parameters + ---------- + value + The explainer to be stored in the DataContainer. + """ + self["explainer"] = value + + @property + def tuning_params(self) -> Any: + """ + Get the tuning parameters from the DataContainer. + + Returns + ------- + Any + The tuning parameters stored in the DataContainer. + """ + return self["tuning_params"] + + @tuning_params.setter + def tuning_params(self, value: Any): + """ + Set the tuning parameters in the DataContainer. + + Parameters + ---------- + value + The tuning parameters to be stored in the DataContainer. + """ + self["tuning_params"] = value + + @property + def target(self) -> Any: + """ + Get the target from the DataContainer. + + Returns + ------- + Any + The target stored in the DataContainer. + """ + return self["target"] + + @target.setter + def target(self, value: Any): + """ + Set the target in the DataContainer. + + Parameters + ---------- + value + The target to be stored in the DataContainer. + """ + self["target"] = value + + @property + def features(self) -> Any: + """ + Get the features from the DataContainer. + + Returns + ------- + Any + The features stored in the DataContainer. + """ + return self["features"] + + @features.setter + def features(self, value: Any): + """ + Set the features in the DataContainer. + + Parameters + ---------- + value + The features to be stored in the DataContainer. + """ + self["features"] = value + def __eq__(self, other) -> bool: """ Compare this DataContainer with another for equality. diff --git a/pipeline_lib/core/steps/calculate_features.py b/pipeline_lib/core/steps/calculate_features.py index d6e0227..c65df61 100644 --- a/pipeline_lib/core/steps/calculate_features.py +++ b/pipeline_lib/core/steps/calculate_features.py @@ -70,7 +70,7 @@ def execute(self, data: DataContainer) -> DataContainer: """Execute the step.""" self.logger.info("Calculating features") - df = data[DataContainer.CLEAN] + df = data.clean created_features = [] if self.datetime_columns: @@ -97,6 +97,6 @@ def execute(self, data: DataContainer) -> DataContainer: self.logger.info(f"Created new features: {created_features}") - data[DataContainer.FEATURES] = df + data.features = df return data diff --git a/pipeline_lib/core/steps/calculate_metrics.py b/pipeline_lib/core/steps/calculate_metrics.py index 8589283..c3c917a 100644 --- a/pipeline_lib/core/steps/calculate_metrics.py +++ b/pipeline_lib/core/steps/calculate_metrics.py @@ -15,20 +15,20 @@ def __init__(self) -> None: def execute(self, data: DataContainer) -> DataContainer: self.logger.debug("Starting metric calculation") - model_output = data[DataContainer.MODEL_OUTPUT] + model_output = data.model_output - target_column_name = data.get(DataContainer.TARGET) + target_column_name = data.target if target_column_name is None: raise ValueError("Target column not found on any configuration.") true_values = model_output[target_column_name] - predictions = model_output[DataContainer.PREDICTIONS] + predictions = model_output["predictions"] mae = mean_absolute_error(true_values, predictions) rmse = np.sqrt(mean_squared_error(true_values, predictions)) results = {"MAE": str(mae), "RMSE": str(rmse)} self.logger.info(results) - data[DataContainer.METRICS] = results + data.metrics = results return data diff --git a/pipeline_lib/core/steps/clean.py b/pipeline_lib/core/steps/clean.py index 64a1576..7876244 100644 --- a/pipeline_lib/core/steps/clean.py +++ b/pipeline_lib/core/steps/clean.py @@ -23,7 +23,7 @@ def __init__( def execute(self, data: DataContainer) -> DataContainer: self.logger.info("Cleaning tabular data...") - df = data[DataContainer.RAW] + df = data.raw if self.fill_missing: for column, fill_value in self.fill_missing.items(): @@ -111,6 +111,6 @@ def execute(self, data: DataContainer) -> DataContainer: else: self.logger.warning(f"Column '{column}' not found in the DataFrame") - data[DataContainer.CLEAN] = df + data.clean = df return data diff --git a/pipeline_lib/core/steps/explainer_dashboard.py b/pipeline_lib/core/steps/explainer_dashboard.py index 6863b95..a5612c1 100644 --- a/pipeline_lib/core/steps/explainer_dashboard.py +++ b/pipeline_lib/core/steps/explainer_dashboard.py @@ -17,19 +17,15 @@ def __init__( def execute(self, data: DataContainer) -> DataContainer: self.logger.debug("Starting explainer dashboard") - model = data.get(DataContainer.MODEL) + model = data.model if model is None: raise ValueError("Model not found in data container.") - target = data.get(DataContainer.TARGET) + target = data.target if target is None: raise ValueError("Target column not found in any parameter.") - df = ( - data[DataContainer.FEATURES] - if DataContainer.FEATURES in data - else data[DataContainer.CLEAN] - ) + df = data.features if data.features is not None else data.clean if len(df) > self.max_samples: # Randomly sample a subset of data points if the dataset is larger than max_samples @@ -53,6 +49,6 @@ def execute(self, data: DataContainer) -> DataContainer: y_test, ) - data[DataContainer.EXPLAINER] = explainer + data.explainer = explainer return data diff --git a/pipeline_lib/core/steps/generate.py b/pipeline_lib/core/steps/generate.py index 6c5173b..24e412f 100644 --- a/pipeline_lib/core/steps/generate.py +++ b/pipeline_lib/core/steps/generate.py @@ -33,7 +33,7 @@ def execute(self, data: DataContainer) -> DataContainer: else: raise ValueError(f"Unsupported file type: {file_type}") - data[DataContainer.RAW] = df + data.raw = df self.logger.info(f"Generated DataFrame with shape: {df.shape}") diff --git a/pipeline_lib/core/steps/tabular_split.py b/pipeline_lib/core/steps/tabular_split.py index 68b78ce..8815929 100644 --- a/pipeline_lib/core/steps/tabular_split.py +++ b/pipeline_lib/core/steps/tabular_split.py @@ -19,11 +19,7 @@ def execute(self, data: DataContainer) -> DataContainer: """Execute the random train-validation split.""" self.logger.info("Splitting tabular data...") - df = ( - data[DataContainer.FEATURES] - if DataContainer.FEATURES in data - else data[DataContainer.CLEAN] - ) + df = data.features if data.features is not None else data.clean train_df, validation_df = train_test_split( df, train_size=self.train_percentage, random_state=42 @@ -41,7 +37,7 @@ def execute(self, data: DataContainer) -> DataContainer: f" {validation_rows/total_rows:.2%}" ) - data[DataContainer.TRAIN] = train_df - data[DataContainer.VALIDATION] = validation_df + data.train = train_df + data.validation = validation_df return data diff --git a/pipeline_lib/implementation/tabular/xgboost/fit_model.py b/pipeline_lib/implementation/tabular/xgboost/fit_model.py index 6200ee0..170c787 100644 --- a/pipeline_lib/implementation/tabular/xgboost/fit_model.py +++ b/pipeline_lib/implementation/tabular/xgboost/fit_model.py @@ -52,11 +52,8 @@ def execute(self, data: DataContainer) -> DataContainer: start_time = time.time() - data[DataContainer.TARGET] = self.target - data[DataContainer.DROP_COLUMNS] = self.drop_columns - - df_train = data[DataContainer.TRAIN] - df_valid = data[DataContainer.VALIDATION] + df_train = data.train + df_valid = data.validation if self.drop_columns: df_train = df_train.drop(columns=self.drop_columns) @@ -75,7 +72,7 @@ def execute(self, data: DataContainer) -> DataContainer: params = self.optimize_with_optuna( X_train, y_train, X_valid, y_valid, self.optuna_params ) - data[DataContainer.TUNING_PARAMS] = params + data.tuning_params = params model = xgb.XGBRegressor(**params) @@ -93,7 +90,8 @@ def execute(self, data: DataContainer) -> DataContainer: self.logger.info(f"XGBoost model fitting took {minutes} minutes and {seconds} seconds.") # Save the model to the data container - data[DataContainer.MODEL] = model + data.model = model + data.target = self.target if self.save_path: self.logger.info(f"Saving the model to {self.save_path}") diff --git a/pipeline_lib/implementation/tabular/xgboost/predict.py b/pipeline_lib/implementation/tabular/xgboost/predict.py index e52e9a1..e28954d 100644 --- a/pipeline_lib/implementation/tabular/xgboost/predict.py +++ b/pipeline_lib/implementation/tabular/xgboost/predict.py @@ -30,11 +30,7 @@ def __init__( def execute(self, data: DataContainer) -> DataContainer: self.logger.debug("Obtaining predictions for XGBoost model.") - model_input = ( - data[DataContainer.FEATURES] - if DataContainer.FEATURES in data - else data[DataContainer.CLEAN] - ) + model_input = data.features if data.features is not None else data.clean if self.drop_columns: self.logger.info(f"Dropping columns: {self.drop_columns}") @@ -44,9 +40,8 @@ def execute(self, data: DataContainer) -> DataContainer: predictions_df = pd.DataFrame(predictions, columns=["prediction"]) - model_input[DataContainer.PREDICTIONS] = predictions_df - data[DataContainer.MODEL] = self.model - data[DataContainer.MODEL_OUTPUT] = model_input - data[DataContainer.TARGET] = self.target - data[DataContainer.DROP_COLUMNS] = self.drop_columns + model_input["predictions"] = predictions_df + data.model = self.model + data.model_output = model_input + data.target = self.target return data From e2e99ec8db47cf19c49b29faebd37ce3f2022212 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Tue, 19 Mar 2024 13:52:37 -0300 Subject: [PATCH 23/33] add data flow key for improving step data connection --- pipeline_lib/core/data_container.py | 24 +++++++++++++++++++ pipeline_lib/core/steps/calculate_features.py | 4 ++-- pipeline_lib/core/steps/clean.py | 1 + .../core/steps/explainer_dashboard.py | 2 +- pipeline_lib/core/steps/generate.py | 1 + pipeline_lib/core/steps/tabular_split.py | 2 +- .../implementation/tabular/xgboost/predict.py | 2 +- 7 files changed, 31 insertions(+), 5 deletions(-) diff --git a/pipeline_lib/core/data_container.py b/pipeline_lib/core/data_container.py index 86c64e5..ed895ae 100644 --- a/pipeline_lib/core/data_container.py +++ b/pipeline_lib/core/data_container.py @@ -592,6 +592,30 @@ def features(self, value: Any): """ self["features"] = value + @property + def flow(self) -> Any: + """ + Get the flow from the DataContainer. + + Returns + ------- + Any + The flow stored in the DataContainer. + """ + return self["flow"] + + @flow.setter + def flow(self, value: Any): + """ + Set the flow in the DataContainer. + + Parameters + ---------- + value + The flow to be stored in the DataContainer. + """ + self["flow"] = value + def __eq__(self, other) -> bool: """ Compare this DataContainer with another for equality. diff --git a/pipeline_lib/core/steps/calculate_features.py b/pipeline_lib/core/steps/calculate_features.py index c65df61..00ca7bb 100644 --- a/pipeline_lib/core/steps/calculate_features.py +++ b/pipeline_lib/core/steps/calculate_features.py @@ -70,7 +70,7 @@ def execute(self, data: DataContainer) -> DataContainer: """Execute the step.""" self.logger.info("Calculating features") - df = data.clean + df = data.flow created_features = [] if self.datetime_columns: @@ -97,6 +97,6 @@ def execute(self, data: DataContainer) -> DataContainer: self.logger.info(f"Created new features: {created_features}") - data.features = df + data.flow = df return data diff --git a/pipeline_lib/core/steps/clean.py b/pipeline_lib/core/steps/clean.py index 7876244..e9fef35 100644 --- a/pipeline_lib/core/steps/clean.py +++ b/pipeline_lib/core/steps/clean.py @@ -112,5 +112,6 @@ def execute(self, data: DataContainer) -> DataContainer: self.logger.warning(f"Column '{column}' not found in the DataFrame") data.clean = df + data.flow = df return data diff --git a/pipeline_lib/core/steps/explainer_dashboard.py b/pipeline_lib/core/steps/explainer_dashboard.py index a5612c1..ee1f325 100644 --- a/pipeline_lib/core/steps/explainer_dashboard.py +++ b/pipeline_lib/core/steps/explainer_dashboard.py @@ -25,7 +25,7 @@ def execute(self, data: DataContainer) -> DataContainer: if target is None: raise ValueError("Target column not found in any parameter.") - df = data.features if data.features is not None else data.clean + df = data.flow if len(df) > self.max_samples: # Randomly sample a subset of data points if the dataset is larger than max_samples diff --git a/pipeline_lib/core/steps/generate.py b/pipeline_lib/core/steps/generate.py index 24e412f..c4c1ff1 100644 --- a/pipeline_lib/core/steps/generate.py +++ b/pipeline_lib/core/steps/generate.py @@ -34,6 +34,7 @@ def execute(self, data: DataContainer) -> DataContainer: raise ValueError(f"Unsupported file type: {file_type}") data.raw = df + data.flow = df self.logger.info(f"Generated DataFrame with shape: {df.shape}") diff --git a/pipeline_lib/core/steps/tabular_split.py b/pipeline_lib/core/steps/tabular_split.py index 8815929..1e7e31e 100644 --- a/pipeline_lib/core/steps/tabular_split.py +++ b/pipeline_lib/core/steps/tabular_split.py @@ -19,7 +19,7 @@ def execute(self, data: DataContainer) -> DataContainer: """Execute the random train-validation split.""" self.logger.info("Splitting tabular data...") - df = data.features if data.features is not None else data.clean + df = data.flow train_df, validation_df = train_test_split( df, train_size=self.train_percentage, random_state=42 diff --git a/pipeline_lib/implementation/tabular/xgboost/predict.py b/pipeline_lib/implementation/tabular/xgboost/predict.py index e28954d..2759685 100644 --- a/pipeline_lib/implementation/tabular/xgboost/predict.py +++ b/pipeline_lib/implementation/tabular/xgboost/predict.py @@ -30,7 +30,7 @@ def __init__( def execute(self, data: DataContainer) -> DataContainer: self.logger.debug("Obtaining predictions for XGBoost model.") - model_input = data.features if data.features is not None else data.clean + model_input = data.flow if self.drop_columns: self.logger.info(f"Dropping columns: {self.drop_columns}") From 4808ab62a1498f8a352d280d15ccaab206ec956e Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Tue, 19 Mar 2024 17:29:27 -0300 Subject: [PATCH 24/33] add more metrics for palf :) --- pipeline_lib/core/steps/calculate_metrics.py | 21 +++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/pipeline_lib/core/steps/calculate_metrics.py b/pipeline_lib/core/steps/calculate_metrics.py index c3c917a..cc851b4 100644 --- a/pipeline_lib/core/steps/calculate_metrics.py +++ b/pipeline_lib/core/steps/calculate_metrics.py @@ -1,5 +1,5 @@ import numpy as np -from sklearn.metrics import mean_absolute_error, mean_squared_error +from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score from pipeline_lib.core import DataContainer from pipeline_lib.core.steps.base import PipelineStep @@ -27,8 +27,23 @@ def execute(self, data: DataContainer) -> DataContainer: mae = mean_absolute_error(true_values, predictions) rmse = np.sqrt(mean_squared_error(true_values, predictions)) - - results = {"MAE": str(mae), "RMSE": str(rmse)} + r2 = r2_score(true_values, predictions) + + # Additional metrics + me = np.mean(true_values - predictions) # Mean Error + mape = np.mean(np.abs((true_values - predictions) / true_values)) * 100 + max_error = np.max(np.abs(true_values - predictions)) + median_absolute_error = np.median(np.abs(true_values - predictions)) + + results = { + "MAE": str(mae), + "RMSE": str(rmse), + "R^2": str(r2), + "Mean Error": str(me), + "MAPE": str(mape), + "Max Error": str(max_error), + "Median Absolute Error": str(median_absolute_error), + } self.logger.info(results) data.metrics = results return data From 2c96b4be3b00b3addb0b026dac37ad0c5ca43acb Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Mon, 25 Mar 2024 11:58:01 -0300 Subject: [PATCH 25/33] add model interface --- pipeline_lib/__init__.py | 2 +- pipeline_lib/core/data_container.py | 48 +++++++++++++++++ pipeline_lib/core/model.py | 22 ++++++++ pipeline_lib/core/model_registry.py | 47 +++++++++++++++++ pipeline_lib/core/pipeline.py | 52 +++++++++++++++++-- .../implementation/tabular/xgboost/model.py | 18 +++++++ 6 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 pipeline_lib/core/model.py create mode 100644 pipeline_lib/core/model_registry.py create mode 100644 pipeline_lib/implementation/tabular/xgboost/model.py diff --git a/pipeline_lib/__init__.py b/pipeline_lib/__init__.py index 0c99341..c97e573 100644 --- a/pipeline_lib/__init__.py +++ b/pipeline_lib/__init__.py @@ -1,6 +1,6 @@ from .core.pipeline import Pipeline Pipeline.step_registry.auto_register_steps_from_package("pipeline_lib.core.steps") -Pipeline.step_registry.auto_register_steps_from_package( +Pipeline.model_registry.auto_register_models_from_package( "pipeline_lib.implementation.tabular.xgboost" ) diff --git a/pipeline_lib/core/data_container.py b/pipeline_lib/core/data_container.py index ed895ae..f0fb134 100644 --- a/pipeline_lib/core/data_container.py +++ b/pipeline_lib/core/data_container.py @@ -616,6 +616,54 @@ def flow(self, value: Any): """ self["flow"] = value + @property + def _drop_columns(self) -> Any: + """ + Get the drop columns from the DataContainer. + + Returns + ------- + Any + The drop columns stored in the DataContainer. + """ + return self["_drop_columns"] + + @_drop_columns.setter + def _drop_columns(self, value: Any): + """ + Set the drop columns in the DataContainer. + + Parameters + ---------- + value + The drop columns to be stored in the DataContainer. + """ + self["_drop_columns"] = value + + @property + def is_train(self) -> bool: + """ + Check if the DataContainer is made for training. + + Returns + ------- + bool + True if the DataContainer contains training data, False otherwise. + """ + return self["is_train"] + + @is_train.setter + def is_train(self, value: bool): + """ + Set the is_train flag in the DataContainer. + + Parameters + ---------- + value + The is_train flag to be stored in the DataContainer. + """ + self["is_train"] = value + def __eq__(self, other) -> bool: """ Compare this DataContainer with another for equality. diff --git a/pipeline_lib/core/model.py b/pipeline_lib/core/model.py new file mode 100644 index 0000000..36b50cd --- /dev/null +++ b/pipeline_lib/core/model.py @@ -0,0 +1,22 @@ +from abc import ABC, abstractmethod +from typing import List, Optional, Tuple + +import pandas as pd + + +class Model(ABC): + """Base class for models.""" + + @abstractmethod + def fit( + self, + X: pd.DataFrame, + y: pd.Series, + eval_set: Optional[List[Tuple[pd.DataFrame, pd.Series]]] = None, + verbose: Optional[bool] = True, + ): + """Abstract method for fitting the model.""" + + @abstractmethod + def predict(self, X: pd.DataFrame) -> pd.Series: + """Abstract method for making predictions.""" diff --git a/pipeline_lib/core/model_registry.py b/pipeline_lib/core/model_registry.py new file mode 100644 index 0000000..3ba2363 --- /dev/null +++ b/pipeline_lib/core/model_registry.py @@ -0,0 +1,47 @@ +import importlib +import logging +import pkgutil + +from pipeline_lib.core.model import Model + + +class ModelClassNotFoundError(Exception): + pass + + +class ModelRegistry: + def __init__(self): + self._model_registry = {} + self.logger = logging.getLogger(__name__) + + def register_model(self, model_class: type): + model_name = model_class.__name__ + if not issubclass(model_class, Model): + raise ValueError(f"{model_class} must be a subclass of Model") + self._model_registry[model_name] = model_class + + def get_model_class(self, model_name: str) -> type: + if model_name in self._model_registry: + return self._model_registry[model_name] + else: + raise ModelClassNotFoundError(f"Model class '{model_name}' not found in registry.") + + def get_all_model_classes(self) -> dict: + return self._model_registry + + def auto_register_models_from_package(self, package_name: str): + try: + package = importlib.import_module(package_name) + prefix = package.__name__ + "." + for importer, modname, ispkg in pkgutil.walk_packages(package.__path__, prefix): + module = importlib.import_module(modname) + for name in dir(module): + attribute = getattr(module, name) + if ( + isinstance(attribute, type) + and issubclass(attribute, Model) + and attribute is not Model + ): + self.register_model(attribute) + except ImportError as e: + self.logger.error(f"Failed to import package: {package_name}. Error: {e}") diff --git a/pipeline_lib/core/pipeline.py b/pipeline_lib/core/pipeline.py index b29f6ca..090e3ae 100644 --- a/pipeline_lib/core/pipeline.py +++ b/pipeline_lib/core/pipeline.py @@ -4,7 +4,10 @@ import logging from typing import Optional +from joblib import load + from pipeline_lib.core.data_container import DataContainer +from pipeline_lib.core.model_registry import ModelRegistry from pipeline_lib.core.step_registry import StepRegistry from pipeline_lib.core.steps import PipelineStep @@ -15,24 +18,34 @@ class Pipeline: _step_registry = {} logger = logging.getLogger("Pipeline") step_registry = StepRegistry() + model_registry = ModelRegistry() def __init__(self, initial_data: Optional[DataContainer] = None): self.steps = [] self.initial_data = initial_data self.save_path = None self.load_path = None + self.model_path = None def add_steps(self, steps: list[PipelineStep]): """Add steps to the pipeline.""" self.steps.extend(steps) - def run(self) -> DataContainer: + def run(self, is_train: bool) -> DataContainer: """Run the pipeline on the given data.""" data = DataContainer.from_pickle(self.load_path) if self.load_path else DataContainer() + data.is_train = is_train + + if is_train: + steps_to_run = [step for step in self.steps if step.used_for_training] + else: + steps_to_run = [step for step in self.steps if step.used_for_prediction] - for i, step in enumerate(self.steps): - Pipeline.logger.info(f"Running {step.__class__.__name__} - {i + 1} / {len(self.steps)}") + for i, step in enumerate(steps_to_run): + Pipeline.logger.info( + f"Running {step.__class__.__name__} - {i + 1} / {len(steps_to_run)}" + ) data = step.execute(data) if self.save_path: @@ -40,6 +53,20 @@ def run(self) -> DataContainer: return data + def train(self) -> DataContainer: + """Run the pipeline on the given data.""" + self.logger.info("Training the pipeline") + return self.run(is_train=True) + + def predict(self) -> DataContainer: + """Run the pipeline on the given data.""" + self.logger.info("Predicting with the pipeline") + data = self.run(is_train=False) + data.predictions = data.model.predict(data.flow) + self.logger.info("Predictions:") + self.logger.info(data.predictions) + return data + @classmethod def from_json(cls, path: str) -> Pipeline: """Load a pipeline from a JSON file.""" @@ -61,6 +88,10 @@ def from_json(cls, path: str) -> Pipeline: steps = [] + model_path = None + drop_columns = None + target = None + for step_config in config["pipeline"]["steps"]: step_type = step_config["step_type"] parameters = step_config.get("parameters", {}) @@ -69,6 +100,21 @@ def from_json(cls, path: str) -> Pipeline: f"Creating step {step_type} with parameters: \n {json.dumps(parameters, indent=4)}" ) + # change model from string to class + if step_type == "FitModelStep": + model_class_name = parameters.pop("model_class") + model_class = cls.model_registry.get_model_class(model_class_name) + parameters["model_class"] = model_class + model_path = parameters.get("save_path") + drop_columns = parameters.get("drop_columns") + target = parameters.get("target") + + # if step type is prediction, add model path + if step_type == "PredictStep": + parameters["load_path"] = model_path + parameters["drop_columns"] = drop_columns + parameters["target"] = target + step_class = cls.step_registry.get_step_class(step_type) step = step_class(**parameters) steps.append(step) diff --git a/pipeline_lib/implementation/tabular/xgboost/model.py b/pipeline_lib/implementation/tabular/xgboost/model.py new file mode 100644 index 0000000..ec7d2c3 --- /dev/null +++ b/pipeline_lib/implementation/tabular/xgboost/model.py @@ -0,0 +1,18 @@ +from typing import Any + +import pandas as pd +import xgboost as xgb + +from pipeline_lib.core.model import Model + + +class XGBoostModel(Model): + def __init__(self, **params): + self.model = xgb.XGBRegressor(**params) + + def fit(self, X: pd.DataFrame, y: pd.Series, eval_set=None, verbose=True) -> Any: + self.model.fit(X, y, eval_set=eval_set, verbose=verbose) + return self + + def predict(self, X: pd.DataFrame) -> pd.Series: + return self.model.predict(X) From ac8443c3da2b564e5e6bee738299af2de62591c2 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Mon, 25 Mar 2024 11:58:55 -0300 Subject: [PATCH 26/33] differentiate between train & predict steps at exec --- pipeline_lib/core/steps/augment.py | 3 + pipeline_lib/core/steps/calculate_features.py | 3 + pipeline_lib/core/steps/calculate_metrics.py | 18 ++- pipeline_lib/core/steps/calculate_reports.py | 3 + pipeline_lib/core/steps/clean.py | 3 + pipeline_lib/core/steps/encode.py | 3 + .../core/steps/explainer_dashboard.py | 5 +- pipeline_lib/core/steps/fit_encoders.py | 3 + pipeline_lib/core/steps/fit_model.py | 120 +++++++++++++++++- pipeline_lib/core/steps/generate.py | 56 +++++--- pipeline_lib/core/steps/input_scaling.py | 3 + pipeline_lib/core/steps/predict.py | 37 +++++- pipeline_lib/core/steps/tabular_split.py | 3 + pipeline_lib/core/steps/target_scaling.py | 7 +- .../tabular/xgboost/fit_model.py | 8 +- .../implementation/tabular/xgboost/predict.py | 1 + 16 files changed, 243 insertions(+), 33 deletions(-) diff --git a/pipeline_lib/core/steps/augment.py b/pipeline_lib/core/steps/augment.py index 21708d7..e9c6968 100644 --- a/pipeline_lib/core/steps/augment.py +++ b/pipeline_lib/core/steps/augment.py @@ -7,6 +7,9 @@ class AugmentStep(PipelineStep): """Augment the data.""" + used_for_prediction = True + used_for_training = True + def __init__(self, config: Optional[dict] = None) -> None: """Initialize AugmentStep.""" super().__init__(config=config) diff --git a/pipeline_lib/core/steps/calculate_features.py b/pipeline_lib/core/steps/calculate_features.py index 00ca7bb..a303114 100644 --- a/pipeline_lib/core/steps/calculate_features.py +++ b/pipeline_lib/core/steps/calculate_features.py @@ -16,6 +16,9 @@ class UnsupportedFeatureError(Exception): class CalculateFeaturesStep(PipelineStep): """Calculate features.""" + used_for_prediction = True + used_for_training = True + def __init__( self, datetime_columns: Optional[List[str]] = None, diff --git a/pipeline_lib/core/steps/calculate_metrics.py b/pipeline_lib/core/steps/calculate_metrics.py index cc851b4..e246d26 100644 --- a/pipeline_lib/core/steps/calculate_metrics.py +++ b/pipeline_lib/core/steps/calculate_metrics.py @@ -8,14 +8,18 @@ class CalculateMetricsStep(PipelineStep): """Calculate metrics.""" - def __init__(self) -> None: + used_for_prediction = True + used_for_training = False + + def __init__(self, mape_threshold: float = 0.01) -> None: """Initialize CalculateMetricsStep.""" super().__init__() self.init_logger() + self.mape_threshold = mape_threshold def execute(self, data: DataContainer) -> DataContainer: self.logger.debug("Starting metric calculation") - model_output = data.model_output + model_output = data.flow target_column_name = data.target @@ -31,10 +35,18 @@ def execute(self, data: DataContainer) -> DataContainer: # Additional metrics me = np.mean(true_values - predictions) # Mean Error - mape = np.mean(np.abs((true_values - predictions) / true_values)) * 100 max_error = np.max(np.abs(true_values - predictions)) median_absolute_error = np.median(np.abs(true_values - predictions)) + # MAPE calculation with threshold + mask = (true_values > self.mape_threshold) & (predictions > self.mape_threshold) + mape_true_values = true_values[mask] + mape_predictions = predictions[mask] + if len(mape_true_values) > 0: + mape = np.mean(np.abs((mape_true_values - mape_predictions) / mape_true_values)) * 100 + else: + mape = np.nan + results = { "MAE": str(mae), "RMSE": str(rmse), diff --git a/pipeline_lib/core/steps/calculate_reports.py b/pipeline_lib/core/steps/calculate_reports.py index 39cab38..5a0e51b 100644 --- a/pipeline_lib/core/steps/calculate_reports.py +++ b/pipeline_lib/core/steps/calculate_reports.py @@ -7,6 +7,9 @@ class CalculateReportsStep(PipelineStep): """Calculate reports.""" + used_for_prediction = True + used_for_training = False + def __init__(self, config: Optional[dict] = None) -> None: """Initialize CalculateReportsStep.""" super().__init__(config=config) diff --git a/pipeline_lib/core/steps/clean.py b/pipeline_lib/core/steps/clean.py index e9fef35..3c094f4 100644 --- a/pipeline_lib/core/steps/clean.py +++ b/pipeline_lib/core/steps/clean.py @@ -5,6 +5,9 @@ class CleanStep(PipelineStep): + used_for_prediction = True + used_for_training = True + def __init__( self, fill_missing: Optional[dict] = None, diff --git a/pipeline_lib/core/steps/encode.py b/pipeline_lib/core/steps/encode.py index c81951b..7649d63 100644 --- a/pipeline_lib/core/steps/encode.py +++ b/pipeline_lib/core/steps/encode.py @@ -7,6 +7,9 @@ class EncodeStep(PipelineStep): """Encode the data.""" + used_for_prediction = True + used_for_training = True + def __init__(self, config: Optional[dict] = None) -> None: """Initialize EncodeStep.""" super().__init__(config=config) diff --git a/pipeline_lib/core/steps/explainer_dashboard.py b/pipeline_lib/core/steps/explainer_dashboard.py index ee1f325..042c104 100644 --- a/pipeline_lib/core/steps/explainer_dashboard.py +++ b/pipeline_lib/core/steps/explainer_dashboard.py @@ -7,6 +7,9 @@ class ExplainerDashboardStep(PipelineStep): """Scale the target using Quantile Transformer.""" + used_for_prediction = True + used_for_training = False + def __init__( self, max_samples: int = 1000, @@ -36,7 +39,7 @@ def execute(self, data: DataContainer) -> DataContainer: self.logger.info(f"Sampling {self.max_samples} data points from the dataset.") df = df.sample(n=self.max_samples, random_state=42) - drop_columns = data.get("drop_columns") + drop_columns = data._drop_columns if drop_columns: df = df.drop(columns=drop_columns) diff --git a/pipeline_lib/core/steps/fit_encoders.py b/pipeline_lib/core/steps/fit_encoders.py index 177949e..dbac990 100644 --- a/pipeline_lib/core/steps/fit_encoders.py +++ b/pipeline_lib/core/steps/fit_encoders.py @@ -7,6 +7,9 @@ class FitEncodersStep(PipelineStep): """Fit encoders.""" + used_for_prediction = True + used_for_training = True + def __init__(self, config: Optional[dict] = None) -> None: """Initialize FitEncodersStep.""" super().__init__(config=config) diff --git a/pipeline_lib/core/steps/fit_model.py b/pipeline_lib/core/steps/fit_model.py index 4eaeef2..9f2e7a6 100644 --- a/pipeline_lib/core/steps/fit_model.py +++ b/pipeline_lib/core/steps/fit_model.py @@ -1,18 +1,130 @@ -from typing import Optional +from typing import Optional, Type + +import optuna +from joblib import dump +from sklearn.metrics import mean_absolute_error from pipeline_lib.core import DataContainer +from pipeline_lib.core.model import Model from pipeline_lib.core.steps.base import PipelineStep class FitModelStep(PipelineStep): """Fit the model.""" - def __init__(self, config: Optional[dict] = None) -> None: + used_for_prediction = False + used_for_training = True + + def __init__( + self, + model_class: Type[Model], + target: str, + model_params: Optional[dict] = None, + drop_columns: Optional[list[str]] = None, + optuna_params: Optional[dict] = None, + search_space: Optional[dict] = None, + save_path: Optional[str] = None, + ) -> None: """Initialize FitModelStep.""" - super().__init__(config=config) + super().__init__() self.init_logger() + self.model_class = model_class + self.target = target + self.model_params = model_params or {} + self.drop_columns = drop_columns or [] + self.optuna_params = optuna_params + self.search_space = search_space or {} + self.save_path = save_path + + self.model = self.model_class(**self.model_params) def execute(self, data: DataContainer) -> DataContainer: """Execute the step.""" - self.logger.info("Fitting the model") + self.logger.info(f"Fitting the {self.model_class.__name__} model") + + df_train = data.train + df_valid = data.validation + + if self.drop_columns: + df_train = df_train.drop(columns=self.drop_columns) + df_valid = df_valid.drop(columns=self.drop_columns) + + X_train = df_train.drop(columns=[self.target]) + y_train = df_train[self.target] + + X_valid = df_valid.drop(columns=[self.target]) + y_valid = df_valid[self.target] + + params = self.model_params + + if self.optuna_params: + params = self.optimize_with_optuna(X_train, y_train, X_valid, y_valid) + data.tuning_params = params + + self.model.fit( + X_train, + y_train, + eval_set=[(X_valid, y_valid)], + verbose=True, + ) + + data.model = self.model + data.target = self.target + data.model_path = self.save_path + + if self.save_path: + self.logger.info(f"Saving the model to {self.save_path}") + dump(self.model, self.save_path) + return data + + def optimize_with_optuna(self, X_train, y_train, X_valid, y_valid): + def objective(trial): + param = {} + for key, value in self.search_space.items(): + if isinstance(value, dict): + suggest_func = getattr(trial, f"suggest_{value['type']}") + param[key] = suggest_func(key, *value["args"], **value["kwargs"]) + else: + param[key] = value + + model = self.model_class(**param) + model.fit( + X_train, + y_train, + eval_set=[(X_valid, y_valid)], + verbose=True, + ) + preds = model.predict(X_valid) + mae = mean_absolute_error(y_valid, preds) + return mae + + def optuna_logging_callback(study, trial): + if trial.state == optuna.trial.TrialState.COMPLETE: + self.logger.info( + f"Trial {trial.number} finished with value: {trial.value} and parameters:" + f" {trial.params}. Best is trial {study.best_trial.number} with value:" + f" {study.best_value}." + ) + + if not self.optuna_params: + raise ValueError("Optuna parameters are not provided.") + + optuna_trials = self.optuna_params.get("trials", 20) + + self.logger.info(f"Optimizing XGBoost hyperparameters with {optuna_trials} trials.") + + study_name = self.optuna_params.get("study_name", "xgboost_optimization") + storage = self.optuna_params.get("storage", "sqlite:///db.sqlite3") + + study = optuna.create_study( + direction="minimize", + study_name=study_name, + storage=storage, + ) + + study.optimize(objective, n_trials=optuna_trials, callbacks=[optuna_logging_callback]) + + best_params = study.best_params + self.logger.info(f"Best parameters found by Optuna: {best_params}") + return best_params diff --git a/pipeline_lib/core/steps/generate.py b/pipeline_lib/core/steps/generate.py index c4c1ff1..bce2cee 100644 --- a/pipeline_lib/core/steps/generate.py +++ b/pipeline_lib/core/steps/generate.py @@ -1,5 +1,6 @@ import os from enum import Enum +from typing import Optional import pandas as pd @@ -13,23 +14,46 @@ class FileType(Enum): class GenerateStep(PipelineStep): - def __init__(self, path: str, **kwargs): + used_for_prediction = True + used_for_training = True + + def __init__( + self, train_path: Optional[str] = None, predict_path: Optional[str] = None, **kwargs + ): self.init_logger() - self.file_path = path + self.train_path = train_path + self.predict_path = predict_path self.kwargs = kwargs def execute(self, data: DataContainer) -> DataContainer: - self.logger.info(f"Generating data from file: {self.file_path}") + """Generate the data from the file.""" + + if data.is_train and not self.train_path: + raise ValueError("train_path must be provided for training.") + + if not data.is_train and not self.predict_path: + raise ValueError("prediction_path must be provided for prediction.") + + file_path = self.train_path if data.is_train else self.predict_path + + self.logger.info(f"Generating data from file: {file_path}") - if not os.path.exists(self.file_path): - raise FileNotFoundError(f"File not found: {self.file_path}") + if not os.path.exists(file_path): + raise FileNotFoundError(f"File not found: {file_path}") - file_type = self._infer_file_type() + file_type = self._infer_file_type(file_path) + + kwargs = self.kwargs.copy() + if data.is_train: + kwargs.pop("predict_path", None) # Remove prediction_path if in train mode + self.logger.info(f"Removing prediction_path from kwargs: {kwargs}") + else: + kwargs.pop("train_path", None) # Remove train_path if in predict mode if file_type == FileType.CSV: - df = self._read_csv() + df = self._read_csv(file_path, **kwargs) elif file_type == FileType.PARQUET: - df = self._read_parquet() + df = self._read_parquet(file_path, **kwargs) else: raise ValueError(f"Unsupported file type: {file_type}") @@ -40,8 +64,8 @@ def execute(self, data: DataContainer) -> DataContainer: return data - def _infer_file_type(self) -> FileType: - _, file_extension = os.path.splitext(self.file_path) + def _infer_file_type(self, file_path: str) -> FileType: + _, file_extension = os.path.splitext(file_path) file_extension = file_extension.lower() try: @@ -49,18 +73,18 @@ def _infer_file_type(self) -> FileType: except ValueError: raise ValueError(f"Unsupported file extension: {file_extension}") - def _read_csv(self) -> pd.DataFrame: - kwargs = self.kwargs.copy() + def _read_csv(self, file_path: str, **kwargs) -> pd.DataFrame: index_col = kwargs.pop("index", None) - df = pd.read_csv(self.file_path, **kwargs) + self.logger.info(f"Reading CSV file with kwargs: {kwargs}") + df = pd.read_csv(file_path, **kwargs) if index_col is not None: df.set_index(index_col, inplace=True) return df - def _read_parquet(self) -> pd.DataFrame: - kwargs = self.kwargs.copy() + def _read_parquet(self, file_path: str, **kwargs) -> pd.DataFrame: index_col = kwargs.pop("index", None) - df = pd.read_parquet(self.file_path, **kwargs) + self.logger.info(f"Reading parquet file with kwargs: {kwargs}") + df = pd.read_parquet(file_path, **kwargs) if index_col is not None: df.set_index(index_col, inplace=True) return df diff --git a/pipeline_lib/core/steps/input_scaling.py b/pipeline_lib/core/steps/input_scaling.py index 18b826b..676b64c 100644 --- a/pipeline_lib/core/steps/input_scaling.py +++ b/pipeline_lib/core/steps/input_scaling.py @@ -7,6 +7,9 @@ class InputScalingStep(PipelineStep): """Scale the input.""" + used_for_prediction = True + used_for_training = True + def __init__(self, config: Optional[dict] = None) -> None: """Initialize InputScalingStep.""" super().__init__(config=config) diff --git a/pipeline_lib/core/steps/predict.py b/pipeline_lib/core/steps/predict.py index 6da0a99..4025649 100644 --- a/pipeline_lib/core/steps/predict.py +++ b/pipeline_lib/core/steps/predict.py @@ -1,4 +1,6 @@ -from typing import Optional +from typing import List, Optional + +from joblib import load from pipeline_lib.core import DataContainer from pipeline_lib.core.steps.base import PipelineStep @@ -7,12 +9,41 @@ class PredictStep(PipelineStep): """Obtain the predictions.""" - def __init__(self, config: Optional[dict] = None) -> None: + used_for_prediction = True + used_for_training = False + + def __init__( + self, + load_path: str, + target: str, + drop_columns: Optional[List[str]] = None, + ) -> None: """Initialize Predict Step.""" - super().__init__(config=config) + super().__init__() self.init_logger() + self.load_path = load_path + self.model = load(self.load_path) + self.target = target + self.drop_columns = drop_columns or [] def execute(self, data: DataContainer) -> DataContainer: """Execute the step.""" self.logger.info("Obtaining predictions") + + drop_columns = self.drop_columns + [self.target] + + missing_columns = [col for col in drop_columns if col not in data.flow.columns] + if missing_columns: + error_message = ( + f"The following columns do not exist in the DataFrame: {', '.join(missing_columns)}" + ) + self.logger.warning(error_message) + raise KeyError(error_message) + + data.predictions = self.model.predict(data.flow.drop(columns=drop_columns)) + + data.flow["predictions"] = data.predictions + + data.target = self.target + return data diff --git a/pipeline_lib/core/steps/tabular_split.py b/pipeline_lib/core/steps/tabular_split.py index 1e7e31e..f17b811 100644 --- a/pipeline_lib/core/steps/tabular_split.py +++ b/pipeline_lib/core/steps/tabular_split.py @@ -7,6 +7,9 @@ class TabularSplitStep(PipelineStep): """Split the data.""" + used_for_prediction = False + used_for_training = True + def __init__(self, train_percentage: float) -> None: """Initialize SplitStep.""" self.init_logger() diff --git a/pipeline_lib/core/steps/target_scaling.py b/pipeline_lib/core/steps/target_scaling.py index cb2f83a..36a1198 100644 --- a/pipeline_lib/core/steps/target_scaling.py +++ b/pipeline_lib/core/steps/target_scaling.py @@ -7,9 +7,12 @@ class TargetScalingStep(PipelineStep): """Scale the target.""" - def __init__(self, config: Optional[dict] = None) -> None: + used_for_prediction = True + used_for_training = True + + def __init__(self) -> None: """Initialize TargetScalingStep.""" - super().__init__(config=config) + super().__init__() self.init_logger() def execute(self, data: DataContainer) -> DataContainer: diff --git a/pipeline_lib/implementation/tabular/xgboost/fit_model.py b/pipeline_lib/implementation/tabular/xgboost/fit_model.py index 170c787..ed82260 100644 --- a/pipeline_lib/implementation/tabular/xgboost/fit_model.py +++ b/pipeline_lib/implementation/tabular/xgboost/fit_model.py @@ -2,7 +2,6 @@ from typing import Optional import optuna -import xgboost as xgb from joblib import dump from optuna.pruners import MedianPruner from sklearn.metrics import mean_absolute_error @@ -10,6 +9,8 @@ from pipeline_lib.core import DataContainer from pipeline_lib.core.steps import FitModelStep +from .model import XGBoostModel + class XGBoostFitModelStep(FitModelStep): """Fit the model with XGBoost.""" @@ -74,7 +75,7 @@ def execute(self, data: DataContainer) -> DataContainer: ) data.tuning_params = params - model = xgb.XGBRegressor(**params) + model = XGBoostModel(**params) model.fit( X_train, @@ -125,12 +126,11 @@ def objective(trial): "n_estimators": trial.suggest_int("n_estimators", n_estimators[0], n_estimators[1]), } - model = xgb.XGBRegressor(**param) + model = XGBoostModel(**param) model.fit( X_train, y_train, eval_set=[(X_valid, y_valid)], - early_stopping_rounds=optuna_params.get("early_stopping_rounds", 50), verbose=True, ) preds = model.predict(X_valid) diff --git a/pipeline_lib/implementation/tabular/xgboost/predict.py b/pipeline_lib/implementation/tabular/xgboost/predict.py index 2759685..8502f7a 100644 --- a/pipeline_lib/implementation/tabular/xgboost/predict.py +++ b/pipeline_lib/implementation/tabular/xgboost/predict.py @@ -44,4 +44,5 @@ def execute(self, data: DataContainer) -> DataContainer: data.model = self.model data.model_output = model_input data.target = self.target + data._drop_columns = self.drop_columns return data From 38279c0e00ee03c2e261fd09f3e5ab03ecb29005 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Mon, 25 Mar 2024 11:59:59 -0300 Subject: [PATCH 27/33] delete unnecesary steps --- pipeline_lib/core/pipeline.py | 2 - pipeline_lib/core/steps/target_scaling.py | 2 - .../tabular/xgboost/__init__.py | 3 +- .../tabular/xgboost/fit_model.py | 166 ------------------ .../implementation/tabular/xgboost/predict.py | 48 ----- 5 files changed, 1 insertion(+), 220 deletions(-) delete mode 100644 pipeline_lib/implementation/tabular/xgboost/fit_model.py delete mode 100644 pipeline_lib/implementation/tabular/xgboost/predict.py diff --git a/pipeline_lib/core/pipeline.py b/pipeline_lib/core/pipeline.py index 090e3ae..112293d 100644 --- a/pipeline_lib/core/pipeline.py +++ b/pipeline_lib/core/pipeline.py @@ -4,8 +4,6 @@ import logging from typing import Optional -from joblib import load - from pipeline_lib.core.data_container import DataContainer from pipeline_lib.core.model_registry import ModelRegistry from pipeline_lib.core.step_registry import StepRegistry diff --git a/pipeline_lib/core/steps/target_scaling.py b/pipeline_lib/core/steps/target_scaling.py index 36a1198..5be5b3c 100644 --- a/pipeline_lib/core/steps/target_scaling.py +++ b/pipeline_lib/core/steps/target_scaling.py @@ -1,5 +1,3 @@ -from typing import Optional - from pipeline_lib.core import DataContainer from pipeline_lib.core.steps.base import PipelineStep diff --git a/pipeline_lib/implementation/tabular/xgboost/__init__.py b/pipeline_lib/implementation/tabular/xgboost/__init__.py index 2887234..479c0b9 100644 --- a/pipeline_lib/implementation/tabular/xgboost/__init__.py +++ b/pipeline_lib/implementation/tabular/xgboost/__init__.py @@ -1,2 +1 @@ -from .fit_model import XGBoostFitModelStep # noqa: F401 -from .predict import XGBoostPredictStep # noqa: F401 +from .model import XGBoostModel # noqa: F401 diff --git a/pipeline_lib/implementation/tabular/xgboost/fit_model.py b/pipeline_lib/implementation/tabular/xgboost/fit_model.py deleted file mode 100644 index ed82260..0000000 --- a/pipeline_lib/implementation/tabular/xgboost/fit_model.py +++ /dev/null @@ -1,166 +0,0 @@ -import time -from typing import Optional - -import optuna -from joblib import dump -from optuna.pruners import MedianPruner -from sklearn.metrics import mean_absolute_error - -from pipeline_lib.core import DataContainer -from pipeline_lib.core.steps import FitModelStep - -from .model import XGBoostModel - - -class XGBoostFitModelStep(FitModelStep): - """Fit the model with XGBoost.""" - - def __init__( - self, - target: str, - drop_columns: Optional[list[str]] = None, - xgb_params: Optional[dict] = None, - optuna_params: Optional[dict] = None, - save_path: Optional[str] = None, - ) -> None: - self.init_logger() - - if target is None: - raise ValueError("Target column not found in the parameters.") - - self.target = target - self.drop_columns = drop_columns - - if optuna_params and xgb_params: - raise ValueError("Both optuna_params and xgb_params are defined. Please choose one.") - - if not optuna_params and not xgb_params: - raise ValueError( - "No parameters defined. Please define either optuna_params or xgb_params." - ) - - self.xgb_params = xgb_params - self.optuna_params = optuna_params - - if save_path: - if not save_path.endswith(".joblib"): - raise ValueError("Only joblib format is supported for saving the model.") - - self.save_path = save_path - - def execute(self, data: DataContainer) -> DataContainer: - self.logger.debug("Starting model fitting with XGBoost") - - start_time = time.time() - - df_train = data.train - df_valid = data.validation - - if self.drop_columns: - df_train = df_train.drop(columns=self.drop_columns) - df_valid = df_valid.drop(columns=self.drop_columns) - - # Prepare the data - X_train = df_train.drop(columns=[self.target]) - y_train = df_train[self.target] - - X_valid = df_valid.drop(columns=[self.target]) - y_valid = df_valid[self.target] - - params = self.xgb_params - - if self.optuna_params: - params = self.optimize_with_optuna( - X_train, y_train, X_valid, y_valid, self.optuna_params - ) - data.tuning_params = params - - model = XGBoostModel(**params) - - model.fit( - X_train, - y_train, - eval_set=[(X_valid, y_valid)], - verbose=True, - ) - - end_time = time.time() - elapsed_time = end_time - start_time - minutes = int(elapsed_time // 60) - seconds = int(elapsed_time % 60) - self.logger.info(f"XGBoost model fitting took {minutes} minutes and {seconds} seconds.") - - # Save the model to the data container - data.model = model - data.target = self.target - - if self.save_path: - self.logger.info(f"Saving the model to {self.save_path}") - dump(model, self.save_path) - return data - - def optimize_with_optuna(self, X_train, y_train, X_valid, y_valid, optuna_params): - def objective(trial): - # Define the search space - max_depth = optuna_params.get("max_depth", [3, 12]) - eta = optuna_params.get("eta", [1e-8, 1.0]) - subsample = optuna_params.get("subsample", [0.2, 1.0]) - colsample_bytree = optuna_params.get("colsample_bytree", [0.2, 1.0]) - min_child_weight = optuna_params.get("min_child_weight", [1, 10]) - n_estimators = optuna_params.get("n_estimators", [100, 1000]) - - param = { - "verbosity": 0, - "objective": "reg:squarederror", - "eval_metric": "mae", - "n_jobs": -1, - "max_depth": trial.suggest_int("max_depth", max_depth[0], max_depth[1]), - "eta": trial.suggest_float("eta", eta[0], eta[1], log=True), - "subsample": trial.suggest_float("subsample", subsample[0], subsample[1]), - "colsample_bytree": trial.suggest_float( - "colsample_bytree", colsample_bytree[0], colsample_bytree[1] - ), - "min_child_weight": trial.suggest_int( - "min_child_weight", min_child_weight[0], min_child_weight[1] - ), - "n_estimators": trial.suggest_int("n_estimators", n_estimators[0], n_estimators[1]), - } - - model = XGBoostModel(**param) - model.fit( - X_train, - y_train, - eval_set=[(X_valid, y_valid)], - verbose=True, - ) - preds = model.predict(X_valid) - mae = mean_absolute_error(y_valid, preds) - return mae - - def optuna_logging_callback(study, trial): - if trial.state == optuna.trial.TrialState.COMPLETE: - self.logger.info( - f"Trial {trial.number} finished with value: {trial.value} and parameters:" - f" {trial.params}. Best is trial {study.best_trial.number} with value:" - f" {study.best_value}." - ) - - optuna_trials = optuna_params.get("trials", 20) - - self.logger.info(f"Optimizing XGBoost hyperparameters with {optuna_trials} trials.") - - study_name = optuna_params.get("study_name", "xgboost_optimization") - storage = optuna_params.get("storage", "sqlite:///db.sqlite3") - - study = optuna.create_study( - direction="minimize", - study_name=study_name, - storage=storage, - pruner=MedianPruner(), - ) - - study.optimize(objective, n_trials=optuna_trials, callbacks=[optuna_logging_callback]) - - best_params = study.best_params - self.logger.info(f"Best parameters found by Optuna: {best_params}") - return best_params diff --git a/pipeline_lib/implementation/tabular/xgboost/predict.py b/pipeline_lib/implementation/tabular/xgboost/predict.py deleted file mode 100644 index 8502f7a..0000000 --- a/pipeline_lib/implementation/tabular/xgboost/predict.py +++ /dev/null @@ -1,48 +0,0 @@ -from typing import Optional - -import pandas as pd -from joblib import load - -from pipeline_lib.core import DataContainer -from pipeline_lib.core.steps import PredictStep - - -class XGBoostPredictStep(PredictStep): - """Obtain the predictions for XGBoost model.""" - - def __init__( - self, - target: str, - load_path: str, - drop_columns: Optional[list[str]] = None, - ) -> None: - self.init_logger() - - if not load_path.endswith(".joblib"): - raise ValueError("Only joblib format is supported for loading the model.") - - self.target = target - self.load_path = load_path - self.drop_columns = drop_columns - - self.model = load(self.load_path) - - def execute(self, data: DataContainer) -> DataContainer: - self.logger.debug("Obtaining predictions for XGBoost model.") - - model_input = data.flow - - if self.drop_columns: - self.logger.info(f"Dropping columns: {self.drop_columns}") - model_input = model_input.drop(columns=self.drop_columns) - - predictions = self.model.predict(model_input.drop(columns=[self.target])) - - predictions_df = pd.DataFrame(predictions, columns=["prediction"]) - - model_input["predictions"] = predictions_df - data.model = self.model - data.model_output = model_input - data.target = self.target - data._drop_columns = self.drop_columns - return data From c32b1abf06732f3d84b22c10d859496fdf920081 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Mon, 25 Mar 2024 12:03:40 -0300 Subject: [PATCH 28/33] refactor metrics --- pipeline_lib/core/steps/calculate_metrics.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pipeline_lib/core/steps/calculate_metrics.py b/pipeline_lib/core/steps/calculate_metrics.py index e246d26..c03d98c 100644 --- a/pipeline_lib/core/steps/calculate_metrics.py +++ b/pipeline_lib/core/steps/calculate_metrics.py @@ -19,15 +19,14 @@ def __init__(self, mape_threshold: float = 0.01) -> None: def execute(self, data: DataContainer) -> DataContainer: self.logger.debug("Starting metric calculation") - model_output = data.flow target_column_name = data.target if target_column_name is None: - raise ValueError("Target column not found on any configuration.") + raise ValueError("Target column nsot found on any configuration.") - true_values = model_output[target_column_name] - predictions = model_output["predictions"] + true_values = data.flow[target_column_name] + predictions = data.predictions mae = mean_absolute_error(true_values, predictions) rmse = np.sqrt(mean_squared_error(true_values, predictions)) From bc9a4dfdc34922aefa823016074ca715d0fff703 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Mon, 25 Mar 2024 15:11:50 -0300 Subject: [PATCH 29/33] improve tabular split and add calc train metrics --- pipeline_lib/core/data_container.py | 84 ++++--------------- pipeline_lib/core/model.py | 19 +++++ pipeline_lib/core/pipeline.py | 10 +-- .../core/steps/calculate_train_metrics.py | 83 ++++++++++++++++++ pipeline_lib/core/steps/fit_model.py | 5 +- pipeline_lib/core/steps/predict.py | 5 +- pipeline_lib/core/steps/tabular_split.py | 64 ++++++++++++-- 7 files changed, 185 insertions(+), 85 deletions(-) create mode 100644 pipeline_lib/core/steps/calculate_train_metrics.py diff --git a/pipeline_lib/core/data_container.py b/pipeline_lib/core/data_container.py index f0fb134..92ab07a 100644 --- a/pipeline_lib/core/data_container.py +++ b/pipeline_lib/core/data_container.py @@ -377,76 +377,52 @@ def validation(self, value: Any): self["validation"] = value @property - def model(self) -> Any: - """ - Get the model from the DataContainer. - - Returns - ------- - Any - The model stored in the DataContainer. - """ - return self["model"] - - @model.setter - def model(self, value: Any): - """ - Set the model in the DataContainer. - - Parameters - ---------- - value - The model to be stored in the DataContainer. - """ - self["model"] = value - - @property - def model_input(self) -> Any: + def test(self) -> Any: """ - Get the model input from the DataContainer. + Get the test data from the DataContainer. Returns ------- Any - The model input stored in the DataContainer. + The test data stored in the DataContainer. """ - return self["model_input"] + return self["test"] - @model_input.setter - def model_input(self, value: Any): + @test.setter + def test(self, value: Any): """ - Set the model input in the DataContainer. + Set the test data in the DataContainer. Parameters ---------- value - The model input to be stored in the DataContainer. + The test data to be stored in the DataContainer. """ - self["model_input"] = value + self["test"] = value @property - def model_output(self) -> Any: + def model(self) -> Any: """ - Get the model output from the DataContainer. + Get the model from the DataContainer. Returns ------- Any - The model output stored in the DataContainer. + The model stored in the DataContainer. """ - return self["model_output"] + return self["model"] - @model_output.setter - def model_output(self, value: Any): + @model.setter + def model(self, value: Any): """ - Set the model output in the DataContainer. + Set the model in the DataContainer. Parameters ---------- value - The model output to be stored in the DataContainer. + The model to be stored in the DataContainer. """ - self["model_output"] = value + self["model"] = value @property def metrics(self) -> Any: @@ -568,30 +544,6 @@ def target(self, value: Any): """ self["target"] = value - @property - def features(self) -> Any: - """ - Get the features from the DataContainer. - - Returns - ------- - Any - The features stored in the DataContainer. - """ - return self["features"] - - @features.setter - def features(self, value: Any): - """ - Set the features in the DataContainer. - - Parameters - ---------- - value - The features to be stored in the DataContainer. - """ - self["features"] = value - @property def flow(self) -> Any: """ diff --git a/pipeline_lib/core/model.py b/pipeline_lib/core/model.py index 36b50cd..1655395 100644 --- a/pipeline_lib/core/model.py +++ b/pipeline_lib/core/model.py @@ -1,6 +1,8 @@ from abc import ABC, abstractmethod +from pathlib import Path from typing import List, Optional, Tuple +import joblib import pandas as pd @@ -20,3 +22,20 @@ def fit( @abstractmethod def predict(self, X: pd.DataFrame) -> pd.Series: """Abstract method for making predictions.""" + + def save(self, path: str) -> None: + """Save the model.""" + if not path.endswith(".joblib"): + raise ValueError("The path must end with .joblib") + joblib.dump(self, path) + + @classmethod + def from_file(cls, path: str) -> "Model": + """Load the model from a .joblib file.""" + if not Path(path).exists(): + raise FileNotFoundError(f"File not found: {path}") + + if not path.endswith(".joblib"): + raise ValueError("The path must end with .joblib") + + return joblib.load(path) diff --git a/pipeline_lib/core/pipeline.py b/pipeline_lib/core/pipeline.py index 112293d..2d6bcf8 100644 --- a/pipeline_lib/core/pipeline.py +++ b/pipeline_lib/core/pipeline.py @@ -37,8 +37,10 @@ def run(self, is_train: bool) -> DataContainer: if is_train: steps_to_run = [step for step in self.steps if step.used_for_training] + self.logger.info("Training the pipeline") else: steps_to_run = [step for step in self.steps if step.used_for_prediction] + self.logger.info("Predicting with the pipeline") for i, step in enumerate(steps_to_run): Pipeline.logger.info( @@ -53,17 +55,11 @@ def run(self, is_train: bool) -> DataContainer: def train(self) -> DataContainer: """Run the pipeline on the given data.""" - self.logger.info("Training the pipeline") return self.run(is_train=True) def predict(self) -> DataContainer: """Run the pipeline on the given data.""" - self.logger.info("Predicting with the pipeline") - data = self.run(is_train=False) - data.predictions = data.model.predict(data.flow) - self.logger.info("Predictions:") - self.logger.info(data.predictions) - return data + return self.run(is_train=False) @classmethod def from_json(cls, path: str) -> Pipeline: diff --git a/pipeline_lib/core/steps/calculate_train_metrics.py b/pipeline_lib/core/steps/calculate_train_metrics.py new file mode 100644 index 0000000..7329830 --- /dev/null +++ b/pipeline_lib/core/steps/calculate_train_metrics.py @@ -0,0 +1,83 @@ +import json +import time +from typing import List, Optional + +import numpy as np +import pandas as pd +from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score + +from pipeline_lib.core import DataContainer +from pipeline_lib.core.model import Model +from pipeline_lib.core.steps.base import PipelineStep + + +class CalculateTrainMetricsStep(PipelineStep): + """Calculate metrics.""" + + used_for_prediction = False + used_for_training = True + + def __init__(self) -> None: + """Initialize CalculateMetricsStep.""" + super().__init__() + self.init_logger() + + def _calculate_metrics(self, true_values: pd.Series, predictions: pd.Series) -> dict: + return { + "MAE": str(mean_absolute_error(true_values, predictions)), + "RMSE": str(np.sqrt(mean_squared_error(true_values, predictions))), + "R^2": str(r2_score(true_values, predictions)), + "Mean Error": str(np.mean(true_values - predictions)), + "Max Error": str(np.max(np.abs(true_values - predictions))), + "Median Absolute Error": str(np.median(np.abs(true_values - predictions))), + } + + def _get_predictions( + self, model: Model, df: pd.DataFrame, target: str, drop_columns: Optional[List[str]] = None + ) -> pd.Series: + drop_columns = (drop_columns or []) + [target] + return model.predict(df.drop(columns=drop_columns)) + + def _log_metrics(self, dataset_name: str, metrics: dict) -> None: + self.logger.info(f"Metrics for {dataset_name} dataset:") + for metric, value in metrics.items(): + self.logger.info(f"{metric}: {value}") + + def execute(self, data: DataContainer) -> DataContainer: + self.logger.debug("Starting metric calculation") + + target_column_name = data.target + if target_column_name is None: + raise ValueError("Target column not found on any configuration.") + + metrics = {} + + for dataset_name in ["train", "validation", "test"]: + start_time = time.time() + dataset = getattr(data, dataset_name, None) + + if dataset is None: + self.logger.warning( + f"Dataset '{dataset_name}' not found. Skipping metric calculation." + ) + continue + + predictions = self._get_predictions( + model=data.model, + df=dataset, + target=target_column_name, + drop_columns=data._drop_columns, + ) + metrics[dataset_name] = self._calculate_metrics( + true_values=dataset[target_column_name], + predictions=predictions, + ) + elapsed_time = time.time() - start_time + self.logger.info(f"Elapsed time for {dataset_name} dataset: {elapsed_time:.2f} seconds") + + # pretty print metrics + self.logger.info(f"Metrics: {json.dumps(metrics, indent=4)}") + + data.metrics = metrics + + return data diff --git a/pipeline_lib/core/steps/fit_model.py b/pipeline_lib/core/steps/fit_model.py index 9f2e7a6..2fcde63 100644 --- a/pipeline_lib/core/steps/fit_model.py +++ b/pipeline_lib/core/steps/fit_model.py @@ -1,7 +1,6 @@ from typing import Optional, Type import optuna -from joblib import dump from sklearn.metrics import mean_absolute_error from pipeline_lib.core import DataContainer @@ -70,11 +69,11 @@ def execute(self, data: DataContainer) -> DataContainer: data.model = self.model data.target = self.target - data.model_path = self.save_path + data._drop_columns = self.drop_columns if self.save_path: self.logger.info(f"Saving the model to {self.save_path}") - dump(self.model, self.save_path) + self.model.save(self.save_path) return data diff --git a/pipeline_lib/core/steps/predict.py b/pipeline_lib/core/steps/predict.py index 4025649..98d1ed3 100644 --- a/pipeline_lib/core/steps/predict.py +++ b/pipeline_lib/core/steps/predict.py @@ -1,8 +1,7 @@ from typing import List, Optional -from joblib import load - from pipeline_lib.core import DataContainer +from pipeline_lib.core.model import Model from pipeline_lib.core.steps.base import PipelineStep @@ -22,7 +21,7 @@ def __init__( super().__init__() self.init_logger() self.load_path = load_path - self.model = load(self.load_path) + self.model = Model.from_file(load_path) self.target = target self.drop_columns = drop_columns or [] diff --git a/pipeline_lib/core/steps/tabular_split.py b/pipeline_lib/core/steps/tabular_split.py index f17b811..fc22ef4 100644 --- a/pipeline_lib/core/steps/tabular_split.py +++ b/pipeline_lib/core/steps/tabular_split.py @@ -1,3 +1,5 @@ +from typing import Optional + from sklearn.model_selection import train_test_split from pipeline_lib.core import DataContainer @@ -10,27 +12,71 @@ class TabularSplitStep(PipelineStep): used_for_prediction = False used_for_training = True - def __init__(self, train_percentage: float) -> None: + def __init__( + self, + train_percentage: float, + validation_percentage: Optional[float] = None, + test_percentage: Optional[float] = None, + ) -> None: """Initialize SplitStep.""" self.init_logger() self.train_percentage = train_percentage + self.validation_percentage = validation_percentage + self.test_percentage = test_percentage if self.train_percentage <= 0 or self.train_percentage >= 1: raise ValueError("train_percentage must be between 0 and 1.") + if self.validation_percentage is not None: + if self.validation_percentage <= 0 or self.validation_percentage >= 1: + raise ValueError("validation_percentage must be between 0 and 1.") + if self.test_percentage is None: + if self.train_percentage + self.validation_percentage != 1: + raise ValueError( + "The sum of train_percentage and validation_percentage must equal 1 when" + " test_percentage is not specified." + ) + else: + if self.train_percentage + self.validation_percentage + self.test_percentage != 1: + raise ValueError( + "The sum of train_percentage, validation_percentage, and test_percentage" + " must equal 1." + ) + + if self.test_percentage is not None: + if self.test_percentage <= 0 or self.test_percentage >= 1: + raise ValueError("test_percentage must be between 0 and 1.") + if self.validation_percentage is None: + raise ValueError( + "validation_percentage must be provided when test_percentage is specified." + ) + def execute(self, data: DataContainer) -> DataContainer: - """Execute the random train-validation split.""" + """Execute the random train-validation-test split.""" self.logger.info("Splitting tabular data...") df = data.flow - train_df, validation_df = train_test_split( - df, train_size=self.train_percentage, random_state=42 - ) + if self.test_percentage is not None: + train_val_df, test_df = train_test_split( + df, test_size=self.test_percentage, random_state=42 + ) + train_df, validation_df = train_test_split( + train_val_df, + train_size=self.train_percentage + / (self.train_percentage + self.validation_percentage), + random_state=42, + ) + else: + train_df, validation_df = train_test_split( + df, train_size=self.train_percentage, random_state=42 + ) + test_df = None train_rows = len(train_df) validation_rows = len(validation_df) - total_rows = train_rows + validation_rows + test_rows = len(test_df) if test_df is not None else 0 + total_rows = train_rows + validation_rows + test_rows self.logger.info( f"Number of rows in training set: {train_rows} | {train_rows/total_rows:.2%}" @@ -39,8 +85,14 @@ def execute(self, data: DataContainer) -> DataContainer: f"Number of rows in validation set: {validation_rows} |" f" {validation_rows/total_rows:.2%}" ) + if test_df is not None: + self.logger.info( + f"Number of rows in test set: {test_rows} | {test_rows/total_rows:.2%}" + ) data.train = train_df data.validation = validation_df + if test_df is not None: + data.test = test_df return data From 58bca73a107c75849e0c5f9c9c1107308fe8f272 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Mon, 25 Mar 2024 18:14:29 -0300 Subject: [PATCH 30/33] save runs & metrics to folder --- pipeline_lib/core/pipeline.py | 44 +++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/pipeline_lib/core/pipeline.py b/pipeline_lib/core/pipeline.py index 2d6bcf8..23809f4 100644 --- a/pipeline_lib/core/pipeline.py +++ b/pipeline_lib/core/pipeline.py @@ -2,7 +2,9 @@ import json import logging -from typing import Optional +import os +from datetime import datetime +from typing import Any, Optional from pipeline_lib.core.data_container import DataContainer from pipeline_lib.core.model_registry import ModelRegistry @@ -24,12 +26,13 @@ def __init__(self, initial_data: Optional[DataContainer] = None): self.save_path = None self.load_path = None self.model_path = None + self.config = None def add_steps(self, steps: list[PipelineStep]): """Add steps to the pipeline.""" self.steps.extend(steps) - def run(self, is_train: bool) -> DataContainer: + def run(self, is_train: bool, save: bool = True) -> DataContainer: """Run the pipeline on the given data.""" data = DataContainer.from_pickle(self.load_path) if self.load_path else DataContainer() @@ -48,8 +51,8 @@ def run(self, is_train: bool) -> DataContainer: ) data = step.execute(data) - if self.save_path: - data.save(self.save_path) + if save: + self.save_run(data) return data @@ -79,6 +82,7 @@ def from_json(cls, path: str) -> Pipeline: pipeline.load_path = config.get("load_path") pipeline.save_path = config.get("save_path") + pipeline.config = config steps = [] @@ -116,6 +120,31 @@ def from_json(cls, path: str) -> Pipeline: pipeline.add_steps(steps) return pipeline + def save_run( + self, + data: DataContainer, + parent_folder: str = "runs", + logs: Optional[logging.LogRecord] = None, + ) -> None: + """Save the pipeline run.""" + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + folder_name = f"{self.__class__.__name__}_{timestamp}" + run_folder = os.path.join(parent_folder, folder_name) + + # Create the run folder + os.makedirs(run_folder, exist_ok=True) + + # Save the JSON configuration + with open(os.path.join(run_folder, "pipeline_config.json"), "w") as f: + json.dump(self.config, f, indent=4, cls=CustomJSONEncoder) + + # Save the training metrics + if data.metrics: + with open(os.path.join(run_folder, "metrics.json"), "w") as f: + json.dump(data.metrics, f, indent=4) + + self.logger.info(f"Pipeline run saved to {run_folder}") + def __str__(self) -> str: step_names = [f"{i + 1}. {step.__class__.__name__}" for i, step in enumerate(self.steps)] return f"{self.__class__.__name__} with steps:\n" + "\n".join(step_names) @@ -124,3 +153,10 @@ def __repr__(self) -> str: """Return an unambiguous string representation of the pipeline.""" step_names = [f"{step.__class__.__name__}()" for step in self.steps] return f"{self.__class__.__name__}({', '.join(step_names)})" + + +class CustomJSONEncoder(json.JSONEncoder): + def default(self, obj: Any) -> Any: + if isinstance(obj, type): + return obj.__name__ + return super().default(obj) From 6276cc5cc2586e43c8c27521c05afd805bece699 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Tue, 26 Mar 2024 17:13:48 -0300 Subject: [PATCH 31/33] update readme --- README.md | 102 +++++++++++++----------------------------------------- 1 file changed, 24 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index 1066d54..43e8fde 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ To install the Pipeline Library, you need to have Python 3.9 or higher and Poetr Here's an example of how to use the library to run an XGBoost pipeline: -1. Create a `train.json` file with the following content: +1. Create a `config.json` file with the following content: ```json @@ -63,14 +63,16 @@ Here's an example of how to use the library to run an XGBoost pipeline: { "step_type": "GenerateStep", "parameters": { - "path": "examples/ocf/data/trainset_new.parquet" + "train_path": "examples/ocf/data/trainset_forecast.parquet", + "predict_path": "examples/ocf/data/testset_forecast.parquet" } }, { "step_type": "CleanStep", "parameters": { "drop_na_columns": [ - "average_power_kw" + "average_power_kw", + "diffuse_radiation" ], "drop_ids": { "ss_id": [ @@ -102,118 +104,62 @@ Here's an example of how to use the library to run an XGBoost pipeline: } }, { - "step_type": "XGBoostFitModelStep", + "step_type": "FitModelStep", "parameters": { + "model_class": "XGBoostModel", "target": "average_power_kw", "drop_columns": [ "ss_id", "operational_at", "total_energy_kwh" ], - "xgb_params": { + "model_params": { "max_depth": 12, "eta": 0.12410097733370863, "objective": "reg:squarederror", "eval_metric": "mae", "n_jobs": -1, - "n_estimators": 672, + "n_estimators": 40, "min_child_weight": 7, "subsample": 0.8057743223537057, "colsample_bytree": 0.6316852278944352, "early_stopping_rounds": 10 }, - "save_path": "model.joblib" + "save_path": "model_forecast.joblib" } + }, + { + "step_type": "PredictStep", + "parameters": {} + }, + { + "step_type": "CalculateTrainMetricsStep", + "parameters": {} } ] } } ``` -2. Run the pipeline using the following code: +2. Run the pipeline in train mode using the following code: ```python import logging -from pipeline_lib.core import Pipeline +from pipeline_lib import Pipeline logging.basicConfig(level=logging.INFO) -Pipeline.from_json("train.json").run() +data = Pipeline.from_json("config.json").run(is_train=True) ``` -3. Create a `predict.json` file with the pipeline configuration for prediction: - -```json -{ - "pipeline": { - "name": "XGBoostPredictionPipeline", - "description": "Prediction pipeline for XGBoost models.", - "steps": [ - { - "step_type": "GenerateStep", - "parameters": { - "path": "examples/ocf/data/testset_new.parquet" - } - }, - { - "step_type": "CleanStep", - "parameters": { - "drop_na_columns": [ - "average_power_kw" - ] - } - }, - { - "step_type": "CalculateFeaturesStep", - "parameters": { - "datetime_columns": [ - "date" - ], - "features": [ - "year", - "month", - "day", - "hour", - "minute" - ] - } - }, - { - "step_type": "XGBoostPredictStep", - "parameters": { - "target": "average_power_kw", - "drop_columns": [ - "ss_id", - "operational_at", - "total_energy_kwh" - ], - "load_path": "model.joblib" - } - }, - { - "step_type": "CalculateMetricsStep", - "parameters": {} - }, - { - "step_type": "ExplainerDashboardStep", - "parameters": { - "max_samples": 1000 - } - } - ] - } -} -``` - -4. Run the prediction pipeline: +3. To run it in prediction mode you have to set `is_train` to `False`. ```python -Pipeline.from_json("predict.json").run() +data = Pipeline.from_json("config.json").run(is_train=False) ``` The library allows users to define custom steps for data generation, cleaning, and preprocessing, which can be seamlessly integrated into the pipeline. ## Contributing - -Contributions to the Pipeline Library are welcome! If you encounter any issues, have suggestions for improvements, or want to add new features, please open an issue or submit a pull request on the GitHub repository. \ No newline at end of file +Contributions to the Pipeline Library are welcome! If you encounter any issues, have suggestions for improvements, or want to add new features, please open an issue or submit a pull request on the GitHub repository. From 556ef5997ae693da9d94045163545269437890a1 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Tue, 26 Mar 2024 17:30:38 -0300 Subject: [PATCH 32/33] refactor clean step --- pipeline_lib/core/steps/clean.py | 170 +++++++++++++++++-------------- 1 file changed, 92 insertions(+), 78 deletions(-) diff --git a/pipeline_lib/core/steps/clean.py b/pipeline_lib/core/steps/clean.py index 3c094f4..9c31ef4 100644 --- a/pipeline_lib/core/steps/clean.py +++ b/pipeline_lib/core/steps/clean.py @@ -28,93 +28,107 @@ def execute(self, data: DataContainer) -> DataContainer: df = data.raw - if self.fill_missing: - for column, fill_value in self.fill_missing.items(): - if column in df.columns: - df[column].fillna(fill_value, inplace=True) - self.logger.info( - f"Filled missing values in column '{column}' with {fill_value}" - ) - else: - self.logger.warning(f"Column '{column}' not found in the DataFrame") - if self.remove_outliers: - for column, method in self.remove_outliers.items(): - if column in df.columns: - if method == "clip": - q1 = df[column].quantile(0.25) - q3 = df[column].quantile(0.75) - iqr = q3 - q1 - lower_bound = q1 - (1.5 * iqr) - upper_bound = q3 + (1.5 * iqr) - df[column] = df[column].clip(lower=lower_bound, upper=upper_bound) - self.logger.info(f"Clipped outliers in column '{column}'") - elif method == "drop": - q1 = df[column].quantile(0.25) - q3 = df[column].quantile(0.75) - iqr = q3 - q1 - lower_bound = q1 - (1.5 * iqr) - upper_bound = q3 + (1.5 * iqr) - outliers = (df[column] < lower_bound) | (df[column] > upper_bound) - df = df[~outliers] - self.logger.info(f"Dropped outliers in column '{column}'") - else: - self.logger.warning(f"Unsupported outlier removal method '{method}'") - else: - self.logger.warning(f"Column '{column}' not found in the DataFrame") + df = self._remove_outliers(df) + + if self.fill_missing: + df = self._fill_missing(df) if self.convert_dtypes: - for column, dtype in self.convert_dtypes.items(): - if column in df.columns: - df[column] = df[column].astype(dtype) - self.logger.info(f"Converted column '{column}' to {dtype}") - else: - self.logger.warning(f"Column '{column}' not found in the DataFrame") + df = self._convert_dtypes(df) if self.drop_na_columns: - for column in self.drop_na_columns: - if column in df.columns: - initial_rows = len(df) - df.dropna(subset=[column], inplace=True) - dropped_rows = initial_rows - len(df) - self.logger.info( - f"Dropped {dropped_rows} rows with None values in column '{column}'" - ) - else: - self.logger.warning(f"Column '{column}' not found in the DataFrame") + df = self._drop_na_columns(df) if self.drop_ids: - for column, ids in self.drop_ids.items(): - if column in df.columns: - initial_rows = len(df) - initial_ids = set(df[column].unique()) - - dropped_ids = set(ids) & initial_ids - not_found_ids = set(ids) - initial_ids - - if dropped_ids: - df = df.loc[~df[column].isin(dropped_ids)].copy() - dropped_rows = initial_rows - len(df) - percentage_dropped = ( - dropped_rows / initial_rows - ) * 100 # Calculate the percentage of rows dropped - self.logger.info( - f"Dropped {dropped_rows} rows ({percentage_dropped:.2f}%) with IDs" - f" {list(dropped_ids)} in column '{column}'" - ) - else: - self.logger.info( - f"No rows dropped for IDs {list(ids)} in column '{column}'" - ) - - if not_found_ids: - self.logger.warning( - f"IDs {list(not_found_ids)} not found in column '{column}'" - ) - else: - self.logger.warning(f"Column '{column}' not found in the DataFrame") + df = self._drop_ids(df) data.clean = df data.flow = df return data + + def _remove_outliers(self, df): + for column, method in self.remove_outliers.items(): + if column in df.columns: + if method == "clip": + q1 = df[column].quantile(0.25) + q3 = df[column].quantile(0.75) + iqr = q3 - q1 + lower_bound = q1 - (1.5 * iqr) + upper_bound = q3 + (1.5 * iqr) + df[column] = df[column].clip(lower=lower_bound, upper=upper_bound) + self.logger.info(f"Clipped outliers in column '{column}'") + elif method == "drop": + q1 = df[column].quantile(0.25) + q3 = df[column].quantile(0.75) + iqr = q3 - q1 + lower_bound = q1 - (1.5 * iqr) + upper_bound = q3 + (1.5 * iqr) + outliers = (df[column] < lower_bound) | (df[column] > upper_bound) + df = df[~outliers] + self.logger.info(f"Dropped outliers in column '{column}'") + else: + self.logger.warning(f"Unsupported outlier removal method '{method}'") + else: + self.logger.warning(f"Column '{column}' not found in the DataFrame") + return df + + def _fill_missing(self, df): + for column, fill_value in self.fill_missing.items(): + if column in df.columns: + df[column].fillna(fill_value, inplace=True) + self.logger.info(f"Filled missing values in column '{column}' with {fill_value}") + else: + self.logger.warning(f"Column '{column}' not found in the DataFrame") + return df + + def _convert_dtypes(self, df): + for column, dtype in self.convert_dtypes.items(): + if column in df.columns: + df[column] = df[column].astype(dtype) + self.logger.info(f"Converted column '{column}' to {dtype}") + else: + self.logger.warning(f"Column '{column}' not found in the DataFrame") + return df + + def _drop_na_columns(self, df): + for column in self.drop_na_columns: + if column in df.columns: + initial_rows = len(df) + df.dropna(subset=[column], inplace=True) + dropped_rows = initial_rows - len(df) + self.logger.info( + f"Dropped {dropped_rows} rows with None values in column '{column}'" + ) + else: + self.logger.warning(f"Column '{column}' not found in the DataFrame") + return df + + def _drop_ids(self, df): + for column, ids in self.drop_ids.items(): + if column in df.columns: + initial_rows = len(df) + initial_ids = set(df[column].unique()) + + dropped_ids = set(ids) & initial_ids + not_found_ids = set(ids) - initial_ids + + if dropped_ids: + df = df.loc[~df[column].isin(dropped_ids)].copy() + dropped_rows = initial_rows - len(df) + percentage_dropped = ( + dropped_rows / initial_rows + ) * 100 # Calculate the percentage of rows dropped + self.logger.info( + f"Dropped {dropped_rows} rows ({percentage_dropped:.2f}%) with IDs" + f" {list(dropped_ids)} in column '{column}'" + ) + else: + self.logger.info(f"No rows dropped for IDs {list(ids)} in column '{column}'") + + if not_found_ids: + self.logger.warning(f"IDs {list(not_found_ids)} not found in column '{column}'") + else: + self.logger.warning(f"Column '{column}' not found in the DataFrame") + return df From a049dee4ad29dcd453a1defa7099e41210080cc0 Mon Sep 17 00:00:00 2001 From: Diego Marvid Date: Tue, 26 Mar 2024 18:08:16 -0300 Subject: [PATCH 33/33] fix typehints in data container --- pipeline_lib/core/data_container.py | 83 +++++++++++++++-------------- 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/pipeline_lib/core/data_container.py b/pipeline_lib/core/data_container.py index 92ab07a..3b45ca5 100644 --- a/pipeline_lib/core/data_container.py +++ b/pipeline_lib/core/data_container.py @@ -6,10 +6,13 @@ import logging import pickle import sys -from typing import Any, Optional, Union +from typing import Any, List, Optional, Union +import pandas as pd import yaml +from pipeline_lib.core.model import Model + class DataContainer: """ @@ -280,19 +283,19 @@ def from_yaml(cls, file_path: str) -> DataContainer: return cls(initial_data=data) @property - def clean(self) -> Any: + def clean(self) -> pd.DataFrame: """ Get the clean data from the DataContainer. Returns ------- - Any + pd.DataFrame The clean data stored in the DataContainer. """ return self["clean"] @clean.setter - def clean(self, value: Any): + def clean(self, value: pd.DataFrame): """ Set the clean data in the DataContainer. @@ -305,19 +308,19 @@ def clean(self, value: Any): # create the same for raw @property - def raw(self) -> Any: + def raw(self) -> pd.DataFrame: """ Get the raw data from the DataContainer. Returns ------- - Any + pd.DataFrame The raw data stored in the DataContainer. """ return self["raw"] @raw.setter - def raw(self, value: Any): + def raw(self, value: pd.DataFrame): """ Set the raw data in the DataContainer. @@ -329,19 +332,19 @@ def raw(self, value: Any): self["raw"] = value @property - def train(self) -> Any: + def train(self) -> pd.DataFrame: """ Get the train data from the DataContainer. Returns ------- - Any + pd.DataFrame The train data stored in the DataContainer. """ return self["train"] @train.setter - def train(self, value: Any): + def train(self, value: pd.DataFrame): """ Set the train data in the DataContainer. @@ -353,19 +356,19 @@ def train(self, value: Any): self["train"] = value @property - def validation(self) -> Any: + def validation(self) -> pd.DataFrame: """ Get the validation data from the DataContainer. Returns ------- - Any + pd.DataFrame The validation data stored in the DataContainer. """ return self["validation"] @validation.setter - def validation(self, value: Any): + def validation(self, value: pd.DataFrame): """ Set the validation data in the DataContainer. @@ -377,19 +380,19 @@ def validation(self, value: Any): self["validation"] = value @property - def test(self) -> Any: + def test(self) -> pd.DataFrame: """ Get the test data from the DataContainer. Returns ------- - Any + pd.DataFrame The test data stored in the DataContainer. """ return self["test"] @test.setter - def test(self, value: Any): + def test(self, value: pd.DataFrame): """ Set the test data in the DataContainer. @@ -401,19 +404,19 @@ def test(self, value: Any): self["test"] = value @property - def model(self) -> Any: + def model(self) -> Model: """ Get the model from the DataContainer. Returns ------- - Any + Model The model stored in the DataContainer. """ return self["model"] @model.setter - def model(self, value: Any): + def model(self, value: Model): """ Set the model in the DataContainer. @@ -425,49 +428,49 @@ def model(self, value: Any): self["model"] = value @property - def metrics(self) -> Any: + def metrics(self) -> dict: """ Get the metrics from the DataContainer. Returns ------- - Any + dict The metrics stored in the DataContainer. """ return self["metrics"] @metrics.setter - def metrics(self, value: Any): + def metrics(self, value: dict): """ Set the metrics in the DataContainer. Parameters ---------- - value + dict The metrics to be stored in the DataContainer. """ self["metrics"] = value @property - def predictions(self) -> Any: + def predictions(self) -> pd.Series: """ Get the predictions from the DataContainer. Returns ------- - Any + pd.Series The predictions stored in the DataContainer. """ return self["predictions"] @predictions.setter - def predictions(self, value: Any): + def predictions(self, value: pd.Series): """ Set the predictions in the DataContainer. Parameters ---------- - value + pd.Series The predictions to be stored in the DataContainer. """ self["predictions"] = value @@ -497,43 +500,43 @@ def explainer(self, value: Any): self["explainer"] = value @property - def tuning_params(self) -> Any: + def tuning_params(self) -> dict: """ Get the tuning parameters from the DataContainer. Returns ------- - Any + dict The tuning parameters stored in the DataContainer. """ return self["tuning_params"] @tuning_params.setter - def tuning_params(self, value: Any): + def tuning_params(self, value: dict): """ Set the tuning parameters in the DataContainer. Parameters ---------- - value + dict The tuning parameters to be stored in the DataContainer. """ self["tuning_params"] = value @property - def target(self) -> Any: + def target(self) -> str: """ Get the target from the DataContainer. Returns ------- - Any + str The target stored in the DataContainer. """ return self["target"] @target.setter - def target(self, value: Any): + def target(self, value: str): """ Set the target in the DataContainer. @@ -545,19 +548,19 @@ def target(self, value: Any): self["target"] = value @property - def flow(self) -> Any: + def flow(self) -> pd.DataFrame: """ Get the flow from the DataContainer. Returns ------- - Any + pd.DataFrame The flow stored in the DataContainer. """ return self["flow"] @flow.setter - def flow(self, value: Any): + def flow(self, value: pd.DataFrame): """ Set the flow in the DataContainer. @@ -569,19 +572,19 @@ def flow(self, value: Any): self["flow"] = value @property - def _drop_columns(self) -> Any: + def _drop_columns(self) -> List[str]: """ Get the drop columns from the DataContainer. Returns ------- - Any + List[str] The drop columns stored in the DataContainer. """ return self["_drop_columns"] @_drop_columns.setter - def _drop_columns(self, value: Any): + def _drop_columns(self, value: List[str]): """ Set the drop columns in the DataContainer.