main 2dcef05b087a cached
60 files
15.5 MB
520.1k tokens
137 symbols
1 requests
Download .txt
Showing preview only (969K chars total). Download the full file or copy to clipboard to get everything.
Repository: milaan9/Clustering_Algorithms_from_Scratch
Branch: main
Commit: 2dcef05b087a
Files: 60
Total size: 15.5 MB

Directory structure:
gitextract_1u4gek_a/

├── 01_MATLAB/
│   ├── Clustering.m
│   ├── README.md
│   ├── data/
│   │   ├── toy_clustering.mat
│   │   └── toy_subspace_clustering.mat
│   ├── lib/
│   │   ├── DBSCAN.m
│   │   ├── Entropy_Weighting_Subspace_Kmeans.m
│   │   ├── Gaussian_Mixture.m
│   │   ├── ISODATA.m
│   │   ├── Kmeans.m
│   │   ├── Kmeanspp.m
│   │   ├── LVQ.m
│   │   ├── Mean_Shift.m
│   │   └── Subspace_Kmeans.m
│   └── tool/
│       ├── GenerateDataset.m
│       └── PlotData.m
├── 02_Python/
│   ├── A_Star.py
│   ├── Collaborative_Filtering.py
│   ├── DBSCAN.py
│   ├── Decision_Trees.py
│   ├── Dimensionality_Reduction/
│   │   └── Dimensionality_Reduction.ipynb
│   ├── Discrete_Cosine_Transform_(DCT)/
│   │   └── Discrete_Cosine_Transform.ipynb
│   ├── FP_Growth.py
│   ├── Genetic_Algorithm.py
│   ├── K-Means_Implementation/
│   │   ├── Iris.csv
│   │   ├── K-Means_Clustering_on_Iris_Dataset.ipynb
│   │   ├── Links.txt
│   │   └── README.md
│   ├── K_Means.py
│   ├── K_Nearest_Neighbours.py
│   ├── K_Nearest_Neighbours_In_Parallel.py
│   ├── Linear_Regression.py
│   ├── Logistic_Regression.py
│   ├── MSCRED/
│   │   ├── cnn_lstm/
│   │   │   ├── Untitled.ipynb
│   │   │   ├── __init__.py
│   │   │   ├── convlstm-update.py
│   │   │   ├── convlstm.ipynb
│   │   │   ├── convlstm.py
│   │   │   ├── evaluation.ipynb
│   │   │   ├── evalution.py
│   │   │   ├── generation_signature_matrice.ipynb
│   │   │   ├── generation_signature_matrice.py
│   │   │   ├── report.txt
│   │   │   └── utils.py
│   │   └── data/
│   │       └── synthetic_data_with_anomaly-s-1.csv
│   ├── Mean_Shift.py
│   ├── Naive_Bayes.py
│   ├── README.md
│   ├── Random_Forest_Classifier.py
│   ├── Support_Vector_Machine.py
│   └── data/
│       ├── chronic_kidney_disease.csv
│       ├── concentric_circles.csv
│       ├── cron_jobs_schedule.csv
│       ├── graph.in
│       ├── ipl.csv
│       ├── iris.csv
│       ├── logistic_regression_data.txt
│       ├── titanic-subset.csv
│       └── titanic.csv
├── LICENSE
└── README.md

================================================
FILE CONTENTS
================================================

================================================
FILE: 01_MATLAB/Clustering.m
================================================
function [centroid, result] = Clustering(data, method, varargin)
% Currently, following clustering algorithms are supported:
%   1. Kmeans
%   2. Kmeans++
%   3. ISODATA (Iterative Self-Organizing Data Analysis)
%   4. Mean Shift
%   5. DBSCAN (Density-Based Spatial Clustering of Application with Noise)
%   6. Gaussian Mixture Model /* Numerical instability problem not completely solved*/
%   7. LVQ (Learning Vector Quantization)

addpath('.\lib');
addpath('.\tool');

% Validate input parameters
if((strcmp(method,'kmeans') || strcmp(method,'kmeans++')) && (size(varargin,2) ~= 2))
    error('The value of K should be predefined when using k-means and k-means++.');
elseif((strcmp(method,'isodata') || strcmp(method,'ISODATA')) && (size(varargin,2) < 5))
    error('Not enough input arguments for ISODATA. Please use help for more information.');
elseif((strcmp(method,'mean_shift') || strcmp(method,'Mean_Shift')) && (size(varargin,2) ~= 1))
    error('Invalid number of input arguments for mean shift.');
elseif((strcmp(method,'dbscan') || strcmp(method,'DBSCAN')) && (size(varargin, 2) ~= 2))
    error('Invalid number of input arguments for dbscan.');
elseif((strcmp(method,'GMM') || strcmp(method,'gmm')) && (size(varargin, 2) ~= 2))
    error('Invalid number of input arguments for Gaussian Mixture Model');
elseif((strcmp(method,'LVQ') || strcmp(method,'lvq')) && (size(varargin, 2) ~= 5))
    error('Invalid number of input arguments for Learning Vector Quantization');
end

% Method entries
% Kmeans
if(strcmp(method,'kmeans'))
    k = varargin{1,1};
    iteration = varargin{1,2};
    [centroid, result] = Kmeans(data, k, iteration);
    PlotData(data, result, centroid);
% Kmeans++
elseif(strcmp(method,'kmeans++') || strcmp(method,'kmeanspp')) 
    k = varargin{1,1};
    iteration = varargin{1,2};
    [centroid, result] = Kmeanspp(data, k, iteration);
    PlotData(data, result, centroid);
% ISODATA
elseif(strcmp(method,'ISODATA') || strcmp(method,'isodata'))
    desired_k = varargin{1,1};  % desired number of classes
    iteration = varargin{1,2};  % maximum iteration time
    minimum_n = varargin{1,3};  % minimum number of samples in one class
    maximum_variance = varargin{1,4};  % maximum allowed variance of samples in one class
    minimum_d = varargin{1,5};  %  minimum distance between two classes
    [centroid, result] = ISODATA(data, iteration, desired_k, minimum_n, maximum_variance, minimum_d);
    PlotData(data, result, centroid);
% Mean Shift
elseif(strcmp(method,'mean_shift') || strcmp(method,'Mean_Shift'))
    thr = varargin{1,1};  % distance threshold
    [centroid, result] = Mean_Shift(data, thr);
    PlotData(data, result, centroid);
% DBSCAN
elseif(strcmp(method,'dbscan') || strcmp(method,'DBSCAN'))
    epsilon = varargin{1,1};  % distance threshold for finding neighbors
    minPts = varargin{1,2}; % minimum required number of neighbor points for adding one core object
    centroid = nan;  % DBSCAN will not calculate centroid
    result = DBSCAN(data, epsilon, minPts);
    PlotData(data, result);
% Gaussian Mixture Model
elseif(strcmp(method,'GMM') || strcmp(method,'gmm'))
    k = varargin{1,1};  % the number of Guassian components
    iter = varargin{1,2};  % maximum number of iterations
    [result, ~, centroid, ~] = Gaussian_Mixture(data, k, iter);
    PlotData(data, result, centroid);
% Learning Vector Quantization
elseif(strcmp(method,'LVQ') || strcmp(method,'lvq'))
    q = varargin{1,1};  % the number of prototypes
    neta = varargin{1,2};  % learning rate
    x = varargin{1,3};  % training data
    y = varargin{1,4};  % label of x
    iter = varargin{1,5};  % maximum number of iterations
    [centroid, result] = LVQ(data, q, neta, x, y, iter);
    PlotData(data, result, centroid);
else
    error('NotImplementedError!');
end
end


================================================
FILE: 01_MATLAB/README.md
================================================
<p align="center"> 
<a href="https://github.com/milaan9"><img src="https://img.shields.io/static/v1?logo=github&label=maintainer&message=milaan9&color=ff3300" alt="Last Commit"/></a> 
<a href="https://hits.seeyoufarm.com"><img src="https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fmilaan9%2FClustering_Algorithms_from_Scratch%2Ftree%2Fmain%2F01_MATLAB&count_bg=%231DC92C&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=views&edge_flat=false"/></a>
</p> 
<!--<img src="https://badges.pufler.dev/contributors/milaan9/01_Python_Introduction?size=50&padding=5&bots=true" alt="milaan9"/>-->
 
# Clustering Algorithms with MATLAB

## 1. Clustering Algorithms
- **K-means**
    - **K-means** algorithm performs the division of data points into 'K' clusters that share similarities and are dissimilar to the objects belonging to another cluster where, each data point belongs to the cluster with the nearest mean (cluster centers or cluster centroids), serving as a prototype of the cluster. The term 'K' is a number. You need to tell the system how many clusters you need to create. For example, K = 2 refers to two clusters.

- **K-means++**
    - Generally speaking, **K-means++** algorithm is similar to **K-means**;
    - Unlike classic K-means randomly choosing initial centroids, a better initialization procedure is integrated into **K-means++**, where observations far from existing centroids have higher probabilities of being chosen as the next centroid.
    - The initializeation procedure can be achieved using Fitness Proportionate Selection.

- **ISODATA (Iterative Self-Organizing Data Analysis)**
    - To be brief, **ISODATA** introduces two additional operations: Splitting and Merging;
    - When the number of observations within one class is less than one pre-defined threshold, **ISODATA** merges two classes with minimum between-class distance; 
    - When the within-class variance of one class exceeds one pre-defined threshold, **ISODATA** splits this class into two different sub-classes.

- **Mean Shift**
	- For each point *x*, find neighbors, calculate mean vector *m*, update *x = m*, until *x == m*;
	- Non-parametric model, no need to specify the number of classes;
	- No structure priori.

- **DBSCAN (Density-Based Spatial Clustering of Application with Noise)**
	- Starting with pre-selected core objects, DBSCAN extends each cluster based on the connectivity between data points;
	- DBSCAN takes noisy data into consideration, hence robust to outliers;
	- Choosing good parameters can be hard without prior knowledge;
- **Gaussian Mixture Model (GMM)**
- **LVQ (Learning Vector Quantization)**

## 2. Subspace Clustering Algorithms
- **Subspace K-means**
    - This algorithm directly extends **K-means** to Subspace Clustering through multiplying each dimension *d<sub>j</sub>* by one weight *m<sub>j</sub>* (s.t. sum(*m<sub>j</sub>*)=1, *j*=1,2,...,*p*);
    - It can be efficiently sovled in an Expectation-Maximization (EM) fashion. In each E-step, it updates weights, centroids using Lagrange Multiplier;
    - This rough algorithm suffers from the problem on its favor of using just a few dimensions when clustering sparse data;

- **Entropy-Weighting Subspace K-means**
    - Generally speaking, this algorithm is similar to **Subspace K-means**;
    - In addition, it introduces one regularization item related to weight entropy into the objective function, in order to mitigate the aforementioned problem in **Subspace K-means**.
    - Apart from its succinctness and efficiency, it works well on a broad range of real-world datasets.


================================================
FILE: 01_MATLAB/lib/DBSCAN.m
================================================
function label = DBSCAN(data, eps, minPts)
% label = DBSCAN(data, epsilon, minPts)
%   Main part of DBSCAN (Density-Based Spatial Clustering of Application with 
% Noise)clustering algorithm.
% ----
% Args:
%   data: data to be clustered (n * p)
%   eps: distance threshold for finding neighbors
%   minPts: minimum required number of neighbor observations for one core object
% ----
% Returns:
%   label: corresponding class for each observation

% Initialization
core_object = [];
count = 1;
k = 1; % label index
vmask = ones(size(data, 1), 1); % variable used to record whether one observation has been visited (1: not visited, 0: visited)
label = zeros(size(data, 1), 1); % pre-allocate result

% Find core objects
for i = 1:size(data, 1)
    x = data(i, :); % current observation of interests
    [~, neighbors] = Find_Neighbor(data, x, eps);
    
    % Check if the number of observations in neighbors of x is larger than minPts 
    if((size(neighbors,1)-1) >= minPts)
        
        % Add the index of x to the set of core object
        core_object(count, 1) = i;
        count = count + 1;
    end
end

fprintf('---- DBSCAN finds a total number of %i core objects ----\n', size(core_object, 1));

while(~isempty(core_object))
    
    % Construct one queue for further use 
    queue = [];
    qcount = 1;
    
    % Update core_object, observations have been visited should be deleted
    tcount = 1;
    list = [];
    for i = 1:size(core_object, 1)
        if(vmask(core_object(i, 1),1) == 0)
            list(tcount ,1) = i;
            tcount = tcount + 1;
        end
    end
    core_object(list,:) = [];
    
    if(isempty(core_object))
        break;
    end
    
    % Randomly choose one core object
    index = core_object(randi(size(core_object, 1)), 1);
    queue(qcount, 1) = index; % add the selected core object to the queue
    qcount = qcount + 1;
    vmask(index, 1) = 0; % update vmask
    label(index, 1) = k; % assign result 
    core_object(core_object == index, :) = []; % remove the selected core object from the set
    
    while(~isempty(queue))
        pivot = queue(1, 1); 
        queue(1, :) = []; % remove the first element in queue
        qcount = qcount - 1;
        
        % Find neighbors for current observation of interests 
        [tindex, tneighbors] = Find_Neighbor(data, data(pivot, :), eps);
        if(size(tneighbors, 1) >= minPts)
            for i = 1 : size(tindex, 1)
                % If one neighbor has not been visited, add it to the queue
                if(vmask(tindex(i, 1),1) == 1)
                    queue(qcount, 1) = tindex(i, 1);
                    qcount = qcount + 1;
                    vmask(tindex(i, 1),1) = 0; % update vmask
                    label(tindex(i, 1),1) = k; % assign result
                end
            end
        end   
    end
    
    fprintf('---- Finish finding observations for class %i ----\n', k);
    k = k + 1; % next class
end
end

function d = Eculidean_Distance(x1, x2)
% Calculate eculidean distance between x1 and x2.
%
% Args:
%   x1: observation 1
%   x2: observation 2
%
% Returns:
%   d: Eculidean distance between x1 and x2

d = sqrt((x1 - x2) * (x1 - x2)');
end

function [index, neighbors] = Find_Neighbor(data, x, eps)
% Find neighbors for x, where neighbors are defined to be observations in data
% with eculidean distance from x smaller than epsilon.
%
% Args:
%   data: dataset
%   x: current observation of interests
%   eps: pre-defined distance threshold
%
% Returns:
%   index: the index of these neighbors in data
%   neighbors: neighbor set of x

neighbors = []; % pre-allocate neighbor result
count = 1;
for i = 1 : size(data,1)
    d = Eculidean_Distance(data(i,:), x);
    if(d <= eps)
        index(count, 1) = i;
        neighbors(count,:) = data(i,:);
        count = count + 1;
    end
end
end




================================================
FILE: 01_MATLAB/lib/Entropy_Weighting_Subspace_Kmeans.m
================================================
function [centroid, dimension_weight, class] = Entropy_Weighting_Subspace_Kmeans(data, iteration, K, beta, lambda, verbose)

% Pre-allocate weights
centroid = zeros(K,size(data,2)); % centroid for each class
class = zeros(size(data,1),1); % classification result for each observation
dimension_weight = ones(K,size(data,2)); % weights for each dimension in each class

% Initialization
n = size(data,1);
J = 0; % used to record objective function value

% First round
centroid = data(unidrnd(n,K,1),:); % randomly choose K initial centroids 
dimension_weight = 1/size(data,2) * dimension_weight; % initialize weights for each dimension using uniform distribution

% Partially optimization
for i = 1:iteration
    
    J0 = J;
    
    % body part
    class = classify(centroid,dimension_weight,data);
    centroid = centroid_update(centroid,data,class);
    [dimension_weight,J] = dimension_weight_update(K,centroid,class,dimension_weight,data,beta,lambda);
    
    % whether display intermediate information
    if(verbose == 1)
        disp(['Objective Function(J) Value: ',num2str(J)]);
    end
    
    % early stop condition
    if(abs(J-J0)<1e-9)
        fprintf('*** Clustering terminates after %i iterations ***\n',i);
        break;
    end
end
end

% Details on the scheme of updating dimension weights
function [alpha,J] = dimension_weight_update(K,m,c,alpha,data,beta)

  % Under writting...
  
end

function [result] = classify(m,alpha,data)
result = zeros(size(data,1),1);
% Construct temporary matrix for efficiently computing dimention-weighted distance
matrix = zeros(size(m,1),size(data,2));
for i = 1:size(result,1)
    for j = 1:size(m,1)
        matrix(j,:) = (m(j,:)-data(i,:)) .* (m(j,:)-data(i,:));
    end
    temp = sum(alpha .* matrix,2);
    % To avoid more than one class having the minimum distance.
    t_index = find(temp == min(temp));
    result(i,1) = t_index(1,1);
end
end

function [m] = centroid_update(m,data,c)
for i = 1:size(m,1)
    if(~isempty(data(c==i,:)))
        m(i,:) = mean(data(c==i,:),1);
    else
        continue;
    end
end
end






================================================
FILE: 01_MATLAB/lib/Gaussian_Mixture.m
================================================
function [label, alpha, miu, sigma] = Gaussian_Mixture(data, k, iter)
% function [label, alpha, miu, sigma] = Gaussian_Mixture(data, k)
%   Use gaussian mixture model (GMM) to conduct data clustering. GMM uses
% probability model to characterize prototypes for each class.
% ----
% Args:
%   data: data to be clustered (n * p)
%   k: the number of guassian components (scalar)
%   iter: maximum number of iterations (scalar)
% ----
% Returns:
%   label: corresponding class for each observation (n * 1)
%   alpha: mixing coefficient for each gaussian component (k * 1) && (sum(alpha) == 1)
%   miu: mean value for each gaussian component (k * p)
%   sigma: coefficient matrix for each gaussian component (cell: k * n * n)

% Initialization
alpha = ones(k, 1) / k;
miu = data(randperm(size(data,1),k)',:);
for i = 1 : k
    sigma{i, 1} = rand * eye(size(data, 2));
end
label = zeros(size(data, 1), 1);

p = Posteriori(data, k, alpha, miu, sigma); % calculate posteriori probability

for i = 1 : iter
    % M-Step
    for j = 1 : k
        sump = sum(p(:, j));
        % Update parameters
        tmiu = zeros(1, size(data, 2));
        for num = 1 : size(data, 1)
            tmiu = tmiu + p(num, j) * data(num, :);
        end
        miu(j, :) = tmiu / sump;
        
        tsigma = zeros(size(data, 2));
        for num = 1 : size(data, 1)
            tsigma  = tsigma + p(num, j) * (data(num, :) - miu(j, :))' * (data(num, :) - miu(j, :));
        end
        sigma{j, 1} = tsigma / sump;
        
        alpha(j, 1) = sump / size(data, 1); % mixing coefficient
    end
    % E-Step
    p = Posteriori(data, k, alpha, miu, sigma);
end
[~, label] = max(p,[], 2); % classification
end

function p = Posteriori(data, k, alpha, miu, sigma)
% function p = Posteriori(data, alpha, miu, sigma)
%   Calculate posteriori probability for each observation and each class. The
% output p is of the size n * p, the entry (i,j) represents the probability
% that observation i belongs to class j.
% ----
% Args:
%   data, k, alpha, miu, sigma: same to the function Gaussian_Mixture
% ----
% Returns:
%   p: posteriori probability for each observation and each class (n * k)

% Pre-allocate result
p = zeros(size(data, 1), k);

for i = 1 : size(data, 1)
    sump = 0; % normalizing factor 
    for j = 1 : k
        tx = data(i, :) - miu(k, :); % x - miu
        sigma{j,1} = sigma{j,1} + 1e-6*eye(size(data,2)); % for numerical stability
        inv_sigma = inv(sigma{j, 1});
        upper = -0.5 * tx * inv_sigma * tx';
        p(i, j) = alpha(j, 1) * exp(upper) / sqrt((2*pi)^(size(data,2))*abs(det(sigma{j,1})));
        sump = sump + p(i, j);
    end
    p(i, :) = p(i, :) / sump;
end
end



================================================
FILE: 01_MATLAB/lib/ISODATA.m
================================================
function [centroid, result] = ISODATA(data, iteration, desired_k, minimum_n, maximum_variance, minimum_d)

% Pre-allocate result
centroid = data(randperm(size(data,1),desired_k)', :);
distance_matrix = zeros(size(data,1), desired_k);
result = zeros(size(data,1),1);

for i = 1 : iteration
    previous_centroid = centroid;
    for j = 1 : size(distance_matrix,1)
        for k = 1 : size(distance_matrix,2)
            distance_matrix(j,k) = sqrt(sum((data(j,:)-centroid(k,:)) .^ 2));
        end
    end
    [~,result] = min(distance_matrix,[],2);
    
    % Whether the number of points in each class is smaller than minimum_n
    for j = 1 : size(centroid, 1)
       if(isempty(find(result == j,1)) || (size(find(result == j),1) < minimum_n))
           
           % One class with number of points in it less than minimum_n
           % should be deleted, along with records in the distance_matrix
           centroid(j,:) = [];
           distance_matrix(:,j) = [];
           
           % Re-assign each point to its closet neighbor class
           [~,result] = min(distance_matrix,[],2);
           
           % Recalculate centroids
           for k = 1:size(centroid,1)
               centroid(k,:) = mean(data(result(:,1) == k,:));
           end
       end 
    end
    
    % Check if combining and splitting are needed
    % Case 1: too few classes
    if(size(centroid,1) <= (desired_k/2)) 
        % Split
        [centroid] = ISODATA_split(data, centroid, result, minimum_n, maximum_variance);
    
    % Case 2: too many classes
    elseif(size(centroid,1) > (2*desired_k)) 
        % Combine
        [centroid] = ISODATA_combine(centroid, result, minimum_d);
        
    end
    
    if(previous_centroid == centroid)
        fprintf('Clustering over after %i iterations...\n', i);
        break;
    end
end
end

% Splitting
function [centroid] = ISODATA_split(data, centroid, current_result, minimum_n, maximum_variance)

[centroid_x, centroid_y] = size(centroid);

variance_matrix = zeros(centroid_x, centroid_y); % pre-allocate the variance matrix

for i = 1 : centroid_x
    for j = 1 : centroid_y
        variance_matrix(i,j) = var(data(current_result == i,j));
    end
end

class_variance = max(variance_matrix,[],2); % find the greatest one-dimension variance per class 

for i = 1 : centroid_x
    if((class_variance(i,1) > maximum_variance) && size(find(current_result == i),1) > (2*minimum_n))
        
        % The current class should be splitted into two different classes
        centroid(i,:) = centroid(i,:) + sqrt(maximum_variance);
        centroid(end+1,:) = centroid(i,:) - sqrt(maximum_variance); % add one new class to centroid set
    end
end
end

% Combining
function [centroid] = ISODATA_combine(centroid, current_result, minimum_d)
centroid_x = size(centroid,1);
class_distance_matrix = zeros(centroid_x, centroid_x);

% Calculate distances between two different classes
for i = 1 : x
    for j = 1 : x
        if(i ~= j)
            class_distance_matrix(i,j) = sqrt(sum((centroid(i,:)-centroid(j,:)) .^ 2));
        end
    end
end

% Combining two classes
for i = 1 : x
    for j = 1 : x
        if((i ~= j) && (class_distance_matrix(i,j) <  minimum_d))
            n1 = size(find(current_result == i),1);
            n2 = size(find(current_result == j),1);
            centroid(i,:) = (1/(n1+n2)) * (n1 * centroid(i,:) + n2 * centroid(j,:));
            centroid(j,:) = [];
            break; % the number of combining operation is limited to 1 within each iteration
        end
    end
end

end



================================================
FILE: 01_MATLAB/lib/Kmeans.m
================================================
function [centroid, class] = Kmeans(data, k, iteration)
% Main part of Kmeans clustering algorithm.
%
% Args:
%   data: data to be clustered (n * p)
%   k: the number of classes
%   iteration: maximum number of iterations
%
% Returns:
%   centroid: clustering centroids for all classes
%   class: corresponding class for all samples

% Choose k initial centroids randomly
centroid = data(randperm(size(data,1),k)', :);

% Record distances between samples and centroids
distance_matrix = zeros(size(data,1), k);

% Pre-allocate result
class = zeros(size(data,1),1);

for i = 1:iteration
    
    previous_result = class; % for early termination
    
    % Calculate eculidean distance between each sample and each centroid
    for j = 1:size(distance_matrix,1)
        for k = 1:size(distance_matrix,2)
            distance_matrix(j,k) = sqrt((data(j,:)-centroid(k,:)) * (data(j,:)-centroid(k,:))');
        end
    end
    
    % Assign each sample to the nearest controid
    [~,class] = min(distance_matrix,[],2);
    
    % Recalculate centroids
    for j = 1:k
        centroid(j,:) = mean(data(class(:,1) == j,:));
    end
    
    % Display
    fprintf('---- %ith iteration completed ----\n',i);
    
    % If classified results on all points do not change after an iteration, 
    % the clustering process will quit immediately.
    if(class == previous_result)
        fprintf('**** Clustering over after %i iterations ****\n',i);
        break;
    end
end
end


================================================
FILE: 01_MATLAB/lib/Kmeanspp.m
================================================
function [centroid, class] = Kmeanspp(data, k, iteration)
% Main part of Kmeans clustering algorithm.
%
% Args:
%   data: data to be clustered (n * p)
%   k: the number of classes
%   iteration: maximum number of iterations
%
% Returns:
%   centroid: clustering centroids for all classes
%   class: corresponding class for all samples

% Choose the first inital centroid randomly
centroid = data(randperm(size(data,1),1)',:);

% Select remaining initial centroids (a total number of k-1)
for i = 2:k
    distance_matrix = zeros(size(data,1),i-1);
    for j = 1:size(distance_matrix,1)
        for p = 1:size(distance_matrix,2)
            distance_matrix(j,p) = sum((data(j,:)-centroid(p,:)) .^ 2);
        end
    end
    % Choose next centroid according to distances between points and
    % previous cluster centroids.
    index = Roulettemethod(distance_matrix);
    centroid(i,:) = data(index,:);
    clear distance_matrix;
end

% Following steps are same to kmeans
class = zeros(size(data,1),1);
distance_matrix = zeros(size(data,1), k);

for i = 1:iteration
    
    previous_result = class; % for early termination
    
    % Calculate eculidean distance between each sample and each centroid
    for j = 1:size(distance_matrix,1)
        for k = 1:size(distance_matrix,2)
            distance_matrix(j,k) = sqrt((data(j,:)-centroid(k,:)) * (data(j,:)-centroid(k,:))');
        end
    end
    
    % Assign each sample to the nearest controid
    [~,class] = min(distance_matrix,[],2);
    
    % Recalculate centroids
    for j = 1:k
        centroid(j,:) = mean(data(class(:,1) == j,:));
    end
    
    % Display
    fprintf('---- %ith iteration completed---- \n',i);
    
    % If classified results on all points do not change after an iteration, 
    % the clustering process will quit immediately.
    if(class == previous_result)
        fprintf('**** Clustering over after %i iterations ****\n',i);
        break;
    end
end
end

function [index] = Roulettemethod(distance_matrix)

% Find shortest distance between one sample and its closest cluster centroid
[min_distance,~] = min(distance_matrix,[],2);

% Normalize for further operations
min_distance = min_distance ./ sum(min_distance);

% Construct roulette according to min_distance
temp_roulette = zeros(size(distance_matrix,1),1);
for i = 1:size(distance_matrix,1)
    temp_roulette(i,1) = sum(min_distance(1:i,:));
end

% Generate a random number for selection
temp_rand = rand();

% Find the corresponding index
for i = 1:size(temp_roulette,1)
    if((i == 1) && temp_roulette(i,1) > temp_rand)
        index = 1;
    elseif((temp_roulette(i,1) > temp_rand) && (temp_roulette(i-1,1) < temp_rand))
        index = i;
    end
end
end


================================================
FILE: 01_MATLAB/lib/LVQ.m
================================================
function [centroid, label] = LVQ(data, q, neta, x, y, iter)
% function [centroid, label] = LVQ(data, q, x, y)
%   Unlike Kmeans, LVQ (Learning Vector Quantization) use labelled data 
% <x,y> to help clustering.After using LVQ, the feature space is split into
% a voronoi mesh.
% ----
% Args:
%   data: data to be clustered (n * p)
%   q: the number of prototypes (scalar)
%   neta: learning rate
%   x: training data (? * p, '?' is the size of training data)
%   y: label of training data (? * 1)
%   iter: maximum number of iterations (scalar)
% ----
%   centroid: clustering centroids (q * p)
%   label: corresponding class for each observation (n * 1)

% Initialization
centroid = x(randperm(size(x,1),q)', :); % randomly select q observations as initial prototypes
label = zeros(size(data, 1), 1);

for i = 1 : iter
    
    s = randperm(size(x,1),1); % ramdomly select one training sample
    tx = x(s, :); 
    
    % Calculate the distance between tx and all prototypes
    distance = zeros(q, 1);
    for j = 1 : q
        distance(j, 1) = Eculidean_Distance(tx, centroid(j, :));
    end
    [~, index] = min(distance); % find the index of the nearest prototype
    
    % Update the prototype
    if(index == y(s, 1))
        centroid(index, :) = centroid(index, :) + neta * (tx - centroid(index, :));
    else
        centroid(index, :) = centroid(index, :) - neta * (tx - centroid(index, :));
    end
end

% Classification
for i = 1 : size(data, 1)
    tx = data(i, :);
    distance = zeros(q, 1);
    for j = 1 : q
        distance(j, 1) = Eculidean_Distance(tx, centroid(j, :));
    end
    [~, index] = min(distance); % find the index of the nearest prototype
    label(i, 1) = index;
end
end

function d = Eculidean_Distance(x1, x2)
% Calculate eculidean distance between x1 and x2.
%
% Args:
%   x1: observation 1
%   x2: observation 2
%
% Returns:
%   d: Eculidean distance between x1 and x2

d = sqrt((x1 - x2) * (x1 - x2)');
end



================================================
FILE: 01_MATLAB/lib/Mean_Shift.m
================================================
function [centroid, result] = Mean_Shift(data, thr)
% Main part of mean shift clustering algorithm.
%
% Args:
%   data: data to be clustered (n * p)
%   thr: distance threshold used to find neighbors
%
% Returns:
%   centroids: clustering centroids for all classes
%   result: corresponding class for each data point
%
% Comment:
%    Note that there is no need to specify the number of classes in mean
%  shift. In this function , I first record the destination point after
%  shifting for each data point. Data points with same destination point
%  are considered to be in the same class.

destination = zeros(size(data)); % variable used to record destination points
result = zeros(size(data,1),1); % pre-allocate classifying result

% Conduct shift for each data point
for i = 1 : size(data,1)
    x = data(i, :); % current point of interests
    mv_new = x;
    mv_old = x * 10;
    
    while(Eculidean_Distance(mv_new, mv_old) ~= 0)
        mv_old = mv_new;
        neighbors = Find_Neighbor(data, x, thr);
        mv_new = Mean_Vector(neighbors, x, thr, '0/1'); % other weighting mechanisms are under writing
    end
    
    destination(i, :) = mv_new;
    
    % display
    fprintf('Shifting for %ith point finishes\n', i);
end

centroid = unique(destination, 'rows');

% Calssification
for i = 1 : size(centroid,1)
    mask = destination == centroid(i,:); % logical index
    mask = mask(:,1);
    result(mask, 1) = i;
end

% Display
fprintf('Clustering over, a total number of %i classes\n', size(centroid,1));

end

function d = Eculidean_Distance(x1, x2)
% Calculate eculidean distance between x1 and x2.
%
% Args:
%   x1: point1
%   x2: point2
%
% Returns:
%   d: Eculidean distance between x1 and x2

d = sqrt((x1 - x2) * (x1 - x2)');
end

function mv = Mean_Vector(neighbors, x, thr, method)
% Use neighbor set to calculate the mean vector for x.
%
% Args:
%   neighbors: neighbor set of x
%   x: current point of interests
%   thr: distance threshold. In this case, also standrad deviation of Gaussean kernal 
%   method: weighting mechanism ('0/1' or 'Gaussian')
%
% Returns:
%   mv: mean vector of x

flag = 1; % method index

% Check designated weighting machanism
if(strcmp(method, '0/1'))
    flag = 0;
elseif(strcmp(method, 'Gaussian') || strcmp(method, 'gaussian'))
    flag = 1;
else
    fprintf('Unknown method, use default Gaussian weighting mechanism\n')
end

mv = zeros(1, size(x,2)); % pre-allocate result

% Calculate mean vector
if(flag == 0)
    mv = mean(neighbors, 1);
elseif(flag == 1)
    denominator = 0;
    for i = 1 : size(neighbors,1)
        weight = exp(-(Eculidean_Distance(neighbors(i,:), x)^2) / (2*thr^2));
        denominator = denominator + weight;
        mv = mv + weight * neighbors(i, :);
    end
    mv = mv / denominator;
end
end

function neighbors = Find_Neighbor(data, x, thr)
% Find neighbors for x, where neighbors are defined to be points in data
% with eculidean distance from x smaller than thr.
% 
% Args:
%   data: all data points
%   x: current point of interests
%   thr: pre-defined distance threshold
%
% Returns:
%   neighbors: neighbor set of x

neighbors = []; % pre-allocate neighbor result
count = 1;
for i = 1 : size(data,1)
    d = Eculidean_Distance(data(i,:), x);
    if(d <= thr)
        neighbors(count,:) = data(i,:);
        count = count + 1;
    end
end
end




================================================
FILE: 01_MATLAB/lib/Subspace_Kmeans.m
================================================
function [centroid, dimension_weight, class] = Subspace_Kmeans(data, iteration, K, beta, verbose)

% Pre-allocate weights
centroid = zeros(K,size(data,2)); % centroid for each class
class = zeros(size(data,1),1); % classification result for each observation
dimension_weight = ones(K,size(data,2)); % weights for each dimension in each class

% Initialization
n = size(data,1);
J = 0; % used to record objective function value

% First round
centroid = data(unidrnd(n,K,1),:); % randomly choose K initial centroids 
dimension_weight = 1/size(data,2) * dimension_weight; % initialize weights for each dimension using uniform distribution

% Partially optimization
for i = 1:iteration
    
    J0 = J;
    
    % body part
    class = classify(centroid,dimension_weight,data);
    centroid = centroid_update(centroid,data,class);
    [dimension_weight,J] = dimension_weight_update(K,centroid,class,dimension_weight,data,beta);
    
    % whether display intermediate information
    if(verbose == 1)
        disp(['Objective Function(J) Value: ',num2str(J)]);
    end
    
    % early stop condition
    if(abs(J-J0)<1e-9)
        fprintf('*** Clustering terminates after %i iterations ***\n',i);
        break;
    end
end
end

% Details on the scheme of updating dimension weights
function [alpha,J] = dimension_weight_update(K,m,c,alpha,data,beta)
all_matrix = zeros(size(data,1),size(data,2));
for i = 1:size(data,1)
    all_matrix(i,:) = (m(c(i,1),:)-data(i,:)) .* (m(c(i,1),:)-data(i,:));
end
J = 0;

for i = 1:K
    class_matrix = all_matrix(c==i,:); % pick out data in all_matrix that corresponds to class i
    t_sum = sum(class_matrix,1) + 1e-9; % adding a small positive constant to make weights computatable
    J = J + (alpha(i,:).^beta) * t_sum';
    if(size(class_matrix,1)>0)
        for j = 1:size(alpha,2)  
            % variables used to avoid redundant calculation
            tt_numerator = t_sum(1,j);
            t_denominator = sum(tt_numerator./t_sum,2);
            alpha(i,j) = 1/((t_denominator+1e-9)^(1/(beta-1)));
        end
    else
        continue;
    end
end

% Normalize
for i = 1:size(alpha,1)
    alpha(i,:) = alpha(i,:)./(sum(alpha(i,:),2));
end

end

function [result] = classify(m,alpha,data)

result = zeros(size(data,1),1);

% Construct temporary matrix for efficiently computing dimention-weighted distance
matrix = zeros(size(m,1),size(data,2));

for i = 1:size(result,1)
    for j = 1:size(m,1)
        matrix(j,:) = (m(j,:)-data(i,:)) .* (m(j,:)-data(i,:));
    end
    temp = sum(alpha .* matrix,2);
    
    % To avoid more than one class having the minimum distance.
    t_index = find(temp == min(temp));
    result(i,1) = t_index(1,1);
end
end

function [m] = centroid_update(m,data,c)
for i = 1:size(m,1)
    if(~isempty(data(c==i,:)))
        m(i,:) = mean(data(c==i,:),1);
    else
        continue;
    end
end
end






================================================
FILE: 01_MATLAB/tool/GenerateDataset.m
================================================
function dataset = GenerateDataset(n, p, k, pd, varargin)
% dataset = GenerateDataset(n, p, k, pd, varargin)
%   Generate synthetic dataset for further use.
% ----
% Args:
%   n: the number of samples in each class (scalar)
%   p: the number of features (scalar)
%   k: the number of classes (scalar)
%   pd: probability distribution of generated data (string)
%   varargin: parameters on p 
% ----
% Returns:
%   dataset: generated dataset

% Pre-allocate dataset to avoid memory overflow
dataset = zeros(n * k, p);

% Generate dataset from gaussian distribution
if((strcmp(pd, 'Gaussian')) || strcmp(pd, 'gaussian'))
    
    % Get parameters on gaussian distribution
    mu = varargin{1, 1}; % (k * p)
    sigma = varargin{1, 2}; % (k * p)
    
    for i = 1 : k
        dataset(n*(i-1)+1:n*i, :) = mvnrnd(mu(i,:), sigma(i,:), n);
    end   
end

% Random permutation
dataset = dataset(randperm(size(dataset, 1)), :);

end



================================================
FILE: 01_MATLAB/tool/PlotData.m
================================================
function PlotData(data, label, varargin)
% PlotData(data, label, varargin)
%   Plot data, with different classes correspond to different colors.
% ----
% Args:
%   data: original data (n * p)
%   label: clustering result (n * 1), '-1' in label corresponds to noisy data
%   varargin{1,1}: clustering centroid (k * p), k the number of classes
%   varargin{1,...}: other parameters
% ----
% Returns: <None>

isc = 0; % whether plot centroid
k = max(label); % the number of classes
p = size(data ,2); % the number of features

if(~isempty(varargin))
    isc = 1;
    centroid = varargin{1, 1};
end

if((p > 3) || (p < 1))
    error('Unable to plot data exceeds 3-deminsion');
end

% Data
if(p == 2)
    for i = 1 : k
        scatter(data(label==i,1), data(label==i,2), 'filled', 'DisplayName', strcat('Class-',num2str(i)));
        hold on;
    end
elseif(p == 3)
    for i = 1 : k
        scatter3(data(label==i,1), data(label==i,2), data(label==i,3), 'filled', 'DisplayName', strcat('Class-',num2str(i)));
        hold on;
    end
end

% Centroid
if(isc == 1)
    if(p == 2)
        scatter(centroid(:,1), centroid(:,2), 150, 'd', 'filled', 'DisplayName', 'Centroid');
    elseif(p == 3)
        scatter3(centroid(:,1), centroid(:,2), centroid(:,3), 150, 'd', 'filled', 'DisplayName', 'Centroid');
    end
end

% Legend
if(k < 10)
    legend('show');
end

% Others
grid on;
end



================================================
FILE: 02_Python/A_Star.py
================================================
#!/usr/bin/env python3

#================================================================================================================
#----------------------------------------------------------------------------------------------------------------
#									A STAR
#----------------------------------------------------------------------------------------------------------------
#================================================================================================================

import sys
import queue

# each Node in the Graph
class Node:
    def __init__(self, key, i, j):
        # Utility to easily check for presence in openList/ClosedList
        self.__key = key

        # Priority by which it is inserted to OpenList
        self.__priority = 0.0

        # Distance Travelled till current node
        self.__distFromStart = 0

        # set of neighbor nodes
        self.__neighbors = set()

        # node from which this was visited
        self.__parent = None

        self.__traversable = False

        # required for calculating heuristic
        # also used as key in graph.nodes
        self.__pos = (i, j)

    def addNeighbor(self, node):
        self.__neighbors.add(node)

    def setTraversability(self, isTraversable):
        self.__traversable = isTraversable

    def isTraversable(self):
        return self.__traversable

    def getKey(self):
        return self.__key

    def getPos(self):
        return self.__pos

    def setPriority(self, newPriority):
        self.__priority = newPriority

    def getPriority(self):
        return self.__priority

    def setParent(self, parent):
        self.__parent = parent

    def getParent(self):
        return self.__parent;

    def getDistFromStart(self):
        return self.__distFromStart

    def setDistFromStart(self, distFromStart):
        self.__distFromStart = distFromStart

    def getNeighbors(self):
        return self.__neighbors

    def setKey(self, key):
        self.__key = key

    # Requred by queue.PriorityQueue's use of heapq for comparing two nodes
    def __lt__(self, node):
        return self.__priority < node.getPriority()

class Graph:
    def __init__(self):
        self.__nodes = dict()

    def addNode(self, node):
        self.__nodes[node.getPos()] = node

    def getNode(self, pos):
        return self.__nodes.get(pos, None)

    # Add an edge from node1 to node2
    def addEdge(self, node1, node2):
        if not (node1 in self.__nodes and node2 in self.__nodes):
            node1.addNeighbor(node2)

class Map2D:
    def __init__(self, filePath):
        self.__graph = Graph()
        self.__startNode = None
        self.__endNode = None

        with open(filePath) as inFile:
            lines = inFile.readlines()

        self.__dims = (len(lines), len(lines[0])-1)
        
        # add Nodes to graph
        for i in range(len(lines)):
            nodeLine = list()
            for j in range(len(lines[i])):
                #each character is a node
                char = lines[i][j]
                if "\n" == char:
                    continue
                node = Node(char,i,j)
                # "#" for non traversable nodes
                if "#" == char:
                    node.setTraversability(False)
                else:
                    node.setTraversability(True)
                    if "S" == char:
                        self.__startNode = node
                    elif "E" == char:
                        self.__endNode = node
                self.__graph.addNode(node)

        if self.__startNode == None or self.__endNode == None:
            sys.exit("No start or no end in map")

        # for each node, for each neighbor, add edge between neighbor and node
        for i in range(self.__dims[0]):
            for j in range(self.__dims[1]):
                currentNode = self.__graph.getNode((i,j))
                if self.__isValid(i+1,j):
                    self.__graph.addEdge(currentNode,self.__graph.getNode((i+1,j)))
                if self.__isValid(i-1,j):
                    self.__graph.addEdge(currentNode,self.__graph.getNode((i-1,j)))
                if self.__isValid(i,j+1):
                    self.__graph.addEdge(currentNode,self.__graph.getNode((i,j+1)))
                if self.__isValid(i,j-1):
                    self.__graph.addEdge(currentNode,self.__graph.getNode((i,j-1)))

    def printMap(self):
        if None == self.__dims:
            print("ERROR: map not initialized")
            return

        for i in range(self.__dims[0]):
            for j in range(self.__dims[1]):
                print(self.__graph.getNode((i,j)).getKey(), end="")
            print()

    def __isValid(self, i, j):
        node = self.__graph.getNode((i,j))
        # if i,j are valid indices and the node is traversable
        if node != None and node.isTraversable():
            return True
        return False

    def getStart(self):
        return self.__startNode

    def getEnd(self):
        return self.__endNode

class AStar:
    def __init__(self, inMap):
        # holds list of nodes from which the next node is chosen
        self.__openList = queue.PriorityQueue()

        # put start node in openList
        start = inMap.getStart()
        start.setDistFromStart(0)
        start.setPriority(self.__heuristic(start, inMap.getEnd()))
        start.setParent(None)
        self.__openList.put((start.getPriority(),start))

        self.__map = inMap

        # list of visited nodes
        self.__closedList = dict()

    # setting this to return 0, will comvert this to Dijkistra's Algorithm
    # manhattan distance
    def __heuristic(self, node, targetNode):
        i1,j1 = node.getPos()
        i2,j2 = targetNode.getPos()
        return abs(i1-i2)+abs(j1-j2)

    def findPath(self):
        while True:
            # OL is empty implies, no route, stop
            if self.__openList.empty():
                print("No route!")
                return

            # get highest priority element from OL
            priority, currentNode = self.__openList.get()

            # if we reached end node, follow parents to get path
            if currentNode == self.__map.getEnd():
                # follow parents till startNode
                while currentNode != self.__map.getStart():
                    # set "*" for visual output
                    currentNode.setKey("*")
                    currentNode = currentNode.getParent()
                currentNode.setKey("*")
                return

            # unweighted graph should add 1, otherwise replace 1 with edge weight
            newLevel = currentNode.getDistFromStart() + 1

            # add neighbors to OL
            for neighbor in currentNode.getNeighbors():
                # if it has already been visited
                if self.__closedList.get(neighbor.getPos(), None) != None:
                    continue

                # if it is in OL but the new path is longer than the already existing one
                if "O" == neighbor.getKey() and newLevel >= neighbor.getDistFromStart():
                    continue

                # add neighbor to OL
                neighbor.setKey("O")
                neighbor.setParent(currentNode)
                neighbor.setDistFromStart(newLevel)
                neighbor.setPriority(neighbor.getDistFromStart() + self.__heuristic(neighbor,self.__map.getEnd()))
                self.__openList.put((neighbor.getPriority(), neighbor))
            # mark node as visited
            self.__closedList[currentNode.getPos()] = True
            currentNode.setKey("C")

            # un-comment following 2 lines to see how algorithm works
            # self.__map.printMap()
            # input()

    def getMap(self):
        return self.__map

if "__main__" == __name__:
    inpMap = Map2D("./data/graph.in")
    print("Initial Map:")
    inpMap.printMap()
    aStar = AStar(inpMap)
    aStar.findPath()
    print("Map with route(*), unvisited nodes(-/O/#), visited nodes(C)")
    aStar.getMap().printMap()

================================================
FILE: 02_Python/Collaborative_Filtering.py
================================================
from math import sqrt

 data = {
 			"Manish": {
 						"Interstellar": 4,
 						"The Dark Knight": 5,
 						"Wanted": 3,
 						"Sucker Punch": 2,
 						"Inception": 5,
 						"The Conjuring": 3,
 						"21 Jump Street": 4,
 						"The Prestige": 5
  					},
  			"Madhu": {
  						"Interstellar": 5,
 						"The Dark Knight": 5,
 						"Wanted": 1,
 						"Devil": 3,
 						"The Conjuring": 1,
 						"21 Jump Street": 4,
 						"Men in Black": 2

  					},
  			"Mansukh": {
  						"Hot Tub Time Machine": 1,
  						"Inception": 5,
  						"Revenant": 3,
  						"Avengers 1": 4,
  						"Iron Man 2": 3,
  						"Batman v Superman": 5,
  						"Wanted": 4,

  					},
  			"Imran": {
  						"Inception": 5,

  					},
  			"Kumar": {
  						"Hot Tub Time Machine": 1,
  						"Avengers 1": 4,
  						"Avengers 2": 3,
  						"The Departed": 5,
  						"Interstellar": 4,
  						"Fight Club": 5,
  						"Vampires Suck": 1,
  						"Twilight": 1
  					},
  			"Tori": {
  						"Notebook": 5,
  						"The Terminal": 4,
  						"Twilight": 5,
  						"Inception": 2,
  						"The Dark Knight": 1,
  						"Hot Tub Time Machine": 2,
  						"The Vow": 4
  					},
  			"Jatin": {
 						"Inception":5,
 						"The Conjuring":4
  					},
  			"Latha": {
  						"Twilight": 1
  					}
  		}

 itemNames = [
 				"Interstellar",
 				"The Dark Knight",
 				"Wanted",
 				"Sucker Punch",
 				"Inception",
 				"The Conjuring",
 				"21 Jump Street",
 				"The Prestige",
 				"Devil",
 				"Men in Black",
 				"Hot Tub Time Machine",
 				"Revenant",
  				"Avengers 1",
  				"Iron Man 2",
  				"Batman v Superman",
  				"Avengers 2",
  				"The Departed",
  				"Fight Club",
  				"Vampires Suck",
  				"Twilight",
  				"Notebook",
  				"The Terminal",
  				"The Vow",
  				"Focus"
 			]

 MAXrating = 5
 MINrating = 1

 def compute_similarity(item1,item2,userRatings):
 	averages = {}
 	for (key,ratings) in userRatings.items():
 		averages[key] = (float(sum(ratings.values()))/len(ratings.values()))

 	num = 0
 	dem1 = 0
 	dem2 = 0

 	for (user,ratings) in userRatings.items():
 		if item1 in ratings and item2 in ratings:
 			avg = averages[user]
 			num += (ratings[item1] - avg) * (ratings[item2] - avg)
 			dem1 += (ratings[item1] - avg) ** 2
 			dem2 += (ratings[item2] - avg) ** 2
 	if dem1*dem2 == 0:
 		return 0
 	return num / (sqrt(dem1 * dem2))

 def build_similarity_matrix(userRatings):
 	similarity_matrix = {}

 	for i in range(0,len(itemNames)):
 		band = {}
 		for j in range(0,len(itemNames)):
 			if itemNames[i] != itemNames[j]:
 				band[itemNames[j]] = compute_similarity(itemNames[i],itemNames[j],data)
 		similarity_matrix[itemNames[i]] = band
 	return similarity_matrix

 def normalize(rating):
 	num = 2 * (rating - MINrating) - (MAXrating - MINrating)
 	den = (MAXrating - MINrating)
 	return num / den

 def denormalize(rating):
 	return (((rating + 1) * (MAXrating - MINrating))/2 ) + MINrating

 def prediction(username,item):
 	num = 0
 	den = 0
 	for band,rating in data[username].items():
 		num += sm[item][band] * normalize(rating)
 		den += abs(sm[item][band])

 	if den == 0:
 		return 0
 	return denormalize(num/den)

 def recommendation(username,userRatings):
 	recommend = []
 	for item in itemNames:
 		if item not in userRatings[username].keys():
 			if prediction(username,item) >= 3.5:
 				recommend.append(item)
 	return recommend

 sm = build_similarity_matrix(data)
 # for k,i in sm.items():
 # 	print(k , i)
 print("Recommendation for Jatin: ")
 print(recommendation("Jatin",data))

 print("Recommendation for Latha: ")
 print(recommendation("Latha",data))


================================================
FILE: 02_Python/DBSCAN.py
================================================
# ================================================================================================================
# ----------------------------------------------------------------------------------------------------------------
#									            DBSCAN
# ----------------------------------------------------------------------------------------------------------------
# ================================================================================================================

import numpy as np
from sklearn import datasets
from sklearn.preprocessing import StandardScaler
from itertools import cycle, islice
import matplotlib.pyplot as plt
import queue
import pandas as pd


class CustomDBSCAN():
    def __init__(self):
        self.core = -1
        self.border = -2

    # Find all neighbour points at epsilon distance
    def neighbour_points(self, data, pointId, epsilon):
        points = []
        for i in range(len(data)):
            # Euclidian distance
            if np.linalg.norm([a_i - b_i for a_i, b_i in zip(data[i], data[pointId])]) <= epsilon:
                points.append(i)
        return points

    # Fit the data into the DBSCAN model
    def fit(self, data, Eps, MinPt):
        # initialize all points as outliers
        point_label = [0] * len(data)
        point_count = []

        # initilize list for core/border points
        core = []
        border = []

        # Find the neighbours of each individual point
        for i in range(len(data)):
            point_count.append(self.neighbour_points(data, i, Eps))

        # Find all the core points, border points and outliers
        for i in range(len(point_count)):
            if (len(point_count[i]) >= MinPt):
                point_label[i] = self.core
                core.append(i)
            else:
                border.append(i)

        for i in border:
            for j in point_count[i]:
                if j in core:
                    point_label[i] = self.border
                    break

        # Assign points to a cluster

        cluster = 1

        # Here we use a queue to find all the neighbourhood points of a core point and find the indirectly reachable points
        # We are essentially performing Breadth First search of all points which are within Epsilon distance for each other
        for i in range(len(point_label)):
            q = queue.Queue()
            if (point_label[i] == self.core):
                point_label[i] = cluster
                for x in point_count[i]:
                    if(point_label[x] == self.core):
                        q.put(x)
                        point_label[x] = cluster
                    elif(point_label[x] == self.border):
                        point_label[x] = cluster
                while not q.empty():
                    neighbors = point_count[q.get()]
                    for y in neighbors:
                        if (point_label[y] == self.core):
                            point_label[y] = cluster
                            q.put(y)
                        if (point_label[y] == self.border):
                            point_label[y] = cluster
                cluster += 1  # Move on to the next cluster

        return point_label, cluster

    # Visualize the clusters
    def visualize(self, data, cluster, numberOfClusters):
        N = len(data)

        colors = np.array(list(islice(cycle(['#FE4A49', '#2AB7CA']), 3)))

        for i in range(numberOfClusters):
            if (i == 0):
                # Plot all outliers point as black
                color = '#000000'
            else:
                color = colors[i % len(colors)]

            x, y = [], []
            for j in range(N):
                if cluster[j] == i:
                    x.append(data[j, 0])
                    y.append(data[j, 1])
            plt.scatter(x, y, c=color, alpha=1, marker='.')
        plt.show()


def main():

    # Reading from the data file
    df = pd.read_csv("./data/concentric_circles.csv")

    dataset = df.astype(float).values.tolist()

    # normalize dataset
    X = StandardScaler().fit_transform(dataset)

    custom_DBSCAN = CustomDBSCAN()
    point_labels, clusters = custom_DBSCAN.fit(X, 0.25, 4)

    print(point_labels, clusters)

    custom_DBSCAN.visualize(X, point_labels, clusters)


if __name__ == "__main__":
    main()


================================================
FILE: 02_Python/Decision_Trees.py
================================================
# ================================================================================================================
# ----------------------------------------------------------------------------------------------------------------
#									DECISION TREES
# ----------------------------------------------------------------------------------------------------------------
# ================================================================================================================
from math import log
import pandas as pd
import random


class CustomDecisionTree():
    def __init__(self):
        pass

    def majorityCnt(self, classList):
        classCount = {}
        for vote in classList:
            if vote not in classCount.keys():
                classCount[vote] = 0
            classCount[vote] += 1
        sortedClassCount = sorted(classCount.items(), reverse=True)
        return sortedClassCount[0][0]

    # for calculting entropy
    def calcShannonEnt(self, dataSet):
        numEntries = len(dataSet)
        labelCounts = {}
        for featVec in dataSet:
            currentLabel = featVec[-1]
            if currentLabel not in labelCounts.keys():
                labelCounts[currentLabel] = 0
                labelCounts[currentLabel] += 1
        shannonEnt = 0.0
        for key in labelCounts:
            prob = float(labelCounts[key])/numEntries
            shannonEnt -= prob * log(prob, 2)
        return shannonEnt

    def splitDataSet(self, dataSet, axis, value):
        retDataSet = []
        for featVec in dataSet:
            if featVec[axis] == value:
                reducedFeatVec = featVec[:axis]
                reducedFeatVec.extend(featVec[axis+1:])
                retDataSet.append(reducedFeatVec)
        return retDataSet

    # choosing the best feature to split
    def chooseBestFeatureToSplit(self, dataSet, labels):
        numFeatures = len(dataSet[0]) - 1
        baseEntropy = self.calcShannonEnt(dataSet)
        bestInfoGain = -1
        bestFeature = 0
        for i in range(numFeatures):
            featList = [example[i] for example in dataSet]
            uniqueVals = set(featList)
            newEntropy = 0.0
            for value in uniqueVals:
                subDataSet = self.splitDataSet(dataSet, i, value)
                prob = len(subDataSet)/float(len(dataSet))
                newEntropy += prob * self.calcShannonEnt(subDataSet)
            infoGain = baseEntropy - newEntropy
            print(infoGain, bestInfoGain)
            if (infoGain > bestInfoGain):
                bestInfoGain = infoGain
                bestFeature = i

        print("the best feature to split is", labels[bestFeature])
        return bestFeature

    # function to build tree recursively
    def createTree(self, dataSet, labels):
        classList = [example[-1] for example in dataSet]
        if len(classList) is 0:
            return

        if classList.count(classList[0]) == len(classList):
            return classList[0]
        if len(dataSet[0]) == 1:
            return self.majorityCnt(classList)
        featureVectorList = [row[:len(row)-1] for row in dataSet]
        bestFeat = self.chooseBestFeatureToSplit(featureVectorList, labels)
        bestFeatLabel = labels[bestFeat]
        myTree = {bestFeatLabel: {}}
        del(labels[bestFeat])
        featValues = [example[bestFeat] for example in dataSet]
        uniqueVals = set(featValues)
        for value in uniqueVals:
            subLabels = labels[:]
            myTree[bestFeatLabel][value] = self.createTree(
                self.splitDataSet(dataSet, bestFeat, value), subLabels)
        return myTree


def main():
    df = pd.read_csv("./data/test.csv")  # Reading from the data file
    # Sex param
    df.replace('male', 0, inplace=True)
    df.replace('female', 1, inplace=True)

    # Embarked param
    df.replace('S', 0, inplace=True)
    df.replace('C', 1, inplace=True)
    df.replace('Q', 2, inplace=True)
    df['embarked'] = df['embarked'].fillna(1)
    dataset = df.astype(float).values.tolist()
    labels = ['pclass', 'sex', 'embarked', 'survived']

    # Shuffle the dataset
    random.shuffle(dataset)  # import random for this

    custom_DTree = CustomDecisionTree()
    print(custom_DTree.createTree(dataset, labels))


if __name__ == "__main__":
    main()


================================================
FILE: 02_Python/Dimensionality_Reduction/Dimensionality_Reduction.ipynb
================================================
{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<small><small><i>\n",
    "All the IPython Notebooks in **Clustering Algorithms** lecture series by **[Dr. Milaan Parmar](https://www.linkedin.com/in/milaanparmar/)** are available @ **[GitHub](https://github.com/milaan9/Clustering_Algorithms)**\n",
    "</i></small></small>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Dimensionality Reduction"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-09-18T15:40:28.921358Z",
     "start_time": "2021-09-18T15:40:21.087390Z"
    },
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 104
    },
    "colab_type": "code",
    "executionInfo": {
     "elapsed": 5681,
     "status": "ok",
     "timestamp": 1546259198259,
     "user": {
      "displayName": "Bob Li",
      "photoUrl": "",
      "userId": "13034580972517925389"
     },
     "user_tz": -480
    },
    "id": "cb9XEtCKNnh-",
    "outputId": "04b24b82-bb55-4e98-b09f-5a32cf27fa3a"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Requirement already satisfied: sklearn in c:\\programdata\\anaconda3\\lib\\site-packages (0.0)\n",
      "Requirement already satisfied: scikit-learn in c:\\programdata\\anaconda3\\lib\\site-packages (from sklearn) (0.24.1)\n",
      "Requirement already satisfied: joblib>=0.11 in c:\\programdata\\anaconda3\\lib\\site-packages (from scikit-learn->sklearn) (1.0.1)\n",
      "Requirement already satisfied: scipy>=0.19.1 in c:\\programdata\\anaconda3\\lib\\site-packages (from scikit-learn->sklearn) (1.6.2)\n",
      "Requirement already satisfied: numpy>=1.13.3 in c:\\programdata\\anaconda3\\lib\\site-packages (from scikit-learn->sklearn) (1.20.1)\n",
      "Requirement already satisfied: threadpoolctl>=2.0.0 in c:\\programdata\\anaconda3\\lib\\site-packages (from scikit-learn->sklearn) (2.1.0)\n"
     ]
    }
   ],
   "source": [
    "!pip install sklearn"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-09-18T15:40:29.264130Z",
     "start_time": "2021-09-18T15:40:28.924289Z"
    },
    "colab": {},
    "colab_type": "code",
    "id": "yr45GEzAOtq6"
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import os\n",
    "\n",
    "# making random output stable\n",
    "np.random.seed(42)\n",
    "m = 60\n",
    "w1, w2 = 0.1, 0.3\n",
    "noise = 0.1\n",
    "\n",
    "# Create an array of the given shape \n",
    "# and populate it with random samples from a uniform distribution over [0, 1).\n",
    "angles = np.random.rand(m) * 3 * np.pi / 2 - 0.5\n",
    "# print('angles: {}\\nsize of dataset: {}'.format(angles, angles.shape))\n",
    "\n",
    "X = np.empty((m, 3))\n",
    "# print(X)\n",
    "X[:, 0] = np.cos(angles) + np.sin(angles)/2 + noise * np.random.randn(m) / 2\n",
    "X[:, 1] = np.sin(angles) * 0.7 + noise * np.random.randn(m) / 2\n",
    "X[:, 2] = X[:, 0] * w1 + X[:, 1] * w2 + noise * np.random.randn(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "ehCixfYWYgBc"
   },
   "source": [
    "## PCA using Scikit-Learn\n",
    "With Scikit-Learn, PCA is really trivial. It even takes care of mean centering for you, which has been perfectly packaged and easy to incorporate"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-09-18T15:40:31.663542Z",
     "start_time": "2021-09-18T15:40:29.269502Z"
    },
    "colab": {},
    "colab_type": "code",
    "id": "d6tMz_zjY_Jz"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "pca = PCA(n_components = 0.99)\n",
      "\n",
      "First 5 Projection Points:\n",
      "[[ 0.690074    0.36150744]\n",
      " [-1.39636097 -0.34497714]\n",
      " [-1.00728461  0.35025708]\n",
      " [-0.2736333   0.50516373]\n",
      " [ 0.91324535 -0.26290852]]\n",
      "\n",
      "\n",
      "Principal Components:\n",
      "[[-0.95250178 -0.24902446 -0.17529172]\n",
      " [ 0.29267159 -0.9076305  -0.30091563]]\n",
      "\n",
      "\n",
      "Explained Variance Ratio:\n",
      "[0.85406025 0.13622918]\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from sklearn.decomposition import PCA\n",
    "\n",
    "pca = PCA(n_components = 0.99)\n",
    "\n",
    "# you have to call 'fit' with appropriate arguments before using transrom method.\n",
    "# a = pca.fit(X)\n",
    "# b = pca.transform(X)\n",
    "# or simply use fit_transform()\n",
    "\n",
    "# 2-D\n",
    "X_decorrelated_DR = pca.fit_transform(X)\n",
    "X_decorrelated_DR[:] = X_decorrelated_DR[:]*(-1) \n",
    "\n",
    "# reconstruct dataset from X_decorrelated_DR\n",
    "X_reconstructed = pca.inverse_transform(X_decorrelated_DR)\n",
    "\n",
    "print(\"pca = PCA(n_components = 0.99)\\n\")\n",
    "print(\"First 5 Projection Points:\\n{}\\n\\n\".format(X_decorrelated_DR[:5]))\n",
    "print(\"Principal Components:\\n{}\\n\\n\".format(pca.components_))\n",
    "print(\"Explained Variance Ratio:\\n{}\\n\".format(pca.explained_variance_ratio_))\n",
    "# print('reconstructed dataset:\\n{}\\n'.format(X_reconstructed))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "YPJErcHIfeWp"
   },
   "source": [
    "***We can visibly tell that we can reduce our dimensions from 3 to 2 while preserving 99% of the variance***"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-09-18T15:40:31.711397Z",
     "start_time": "2021-09-18T15:40:31.674284Z"
    },
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 252
    },
    "colab_type": "code",
    "executionInfo": {
     "elapsed": 684,
     "status": "ok",
     "timestamp": 1546322325444,
     "user": {
      "displayName": "Bob Li",
      "photoUrl": "",
      "userId": "13034580972517925389"
     },
     "user_tz": -480
    },
    "id": "_jJIoUzlf6oa",
    "outputId": "45ed6c46-eb02-4d40-e324-eee30b51620f"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "pca = PCA(n_components = 0.999)\n",
      "\n",
      "First 5 Projection Points:\n",
      "[[-0.690074   -0.36150744  0.05176509]\n",
      " [ 1.39636097  0.34497714  0.04887253]\n",
      " [ 1.00728461 -0.35025708  0.04753837]\n",
      " [ 0.2736333  -0.50516373  0.13786693]\n",
      " [-0.91324535  0.26290852 -0.03027986]]\n",
      "\n",
      "\n",
      "Principal Components:\n",
      "[[-0.95250178 -0.24902446 -0.17529172]\n",
      " [ 0.29267159 -0.9076305  -0.30091563]\n",
      " [-0.08416476 -0.33792558  0.93740205]]\n",
      "\n",
      "\n",
      "Explained Variance Ratio:\n",
      "[0.85406025 0.13622918 0.00971057]\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# preserving 99.9% of the variance\n",
    "pca = PCA(n_components = 0.999)\n",
    "\n",
    "# 3-D\n",
    "X_decorrelated = pca.fit_transform(X)\n",
    "\n",
    "print(\"pca = PCA(n_components = 0.999)\\n\")\n",
    "print(\"First 5 Projection Points:\\n{}\\n\\n\".format(X_decorrelated[:5]))\n",
    "print(\"Principal Components:\\n{}\\n\\n\".format(pca.components_))\n",
    "print(\"Explained Variance Ratio:\\n{}\\n\".format(pca.explained_variance_ratio_))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-09-18T15:40:32.577605Z",
     "start_time": "2021-09-18T15:40:31.721159Z"
    },
    "colab": {},
    "colab_type": "code",
    "id": "zmUyRlSPmse_"
   },
   "outputs": [],
   "source": [
    "from matplotlib.patches import FancyArrowPatch\n",
    "from mpl_toolkits.mplot3d import proj3d\n",
    "\n",
    "class Arrow3D(FancyArrowPatch):\n",
    "    def __init__(self, xs, ys, zs, *args, **kwargs):\n",
    "        FancyArrowPatch.__init__(self, (0,0), (0,0), *args, **kwargs)\n",
    "        self._verts3d = xs, ys, zs\n",
    "\n",
    "    def draw(self, renderer):\n",
    "        xs3d, ys3d, zs3d = self._verts3d\n",
    "        xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M)\n",
    "        self.set_positions((xs[0],ys[0]),(xs[1],ys[1]))\n",
    "        FancyArrowPatch.draw(self, renderer)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-09-18T15:40:32.659633Z",
     "start_time": "2021-09-18T15:40:32.609343Z"
    },
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 454
    },
    "colab_type": "code",
    "executionInfo": {
     "elapsed": 694,
     "status": "ok",
     "timestamp": 1546328037918,
     "user": {
      "displayName": "Bob Li",
      "photoUrl": "",
      "userId": "13034580972517925389"
     },
     "user_tz": -480
    },
    "id": "tQT8YSDxA1f8",
    "outputId": "3a43b0ba-0cb8-47e1-b019-2db84631fc6b"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Principal Components:\n",
      "[[-0.95250178 -0.24902446 -0.17529172]\n",
      " [ 0.29267159 -0.9076305  -0.30091563]\n",
      " [-0.08416476 -0.33792558  0.93740205]]\n",
      "\n",
      "\n",
      "Z value of the hyperplane:\n",
      "[[-0.63025233 -0.59433828 -0.55842423 -0.52251018 -0.48659612 -0.45068207\n",
      "  -0.41476802 -0.37885397 -0.34293992 -0.30702587]\n",
      " [-0.52611031 -0.49019626 -0.45428221 -0.41836815 -0.3824541  -0.34654005\n",
      "  -0.310626   -0.27471195 -0.2387979  -0.20288384]\n",
      " [-0.42196829 -0.38605424 -0.35014018 -0.31422613 -0.27831208 -0.24239803\n",
      "  -0.20648398 -0.17056993 -0.13465587 -0.09874182]\n",
      " [-0.31782626 -0.28191221 -0.24599816 -0.21008411 -0.17417006 -0.13825601\n",
      "  -0.10234196 -0.0664279  -0.03051385  0.0054002 ]\n",
      " [-0.21368424 -0.17777019 -0.14185614 -0.10594209 -0.07002804 -0.03411399\n",
      "   0.00180007  0.03771412  0.07362817  0.10954222]\n",
      " [-0.10954222 -0.07362817 -0.03771412 -0.00180007  0.03411399  0.07002804\n",
      "   0.10594209  0.14185614  0.17777019  0.21368424]\n",
      " [-0.0054002   0.03051385  0.0664279   0.10234196  0.13825601  0.17417006\n",
      "   0.21008411  0.24599816  0.28191221  0.31782626]\n",
      " [ 0.09874182  0.13465587  0.17056993  0.20648398  0.24239803  0.27831208\n",
      "   0.31422613  0.35014018  0.38605424  0.42196829]\n",
      " [ 0.20288384  0.2387979   0.27471195  0.310626    0.34654005  0.3824541\n",
      "   0.41836815  0.45428221  0.49019626  0.52611031]\n",
      " [ 0.30702587  0.34293992  0.37885397  0.41476802  0.45068207  0.48659612\n",
      "   0.52251018  0.55842423  0.59433828  0.63025233]]\n",
      "\n"
     ]
    }
   ],
   "source": [
    "axes = [-1.8, 1.8, -1.3, 1.3, -1.0, 1.0]\n",
    "\n",
    "x1s = np.linspace(axes[0], axes[1], 10)\n",
    "x2s = np.linspace(axes[2], axes[3], 10)\n",
    "x1, x2 = np.meshgrid(x1s, x2s)\n",
    "# principal components (orthogonal vectors that decide the hyperplane)\n",
    "C = pca.components_\n",
    "print(\"Principal Components:\\n{}\\n\\n\".format(C))\n",
    "# normal-vector of the hyperplane\n",
    "normal_vector = np.cross(C[0,:], C[1,:])\n",
    "\n",
    "# calculate z-value of the dataset\n",
    "z = np.empty((10, 10))\n",
    "for i in range(0,10):\n",
    "    for j in range(0,10):\n",
    "        z[i, j] = -(normal_vector[0]*x1[i, j]+normal_vector[1]*x2[i ,j])/normal_vector[2]\n",
    "\n",
    "print(\"Z value of the hyperplane:\\n{}\\n\".format(z))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-09-18T15:40:34.718707Z",
     "start_time": "2021-09-18T15:40:32.664516Z"
    },
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 357
    },
    "colab_type": "code",
    "executionInfo": {
     "elapsed": 1767,
     "status": "ok",
     "timestamp": 1546330698940,
     "user": {
      "displayName": "Bob Li",
      "photoUrl": "",
      "userId": "13034580972517925389"
     },
     "user_tz": -480
    },
    "id": "9BqcncFeKY7o",
    "outputId": "f72323ec-0f97-4219-c00a-c892e3b55912",
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 720x432 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib\n",
    "import matplotlib.pyplot as plt\n",
    "from mpl_toolkits.mplot3d import Axes3D\n",
    "\n",
    "# setup parameters of the coordinates\n",
    "fig = plt.figure(figsize=(10, 6))\n",
    "ax = fig.add_subplot(111, projection=\"3d\")    # first figure of 1*1 space\n",
    "ax.set_xlabel(\"$x_1$\", fontsize=18, color=\"tab:blue\")\n",
    "ax.set_ylabel(\"$x_2$\", fontsize=18, color=\"tab:blue\")\n",
    "ax.set_zlabel(\"$x_3$\", fontsize=18, color=\"tab:blue\")\n",
    "ax.set_xlim(axes[0:2])\n",
    "ax.set_ylim(axes[2:4])\n",
    "ax.set_zlim(axes[4:6])\n",
    "\n",
    "# plot the X_decorrelated_DR dataset\n",
    "k = 0\n",
    "Z_projected = np.empty((60, 1))\n",
    "for i in range(0,60):\n",
    "    for j in range(0,2):\n",
    "        while k<60:\n",
    "            Z_projected[k] = -(normal_vector[0]*X_decorrelated_DR[i, j]+normal_vector[1]*X_decorrelated_DR[i ,j])/normal_vector[2]\n",
    "            k = k+1\n",
    "\n",
    "# plot the original datatset\n",
    "ax.plot(X[:, 0], X[:, 1], X[:, 2], \"bo\", alpha=0.6)\n",
    "      \n",
    "# plot the X_decorrelated_DR dataset\n",
    "ax.plot(X_decorrelated_DR[:, 0], X_decorrelated_DR[:, 1], Z_projected[:, 0], \"mo\", alpha=0.6)\n",
    "\n",
    "# plot the track of projection\n",
    "for j in range(0, 60):\n",
    "    ax.add_artist(Arrow3D([X[j, 0], X_decorrelated_DR[j, 0]],[X[j, 1], X_decorrelated_DR[j, 1]],[X[j, 2], Z_projected[j, 0]], mutation_scale=15, lw=1, arrowstyle=\"-|>\", color=\"tab:gray\"))\n",
    "\n",
    "# plot the hyperplane(2_D) for projection\n",
    "ax.plot_surface(x1, x2, z, alpha=0.2, color=\"k\")\n",
    "\n",
    "# plot the principal compoents(orthogonal vectors that decide the hyperplane)\n",
    "ax.add_artist(Arrow3D([0, C[0, 0]],[0, C[0, 1]],[0, C[0, 2]], mutation_scale=15, lw=1, arrowstyle=\"-|>\", color=\"tab:red\"))\n",
    "ax.add_artist(Arrow3D([0, C[1, 0]],[0, C[1, 1]],[0, C[1, 2]], mutation_scale=15, lw=1, arrowstyle=\"-|>\", color=\"tab:red\"))\n",
    "\n",
    "# plot the origin\n",
    "ax.plot([0], [0], [0], \"r.\")\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "colab": {
   "collapsed_sections": [],
   "name": "dimensionality_reduction.ipynb",
   "provenance": [],
   "version": "0.3.2"
  },
  "hide_input": false,
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}


================================================
FILE: 02_Python/Discrete_Cosine_Transform_(DCT)/Discrete_Cosine_Transform.ipynb
================================================
{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<small><small><i>\n",
    "All the IPython Notebooks in **Clustering Algorithms** lecture series by **[Dr. Milaan Parmar](https://www.linkedin.com/in/milaanparmar/)** are available @ **[GitHub](https://github.com/milaan9/Clustering_Algorithms)**\n",
    "</i></small></small>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Discrete Cosine Transform\n",
    "This is a little jupyter notebook that does a discrete cosine transform (DCT). DCT is a thing like the Fourier transform that's used in JPGs."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Requirement already satisfied: numpy in c:\\programdata\\anaconda3\\lib\\site-packages (1.20.1)\n",
      "Requirement already satisfied: pandas in c:\\programdata\\anaconda3\\lib\\site-packages (1.2.4)\n",
      "Requirement already satisfied: numpy>=1.16.5 in c:\\programdata\\anaconda3\\lib\\site-packages (from pandas) (1.20.1)\n",
      "Requirement already satisfied: pytz>=2017.3 in c:\\programdata\\anaconda3\\lib\\site-packages (from pandas) (2021.1)\n",
      "Requirement already satisfied: python-dateutil>=2.7.3 in c:\\programdata\\anaconda3\\lib\\site-packages (from pandas) (2.8.1)\n",
      "Requirement already satisfied: six>=1.5 in c:\\programdata\\anaconda3\\lib\\site-packages (from python-dateutil>=2.7.3->pandas) (1.15.0)\n",
      "Requirement already satisfied: matplotlib in c:\\programdata\\anaconda3\\lib\\site-packages (3.3.4)\n",
      "Requirement already satisfied: python-dateutil>=2.1 in c:\\programdata\\anaconda3\\lib\\site-packages (from matplotlib) (2.8.1)\n",
      "Requirement already satisfied: pillow>=6.2.0 in c:\\programdata\\anaconda3\\lib\\site-packages (from matplotlib) (8.2.0)\n",
      "Requirement already satisfied: cycler>=0.10 in c:\\programdata\\anaconda3\\lib\\site-packages (from matplotlib) (0.10.0)\n",
      "Requirement already satisfied: numpy>=1.15 in c:\\programdata\\anaconda3\\lib\\site-packages (from matplotlib) (1.20.1)\n",
      "Requirement already satisfied: kiwisolver>=1.0.1 in c:\\programdata\\anaconda3\\lib\\site-packages (from matplotlib) (1.3.1)\n",
      "Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.3 in c:\\programdata\\anaconda3\\lib\\site-packages (from matplotlib) (2.4.7)\n",
      "Requirement already satisfied: six in c:\\programdata\\anaconda3\\lib\\site-packages (from cycler>=0.10->matplotlib) (1.15.0)\n",
      "Requirement already satisfied: scipy in c:\\programdata\\anaconda3\\lib\\site-packages (1.6.2)\n",
      "Requirement already satisfied: numpy<1.23.0,>=1.16.5 in c:\\programdata\\anaconda3\\lib\\site-packages (from scipy) (1.20.1)\n",
      "Populating the interactive namespace from numpy and matplotlib\n"
     ]
    }
   ],
   "source": [
    "!pip install numpy\n",
    "!pip install pandas\n",
    "!pip install matplotlib\n",
    "!pip install scipy\n",
    "\n",
    "# Imports and the like\n",
    "%pylab inline\n",
    "\n",
    "import numpy as np\n",
    "from PIL import Image\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# Graph settings. No more jet plz\n",
    "mpl.rcParams['image.cmap'] = 'gray'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1.        , 1.        , 1.        , 1.        , 1.        ,\n",
       "        1.        , 1.        , 1.        ],\n",
       "       [1.        , 1.        , 0.92156863, 0.08235294, 0.72941176,\n",
       "        1.        , 1.        , 1.        ],\n",
       "       [1.        , 1.        , 0.54901961, 0.29019608, 0.2627451 ,\n",
       "        1.        , 1.        , 1.        ],\n",
       "       [1.        , 0.99607843, 0.15686275, 0.8745098 , 0.16862745,\n",
       "        0.8627451 , 1.        , 1.        ],\n",
       "       [1.        , 0.75294118, 0.00392157, 0.09019608, 0.02745098,\n",
       "        0.4745098 , 1.        , 1.        ],\n",
       "       [1.        , 0.35686275, 0.6       , 1.        , 0.88235294,\n",
       "        0.09803922, 0.98039216, 1.        ],\n",
       "       [1.        , 0.2627451 , 0.90196078, 1.        , 1.        ,\n",
       "        0.29411765, 0.8745098 , 1.        ],\n",
       "       [1.        , 1.        , 1.        , 1.        , 1.        ,\n",
       "        1.        , 1.        , 1.        ]])"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Load up the image, and normalise it\n",
    "img_path = 'data/a.png'\n",
    "# Open image. We also convert to grayscale to make it simpler to deal with\n",
    "img = np.asarray(Image.open(img_path).convert('L'))\n",
    "# Normalise to 0-1\n",
    "img = img / 255\n",
    "\n",
    "plt.imshow(img)\n",
    "img"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "saved to output/test.png\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# And a little function to save images\n",
    "\n",
    "# Shout out to\n",
    "# stackoverflow.com/questions/31544130/saving-an-imshow-like-image-while-preserving-resolution\n",
    "def save_image(data, name, vmin=0, vmax=1):\n",
    "    cmap = plt.cm.gray\n",
    "    norm = plt.Normalize(vmin=vmin, vmax=vmax)\n",
    "    \n",
    "    img = cmap(norm(data))\n",
    "    plt.imsave(name, img)\n",
    "    print('saved to {}'.format(name))\n",
    "\n",
    "# quick test\n",
    "arr = np.linspace(-1, 1, 8 * 8).reshape((8, 8))\n",
    "plt.imshow(arr)\n",
    "save_image(arr, name=\"output/test.png\", vmin=-1, vmax=1)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Displaying the frequency things\n",
    "Let's start with seeing if we can render each of the 'frequencies' that the DCT gives us."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1., 0., 0., 0., 0., 0., 0., 0.],\n",
       "       [0., 0., 0., 0., 0., 0., 0., 0.],\n",
       "       [0., 0., 0., 0., 0., 0., 0., 0.],\n",
       "       [0., 0., 0., 0., 0., 0., 0., 0.],\n",
       "       [0., 0., 0., 0., 0., 0., 0., 0.],\n",
       "       [0., 0., 0., 0., 0., 0., 0., 0.],\n",
       "       [0., 0., 0., 0., 0., 0., 0., 0.],\n",
       "       [0., 0., 0., 0., 0., 0., 0., 0.]])"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "freq = np.zeros((8, 8))\n",
    "freq[0,0] = 1\n",
    "freq"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x23939b9e760>"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "from scipy.fftpack import idct\n",
    "\n",
    "freq_img = idct(idct(freq, axis=0), axis=1)\n",
    "plt.imshow(freq_img)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x23939c040a0>"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Ok, lets try for a more interesting frequency\n",
    "freq = np.zeros((8, 8))\n",
    "freq[2,3] = 1\n",
    "freq_img = idct(idct(freq, axis=0), axis=1)\n",
    "plt.imshow(freq_img)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Alright sweet, that worked. (Side point: that plot looks wiggly haha)\n",
    "\n",
    "Now lets try... ALL OF THEM"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "saved to output/components-0-0.png\n",
      "saved to output/components-0-1.png\n",
      "saved to output/components-0-2.png\n",
      "saved to output/components-0-3.png\n",
      "saved to output/components-0-4.png\n",
      "saved to output/components-0-5.png\n",
      "saved to output/components-0-6.png\n",
      "saved to output/components-0-7.png\n",
      "saved to output/components-1-0.png\n",
      "saved to output/components-1-1.png\n",
      "saved to output/components-1-2.png\n",
      "saved to output/components-1-3.png\n",
      "saved to output/components-1-4.png\n",
      "saved to output/components-1-5.png\n",
      "saved to output/components-1-6.png\n",
      "saved to output/components-1-7.png\n",
      "saved to output/components-2-0.png\n",
      "saved to output/components-2-1.png\n",
      "saved to output/components-2-2.png\n",
      "saved to output/components-2-3.png\n",
      "saved to output/components-2-4.png\n",
      "saved to output/components-2-5.png\n",
      "saved to output/components-2-6.png\n",
      "saved to output/components-2-7.png\n",
      "saved to output/components-3-0.png\n",
      "saved to output/components-3-1.png\n",
      "saved to output/components-3-2.png\n",
      "saved to output/components-3-3.png\n",
      "saved to output/components-3-4.png\n",
      "saved to output/components-3-5.png\n",
      "saved to output/components-3-6.png\n",
      "saved to output/components-3-7.png\n",
      "saved to output/components-4-0.png\n",
      "saved to output/components-4-1.png\n",
      "saved to output/components-4-2.png\n",
      "saved to output/components-4-3.png\n",
      "saved to output/components-4-4.png\n",
      "saved to output/components-4-5.png\n",
      "saved to output/components-4-6.png\n",
      "saved to output/components-4-7.png\n",
      "saved to output/components-5-0.png\n",
      "saved to output/components-5-1.png\n",
      "saved to output/components-5-2.png\n",
      "saved to output/components-5-3.png\n",
      "saved to output/components-5-4.png\n",
      "saved to output/components-5-5.png\n",
      "saved to output/components-5-6.png\n",
      "saved to output/components-5-7.png\n",
      "saved to output/components-6-0.png\n",
      "saved to output/components-6-1.png\n",
      "saved to output/components-6-2.png\n",
      "saved to output/components-6-3.png\n",
      "saved to output/components-6-4.png\n",
      "saved to output/components-6-5.png\n",
      "saved to output/components-6-6.png\n",
      "saved to output/components-6-7.png\n",
      "saved to output/components-7-0.png\n",
      "saved to output/components-7-1.png\n",
      "saved to output/components-7-2.png\n",
      "saved to output/components-7-3.png\n",
      "saved to output/components-7-4.png\n",
      "saved to output/components-7-5.png\n",
      "saved to output/components-7-6.png\n",
      "saved to output/components-7-7.png\n"
     ]
    },
    {
     "data": {
      "image/png": "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
Download .txt
gitextract_1u4gek_a/

├── 01_MATLAB/
│   ├── Clustering.m
│   ├── README.md
│   ├── data/
│   │   ├── toy_clustering.mat
│   │   └── toy_subspace_clustering.mat
│   ├── lib/
│   │   ├── DBSCAN.m
│   │   ├── Entropy_Weighting_Subspace_Kmeans.m
│   │   ├── Gaussian_Mixture.m
│   │   ├── ISODATA.m
│   │   ├── Kmeans.m
│   │   ├── Kmeanspp.m
│   │   ├── LVQ.m
│   │   ├── Mean_Shift.m
│   │   └── Subspace_Kmeans.m
│   └── tool/
│       ├── GenerateDataset.m
│       └── PlotData.m
├── 02_Python/
│   ├── A_Star.py
│   ├── Collaborative_Filtering.py
│   ├── DBSCAN.py
│   ├── Decision_Trees.py
│   ├── Dimensionality_Reduction/
│   │   └── Dimensionality_Reduction.ipynb
│   ├── Discrete_Cosine_Transform_(DCT)/
│   │   └── Discrete_Cosine_Transform.ipynb
│   ├── FP_Growth.py
│   ├── Genetic_Algorithm.py
│   ├── K-Means_Implementation/
│   │   ├── Iris.csv
│   │   ├── K-Means_Clustering_on_Iris_Dataset.ipynb
│   │   ├── Links.txt
│   │   └── README.md
│   ├── K_Means.py
│   ├── K_Nearest_Neighbours.py
│   ├── K_Nearest_Neighbours_In_Parallel.py
│   ├── Linear_Regression.py
│   ├── Logistic_Regression.py
│   ├── MSCRED/
│   │   ├── cnn_lstm/
│   │   │   ├── Untitled.ipynb
│   │   │   ├── __init__.py
│   │   │   ├── convlstm-update.py
│   │   │   ├── convlstm.ipynb
│   │   │   ├── convlstm.py
│   │   │   ├── evaluation.ipynb
│   │   │   ├── evalution.py
│   │   │   ├── generation_signature_matrice.ipynb
│   │   │   ├── generation_signature_matrice.py
│   │   │   ├── report.txt
│   │   │   └── utils.py
│   │   └── data/
│   │       └── synthetic_data_with_anomaly-s-1.csv
│   ├── Mean_Shift.py
│   ├── Naive_Bayes.py
│   ├── README.md
│   ├── Random_Forest_Classifier.py
│   ├── Support_Vector_Machine.py
│   └── data/
│       ├── chronic_kidney_disease.csv
│       ├── concentric_circles.csv
│       ├── cron_jobs_schedule.csv
│       ├── graph.in
│       ├── ipl.csv
│       ├── iris.csv
│       ├── logistic_regression_data.txt
│       ├── titanic-subset.csv
│       └── titanic.csv
├── LICENSE
└── README.md
Download .txt
SYMBOL INDEX (137 symbols across 18 files)

FILE: 02_Python/A_Star.py
  class Node (line 13) | class Node:
    method __init__ (line 14) | def __init__(self, key, i, j):
    method addNeighbor (line 36) | def addNeighbor(self, node):
    method setTraversability (line 39) | def setTraversability(self, isTraversable):
    method isTraversable (line 42) | def isTraversable(self):
    method getKey (line 45) | def getKey(self):
    method getPos (line 48) | def getPos(self):
    method setPriority (line 51) | def setPriority(self, newPriority):
    method getPriority (line 54) | def getPriority(self):
    method setParent (line 57) | def setParent(self, parent):
    method getParent (line 60) | def getParent(self):
    method getDistFromStart (line 63) | def getDistFromStart(self):
    method setDistFromStart (line 66) | def setDistFromStart(self, distFromStart):
    method getNeighbors (line 69) | def getNeighbors(self):
    method setKey (line 72) | def setKey(self, key):
    method __lt__ (line 76) | def __lt__(self, node):
  class Graph (line 79) | class Graph:
    method __init__ (line 80) | def __init__(self):
    method addNode (line 83) | def addNode(self, node):
    method getNode (line 86) | def getNode(self, pos):
    method addEdge (line 90) | def addEdge(self, node1, node2):
  class Map2D (line 94) | class Map2D:
    method __init__ (line 95) | def __init__(self, filePath):
    method printMap (line 141) | def printMap(self):
    method __isValid (line 151) | def __isValid(self, i, j):
    method getStart (line 158) | def getStart(self):
    method getEnd (line 161) | def getEnd(self):
  class AStar (line 164) | class AStar:
    method __init__ (line 165) | def __init__(self, inMap):
    method __heuristic (line 183) | def __heuristic(self, node, targetNode):
    method findPath (line 188) | def findPath(self):
    method getMap (line 235) | def getMap(self):

FILE: 02_Python/Collaborative_Filtering.py
  function compute_similarity (line 96) | def compute_similarity(item1,item2,userRatings):
  function build_similarity_matrix (line 115) | def build_similarity_matrix(userRatings):
  function normalize (line 126) | def normalize(rating):
  function denormalize (line 131) | def denormalize(rating):
  function prediction (line 134) | def prediction(username,item):
  function recommendation (line 145) | def recommendation(username,userRatings):

FILE: 02_Python/DBSCAN.py
  class CustomDBSCAN (line 16) | class CustomDBSCAN():
    method __init__ (line 17) | def __init__(self):
    method neighbour_points (line 22) | def neighbour_points(self, data, pointId, epsilon):
    method fit (line 31) | def fit(self, data, Eps, MinPt):
    method visualize (line 87) | def visualize(self, data, cluster, numberOfClusters):
  function main (line 108) | def main():

FILE: 02_Python/Decision_Trees.py
  class CustomDecisionTree (line 11) | class CustomDecisionTree():
    method __init__ (line 12) | def __init__(self):
    method majorityCnt (line 15) | def majorityCnt(self, classList):
    method calcShannonEnt (line 25) | def calcShannonEnt(self, dataSet):
    method splitDataSet (line 39) | def splitDataSet(self, dataSet, axis, value):
    method chooseBestFeatureToSplit (line 49) | def chooseBestFeatureToSplit(self, dataSet, labels):
    method createTree (line 72) | def createTree(self, dataSet, labels):
  function main (line 95) | def main():

FILE: 02_Python/FP_Growth.py
  class FPGrowth (line 12) | class FPGrowth:
    method __init__ (line 13) | def __init__(self, k =3, tolerance = 0.0001, max_iterations = 500):
  function main (line 17) | def main():

FILE: 02_Python/Genetic_Algorithm.py
  class CustomGeneticAlgorithm (line 7) | class CustomGeneticAlgorithm():
    method server_present (line 9) | def server_present(self, server, time):
    method deployed_to_hourlyplanning (line 17) | def deployed_to_hourlyplanning(self, deployed_hourly_cron_capacity):
    method generate_random_plan (line 40) | def generate_random_plan(self, n_days, n_racks):
    method generate_initial_population (line 54) | def generate_initial_population(self, population_size, n_days=7, n_rac...
    method calculate_fitness (line 62) | def calculate_fitness(self, deployed_hourly_cron_capacity, required_ho...
    method crossover (line 73) | def crossover(self, population, n_offspring):
    method mutate_parent (line 93) | def mutate_parent(self, parent, n_mutations):
    method mutate_gen (line 104) | def mutate_gen(self, population, n_mutations):
    method is_acceptable (line 110) | def is_acceptable(self, parent):
    method select_acceptable (line 113) | def select_acceptable(self, population):
    method select_best (line 118) | def select_best(self, population, required_hourly_cron_capacity, n_best):
    method run (line 139) | def run(self, required_hourly_cron_capacity, n_iterations, n_populatio...
  function main (line 156) | def main():

FILE: 02_Python/K_Means.py
  class K_Means (line 17) | class K_Means:
    method __init__ (line 18) | def __init__(self, k =3, tolerance = 0.0001, max_iterations = 500):
    method fit (line 23) | def fit(self, data):
    method pred (line 63) | def pred(self, data):
  function main (line 68) | def main():

FILE: 02_Python/K_Nearest_Neighbours.py
  class CustomKNN (line 22) | class CustomKNN:
    method __init__ (line 24) | def __init__(self):
    method predict (line 29) | def predict(self, training_data, to_predict, k = 3):
    method test (line 46) | def test(self, test_set, training_set):
  function mod_data (line 58) | def mod_data(df):
  function main (line 76) | def main():

FILE: 02_Python/K_Nearest_Neighbours_In_Parallel.py
  class CustomKNN (line 25) | class CustomKNN:
    method __init__ (line 27) | def __init__(self):
    method predict (line 32) | def predict(self, training_data, to_predict, k = 3):
    method test (line 50) | def test(self, test_set, training_set):
  function mod_data (line 73) | def mod_data(df):
  function main (line 92) | def main():

FILE: 02_Python/Linear_Regression.py
  class CustomLinearRegression (line 23) | class CustomLinearRegression:
    method __init__ (line 25) | def __init__(self):
    method am (line 30) | def am(self, arr):
    method best_fit (line 37) | def best_fit(self, dimOne, dimTwo):
    method y_intercept (line 42) | def y_intercept(self, dimOne ,dimTwo):
    method predict (line 47) | def predict(self, ip):
    method squared_error (line 53) | def squared_error(self, original, model):
    method cod (line 57) | def cod(self, original, model):
  function main (line 63) | def main():

FILE: 02_Python/Logistic_Regression.py
  class CustomLogisticRegression (line 22) | class CustomLogisticRegression:
    method __init__ (line 24) | def __init__(self, x, y, tolerence = 0.00001):
    method cost_fn (line 37) | def cost_fn(self, m):
    method sigmoid_function (line 43) | def sigmoid_function(z):
    method gradients (line 47) | def gradients(self, m):
    method descent (line 54) | def descent(self):
  function main (line 67) | def main():

FILE: 02_Python/MSCRED/cnn_lstm/convlstm-update.py
  function cnn_encoder_layer (line 7) | def cnn_encoder_layer(data, filter_layer, strides):
  function tensor_variable (line 25) | def tensor_variable(shape, name):
  function cnn_encoder (line 37) | def cnn_encoder(data):
  function cnn_lstm_attention_layer (line 66) | def cnn_lstm_attention_layer(input_data, layer_number):
  function cnn_decoder_layer (line 99) | def cnn_decoder_layer(conv_lstm_out_c, filter, output_shape, strides):
  function cnn_decoder (line 119) | def cnn_decoder(lstm1_out, lstm2_out, lstm3_out, lstm4_out):
  function main (line 138) | def main():

FILE: 02_Python/MSCRED/cnn_lstm/convlstm.py
  function cnn_encoder_layer (line 7) | def cnn_encoder_layer(data, filter_layer, strides):
  function tensor_variable (line 25) | def tensor_variable(shape, name):
  function cnn_encoder (line 37) | def cnn_encoder(data):
  function cnn_lstm_attention_layer (line 66) | def cnn_lstm_attention_layer(input_data, layer_number):
  function cnn_decoder_layer (line 99) | def cnn_decoder_layer(conv_lstm_out_c, filter, output_shape, strides):
  function cnn_decoder (line 119) | def cnn_decoder(lstm1_out, lstm2_out, lstm3_out, lstm4_out):
  function main (line 138) | def main():

FILE: 02_Python/MSCRED/cnn_lstm/generation_signature_matrice.py
  class SignatureMatrices (line 15) | class SignatureMatrices:
    method __init__ (line 16) | def __init__(self):
    method signature_matrices_generation (line 27) | def signature_matrices_generation(self, win):
    method generate_train_test (line 50) | def generate_train_test(self, signature_matrices):

FILE: 02_Python/Mean_Shift.py
  class CustomMS (line 18) | class CustomMS:
    method __init__ (line 20) | def __init__(self):
  function main (line 24) | def main():

FILE: 02_Python/Naive_Bayes.py
  class CustomNB (line 20) | class CustomNB:
    method __init__ (line 22) | def __init__(self):
    method create_freq_table (line 25) | def create_freq_table(self, texts, labels=None):
    method train (line 40) | def train(self, freq):
    method predict (line 69) | def predict(self, text, spam_model,non_spam_model):
  function main (line 87) | def main():

FILE: 02_Python/Random_Forest_Classifier.py
  function evaluate_model (line 66) | def evaluate_model(predictions, probs, train_predictions, train_probs):
  function plot_confusion_matrix (line 113) | def plot_confusion_matrix(cm, classes,

FILE: 02_Python/Support_Vector_Machine.py
  class CustomSVM (line 18) | class CustomSVM:
    method __init__ (line 20) | def __init__(self):
    method fit (line 25) | def fit(self, dataset):
    method predict (line 78) | def predict(self, attrs):
    method test (line 85) | def test(self, test_set):
  function main (line 96) | def main():
Condensed preview — 60 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (990K chars).
[
  {
    "path": "01_MATLAB/Clustering.m",
    "chars": 3810,
    "preview": "function [centroid, result] = Clustering(data, method, varargin)\n% Currently, following clustering algorithms are suppor"
  },
  {
    "path": "01_MATLAB/README.md",
    "chars": 3613,
    "preview": "<p align=\"center\"> \n<a href=\"https://github.com/milaan9\"><img src=\"https://img.shields.io/static/v1?logo=github&label=ma"
  },
  {
    "path": "01_MATLAB/lib/DBSCAN.m",
    "chars": 3843,
    "preview": "function label = DBSCAN(data, eps, minPts)\n% label = DBSCAN(data, epsilon, minPts)\n%   Main part of DBSCAN (Density-Base"
  },
  {
    "path": "01_MATLAB/lib/Entropy_Weighting_Subspace_Kmeans.m",
    "chars": 2089,
    "preview": "function [centroid, dimension_weight, class] = Entropy_Weighting_Subspace_Kmeans(data, iteration, K, beta, lambda, verbo"
  },
  {
    "path": "01_MATLAB/lib/Gaussian_Mixture.m",
    "chars": 2685,
    "preview": "function [label, alpha, miu, sigma] = Gaussian_Mixture(data, k, iter)\n% function [label, alpha, miu, sigma] = Gaussian_M"
  },
  {
    "path": "01_MATLAB/lib/ISODATA.m",
    "chars": 3552,
    "preview": "function [centroid, result] = ISODATA(data, iteration, desired_k, minimum_n, maximum_variance, minimum_d)\n\n% Pre-allocat"
  },
  {
    "path": "01_MATLAB/lib/Kmeans.m",
    "chars": 1470,
    "preview": "function [centroid, class] = Kmeans(data, k, iteration)\n% Main part of Kmeans clustering algorithm.\n%\n% Args:\n%   data: "
  },
  {
    "path": "01_MATLAB/lib/Kmeanspp.m",
    "chars": 2712,
    "preview": "function [centroid, class] = Kmeanspp(data, k, iteration)\n% Main part of Kmeans clustering algorithm.\n%\n% Args:\n%   data"
  },
  {
    "path": "01_MATLAB/lib/LVQ.m",
    "chars": 1949,
    "preview": "function [centroid, label] = LVQ(data, q, neta, x, y, iter)\n% function [centroid, label] = LVQ(data, q, x, y)\n%   Unlike"
  },
  {
    "path": "01_MATLAB/lib/Mean_Shift.m",
    "chars": 3347,
    "preview": "function [centroid, result] = Mean_Shift(data, thr)\n% Main part of mean shift clustering algorithm.\n%\n% Args:\n%   data: "
  },
  {
    "path": "01_MATLAB/lib/Subspace_Kmeans.m",
    "chars": 2875,
    "preview": "function [centroid, dimension_weight, class] = Subspace_Kmeans(data, iteration, K, beta, verbose)\n\n% Pre-allocate weight"
  },
  {
    "path": "01_MATLAB/tool/GenerateDataset.m",
    "chars": 927,
    "preview": "function dataset = GenerateDataset(n, p, k, pd, varargin)\n% dataset = GenerateDataset(n, p, k, pd, varargin)\n%   Generat"
  },
  {
    "path": "01_MATLAB/tool/PlotData.m",
    "chars": 1378,
    "preview": "function PlotData(data, label, varargin)\n% PlotData(data, label, varargin)\n%   Plot data, with different classes corresp"
  },
  {
    "path": "02_Python/A_Star.py",
    "chars": 8074,
    "preview": "#!/usr/bin/env python3\n\n#==============================================================================================="
  },
  {
    "path": "02_Python/Collaborative_Filtering.py",
    "chars": 3633,
    "preview": "from math import sqrt\n\n data = {\n \t\t\t\"Manish\": {\n \t\t\t\t\t\t\"Interstellar\": 4,\n \t\t\t\t\t\t\"The Dark Knight\": 5,\n \t\t\t\t\t\t\"Wanted\":"
  },
  {
    "path": "02_Python/DBSCAN.py",
    "chars": 4358,
    "preview": "# ================================================================================================================\n# ---"
  },
  {
    "path": "02_Python/Decision_Trees.py",
    "chars": 4331,
    "preview": "# ================================================================================================================\n# ---"
  },
  {
    "path": "02_Python/Dimensionality_Reduction/Dimensionality_Reduction.ipynb",
    "chars": 97177,
    "preview": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"<small><small><i>\\n\",\n    \"All the "
  },
  {
    "path": "02_Python/Discrete_Cosine_Transform_(DCT)/Discrete_Cosine_Transform.ipynb",
    "chars": 228655,
    "preview": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"<small><small><i>\\n\",\n    \"All the "
  },
  {
    "path": "02_Python/FP_Growth.py",
    "chars": 686,
    "preview": "#================================================================================================================\n#-----"
  },
  {
    "path": "02_Python/Genetic_Algorithm.py",
    "chars": 6353,
    "preview": "import copy\n\nimport numpy as np\nimport pandas as pd\n\n\nclass CustomGeneticAlgorithm():\n\n    def server_present(self, serv"
  },
  {
    "path": "02_Python/K-Means_Implementation/Iris.csv",
    "chars": 5107,
    "preview": "Id,SepalLengthCm,SepalWidthCm,PetalLengthCm,PetalWidthCm,Species\n1,5.1,3.5,1.4,0.2,Iris-setosa\n2,4.9,3.0,1.4,0.2,Iris-se"
  },
  {
    "path": "02_Python/K-Means_Implementation/K-Means_Clustering_on_Iris_Dataset.ipynb",
    "chars": 237560,
    "preview": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"<a href=\\\"https://github.com/milaan"
  },
  {
    "path": "02_Python/K-Means_Implementation/Links.txt",
    "chars": 75,
    "preview": "https://www.machinelearningplus.com/predictive-modeling/k-means-clustering/"
  },
  {
    "path": "02_Python/K-Means_Implementation/README.md",
    "chars": 176,
    "preview": "# K-Means-Clustering\n\nIt is a Unsupervised Machine Learning Algorithm. In this notebook we to predict the optimum number"
  },
  {
    "path": "02_Python/K_Means.py",
    "chars": 2922,
    "preview": "#================================================================================================================\n#-----"
  },
  {
    "path": "02_Python/K_Nearest_Neighbours.py",
    "chars": 3929,
    "preview": "#================================================================================================================\n#-----"
  },
  {
    "path": "02_Python/K_Nearest_Neighbours_In_Parallel.py",
    "chars": 4269,
    "preview": "#================================================================================================================\n#-----"
  },
  {
    "path": "02_Python/Linear_Regression.py",
    "chars": 3966,
    "preview": "#================================================================================================================\n#-----"
  },
  {
    "path": "02_Python/Logistic_Regression.py",
    "chars": 3016,
    "preview": "#================================================================================================================\n#-----"
  },
  {
    "path": "02_Python/MSCRED/cnn_lstm/Untitled.ipynb",
    "chars": 4704,
    "preview": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n "
  },
  {
    "path": "02_Python/MSCRED/cnn_lstm/__init__.py",
    "chars": 1,
    "preview": "\n"
  },
  {
    "path": "02_Python/MSCRED/cnn_lstm/convlstm-update.py",
    "chars": 7313,
    "preview": "import tensorflow as tf\nimport cnn_lstm.utils as util\nimport numpy as np\nimport os\n\n\ndef cnn_encoder_layer(data, filter_"
  },
  {
    "path": "02_Python/MSCRED/cnn_lstm/convlstm.ipynb",
    "chars": 104518,
    "preview": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\":"
  },
  {
    "path": "02_Python/MSCRED/cnn_lstm/convlstm.py",
    "chars": 7195,
    "preview": "import tensorflow as tf\nimport cnn_lstm.utils as util\nimport numpy as np\nimport os\n\n\ndef cnn_encoder_layer(data, filter_"
  },
  {
    "path": "02_Python/MSCRED/cnn_lstm/evaluation.ipynb",
    "chars": 45011,
    "preview": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\":"
  },
  {
    "path": "02_Python/MSCRED/cnn_lstm/evalution.py",
    "chars": 3270,
    "preview": "import numpy as np\nimport matplotlib.pyplot as plt\nimport os\nimport cnn_lstm.utils as util\nimport re\n\n# score initializa"
  },
  {
    "path": "02_Python/MSCRED/cnn_lstm/generation_signature_matrice.ipynb",
    "chars": 7138,
    "preview": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\":"
  },
  {
    "path": "02_Python/MSCRED/cnn_lstm/generation_signature_matrice.py",
    "chars": 4182,
    "preview": "\"\"\"\nTo represent the inter-correlations between different pairs of time series om a multivariate\ntime  series segment fr"
  },
  {
    "path": "02_Python/MSCRED/cnn_lstm/report.txt",
    "chars": 3550,
    "preview": "TensorFlow 2.0 Upgrade Script\n-----------------------------\nConverted 1 files\nDetected 3 issues that require attention\n-"
  },
  {
    "path": "02_Python/MSCRED/cnn_lstm/utils.py",
    "chars": 619,
    "preview": "# Parameter initialization\n\ngap_time = 10  # gap time between each segment\nwin_size = [10, 30, 60]  # window size of eac"
  },
  {
    "path": "02_Python/Mean_Shift.py",
    "chars": 2445,
    "preview": "#================================================================================================================\n#-----"
  },
  {
    "path": "02_Python/Naive_Bayes.py",
    "chars": 4171,
    "preview": "#================================================================================================================\n#-----"
  },
  {
    "path": "02_Python/README.md",
    "chars": 1243,
    "preview": "<p align=\"center\"> \n<a href=\"https://github.com/milaan9\"><img src=\"https://img.shields.io/static/v1?logo=github&label=ma"
  },
  {
    "path": "02_Python/Random_Forest_Classifier.py",
    "chars": 5551,
    "preview": "import pandas as pd\nimport numpy as np\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.ensemble import"
  },
  {
    "path": "02_Python/Support_Vector_Machine.py",
    "chars": 4172,
    "preview": "#================================================================================================================\n#-----"
  },
  {
    "path": "02_Python/data/chronic_kidney_disease.csv",
    "chars": 43147,
    "preview": "age,bp,sg,al,su,rbc,pc,pcc,ba,bgr,bu,sc,sod,pot,hemo,pcv,wbcc,rbcc,htn,dm,cad,appet,pe,ane,class\n48,80,1.020,1,0,?,norma"
  },
  {
    "path": "02_Python/data/concentric_circles.csv",
    "chars": 19802,
    "preview": "-0.5964976874818628,-0.7767253221036915\n-0.08971783096451161,-0.4135830691211844\n-1.0007531618691181,-0.326171630582138\n"
  },
  {
    "path": "02_Python/data/cron_jobs_schedule.csv",
    "chars": 2216,
    "preview": "job_id,day,hour,capacity\n1,0,1,1\n2,0,1,1\n3,0,2,2\n4,0,3,2\n5,0,4,2\n6,0,4,2\n7,0,5,4\n8,0,7,4\n9,0,7,4\n10,0,8,2\n11,0,8,2\n12,0,"
  },
  {
    "path": "02_Python/data/graph.in",
    "chars": 109,
    "preview": "-----###E-\n-------#--\n--#-#--#--\n--#-##S#--\n--#--###--\n--#----#--\n--#---##--\n--#--##---\n--####----\n----------"
  },
  {
    "path": "02_Python/data/ipl.csv",
    "chars": 3360,
    "preview": "one,two\n0.22767982399693698,0.8582041480574577\n0.9791882160551239,0.07715064988053028\n0.504576604695406,0.55311441372998"
  },
  {
    "path": "02_Python/data/iris.csv",
    "chars": 3001,
    "preview": "5.1,3.5,1.4,0.2,Iris-setosa\n4.9,3.0,1.4,0.2,Iris-setosa\n4.7,3.2,1.3,0.2,Iris-setosa\n4.6,3.1,1.5,0.2,Iris-setosa\n5.0,3.6,"
  },
  {
    "path": "02_Python/data/logistic_regression_data.txt",
    "chars": 2232,
    "preview": "0.051267,0.69956,1\n-0.092742,0.68494,1\n-0.21371,0.69225,1\n-0.375,0.50219,1\n-0.51325,0.46564,1\n-0.52477,0.2098,1\n-0.39804"
  },
  {
    "path": "02_Python/data/titanic-subset.csv",
    "chars": 1214,
    "preview": "pclass,sex,embarked,survived\n1,female,S,1\n1,male,S,1\n2,female,S,0\n1,male,S,0\n2,female,S,0\n1,male,S,1\n1,female,S,1\n1,male"
  },
  {
    "path": "02_Python/data/titanic.csv",
    "chars": 24049,
    "preview": "pclass,sex,age,embarked,sibsp,parch,survived\n1,female,29,0,0,S,1\n1,male,0.9167,1,2,S,1\n1,female,2,1,2,S,0\n1,male,30,1,2,"
  },
  {
    "path": "LICENSE",
    "chars": 1064,
    "preview": "MIT License\n\nCopyright (c) 2021 milaan9\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof"
  },
  {
    "path": "README.md",
    "chars": 3592,
    "preview": "<p align=\"center\"> \n<a href=\"https://github.com/milaan9\"><img src=\"https://img.shields.io/static/v1?logo=github&label=ma"
  }
]

// ... and 3 more files (download for full content)

About this extraction

This page contains the full source code of the milaan9/Clustering_Algorithms_from_Scratch GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 60 files (15.5 MB), approximately 520.1k tokens, and a symbol index with 137 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!