Main Content

Extract Features and Partition Data for Transmission System Fault Detection

R2026b
Since R2026b

This example shows how to extract features and partition data for training and testing fault detection models in an automotive transmission system. The extracted features are derived from the original signal data and reformatted into tabular data. After partitioning the tabular data into training and test sets, you must verify that the data sets conform to the requirements outlined in Define Requirements for Transmission System Fault Detection.

Note: This example requires Predictive Maintenance Toolbox™.

Extract Tabular Features

In order to create machine learning models for fault detection, the signal data must first be converted into tabular features. These features can then be provided as training data to the models.

You can begin by loading the signal data for the eight fault and failure scenarios described in Fault and Failure Scenarios. Then, use the helper function helperExtractFeatures to compute various statistics over the signals—such as mean, variance, median, root mean square (RMS), and more—along with predictive maintenance metrics. Finally, combine the extracted features for all the signals into the faultData table. To reduce the time required to extract features, the helperExtractFeatures function performs some computations in parallel when you have Parallel Computing Toolbox™.

To extract tabular features from the raw signal data, set runExtractFeatures to true in the code below. Because the feature extraction process can take some time, runExtractFeatures is set to false by default. In this case, the example uses previously saved data instead.

runExtractFeatures = false;

if runExtractFeatures
    supportFileNames = ["faultScenario1","faultScenario2","faultScenario3","faultScenario4", ...
        "faultScenario5","faultScenario6","faultScenario7","faultScenario8"];
    scenarioVarNames = ["dataTable_s1","dataTable_s2","dataTable_s3","dataTable_s4", ...
        "dataTable_s5","dataTable_s6","dataTable_s7","dataTable_s8"];
    
    faultData = table;
    for k = 1:numel(supportFileNames)
        if ~exist(scenarioVarNames(k),"var")
            d = load(matlab.internal.examples.downloadSupportFile("nnet", ...
                "data/transmissionfaults/" + supportFileNames(k) + ".mat"));
            scenarioData = d.(scenarioVarNames(k));
        else
            scenarioData = scenarioVarNames(k);
        end
        numObservations = size(scenarioData,1);
        chunk = helperExtractFeatures(scenarioData,numObservations);
        chunk.ScenarioID = repmat(k,height(chunk),1);
        faultData = [faultData; chunk];
    end
else
    if ~exist("faultData","var")
        load("TransmissionFaultData.mat","faultData");
    end
end

Display the first eight observations in the faultData table.

head(faultData)
      SigMean       SigMedian     SigRMS     SigVar     SigPeak    SigPeak2Peak    SigSkewness    SigKurtosis    SigCrestFactor    SigMAD     SigRangeCumSum    SigApproxEntropy    SigLyapExponent    PeakFreq    PeakSpecKurtosis    RPMMedian     RPMStd      RPMIQR      ShaftHz    FaultCode    ScenarioID
    ___________    ___________    _______    _______    _______    ____________    ___________    ___________    ______________    _______    ______________    ________________    _______________    ________    ________________    _________    ________    _________    _______    _________    __________

      -0.008488     -0.0014868     1.0048     1.0097    1.9065        3.813          0.0094102       2.264           1.8973        0.81736        1191.9            0.098646            119.65          10.417          113.06          19.881      0.006589    0.0082359    0.33135        0            1     
       0.016147       0.004569    0.99833    0.99643    1.9065        3.813           -0.01328      2.2809           1.9097         0.8112         911.6             0.16966            161.24          39.993          6.9331          31.847        24.029       47.417    0.53079        0            1     
     -0.0062246    -0.00087335     1.0012     1.0025    1.9065        3.813          0.0060058      2.2689           1.9042        0.81418        250.69             0.34513            164.76          10.893          6.3998          50.719        33.319       65.764    0.84531        0            1     
       0.002205     0.00017328     1.0025      1.005    1.9065        3.813         -0.0017144      2.2717           1.9018        0.81488        147.27             0.43647            175.18          67.948          9.5997          39.428        47.749       68.388    0.65713        0            1     
    -0.00074636    -6.2967e-05     1.0007     1.0015    1.9065        3.813          0.0016406      2.2724           1.9051        0.81341        106.54             0.42986            177.77          359.93          20.799          58.797        60.276       51.524    0.97994        0            1     
    -0.00043971     3.4018e-06     1.0019     1.0039    1.9065        3.813        -0.00059818       2.271           1.9028        0.81449        87.845             0.41084            179.47          298.38          25.066          20.694        107.46       127.97    0.34489        0            1     
         0.0011     7.4387e-05     1.0004     1.0008    1.9065        3.813         0.00079942      2.2733           1.9058        0.81319        71.399             0.40673            182.89          201.84          29.333          32.662        87.873       31.055    0.54437        0            1     
     0.00074992     8.9083e-07     1.0012     1.0024    1.9065        3.813        -0.00086386      2.2715           1.9042        0.81384        63.251             0.41399            187.21          1310.9          33.599          28.169        115.39       16.858    0.46948        0            1     

Create Separate Target Variable for Each Fault

As described in Fault and Failure Scenarios, the transmission system has two possible target faults (sensor drift and shaft wear) and one possible failure (gear tooth failure). The FaultCode variable in faultData encodes the two fault types in the following way:

  • 0 indicates a healthy state (with no faults).

  • 1 indicates a sensor drift fault.

  • 2 indicates a shaft wear fault.

  • 3 indicates a sensor drift fault and a shaft wear fault.

Because the gear tooth failure is an environmental condition rather than a detection target, it is not encoded in FaultCode.

Use the FaultCode variable to create two binary variables, one for detecting a sensor drift fault (SensorDrift) and one for detecting a shaft wear fault (ShaftWear). Then, remove the FaultCode variable from the faultData table.

n = size(faultData,1);
SensorDrift = zeros(n,1);
ShaftWear = zeros(n,1);
FaultCode = faultData.FaultCode;

for i = 1:n
    switch FaultCode(i)
        case 1
            SensorDrift(i) = 1;
        case 2
            ShaftWear(i) = 1;
        case 3
            SensorDrift(i) = 1;
            ShaftWear(i) = 1;
    end
end

faultData.FaultCode = [];

You can train a classifier to detect a fault type using the corresponding binary response variable. Separating fault detection into two binary classification problems has the following advantages:

  • Each classifier solves a simpler binary classification problem rather than a more complex multiclass problem, with enough training observations for both its positive and negative classes.

  • Each classifier is trained on only the features most relevant to its fault mode, which reduces dimensionality and improves interpretability.

Partition Data

Split the extracted features into training and test sets. Reserve 30% of the observations for testing, and use the remaining observations for training. Note that the training and test sets are stratified over the scenarios (faultData.ScenarioID). This stratified partitioning ensures that each set contains the same proportion of scenarios.

rng(1,"twister");
cv = cvpartition(faultData.ScenarioID,Holdout=0.3);
faultDataTrain = faultData(cv.training,:);
faultDataTest = faultData(cv.test,:);

Split the labels for sensor drift fault detection.

SensorDriftTrain = SensorDrift(cv.training);
SensorDriftTest = SensorDrift(cv.test);

Split the labels for shaft wear fault detection.

ShaftWearTrain = ShaftWear(cv.training);
ShaftWearTest = ShaftWear(cv.test);

After verifying that the data sets pass all data requirements, you can use the faultDataTrain features and the SensorDriftTrain labels to train a binary classifier for detecting sensor drift. Similarly, you can use the faultDataTrain features and the ShaftWearTrain labels to train a binary classifier for detecting shaft wear. For more information on this next step, see Perform Feature Selection and Model Training for Transmission System Fault Detection.

Verify Extracted Tabular Data Requirements

Verify that the training set faultDataTrain and the test set faultDataTest meet the extracted tabular data requirements described in Extracted Tabular Data Requirements. In particular, both data sets must contain sufficient observations for each simulation scenario (that is, at least 140 observations per scenario in the training set and at least 60 observations per scenario in the test set) and must contain exactly 20 extracted features. Use the helper function helperVerifyRequirements to summarize the results in a table.

verificationTable = helperVerifyRequirements(faultDataTrain,faultDataTest)
verificationTable = 18×5 table
              RequirementID                                       Description                            Value    Threshold    Result
    __________________________________    ___________________________________________________________    _____    _________    ______

    "TRAINING_COMPLETENESS_SCENARIO_1"    "Training observations from Scenario 1: Healthy"                280        140       "PASS"
    "TRAINING_COMPLETENESS_SCENARIO_2"    "Training observations from Scenario 2: Sensor Drift"           140        140       "PASS"
    "TRAINING_COMPLETENESS_SCENARIO_3"    "Training observations from Scenario 3: Shaft Wear"             140        140       "PASS"
    "TRAINING_COMPLETENESS_SCENARIO_4"    "Training observations from Scenario 4: SD + SW"                140        140       "PASS"
    "TRAINING_COMPLETENESS_SCENARIO_5"    "Training observations from Scenario 5: Gear Tooth Failure"     147        140       "PASS"
    "TRAINING_COMPLETENESS_SCENARIO_6"    "Training observations from Scenario 6: SD + GTF"               147        140       "PASS"
    "TRAINING_COMPLETENESS_SCENARIO_7"    "Training observations from Scenario 7: SW + GTF"               146        140       "PASS"
    "TRAINING_COMPLETENESS_SCENARIO_8"    "Training observations from Scenario 8: SD + SW + GTF"          147        140       "PASS"
    "TEST_COMPLETENESS_SCENARIO_1"        "Test observations from Scenario 1: Healthy"                    120         60       "PASS"
    "TEST_COMPLETENESS_SCENARIO_2"        "Test observations from Scenario 2: Sensor Drift"                60         60       "PASS"
    "TEST_COMPLETENESS_SCENARIO_3"        "Test observations from Scenario 3: Shaft Wear"                  60         60       "PASS"
    "TEST_COMPLETENESS_SCENARIO_4"        "Test observations from Scenario 4: SD + SW"                     60         60       "PASS"
    "TEST_COMPLETENESS_SCENARIO_5"        "Test observations from Scenario 5: Gear Tooth Failure"          63         60       "PASS"
    "TEST_COMPLETENESS_SCENARIO_6"        "Test observations from Scenario 6: SD + GTF"                    63         60       "PASS"
    "TEST_COMPLETENESS_SCENARIO_7"        "Test observations from Scenario 7: SW + GTF"                    63         60       "PASS"
    "TEST_COMPLETENESS_SCENARIO_8"        "Test observations from Scenario 8: SD + SW + GTF"               62         60       "PASS"

	Get insights using Copilot
      ⋮

The verification table shows that the training and test sets pass all the completeness and feature count data requirements.

Helper Functions

helperExtractFeatures

The helperExtractFeatures function derives 20 features from the vibration signal data (data), which contains the specified number of observations (n). The function returns the features as columns in a table (extractedTbl).

The function performs some computations in parallel when you have Parallel Computing Toolbox™.

function extractedTbl = helperExtractFeatures(data,n)

% Remove invalid rows
tf = cellfun(@(x) isempty(x),data.TachoPulses);
data(tf,:) = [];
n = n - sum(tf);
pulsesPerRev = 1; % Set tacho pulses per revolution (PPR) if not 1

% Create list of features to compute
varnames = ["SigMean","SigMedian","SigRMS","SigVar","SigPeak","SigPeak2Peak", ...
    "SigSkewness","SigKurtosis","SigCrestFactor","SigMAD","SigRangeCumSum", ...
    "SigApproxEntropy","SigLyapExponent","PeakFreq", "PeakSpecKurtosis", ...
    "RPMMedian","RPMStd","RPMIQR","ShaftHz", "FaultCode"];

% Create empty table
extractedTbl = table(Size=[0 numel(varnames)], ...
    VariableTypes=[repmat("double",1,numel(varnames)-1) "double"], ...
    VariableNames=varnames);

if ischar(data)
    % Return the names of the features being computed
    extractedTbl = varnames(:);
    return
end

parfor i = 1:n % Run computations in parallel

    % Extract the vibration signal for feature calculation
    Vibration = data.Vibration{i};

    % Interpolate the vibration signal in the periodic time base suitable
    % for fast Fourier transform (FFT) analysis
    np = 2^floor(log(height(Vibration))/log(2));
    dt = Vibration.Time(end)/(np-1);
    tv = 0:dt:Vibration.Time(end);
    y = retime(Vibration,tv,"linear");

    % Signal mean
    SigMean = mean(Vibration.Data);

    % Signal median
    SigMedian = median(Vibration.Data);

    % Signal root mean square (RMS)
    SigRMS = rms(Vibration.Data);

    % Signal variance
    SigVar = var(Vibration.Data);

    % Signal peak
    SigPeak = max(Vibration.Data);

    % Signal peak-to-peak
    SigPeak2Peak = peak2peak(Vibration.Data);

    % Signal skewness
    SigSkewness = skewness(Vibration.Data);

    % Signal kurtosis
    SigKurtosis = kurtosis(Vibration.Data);

    % Signal crest factor
    SigCrestFactor = peak2rms(Vibration.Data);

    % Signal median absolute deviation (MAD)
    SigMAD = mad(Vibration.Data);

    % Signal range of cumulative sum
    d = cumsum(Vibration.Data);
    SigRangeCumSum = max(d)-min(d);

    % Signal approximate entropy
    SigApproxEntropy = approximateEntropy(y.Data);

    % Signal Lyapunov exponent
    SigLyapExponent = lyapunovExponent(y.Data,1/seconds(dt));

    % RPM metrics and shaft frequency from tacho pulse times
    tp = seconds(data.TachoPulses{i}); % Pulse times within window (in seconds)
    [RPMMedian,RPMStd,RPMIQR,ShaftHz] = helperRPMFromPulseTimes(tp,pulsesPerRev);

    % Peak frequency
    % Compute the FFT of the time-synchronous average (TSA) of the vibration
    % signal
    dt = seconds(dt); % Sample period in seconds
    vibrationTSA = tsa(y,tp);
    np = numel(vibrationTSA.tsa);
    f = fft(vibrationTSA.tsa .* hamming(np))/np;
    % Compute the peak frequency
    frTSA = f(1:floor(np/2)+1);        
    wTSA = (0:np/2)/np*(2*pi/dt);
    mTSA = abs(frTSA);
    fHz = (0:np/2)/np*(1/dt); % Hz frequency vector for band segmentation
    [~,idx] = max(mTSA);
    PeakFreq = wTSA(idx);

    % Frequency with maximum spectral kurtosis
    [~,~,~,fc] = kurtogram(y.Data,1/dt,8);
    PeakSpecKurtosis = fc;

    % Save and return computed values
    sData = table(SigMean,SigMedian,SigRMS,SigVar,SigPeak, ...
        SigPeak2Peak,SigSkewness,SigKurtosis,SigCrestFactor, ...
        SigMAD,SigRangeCumSum,SigApproxEntropy,SigLyapExponent, ...
        PeakFreq,PeakSpecKurtosis,RPMMedian,RPMStd, ...
        RPMIQR,ShaftHz,data.FaultCode(i), ...
        VariableNames=varnames);
    extractedTbl = [extractedTbl; sData];        
end
end

helperRPMFromPulseTimes

The helperRPMFromPulseTimes function computes revolutions per minute (RPM) metrics based on the tacho pulse times within a specific window of time. In particular, the function returns the RPM median (rpmMed), standard deviation (rpmStd), and interquartile range (rpmIQR), as well as the shaft frequency (shaftHz).

function [rpmMed,rpmStd,rpmIQR,shaftHz] = helperRPMFromPulseTimes(tpSec,pulsesPerRev)

tpSec = tpSec(:);
if numel(tpSec) < 2
    rpmMed = NaN;
    rpmStd = NaN;
    rpmIQR = NaN;
    shaftHz = NaN;
    return;
end

dtPulse = diff(tpSec);
dtPulse(dtPulse<=0) = NaN;
rpmInst = 60./(dtPulse*pulsesPerRev);

rpmMed = median(rpmInst,"omitnan");
rpmStd = std(rpmInst,0,"omitnan");
rpmIQR = iqr(rpmInst);
shaftHz = rpmMed/60;

end

helperVerifyRequirements

The helperVerifyRequirements function determines whether the requirements described in Extracted Tabular Data Requirements are met, given the training set dataTrain and the test set dataTest. The function returns a table (verificationTable) with the results.

function verificationTable = helperVerifyRequirements(dataTrain,dataTest)

minTrainObservations = 140;
minTestObservations = 60;
expectedFeatures = 20;

scenarioNames = ["Scenario 1: Healthy","Scenario 2: Sensor Drift", ...
    "Scenario 3: Shaft Wear","Scenario 4: SD + SW", ...
    "Scenario 5: Gear Tooth Failure","Scenario 6: SD + GTF", ...
    "Scenario 7: SW + GTF","Scenario 8: SD + SW + GTF"];

RequirementID = strings(18,1);
Description = strings(18,1);
Value = zeros(18,1);
Threshold = zeros(18,1);
Result = strings(18,1);

% Training data completeness requirements (rows 1–8)
for k = 1:8
    count = nnz(dataTrain.ScenarioID == k);
    RequirementID(k) = sprintf("TRAINING_COMPLETENESS_SCENARIO_%d",k);
    Description(k) = "Training observations from " + scenarioNames(k);
    Value(k) = count;
    Threshold(k) = minTrainObservations;
    if count >= minTrainObservations
        Result(k) = "PASS";
    else
        Result(k) = "FAIL";
    end
end

% Test data completeness requirements (rows 9–16)
for k = 1:8
    count = nnz(dataTest.ScenarioID == k);
    RequirementID(8+k) = sprintf("TEST_COMPLETENESS_SCENARIO_%d",k);
    Description(8+k) = "Test observations from " + scenarioNames(k);
    Value(8+k) = count;
    Threshold(8+k) = minTestObservations;
    if count >= minTestObservations
        Result(8+k) = "PASS";
    else
        Result(8+k) = "FAIL";
    end
end

% Training data feature count requirement (row 17)
trainFeatureCount = width(dataTrain);
RequirementID(17) = "TRAINING_FEATURE_COUNT";
Description(17) = "Training set contains expected number of features";
Value(17) = trainFeatureCount;
Threshold(17) = expectedFeatures;
if trainFeatureCount == expectedFeatures
    Result(17) = "PASS";
else
    Result(17) = "FAIL";
end

% Test data feature count requirement (row 18)
testFeatureCount = width(dataTest);
RequirementID(18) = "TEST_FEATURE_COUNT";
Description(18) = "Test set contains expected number of features";
Value(18) = testFeatureCount;
Threshold(18) = expectedFeatures;
if testFeatureCount == expectedFeatures
    Result(18) = "PASS";
else
    Result(18) = "FAIL";
end

verificationTable = table(RequirementID,Description,Value,Threshold,Result);
end

See Also

Topics