%% Demo-MAT4: Predictive Maintenance mit MATLAB
% Demo-MAT4 veranschaulicht die Vorausschauende Wartung am Beispiel eines Automobildatensatzes. Die Datenanalyse wird in diesem interaktiven Live Script durchgeführt und beinhaltet alle Schritte vom Einlesen der Trainingsdaten bis zur Erstellung des Entscheidungsbaum-Modells, das dann zur Klassifikation bzw. der Vorhersage eines Ausfalls im Testdatensatz verwendet wird.
% Übersicht
% 1 Trainingsdaten einlesen
% 2 Die Fragestellung
% 3 Datenvorbereitung
% 4 Das Entscheidungsbaum-Vorhersagemodell
%     4-1 Modell erstellen mit Kreuzvalidierung
%     4-2 Modell visualisieren: Grafikausgabe des Baums
%     4-3 Modell visualisieren: Textausgabe des Baums
% 5 Vorhersage
%     5-1 Einlesen der Testdaten
%     5-2 Vorhersage
% 6 Pruning

warning('off','stats:cvpartition:KFoldMissingGrp')

%
%% 1 Trainingsdaten einlesen
% Die Trainingsdaten des Automobildatensatzes werden mit der Funktion readtable eingelesen.

% Daten einlesen
data = readtable('automotive_data_train.csv', ...
    'ReadVariableNames', true, ...
    'VariableNamingRule', 'preserve'); 
% Erste 6 Zeilen anzeigen
disp(head(data))
% Statistische Übersicht der Variablen
summary(data)

%
%% 2 Die Fragestellung
% Uns interessiert, welche Merkmalskombinationen, d.h. welches Zusammenspiel der Sensorwerte, zu einem Ausfall des Motors führen. Die Frage, die der Entscheidungsbaum beantworten soll, lautet also: Welche Kombination von Merkmalen wird zu einem Ausfall führen? Werden alle Merkmale einen Einfluss auf den Ausfall haben und falls nein, mit welchem Gewicht werden welche Merkmale einen Ausfall bewirken?2. Zielvariable extrahieren.
%
%% 3 Datenvorbereitung
% Hier extrahieren wir die Zielvariable und die Merkmale aus dem Datensatz.
%

% Merkmale: Variablen 3 bis 23
X_train = data(1:end, 3:23); 
% Zielvariable: Ausfall (Zweite Spalte)
Y_train = data(1:end,2);
disp([Y_train(1:5, 1), X_train(1:5, 1:6)]);
% Name der Variablen / Merkmale
varNames = X_train.Properties.VariableNames'

%
%% 4 Das Entscheidungsbaum-Vorhersagemodell
% Hier wird ein interaktiver Entscheidungsbaum erstellt und anschließend gleich mit der Methode der Kreuzvalidierung bewertet.
% Die sofortige Berechnung der Performance-Kennzahlen hilft, den besten Entscheidungsbaum zu finden.
%% 4-1 Modell erstellen mit Kreuzvalidierung
% Der Entscheidungsbaum wird mit der Funktion fitctree erstellt, dabei können die Parameter angepasst werden. min_samples_split: gibt die Mindestanzahl der Beobachtungen pro Knoten an. max_num_splits: gibt die maximale Anzahl an Verzweigungen im Baum an.

% Parameterwahl
min_samples_split = 11; 
max_num_splits = 14;

% Erstellung des Modells
model = fitctree(X_train, Y_train,'SplitCriterion','deviance',...
'MinParentSize', min_samples_split,'MaxNumSplits', max_num_splits,...
'Reproducible', true);

% Kreuzvalidierung (1) mit Toolbox-Funktionen
kFold = 10; % Anzahl der Elemente in einer Partition
partitionedModel = crossval(model, 'KFold', 10);
% Berechne Vorhersagen
[valPredictions, valScores] = kfoldPredict(partitionedModel);
% Berechne Accuracy
valAccuracy = 1 - kfoldLoss(partitionedModel, 'LossFun', 'ClassifError');
T = array2table([valAccuracy], 'VariableNames', {'Accuracy'});

% Kreuzvalidierung (2) mit eigenen Funktionen
T = cross_validation(X_train, Y_train, min_samples_split, max_num_splits, kFold);
disp(T);

%% 4-2 Modell visualisieren: Grafikausgabe des Baums

view(model,'mode','graph') 

%
%% 4-3 Modell visualisieren: Textausgabe des Baums

view(model) 

%
%% 5 Vorhersage
%% 5-1 Einlesen der Testdaten
% Die Testdaten werden mit der Funktion readtable eingelesen.

data_test = readtable('automotive_data_test.csv','ReadVariableNames',true, ...
    'VariableNamingRule', 'preserve');
X_test = data_test(1:end,3:23);
disp(X_test(:, 1:6)); 

%
%% 5-2 Vorhersage
% Der erstellte Entscheidungsbaums wird zur Vorhersage eines Ausfall für neue Merkmalskombinationen der Funktion predict übergeben.

% Vorhersage mittels Baum
disp('Die Vorhersage für die neuen Merkmalskombinationen lautet')
Y_pred = predict(model, X_test)

%
%% 6 Pruning
% Ein Entscheidungsbaum kann durch sogenanntes Pruning vereinfacht und gekürzt werden, dabei werden Teilbäume weggeschnitten.
%

pruneLevel = max(model.PruneList);
model_pruned = prune(model, "level", 2);
view(model_pruned,'mode','graph');

%

function T = cross_validation(X_train, Y_train, min_samples_split, max_num_splits, k)
% Funktion für die Kreuzvalidierung des Modells
% Parameter: X_train, Y_train, min_samples_split, max_num_splits
% Rückgabewert: Tabelle mit Kennzahlen Accuracy, Precision, Recall

% Erstellung der Teildatensätze
index = cvpartition(1:size(Y_train, 1),'k', k);

% Erstelle Ausgabe-Matrix für die CV-Durchläufe
% mit Kennzahlen (Spalten: Precision, Sensitivity and Accuracy)
m_kenn = zeros(index.NumTestSets, 3);

% Kreuzvalidierung
for i = 1:index.NumTestSets
    trIdx = index.training(i);
    teIdx = index.test(i);
    % Bestimmung des Modells 
    treeCV = fitctree(X_train(trIdx,:), Y_train(trIdx,:), ...
        'SplitCriterion','deviance',...
        'MinParentSize', min_samples_split, ...
        'MaxNumSplits', max_num_splits, ...
        'Reproducible', true);
    % ... und der Vorhersage
    predictCV = predict(treeCV, X_train(teIdx,:));
    
    % Bestimmung der Kennzahlen
    actual = strcmp(Y_train{teIdx,:}, 'ja');
    predictCV = strcmp(predictCV,'ja');
    % ... und der Matrix
    Cf = cfmatrix2(actual, predictCV, [0,1], 0, 0); 
    mCv = Cf{1};
    m_kenn(i,1) = mCv(1,2); % Precision
    m_kenn(i,2) = mCv(2,2); % Recall
    m_kenn(i,3) = Cf{2}; % Accuracy
end

kenn = [sum(m_kenn(:,3))/index.NumTestSets,...
        sum(m_kenn(:,2))/index.NumTestSets,...
        sum(m_kenn(:,1))/index.NumTestSets ];

T = array2table(kenn, 'VariableNames', {'Accuracy', 'Recall', 'Precision'});

end