Statistical_IV: J-Divergence Hypothesis Test for the Information Value (IV)
from distutils.core import setup
from setuptools import find_packages

from pathlib import Path
this_directory = Path(__file__).parent
long_description = (this_directory / "README.md").read_text()

setup(
  name = 'statistical_iv',       
  packages = find_packages(), 
  version = '0.3.1',      # Start with a small number and increase it with every change you make
  license='MIT',        # Chose a license from here: https://help.github.com/articles/licensing-a-repository
  description = 'Statistical IV: Statistical Hypothesis Testing for the Information Value (IV). Evaluation of the predictive power of features using the IV with specific thresholds for each dataset.', #'Statistical IV: J-Divergence Hypothesis Test for the Information Value (IV)', # An statistical use of information value with a defined predictived power',   # Give a short description about your library
  long_description=long_description,
  long_description_content_type='text/markdown',
  author = ' Nilton Rojas, Helder Rojas',
  maintainer="Nilton Rojas Vales",
  maintainer_email="nrojasv@uni.pe",
  url = 'https://github.com/Nicerova7/statistical_iv',   # Provide either the link to your github or to your website
  keywords = ['information_value', 'woe', 'data science', 'hypothesis test'],   # Keywords that define your package best
  install_requires=[            # I get to this in a second
          'pandas',
          'numpy',
          'scipy',
          'optbinning'
      ],
  classifiers=[
    'Development Status :: 3 - Alpha',      # Chose either "3 - Alpha", "4 - Beta" or "5 - Production/Stable" as the current state of your package
    'Intended Audience :: Developers',
    'Intended Audience :: Science/Research',
    'Intended Audience :: Information Technology',      # Define that your audience are developers
    'Topic :: Scientific/Engineering',
    'Topic :: Software Development :: Build Tools',
    'License :: OSI Approved :: MIT License',   # Again, pick a license
    'Programming Language :: Python :: 3',      #Specify which pyhton versions that you want to support
    'Programming Language :: Python :: 3.8',
    'Programming Language :: Python :: 3.9',
    'Programming Language :: Python :: 3.10',
  ],
)
setup.py
Nicerova7/statistical_iv
import numpy as np
from optbinning import OptimalBinning
from scipy.stats import norm
import pandas as pd
from typing import List
from .helpers.consts import BinningParameters, Configuration, OutPut



def statistical_iv(df: pd.DataFrame, variables_names: List, var_y: str, type_vars:str, max_bins:int = 10)-> pd.DataFrame:
    """
    Calculates the Information Value (IV) for a set of predictor variables in relation to a target variable.

    Args:
        df (pd.DataFrame): DataFrame containing the data.
        variables_names (List): List of predictor variable names.
        var_y (str): Name of the target variable.
        type_vars (str): Type of variables ('categorical' or 'numerical').
        max_bins (int, optional): Maximum number of bins for discretization (default: 10).

    Returns:
        pd.DataFrame: A DataFrame with the results of the Statistical IV calculation for each variable.
            - variable: Variable name.
            - Ĵ-Estimate: Estimated Statistical IV value.
            - Std-error: Standard error.
            - p-value: Associated p-value.
    """
        
    variables = []
    js = []
    rvs = []
    pvls = []

    for var in variables_names:

        x_ = df[var].values
        y_ = df[var_y].values
        optb = OptimalBinning(name=var, dtype=type_vars, max_n_bins=max_bins)
        optb.fit(x_, y_)
        resume_table = optb.binning_table.build()
        
        if resume_table[BinningParameters.bin_column].loc[0] == BinningParameters.one_bin_fail and len(resume_table) == BinningParameters.max_table_one_bin_fail:
            variables.append(var)
            js.append(0)
            rvs.append(0)
            pvls.append(None)
        
        else:
            p = (resume_table[BinningParameters.event_column] / resume_table[BinningParameters.event_column][BinningParameters.totals_column]).iloc[:-3].values.reshape(-1, 1)
            q = (resume_table[BinningParameters.nonevent_column] / resume_table[BinningParameters.nonevent_column][BinningParameters.totals_column]).iloc[:-3].values.reshape(-1, 1)
        
            ## STEP 1: J-normal
            J = np.zeros((1, 1))
            J[:, 0] = np.sum((p-q) * np.log(p / q), axis=0) # Definition 1 in article

            # STEP 2: Calculating Sigma, corollary 1
            V1 = np.zeros((1, 1))
            V2 = np.zeros((1, 1))

            p_ = p[:, 0]
            q_ = q[:, 0]
            diag = 1
                
            matriz_productos = np.outer(p_, p_)
            matriz_log = 1 + np.log(p_ / q_)
            cross_matriz_log = np.outer(matriz_log, matriz_log)
            cross_matriz_log = np.triu(cross_matriz_log, k=diag)
            matriz_productos = np.triu(matriz_productos, k=diag)
            matriz_productos_q = np.outer(q_, q_)
            matriz_productos_q = np.triu(matriz_productos_q, k=diag)
            matriz_final = matriz_productos * cross_matriz_log + matriz_productos_q
            V1[0, 0] = np.sum(matriz_final)
            V1[:, 0] = np.sum(p * (1 - p) * ((1 + np.log(p / q)) ** 2 + (q / p) ** 2), axis=0) - 2 * V1[:, 0] # Theorem 2, (11)

            matriz_productos = np.outer(q_, q_)
            matriz_log = 1 + np.log(q_ / p_)
            cross_matriz_log = np.outer(matriz_log, matriz_log)
            cross_matriz_log = np.triu(cross_matriz_log, k=diag)
            matriz_productos = np.triu(matriz_productos, k=diag)
            matriz_productos_p = np.outer(p_, p_)
            matriz_productos_p = np.triu(matriz_productos_p, k=diag)
            matriz_final = matriz_productos * cross_matriz_log + matriz_productos_p
            V2[0, 0] = np.sum(matriz_final)
            V2[:, 0] = np.sum(q * (1 - q) * ((1 + np.log(q / p)) ** 2 + (p / q) ** 2), axis=0) - 2 * V2[:, 0] # Theorem 2, (12)

            n = resume_table[BinningParameters.event_column][BinningParameters.totals_column]
            m = resume_table[BinningParameters.nonevent_column][BinningParameters.totals_column]

            Sigma = (m * V1 + n * V2) / (n + m)

            ## STEP 3 : Variance
            Var = ((n + m) / (n * m)) * Sigma

            ## STEP 4 : Quantile
            Raiz_Var = np.sqrt(Var)
            rvs.append(np.round(Raiz_Var[0][0], Configuration.max_decimals))
            #alpha = 0.05
            #Q = norm.ppf(1 - alpha, loc=0, scale=Raiz_Var)
        
            variables.append(var)
            js.append(np.round(J[0][0], Configuration.max_decimals))

            ## STEP 5:
            mean = 0
            pvls.append(2*norm.cdf(-J[0][0], loc=mean, scale=Raiz_Var)[0][0])


    out_df = pd.DataFrame()
    out_df[OutPut.variable_column] = variables
    out_df[OutPut.jhat_column] = js
    out_df[OutPut.stderror_column] = rvs
    out_df[OutPut.pvalue_column] = pvls
    return out_df
statistical_iv/api.py
Nicerova7/statistical_iv
class BinningParameters:
    bin_column = 'Bin'
    event_column = 'Event'
    nonevent_column = 'Non-event'
    totals_column = 'Totals'
    one_bin_fail = '(-inf, inf)'
    max_table_one_bin_fail= 4

class OutPut:
    variable_column = 'variable'
    jhat_column = 'Ĵ-Estimate'
    stderror_column = 'Std-error'
    pvalue_column = 'p-value'

class Configuration:
    max_decimals = 6
statistical_iv/helpers/consts.py
Nicerova7/statistical_iv