<jupyter_start><jupyter_text>Unmet targets workflow  This notebook outlines the steps to identify possible reasons why the targets were not met to help guide the user to take an appropriate action in each case Identify unmet targets: REQUIREMENTS:      File **'output_mvbest.csv'**, columns 'Target Met' + 'MPM' (Minimum Proportion Met) Worflow for unmet solutions:    If the feature is not met but by very little --> mark as met or decrease MISSLEVEL     Else:        If the feature is in locked-out planning units --> Send warning/mark as met/rethink problem        Else:            If the feature is in a high cost area --> Increase SPF            Else:                The feature has low range and/or is isolated --> Increase SPF                PARAMETERS:  * **threshold met**: when to consider a target as met (by default the misslevel set at input.dat)--> number between 0-1  * **lock_out_limit**: max percentage of the feature pu's that are in locked out areas that trigger response --> number between 0-1 * **high_cost_quantile**: value to define what is high cost (quatile is used to generalize any cost values) --> number between 0-1 * **hcost_limit**: max percentage of the feature pu's that are in high cost areas that trigger response --> number between 0-1<jupyter_code>%run marxan_utils.ipynb<jupyter_output><empty_output><jupyter_text>Helper functions<jupyter_code>def locatePU(MARXAN_FOLDER:str,MARXAN_INPUTDATA:str,pus_of_interest=None, feature=None) -> dict:
    """ Check if feature is in an area of interest
    """
    userInputFile = readInput(MARXAN_FOLDER,MARXAN_INPUTDATA)
    puvsp = validateFile(MARXAN_FOLDER,MARXAN_INPUTDATA, planningUnitVSConservationFeatureV)
    
    feature_in_pu=puvsp[(puvsp['species']==feature) & (puvsp.pu.isin(pus_of_interest))]
    
    if len(feature_in_pu)== 0: 
        percentage_in_pu = 0
    else:
        amount_total = puvsp[puvsp['species']==feature].groupby(['species']).sum('amount').reset_index().amount.values[0]
        amount_in_pu= feature_in_pu.groupby('species').sum('amount').reset_index().amount.values[0]
        percentage_in_pu = round((amount_in_pu/amount_total)*100,2)
    return percentage_in_pu

def unmetDecisionTree(MARXAN_FOLDER, MARXAN_INPUTDATA,
                      lock_out_limit=0.5, 
                      high_cost_quantile= 0.7, 
                      hcost_limit=0.2):
    
    """Follow the decision flow:
    - Check if the target is missed by little (already considering the defined misslevel)
    - Check if the feature is in a locked-out area 
    - Check if the feature is in a high cost area
    - Check if the feature is very isolated or the range of the feature is very small
    
    Parameteres:
    - threshold met: when to consider a target as met (dy default the misslevel set at input.dat)
    - lock_out_limit: amount of the feature pu's that are in locked out areas that trigger response
    - high_cost_quantile: decide what is high cost
    - hcost_limit: amount of the feature pu's that are in high cost areas that trigger response
    """
    
    # Validate and read files
    userInputFile = readInput(MARXAN_FOLDER,MARXAN_INPUTDATA)
    pu = validateFile(MARXAN_FOLDER,MARXAN_INPUTDATA, planningUnits)
    puvsp = validateFile(MARXAN_FOLDER,MARXAN_INPUTDATA, planningUnitVSConservationFeatureV)
    mvbest= validateFile(MARXAN_FOLDER,MARXAN_INPUTDATA, OutputMV)
    
    df = mvbest[['Conservation_Feature','Target_Met','MPM']].copy()
    threshold_met = userInputFile.MISSLEVEL
    
    if len(df.loc[df['Target_Met']=='no','Conservation_Feature'])==0:
        unmet = {'None':'All targets met'}
    else: 
        for feature in list(df.loc[df['Target_Met']=='no','Conservation_Feature'].values):
        
            # 1. Close to target 
            # Solution: 
            # - Mark as met: Modify output_mvbest.csv (Target Met) MPM: minimum proportion met
            # - Decrease MISSLEVEL (input.dat)
            if (df.loc[df['Conservation_Feature']==feature,'MPM']>=threshold_met-(threshold_met*1/100)).bool():
                df.loc[df['Conservation_Feature']==feature,'Eval']='Close to target (1% away of misslevel)'

            else:
        
            # 2. In lock-out areas (re-think problem)
            # Solution: 
            # - Rethink problem (Assume not met)
            # - Unblock lock-out pu: Modifir pu.dat (status)
        
                pu_excluded = list(pu[pu['status']==3].id) 
                excluded_per = locatePU(MARXAN_FOLDER,MARXAN_INPUTDATA,pus_of_interest=pu_excluded, feature=feature)/100
                if excluded_per > lock_out_limit: df.loc[df['Conservation_Feature']==feature,'Eval'] = f'{excluded_per*100} % in locked-out areas'
            
                else:
                        
            # 3. In high cost areas (increase SPF)
            # Solution: 
            # - Increase SPF: Modify spec.dat (spf)
        
                    pu_hcost = list(pu[pu['cost']>pu['cost'].quantile([high_cost_quantile], interpolation='nearest').values[0]].id)
                    hcost_per = locatePU(MARXAN_FOLDER,MARXAN_INPUTDATA,pus_of_interest=pu_excluded, feature=feature)/100
                    if hcost_per > hcost_limit: df.loc[df['Conservation_Feature']==feature,'Eval'] = f'{hcost_per*100} % in high cost areas'
                
                    else:
        
            # 4. Small range or isolated 
            # Solution:
            # - Increase SPF: Modify spec.dat (spf)
                        feat_range = round(puvsp[puvsp['species']==feature].count().pu/len(pu)*100,2)
                        df.loc[df['Conservation_Feature']==feature,'Eval'] = f'Small range ({feat_range} % of planning area) or isolated'     
        
            unmet = dict(zip(df[df['Target_Met']=='no'].Conservation_Feature, df[df['Target_Met']=='no'].Eval))
    return unmet
### Next step is to modify input the files according to this output, and then run marxan again<jupyter_output><empty_output><jupyter_text>Example data<jupyter_code>#MARXAN_FOLDER = '/home/jovyan/work/datasets/raw/Marxan_Targets/example_1_Papua'
MARXAN_FOLDER = '/home/jovyan/work/datasets/raw/demo_Papua_New_Guinea/marxan_Papua_New_Guinea'

MARXAN_INPUTDATA = 'input.dat'
df = gapAnalysis(MARXAN_FOLDER, 'input.dat')
df.head()
fig, anx = plt.subplots(nrows=1, ncols=2,figsize=(10,20), constrained_layout=True)
plotGap(df, 'Pre-Marxan', anx[0], post_marxan=False)
plotGap(df, 'Post-Marxan', anx[1], post_marxan=True)
unmet_dict = unmetDecisionTree(MARXAN_FOLDER, MARXAN_INPUTDATA,
                      lock_out_limit=0.5, 
                      high_cost_quantile= 0.7, 
                      hcost_limit=0.2)
unmet_dict<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10507462686567164, 'fraction_numerical': 0.009701492537313432, 'mean_word_length': 3.007775119617225, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 26, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '3', 'dir': 'jupyter-structured-clean-dedup', 'id': '130904', 'license': 'MIT', 'n_tokens_mistral': 2185, 'n_tokens_neox': 2062, 'n_words': 520, 'path': 'data/notebooks/Lab/marxan_unmet_targets.ipynb', 'repo_name': 'tnc-css/marxan-cloud', 'size': '6700'}