Results of (non) parametric paired t-test
Closed this issue · 3 comments
Hello everyone,
I am currently trying to conduct a paired samples t-test on a set of data containing information on the movement of several joints over time.
My goal is to compare two different motion capture systems and identify time periods, where these show statistically significant differences.
The data I am trying to run the SPM operations on contains the "overall grand mean" of all motion capture data for the given joint degree of freedom, averaged across all participants and their respective trials.
I have time-normalized the data, however not to 101 samples as is shown in several explanatory YouTube videos and the example datasets as that lead to great alterations in the waveforms of both systems. Currently I am working with 320 data entries per movement for both systems I used.
My code currently looks like this:
# transpose overall_grand_mean dataframes so they work with SPM1D
# also convert them to np arrays
vic_spm_data = vic_overall_grand_mean.transpose()
vic_spm_data = vic_spm_data.to_numpy()
oc_spm_data = oc_overall_grand_mean.transpose()
oc_spm_data = oc_spm_data.to_numpy().astype(float)
# Rows of interest
rows_of_interest = [
'hip_flexion_r', 'hip_adduction_r', 'hip_rotation_r',
'hip_flexion_l', 'hip_adduction_l', 'hip_rotation_l',
'knee_angle_r', 'knee_adduction_r', 'knee_angle_l', 'knee_adduction_l',
'ankle_angle_r', 'ankle_angle_l',
#'subtalar_angle_r', 'subtalar_angle_l'
]
# Movement mapping
# Used to associate unnamed np array rows to joint movements/rows of interest
movement_mapping = {
'hip_flexion_r': 7,
'hip_adduction_r': 8,
'hip_rotation_r': 9,
'knee_angle_r': 10,
'knee_adduction_r': 11,
'ankle_angle_r': 13,
'hip_flexion_l': 16,
'hip_adduction_l': 17,
'hip_rotation_l': 18,
'knee_angle_l': 19,
'knee_adduction_l': 20,
'ankle_angle_l': 22,
}
# Initialize an empty dictionary to store SPM results
spm_results = {}
# Iterate over each row of interest
for row_name in rows_of_interest:
# Extract the relevant row from transposed NumPy arrays using the movement_mapping
vic_row = vic_spm_data[movement_mapping[row_name], :]
oc_row = oc_spm_data[movement_mapping[row_name], :]
print(f"Shape of {row_name} - VIC: {vic_row.shape}")
print(f"Shape of {row_name} - OC: {oc_row.shape}")
# Check normality with k2 test
spmi_norm = spm1d.stats.normality.ttest_paired(vic_row, oc_row).inference(alpha=0.05)
print(spmi_norm)
if spmi_norm.h0reject:
# Run the parametric dep ttest
spm = spm1d.stats.ttest_paired(vic_row, oc_row)
spmi = spm.inference(alpha=0.05, two_tailed=True)
print(spmi)
else:
# Run the nonparametric dep ttest
snpm = spm1d.stats.nonparam.ttest_paired(vic_row, oc_row)
snpmi = snpm.inference(alpha=0.05, two_tailed=True, iterations=500)
print(snpmi)
# Store the SPM result in the dictionary
spm_results[row_name] = spmi if spmi_norm.h0reject else snpmi
I first create the needed datastructure for SMP1D (horizontally laid out numpy array) and a list to associate the unnamed rows to the previous column headers from my pd dataframes (this increases readability for me as I am not too experienced in Python).
I then want to loop across my specified rows of interest. I first check for normality and then perform either a parametric or non-parametric paired ttest.
So far for my intentions with this code, if I need to elaborate on certain aspects more please let me know and I'll do my best to clarify.
When running this code this is the console output:
Shape of hip_flexion_r - VIC: (320,)
Shape of hip_flexion_r - OC: (320,)
SPM{X2} (0D) inference
SPM.z : 101.15444
SPM.df : (1, 2)
Inference:
SPM.alpha : 0.050
SPM.zstar : 5.99146
SPM.h0reject : True
SPM.p : 0.00000
SPM{T} (0D) inference
SPM.z : -7.18473
SPM.df : (1, 319)
Inference:
SPM.alpha : 0.050
SPM.zstar : 1.96743
SPM.h0reject : True
SPM.p : 0.00000
Shape of hip_adduction_r - VIC: (320,)
Shape of hip_adduction_r - OC: (320,)
SPM{X2} (0D) inference
SPM.z : 12.88766
SPM.df : (1, 2)
Inference:
SPM.alpha : 0.050
SPM.zstar : 5.99146
SPM.h0reject : True
SPM.p : 0.00159
SPM{T} (0D) inference
SPM.z : -107.40437
SPM.df : (1, 319)
Inference:
SPM.alpha : 0.050
SPM.zstar : 1.96743
SPM.h0reject : True
SPM.p : 0.00000
Shape of hip_rotation_r - VIC: (320,)
Shape of hip_rotation_r - OC: (320,)
SPM{X2} (0D) inference
SPM.z : 117.18431
SPM.df : (1, 2)
Inference:
SPM.alpha : 0.050
SPM.zstar : 5.99146
SPM.h0reject : True
SPM.p : 0.00000
SPM{T} (0D) inference
SPM.z : -21.26529
SPM.df : (1, 319)
Inference:
SPM.alpha : 0.050
SPM.zstar : 1.96743
SPM.h0reject : True
SPM.p : 0.00000
Shape of hip_flexion_l - VIC: (320,)
Shape of hip_flexion_l - OC: (320,)
SPM{X2} (0D) inference
SPM.z : 32.78803
SPM.df : (1, 2)
Inference:
SPM.alpha : 0.050
SPM.zstar : 5.99146
SPM.h0reject : True
SPM.p : 0.00000
SPM{T} (0D) inference
SPM.z : -10.67827
SPM.df : (1, 319)
Inference:
SPM.alpha : 0.050
SPM.zstar : 1.96743
SPM.h0reject : True
SPM.p : 0.00000
Shape of hip_adduction_l - VIC: (320,)
Shape of hip_adduction_l - OC: (320,)
SPM{X2} (0D) inference
SPM.z : 5.57507
SPM.df : (1, 2)
Inference:
SPM.alpha : 0.050
SPM.zstar : 5.99146
SPM.h0reject : False
SPM.p : 0.06157
SnPM{t} inference (0D)
SPM.z : -60.090
SnPM.nPermUnique : 2.136e+96 permutations possible
Inference:
SnPM.nPermActual : 500 actual permutations
SPM.alpha : 0.050
SPM.zstar (lower) : -2.35024
SPM.zstar (upper) : 2.20365
SPM.two_tailed : True
SPM.h0reject : True
SPM.p : 0.002
Shape of hip_rotation_l - VIC: (320,)
Shape of hip_rotation_l - OC: (320,)
SPM{X2} (0D) inference
SPM.z : 62.09990
SPM.df : (1, 2)
Inference:
SPM.alpha : 0.050
SPM.zstar : 5.99146
SPM.h0reject : True
SPM.p : 0.00000
SPM{T} (0D) inference
SPM.z : -46.79220
SPM.df : (1, 319)
Inference:
SPM.alpha : 0.050
SPM.zstar : 1.96743
SPM.h0reject : True
SPM.p : 0.00000
Shape of knee_angle_r - VIC: (320,)
Shape of knee_angle_r - OC: (320,)
SPM{X2} (0D) inference
SPM.z : 121.73188
SPM.df : (1, 2)
Inference:
SPM.alpha : 0.050
SPM.zstar : 5.99146
SPM.h0reject : True
SPM.p : 0.00000
SPM{T} (0D) inference
SPM.z : 0.05023
SPM.df : (1, 319)
Inference:
SPM.alpha : 0.050
SPM.zstar : 1.96743
SPM.h0reject : False
SPM.p : 0.95997
Shape of knee_adduction_r - VIC: (320,)
Shape of knee_adduction_r - OC: (320,)
SPM{X2} (0D) inference
SPM.z : 171.71350
SPM.df : (1, 2)
Inference:
SPM.alpha : 0.050
SPM.zstar : 5.99146
SPM.h0reject : True
SPM.p : 0.00000
SPM{T} (0D) inference
SPM.z : -9.70084
SPM.df : (1, 319)
Inference:
SPM.alpha : 0.050
SPM.zstar : 1.96743
SPM.h0reject : True
SPM.p : 0.00000
Shape of knee_angle_l - VIC: (320,)
Shape of knee_angle_l - OC: (320,)
SPM{X2} (0D) inference
SPM.z : 23.90636
SPM.df : (1, 2)
Inference:
SPM.alpha : 0.050
SPM.zstar : 5.99146
SPM.h0reject : True
SPM.p : 0.00001
SPM{T} (0D) inference
SPM.z : -4.93954
SPM.df : (1, 319)
Inference:
SPM.alpha : 0.050
SPM.zstar : 1.96743
SPM.h0reject : True
SPM.p : 0.00000
Shape of knee_adduction_l - VIC: (320,)
Shape of knee_adduction_l - OC: (320,)
SPM{X2} (0D) inference
SPM.z : 93.77705
SPM.df : (1, 2)
Inference:
SPM.alpha : 0.050
SPM.zstar : 5.99146
SPM.h0reject : True
SPM.p : 0.00000
SPM{T} (0D) inference
SPM.z : -12.57172
SPM.df : (1, 319)
Inference:
SPM.alpha : 0.050
SPM.zstar : 1.96743
SPM.h0reject : True
SPM.p : 0.00000
Shape of ankle_angle_r - VIC: (320,)
Shape of ankle_angle_r - OC: (320,)
SPM{X2} (0D) inference
SPM.z : 2.93254
SPM.df : (1, 2)
Inference:
SPM.alpha : 0.050
SPM.zstar : 5.99146
SPM.h0reject : False
SPM.p : 0.23078
SnPM{t} inference (0D)
SPM.z : 16.456
SnPM.nPermUnique : 2.136e+96 permutations possible
Inference:
SnPM.nPermActual : 500 actual permutations
SPM.alpha : 0.050
SPM.zstar (lower) : -2.33346
SPM.zstar (upper) : 2.21557
SPM.two_tailed : True
SPM.h0reject : True
SPM.p : 0.002
Shape of ankle_angle_l - VIC: (320,)
Shape of ankle_angle_l - OC: (320,)
SPM{X2} (0D) inference
SPM.z : 221.94342
SPM.df : (1, 2)
Inference:
SPM.alpha : 0.050
SPM.zstar : 5.99146
SPM.h0reject : True
SPM.p : 0.00000
SPM{T} (0D) inference
SPM.z : 17.30938
SPM.df : (1, 319)
Inference:
SPM.alpha : 0.050
SPM.zstar : 1.96743
SPM.h0reject : True
SPM.p : 0.00000
When inspecting the spm_results dictionary, the accessible data looks much different than that from the examples provided (please see attached screenshot).
My main concern is that the .plot entry is missing which is why I cannot visualize my results.
Is there an issue in my code which leads to this and if so: What can I do to correct it? Are there other concerns to my approach?
As you can probably tell, I am fairly new to posting such questions on GitHub. I hope my issue is not beyond the scope of this forum.
Thank you in advance for any support and have a happy new year.
If the plot
method is missing it means that spm1d has interpreted the data as 0D and not as a set of 1D time series observations. You'll see this in the SPM object print-outs, for example:
SPM{T} (0D) inference
SPM.z : -12.57172
SPM.df : (1, 319)
Inference:
SPM.alpha : 0.050
SPM.zstar : 1.96743
SPM.h0reject : True
SPM.p : 0.00000
Notice the "0D
" in the header.
These data are 0D because they are 1D arrays (i.e., several observations of 0D values). In order to run 1D analysis you need 2D arrays (i.e., several observations of 1D values). The required array shapes are:
(J,)
for 0D analysis(J,Q)
for 1D analysis
where:
J
= number of obervationsQ
= number of 1D domain nodes (e.g. number of time nodes)
Hi Todd,
thank you for your reply.
If I understand correctly, this would mean not to use the overall grand mean data (calculated across all trials and participants for both systems individually over time), but rather to collect the individual trial data on the given joint DOF from both systems and feed this data into the SPM operation.
Is that the correct approach in order to get a 1D SPM object which has the plot method?
Thank you for your support.
Best
Bernhard
I am unsure whether using or not using the grand mean is relevant because it is a bit unclear what it is and how it is used. If the grand mean is a scalar or a 1D trajectory then its use should not matter. Regardless, if you submit 2D arrays to spm1d's univariate procedures (like t tests) then the data will be interpreted as 1D and 1D analysis will be conducted. I suggest simplifying the code you attached to conduct just a single test (on hip_flexion_r
, for example). If you can create 2D arrays for this test and thereby conduct 1D analysis then it should become straightforward how to analyze other cases.