Source code for endoreg_db.models.other.distribution.numeric_value_distribution

'''Model for numeric value distribution'''

from django.db import models
import numpy as np
from .base_value_distribution import BaseValueDistribution
from scipy.stats import skewnorm


class NumericValueDistributionManager(models.Manager):
    '''Object manager for NumericValueDistribution'''
    def get_by_natural_key(self, name):
        '''Retrieve a NumericValueDistribution by its natural key.'''
        return self.get(name=name)

[docs] class NumericValueDistribution(BaseValueDistribution): """ Numeric value distribution model. Supports uniform, normal, and skewed normal distributions with hard limits. """ objects = NumericValueDistributionManager() DISTRIBUTION_CHOICES = [ ('uniform', 'Uniform'), ('normal', 'Normal'), ('skewed_normal', 'Skewed Normal'), ] distribution_type = models.CharField(max_length=20, choices=DISTRIBUTION_CHOICES) min_descriptor = models.CharField( max_length=20 ) max_descriptor = models.CharField( max_length=20 )
[docs] def generate_value(self, lab_value, patient): '''Generate a value based on the distribution rules.''' #FIXME from endoreg_db.models import LabValue, Patient assert isinstance(patient, Patient) assert isinstance(lab_value, LabValue) default_normal_range_dict = lab_value.get_normal_range(patient.age(), patient.gender) assert isinstance(default_normal_range_dict, dict) if self.distribution_type == 'uniform': assert self.min_descriptor and self.max_descriptor assert "min" in default_normal_range_dict and "max" in default_normal_range_dict value = self._generate_value_uniform(default_normal_range_dict) return value elif self.distribution_type == 'normal': value = np.random.normal(self.mean, self.std_dev) return np.clip(value, self.min_value, self.max_value) elif self.distribution_type == 'skewed_normal': value = skewnorm.rvs(a=self.skewness, loc=self.mean, scale=self.std_dev) return np.clip(value, self.min_value, self.max_value) else: raise ValueError("Unsupported distribution type")
[docs] def parse_value_descriptor(self, value_descriptor:str): '''Parse the value descriptor string into a dict with a lambda function.''' # strings of shape f"{value_key}_{operator}_{value}" # extract value_key, operator, value value_key, operator, value = value_descriptor.split("_") value = float(value) operator_functions = { "+": lambda x: x + value, "-": lambda x: x - value, "x": lambda x: x * value, "/": lambda x: x / value, } return {value_key: operator_functions[operator]}
# create dict with {value_key: lambda x: x operator value} def _generate_value_uniform(self, default_normal_range_dict:dict): value_function_dict = self.parse_value_descriptor( self.min_descriptor ) _ = self.parse_value_descriptor( self.max_descriptor ) value_function_dict.update(_) result_dict = { key: value_function(default_normal_range_dict[key]) for key, value_function in value_function_dict.items() } # generate value return np.random.uniform(result_dict["min"], result_dict["max"])