Repository: evidentlyai/evidently
Issue: Jensen-Shannon distance should be computed using base 2 logarithm for it to be bound between 0 and 1
Root cause: Algorithmic Error

Your [current implementation for the Jensen-Shannon Distance (JSD)](https://github.com/evidentlyai/evidently/blob/main/src/evidently/calculations/stattests/jensenshannon.py) is leveraging [scipy.spatial.distance.jensenshannon](https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.jensenshannon.html), which defaults to computing the JSD using logarithms with base e. Under this definition, the JSD is bound between 0 and sqrt(ln(2))=0.83255... (see, for example, the [JSD Wikipedia article](https://en.wikipedia.org/wiki/Jensen%E2%80%93Shannon_divergence))

I believe you want to compute JSD using base 2 logarithms, so that it is bound between 0 and 1 as you state in your [data drift detection blog post](https://www.evidentlyai.com/blog/data-drift-detection-large-datasets).

Here is code which reproduces this issue by computing the JSD between two extremely drifted distributions: 
```
from evidently.calculations.stattests import jensenshannon_stat_test
import numpy as np
import pandas as pd

x = pd.Series(np.random.normal(100, 10, 100_000))
y = pd.Series(np.random.normal(10_000, 10, 100_000))

print(jensenshannon_stat_test(x, y, feature_type='num',threshold=0.1))
>>> StatTestResult(drift_score=0.8325546111576977, drifted=True, actual_threshold=0.1)
```
Note that the `drift_score` is close to the current maximum theoretical value of `sqrt(ln(2))=0.83255...`, instead of the desired value of `1`. 


Hi @perezbecker ,

Thank you for mentioning that! We have so many nonnormalized drift detection methods (like PSI) that we have given up trying to normalize everything🙈 But in vain! :) I agree that many users will be happy to use the normalized JS test. The only thing is that we will keep the base e by default for now because the selected default thresholds for drift detection do not take into account normalization.



Hey @SangamSwadiK ,
Thank you for the contribution! We will merge it soon:)

Hi @perezbecker,

We recently merged the update to JensenShannon distance. Now ```_jensenshannon``` accept logarithm base as a parameter (Thanks to @SangamSwadiK !).

Unfortunately, it is not possible to specify ```base``` parameter directly when specifying drift detection method in ```Reports``` or ```TestSuites```, because we do not accept any parameters there. We will consider accepting parameters for drift calculation later. However, there are two ways how you can already specify the base for the logarithm.

Option 1: use mechanism to pass any python function as ```stattest``` in ```ColumnDrift``` or ```DataDrfitTable``` metrics. You can create a function-wrapper and set ```base=2``` there.  Here is an example:
```
from evidently.report import Report
from evidently.metrics import DataDriftTable
from evidently.calculations.stattests.jensenshannon import _jensenshannon

def _normalized_js(reference_data: pd.Series, current_data: pd.Series, feature_type: str, threshold: float):
    return _jensenshannon(reference_data, current_data, feature_type='num', threshold=0.1, base=2)

data_drift_column_report = Report(metrics=[
    DataDriftTable(num_stattest=_normalized_js), 
])

data_drift_column_report.run(reference_data=reference_data, current_data=current_data)
data_drift_column_report
```

The only thing is that you will not see any drift detection method name displayed in the DataDrift table.

Option 2: To specify the name for your custom  drift detection method, use ```SatatTest```  object from evidently: 
```
from evidently.report import Report
from evidently.metrics import DataDriftTable
from evidently.calculations.stattests import StatTest 
from evidently.calculations.stattests.jensenshannon import _jensenshannon

def _normalized_js(reference_data: pd.Series, current_data: pd.Series, feature_type: str, threshold: float):
    return _jensenshannon(reference_data, current_data, feature_type='num', threshold=0.1, base=2)

normalized_js_test = StatTest(
    name="normalized_js",
    display_name="Normalized JS",
    func=_normalized_js,
    allowed_feature_types=["num"],
    default_threshold=0.1
)

data_drift_column_report = Report(metrics=[
    DataDriftTable(num_stattest=normalized_js_test), 
])

data_drift_column_report.run(reference_data=reference_data, current_data=current_data)
data_drift_column_report
```

Thank you @emeli-dral and @SangamSwadiK for adding the base parameter to the function and providing these instructions!