From 8acec1d5beab2829e53677a96f981f1bcd05bafa Mon Sep 17 00:00:00 2001 From: Tommy Odland Date: Thu, 14 Aug 2025 10:28:55 +0200 Subject: [PATCH 01/13] added example with IF modeling --- tests/test_modeling.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/test_modeling.py b/tests/test_modeling.py index fb13230..fa89c8f 100644 --- a/tests/test_modeling.py +++ b/tests/test_modeling.py @@ -132,6 +132,29 @@ def test_total_person_hours(self): np.testing.assert_allclose(sample_mean, expected_mean, rtol=0.02) np.testing.assert_allclose(sample_std, expected_std, rtol=0.02) + def test_conditional_if_statement(self): + """Suppose mens height has distribution N(176, 7.1). + What is the distribution of the difference between height of two men? + Caveat: there is a 10% chance that the two men are identical twins, + and in that case their height should be perfectly equal. + """ + + # Height of two random men + height1 = Distribution("norm", loc=176, scale=7.1) + height2 = Distribution("norm", loc=176, scale=7.1) + + # If they are twins, their height should be perfectly correlated + is_twin = Distribution("bernoulli", p=0.1) + + # height2 = IF(is_twin, height1, height2) + height2 = is_twin * height1 + (1 - is_twin) * height2 + + # This is the answer to the question + (abs(height2 - height1)).sample(999, random_state=42) + + # At least one of the realizations should be idential + assert np.any(np.isclose(height1.samples_, height2.samples_)) + def test_copying(): # Create a graph From 8d15c1d3a0f2fe63f6d481839fd9fba33fd186d9 Mon Sep 17 00:00:00 2001 From: Tommy Odland <10076072+tommyod@users.noreply.github.com> Date: Thu, 14 Aug 2025 10:33:31 +0200 Subject: [PATCH 02/13] Update tests/test_modeling.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/test_modeling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_modeling.py b/tests/test_modeling.py index fa89c8f..ef263e5 100644 --- a/tests/test_modeling.py +++ b/tests/test_modeling.py @@ -152,7 +152,7 @@ def test_conditional_if_statement(self): # This is the answer to the question (abs(height2 - height1)).sample(999, random_state=42) - # At least one of the realizations should be idential + # At least one of the realizations should be identical assert np.any(np.isclose(height1.samples_, height2.samples_)) From 2414994611eeed9e9c675b56d13aed149e3b0551 Mon Sep 17 00:00:00 2001 From: Tommy Odland Date: Tue, 19 Aug 2025 13:08:46 +0200 Subject: [PATCH 03/13] first draft without corr --- src/probabilit/config.yml | 20 +++++++++++ src/probabilit/read_yaml_create_samples.py | 40 ++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 src/probabilit/config.yml create mode 100644 src/probabilit/read_yaml_create_samples.py diff --git a/src/probabilit/config.yml b/src/probabilit/config.yml new file mode 100644 index 0000000..ee855aa --- /dev/null +++ b/src/probabilit/config.yml @@ -0,0 +1,20 @@ + +metadata: + name: "Comprehensive Project Risk Analysis" + description: "Complete risk analysis including direct costs, quality impacts, schedule effects, market variations, and risk adjustments" + samples: 9 + sampling_method: "latin_hypercube" + +variables: + # Direct Material and Equipment + Steel_Cost: + type: "norm" + parameters: + loc: 800000 + scale: 1200000 + + Maintenance_Cost: + type: "uniform" + parameters: + loc: 0 + scale: 1 \ No newline at end of file diff --git a/src/probabilit/read_yaml_create_samples.py b/src/probabilit/read_yaml_create_samples.py new file mode 100644 index 0000000..a13de2d --- /dev/null +++ b/src/probabilit/read_yaml_create_samples.py @@ -0,0 +1,40 @@ +from probabilit.modeling import Distribution, Add +import yaml +from yaml.loader import Loader +import pandas as pd + +file = "config.yml" + + +# Load data from file into a dictionary +with open(file, "r") as file_handle: + yaml_data = yaml.load(file_handle, Loader) + +# Load sections of the dictionary as variables +metadata = yaml_data["metadata"] +variables = yaml_data["variables"] + +# Convert dict of {name:data, ...} to {name:Distribution, ...} +for variable_name in variables.keys(): + variable_data = variables[variable_name] + variables[variable_name] = Distribution(variable_data["type"], + **variable_data["parameters"]) + + +# Dummy expression to sample all parents +expression = Add(*variables.values()) +expression.sample(size=metadata["samples"]) + +# Collect all samples into a dataframe +df_samples = pd.DataFrame({name:distr.samples_ for (name, distr) in variables.items()}) + +print(df_samples) + + + + + + + + + \ No newline at end of file From 1138279115cd86fd2c4d40f3015c0e165f2405f7 Mon Sep 17 00:00:00 2001 From: Tommy Odland Date: Tue, 19 Aug 2025 13:21:42 +0200 Subject: [PATCH 04/13] added correlations --- src/probabilit/config.yml | 13 ++++- src/probabilit/read_yaml_create_samples.py | 60 +++++++++++++--------- 2 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/probabilit/config.yml b/src/probabilit/config.yml index ee855aa..9f4578d 100644 --- a/src/probabilit/config.yml +++ b/src/probabilit/config.yml @@ -17,4 +17,15 @@ variables: type: "uniform" parameters: loc: 0 - scale: 1 \ No newline at end of file + scale: 1 + + Height: + type: "norm" + parameters: + loc: 180 + scale: 10 + + +correlations: + - 0.2: [Steel_Cost, Maintenance_Cost] + - 0.5: [Height, Maintenance_Cost] \ No newline at end of file diff --git a/src/probabilit/read_yaml_create_samples.py b/src/probabilit/read_yaml_create_samples.py index a13de2d..67269d5 100644 --- a/src/probabilit/read_yaml_create_samples.py +++ b/src/probabilit/read_yaml_create_samples.py @@ -2,39 +2,51 @@ import yaml from yaml.loader import Loader import pandas as pd +import numpy as np +import scipy as sp -file = "config.yml" +if __name__ == "__main__": + file = "config.yml" + # Load data from file into a dictionary + with open(file, "r") as file_handle: + yaml_data = yaml.load(file_handle, Loader) -# Load data from file into a dictionary -with open(file, "r") as file_handle: - yaml_data = yaml.load(file_handle, Loader) - -# Load sections of the dictionary as variables -metadata = yaml_data["metadata"] -variables = yaml_data["variables"] + # Load sections of the dictionary as variables + metadata = yaml_data["metadata"] + variables = yaml_data["variables"] + correlations = yaml_data.get("correlations", []) -# Convert dict of {name:data, ...} to {name:Distribution, ...} -for variable_name in variables.keys(): - variable_data = variables[variable_name] - variables[variable_name] = Distribution(variable_data["type"], - **variable_data["parameters"]) - - -# Dummy expression to sample all parents -expression = Add(*variables.values()) -expression.sample(size=metadata["samples"]) + # Convert dict of {name:data, ...} to {name:Distribution, ...} + for varname in variables.keys(): + vardata = variables[varname] + variables[varname] = Distribution(vardata["type"], **vardata["parameters"]) -# Collect all samples into a dataframe -df_samples = pd.DataFrame({name:distr.samples_ for (name, distr) in variables.items()}) + # Dummy expression to sample all parents + expression = Add(*variables.values()) -print(df_samples) + # Add every correlation pair + # NOTE if we define correlations between (a, b) and (c, d) + # then the non-specified correlations (e.g. (a, c)) will be optimized + # towards zero. + for correlation in correlations: + value, (varname1, varname2) = next(iter(correlation.items())) + # Prepare input + corr_mat = sp.linalg.circulant([1.0, value]) + var1, var2 = variables[varname1], variables[varname2] + # Add correlation pair + expression.correlate(var1, var2, corr_mat=corr_mat) + # Samle the expression, which populates `.samples_` on Distributions + expression.sample(size=metadata["samples"]) + # Collect all samples into a dataframe + df_samples = pd.DataFrame( + {name: distr.samples_ for (name, distr) in variables.items()} + ) + print(df_samples) - - - \ No newline at end of file + print(df_samples.corr()) From f479281c40318b7f38f56dcba5483279aec767ad Mon Sep 17 00:00:00 2001 From: Tommy Odland Date: Tue, 19 Aug 2025 13:31:26 +0200 Subject: [PATCH 05/13] non-scipy distrs --- src/probabilit/config.yml | 7 +++++++ src/probabilit/read_yaml_create_samples.py | 24 +++++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/probabilit/config.yml b/src/probabilit/config.yml index 9f4578d..34f7cec 100644 --- a/src/probabilit/config.yml +++ b/src/probabilit/config.yml @@ -25,6 +25,13 @@ variables: loc: 180 scale: 10 + Children: + type: "EmpiricalDistribution" + parameters: + data: [1, 3, 2, 4, 5, 2, 3, 4] + method: "closest_observation" + + correlations: - 0.2: [Steel_Cost, Maintenance_Cost] diff --git a/src/probabilit/read_yaml_create_samples.py b/src/probabilit/read_yaml_create_samples.py index 67269d5..7215e1f 100644 --- a/src/probabilit/read_yaml_create_samples.py +++ b/src/probabilit/read_yaml_create_samples.py @@ -1,10 +1,19 @@ -from probabilit.modeling import Distribution, Add +from probabilit.modeling import ( + Distribution, + Add, + EmpiricalDistribution, + CumulativeDistribution, +) import yaml from yaml.loader import Loader import pandas as pd -import numpy as np import scipy as sp +TYPE_MAPPING = { + "empiricaldistribution": EmpiricalDistribution, + "cumulativedistribution": CumulativeDistribution, +} + if __name__ == "__main__": file = "config.yml" @@ -20,7 +29,16 @@ # Convert dict of {name:data, ...} to {name:Distribution, ...} for varname in variables.keys(): vardata = variables[varname] - variables[varname] = Distribution(vardata["type"], **vardata["parameters"]) + var_type = vardata["type"].lower() + + # See if variable matches one of the non-scipy distributions first + if var_type in TYPE_MAPPING.keys(): + var_class = TYPE_MAPPING[var_type] + variables[varname] = var_class(**vardata["parameters"]) + continue + + # Not a non-scipy distribution, so try scipy next + variables[varname] = Distribution(var_type, **vardata["parameters"]) # Dummy expression to sample all parents expression = Add(*variables.values()) From 4ec53610a4b024cf93bea3ef5c1e9238152eb384 Mon Sep 17 00:00:00 2001 From: Tommy Odland Date: Tue, 19 Aug 2025 13:37:36 +0200 Subject: [PATCH 06/13] remove 'parameters', add seed and sampling method --- src/probabilit/config.yml | 25 ++++++++++------------ src/probabilit/read_yaml_create_samples.py | 20 +++++++++++++---- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/probabilit/config.yml b/src/probabilit/config.yml index 34f7cec..fe659b4 100644 --- a/src/probabilit/config.yml +++ b/src/probabilit/config.yml @@ -2,34 +2,31 @@ metadata: name: "Comprehensive Project Risk Analysis" description: "Complete risk analysis including direct costs, quality impacts, schedule effects, market variations, and risk adjustments" - samples: 9 - sampling_method: "latin_hypercube" + samples: 999 + sampling_method: "lhs" + seed: 42 variables: # Direct Material and Equipment Steel_Cost: type: "norm" - parameters: - loc: 800000 - scale: 1200000 + loc: 800000 + scale: 1200000 Maintenance_Cost: type: "uniform" - parameters: - loc: 0 - scale: 1 + loc: 0 + scale: 1 Height: type: "norm" - parameters: - loc: 180 - scale: 10 + loc: 180 + scale: 10 Children: type: "EmpiricalDistribution" - parameters: - data: [1, 3, 2, 4, 5, 2, 3, 4] - method: "closest_observation" + data: [1, 3, 2, 4, 5, 2, 3, 4] + method: "closest_observation" diff --git a/src/probabilit/read_yaml_create_samples.py b/src/probabilit/read_yaml_create_samples.py index 7215e1f..78758f6 100644 --- a/src/probabilit/read_yaml_create_samples.py +++ b/src/probabilit/read_yaml_create_samples.py @@ -17,6 +17,8 @@ if __name__ == "__main__": file = "config.yml" + # =================== LOAD =================== + # Load data from file into a dictionary with open(file, "r") as file_handle: yaml_data = yaml.load(file_handle, Loader) @@ -26,19 +28,23 @@ variables = yaml_data["variables"] correlations = yaml_data.get("correlations", []) + # =================== CONVERT =================== + # Convert dict of {name:data, ...} to {name:Distribution, ...} for varname in variables.keys(): vardata = variables[varname] - var_type = vardata["type"].lower() + var_type = vardata.pop("type").lower() # See if variable matches one of the non-scipy distributions first if var_type in TYPE_MAPPING.keys(): var_class = TYPE_MAPPING[var_type] - variables[varname] = var_class(**vardata["parameters"]) + variables[varname] = var_class(**vardata) continue # Not a non-scipy distribution, so try scipy next - variables[varname] = Distribution(var_type, **vardata["parameters"]) + variables[varname] = Distribution(var_type, **vardata) + + # =================== CORRELATIONS =================== # Dummy expression to sample all parents expression = Add(*variables.values()) @@ -57,8 +63,14 @@ # Add correlation pair expression.correlate(var1, var2, corr_mat=corr_mat) + # =================== SAMPLE =================== + # Samle the expression, which populates `.samples_` on Distributions - expression.sample(size=metadata["samples"]) + expression.sample( + size=metadata["samples"], + random_state=metadata["seed"], + method=metadata["sampling_method"], + ) # Collect all samples into a dataframe df_samples = pd.DataFrame( From a67b1ddf1f5d24bb2e87f10d9722a8b306227288 Mon Sep 17 00:00:00 2001 From: Tommy Odland Date: Tue, 19 Aug 2025 13:52:38 +0200 Subject: [PATCH 07/13] plotting :) --- src/probabilit/config.yml | 9 ++++++++- src/probabilit/read_yaml_create_samples.py | 12 +++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/probabilit/config.yml b/src/probabilit/config.yml index fe659b4..60fcfec 100644 --- a/src/probabilit/config.yml +++ b/src/probabilit/config.yml @@ -4,6 +4,7 @@ metadata: description: "Complete risk analysis including direct costs, quality impacts, schedule effects, market variations, and risk adjustments" samples: 999 sampling_method: "lhs" + # correlation_method: "" seed: 42 variables: @@ -32,4 +33,10 @@ variables: correlations: - 0.2: [Steel_Cost, Maintenance_Cost] - - 0.5: [Height, Maintenance_Cost] \ No newline at end of file + - 0.5: [Height, Maintenance_Cost] + + +plot: + - [Steel_Cost, Maintenance_Cost] + - Steel_Cost + - [Steel_Cost, Maintenance_Cost, Height, Children] \ No newline at end of file diff --git a/src/probabilit/read_yaml_create_samples.py b/src/probabilit/read_yaml_create_samples.py index 78758f6..9ffcc97 100644 --- a/src/probabilit/read_yaml_create_samples.py +++ b/src/probabilit/read_yaml_create_samples.py @@ -4,6 +4,7 @@ EmpiricalDistribution, CumulativeDistribution, ) +import seaborn import yaml from yaml.loader import Loader import pandas as pd @@ -27,6 +28,7 @@ metadata = yaml_data["metadata"] variables = yaml_data["variables"] correlations = yaml_data.get("correlations", []) + plots = yaml_data.get("plot", []) # =================== CONVERT =================== @@ -78,5 +80,13 @@ ) print(df_samples) - print(df_samples.corr()) + + # =================== PLOTS =================== + + for vars_plot in plots: + vars_plot = [vars_plot] if isinstance(vars_plot, str) else vars_plot + df = pd.DataFrame( + {var_plot: variables[var_plot].samples_ for var_plot in vars_plot}) + seaborn.pairplot(df) + From 124eed822058b9a3638da6387c36daf740b76fe3 Mon Sep 17 00:00:00 2001 From: Tommy Odland Date: Tue, 19 Aug 2025 13:55:07 +0200 Subject: [PATCH 08/13] prettier config --- src/probabilit/config.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/probabilit/config.yml b/src/probabilit/config.yml index 60fcfec..23b79c7 100644 --- a/src/probabilit/config.yml +++ b/src/probabilit/config.yml @@ -1,14 +1,12 @@ - metadata: - name: "Comprehensive Project Risk Analysis" - description: "Complete risk analysis including direct costs, quality impacts, schedule effects, market variations, and risk adjustments" + name: "Name for a test" + description: "Description for a test" samples: 999 sampling_method: "lhs" # correlation_method: "" seed: 42 variables: - # Direct Material and Equipment Steel_Cost: type: "norm" loc: 800000 @@ -28,14 +26,11 @@ variables: type: "EmpiricalDistribution" data: [1, 3, 2, 4, 5, 2, 3, 4] method: "closest_observation" - - - + correlations: - 0.2: [Steel_Cost, Maintenance_Cost] - 0.5: [Height, Maintenance_Cost] - - + plot: - [Steel_Cost, Maintenance_Cost] - Steel_Cost From 99b3bef0f2953cffd74a0c9b2e39809f3794b9ef Mon Sep 17 00:00:00 2001 From: Tommy Odland Date: Tue, 19 Aug 2025 13:57:41 +0200 Subject: [PATCH 09/13] ruff --- src/probabilit/read_yaml_create_samples.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/probabilit/read_yaml_create_samples.py b/src/probabilit/read_yaml_create_samples.py index 9ffcc97..b6e062f 100644 --- a/src/probabilit/read_yaml_create_samples.py +++ b/src/probabilit/read_yaml_create_samples.py @@ -81,12 +81,12 @@ print(df_samples) print(df_samples.corr()) - + # =================== PLOTS =================== - + for vars_plot in plots: vars_plot = [vars_plot] if isinstance(vars_plot, str) else vars_plot df = pd.DataFrame( - {var_plot: variables[var_plot].samples_ for var_plot in vars_plot}) + {var_plot: variables[var_plot].samples_ for var_plot in vars_plot} + ) seaborn.pairplot(df) - From 6e68da4f82d47d540e750fb2c5b0809d30071360 Mon Sep 17 00:00:00 2001 From: Tommy Odland Date: Wed, 20 Aug 2025 10:53:26 +0200 Subject: [PATCH 10/13] add derived variables --- src/probabilit/config.yml | 12 ++++++++++++ src/probabilit/modeling.py | 7 +++++++ src/probabilit/read_yaml_create_samples.py | 18 ++++++++++++++++-- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/probabilit/config.yml b/src/probabilit/config.yml index 23b79c7..984be47 100644 --- a/src/probabilit/config.yml +++ b/src/probabilit/config.yml @@ -26,10 +26,22 @@ variables: type: "EmpiricalDistribution" data: [1, 3, 2, 4, 5, 2, 3, 4] method: "closest_observation" + + Adult: + type: "DiscreteDistribution" + values: ["no", "yes"] + probabilities: [0.4, 0.6] correlations: - 0.2: [Steel_Cost, Maintenance_Cost] - 0.5: [Height, Maintenance_Cost] + +derived: + Adult: # Derived FROM this variable + TypicalAge: # The derived parameter + - "no": 16 # mapping: From -> To + - "yes": 32 + plot: - [Steel_Cost, Maintenance_Cost] diff --git a/src/probabilit/modeling.py b/src/probabilit/modeling.py index 6a946f5..576f94d 100644 --- a/src/probabilit/modeling.py +++ b/src/probabilit/modeling.py @@ -828,6 +828,13 @@ def _sample(self, size=None): return np.average(np.vstack(samples), axis=0) +class NoOp(VariadicTransform): + """Sample all ancestor variables, but do nothing else.""" + + def _sample(self, size=None): + tuple(parent.samples_ for parent in self.parents) + + class BinaryTransform(Transform): """Class for binary transforms, such as Divide, Power, Subtract, etc.""" diff --git a/src/probabilit/read_yaml_create_samples.py b/src/probabilit/read_yaml_create_samples.py index b6e062f..36e4c55 100644 --- a/src/probabilit/read_yaml_create_samples.py +++ b/src/probabilit/read_yaml_create_samples.py @@ -3,16 +3,21 @@ Add, EmpiricalDistribution, CumulativeDistribution, + DiscreteDistribution, + NoOp, ) import seaborn import yaml from yaml.loader import Loader import pandas as pd import scipy as sp +import operator +import functools TYPE_MAPPING = { "empiricaldistribution": EmpiricalDistribution, "cumulativedistribution": CumulativeDistribution, + "discretedistribution": DiscreteDistribution, } if __name__ == "__main__": @@ -29,6 +34,7 @@ variables = yaml_data["variables"] correlations = yaml_data.get("correlations", []) plots = yaml_data.get("plot", []) + derived = yaml_data.get("derived", []) # =================== CONVERT =================== @@ -49,7 +55,7 @@ # =================== CORRELATIONS =================== # Dummy expression to sample all parents - expression = Add(*variables.values()) + expression = NoOp(*variables.values()) # Add every correlation pair # NOTE if we define correlations between (a, b) and (c, d) @@ -79,8 +85,16 @@ {name: distr.samples_ for (name, distr) in variables.items()} ) + # =================== DERIVED =================== + for derived_from, data in derived.items(): + for derived_to in data.keys(): + mapping = functools.reduce(operator.ior, data[derived_to]) + df_samples = df_samples.assign( + **{derived_to: lambda df: df[derived_from].map(mapping)} + ) + print(df_samples) - print(df_samples.corr()) + print(df_samples.select_dtypes("number").corr()) # =================== PLOTS =================== From 0230d4017d558358f03cdb9842f26b7fc164a7c9 Mon Sep 17 00:00:00 2001 From: Tommy Odland Date: Wed, 20 Aug 2025 10:56:34 +0200 Subject: [PATCH 11/13] cleanup a bit --- src/probabilit/config.yml | 2 -- src/probabilit/read_yaml_create_samples.py | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/probabilit/config.yml b/src/probabilit/config.yml index 984be47..f421bb7 100644 --- a/src/probabilit/config.yml +++ b/src/probabilit/config.yml @@ -41,8 +41,6 @@ derived: TypicalAge: # The derived parameter - "no": 16 # mapping: From -> To - "yes": 32 - - plot: - [Steel_Cost, Maintenance_Cost] - Steel_Cost diff --git a/src/probabilit/read_yaml_create_samples.py b/src/probabilit/read_yaml_create_samples.py index 36e4c55..e066332 100644 --- a/src/probabilit/read_yaml_create_samples.py +++ b/src/probabilit/read_yaml_create_samples.py @@ -1,6 +1,5 @@ from probabilit.modeling import ( Distribution, - Add, EmpiricalDistribution, CumulativeDistribution, DiscreteDistribution, @@ -86,8 +85,11 @@ ) # =================== DERIVED =================== + # Derived variables AFTER correlations, since inducing correlations + # on derived variables would break the connection. for derived_from, data in derived.items(): for derived_to in data.keys(): + # Create mapping {from1: to1, from2: to2, ...} and apply it mapping = functools.reduce(operator.ior, data[derived_to]) df_samples = df_samples.assign( **{derived_to: lambda df: df[derived_from].map(mapping)} From e1302c02c325d3ceb5d142a53e8a40d0cf384202 Mon Sep 17 00:00:00 2001 From: Tommy Odland Date: Fri, 22 Aug 2025 12:48:00 +0200 Subject: [PATCH 12/13] command line tool --- src/probabilit/config_onebyone.yml | 4 + src/probabilit/read_yaml_create_samples.py | 131 ++++++++++++++++++--- 2 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 src/probabilit/config_onebyone.yml diff --git a/src/probabilit/config_onebyone.yml b/src/probabilit/config_onebyone.yml new file mode 100644 index 0000000..fdb39ee --- /dev/null +++ b/src/probabilit/config_onebyone.yml @@ -0,0 +1,4 @@ +defaults: + Steel_Cost: 800000 + Maintenance_Cost: 0.5 + Adult: "yes" \ No newline at end of file diff --git a/src/probabilit/read_yaml_create_samples.py b/src/probabilit/read_yaml_create_samples.py index e066332..d5fabbb 100644 --- a/src/probabilit/read_yaml_create_samples.py +++ b/src/probabilit/read_yaml_create_samples.py @@ -12,6 +12,7 @@ import scipy as sp import operator import functools +import argparse TYPE_MAPPING = { "empiricaldistribution": EmpiricalDistribution, @@ -19,21 +20,16 @@ "discretedistribution": DiscreteDistribution, } -if __name__ == "__main__": - file = "config.yml" - - # =================== LOAD =================== - # Load data from file into a dictionary - with open(file, "r") as file_handle: - yaml_data = yaml.load(file_handle, Loader) +def design_matrix(config, verbose=0): + """Given a dictionary config, returns a dataframe with samples.""" # Load sections of the dictionary as variables - metadata = yaml_data["metadata"] - variables = yaml_data["variables"] - correlations = yaml_data.get("correlations", []) - plots = yaml_data.get("plot", []) - derived = yaml_data.get("derived", []) + metadata = config["metadata"] + variables = config["variables"] + correlations = config.get("correlations", []) + plots = config.get("plot", []) + derived = config.get("derived", []) # =================== CONVERT =================== @@ -95,8 +91,11 @@ **{derived_to: lambda df: df[derived_from].map(mapping)} ) - print(df_samples) - print(df_samples.select_dtypes("number").corr()) + if verbose > 0: + print("========== DESIGN MATRIX ==========") + print(df_samples) + print("========== CORRELATIONS ==========") + print(df_samples.select_dtypes("number").corr()) # =================== PLOTS =================== @@ -106,3 +105,107 @@ {var_plot: variables[var_plot].samples_ for var_plot in vars_plot} ) seaborn.pairplot(df) + + return df_samples + + +def cmd_designmatr(args): + """Execute the subcommand.""" + print(args) + + # Load data from file into a dictionary + with open(args.config, "r") as file_handle: + config = yaml.load(file_handle, Loader) + print(f"Loaded: {args.config}") + + df_samples = design_matrix(config, verbose=args.verbose) + df_samples.to_csv(args.output, index=False) + print(f"Saved: {args.output}") + + +def one_by_one(df, defaults=None, verbose=0): + """Transform a (n, p) dataframe to (n x p, p), keeping + all but one variable (column) constant at a time.""" + if defaults is None: + defaults = dict() + + for key in defaults.keys(): + if key not in df.columns: + raise ValueError("Default {key=} not in {df.columns=}") + + # Average for numeric, mode for rest + averages = df.select_dtypes("number").mean().to_dict() + modes = df.select_dtypes("number").mode().T.to_dict()[0] + constants = (averages | modes) | defaults + + assert set(constants.keys()) == set(df.columns) + + if verbose: + print(f"Constants: {constants}") + + dfs = [] + for column in df.columns: + # Set all columns except the current one to a constant + avg_map = {k: v for (k, v) in constants.items() if k != column} + dfs.append(df.assign(**avg_map)) + + return pd.concat(dfs) + + +def cmd_onebyone(args): + """Execute the subcommand.""" + print(args) + + df = pd.read_csv(args.designmatrix) + print(f"Loaded: {args.designmatrix}") + + if args.config is not None: + with open(args.config, "r") as file_handle: + config = yaml.load(file_handle, Loader) + print(f"Loaded: {args.config}") + defaults = config["defaults"] # Default map col -> const + else: + defaults = None + + df = one_by_one(df, defaults=defaults, verbose=args.verbose) + df.to_csv(args.output, index=False) + print(f"Saved: {args.output}") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + prog="ProgramName", + description="What the program does", + epilog="Text at the bottom of help", + ) + + subparsers = parser.add_subparsers(help="") + + # Parse the 'designmatrix' subcommand + p1 = subparsers.add_parser( + "designmatrix", help="Creates a design matrix (random samples)." + ) + p1.add_argument("config", help="A .yml config file.") + p1.add_argument("--output", help="An output .csv file.", default="designmatrix.csv") + p1.add_argument("--verbose", "-v", action="count", default=0) + p1.set_defaults(func=cmd_designmatr) + + # Parse the 'onebyone' subcommand + p2 = subparsers.add_parser( + "onebyone", help="Transform a design matrix to a one-by-one matrix." + ) + p2.add_argument("designmatrix", help="Input .csv file.", default="designmatrix.csv") + p2.add_argument("--config", help="A .yml config file.", default=None) + p2.add_argument( + "--output", + action="store", + type=str, + help="Output file name.", + default="onebyone.csv", + ) + p2.add_argument("--verbose", "-v", action="count", default=0) + p2.set_defaults(func=cmd_onebyone) + + # Parse args and pass to function + args = parser.parse_args() + args.func(args) From 69d697063c507bfeb1e6977c5a337d4372aae167 Mon Sep 17 00:00:00 2001 From: Tommy Odland Date: Fri, 22 Aug 2025 12:53:36 +0200 Subject: [PATCH 13/13] reorder --- src/probabilit/read_yaml_create_samples.py | 28 ++++++++++------------ 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/probabilit/read_yaml_create_samples.py b/src/probabilit/read_yaml_create_samples.py index d5fabbb..2f5cec6 100644 --- a/src/probabilit/read_yaml_create_samples.py +++ b/src/probabilit/read_yaml_create_samples.py @@ -109,20 +109,6 @@ def design_matrix(config, verbose=0): return df_samples -def cmd_designmatr(args): - """Execute the subcommand.""" - print(args) - - # Load data from file into a dictionary - with open(args.config, "r") as file_handle: - config = yaml.load(file_handle, Loader) - print(f"Loaded: {args.config}") - - df_samples = design_matrix(config, verbose=args.verbose) - df_samples.to_csv(args.output, index=False) - print(f"Saved: {args.output}") - - def one_by_one(df, defaults=None, verbose=0): """Transform a (n, p) dataframe to (n x p, p), keeping all but one variable (column) constant at a time.""" @@ -154,7 +140,6 @@ def one_by_one(df, defaults=None, verbose=0): def cmd_onebyone(args): """Execute the subcommand.""" - print(args) df = pd.read_csv(args.designmatrix) print(f"Loaded: {args.designmatrix}") @@ -172,6 +157,19 @@ def cmd_onebyone(args): print(f"Saved: {args.output}") +def cmd_designmatr(args): + """Execute the subcommand.""" + + # Load data from file into a dictionary + with open(args.config, "r") as file_handle: + config = yaml.load(file_handle, Loader) + print(f"Loaded: {args.config}") + + df = design_matrix(config, verbose=args.verbose) + df.to_csv(args.output, index=False) + print(f"Saved: {args.output}") + + if __name__ == "__main__": parser = argparse.ArgumentParser( prog="ProgramName",