[docs]defcomplexity_k(signal,k_max="max",show=False):"""**Automated selection of k for Higuchi Fractal Dimension (HFD)** The optimal *k-max* is computed based on the point at which HFD values plateau for a range of *k-max* values (see Vega, 2015). Parameters ---------- signal : Union[list, np.array, pd.Series] The signal (i.e., a time series) in the form of a vector of values. k_max : Union[int, str, list], optional Maximum number of interval times (should be greater than or equal to 3) to be tested. If ``max``, it selects the maximum possible value corresponding to half the length of the signal. show : bool Visualise the slope of the curve for the selected kmax value. Returns -------- k : float The optimal kmax of the time series. info : dict A dictionary containing additional information regarding the parameters used to compute optimal kmax. See Also -------- fractal_higuchi Examples ---------- .. ipython:: python import neurokit2 as nk signal = nk.signal_simulate(duration=2, sampling_rate=100, frequency=[5, 6], noise=0.5) @savefig p_complexity_k1.png scale=100% k_max, info = nk.complexity_k(signal, k_max='default', show=True) @suppress plt.close() .. ipython:: python k_max References ---------- * Higuchi, T. (1988). Approach to an irregular time series on the basis of the fractal theory. Physica D: Nonlinear Phenomena, 31(2), 277-283. * Vega, C. F., & Noel, J. (2015, June). Parameters analyzed of Higuchi's fractal dimension for EEG brain signals. In 2015 Signal Processing Symposium (SPSympo) (pp. 1-5). IEEE. https:// ieeexplore.ieee.org/document/7168285 """# Get the range of k-max values to be tested# ------------------------------------------ifisinstance(k_max,str):# e.g., "default"# upper limit for k value (max possible value)k_max=int(np.floor(len(signal)/2))# so that normalizing factor is positiveifisinstance(k_max,int):kmax_range=np.arange(2,k_max+1)elifisinstance(k_max,(list,np.ndarray,pd.Series)):kmax_range=np.array(k_max)else:warn("k_max should be an int or a list of values of kmax to be tested.",category=NeuroKitWarning,)# Compute the slope for each kmax value# --------------------------------------vectorized_k_slope=np.vectorize(_complexity_k_slope,excluded=[1])slopes,intercepts,info=vectorized_k_slope(kmax_range,signal)# k_values = [d["k_values"] for d in info]average_values=[d["average_values"]fordininfo]# Find plateau (the saturation point of slope)# --------------------------------------------optimal_point=find_plateau(slopes,show=False)ifoptimal_pointisnotNone:kmax_optimal=kmax_range[optimal_point]else:kmax_optimal=np.max(kmax_range)warn("The optimal kmax value detected is 2 or less. There may be no plateau in this case. "+f"You can inspect the plot by set `show=True`. We will return optimal k_max = {kmax_optimal} (the max).",category=NeuroKitWarning,)# Plotifshow:_complexity_k_plot(kmax_range,slopes,kmax_optimal,ax=None)# Return optimal tau and info dictreturnkmax_optimal,{"Values":kmax_range,"Scores":slopes,"Intercepts":intercepts,"Average_Values":average_values,}
# =============================================================================# Utilities# =============================================================================def_complexity_k_Lk(k,signal):n=len(signal)# Step 1: construct k number of new time series for range of k_values from 1 to kmaxk_subrange=np.arange(1,k+1)# where m = 1, 2... kidx=np.tile(np.arange(0,len(signal),k),(k,1)).astype(float)idx+=np.tile(np.arange(0,k),(idx.shape[1],1)).Tmask=idx>=len(signal)idx[mask]=0sig_values=signal[idx.astype(int)].astype(float)sig_values[mask]=np.nan# Step 2: Calculate length Lm(k) of each curvenormalization=(n-1)/(np.floor((n-k_subrange)/k).astype(int)*k)sets=(np.nansum(np.abs(np.diff(sig_values)),axis=1)*normalization)/k# Step 3: Compute average value over k sets of Lm(k)returnnp.sum(sets)/kdef_complexity_k_slope(kmax,signal,k_number="max"):ifk_number=="max":k_values=np.arange(1,kmax+1)else:k_values=np.unique(np.linspace(1,kmax+1,k_number).astype(int))# Step 3 of Vega & Noel (2015)vectorized_Lk=np.vectorize(_complexity_k_Lk,excluded=[1])# Compute length of the curve, Lm(k)average_values=vectorized_Lk(k_values,signal)# Slope of best-fit line through points (slope equal to FD)slope,intercept=-np.polyfit(np.log(k_values),np.log(average_values),1)returnslope,intercept,{"k_values":k_values,"average_values":average_values}# =============================================================================# Plotting# =============================================================================def_complexity_k_plot(k_range,slope_values,k_optimal,ax=None):# Prepare plotifaxisNone:fig,ax=plt.subplots()else:fig=Noneax.set_title("Optimization of $k_{max}$ parameter")ax.set_xlabel("$k_{max}$ values")ax.set_ylabel("Higuchi Fractal Dimension (HFD) values")colors=plt.cm.PuBu(np.linspace(0,1,len(k_range)))# if single time seriesax.plot(k_range,slope_values,color="#2196F3",zorder=1)fori,_inenumerate(k_range):ax.scatter(k_range[i],slope_values[i],color=colors[i],marker="o",zorder=2)ax.axvline(x=k_optimal,color="#E91E63",label="Optimal $k_{max}$: "+str(k_optimal))ax.legend(loc="upper right")returnfig