# Install vjsim if you haven't already by running the following commands # install.packages("devtools") # devtools::install_github("mladenjovanovic/vjsim") # Install bmbstats if you haven't already by running the following commands # devtools::install_github("mladenjovanovic/bmbstats") # Install tidyverse and cowplot packages # install.packages(c("tidyverse", "cowplot", "DT", "ggdendro", "ClustOfVar", "vip", "pdp"), dependencies = TRUE) library(vjsim) library(tidyverse) library(cowplot) library(DT) library(ggdendro) library(ClustOfVar) library(vip) library(pdp) library(bmbstats)
Before reading this vignette, please read Introduction to vjsim, Simulation in vjsim, Profiling in vjsim, and Optimization in vjsim vignettes by running:
vignette("introduction-vjsim") vignette("simulation-vjsim") vignette("profiling-vjsim") vignette("optimization-vjsim")
To explore various assumptions and models, I have generated a large simulation dataset (source code is available in data-raw/
folder of the vjsim
package or through GitHub) which features various combinations of Force Generator characteristics and thus allows to explore various assumptions. Here is the sample (100 rows) of the data set:
data("vjsim_data") datatable(vjsim_data[sample(nrow(vjsim_data), 100), ], rownames = FALSE) %>% formatRound(columns = 1:ncol(vjsim_data), digits = 2)
Full data set features 8640 rows and 194 columns (or variables) that are grouped in the following sections:
force_generator.
; first 7 columns are Force Generator characteristics). This also included Samozino’s optimization models for the Force Generator.bodyweight_jump.
)probe_bodyweight_jump.
), which includes effects on bodyweight jump metrics when max force, max velocity, or time to max activation characteristics of the Force Generator change by 10%. These column are very important for testing predictions of optimization models.bosco.
) indicated the jump height with 2xBW and the ratio with BW jumpprofile_
). See Profiling in vjsim vignette for more info.LPF_profile.
). See Optimization in vjsim vignette for more info.samozino_
). See Optimization in vjsim vignette for more info.simple_profile.
). See Optimization in vjsim vignette for more info.Here is the list of all the columns in this data set:
colnames(vjsim_data) #> [1] "force_generator.bodyweight" #> [2] "force_generator.push_off_distance" #> [3] "force_generator.max_force" #> [4] "force_generator.max_velocity" #> [5] "force_generator.decline_rate" #> [6] "force_generator.peak_location" #> [7] "force_generator.time_to_max_activation" #> [8] "force_generator.F0" #> [9] "force_generator.F0_rel" #> [10] "force_generator.V0" #> [11] "force_generator.Pmax" #> [12] "force_generator.Pmax_rel" #> [13] "force_generator.Sfv" #> [14] "force_generator.Sfv_rel" #> [15] "force_generator.take_off_velocity" #> [16] "force_generator.height" #> [17] "force_generator.optimal_F0" #> [18] "force_generator.optimal_F0_rel" #> [19] "force_generator.optimal_V0" #> [20] "force_generator.optimal_height" #> [21] "force_generator.optimal_height_diff" #> [22] "force_generator.optimal_height_ratio" #> [23] "force_generator.optimal_Pmax" #> [24] "force_generator.optimal_Pmax_rel" #> [25] "force_generator.optimal_take_off_velocity" #> [26] "force_generator.optimal_take_off_velocity_diff" #> [27] "force_generator.optimal_take_off_velocity_ratio" #> [28] "force_generator.optimal_Sfv" #> [29] "force_generator.optimal_Sfv_rel" #> [30] "force_generator.Sfv_perc" #> [31] "force_generator.FV_imbalance" #> [32] "force_generator.probe_IMB" #> [33] "bodyweight_jump.take_off_time" #> [34] "bodyweight_jump.take_off_velocity" #> [35] "bodyweight_jump.height" #> [36] "bodyweight_jump.mean_GRF_over_distance" #> [37] "bodyweight_jump.mean_GRF_over_time" #> [38] "bodyweight_jump.mean_velocity" #> [39] "bodyweight_jump.work_done" #> [40] "bodyweight_jump.impulse" #> [41] "bodyweight_jump.mean_power" #> [42] "bodyweight_jump.mean_RFD" #> [43] "bodyweight_jump.mean_RPD" #> [44] "bodyweight_jump.peak_GRF" #> [45] "bodyweight_jump.peak_GRF_time" #> [46] "bodyweight_jump.peak_GRF_distance" #> [47] "bodyweight_jump.peak_velocity" #> [48] "bodyweight_jump.peak_velocity_time" #> [49] "bodyweight_jump.peak_velocity_distance" #> [50] "bodyweight_jump.peak_power" #> [51] "bodyweight_jump.peak_power_distance" #> [52] "bodyweight_jump.peak_power_time" #> [53] "bodyweight_jump.peak_RFD" #> [54] "bodyweight_jump.peak_RFD_distance" #> [55] "bodyweight_jump.peak_RFD_time" #> [56] "bodyweight_jump.GRF_at_100ms" #> [57] "bodyweight_jump.GRF_at_200ms" #> [58] "bodyweight_jump.peak_RPD" #> [59] "bodyweight_jump.peak_RPD_distance" #> [60] "bodyweight_jump.peak_RPD_time" #> [61] "bodyweight_jump.diff_peak_to_take_off_velocity" #> [62] "bodyweight_jump.ratio_peak_to_take_off_velocity" #> [63] "bodyweight_jump.diff_mean_to_take_off_velocity" #> [64] "bodyweight_jump.ratio_mean_to_take_off_velocity" #> [65] "bodyweight_jump.diff_mean_to_peak_velocity" #> [66] "bodyweight_jump.ratio_mean_to_peak_velocity" #> [67] "bodyweight_jump.diff_mean_to_peak_power" #> [68] "bodyweight_jump.ratio_mean_to_peak_power" #> [69] "bodyweight_jump.diff_mean_GRF_over_distance_to_time" #> [70] "bodyweight_jump.ratio_mean_GRF_over_distance_to_time" #> [71] "bodyweight_jump.mean_velocity_as_TOV_half" #> [72] "probe_bodyweight_jump.force_height" #> [73] "probe_bodyweight_jump.force_height_diff" #> [74] "probe_bodyweight_jump.force_height_ratio" #> [75] "probe_bodyweight_jump.velocity_height" #> [76] "probe_bodyweight_jump.velocity_height_diff" #> [77] "probe_bodyweight_jump.velocity_height_ratio" #> [78] "probe_bodyweight_jump.activation_height" #> [79] "probe_bodyweight_jump.activation_height_diff" #> [80] "probe_bodyweight_jump.activation_height_ratio" #> [81] "probe_bodyweight_jump.velocity_to_force_height_diff" #> [82] "probe_bodyweight_jump.velocity_to_force_height_ratio" #> [83] "probe_bodyweight_jump.velocity_to_activation_height_diff" #> [84] "probe_bodyweight_jump.velocity_to_activation_height_ratio" #> [85] "probe_bodyweight_jump.force_to_velocity_height_diff" #> [86] "probe_bodyweight_jump.force_to_velocity_height_ratio" #> [87] "probe_bodyweight_jump.force_to_activation_height_diff" #> [88] "probe_bodyweight_jump.force_to_activation_height_ratio" #> [89] "probe_bodyweight_jump.activation_to_velocity_height_diff" #> [90] "probe_bodyweight_jump.activation_to_velocity_height_ratio" #> [91] "probe_bodyweight_jump.activation_to_force_height_diff" #> [92] "probe_bodyweight_jump.activation_to_force_height_ratio" #> [93] "bosco.height_2BW" #> [94] "bosco.index" #> [95] "profile_mean_FV.F0" #> [96] "profile_mean_FV.F0_rel" #> [97] "profile_mean_FV.V0" #> [98] "profile_mean_FV.Pmax" #> [99] "profile_mean_FV.Pmax_rel" #> [100] "profile_mean_FV.Sfv" #> [101] "profile_mean_FV.Sfv_rel" #> [102] "profile_mean_power.Pmax" #> [103] "profile_mean_power.Pmax_rel" #> [104] "profile_mean_power.Pmax_location" #> [105] "profile_mean_power.F0_perc" #> [106] "profile_peak_FV.F0" #> [107] "profile_peak_FV.F0_rel" #> [108] "profile_peak_FV.V0" #> [109] "profile_peak_FV.Pmax" #> [110] "profile_peak_FV.Pmax_rel" #> [111] "profile_peak_FV.Sfv" #> [112] "profile_peak_FV.Sfv_rel" #> [113] "profile_peak_power.Pmax" #> [114] "profile_peak_power.Pmax_rel" #> [115] "profile_peak_power.Pmax_location" #> [116] "profile_peak_power.F0_perc" #> [117] "profile_load_take_off_velocity.V0" #> [118] "profile_load_take_off_velocity.L0" #> [119] "profile_load_take_off_velocity.L0_rel" #> [120] "profile_load_take_off_velocity.Imax" #> [121] "profile_load_take_off_velocity.Imax_rel" #> [122] "profile_load_take_off_velocity.Slv" #> [123] "profile_load_take_off_velocity.Slv_rel" #> [124] "profile_load_impulse.Imax" #> [125] "profile_load_impulse.Imax_rel" #> [126] "profile_load_impulse.Imax_location" #> [127] "profile_load_impulse.L0_perc" #> [128] "LPF_profile.slope" #> [129] "LPF_profile.slope_rel" #> [130] "samozino_theoretical_profile.F0" #> [131] "samozino_theoretical_profile.F0_rel" #> [132] "samozino_theoretical_profile.V0" #> [133] "samozino_theoretical_profile.Pmax" #> [134] "samozino_theoretical_profile.Pmax_rel" #> [135] "samozino_theoretical_profile.Sfv" #> [136] "samozino_theoretical_profile.Sfv_rel" #> [137] "samozino_theoretical_profile.take_off_velocity" #> [138] "samozino_theoretical_profile.height" #> [139] "samozino_theoretical_profile.optimal_F0" #> [140] "samozino_theoretical_profile.optimal_F0_rel" #> [141] "samozino_theoretical_profile.optimal_V0" #> [142] "samozino_theoretical_profile.optimal_height" #> [143] "samozino_theoretical_profile.optimal_height_diff" #> [144] "samozino_theoretical_profile.optimal_height_ratio" #> [145] "samozino_theoretical_profile.optimal_Pmax" #> [146] "samozino_theoretical_profile.optimal_Pmax_rel" #> [147] "samozino_theoretical_profile.optimal_take_off_velocity" #> [148] "samozino_theoretical_profile.optimal_take_off_velocity_diff" #> [149] "samozino_theoretical_profile.optimal_take_off_velocity_ratio" #> [150] "samozino_theoretical_profile.optimal_Sfv" #> [151] "samozino_theoretical_profile.optimal_Sfv_rel" #> [152] "samozino_theoretical_profile.Sfv_perc" #> [153] "samozino_theoretical_profile.FV_imbalance" #> [154] "samozino_theoretical_profile.probe_IMB" #> [155] "samozino_practical_profile.F0" #> [156] "samozino_practical_profile.F0_rel" #> [157] "samozino_practical_profile.V0" #> [158] "samozino_practical_profile.Pmax" #> [159] "samozino_practical_profile.Pmax_rel" #> [160] "samozino_practical_profile.Sfv" #> [161] "samozino_practical_profile.Sfv_rel" #> [162] "samozino_practical_profile.take_off_velocity" #> [163] "samozino_practical_profile.height" #> [164] "samozino_practical_profile.optimal_F0" #> [165] "samozino_practical_profile.optimal_F0_rel" #> [166] "samozino_practical_profile.optimal_V0" #> [167] "samozino_practical_profile.optimal_height" #> [168] "samozino_practical_profile.optimal_height_diff" #> [169] "samozino_practical_profile.optimal_height_ratio" #> [170] "samozino_practical_profile.optimal_Pmax" #> [171] "samozino_practical_profile.optimal_Pmax_rel" #> [172] "samozino_practical_profile.optimal_take_off_velocity" #> [173] "samozino_practical_profile.optimal_take_off_velocity_diff" #> [174] "samozino_practical_profile.optimal_take_off_velocity_ratio" #> [175] "samozino_practical_profile.optimal_Sfv" #> [176] "samozino_practical_profile.optimal_Sfv_rel" #> [177] "samozino_practical_profile.Sfv_perc" #> [178] "samozino_practical_profile.FV_imbalance" #> [179] "samozino_practical_profile.probe_IMB" #> [180] "simple_profile.L0" #> [181] "simple_profile.TOV0" #> [182] "simple_profile.Sfv" #> [183] "simple_profile.Sfv_rel" #> [184] "simple_profile.take_off_velocity" #> [185] "simple_profile.height" #> [186] "simple_profile.optimal_L0" #> [187] "simple_profile.optimal_TOV0" #> [188] "simple_profile.optimal_Sfv" #> [189] "simple_profile.optimal_Sfv_rel" #> [190] "simple_profile.optimal_take_off_velocity" #> [191] "simple_profile.optimal_height" #> [192] "simple_profile.Sfv_perc" #> [193] "simple_profile.FV_imbalance" #> [194] "simple_profile.probe_IMB"
This data set can be used for multiple things: from exploring simple relationships between variables, to performing variable clustering and factor analysis as well for informing future studies. I will provide few ideas to get you started and finish with my summary opinion on the Samozino optimization models (Samozino et al. 2008, 2010, 2012, 2013; Pierre Samozino 2018b, 2018a, 2018a) and the most recent intervention studies (Jiménez-Reyes et al. 2017; Jiménez-Reyes, Samozino, and Morin 2019).
This section will provide simple scatter-plots and linear regression models. Before we start, let’s create a plotting and analysis function. This function will return ggplot
object and \(R^2\) using simple linear model.
plot_relationship <- function(x_var, y_var, data = vjsim_data, identity = FALSE) { df <- data.frame( x_var = data[[x_var]], y_var = data[[y_var]] ) gg <- ggplot(df, aes(x = x_var, y = y_var)) + theme_cowplot(8) + geom_point(alpha = 0.1) + stat_smooth(color = "blue", method = "lm", formula = y ~ x) + labs(x = x_var, y = y_var) if (identity == TRUE) { gg <- gg + geom_abline(slope = 1, linetype = "dashed") } # Linear model R_squared <- cor(df$x_var, df$y_var)^2 return(list(graph = gg, R_squared = R_squared)) }
Samozino model assumes that take-off velocity (TOV) is double mean velocity (MV) (it is simplification that allows for easier calculus from practical field data collection). But is that the case? Here is the :
mv_tov <- plot_relationship( "bodyweight_jump.mean_velocity", "bodyweight_jump.take_off_velocity" ) mv_tov$graph + geom_abline(slope = 2, linetype = "dashed")
mv_tov$R_squared #> [1] 0.500241
Another assumption is that mean ground reaction force (GRF) over distance is equal to mean GRF over time (which is the case with uniform acceleration jump, when TOV = 2x MV). Let’s check that assumption:
plot_relationship( "bodyweight_jump.mean_GRF_over_distance", "bodyweight_jump.mean_GRF_over_time", identity = TRUE ) #> $graph
#>
#> $R_squared
#> [1] 0.9167995
As can be seen from the figure above, there is a difference between the two and they should not be used interchangeably.
Let’s compare the true bodyweight jump height with predicted jump height with two Samozino models (theoretical and practical):
plot_relationship( "bodyweight_jump.height", "samozino_theoretical_profile.height", identity = TRUE ) #> $graph
#>
#> $R_squared
#> [1] 0.7808933
As can be seen, theoretical model (the one that uses true mean velocity) is not very good at predicting bodyweight jump height. Practical Samozino (or the original one), one the other hand is very good:
plot_relationship( "bodyweight_jump.height", "samozino_practical_profile.height", identity = TRUE ) #> $graph
#>
#> $R_squared
#> [1] 0.9999803
It is thus my recommendation to Samozino et al. to avoid using mean velocity in the profile, and rather stick to TOV instead. This way the unnecessary assumptions doesn’t need to be applied and optimization procedure becomes less confusing.
One of my main issues with Samozino model, is the realist ontological perspective on the constructs (\(F_0\), \(V_0\), and \(P_{max}\)) (Borsboom 2008, 2009; Borsboom, Mellenbergh, and van Heerden 2003). In plain English, the assumption is that constructs estimated using manifested performance represent true characteristics of the lower body (i.e., Force Generator). But, we have already seen that these constructs depends on the push-off distance and other Force Generator characteristics simulated in vjsim
(e.g., time to max activation, force-length relationship). In real-life, these manifestations depend on the activity as well (e.g., jumping, sprinting). Let’s explore those relationships a bit further.
Let’s see how true max force of the Force Generator is related to estimated \(F_0\)s.
plot_relationship( "force_generator.max_force", "profile_mean_FV.F0", identity = TRUE ) #> $graph
#>
#> $R_squared
#> [1] 0.6790944
plot_relationship( "force_generator.max_force", "profile_peak_FV.F0", identity = TRUE ) #> $graph
#>
#> $R_squared
#> [1] 0.7941104
plot_relationship( "force_generator.max_force", "samozino_practical_profile.F0", identity = TRUE ) #> $graph
#>
#> $R_squared
#> [1] 0.9406742
Interestingly, practical Samozino profile has really good relationship between true max force of the Force Generator (although there is some systematic bias). Let’s write a multiple linear regression function, than create a regression model from multiple predictors (with or without interaction). Besides, this functions allows for variable importance estimation (Greenwell, Boehmke, and McCarthy 2018; Molnar 2018) as well as PDP and ICE plots (Goldstein et al. 2013; Molnar 2018; Greenwell 2017; Altmann et al. 2019). This function defines smallest effect size of interest (SESOI) using target variable \(SD\) times 0.2. This provides an insight into how practically good the predictions are. The plots are made using bmbstats::plot_pair_BA
(Jovanović 2020b, 2019, 2020a).
regression_model <- function(data, target, predictors, interactions = FALSE, var_imp = "firm") { model_df <- select_(data, .dots = c( target, predictors )) interactions_string <- "~." if (interactions) interactions_string <- "~.*." model <- lm(as.formula(paste(target, interactions_string)), data = model_df) # Plot SESOI_upper <- sd(model_df[[target]]) * 0.2 SESOI_lower <- -sd(model_df[[target]]) * 0.2 predicted_target_val <- predict(model) observed_target_val <- model_df[[target]] gg <- bmbstats::plot_pair_BA( predictor = observed_target_val, outcome = predicted_target_val, predictor_label = "Predicted", outcome_label = "Observed", SESOI_lower = SESOI_lower, SESOI_upper = SESOI_upper ) # Variable importance pfun <- function(object, newdata) predict(object, newdata = newdata) variable_importance <- vip( model, train = model_df, method = var_imp, target = target, nsim = 10, metric = "rmse", pred_wrapper = pfun, all_permutations = TRUE ) + theme_cowplot(8) # Return object return(list( train = model_df, model = model, summary = summary(model), graph = gg, var_imp = variable_importance )) } # -------------------------------------------- # PDP_ICE plot plot_pdp <- function(object, predictor = 2) { # PDP + ICE partial( object$model, train = object$train, pred.var = predictor, plot = TRUE, rug = FALSE, ice = TRUE, plot.engine = "ggplot2", alpha = 0.01, ) + theme_cowplot(8) + ylab(paste("Predicted", colnames(object$train)[1])) }
Let’s check if we can predict (ideally we want to predict on unseen data, but we will leave that out (Jovanović 2020b, 2019)) max force from estimated \(F_0\), bodyweight and push-off distance:
max_force_model <- regression_model( data = vjsim_data, target = "force_generator.max_force", predictors = c( "force_generator.bodyweight", "force_generator.push_off_distance", "samozino_practical_profile.F0" ), interactions = TRUE, var_imp = "firm" ) max_force_model$summary #> #> Call: #> lm(formula = as.formula(paste(target, interactions_string)), #> data = model_df) #> #> Residuals: #> Min 1Q Median 3Q Max #> -485.60 -94.40 -12.67 68.75 612.16 #> #> Coefficients: #> Estimate #> (Intercept) 2.364e-10 #> force_generator.bodyweight 1.693e+01 #> force_generator.push_off_distance -3.449e-10 #> samozino_practical_profile.F0 6.328e-01 #> force_generator.bodyweight:force_generator.push_off_distance -1.510e+01 #> force_generator.bodyweight:samozino_practical_profile.F0 4.245e-16 #> force_generator.push_off_distance:samozino_practical_profile.F0 4.210e-01 #> Std. Error #> (Intercept) 5.070e+01 #> force_generator.bodyweight 9.549e-01 #> force_generator.push_off_distance 9.564e+01 #> samozino_practical_profile.F0 2.082e-02 #> force_generator.bodyweight:force_generator.push_off_distance 2.084e+00 #> force_generator.bodyweight:samozino_practical_profile.F0 1.711e-04 #> force_generator.push_off_distance:samozino_practical_profile.F0 4.049e-02 #> t value #> (Intercept) 0.000 #> force_generator.bodyweight 17.728 #> force_generator.push_off_distance 0.000 #> samozino_practical_profile.F0 30.388 #> force_generator.bodyweight:force_generator.push_off_distance -7.245 #> force_generator.bodyweight:samozino_practical_profile.F0 0.000 #> force_generator.push_off_distance:samozino_practical_profile.F0 10.397 #> Pr(>|t|) #> (Intercept) 1 #> force_generator.bodyweight < 2e-16 *** #> force_generator.push_off_distance 1 #> samozino_practical_profile.F0 < 2e-16 *** #> force_generator.bodyweight:force_generator.push_off_distance 4.68e-13 *** #> force_generator.bodyweight:samozino_practical_profile.F0 1 #> force_generator.push_off_distance:samozino_practical_profile.F0 < 2e-16 *** #> --- #> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 #> #> Residual standard error: 143.7 on 8633 degrees of freedom #> Multiple R-squared: 0.9604, Adjusted R-squared: 0.9603 #> F-statistic: 3.486e+04 on 6 and 8633 DF, p-value: < 2.2e-16 max_force_model$graph
max_force_model$var_imp
plot_pdp(max_force_model, "samozino_practical_profile.F0")
plot_pdp(max_force_model, "force_generator.bodyweight")
plot_pdp(max_force_model, "force_generator.push_off_distance")
Although high \(R^2\), using SESOI, this model is not great for predicting max force. But it is indeed highly correlated. From the variable importance graph, it can be seen that samozino_practical_profile.F0
is the most important variable, followed by bodyweight and push-off distance. This can be seen from the PDP+ICE plots as well (these are used to estimate the variable importance using algorithm explained in Greenwell, Boehmke, and McCarthy (2018)).
Let’s see the situation with velocity:
plot_relationship( "force_generator.max_velocity", "profile_mean_FV.V0", identity = TRUE ) #> $graph
#>
#> $R_squared
#> [1] 0.461428
plot_relationship( "force_generator.max_velocity", "profile_peak_FV.V0", identity = TRUE ) #> $graph
#>
#> $R_squared
#> [1] 0.5961357
plot_relationship( "force_generator.max_velocity", "samozino_practical_profile.V0", identity = TRUE ) #> $graph
#>
#> $R_squared
#> [1] 0.9587938
Although there is high \(R^2\), there is big systematic bias involved. Let’s see if we can predict max velocity from samozino_practical_profile.V0
, bodyweight and push-off distance:
max_velocity_model <- regression_model( data = vjsim_data, target = "force_generator.max_velocity", predictors = c( "force_generator.bodyweight", "force_generator.push_off_distance", "samozino_practical_profile.V0" ), interactions = TRUE, var_imp = "firm" ) max_velocity_model$summary #> #> Call: #> lm(formula = as.formula(paste(target, interactions_string)), #> data = model_df) #> #> Residuals: #> Min 1Q Median 3Q Max #> -1.09390 -0.15821 -0.04927 0.07373 2.01467 #> #> Coefficients: #> Estimate #> (Intercept) 3.907e-01 #> force_generator.bodyweight -1.410e-16 #> force_generator.push_off_distance 2.921e-01 #> samozino_practical_profile.V0 1.589e+00 #> force_generator.bodyweight:force_generator.push_off_distance 3.971e-16 #> force_generator.bodyweight:samozino_practical_profile.V0 1.562e-17 #> force_generator.push_off_distance:samozino_practical_profile.V0 -3.177e-01 #> Std. Error #> (Intercept) 1.109e-01 #> force_generator.bodyweight 1.428e-03 #> force_generator.push_off_distance 2.521e-01 #> samozino_practical_profile.V0 2.283e-02 #> force_generator.bodyweight:force_generator.push_off_distance 3.191e-03 #> force_generator.bodyweight:samozino_practical_profile.V0 2.273e-04 #> force_generator.push_off_distance:samozino_practical_profile.V0 3.960e-02 #> t value #> (Intercept) 3.523 #> force_generator.bodyweight 0.000 #> force_generator.push_off_distance 1.159 #> samozino_practical_profile.V0 69.592 #> force_generator.bodyweight:force_generator.push_off_distance 0.000 #> force_generator.bodyweight:samozino_practical_profile.V0 0.000 #> force_generator.push_off_distance:samozino_practical_profile.V0 -8.023 #> Pr(>|t|) #> (Intercept) 0.000428 *** #> force_generator.bodyweight 1.000000 #> force_generator.push_off_distance 0.246662 #> samozino_practical_profile.V0 < 2e-16 *** #> force_generator.bodyweight:force_generator.push_off_distance 1.000000 #> force_generator.bodyweight:samozino_practical_profile.V0 1.000000 #> force_generator.push_off_distance:samozino_practical_profile.V0 1.17e-15 *** #> --- #> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 #> #> Residual standard error: 0.3424 on 8633 degrees of freedom #> Multiple R-squared: 0.9598, Adjusted R-squared: 0.9598 #> F-statistic: 3.439e+04 on 6 and 8633 DF, p-value: < 2.2e-16 max_velocity_model$graph
max_velocity_model$var_imp
plot_pdp(max_velocity_model, "samozino_practical_profile.V0")
plot_pdp(max_velocity_model, "force_generator.bodyweight")
plot_pdp(max_velocity_model, "force_generator.push_off_distance")
As opposed to max force, push-off distance is now more important than bodyweight in estimating max velocity. Although highly correlated, profile \(V_0\) is not a great predictor of true simulator max velocity (since residuals are outside of SESOI range).
The final exploration involve max power (\(P_{max}\)). Let’s check the simple scatter-plots first:
plot_relationship( "force_generator.Pmax", "profile_mean_FV.Pmax", identity = TRUE ) #> $graph
#>
#> $R_squared
#> [1] 0.81796
plot_relationship( "force_generator.Pmax", "profile_mean_power.Pmax", identity = TRUE ) #> $graph
#>
#> $R_squared
#> [1] 0.8188265
plot_relationship( "force_generator.Pmax", "profile_peak_FV.Pmax", identity = TRUE ) #> $graph
#>
#> $R_squared
#> [1] 0.7464624
plot_relationship( "force_generator.Pmax", "profile_peak_power.Pmax", identity = TRUE ) #> $graph
#>
#> $R_squared
#> [1] 0.02388299
plot_relationship( "force_generator.Pmax", "samozino_practical_profile.Pmax", identity = TRUE ) #> $graph
#>
#> $R_squared
#> [1] 0.9795487
Again the Samozino practical profile demonstrates high correlation with true \(P_{max}\), but with some systematic bias. Let’s use multivariate linear model to predict \(P_{max}\) from samozino_practical_profile.Pmax
, bodyweight and known push-off distance:
max_power_model <- regression_model( data = vjsim_data, target = "force_generator.Pmax", predictors = c( "force_generator.bodyweight", "force_generator.push_off_distance", "samozino_practical_profile.Pmax" ), interactions = TRUE, var_imp = "firm" ) max_power_model$summary #> #> Call: #> lm(formula = as.formula(paste(target, interactions_string)), #> data = model_df) #> #> Residuals: #> Min 1Q Median 3Q Max #> -738.06 -108.83 -16.45 91.63 1621.34 #> #> Coefficients: #> Estimate #> (Intercept) 7.875e-11 #> force_generator.bodyweight 2.497e+00 #> force_generator.push_off_distance -8.892e-11 #> samozino_practical_profile.Pmax 1.737e+00 #> force_generator.bodyweight:force_generator.push_off_distance 6.772e+00 #> force_generator.bodyweight:samozino_practical_profile.Pmax 1.723e-16 #> force_generator.push_off_distance:samozino_practical_profile.Pmax -4.083e-01 #> Std. Error #> (Intercept) 6.004e+01 #> force_generator.bodyweight 9.082e-01 #> force_generator.push_off_distance 1.370e+02 #> samozino_practical_profile.Pmax 1.874e-02 #> force_generator.bodyweight:force_generator.push_off_distance 2.097e+00 #> force_generator.bodyweight:samozino_practical_profile.Pmax 1.791e-04 #> force_generator.push_off_distance:samozino_practical_profile.Pmax 3.146e-02 #> t value #> (Intercept) 0.000 #> force_generator.bodyweight 2.749 #> force_generator.push_off_distance 0.000 #> samozino_practical_profile.Pmax 92.711 #> force_generator.bodyweight:force_generator.push_off_distance 3.229 #> force_generator.bodyweight:samozino_practical_profile.Pmax 0.000 #> force_generator.push_off_distance:samozino_practical_profile.Pmax -12.978 #> Pr(>|t|) #> (Intercept) 1.00000 #> force_generator.bodyweight 0.00599 ** #> force_generator.push_off_distance 1.00000 #> samozino_practical_profile.Pmax < 2e-16 *** #> force_generator.bodyweight:force_generator.push_off_distance 0.00125 ** #> force_generator.bodyweight:samozino_practical_profile.Pmax 1.00000 #> force_generator.push_off_distance:samozino_practical_profile.Pmax < 2e-16 *** #> --- #> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 #> #> Residual standard error: 205.9 on 8633 degrees of freedom #> Multiple R-squared: 0.9821, Adjusted R-squared: 0.9821 #> F-statistic: 7.9e+04 on 6 and 8633 DF, p-value: < 2.2e-16 max_power_model$graph
max_power_model$var_imp
plot_pdp(max_power_model, "samozino_practical_profile.Pmax")
plot_pdp(max_power_model, "force_generator.bodyweight")
plot_pdp(max_power_model, "force_generator.push_off_distance")
Overall, given the simulated data and vjsim
model of the vertical jump, Samozino practical model profile constructs (i.e., \(F_0\), \(V_0\), and \(P_{max}\)) has great correlation to the true Force Generator values. My reluctance to accept these as ontological qualities is still ongoing, particularly since this simulation is related only to concentric squat jump. Real force generator qualities might manifest differently in different activities. In other words, \(P_{max}\) estimated in squat jump should highly correlated with \(P_{max}\) in broad jump, sprint and other activities. But this doesn’t negate their practical utility (I am leaning more toward pragmatist-realist stance).
Let’s see how different model jump height predictions align with true jump height.
samozino_theoretical_pred <- regression_model( data = vjsim_data, target = "bodyweight_jump.height", predictors = c( "samozino_theoretical_profile.height" ), interactions = TRUE, var_imp = "firm" ) samozino_theoretical_pred$summary #> #> Call: #> lm(formula = as.formula(paste(target, interactions_string)), #> data = model_df) #> #> Residuals: #> Min 1Q Median 3Q Max #> -0.19956 -0.06419 -0.01594 0.05562 0.26333 #> #> Coefficients: #> Estimate Std. Error t value Pr(>|t|) #> (Intercept) -0.024366 0.002403 -10.14 <2e-16 *** #> samozino_theoretical_profile.height 1.121217 0.006390 175.46 <2e-16 *** #> --- #> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 #> #> Residual standard error: 0.08584 on 8638 degrees of freedom #> Multiple R-squared: 0.7809, Adjusted R-squared: 0.7809 #> F-statistic: 3.079e+04 on 1 and 8638 DF, p-value: < 2.2e-16 samozino_theoretical_pred$graph
Theoretical Samozino model (the one that uses true mean velocity, rather than take-off velocity) is very bad at predicting jump height. Let’s see how the practical model performs:
samozino_practical_pred <- regression_model( data = vjsim_data, target = "bodyweight_jump.height", predictors = c( "samozino_practical_profile.height" ), interactions = TRUE, var_imp = "firm" ) samozino_practical_pred$summary #> #> Call: #> lm(formula = as.formula(paste(target, interactions_string)), #> data = model_df) #> #> Residuals: #> Min 1Q Median 3Q Max #> -3.077e-03 -4.206e-04 5.937e-05 4.930e-04 2.648e-03 #> #> Coefficients: #> Estimate Std. Error t value Pr(>|t|) #> (Intercept) -5.775e-04 1.953e-05 -29.57 <2e-16 *** #> samozino_practical_profile.height 9.957e-01 4.757e-05 20932.38 <2e-16 *** #> --- #> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 #> #> Residual standard error: 0.0008143 on 8638 degrees of freedom #> Multiple R-squared: 1, Adjusted R-squared: 1 #> F-statistic: 4.382e+08 on 1 and 8638 DF, p-value: < 2.2e-16 samozino_practical_pred$graph
And this is, ladies and gents, example of perfect prediction. Let’s see how the “simple model” performs:
simple_pred <- regression_model( data = vjsim_data, target = "bodyweight_jump.height", predictors = c( "simple_profile.height" ), interactions = TRUE, var_imp = "firm" ) simple_pred$summary #> #> Call: #> lm(formula = as.formula(paste(target, interactions_string)), #> data = model_df) #> #> Residuals: #> Min 1Q Median 3Q Max #> -0.0051288 -0.0010871 0.0000921 0.0008277 0.0048459 #> #> Coefficients: #> Estimate Std. Error t value Pr(>|t|) #> (Intercept) -2.866e-03 4.143e-05 -69.17 <2e-16 *** #> simple_profile.height 1.022e+00 1.030e-04 9919.16 <2e-16 *** #> --- #> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 #> #> Residual standard error: 0.001718 on 8638 degrees of freedom #> Multiple R-squared: 0.9999, Adjusted R-squared: 0.9999 #> F-statistic: 9.839e+07 on 1 and 8638 DF, p-value: < 2.2e-16 simple_pred$graph
This also represents perfect prediction.
Let’s explore what variables predict vertical jump height. I will split this analysis into two parts: (1) exploration of the Force Generator characteristics, and (2) exploration using practical Samozino profile.
I will use the above function to plot relationship of Force Generator characteristics with vertical jump height, as well as to model using linear regression model at the end of this section.
plot_relationship( "force_generator.max_force", "bodyweight_jump.height" ) #> $graph
#>
#> $R_squared
#> [1] 0.01901522
Max force seems to have very low relationship with jump height. Let’s check relative max force (divided by bodyweight):
plot_relationship( "force_generator.F0_rel", "bodyweight_jump.height" ) #> $graph
#>
#> $R_squared
#> [1] 0.06465174
Let’s check max velocity:
plot_relationship( "force_generator.max_velocity", "bodyweight_jump.height" ) #> $graph
#>
#> $R_squared
#> [1] 0.816508
Much better association between the two. Let’s check the max power:
plot_relationship( "force_generator.Pmax", "bodyweight_jump.height" ) #> $graph
#>
#> $R_squared
#> [1] 0.6942989
Interestingly, although high association, it is lower than max velocity. Let’s check relative max power:
plot_relationship( "force_generator.Pmax_rel", "bodyweight_jump.height" ) #> $graph
#>
#> $R_squared
#> [1] 0.8956185
So far, relative max power has the highest association with jump height.
Let’s perform some modeling using know Force Generator parameters:
fgen_height_model <- regression_model( data = vjsim_data, target = "bodyweight_jump.height", predictors = c( "force_generator.bodyweight", "force_generator.push_off_distance", "force_generator.max_force", "force_generator.max_velocity", "force_generator.time_to_max_activation", "force_generator.decline_rate", "force_generator.peak_location" ), interactions = TRUE, var_imp = "firm" ) fgen_height_model$summary #> #> Call: #> lm(formula = as.formula(paste(target, interactions_string)), #> data = model_df) #> #> Residuals: #> Min 1Q Median 3Q Max #> -0.086947 -0.018600 0.002937 0.017716 0.059034 #> #> Coefficients: #> Estimate #> (Intercept) 5.907e-04 #> force_generator.bodyweight 5.330e-03 #> force_generator.push_off_distance -4.890e-01 #> force_generator.max_force -3.977e-05 #> force_generator.max_velocity 2.690e-02 #> force_generator.time_to_max_activation -8.122e-02 #> force_generator.decline_rate 4.262e-02 #> force_generator.peak_location 5.959e-01 #> force_generator.bodyweight:force_generator.push_off_distance -6.325e-03 #> force_generator.bodyweight:force_generator.max_force -6.118e-07 #> force_generator.bodyweight:force_generator.max_velocity -1.497e-03 #> force_generator.bodyweight:force_generator.time_to_max_activation 2.746e-03 #> force_generator.bodyweight:force_generator.decline_rate -1.009e-04 #> force_generator.bodyweight:force_generator.peak_location -2.355e-03 #> force_generator.push_off_distance:force_generator.max_force 1.488e-04 #> force_generator.push_off_distance:force_generator.max_velocity 2.172e-01 #> force_generator.push_off_distance:force_generator.time_to_max_activation 2.919e-01 #> force_generator.push_off_distance:force_generator.decline_rate -1.241e-01 #> force_generator.push_off_distance:force_generator.peak_location -8.296e-01 #> force_generator.max_force:force_generator.max_velocity 3.522e-05 #> force_generator.max_force:force_generator.time_to_max_activation -6.460e-05 #> force_generator.max_force:force_generator.decline_rate 2.373e-06 #> force_generator.max_force:force_generator.peak_location 5.540e-05 #> force_generator.max_velocity:force_generator.time_to_max_activation -2.959e-02 #> force_generator.max_velocity:force_generator.decline_rate -6.591e-03 #> force_generator.max_velocity:force_generator.peak_location 6.835e-02 #> force_generator.time_to_max_activation:force_generator.decline_rate 4.451e-02 #> force_generator.time_to_max_activation:force_generator.peak_location 1.680e-01 #> force_generator.decline_rate:force_generator.peak_location -1.522e-01 #> Std. Error #> (Intercept) 1.386e-02 #> force_generator.bodyweight 2.524e-04 #> force_generator.push_off_distance 2.313e-02 #> force_generator.max_force 5.169e-06 #> force_generator.max_velocity 1.260e-03 #> force_generator.time_to_max_activation 1.950e-02 #> force_generator.decline_rate 8.563e-03 #> force_generator.peak_location 1.061e-01 #> force_generator.bodyweight:force_generator.push_off_distance 3.912e-04 #> force_generator.bodyweight:force_generator.max_force 2.820e-08 #> force_generator.bodyweight:force_generator.max_velocity 1.870e-05 #> force_generator.bodyweight:force_generator.time_to_max_activation 2.857e-04 #> force_generator.bodyweight:force_generator.decline_rate 1.304e-04 #> force_generator.bodyweight:force_generator.peak_location 1.597e-03 #> force_generator.push_off_distance:force_generator.max_force 7.667e-06 #> force_generator.push_off_distance:force_generator.max_velocity 1.792e-03 #> force_generator.push_off_distance:force_generator.time_to_max_activation 2.738e-02 #> force_generator.push_off_distance:force_generator.decline_rate 1.250e-02 #> force_generator.push_off_distance:force_generator.peak_location 1.530e-01 #> force_generator.max_force:force_generator.max_velocity 3.666e-07 #> force_generator.max_force:force_generator.time_to_max_activation 5.599e-06 #> force_generator.max_force:force_generator.decline_rate 2.556e-06 #> force_generator.max_force:force_generator.peak_location 3.130e-05 #> force_generator.max_velocity:force_generator.time_to_max_activation 1.309e-03 #> force_generator.max_velocity:force_generator.decline_rate 5.974e-04 #> force_generator.max_velocity:force_generator.peak_location 7.317e-03 #> force_generator.time_to_max_activation:force_generator.decline_rate 9.125e-03 #> force_generator.time_to_max_activation:force_generator.peak_location 1.118e-01 #> force_generator.decline_rate:force_generator.peak_location 5.101e-02 #> t value #> (Intercept) 0.043 #> force_generator.bodyweight 21.116 #> force_generator.push_off_distance -21.140 #> force_generator.max_force -7.694 #> force_generator.max_velocity 21.345 #> force_generator.time_to_max_activation -4.165 #> force_generator.decline_rate 4.978 #> force_generator.peak_location 5.616 #> force_generator.bodyweight:force_generator.push_off_distance -16.170 #> force_generator.bodyweight:force_generator.max_force -21.693 #> force_generator.bodyweight:force_generator.max_velocity -80.032 #> force_generator.bodyweight:force_generator.time_to_max_activation 9.611 #> force_generator.bodyweight:force_generator.decline_rate -0.774 #> force_generator.bodyweight:force_generator.peak_location -1.475 #> force_generator.push_off_distance:force_generator.max_force 19.412 #> force_generator.push_off_distance:force_generator.max_velocity 121.210 #> force_generator.push_off_distance:force_generator.time_to_max_activation 10.661 #> force_generator.push_off_distance:force_generator.decline_rate -9.935 #> force_generator.push_off_distance:force_generator.peak_location -5.421 #> force_generator.max_force:force_generator.max_velocity 96.077 #> force_generator.max_force:force_generator.time_to_max_activation -11.539 #> force_generator.max_force:force_generator.decline_rate 0.929 #> force_generator.max_force:force_generator.peak_location 1.770 #> force_generator.max_velocity:force_generator.time_to_max_activation -22.607 #> force_generator.max_velocity:force_generator.decline_rate -11.033 #> force_generator.max_velocity:force_generator.peak_location 9.341 #> force_generator.time_to_max_activation:force_generator.decline_rate 4.877 #> force_generator.time_to_max_activation:force_generator.peak_location 1.504 #> force_generator.decline_rate:force_generator.peak_location -2.984 #> Pr(>|t|) #> (Intercept) 0.96601 #> force_generator.bodyweight < 2e-16 #> force_generator.push_off_distance < 2e-16 #> force_generator.max_force 1.58e-14 #> force_generator.max_velocity < 2e-16 #> force_generator.time_to_max_activation 3.14e-05 #> force_generator.decline_rate 6.56e-07 #> force_generator.peak_location 2.02e-08 #> force_generator.bodyweight:force_generator.push_off_distance < 2e-16 #> force_generator.bodyweight:force_generator.max_force < 2e-16 #> force_generator.bodyweight:force_generator.max_velocity < 2e-16 #> force_generator.bodyweight:force_generator.time_to_max_activation < 2e-16 #> force_generator.bodyweight:force_generator.decline_rate 0.43919 #> force_generator.bodyweight:force_generator.peak_location 0.14038 #> force_generator.push_off_distance:force_generator.max_force < 2e-16 #> force_generator.push_off_distance:force_generator.max_velocity < 2e-16 #> force_generator.push_off_distance:force_generator.time_to_max_activation < 2e-16 #> force_generator.push_off_distance:force_generator.decline_rate < 2e-16 #> force_generator.push_off_distance:force_generator.peak_location 6.08e-08 #> force_generator.max_force:force_generator.max_velocity < 2e-16 #> force_generator.max_force:force_generator.time_to_max_activation < 2e-16 #> force_generator.max_force:force_generator.decline_rate 0.35308 #> force_generator.max_force:force_generator.peak_location 0.07674 #> force_generator.max_velocity:force_generator.time_to_max_activation < 2e-16 #> force_generator.max_velocity:force_generator.decline_rate < 2e-16 #> force_generator.max_velocity:force_generator.peak_location < 2e-16 #> force_generator.time_to_max_activation:force_generator.decline_rate 1.09e-06 #> force_generator.time_to_max_activation:force_generator.peak_location 0.13272 #> force_generator.decline_rate:force_generator.peak_location 0.00285 #> #> (Intercept) #> force_generator.bodyweight *** #> force_generator.push_off_distance *** #> force_generator.max_force *** #> force_generator.max_velocity *** #> force_generator.time_to_max_activation *** #> force_generator.decline_rate *** #> force_generator.peak_location *** #> force_generator.bodyweight:force_generator.push_off_distance *** #> force_generator.bodyweight:force_generator.max_force *** #> force_generator.bodyweight:force_generator.max_velocity *** #> force_generator.bodyweight:force_generator.time_to_max_activation *** #> force_generator.bodyweight:force_generator.decline_rate #> force_generator.bodyweight:force_generator.peak_location #> force_generator.push_off_distance:force_generator.max_force *** #> force_generator.push_off_distance:force_generator.max_velocity *** #> force_generator.push_off_distance:force_generator.time_to_max_activation *** #> force_generator.push_off_distance:force_generator.decline_rate *** #> force_generator.push_off_distance:force_generator.peak_location *** #> force_generator.max_force:force_generator.max_velocity *** #> force_generator.max_force:force_generator.time_to_max_activation *** #> force_generator.max_force:force_generator.decline_rate #> force_generator.max_force:force_generator.peak_location . #> force_generator.max_velocity:force_generator.time_to_max_activation *** #> force_generator.max_velocity:force_generator.decline_rate *** #> force_generator.max_velocity:force_generator.peak_location *** #> force_generator.time_to_max_activation:force_generator.decline_rate *** #> force_generator.time_to_max_activation:force_generator.peak_location #> force_generator.decline_rate:force_generator.peak_location ** #> --- #> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 #> #> Residual standard error: 0.02323 on 8611 degrees of freedom #> Multiple R-squared: 0.984, Adjusted R-squared: 0.984 #> F-statistic: 1.892e+04 on 28 and 8611 DF, p-value: < 2.2e-16 fgen_height_model$graph
fgen_height_model$var_imp
plot_pdp(fgen_height_model, "force_generator.max_velocity")
plot_pdp(fgen_height_model, "force_generator.max_force")
plot_pdp(fgen_height_model, "force_generator.bodyweight")
The jump height prediction (using multivariate linear regression with interaction) from the known Force Generator characteristics is almost perfect, with very high \(R^2\).
Let’s repeat this analysis using Samozino profile:
plot_relationship( "samozino_practical_profile.F0", "bodyweight_jump.height" ) #> $graph
#>
#> $R_squared
#> [1] 0.05788338
Not great as expected. Let’s use relative \(F_0\):
plot_relationship( "samozino_practical_profile.F0_rel", "bodyweight_jump.height" ) #> $graph
#>
#> $R_squared
#> [1] 0.1467072
Let’s check \(V_0\):
plot_relationship( "samozino_practical_profile.V0", "bodyweight_jump.height" ) #> $graph
#>
#> $R_squared
#> [1] 0.7916385
Again, much higher association than force. Let’s check \(P_{max}\):
plot_relationship( "samozino_practical_profile.Pmax", "bodyweight_jump.height" ) #> $graph
#>
#> $R_squared
#> [1] 0.7449721
And finally, relative \(P_{max}\):
plot_relationship( "samozino_practical_profile.Pmax_rel", "bodyweight_jump.height" ) #> $graph
#>
#> $R_squared
#> [1] 0.9258284
Relative \(P_{max}\) shows very high association with jump height (this could be the circular causality explained in the previous vignette: we use performance metrics to device underlying determinants of performance). Let’s perform multivariate prediction using linear regression with interactions:
samozino_height_model <- regression_model( data = vjsim_data, target = "bodyweight_jump.height", predictors = c( "force_generator.bodyweight", "force_generator.push_off_distance", "samozino_practical_profile.F0", "samozino_practical_profile.V0" ), interactions = TRUE, var_imp = "firm" ) samozino_height_model$summary #> #> Call: #> lm(formula = as.formula(paste(target, interactions_string)), #> data = model_df) #> #> Residuals: #> Min 1Q Median 3Q Max #> -0.093858 -0.014627 0.002152 0.015270 0.052084 #> #> Coefficients: #> Estimate #> (Intercept) -7.622e-02 #> force_generator.bodyweight 4.964e-03 #> force_generator.push_off_distance -2.587e-01 #> samozino_practical_profile.F0 -5.118e-06 #> samozino_practical_profile.V0 3.203e-02 #> force_generator.bodyweight:force_generator.push_off_distance -4.673e-03 #> force_generator.bodyweight:samozino_practical_profile.F0 -8.937e-07 #> force_generator.bodyweight:samozino_practical_profile.V0 -2.276e-03 #> force_generator.push_off_distance:samozino_practical_profile.F0 1.212e-04 #> force_generator.push_off_distance:samozino_practical_profile.V0 2.660e-01 #> samozino_practical_profile.F0:samozino_practical_profile.V0 5.864e-05 #> Std. Error #> (Intercept) 8.269e-03 #> force_generator.bodyweight 1.503e-04 #> force_generator.push_off_distance 1.556e-02 #> samozino_practical_profile.F0 3.256e-06 #> samozino_practical_profile.V0 1.410e-03 #> force_generator.bodyweight:force_generator.push_off_distance 3.066e-04 #> force_generator.bodyweight:samozino_practical_profile.F0 2.517e-08 #> force_generator.bodyweight:samozino_practical_profile.V0 2.210e-05 #> force_generator.push_off_distance:samozino_practical_profile.F0 5.962e-06 #> force_generator.push_off_distance:samozino_practical_profile.V0 2.447e-03 #> samozino_practical_profile.F0:samozino_practical_profile.V0 4.451e-07 #> t value #> (Intercept) -9.217 #> force_generator.bodyweight 33.026 #> force_generator.push_off_distance -16.631 #> samozino_practical_profile.F0 -1.572 #> samozino_practical_profile.V0 22.711 #> force_generator.bodyweight:force_generator.push_off_distance -15.241 #> force_generator.bodyweight:samozino_practical_profile.F0 -35.500 #> force_generator.bodyweight:samozino_practical_profile.V0 -102.983 #> force_generator.push_off_distance:samozino_practical_profile.F0 20.333 #> force_generator.push_off_distance:samozino_practical_profile.V0 108.693 #> samozino_practical_profile.F0:samozino_practical_profile.V0 131.740 #> Pr(>|t|) #> (Intercept) <2e-16 *** #> force_generator.bodyweight <2e-16 *** #> force_generator.push_off_distance <2e-16 *** #> samozino_practical_profile.F0 0.116 #> samozino_practical_profile.V0 <2e-16 *** #> force_generator.bodyweight:force_generator.push_off_distance <2e-16 *** #> force_generator.bodyweight:samozino_practical_profile.F0 <2e-16 *** #> force_generator.bodyweight:samozino_practical_profile.V0 <2e-16 *** #> force_generator.push_off_distance:samozino_practical_profile.F0 <2e-16 *** #> force_generator.push_off_distance:samozino_practical_profile.V0 <2e-16 *** #> samozino_practical_profile.F0:samozino_practical_profile.V0 <2e-16 *** #> --- #> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 #> #> Residual standard error: 0.02112 on 8629 degrees of freedom #> Multiple R-squared: 0.9868, Adjusted R-squared: 0.9867 #> F-statistic: 6.426e+04 on 10 and 8629 DF, p-value: < 2.2e-16 samozino_height_model$graph
samozino_height_model$var_imp
plot_pdp(samozino_height_model, "samozino_practical_profile.F0")
plot_pdp(samozino_height_model, "samozino_practical_profile.V0")
plot_pdp(samozino_height_model, "force_generator.bodyweight")
Multivariate model using practical Samozino profile shows great predictive power for predicting vertical jump height.
If you check PDP+ICE plots for \(F_0\) and \(V_0\), you can see that not all ICE lines are parallel. This implies that for some individuals increasing \(F_0\) and \(V_0\) yields more or less benefit in increasing jump height. This is further discussed in optimization vignette, and it is interesting it showed up in multivariate model as well.
The goal of Samozino’s practical method is to give intervention ideas to practitioners in terms of which side of the FV curve (i.e., \(F_0\) or \(V_0\)) improvements will yield highest improvements in the bodyweight vertical jump. This is very useful information to practitioners. One problem with such information is the following: it doesn’t give us information what is easier to improve. For example, increasing \(V_0\) for 10% that yields higher improvement in vertical jump height compared to 10% improvement in \(F_0\) doesn’t tell us which one is easier or faster (or even safer) to increase. For example, even if increasing \(V_0\) yields higher increase in vertical jump height, maybe increasing \(F_0\) is much simpler, and vice versa. But this is a topic for the end of this vignette.
So far we are interested in validity of such statement/model (which was explored in the optimization vignette). What I have done when I generated vjsim_data
is to provide a probing variables (see columns that start with probe_bodyweight_jump.
). These are generated by simulating the same jump, but with either 10% better max force, max velocity, or time to max activation (Force Generator characteristic). Variable probe_bodyweight_jump.velocity_to_force_height_ratio
contains the ratio between improvements in jump height when max velocity is improved to improvements in jump height when max force is improved:
\[ height \; ratio = \frac{height_{10\%velocity} - height}{height_{10\%force} - height} \]
Thus probe_bodyweight_jump.velocity_to_force_height_ratio
represents some type of an index which Force Generator characteristic (either force or velocity) improvement yields more improvement in jump height. Let’s plot this variable against samozino_practical_profile.Sfv_perc
(see optimization vignette for more details):
plot_relationship( "samozino_practical_profile.Sfv_perc", "probe_bodyweight_jump.velocity_to_force_height_ratio" ) #> $graph
#>
#> $R_squared
#> [1] 0.9295965
probing_Sfv_perc <- regression_model(
data = vjsim_data,
target = "probe_bodyweight_jump.velocity_to_force_height_ratio",
predictors = c(
"samozino_practical_profile.Sfv_perc"
),
interactions = TRUE, var_imp = "firm"
)
probing_Sfv_perc$graph
The relationship between the two is outstanding. Although the prediction is not ideal (given SESOI of SDx0.2), this is very interesting and confirmatory insight. Let’s repeat the same, but this time for the ratio between max velocity and time to max activation improvement:
plot_relationship( "samozino_practical_profile.Sfv_perc", "probe_bodyweight_jump.velocity_to_activation_height_ratio" ) #> $graph
#>
#> $R_squared
#> [1] 1.198197e-08
probing_Sfv_perc <- regression_model(
data = vjsim_data,
target = "probe_bodyweight_jump.velocity_to_activation_height_ratio",
predictors = c(
"samozino_practical_profile.Sfv_perc"
),
interactions = TRUE, var_imp = "firm"
)
probing_Sfv_perc$graph
The practical Samozino is not able to pick-up improvements between max velocity and time to max activation. Let’s check the relationship with ratio between max force and time to max activation improvements:
plot_relationship( "samozino_practical_profile.Sfv_perc", "probe_bodyweight_jump.force_to_activation_height_ratio" ) #> $graph
#>
#> $R_squared
#> [1] 6.990933e-05
probing_Sfv_perc <- regression_model(
data = vjsim_data,
target = "probe_bodyweight_jump.force_to_activation_height_ratio",
predictors = c(
"samozino_practical_profile.Sfv_perc"
),
interactions = TRUE, var_imp = "firm"
)
probing_Sfv_perc$graph
Again, very bad prediction by the model. This implies that Samozino model was able to (only) predict (with a very decent amount) the intervention effects of increasing max force or max velocity of the Force Generator, thus give insights with one will give more benefits.
Let’s see how other optimization models perform. Let’s check the “simple model” (see optimization vignette for more details):
plot_relationship( "simple_profile.Sfv_perc", "probe_bodyweight_jump.velocity_to_force_height_ratio" ) #> $graph
#>
#> $R_squared
#> [1] 0.2273651
probing_simple_Sfv_perc <- regression_model(
data = vjsim_data,
target = "probe_bodyweight_jump.velocity_to_force_height_ratio",
predictors = c(
"simple_profile.Sfv_perc"
),
interactions = TRUE, var_imp = "firm"
)
probing_simple_Sfv_perc$graph
And for other two ratios:
plot_relationship( "simple_profile.Sfv_perc", "probe_bodyweight_jump.velocity_to_activation_height_ratio" ) #> $graph
#>
#> $R_squared
#> [1] 0.0003882336
probing_simple_Sfv_perc <- regression_model(
data = vjsim_data,
target = "probe_bodyweight_jump.velocity_to_activation_height_ratio",
predictors = c(
"simple_profile.Sfv_perc"
),
interactions = TRUE, var_imp = "firm"
)
probing_simple_Sfv_perc$graph
plot_relationship( "simple_profile.Sfv_perc", "probe_bodyweight_jump.force_to_activation_height_ratio" ) #> $graph
#>
#> $R_squared
#> [1] 2.290225e-05
probing_simple_Sfv_perc <- regression_model(
data = vjsim_data,
target = "probe_bodyweight_jump.force_to_activation_height_ratio",
predictors = c(
"simple_profile.Sfv_perc"
),
interactions = TRUE, var_imp = "firm"
)
probing_simple_Sfv_perc$graph
This implies that the simple optimization model is useless in predicting intervention effects.
Let’s now turn to Load~Peak Force profile (see optimization vignette for more details). Since this profile doesn’t give us any idea what quality needs to be improved, let’s check how it is related to improvements in height when max force, max velocity, or time to max activation is improved for 10%:
plot_relationship( "LPF_profile.slope", "probe_bodyweight_jump.force_height_ratio" ) #> $graph
#>
#> $R_squared
#> [1] 0.5804511
plot_relationship( "LPF_profile.slope", "probe_bodyweight_jump.velocity_height_ratio" ) #> $graph
#>
#> $R_squared
#> [1] 0.2331085
plot_relationship( "LPF_profile.slope", "probe_bodyweight_jump.activation_height_ratio" ) #> $graph
#>
#> $R_squared
#> [1] 0.0919479
It seems that LPF slope give imperfect hints only for effects of improving max force, but nothing else.
Let’s see if we can predict samozino_practical_profile.Sfv_perc
from probing variables:
probing_samozino <- regression_model( data = vjsim_data, target = "samozino_practical_profile.Sfv_perc", predictors = c( "probe_bodyweight_jump.velocity_to_force_height_ratio", "probe_bodyweight_jump.velocity_to_activation_height_ratio", "probe_bodyweight_jump.force_to_activation_height_ratio" ), interactions = FALSE, var_imp = "firm" ) probing_samozino$summary #> #> Call: #> lm(formula = as.formula(paste(target, interactions_string)), #> data = model_df) #> #> Residuals: #> Min 1Q Median 3Q Max #> -58.315 -7.731 -0.396 8.166 68.139 #> #> Coefficients: #> Estimate Std. Error #> (Intercept) 3.279e+01 3.051e-01 #> probe_bodyweight_jump.velocity_to_force_height_ratio 5.420e+01 1.605e-01 #> probe_bodyweight_jump.velocity_to_activation_height_ratio -5.259e-05 1.770e-04 #> probe_bodyweight_jump.force_to_activation_height_ratio 1.387e-04 2.196e-04 #> t value Pr(>|t|) #> (Intercept) 107.469 <2e-16 *** #> probe_bodyweight_jump.velocity_to_force_height_ratio 337.640 <2e-16 *** #> probe_bodyweight_jump.velocity_to_activation_height_ratio -0.297 0.766 #> probe_bodyweight_jump.force_to_activation_height_ratio 0.632 0.528 #> --- #> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 #> #> Residual standard error: 15.51 on 8636 degrees of freedom #> Multiple R-squared: 0.9296, Adjusted R-squared: 0.9296 #> F-statistic: 3.801e+04 on 3 and 8636 DF, p-value: < 2.2e-16 probing_samozino$graph
probing_samozino$var_imp
plot_pdp(probing_samozino, "probe_bodyweight_jump.velocity_to_force_height_ratio")
This simple model concludes that samozino_practical_profile.Sfv_perc
can give us glimpses into which intervention should be done (i.e., improving max force or max velocity).
Let’s do the same for LPF slope:
probing_LPF_slope <- regression_model( data = vjsim_data, target = "LPF_profile.slope", predictors = c( "probe_bodyweight_jump.velocity_height_ratio", "probe_bodyweight_jump.force_height_ratio", "probe_bodyweight_jump.activation_height_ratio" ), interactions = FALSE, var_imp = "firm" ) probing_LPF_slope$summary #> #> Call: #> lm(formula = as.formula(paste(target, interactions_string)), #> data = model_df) #> #> Residuals: #> Min 1Q Median 3Q Max #> -5.7939 -0.8850 0.2009 1.0639 4.3388 #> #> Coefficients: #> Estimate Std. Error t value #> (Intercept) -78.2235 3.6106 -21.66 #> probe_bodyweight_jump.velocity_height_ratio 13.6226 0.3722 36.60 #> probe_bodyweight_jump.force_height_ratio -92.2693 1.1601 -79.53 #> probe_bodyweight_jump.activation_height_ratio 170.0740 2.7265 62.38 #> Pr(>|t|) #> (Intercept) <2e-16 *** #> probe_bodyweight_jump.velocity_height_ratio <2e-16 *** #> probe_bodyweight_jump.force_height_ratio <2e-16 *** #> probe_bodyweight_jump.activation_height_ratio <2e-16 *** #> --- #> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 #> #> Residual standard error: 1.507 on 8636 degrees of freedom #> Multiple R-squared: 0.7126, Adjusted R-squared: 0.7125 #> F-statistic: 7137 on 3 and 8636 DF, p-value: < 2.2e-16 probing_LPF_slope$graph
probing_LPF_slope$var_imp
And using the ratios:
probing_LPF_slope <- regression_model( data = vjsim_data, target = "LPF_profile.slope", predictors = c( "probe_bodyweight_jump.velocity_to_force_height_ratio", "probe_bodyweight_jump.velocity_to_activation_height_ratio", "probe_bodyweight_jump.force_to_activation_height_ratio" ), interactions = FALSE, var_imp = "firm" ) probing_LPF_slope$summary #> #> Call: #> lm(formula = as.formula(paste(target, interactions_string)), #> data = model_df) #> #> Residuals: #> Min 1Q Median 3Q Max #> -6.1087 -1.7631 0.0206 1.6652 5.6566 #> #> Coefficients: #> Estimate Std. Error #> (Intercept) 4.938e+00 4.407e-02 #> probe_bodyweight_jump.velocity_to_force_height_ratio 1.631e+00 2.319e-02 #> probe_bodyweight_jump.velocity_to_activation_height_ratio -9.707e-05 2.556e-05 #> probe_bodyweight_jump.force_to_activation_height_ratio 9.323e-05 3.172e-05 #> t value Pr(>|t|) #> (Intercept) 112.045 < 2e-16 *** #> probe_bodyweight_jump.velocity_to_force_height_ratio 70.346 < 2e-16 *** #> probe_bodyweight_jump.velocity_to_activation_height_ratio -3.797 0.000147 *** #> probe_bodyweight_jump.force_to_activation_height_ratio 2.939 0.003303 ** #> --- #> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 #> #> Residual standard error: 2.24 on 8636 degrees of freedom #> Multiple R-squared: 0.3654, Adjusted R-squared: 0.3652 #> F-statistic: 1657 on 3 and 8636 DF, p-value: < 2.2e-16 probing_LPF_slope$graph
probing_LPF_slope$var_imp
Unfortunately, LPF slope does not provide insight to which intervention should be done to improve the vertical jump height.
Bosco Index is another measure that is used to indicate whether someone needs to work on “speed” or “force” qualities. Let’s see how is it associated with aforementioned vjsim
interventions:
plot_relationship( "bosco.index", "probe_bodyweight_jump.force_height_ratio" ) #> $graph
#>
#> $R_squared
#> [1] 0.5274059
plot_relationship( "bosco.index", "probe_bodyweight_jump.velocity_height_ratio" ) #> $graph
#>
#> $R_squared
#> [1] 0.0009284794
plot_relationship( "bosco.index", "probe_bodyweight_jump.activation_height_ratio" ) #> $graph
#>
#> $R_squared
#> [1] 0.03715357
It looks like Bosco Index has a similar relationship with max force increments as LPF slope. Let’s check the relationship between the two:
plot_relationship( "bosco.index", "LPF_profile.slope" ) #> $graph
#>
#> $R_squared
#> [1] 0.2167145
As can be seen from the above graph, these two metrics are not related.
In conclusion, Samozino model was able to predict which would be better intervention to be made (i.e. either increasing max force of max velocity) to increase the vertical jump height. LPF slope was able to less than perfectly predict interventions of max force increase (higher the slope, the lower the effects of increasing max force). Keep in mind that these conclusions are only given the vjsim
model and related to improvements in bodyweight vertical jump height. LPF slope and Bosco Index might be useful for other purposes beyond bodyweight vertical jump height.
Another useful exploration that might be done is factor analysis. This analysis can help us find latent variables that explain the observed metrics (Borsboom 2008, 2009; Borsboom, Mellenbergh, and van Heerden 2003). Rather than performing a proper exploratory and confirmatory factor analysis, I will perform simple and more visual variable clustering (Chavent et al. 2017). Here is the function that perform variable clustering:
var_clust <- function(data, predictors) { # Feature clustering data <- select_(data, .dots = predictors) cluster_model <- hclustvar(data) var_clust <- ggdendrogram(as.dendrogram(cluster_model), rotate = TRUE) return(list( model = cluster_model, graph = var_clust )) }
For a start, let’s see how bodyweight jump summary metrics cluster together:
jump_metrics_cluster <- var_clust( vjsim_data, c( "force_generator.bodyweight", "bodyweight_jump.take_off_time", "bodyweight_jump.take_off_velocity", "bodyweight_jump.height", "bodyweight_jump.mean_GRF_over_distance", "bodyweight_jump.mean_velocity", "bodyweight_jump.work_done", "bodyweight_jump.impulse", "bodyweight_jump.mean_power", "bodyweight_jump.mean_RFD", "bodyweight_jump.mean_RPD", "bodyweight_jump.peak_GRF", "bodyweight_jump.peak_velocity", "bodyweight_jump.peak_power", "bodyweight_jump.peak_RFD", "bodyweight_jump.GRF_at_100ms", "bodyweight_jump.GRF_at_200ms", "bodyweight_jump.peak_RPD" ) ) jump_metrics_cluster$graph
Eyeballing the figure above, we can see that there are 2-3 variable clusters (latent variables?):
part <- cutreevar(jump_metrics_cluster$model, 3) summary(part) #> #> Call: #> cutreevar(obj = jump_metrics_cluster$model, k = 3) #> #> #> #> Data: #> number of observations: 8640 #> number of variables: 18 #> number of clusters: 3 #> #> Cluster 1 : #> squared loading correlation #> bodyweight_jump.impulse 0.97 -0.99 #> bodyweight_jump.mean_GRF_over_distance 0.92 -0.96 #> bodyweight_jump.peak_power 0.88 -0.94 #> bodyweight_jump.work_done 0.86 -0.93 #> bodyweight_jump.mean_power 0.83 -0.91 #> bodyweight_jump.GRF_at_200ms 0.59 -0.77 #> force_generator.bodyweight 0.44 -0.66 #> #> #> Cluster 2 : #> squared loading correlation #> bodyweight_jump.peak_RPD 0.91 0.95 #> bodyweight_jump.mean_RPD 0.87 0.93 #> bodyweight_jump.peak_GRF 0.80 0.89 #> bodyweight_jump.peak_RFD 0.79 0.89 #> bodyweight_jump.GRF_at_100ms 0.76 0.87 #> bodyweight_jump.mean_RFD 0.73 0.86 #> bodyweight_jump.take_off_time 0.57 -0.75 #> #> #> Cluster 3 : #> squared loading correlation #> bodyweight_jump.peak_velocity 0.97 0.99 #> bodyweight_jump.take_off_velocity 0.97 0.98 #> bodyweight_jump.height 0.97 0.98 #> bodyweight_jump.mean_velocity 0.67 0.82 #> #> #> Gain in cohesion (in %): 49.78 plot(part)
#> $coord.quanti
#> $coord.quanti$Cluster1
#> dim 1
#> force_generator.bodyweight 0.6630546
#> bodyweight_jump.GRF_at_200ms 0.7690410
#> bodyweight_jump.mean_power 0.9096675
#> bodyweight_jump.work_done 0.9264730
#> bodyweight_jump.peak_power 0.9405246
#> bodyweight_jump.mean_GRF_over_distance 0.9605437
#> bodyweight_jump.impulse 0.9870264
#>
#> $coord.quanti$Cluster2
#> dim 1
#> bodyweight_jump.take_off_time -0.7527464
#> bodyweight_jump.mean_RFD 0.8569294
#> bodyweight_jump.GRF_at_100ms 0.8711827
#> bodyweight_jump.peak_RFD 0.8867181
#> bodyweight_jump.peak_GRF 0.8921435
#> bodyweight_jump.mean_RPD 0.9322654
#> bodyweight_jump.peak_RPD 0.9530226
#>
#> $coord.quanti$Cluster3
#> dim 1
#> bodyweight_jump.mean_velocity 0.8165407
#> bodyweight_jump.height 0.9838033
#> bodyweight_jump.take_off_velocity 0.9843257
#> bodyweight_jump.peak_velocity 0.9863779
#>
#>
#> $coord.levels
#> $coord.levels$Cluster1
#> [1] "No categorical variables in this cluster"
#>
#> $coord.levels$Cluster2
#> [1] "No categorical variables in this cluster"
#>
#> $coord.levels$Cluster3
#> [1] "No categorical variables in this cluster"
Power and force metrics can be normalized (divided by bodyweight), but I will leave this to you to explore. So far the three clusters, in my humble opinion, reflect three latent variable:
Let’s see how the profiles cluster together with Force Generator characteristics:
profiles_cluster <- var_clust( vjsim_data, c( # Force Generator characteristics # "force_generator.bodyweight", "force_generator.push_off_distance", "force_generator.max_force", "force_generator.max_velocity", "force_generator.decline_rate", "force_generator.peak_location", "force_generator.time_to_max_activation", "force_generator.Pmax", "force_generator.Sfv", # Profiles "profile_mean_FV.F0", "profile_mean_FV.V0", "profile_mean_FV.Pmax", "profile_mean_FV.Pmax", "profile_mean_FV.Sfv", "profile_mean_power.Pmax", "profile_peak_FV.F0", "profile_peak_FV.V0", "profile_peak_FV.Pmax", "profile_peak_FV.Sfv", "profile_load_take_off_velocity.V0", "profile_load_take_off_velocity.L0", "profile_load_take_off_velocity.Imax", "profile_load_take_off_velocity.Slv", "profile_load_impulse.Imax", "LPF_profile.slope", "samozino_practical_profile.F0", "samozino_practical_profile.V0", "samozino_practical_profile.Pmax", "samozino_practical_profile.Sfv", "simple_profile.L0", "simple_profile.TOV0", "simple_profile.Sfv" ) ) profiles_cluster$graph
Here are the list of metrics if 4 clusters are selected:
part <- cutreevar(profiles_cluster$model, 4) summary(part) #> #> Call: #> cutreevar(obj = profiles_cluster$model, k = 4) #> #> #> #> Data: #> number of observations: 8640 #> number of variables: 30 #> number of clusters: 4 #> #> Cluster 1 : #> squared loading correlation #> profile_mean_FV.V0 0.891 0.94 #> LPF_profile.slope 0.874 -0.94 #> profile_peak_FV.V0 0.793 0.89 #> profile_mean_FV.Sfv 0.750 0.87 #> force_generator.time_to_max_activation 0.489 -0.70 #> force_generator.push_off_distance 0.079 0.28 #> force_generator.decline_rate 0.024 0.16 #> #> #> Cluster 2 : #> squared loading correlation #> profile_load_take_off_velocity.L0 0.960 -0.980 #> simple_profile.L0 0.960 -0.980 #> samozino_practical_profile.F0 0.956 -0.978 #> force_generator.max_force 0.954 -0.977 #> profile_peak_FV.F0 0.883 -0.940 #> profile_mean_FV.F0 0.814 -0.902 #> force_generator.peak_location 0.005 -0.071 #> #> #> Cluster 3 : #> squared loading correlation #> force_generator.Sfv 0.93 0.96 #> samozino_practical_profile.Sfv 0.91 0.96 #> profile_load_take_off_velocity.Slv 0.89 0.95 #> simple_profile.Sfv 0.89 0.95 #> profile_load_take_off_velocity.V0 0.85 0.92 #> simple_profile.TOV0 0.85 0.92 #> samozino_practical_profile.V0 0.84 0.92 #> profile_peak_FV.Sfv 0.83 0.91 #> force_generator.max_velocity 0.82 0.91 #> #> #> Cluster 4 : #> squared loading correlation #> profile_mean_power.Pmax 0.96 0.98 #> profile_mean_FV.Pmax 0.96 0.98 #> samozino_practical_profile.Pmax 0.95 0.97 #> profile_load_impulse.Imax 0.94 0.97 #> profile_load_take_off_velocity.Imax 0.94 0.97 #> force_generator.Pmax 0.93 0.96 #> profile_peak_FV.Pmax 0.88 0.94 #> #> #> Gain in cohesion (in %): 62.21 plot(part)
#> $coord.quanti
#> $coord.quanti$Cluster1
#> dim 1
#> LPF_profile.slope -0.9350605
#> force_generator.time_to_max_activation -0.6992094
#> force_generator.decline_rate 0.1550889
#> force_generator.push_off_distance 0.2818969
#> profile_mean_FV.Sfv 0.8661340
#> profile_peak_FV.V0 0.8906017
#> profile_mean_FV.V0 0.9438788
#>
#> $coord.quanti$Cluster2
#> dim 1
#> force_generator.peak_location 0.0707664
#> profile_mean_FV.F0 0.9023549
#> profile_peak_FV.F0 0.9398599
#> force_generator.max_force 0.9767068
#> samozino_practical_profile.F0 0.9777222
#> profile_load_take_off_velocity.L0 0.9795751
#> simple_profile.L0 0.9795751
#>
#> $coord.quanti$Cluster3
#> dim 1
#> force_generator.max_velocity 0.9074145
#> profile_peak_FV.Sfv 0.9089502
#> samozino_practical_profile.V0 0.9176766
#> simple_profile.TOV0 0.9245467
#> profile_load_take_off_velocity.V0 0.9245467
#> profile_load_take_off_velocity.Slv 0.9455146
#> simple_profile.Sfv 0.9455146
#> samozino_practical_profile.Sfv 0.9553041
#> force_generator.Sfv 0.9638523
#>
#> $coord.quanti$Cluster4
#> dim 1
#> profile_peak_FV.Pmax 0.9354888
#> force_generator.Pmax 0.9647642
#> profile_load_take_off_velocity.Imax 0.9712058
#> profile_load_impulse.Imax 0.9717904
#> samozino_practical_profile.Pmax 0.9743840
#> profile_mean_FV.Pmax 0.9803157
#> profile_mean_power.Pmax 0.9813673
#>
#>
#> $coord.levels
#> $coord.levels$Cluster1
#> [1] "No categorical variables in this cluster"
#>
#> $coord.levels$Cluster2
#> [1] "No categorical variables in this cluster"
#>
#> $coord.levels$Cluster3
#> [1] "No categorical variables in this cluster"
#>
#> $coord.levels$Cluster4
#> [1] "No categorical variables in this cluster"
For the final variable cluster analysis, let’s use probing metrics and optimization profiles:
optimization_cluster <- var_clust( vjsim_data, c( # Probing characteristics "probe_bodyweight_jump.force_height_ratio", "probe_bodyweight_jump.velocity_height_ratio", "probe_bodyweight_jump.activation_height_ratio", "probe_bodyweight_jump.velocity_to_force_height_ratio", "probe_bodyweight_jump.velocity_to_activation_height_ratio", "probe_bodyweight_jump.force_to_activation_height_ratio", # Bosco index "bosco.index", # Profiles "LPF_profile.slope", "samozino_practical_profile.Sfv_perc", "samozino_practical_profile.probe_IMB", "simple_profile.Sfv_perc", "simple_profile.probe_IMB" ) ) optimization_cluster$graph
As can be seen from the above figure, Samozino optimization model \(S_{FV}\%\) and \(FV_{IMB}\) cluster very well with probe_bodyweight_jump.velocity_to_force_height_ratio
. Interesting cluster is simple profile and Bosco Index cluster.
So far, we have added some simulation evidence on the validity of the Samozino model to “predict” intervention effects on the FV curve. This is useful from a practical perspective, since as coaches we are interested in what quality needs to be developed to improve training effect. Samozino model allows some of those insights when it comes to squat jump. The procedure is simple (will walk you through the example data set in the modeling vignette): measure individual’s push-off distance, put him or her on the jump mat or something that can measure height and perform incremental jumps by adding extra weight (this is usually done with the barbell, but I prefer hex bar barbell, but again, the height of the barbell might be lower or higher than the distance at the bottom of the squat jump). Once we have these values, Samozino model can be performed and suggestions to what part of the FV curve to intervene. Unfortunately, Samozino model doesn’t tell us which effects are easier or less costly to get - it only tell us what would happen if the FV curve shifted. For example, if someone is weak as a kitten (has low \(F0\)), improving his force capabilities might be easier and thus more beneficial, even if the model suggest otherwise (i.e., improving \(V0\)).
So far there have been only two studies that attempted that (Jiménez-Reyes et al. 2017; Jiménez-Reyes, Samozino, and Morin 2019) with very interesting findings. I won’t go into detail of the studies, but the basic premise is simple: distribute individuals in personalized training based on their needs (identified by Samozino model) and see if vertical jump height improves better compared to well-balanced (or generic, mixed training).
Although I welcome this research I have few issues that I will cover here and finish with that. First issue is that groups were not blinded. This means that just by having a special training based on your needs might increase motivation and effects of training. These effects could be reduced by blinding the participants and researchers. Or estimated by adding someone on-purpose from the opposite optimization group (not to confirm the benefits, but to confirm the lack of benefits of personalized training, which is also important). For example, each optimization group (i.e. velocity group vs force group) has not only individuals that are identified as lacking those qualities, but a mix of everyone (but we lie to everyone that this is ideal scenario based on the model insights). For example, the velocity group has a mix of everyone, but we tell them it is their optimal group based on their needs. Then we analyze the sub-strata of the group.
Second issues is the change in training direction (variety) from a normal training, which can cause training effects by itself. Jimenez-Reyes et al. (Jiménez-Reyes et al. 2017; Jiménez-Reyes, Samozino, and Morin 2019) tried to control for this by having non-optimized group (which was similar to well-balanced group in terms of training content).
Third, and the most important issue is the message they are sending with realist ontology stance, that simple jump squat modeling represent an estimate of the whole lower body Force Generator, which implies that the whole training can be optimized based on the jump squat model. This might not be the intention of the authors, but readers are getting that impressions. Athlete not only need to jump from a static stance, but they needs to perform many other activities. If the whole training is optimized to make improvements in the jump squat, then maybe it might be much more under-optimized for other activities, or maybe even harmful. I have spent much more time and words on discussing the concepts of optimality vs. robustness in a pre-print paper and my books (Jovanović and Jukić 2019; Jovanović 2018, 2020c), so I direct you to those source.
I have started vjsim
project since I failed to understand the math and logic behind Samozino model. This simulation allowed me to understand biomechanics of jumping a bit better and to appreciate the Samozino model application. I think it is useful model, but at the end of the day , it represents only one source of information for the coaches to consider and put under perspective and context. For example, Bosco Index and LPF slope might also give us some insight to which element someone needs to work (without a direct optimization objective). Overall, Samozino model represents a sound tool in your toolbox.
In the next vignette I will walk you through the analysis and modeling using the collected test data.
Now start playing with the code or with the Shiny App by click on the previous link or by running the following code:
vjsim::run_simulator()
The Shiny App will allow you much more interactive environment for exploring the vjsim
.
Altmann, Thomas, Jakob Bodensteiner, Cord Dankers, Thommy Dassen, Nikolas Fritz, Sebastian Gruber, Philipp Kopper, Veronika Kronseder, Moritz Wagner, and Emanuel Renkl. 2019. Limitations of Interpretable Machine Learning Methods.
Borsboom, Denny. 2009. Measuring the Mind: Conceptual Issues in Modern Psychometrics. Cambridge: Cambridge University Press.
———. 2008. “Latent Variable Theory.” Measurement: Interdisciplinary Research & Perspective 6 (1-2): 25–53. https://doi.org/10.1080/15366360802035497.
Borsboom, Denny, Gideon J. Mellenbergh, and Jaap van Heerden. 2003. “The Theoretical Status of Latent Variables.” Psychological Review 110 (2): 203–19. https://doi.org/10.1037/0033-295X.110.2.203.
Chavent, Marie, Vanessa Kuentz, Benoit Liquet, and Jerome Saracco. 2017. ClustOfVar: Clustering of Variables. https://CRAN.R-project.org/package=ClustOfVar.
Goldstein, Alex, Adam Kapelner, Justin Bleich, and Emil Pitkin. 2013. “Peeking Inside the Black Box: Visualizing Statistical Learning with Plots of Individual Conditional Expectation.” arXiv:1309.6392 [Stat], September. http://arxiv.org/abs/1309.6392.
Greenwell, Brandon M. 2017. “Pdp: An R Package for Constructing Partial Dependence Plots.” The R Journal 9 (1): 421–36. https://doi.org/10.32614/RJ-2017-016.
Greenwell, Brandon M., Bradley C. Boehmke, and Andrew J. McCarthy. 2018. “A Simple and Effective Model-Based Variable Importance Measure.” arXiv:1805.04755 [Cs, Stat], May. http://arxiv.org/abs/1805.04755.
Jiménez-Reyes, Pedro, Pierre Samozino, Matt Brughelli, and Jean-Benoît Morin. 2017. “Effectiveness of an Individualized Training Based on Force-Velocity Profiling During Jumping.” Frontiers in Physiology 7 (January). https://doi.org/10.3389/fphys.2016.00677.
Jiménez-Reyes, Pedro, Pierre Samozino, and Jean-Benoît Morin. 2019. “Optimized Training for Jumping Performance Using the Force-Velocity Imbalance: Individual Adaptation Kinetics.” Edited by Daniel Boullosa. PLOS ONE 14 (5): e0216681. https://doi.org/10.1371/journal.pone.0216681.
Jovanović, Mladen. 2020a. Bmbstats: Bootstrap Magnitude-Based Statistics. https://github.com/mladenjovanovic/bmbstats.
———. 2020b. “Bmbstats: Magnitude-Based Statistics for Sports Scientists.” https://mladenjovanovic.github.io/bmbstats-book/. https://mladenjovanovic.github.io/bmbstats-book/.
———. 2018. HIIT Manual: High Intensity Interval Training and Agile Periodization. Ultimate Athlete Concepts.
———. 2019. “Statistical Modelling for Sports Scientists: Practical Introduction Using R (Part 1).” SportRxiv, September. https://doi.org/10.31236/osf.io/dnq3m.
———. 2020c. Strength Training Manual: The Agile Periodization Approach. Independently published.
Jovanović, Mladen, and Ivan Jukić. 2019. “Optimal Vs. Robust: Applications to Planning Strategies. Insights from a Simulation Study.” SportRxiv, April. https://doi.org/10.31236/osf.io/8n4jf.
Molnar, Christoph. 2018. Interpretable Machine Learning. Leanpub.
Samozino, P., P. Edouard, S. Sangnier, M. Brughelli, P. Gimenez, and J.-B. Morin. 2013. “Force-Velocity Profile: Imbalance Determination and Effect on Lower Limb Ballistic Performance.” International Journal of Sports Medicine 35 (06): 505–10. https://doi.org/10.1055/s-0033-1354382.
Samozino, Pierre. 2018a. “A Simple Method for Measuring Lower Limb Force, Velocity and Power Capabilities During Jumping.” In Biomechanics of Training and Testing, edited by Jean-Benoit Morin and Pierre Samozino, 65–96. Cham: Springer International Publishing. https://doi.org/10.1007/978-3-319-05633-3_4.
———. 2018b. “Optimal Force-Velocity Profile in Ballistic Push-Off: Measurement and Relationship with Performance.” In Biomechanics of Training and Testing, edited by Jean-Benoit Morin and Pierre Samozino, 97–119. Cham: Springer International Publishing. https://doi.org/10.1007/978-3-319-05633-3_5.
Samozino, Pierre, Jean-Benoît Morin, Frédérique Hintzy, and Alain Belli. 2008. “A Simple Method for Measuring Force, Velocity and Power Output During Squat Jump.” Journal of Biomechanics 41 (14): 2940–5. https://doi.org/10.1016/j.jbiomech.2008.07.028.
Samozino, Pierre, Jean-Benoît Morin, Frédérique Hintzy, and Alain Belli. 2010. “Jumping Ability: A Theoretical Integrative Approach.” Journal of Theoretical Biology 264 (1): 11–18. https://doi.org/10.1016/j.jtbi.2010.01.021.
Samozino, Pierre, Enrico Rejc, Pietro Enrico Di Prampero, Alain Belli, and Jean-Benoît Morin. 2012. “Optimal Force–Velocity Profile in Ballistic Movements—Altius: Citius or Fortius?” Medicine & Science in Sports & Exercise 44 (2): 313–22. https://doi.org/10.1249/MSS.0b013e31822d757a.