lace.analysis module

Tools for analysis of probabilistic cross-categorization results in Lace.

class lace.analysis.HoldOutFunc(value)

Hold out evaluation function.

class lace.analysis.HoldOutSearchMethod(value)

Method for hold out search.

lace.analysis.attributable_inconsistency(engine: Engine, values, given: dict[Union[str, int], Any], quiet: bool = False, greedy: bool = True) Tuple[float, DataFrame]

Determine what fraction of inconsistency is attributable.

The fraction will be higher if dropping fewer predictor reduces inconsistency quickly. The fraction will be 1 if one predictor drops inconsistency to zero (this is unlikely to ever occur). The fraction will be 0 if dropping predictors has no effect.

Parameters:
  • engine (Engine) – The Engine used to compute inconsistency

  • values (polars or pandas DataFrame or Series) – The values over which to compute the inconsistency. Each row of the DataFrame, or each entry of the Series, is an observation. Column names (or the Series name) should correspond to names of features in the table.

  • given (dict[index, value], optional) – A dictionary mapping column indices/name to values, which specifies conditions on the observations.

  • quiet (bool) – Prevent the display of a progress bar.

  • greedy (bool) – Use a greedy algorithm which is faster but may be less optimal.

Returns:

  • float – The fraction [0, 1] of the inconsistency that is attributable

  • polars.DataFrame – The result of held_out_inconsistency

Examples

>>> import polars as pl
>>> from lace.examples import Satellites
>>> from lace.analysis import attributable_inconsistency
>>> satellites = Satellites()
>>> given = (
...     satellites.df.to_pandas()
...     .set_index("ID")
...     .loc["Intelsat 903", :]
...     .dropna()
...     .to_dict()
... )
>>> period = given.pop("Period_minutes")
>>> frac, df = attributable_inconsistency(
...     satellites,
...     pl.Series("Period_minutes", [period]),
...     given,
...     quiet=True,
... )  
>>> frac
0.2930260843667006
lace.analysis.attributable_neglogp(engine: Engine, values, given: dict[Union[str, int], Any], quiet: bool = False, greedy: bool = True) Tuple[float, DataFrame]

Determine what fraction of surprisal (-log p) is attributable.

The fraction will be higher if dropping fewer predictor reduces surprisal quickly. The fraction will be 1 if one predictor drops surprisal to zero (this can never occur). The fraction will be 0 if dropping predictors has no effect.

Parameters:
  • engine (Engine) – The Engine used to compute inconsistency

  • values (polars or pandas DataFrame or Series) – The values over which to compute the -log p. Each row of the DataFrame, or each entry of the Series, is an observation. Column names (or the Series name) should correspond to names of features in the table.

  • given (dict[index, value], optional) – A dictionary mapping column indices/name to values, which specifies conditions on the observations.

  • quiet (bool) – Prevent the display of a progress bar.

  • greedy (bool) – Use a greedy algorithm which is faster but may be less optimal.

Returns:

  • float – The fraction [0, 1] of the surprisal that is attributable

  • polars.DataFrame – The result of held_out_neglogp

Examples

>>> import polars as pl
>>> from lace.examples import Satellites
>>> from lace.analysis import attributable_neglogp
>>> satellites = Satellites()
>>> given = (
...     satellites.df.to_pandas()
...     .set_index("ID")
...     .loc["Intelsat 903", :]
...     .dropna()
...     .to_dict()
... )
>>> period = given.pop("Period_minutes")
>>> frac, df =  attributable_neglogp(
...     satellites,
...     pl.Series("Period_minutes", [period]),
...     given,
...     quiet=True,
... )  
>>> frac
0.29302608436670047
lace.analysis.attributable_uncertainty(engine: Engine, target: str | int, given: dict[Union[str, int], Any], quiet: bool = False, greedy: bool = True) Tuple[float, DataFrame]

Determine what fraction of uncertainty is attributable.

The fraction will be higher if dropping fewer predictor reduces uncertainty quickly. The fraction will be 1 if one predictor drops uncertainty to zero (this is unlikely). The fraction will be 0 if dropping predictors has no effect.

Parameters:
  • engine (Engine) – The Engine used to compute inconsistency

  • target (str or int) – The prediction target

  • given (dict[index, value], optional) – A dictionary mapping column indices/name to values, which specifies conditions on the observations.

  • quiet (bool) – Prevent the display of a progress bar.

  • greedy (bool) – Use a greedy algorithm which is faster but may be less optimal.

Returns:

  • float – The fraction [0, 1] of the uncertainty that is attributable

  • polars.DataFrame – The result of held_out_uncertainty

Examples

>>> import polars as pl
>>> from lace.examples import Satellites
>>> from lace.analysis import attributable_uncertainty
>>> satellites = Satellites()
>>> given = (
...     satellites.df.to_pandas()
...     .set_index("ID")
...     .loc["Intelsat 903", :]
...     .dropna()
...     .to_dict()
... )
>>> period = given.pop("Period_minutes")
>>> frac, df =  attributable_uncertainty(
...     satellites,
...     "Period_minutes",
...     given,
...     quiet=True,
... )  
>>> frac
0.1814171785207335
lace.analysis.explain_prediction(engine: Engine, target: int | str, given: dict[Union[str, int], Any], *, method: str | None = None)

Explain the relevance of each predictor when predicting a target.

Parameters:
  • engine (lace.Engine) – The source engine

  • target (str, int) – The target variable – the variable to predict

  • given (Dict[index, value], optional) – A dictionary mapping column indices/name to values, which specifies conditions on the observations.

  • method (str, optional) –

    The method to use for explanation: * ‘ablative-err’ (default): computes the different between p(y|X) and

    p(x|X - xᵢ) for each predictor xᵢ in the given, X.

    • ’ablative-dist’: computed the error between the predictions (argmax) of p(y|X) and p(x|X - xᵢ) for each predictor xᵢ in the given, X. Note that this method does not support categorical targets.

Returns:

  • cols (List[str]) – The column names associated with each importance

  • imps (List[float]) – The list of importances for each column

Examples

>>> import polars as pl
>>> from lace.examples import Satellites
>>> from lace.analysis import explain_prediction
>>> engine = Satellites()

Define a target

>>> target = 'Period_minutes'

We’ll use a row from the data

>>> row = engine[5, :].to_dicts()[0]
>>> ix = row.pop('index')
>>> _ = row.pop(target)
>>> given = { k: v for k, v in row.items() if v is not None }

The default importance method, ‘ablative-err’, measures the error between the baseline predictive distribution, and the distribution when a predictor is dropped.

>>> cols, imps = explain_prediction(
...     engine,
...     target,
...     given,
... )  
>>> pl.DataFrame({'col': cols, 'imp': imps})
shape: (18, 2)
┌──────────────────────────────┬─────────────┐
│ col                          ┆ imp         │
│ ---                          ┆ ---         │
│ str                          ┆ f64         │
╞══════════════════════════════╪═════════════╡
│ Country_of_Operator          ┆ 2.4617e-16  │
│ Users                        ┆ -2.1412e-15 │
│ Purpose                      ┆ -8.0193e-15 │
│ Class_of_Orbit               ┆ -2.2727e-15 │
│ …                            ┆ …           │
│ Launch_Site                  ┆ -5.8214e-16 │
│ Launch_Vehicle               ┆ -9.6101e-16 │
│ Source_Used_for_Orbital_Data ┆ -9.1997e-15 │
│ Inclination_radians          ┆ -1.5407e-15 │
└──────────────────────────────┴─────────────┘

Get the importances using the ‘ablative-dist’ method, which measures how much the prediction would change if a predictor was dropped.

>>> cols, imps = explain_prediction(
...     engine,
...     target,
...     given,
...     method='ablative-dist'
... )  
>>> pl.DataFrame({'col': cols, 'imp': imps})
shape: (18, 2)
┌──────────────────────────────┬───────────┐
│ col                          ┆ imp       │
│ ---                          ┆ ---       │
│ str                          ┆ f64       │
╞══════════════════════════════╪═══════════╡
│ Country_of_Operator          ┆ -0.000109 │
│ Users                        ┆ 0.081289  │
│ Purpose                      ┆ 0.18938   │
│ Class_of_Orbit               ┆ 0.000119  │
│ …                            ┆ …         │
│ Launch_Site                  ┆ 0.003411  │
│ Launch_Vehicle               ┆ -0.018817 │
│ Source_Used_for_Orbital_Data ┆ 0.001454  │
│ Inclination_radians          ┆ 0.057333  │
└──────────────────────────────┴───────────┘
lace.analysis.held_out_inconsistency(engine: Engine, values, given: dict[Union[str, int], Any], quiet: bool = False, greedy: bool = True) DataFrame

Compute inconsistency for values while sequentially dropping given conditions.

Parameters:
  • engine (Engine) – The Engine used to compute inconsistency

  • values (polars or pandas DataFrame or Series) – The values over which to compute the inconsistency. Each row of the DataFrame, or each entry of the Series, is an observation. Column names (or the Series name) should correspond to names of features in the table.

  • given (dict[index, value], optional) – A dictionary mapping column indices/name to values, which specifies conditions on the observations.

  • quiet (bool) – Prevent the display of a progress bar.

  • greedy (bool) – Use a greedy algorithm which is faster but may be less optimal.

Returns:

A DataFrame with a ‘feature’ column and a ‘-logp’ column.

Return type:

polars.DataFrame

Examples

>>> import polars as pl
>>> from lace.examples import Satellites
>>> from lace.analysis import held_out_inconsistency
>>> satellites = Satellites()
>>> given = (
...     satellites.df.to_pandas()
...     .set_index("ID")
...     .loc["Intelsat 903", :]
...     .dropna()
...     .to_dict()
... )
>>> period = given.pop("Period_minutes")
>>> held_out_inconsistency(
...     satellites,
...     pl.Series("Period_minutes", [period]),
...     given,
...     quiet=True,
... )  
shape: (19, 3)
┌─────────────────────────┬───────────────────────────┬───────────┐
│ feature_rmed            ┆ HoldOutFunc.Inconsistency ┆ keys_rmed │
│ ---                     ┆ ---                       ┆ ---       │
│ list[str]               ┆ f64                       ┆ i64       │
╞═════════════════════════╪═══════════════════════════╪═══════════╡
│ null                    ┆ 1.973348                  ┆ 0         │
│ ["Apogee_km"]           ┆ 1.284557                  ┆ 1         │
│ ["Eccentricity"]        ┆ 0.740964                  ┆ 2         │
│ ["Launch_Vehicle"]      ┆ 0.740964                  ┆ 3         │
│ …                       ┆ …                         ┆ …         │
│ ["Power_watts"]         ┆ 0.741036                  ┆ 15        │
│ ["Inclination_radians"] ┆ 0.741448                  ┆ 16        │
│ ["Users"]               ┆ 0.743201                  ┆ 17        │
│ ["Perigee_km"]          ┆ 1.0                       ┆ 18        │
└─────────────────────────┴───────────────────────────┴───────────┘

If we don’t want to use the greedy search, we can enumerate, but we need to be mindful that the number of conditions we must enumerate over is 2^n

>>> keys = sorted(list(given.keys()))
>>> _ = [given.pop(c) for c in keys[-10:]]
>>> held_out_inconsistency(
...     satellites,
...     pl.Series("Period_minutes", [period]),
...     given,
...     quiet=True,
...     greedy=False,
... )  
shape: (9, 3)
┌───────────────────────────────────┬───────────────────────────┬───────────┐
│ feature_rmed                      ┆ HoldOutFunc.Inconsistency ┆ keys_rmed │
│ ---                               ┆ ---                       ┆ ---       │
│ list[str]                         ┆ f64                       ┆ i64       │
╞═══════════════════════════════════╪═══════════════════════════╪═══════════╡
│ null                              ┆ 1.984823                  ┆ 0         │
│ ["Apogee_km"]                     ┆ 1.290609                  ┆ 1         │
│ ["Apogee_km", "Eccentricity"]     ┆ 0.74598                   ┆ 2         │
│ ["Apogee_km", "Country_of_Operat… ┆ 0.745877                  ┆ 3         │
│ ["Apogee_km", "Country_of_Operat… ┆ 0.746268                  ┆ 4         │
│ ["Apogee_km", "Country_of_Contra… ┆ 0.747133                  ┆ 5         │
│ ["Apogee_km", "Country_of_Contra… ┆ 0.749297                  ┆ 6         │
│ ["Apogee_km", "Country_of_Contra… ┆ 0.756218                  ┆ 7         │
│ ["Apogee_km", "Class_of_Orbit", … ┆ 1.0                       ┆ 8         │
└───────────────────────────────────┴───────────────────────────┴───────────┘
lace.analysis.held_out_neglogp(engine: Engine, values, given: dict[Union[str, int], Any], quiet: bool = False, greedy: bool = True) DataFrame

Compute -logp for values while sequentially dropping given conditions.

Parameters:
  • engine (Engine) – The Engine used to compute logp

  • values (polars or pandas DataFrame or Series) – The values over which to compute the log likelihood. Each row of the DataFrame, or each entry of the Series, is an observation. Column names (or the Series name) should correspond to names of features in the table.

  • given (dict[index, value], optional) – A dictionary mapping column indices/name to values, which specifies conditions on the observations.

  • quiet (bool) – Prevent the display of a progress bar.

  • greedy (bool) – Use a greedy algorithm which is faster but may be less optimal.

Returns:

A DataFrame with a ‘feature’ column and a ‘-logp’ column.

Return type:

polars.DataFrame

Examples

>>> import polars as pl
>>> from lace.examples import Satellites
>>> from lace.analysis import held_out_neglogp
>>> satellites = Satellites()
>>> given = (
...     satellites.df.to_pandas()
...     .set_index("ID")
...     .loc["Intelsat 903", :]
...     .dropna()
...     .to_dict()
... )
>>> period = given.pop("Period_minutes")
>>> held_out_neglogp(
...     satellites,
...     pl.Series("Period_minutes", [period]),
...     given,
...     quiet=True,
... )  
shape: (19, 3)
┌─────────────────────────┬─────────────────────┬───────────┐
│ feature_rmed            ┆ HoldOutFunc.NegLogp ┆ keys_rmed │
│ ---                     ┆ ---                 ┆ ---       │
│ list[str]               ┆ f64                 ┆ i64       │
╞═════════════════════════╪═════════════════════╪═══════════╡
│ null                    ┆ 7.808063            ┆ 0         │
│ ["Apogee_km"]           ┆ 5.082683            ┆ 1         │
│ ["Eccentricity"]        ┆ 2.931816            ┆ 2         │
│ ["Launch_Vehicle"]      ┆ 2.931816            ┆ 3         │
│ …                       ┆ …                   ┆ …         │
│ ["Power_watts"]         ┆ 2.932103            ┆ 15        │
│ ["Inclination_radians"] ┆ 2.933732            ┆ 16        │
│ ["Users"]               ┆ 2.940667            ┆ 17        │
│ ["Perigee_km"]          ┆ 3.956759            ┆ 18        │
└─────────────────────────┴─────────────────────┴───────────┘

If we don’t want to use the greedy search, we can enumerate, but we need to be mindful that the number of conditions we must enumerate over is 2^n

>>> keys = sorted(list(given.keys()))
>>> _ = [given.pop(c) for c in keys[-10:]]
>>> held_out_neglogp(
...     satellites,
...     pl.Series("Period_minutes", [period]),
...     given,
...     quiet=True,
...     greedy=False,
... )  
shape: (9, 3)
┌───────────────────────────────────┬─────────────────────┬───────────┐
│ feature_rmed                      ┆ HoldOutFunc.NegLogp ┆ keys_rmed │
│ ---                               ┆ ---                 ┆ ---       │
│ list[str]                         ┆ f64                 ┆ i64       │
╞═══════════════════════════════════╪═════════════════════╪═══════════╡
│ null                              ┆ 7.853468            ┆ 0         │
│ ["Apogee_km"]                     ┆ 5.106627            ┆ 1         │
│ ["Apogee_km", "Eccentricity"]     ┆ 2.951662            ┆ 2         │
│ ["Apogee_km", "Country_of_Operat… ┆ 2.951254            ┆ 3         │
│ ["Apogee_km", "Country_of_Operat… ┆ 2.952801            ┆ 4         │
│ ["Apogee_km", "Country_of_Contra… ┆ 2.956224            ┆ 5         │
│ ["Apogee_km", "Country_of_Contra… ┆ 2.96479             ┆ 6         │
│ ["Apogee_km", "Country_of_Contra… ┆ 2.992173            ┆ 7         │
│ ["Apogee_km", "Class_of_Orbit", … ┆ 3.956759            ┆ 8         │
└───────────────────────────────────┴─────────────────────┴───────────┘
lace.analysis.held_out_uncertainty(engine: Engine, target: str | int, given: dict[Union[str, int], Any], quiet: bool = False, greedy: bool = True) DataFrame

Compute prediction uncertainty while sequentially dropping given conditions.

Parameters:
  • engine (Engine) – The Engine used to compute inconsistency

  • target (str or int) – The target column for prediction

  • given (dict[index, value], optional) – A dictionary mapping column indices/name to values, which specifies conditions on the observations.

  • quiet (bool) – Prevent the display of a progress bar.

  • greedy (bool) – Use a greedy algorithm which is faster but may be less optimal.

Returns:

A DataFrame with a ‘feature’ column and a uncertainty column.

Return type:

polars.DataFrame

Examples

>>> import polars as pl
>>> from lace.examples import Satellites
>>> from lace.analysis import held_out_uncertainty
>>> satellites = Satellites()
>>> given = (
...     satellites.df.to_pandas()
...     .set_index("ID")
...     .loc["Intelsat 903", :]
...     .dropna()
...     .to_dict()
... )
>>> period = given.pop("Period_minutes")
>>> held_out_uncertainty(
...     satellites,
...     "Period_minutes",
...     given,
...     quiet=True,
... )  
shape: (19, 3)
┌──────────────────────────────────┬─────────────────────────┬───────────┐
│ feature_rmed                     ┆ HoldOutFunc.Uncertainty ┆ keys_rmed │
│ ---                              ┆ ---                     ┆ ---       │
│ list[str]                        ┆ f64                     ┆ i64       │
╞══════════════════════════════════╪═════════════════════════╪═══════════╡
│ null                             ┆ 0.43212                 ┆ 0         │
│ ["Perigee_km"]                   ┆ 0.43212                 ┆ 1         │
│ ["Class_of_Orbit"]               ┆ 0.43212                 ┆ 2         │
│ ["Source_Used_for_Orbital_Data"] ┆ 0.431921                ┆ 3         │
│ …                                ┆ …                       ┆ …         │
│ ["Country_of_Operator"]          ┆ 0.054156                ┆ 15        │
│ ["Country_of_Contractor"]        ┆ 0.06069                 ┆ 16        │
│ ["Dry_Mass_kg"]                  ┆ 0.139502                ┆ 17        │
│ ["Inclination_radians"]          ┆ 0.089026                ┆ 18        │
└──────────────────────────────────┴─────────────────────────┴───────────┘

If we don’t want to use the greedy search, we can enumerate, but we need to be mindful that the number of conditions we must enumerate over is 2^n

>>> keys = sorted(list(given.keys()))
>>> _ = [given.pop(c) for c in keys[-10:]]
>>> held_out_uncertainty(
...     satellites,
...     "Period_minutes",
...     given,
...     quiet=True,
...     greedy=False,
... )  
shape: (9, 3)
┌───────────────────────────────────┬─────────────────────────┬───────────┐
│ feature_rmed                      ┆ HoldOutFunc.Uncertainty ┆ keys_rmed │
│ ---                               ┆ ---                     ┆ ---       │
│ list[str]                         ┆ f64                     ┆ i64       │
╞═══════════════════════════════════╪═════════════════════════╪═══════════╡
│ null                              ┆ 0.445501                ┆ 0         │
│ ["Expected_Lifetime"]             ┆ 0.437647                ┆ 1         │
│ ["Apogee_km", "Eccentricity"]     ┆ 0.05561                 ┆ 2         │
│ ["Apogee_km", "Country_of_Operat… ┆ 0.055283                ┆ 3         │
│ ["Apogee_km", "Country_of_Operat… ┆ 0.056185                ┆ 4         │
│ ["Apogee_km", "Country_of_Operat… ┆ 0.057624                ┆ 5         │
│ ["Apogee_km", "Country_of_Contra… ┆ 0.0595                  ┆ 6         │
│ ["Apogee_km", "Country_of_Contra… ┆ 0.077359                ┆ 7         │
│ ["Apogee_km", "Class_of_Orbit", … ┆ 0.089026                ┆ 8         │
└───────────────────────────────────┴─────────────────────────┴───────────┘