Mohammad Borhan UddinMain
← Appendix B

ল্যাব কোড : পাইথন দিয়ে শ্যানন এন্ট্রপি এবং মিউচুয়াল ইনফরমেশন বের করা

8 January 2025 · 4 min read

Pandas এবং NumPy লাইব্রেরী দুইটি ইন্সটল থাকতে হবে । যদি ইন্সটল না থাকে তাহলে ইন্সটল করে নিতে cmd-তে গিয়ে লিখতে হবে,

pip install pandas numpy 


শ্যানন এন্ট্রপি :

H(X) = - \sum_{i=1}^{n} p(x_i) \log_b p(x_i)

অ্যালগরিদম:

  • প্রথমে নির্দিষ্ট (csv or others) ফাইলটা রিড করতে হবে
import pandas as pd
import numpy as np
data = pd.read_csv("data/^TWII.csv")
  • তারিখ অনুযায়ী সর্ট করবো
data.loc[:, 'Date'] = pd.to_datetime(data['Date'])
data.sort_values(by='Date', ascending=True, inplace=True)

  • এই ধাপে, আমরা যে কলামের ভ্যালু (ধরলাম Close) নিয়ে কাজ করতে চাই , তাই এর ভ্যালুকে নাম্বারে কাস্টিং করেছি আর যেসব ভ্যালু নাম্বার নয় বা নাম্বারে পরিণত করা যায় না তাদের NaN (not a number) হিসেবে ধরে নিয়েছি ।
data['Close'] = pd.to_numeric(data['Close'], errors='coerce')
  • চাইলে আমরা সব NaN ভ্যালুকে বাদ দিতে পারি ।
data = data.dropna(subset=['Close'])
  • এবার আমরা Rt বের করব এবং ঐ ভ্যালুটাই column ভ্যারিয়েবলে রেখে দিব । আর নাল ভ্যালু থাকলে তা বাদ দিয়ে দিবো ।
    R_t = \log(\text{Column}[i]) - \log(\text{Column}[i-1])
column= np.log(data['Close']) - np.log(data['Close'].shift(1))
column = column.dropna()
  • এবার এন্ট্রপি বের করার পালা । এর জন্য আমরা এন্ট্রপি নামের একটা ফাংশন বানাবো । আমাদের Close এর ডেটাগুলা continuous । তাই আমরা এগুলোকে বেশ কয়েকটি গ্রুপে ভাগ করে ফেলবো (যেভাবে আমরা নবম-দশম শ্রেণিতে করতাম), এই পদ্ধতির একটা নাম আছে “ডেটা বিনিং” বা “ডিসক্রেটাইজেশন” । আমরা কয়টা গ্রুপে ভাগ করতে চাই, সেই সংখ্যাকে বললাম “Bins” বা ‘শ্রেণি সংখ্যা’ । তারপর প্রত্যেকটা বিনের রেঞ্জ (bin_edges ভ্যারিয়েবলে রাখা হয়েছে) এবং ফ্রিকুয়েন্সি (ঐ বিন বা রেঞ্জের মধ্যে কয়টি ভ্যালু আছে, binned_dist ভ্যারিয়েবলে রাখা হয়েছে) তা বের করার জন্য ব্যবহার করবো NumPy এর histogram ফাংশনটা । তারপর কোন একটা নির্দিষ্ট বিন বা রেঞ্জের probability নির্ণয়ের জন্য ঐ বিনের ফ্রিকুয়েন্সিকে সব বিনের ফ্রিকুয়েন্সির যোগফল দিয়ে ভাগ করতে হয় । তাই আমরা সব বিনের probability কাউন্ট করে একটি অ্যারে (probs) এ রেখে দিব ।
def entropy(column, bins) :
    binned_dist, bin_edges = np.histogram(column, bins)
    probs = binned_dist / np.sum(binned_dist)

bin_edges অথবা রেঞ্জ দেখানোর ভ্যারিয়েবলটা এভাবেই হয়ে থাকে, [0.09237332, 0.12021244, 0.148155, 0.17589067] । যেখান থেকে সহজেই অনুমান করা যায় বিনগুলোর রেঞ্জ কত থেকে কত এর মধ্যে হবে । যেমন একদম প্রথম বিনের রেঞ্জ হবে 0.09237332 থেকে 0.12021244 এর আগ পর্যন্ত । যেটা লেখা যায় [ 0.09237332, 0.12021244) । বাকিগুলোও এভাবেই পাওয়া যায় ।

  • Probability যেহেতু আছে তাহলে আমরা Shannon entropy বের করে নিতে পারি । তবে এর আগেই আমাদের সেসব Probability 0 তা বাদ দিয়ে দিতে হবে, কেননা log2 (0) = Undefined । তারপর Shannon Entropy’র সূত্র বসিয়ে দিলে হয়ে যাবে ।
def entropy(column, bins) :
    binned_dist, bin_edges = np.histogram(column, bins)
    probs = binned_dist / np.sum(binned_dist)
    probs = probs[np.nonzero(probs)]
    entropy = 0
    for i in range(len(probs)):
        entropy -= probs[i] * np.log2(probs[i]);
    return entropy
  • এখন আমরা entropy ফাংশনটা ব্যবহার করতে পারি
print("Entropy :",  entropy(column, 12).round(4))

এবার বেশ কিছু “অতিরিক্ত” আউটপুটসহ পুরো কোডটি রিভিউ করা যাক ।

import pandas as pd
import numpy as np

data = pd.read_csv("data/canada.csv")
data.loc[:, 'Date'] = pd.to_datetime(data['Date'])
data.sort_values(by='Date', ascending=True, inplace=True)

data['Close'] = pd.to_numeric(data['Close'], errors='coerce')
column= np.log(data['Close']) - np.log(data['Close'].shift(1))
column = column.dropna()

print(f"Max Value : {column.max()}")
print(f"Min Value : {column.min()}")
print(f"Differece : {column.max() - column.min()}")
print("Bins : 12")

def entropy(column, bins) :
    binned_dist, bin_edges = np.histogram(column, bins)
    probs = binned_dist / np.sum(binned_dist)
    bin_table = pd.DataFrame({
        'Symbol' : [f"{i}" for i in range(1, len(bin_edges))],
        'Bin Range': [f"[{bin_edges[i]:.4f}, {bin_edges[i+1]:.4f})" for i in range(len(bin_edges)-1)],
        'Frequency': binned_dist,
        'Probability': probs
    });
    probs = probs[np.nonzero(probs)]
    print(bin_table)
    entropy = 0
    for i in range(len(probs)):
        entropy -= probs[i] * np.log2(probs[i]);
    return entropy

print("Entropy :",  entropy(column, 12).round(4))

মিউচুয়াল ইনফরমেশন

I(X; Y) = H(X) + H(Y) - H(X, Y)

H(X, Y) যা Joint Entropy বোঝায় । এর জন্য আমাদের X এবং Y এর জয়েন্ট Probability জানা লাগবে । তাই আগের মতোই তবে আরেকটি ফাংশন ব্যবহার করে আমরা তা করতে পারি । প্রথমে X,Y এর একসাথে রেঞ্জ বা বিনগুলোর কম্বিনেশন এবং এদের ফ্রিকুয়েন্সি জানা লাগবে । এর জন্য আমরা ব্যবহার করবো histogram2d() ফাংশন । আর বাকি প্রসেস ঠিক আগের মতোই । histogram2d() প্রথমে একটা 2D অ্যারে রিটার্ণ করে (যা X এবং Y এর কোন নির্দিষ্ট রেঞ্জের মধ্যে ফ্রিক্যুয়েন্সি বোঝায়, binned_XY ভ্যারিয়েবলে রাখা হয়েছে), তারপর একটা অ্যারেতে X এর রেঞ্জ (binnedX ভ্যারিয়েবল) এবং সবশেষে আরেকটা অ্যারেতে Y এর রেঞ্জ (binnedY ভ্যারিয়েবল)। জয়েন্ট এন্ট্রপি বের করার কোড:

def jointEntropy(X, Y, bins):
    binned_XY, binnedX, binnedY = np.histogram2d(X, Y, bins)
    probsXY = binned_XY / np.sum(binned_XY)
   
    probsXY = probsXY[np.nonzero(probsXY)]
    jointEntropy = -np.sum(probsXY * np.log2(probsXY))
    return jointEntropy

# Calling this function
joint = jointEntropy(X, Y, bins);
print(f"Joint Entropy H(X, Y) : {joint:.4f}");

নিচে দুইটি ভিন্ন ফাইল থেকে ডাটা নিয়ে মিউচুয়াল ইনফরমেশন বের করার সম্পূর্ণ কোড দেওয়া হলো:

import pandas as pd
import numpy as np

# X
data = pd.read_csv("data/^SSMI_swizerland.csv");
X = data[['Date', 'Close']]
X = X.copy()

X.loc[:, 'Date'] = pd.to_datetime(X['Date'])
X.loc[:, 'Close'] = np.log(X['Close']) - np.log(X['Close'].shift(1));
X = X[X['Close'].notna()];
X.sort_values(by='Date', ascending=True, inplace=True)
X = X.dropna()

# Y
data2 = pd.read_csv("data/^TWII.csv");
Y = data2[['Date', 'Close']];
Y = Y.copy()

Y.loc[:, 'Date'] = pd.to_datetime(Y['Date'])
Y['Close'] = pd.to_numeric(Y['Close'], errors='coerce')

Y.loc[:, 'Close']  = np.log(Y['Close']) - np.log(Y['Close'].shift(1))   
Y = Y[Y['Close'].notna()];
Y.sort_values(by='Date', ascending=True, inplace=True)
Y = Y.dropna()

# Merge
XY = pd.merge(X, Y, on='Date', how='inner');

def entropy(X, bins):
    print(f"Max Value : {X.max() : .4f}")
    print(f"Min Value : {X.min() : .4f}")
    print(f"Differece : {X.max() - X.min() : .4f}")
    print(f"Bins : {bins}")
    binned_dist, bin_edges = np.histogram(X, bins)
    probs = binned_dist / np.sum(binned_dist)
    

    bin_table = pd.DataFrame({
        'Symbol' : [f"{i}" for i in range(1, len(bin_edges))],
        'Bin Range': [f"[{bin_edges[i]:.4f}, {bin_edges[i+1]:.4f})" for i in range(len(bin_edges)-1)],
        'Frequency': binned_dist,
        'Probability': probs
    });
    print(bin_table)
    probs = probs[np.nonzero(probs)]
    entropy = -np.sum(probs * np.log2(probs))
   
    return entropy

def jointEntropy(X, Y, bins):
    binned_XY, binnedX, binnedY = np.histogram2d(X, Y, bins)
    probsXY = binned_XY / np.sum(binned_XY)

    # Show the joint distribution
    rows = []
    for i in range(len(binnedX) - 1):
        for j in range(len(binnedY) - 1):
            rows.append({
                'Symbol' : f"({i+1},{j+1})",
                'X Bin': f"[{binnedX[i]:.4f}, {binnedX[i+1]:.4f})",
                'Y Bin': f"[{binnedY[j]:.4f}, {binnedY[j+1]:.4f})",
                'Frequency': binned_XY[i, j],
                'Probability': probsXY[i, j]
            })

    table = pd.DataFrame(rows)
    print(table)   

    probsXY = probsXY[np.nonzero(probsXY)]
    jointEntropy = -np.sum(probsXY * np.log2(probsXY))

    return jointEntropy

def mutualInformation(X, Y, bins) :
    print("X : ");
    entropyX = entropy(X, bins);
    print(f"Entropy X, H(X) : {entropyX:.4f}");
    print("\n\nY : ");
    entropyY = entropy(Y, bins);
    print(f"Entropy Y, H(Y) : {entropyY:.4f}");
    print("\nJoint : ");
    joint = jointEntropy(X, Y, bins);
    print(f"Joint Entropy H(X, Y) : {joint:.4f}");
    return entropyX + entropyY - joint

print(f"\n Mutual Information: {mutualInformation(XY['Close_x'], XY['Close_y'], 12) : .4f}");


Originally published on WordPress; comments live there.