Skip to content

Complexity API Reference

Complete API reference for complexity measures.

ComplexityMeasures

Main class for calculating complexity measures.

fairsample.complexity.ComplexityMeasures

User-friendly wrapper for ComplexityMetrics with all complexity measures.

This class provides easy access to all complexity measures with options to: - Get all measures at once - Get specific categories (feature/instance/structural/multiresolution) - Get specific measures by name

Parameters:

Name Type Description Default
X array-like of shape (n_samples, n_features)

Feature matrix

required
y array-like of shape (n_samples,)

Target vector

required
distance_func str

Distance function to use

"default"
Example

cm = ComplexityMeasures(X, y)

Get all measures

all_measures = cm.get_all_complexity_measures()

Get specific category

feature_measures = cm.get_all_complexity_measures(measures='feature')

Get specific measures

selected = cm.get_all_complexity_measures(measures=['N3', 'F1', 'N1'])

Quick analysis

basic = cm.analyze_overlap()

Source code in fairsample/complexity/measures.py
class ComplexityMeasures:
    """
    User-friendly wrapper for ComplexityMetrics with all complexity measures.

    This class provides easy access to all complexity measures with options to:
    - Get all measures at once
    - Get specific categories (feature/instance/structural/multiresolution)
    - Get specific measures by name

    Parameters
    ----------
    X : array-like of shape (n_samples, n_features)
        Feature matrix
    y : array-like of shape (n_samples,)
        Target vector
    distance_func : str, default="default"
        Distance function to use

    Example
    -------
    >>> cm = ComplexityMeasures(X, y)
    >>> 
    >>> # Get all measures
    >>> all_measures = cm.get_all_complexity_measures()
    >>> 
    >>> # Get specific category
    >>> feature_measures = cm.get_all_complexity_measures(measures='feature')
    >>> 
    >>> # Get specific measures
    >>> selected = cm.get_all_complexity_measures(measures=['N3', 'F1', 'N1'])
    >>> 
    >>> # Quick analysis
    >>> basic = cm.analyze_overlap()
    """

    def __init__(self, X, y, distance_func="default"):
        """Initialize complexity measures calculator."""
        self.metrics = ComplexityMetrics(X, y)
        self.X = self.metrics.X
        self.y = self.metrics.y
        self.classes = self.metrics.classes
        self.class_count = self.metrics.class_count
        self.class_inxs = self.metrics.class_inxs
        self.dist_matrix = self.metrics.dist_matrix

    def get_all_complexity_measures(self, k=5, imb=False, measures='all'):
        """
        Get all available complexity measures in a structured format.

        Parameters
        ----------
        k : int, default=5
            Number of neighbors for k-NN based measures
        imb : bool, default=False
            Whether to return class-specific values for imbalanced analysis
        measures : str or list, default='all'
            Which measures to calculate:
            - 'all': All available measures
            - 'basic': Essential measures (N3, N1, N2, F1, F2)
            - 'feature': Only feature overlap measures
            - 'instance': Only instance overlap measures
            - 'structural': Only structural overlap measures
            - 'multiresolution': Only multiresolution measures
            - list: Specific measure names (e.g., ['N3', 'F1', 'N1'])

        Returns
        -------
        results : dict
            Structured dictionary with all complexity measures
        """
        results = {
            'dataset_info': {
                'n_samples': len(self.X),
                'n_features': self.X.shape[1],
                'n_classes': len(self.classes),
                'class_distribution': np.bincount(self.y).tolist(),
                'imbalance_ratio': np.max(np.bincount(self.y)) / np.min(np.bincount(self.y))
            },
            'feature_overlap': {},
            'instance_overlap': {},
            'structural_overlap': {},
            'multiresolution_overlap': {}
        }

        # Define measure categories
        feature_measures = ['F1', 'F1v', 'F2', 'F3', 'F4', 'input_noise']
        instance_measures = ['N3', 'N4', 'kDN', 'CM', 'R_value', 'D3_value', 'SI', 
                           'borderline', 'deg_overlap']
        structural_measures = ['N1', 'N2', 'T1', 'DBC', 'LSC', 'Clust', 'NSG', 
                             'ICSV', 'ONB']
        multiresolution_measures = ['purity', 'neighbourhood_separability', 'MRCA', 
                                   'C1', 'C2']

        # Determine which measures to calculate
        if measures == 'basic':
            calc_measures = ['N3', 'N1', 'N2', 'F1', 'F2']
        elif measures == 'all':
            calc_measures = (feature_measures + instance_measures + 
                           structural_measures + multiresolution_measures)
        elif measures == 'feature':
            calc_measures = feature_measures
        elif measures == 'instance':
            calc_measures = instance_measures
        elif measures == 'structural':
            calc_measures = structural_measures
        elif measures == 'multiresolution':
            calc_measures = multiresolution_measures
        elif isinstance(measures, list):
            calc_measures = measures
        else:
            calc_measures = ['N3', 'N1', 'N2', 'F1', 'F2']

        # Calculate measures
        for measure_name in calc_measures:
            try:
                if measure_name == 'F1':
                    results['feature_overlap']['F1'] = self.metrics.F1()
                elif measure_name == 'F1v':
                    results['feature_overlap']['F1v'] = self.metrics.F1v()
                elif measure_name == 'F2':
                    results['feature_overlap']['F2'] = self.metrics.F2(imb=imb)
                elif measure_name == 'F3':
                    results['feature_overlap']['F3'] = self.metrics.F3(imb=imb)
                elif measure_name == 'F4':
                    results['feature_overlap']['F4'] = self.metrics.F4(imb=imb)
                elif measure_name == 'input_noise':
                    results['feature_overlap']['input_noise'] = self.metrics.input_noise(imb=imb)

                elif measure_name == 'N3':
                    results['instance_overlap']['N3'] = self.metrics.N3(k=k, imb=imb)
                elif measure_name == 'N4':
                    results['instance_overlap']['N4'] = self.metrics.N4(k=k, imb=imb)
                elif measure_name == 'kDN':
                    results['instance_overlap']['kDN'] = self.metrics.kDN(k=k, imb=imb)
                elif measure_name == 'CM':
                    results['instance_overlap']['CM'] = self.metrics.CM(k=k, imb=imb)
                elif measure_name == 'R_value':
                    results['instance_overlap']['R_value'] = self.metrics.R_value(k=k, imb=imb)
                elif measure_name == 'D3_value':
                    results['instance_overlap']['D3'] = self.metrics.D3_value(k=k)
                elif measure_name == 'SI':
                    results['instance_overlap']['SI'] = self.metrics.SI(k=k, imb=imb)
                elif measure_name == 'borderline':
                    results['instance_overlap']['borderline'] = self.metrics.borderline(imb=imb)
                elif measure_name == 'deg_overlap':
                    results['instance_overlap']['deg_overlap'] = self.metrics.deg_overlap(k=k, imb=imb)

                elif measure_name == 'N1':
                    results['structural_overlap']['N1'] = self.metrics.N1(imb=imb)
                elif measure_name == 'N2':
                    results['structural_overlap']['N2'] = self.metrics.N2(imb=imb)
                elif measure_name == 'T1':
                    results['structural_overlap']['T1'] = self.metrics.T1(imb=imb)
                elif measure_name == 'DBC':
                    results['structural_overlap']['DBC'] = self.metrics.DBC(imb=imb)
                elif measure_name == 'LSC':
                    results['structural_overlap']['LSC'] = self.metrics.LSC(imb=imb)
                elif measure_name == 'Clust':
                    results['structural_overlap']['Clust'] = self.metrics.Clust(imb=imb)
                elif measure_name == 'NSG':
                    results['structural_overlap']['NSG'] = self.metrics.NSG(imb=imb)
                elif measure_name == 'ICSV':
                    results['structural_overlap']['ICSV'] = self.metrics.ICSV(imb=imb)
                elif measure_name == 'ONB':
                    results['structural_overlap']['ONB'] = self.metrics.ONB(imb=imb)

                elif measure_name == 'purity':
                    results['multiresolution_overlap']['purity'] = self.metrics.purity()
                elif measure_name == 'neighbourhood_separability':
                    results['multiresolution_overlap']['neighbourhood_separability'] = self.metrics.neighbourhood_separability()
                elif measure_name == 'MRCA':
                    results['multiresolution_overlap']['MRCA'] = self.metrics.MRCA()
                elif measure_name == 'C1':
                    results['multiresolution_overlap']['C1'] = self.metrics.C1(imb=imb)
                elif measure_name == 'C2':
                    results['multiresolution_overlap']['C2'] = self.metrics.C2(imb=imb)

            except Exception as e:
                if measures != 'all':  # Only warn if not calculating all
                    print(f"Warning: Could not calculate {measure_name}: {e}")

        return results

    def analyze_overlap(self, measures='basic'):
        """
        Quick overlap analysis using essential measures.

        Parameters
        ----------
        measures : str, default='basic'
            Level of analysis:
            - 'basic': Essential measures (N3, N1, N2, F1, F2)
            - 'standard': Common measures
            - 'all': All available measures

        Returns
        -------
        results : dict
            Dictionary containing complexity measures
        """
        if measures == 'basic':
            results = {
                'n_samples': len(self.X),
                'n_features': self.X.shape[1],
                'n_classes': len(self.classes),
                'class_distribution': np.bincount(self.y).tolist(),
                'imbalance_ratio': np.max(np.bincount(self.y)) / np.min(np.bincount(self.y)),
                'N3': self._safe_calc(lambda: self.metrics.N3(k=1)),
                'N1': self._safe_calc(lambda: self.metrics.N1()),
                'N2': self._safe_calc(lambda: self.metrics.N2()),
                'F1': self._safe_calc(lambda: np.mean(self.metrics.F1())),
                'F2': self._safe_calc(lambda: np.mean(self.metrics.F2())),
            }
        elif measures == 'standard':
            all_measures = self.get_all_complexity_measures(measures='all')
            results = self._flatten_dict(all_measures)
        elif measures == 'all':
            all_measures = self.get_all_complexity_measures(measures='all')
            results = all_measures
        else:
            results = self.analyze_overlap('basic')

        return results

    def _safe_calc(self, func):
        """Safely calculate a measure."""
        try:
            return func()
        except:
            return None

    def _flatten_dict(self, nested_dict):
        """Flatten nested dictionary."""
        flat = {}
        for category, measures in nested_dict.items():
            if isinstance(measures, dict):
                for key, value in measures.items():
                    if isinstance(value, (list, np.ndarray)):
                        try:
                            flat[key] = float(np.mean(value))
                        except:
                            flat[key] = value
                    else:
                        flat[key] = value
            else:
                flat[category] = measures
        return flat

__init__(X, y, distance_func='default')

Initialize complexity measures calculator.

Source code in fairsample/complexity/measures.py
def __init__(self, X, y, distance_func="default"):
    """Initialize complexity measures calculator."""
    self.metrics = ComplexityMetrics(X, y)
    self.X = self.metrics.X
    self.y = self.metrics.y
    self.classes = self.metrics.classes
    self.class_count = self.metrics.class_count
    self.class_inxs = self.metrics.class_inxs
    self.dist_matrix = self.metrics.dist_matrix

analyze_overlap(measures='basic')

Quick overlap analysis using essential measures.

Parameters:

Name Type Description Default
measures str

Level of analysis: - 'basic': Essential measures (N3, N1, N2, F1, F2) - 'standard': Common measures - 'all': All available measures

'basic'

Returns:

Name Type Description
results dict

Dictionary containing complexity measures

Source code in fairsample/complexity/measures.py
def analyze_overlap(self, measures='basic'):
    """
    Quick overlap analysis using essential measures.

    Parameters
    ----------
    measures : str, default='basic'
        Level of analysis:
        - 'basic': Essential measures (N3, N1, N2, F1, F2)
        - 'standard': Common measures
        - 'all': All available measures

    Returns
    -------
    results : dict
        Dictionary containing complexity measures
    """
    if measures == 'basic':
        results = {
            'n_samples': len(self.X),
            'n_features': self.X.shape[1],
            'n_classes': len(self.classes),
            'class_distribution': np.bincount(self.y).tolist(),
            'imbalance_ratio': np.max(np.bincount(self.y)) / np.min(np.bincount(self.y)),
            'N3': self._safe_calc(lambda: self.metrics.N3(k=1)),
            'N1': self._safe_calc(lambda: self.metrics.N1()),
            'N2': self._safe_calc(lambda: self.metrics.N2()),
            'F1': self._safe_calc(lambda: np.mean(self.metrics.F1())),
            'F2': self._safe_calc(lambda: np.mean(self.metrics.F2())),
        }
    elif measures == 'standard':
        all_measures = self.get_all_complexity_measures(measures='all')
        results = self._flatten_dict(all_measures)
    elif measures == 'all':
        all_measures = self.get_all_complexity_measures(measures='all')
        results = all_measures
    else:
        results = self.analyze_overlap('basic')

    return results

get_all_complexity_measures(k=5, imb=False, measures='all')

Get all available complexity measures in a structured format.

Parameters:

Name Type Description Default
k int

Number of neighbors for k-NN based measures

5
imb bool

Whether to return class-specific values for imbalanced analysis

False
measures str or list

Which measures to calculate: - 'all': All available measures - 'basic': Essential measures (N3, N1, N2, F1, F2) - 'feature': Only feature overlap measures - 'instance': Only instance overlap measures - 'structural': Only structural overlap measures - 'multiresolution': Only multiresolution measures - list: Specific measure names (e.g., ['N3', 'F1', 'N1'])

'all'

Returns:

Name Type Description
results dict

Structured dictionary with all complexity measures

Source code in fairsample/complexity/measures.py
def get_all_complexity_measures(self, k=5, imb=False, measures='all'):
    """
    Get all available complexity measures in a structured format.

    Parameters
    ----------
    k : int, default=5
        Number of neighbors for k-NN based measures
    imb : bool, default=False
        Whether to return class-specific values for imbalanced analysis
    measures : str or list, default='all'
        Which measures to calculate:
        - 'all': All available measures
        - 'basic': Essential measures (N3, N1, N2, F1, F2)
        - 'feature': Only feature overlap measures
        - 'instance': Only instance overlap measures
        - 'structural': Only structural overlap measures
        - 'multiresolution': Only multiresolution measures
        - list: Specific measure names (e.g., ['N3', 'F1', 'N1'])

    Returns
    -------
    results : dict
        Structured dictionary with all complexity measures
    """
    results = {
        'dataset_info': {
            'n_samples': len(self.X),
            'n_features': self.X.shape[1],
            'n_classes': len(self.classes),
            'class_distribution': np.bincount(self.y).tolist(),
            'imbalance_ratio': np.max(np.bincount(self.y)) / np.min(np.bincount(self.y))
        },
        'feature_overlap': {},
        'instance_overlap': {},
        'structural_overlap': {},
        'multiresolution_overlap': {}
    }

    # Define measure categories
    feature_measures = ['F1', 'F1v', 'F2', 'F3', 'F4', 'input_noise']
    instance_measures = ['N3', 'N4', 'kDN', 'CM', 'R_value', 'D3_value', 'SI', 
                       'borderline', 'deg_overlap']
    structural_measures = ['N1', 'N2', 'T1', 'DBC', 'LSC', 'Clust', 'NSG', 
                         'ICSV', 'ONB']
    multiresolution_measures = ['purity', 'neighbourhood_separability', 'MRCA', 
                               'C1', 'C2']

    # Determine which measures to calculate
    if measures == 'basic':
        calc_measures = ['N3', 'N1', 'N2', 'F1', 'F2']
    elif measures == 'all':
        calc_measures = (feature_measures + instance_measures + 
                       structural_measures + multiresolution_measures)
    elif measures == 'feature':
        calc_measures = feature_measures
    elif measures == 'instance':
        calc_measures = instance_measures
    elif measures == 'structural':
        calc_measures = structural_measures
    elif measures == 'multiresolution':
        calc_measures = multiresolution_measures
    elif isinstance(measures, list):
        calc_measures = measures
    else:
        calc_measures = ['N3', 'N1', 'N2', 'F1', 'F2']

    # Calculate measures
    for measure_name in calc_measures:
        try:
            if measure_name == 'F1':
                results['feature_overlap']['F1'] = self.metrics.F1()
            elif measure_name == 'F1v':
                results['feature_overlap']['F1v'] = self.metrics.F1v()
            elif measure_name == 'F2':
                results['feature_overlap']['F2'] = self.metrics.F2(imb=imb)
            elif measure_name == 'F3':
                results['feature_overlap']['F3'] = self.metrics.F3(imb=imb)
            elif measure_name == 'F4':
                results['feature_overlap']['F4'] = self.metrics.F4(imb=imb)
            elif measure_name == 'input_noise':
                results['feature_overlap']['input_noise'] = self.metrics.input_noise(imb=imb)

            elif measure_name == 'N3':
                results['instance_overlap']['N3'] = self.metrics.N3(k=k, imb=imb)
            elif measure_name == 'N4':
                results['instance_overlap']['N4'] = self.metrics.N4(k=k, imb=imb)
            elif measure_name == 'kDN':
                results['instance_overlap']['kDN'] = self.metrics.kDN(k=k, imb=imb)
            elif measure_name == 'CM':
                results['instance_overlap']['CM'] = self.metrics.CM(k=k, imb=imb)
            elif measure_name == 'R_value':
                results['instance_overlap']['R_value'] = self.metrics.R_value(k=k, imb=imb)
            elif measure_name == 'D3_value':
                results['instance_overlap']['D3'] = self.metrics.D3_value(k=k)
            elif measure_name == 'SI':
                results['instance_overlap']['SI'] = self.metrics.SI(k=k, imb=imb)
            elif measure_name == 'borderline':
                results['instance_overlap']['borderline'] = self.metrics.borderline(imb=imb)
            elif measure_name == 'deg_overlap':
                results['instance_overlap']['deg_overlap'] = self.metrics.deg_overlap(k=k, imb=imb)

            elif measure_name == 'N1':
                results['structural_overlap']['N1'] = self.metrics.N1(imb=imb)
            elif measure_name == 'N2':
                results['structural_overlap']['N2'] = self.metrics.N2(imb=imb)
            elif measure_name == 'T1':
                results['structural_overlap']['T1'] = self.metrics.T1(imb=imb)
            elif measure_name == 'DBC':
                results['structural_overlap']['DBC'] = self.metrics.DBC(imb=imb)
            elif measure_name == 'LSC':
                results['structural_overlap']['LSC'] = self.metrics.LSC(imb=imb)
            elif measure_name == 'Clust':
                results['structural_overlap']['Clust'] = self.metrics.Clust(imb=imb)
            elif measure_name == 'NSG':
                results['structural_overlap']['NSG'] = self.metrics.NSG(imb=imb)
            elif measure_name == 'ICSV':
                results['structural_overlap']['ICSV'] = self.metrics.ICSV(imb=imb)
            elif measure_name == 'ONB':
                results['structural_overlap']['ONB'] = self.metrics.ONB(imb=imb)

            elif measure_name == 'purity':
                results['multiresolution_overlap']['purity'] = self.metrics.purity()
            elif measure_name == 'neighbourhood_separability':
                results['multiresolution_overlap']['neighbourhood_separability'] = self.metrics.neighbourhood_separability()
            elif measure_name == 'MRCA':
                results['multiresolution_overlap']['MRCA'] = self.metrics.MRCA()
            elif measure_name == 'C1':
                results['multiresolution_overlap']['C1'] = self.metrics.C1(imb=imb)
            elif measure_name == 'C2':
                results['multiresolution_overlap']['C2'] = self.metrics.C2(imb=imb)

        except Exception as e:
            if measures != 'all':  # Only warn if not calculating all
                print(f"Warning: Could not calculate {measure_name}: {e}")

    return results

Usage

from fairsample.complexity import ComplexityMeasures

# Create analyzer
cm = ComplexityMeasures(X, y)

# Get basic overlap measures
basic = cm.analyze_overlap()

# Get all measures
all_measures = cm.get_all_complexity_measures(measures='all')

# Get specific category
feature = cm.get_all_complexity_measures(measures='feature')

# Get specific measures
selected = cm.get_all_complexity_measures(measures=['N3', 'F1'])

Individual Measures

Feature Overlap

# F1 - Maximum Fisher's Discriminant Ratio
f1 = cm.calculate_F1()

# F1v - Directional-vector maximum Fisher's discriminant ratio
f1v = cm.calculate_F1v()

# F2 - Volume of overlapping region
f2 = cm.calculate_F2()

# F3 - Maximum individual feature efficiency
f3 = cm.calculate_F3()

# F4 - Collective feature efficiency
f4 = cm.calculate_F4()

# Input Noise
noise = cm.calculate_input_noise()

Instance Overlap

# N3 - Error rate of nearest neighbor
n3 = cm.calculate_N3()

# N4 - Non-linearity of nearest neighbor
n4 = cm.calculate_N4()

# kDN - k-Disagreeing neighbors
kdn = cm.calculate_kDN(k=5)

# CM - Class imbalance metric
cm_score = cm.calculate_CM()

# R-value - Overlap region size
r_value = cm.calculate_R_value()

# D3 - Disjunct class percentage
d3 = cm.calculate_D3()

# SI - Silhouette index
si = cm.calculate_SI()

# Borderline - Borderline instance ratio
borderline = cm.calculate_borderline()

# Degree of overlap
overlap = cm.calculate_degree_of_overlap()

Structural

# N1 - Fraction of borderline points
n1 = cm.calculate_N1()

# N2 - Ratio of intra/extra class nearest neighbor distance
n2 = cm.calculate_N2()

# T1 - Fraction of hyperspheres covering data
t1 = cm.calculate_T1()

# DBC - Distance-based complexity
dbc = cm.calculate_DBC()

# LSC - Local set cardinality
lsc = cm.calculate_LSC()

# Clust - Clustering measure
clust = cm.calculate_Clust()

# NSG - Number of spanning graphs
nsg = cm.calculate_NSG()

# ICSV - Inter-class to intra-class similarity variance
icsv = cm.calculate_ICSV()

# ONB - Overlap of neighborhoods between classes
onb = cm.calculate_ONB()

Multiresolution

# Purity
purity = cm.calculate_purity()

# Neighbourhood Separability
ns = cm.calculate_neighbourhood_separability()

# MRCA - Multiresolution complexity analysis
mrca = cm.calculate_MRCA()

# C1 - Entropy of class proportions
c1 = cm.calculate_C1()

# C2 - Imbalance ratio
c2 = cm.calculate_C2()

Utility Functions

compare_pre_post_overlap

Compare complexity before and after resampling.

from fairsample.complexity import compare_pre_post_overlap

comparison = compare_pre_post_overlap(
    X_before, y_before,
    X_after, y_after,
    measures='basic'
)

print(comparison['before'])
print(comparison['after'])
print(comparison['improvements'])

Measure Categories

Use these strings with get_all_complexity_measures():

  • 'all' - All 40+ measures
  • 'basic' - Quick subset (N3, F1, N1, T1, imbalance_ratio)
  • 'feature' - Feature overlap measures
  • 'instance' - Instance overlap measures
  • 'structural' - Structural measures
  • 'multiresolution' - Multiresolution measures
  • ['N3', 'F1', ...] - List of specific measures