"""Class that provides some automatic specification for segmented parametersMichel BierlaireThu Apr 3 12:07:44 2025"""from__future__importannotationsfromcollections.abcimportIterablefromitertoolsimportproductfromtypingimportNamedTuple,TYPE_CHECKINGimportpandasaspdfrom.one_segmentationimportOneSegmentationfrom.segmentation_contextimportDiscreteSegmentationTuplefrombiogeme.exceptionsimportBiogemeErrorfrombiogeme.results_processingimportEstimationResultsifTYPE_CHECKING:frombiogeme.expressionsimportBeta,Expression,MultipleSum
[docs]classSegmentation:"""Segmentation of a parameter, possibly with multiple socio-economic variables"""def__init__(self,beta:Beta,segmentation_tuples:Iterable[DiscreteSegmentationTuple],prefix:str='segmented',):"""Ctor :param beta: parameter to be segmented :param segmentation_tuples: characterization of the segmentations :param prefix: prefix to be used to generate the name of the segmented parameter """segmentation_tuples=tuple(segmentation_tuples)ifnotsegmentation_tuples:raiseBiogemeError('segmentation_tuples cannot be empty')self.beta:Beta=betaself.segmentations:tuple[OneSegmentation,...]=tuple(OneSegmentation(beta,s)forsinsegmentation_tuples)self.prefix=prefix
[docs]defget_beta_ref_name(self)->str:""" Add a suffix to the name of the parameter """returnself.beta.name+'_ref'
[docs]defbeta_ref_code(self)->str:"""Constructs the Python code for the parameter :return: Python code :rtype: str """beta_name=f"'{self.get_beta_ref_name()}'"return(f'Beta({beta_name}, {self.beta.init_value}, {self.beta.lower_bound}, 'f'{self.beta.upper_bound}, {self.beta.status})')
[docs]defget_reference_beta(self)->Beta:"""Obtain the reference beta"""frombiogeme.expressionsimportBetabeta_name=self.get_beta_ref_name()returnBeta(beta_name,self.beta.init_value,self.beta.lower_bound,self.beta.upper_bound,self.beta.status,)
[docs]defsegmented_beta(self)->Expression:"""Create an expressions that combines all the segments :return: combined expression :rtype: biogeme.expressions.Expression """frombiogeme.expressionsimportMultipleSumref_beta=self.get_reference_beta()terms=[ref_beta]terms+=[elementforsinself.segmentationsforelementins.list_of_expressions()]returnMultipleSum(terms)
[docs]defsegmented_code(self)->str:"""Create the Python code for an expressions that combines all the segments :return: Python code for the combined expression :rtype: str """result='\n'.join([s.beta_code(c,assignment=True)forsinself.segmentationsforcins.mapping.values()])result+='\n'terms=[self.beta_ref_code()]terms+=[elementforsinself.segmentationsforelementins.list_of_code()]iflen(terms)==1:result+=terms[0]else:joined_terms=', '.join(terms)result+=f'{self.prefix}_{self.beta.name} = bioMultSum([{joined_terms}])'returnresult
[docs]defcalculates_estimated_values(self,estimation_results:EstimationResults)->pd.DataFrame:"""Calculates the estimated values of the parameter for each segment. :param estimation_results: results of the estimation :return: a pandas data frame with the definition of the segments and the corresponding values for the coefficient """classSegmentationValue(NamedTuple):segmentation:OneSegmentationvalue:strall_segmentations=[list(SegmentationValue(segment,value)forvalueinsegment.segmentation_tuple.mapping.values())forsegmentinself.segmentations]# Use itertools.product to generate all combinationsbeta_values=estimation_results.get_beta_values()ref_beta_name=self.get_reference_beta().nameref_value=beta_values[ref_beta_name]list_of_rows=[]forcombinationinproduct(*all_segmentations):the_row={element.segmentation.variable.name:element.valueforelementincombination}the_row['parameter estimate']=ref_valueforelementincombination:the_name=element.segmentation.beta_name(category=element.value)ifthe_name!=ref_beta_name:the_value=beta_values[the_name]the_row['parameter estimate']+=the_valuelist_of_rows.append(the_row)df=pd.DataFrame(list_of_rows)returndf