1. Introduction¶
L’ingénierie des caractéristiques (feature engineering) est le processus qui transforme des données brutes en grandeurs — les caractéristiques (features) — utilisables pour l’analyse statistique et la prédiction.
Les grands types de caractéristiques :
Caractéristiques statistiques¶
Les caractéristiques statistiques résument la distribution des valeurs : tendance centrale, dispersion et forme.
- Moyenne : valeur moyenne des points de données.
- Variance / écart-type : mesure de l’étalement des données.
- Asymétrie (skewness) : asymétrie de la distribution des valeurs.
- Kurtosis : « épaisseur des queues » de la distribution.
- Centiles : valeurs seuils délimitant différents segments de la distribution.
Caractéristiques temporelles¶
Les caractéristiques temporelles décrivent les motifs qui dépendent du temps au sein d’une série.
- Autocorrélation : corrélation d’une série temporelle avec une version décalée d’elle-même.
- Tendance : augmentation ou diminution de long terme dans les données.
- Saisonnalité : motifs réguliers qui se répètent sur une période donnée.
- Points de rupture : endroits où les propriétés statistiques de la série changent.
Ci-dessous, nous construisons une série journalière synthétique avec une tendance et un cycle annuel, nous en retirons la tendance et nous mesurons l’autocorrélation à décalage 1 de ce qui reste.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
rng = np.random.default_rng(42)
# Create a 3-year daily time series with trend and seasonality
t = np.arange(0, 365 * 3)
seasonal_series = rng.standard_normal(3 * 365) + np.linspace(0, 10, 3 * 365) + \
2 * np.sin(2 * np.pi * np.linspace(0, 3, 3 * 365))
# fit a trend using least squares regression
slope, intercept = np.polyfit(t, seasonal_series, 1)
trend = slope * t + intercept
# the seasonal component is what remains after removing the trend
seasonal = seasonal_series - trend
# lag-1 autocorrelation of the detrended series
autocorr_seasonal = np.corrcoef(seasonal[:-1], seasonal[1:])[0, 1]
print(f'Lag-1 autocorrelation of the detrended seasonal series: {autocorr_seasonal:.2f}')
# Plot the original time series, trend, and seasonal component
plt.figure(figsize=(10, 6))
plt.plot(seasonal_series, label='Original Time Series')
plt.plot(trend, label='Trend')
plt.plot(seasonal, label='Seasonal (detrended)')
plt.xlabel('Day')
plt.legend()Lag-1 autocorrelation of the detrended seasonal series: 0.68

La série détendancée porte toujours le cycle annuel : des jours voisins se ressemblent donc, et l’autocorrélation à décalage 1 est élevée. Comparez avec un bruit blanc pur de même longueur : le bruit blanc n’a pas de mémoire, donc son autocorrélation à décalage 1 est proche de zéro.
white_series = rng.standard_normal(3 * 365)
autocorr_white = np.corrcoef(white_series[:-1], white_series[1:])[0, 1]
print(f'Lag-1 autocorrelation of the detrended seasonal series: {autocorr_seasonal:.2f}')
print(f'Lag-1 autocorrelation of white noise: {autocorr_white:.2f}')Lag-1 autocorrelation of the detrended seasonal series: 0.68
Lag-1 autocorrelation of white noise: -0.00
Caractéristiques spatiales¶
Pour les données géospatiales, les caractéristiques spatiales capturent les relations entre différents lieux d’une image ou d’un jeu de données.
- Texture : motifs des variations locales d’intensité (lisse, rugueux, par exemple).
- Corrélation spatiale : mesure à quel point des lieux voisins se ressemblent en valeurs d’intensité.
from scipy.ndimage import gaussian_filter
from scipy.fft import fft2, ifft2, fftshift
# Generate a 2D grid (e.g., geospatial data, such as a topographic map)
n = 100 # size of the grid
x = np.linspace(0, 10, n)
y = np.linspace(0, 10, n)
X2d, Y2d = np.meshgrid(x, y)
# 2D white noise
white_noise_2d = rng.standard_normal((n, n))
# Spatially correlated noise: smooth the white noise with a Gaussian filter
spatially_correlated_noise = gaussian_filter(white_noise_2d, sigma=3)
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.imshow(white_noise_2d, extent=[0, 10, 0, 10], cmap='viridis')
plt.xlabel('x')
plt.ylabel('y')
plt.colorbar(label='Amplitude')
plt.title('2D White Noise')
plt.subplot(1, 2, 2)
plt.imshow(spatially_correlated_noise, extent=[0, 10, 0, 10], cmap='viridis')
plt.xlabel('x')
plt.ylabel('y')
plt.colorbar(label='Amplitude')
plt.title('2D Spatially Correlated Noise')
Autocorrélation spatiale du bruit blanc, calculée via le spectre de puissance (théorème de Wiener-Khintchine) :
# 2D Fourier transform of the white noise
fft_white_noise = fft2(white_noise_2d)
# power spectrum
power_spectrum = np.abs(fft_white_noise) ** 2
# inverse transform of the power spectrum gives the autocorrelation function
autocorrelation_white = np.real(ifft2(power_spectrum))
# shift the zero-lag component to the center
autocorrelation_white = fftshift(autocorrelation_white)
# normalize
autocorrelation_white /= autocorrelation_white.max()Autocorrélation spatiale du bruit corrélé :
fft_noise = fft2(spatially_correlated_noise)
power_spectrum = np.abs(fft_noise) ** 2
autocorrelation = np.real(ifft2(power_spectrum))
autocorrelation = fftshift(autocorrelation)
autocorrelation /= autocorrelation.max()plt.figure(figsize=(11, 6))
# White noise (unfiltered)
plt.subplot(2, 2, 1)
plt.imshow(white_noise_2d, extent=[0, 10, 0, 10], cmap='viridis')
plt.xlabel('x')
plt.ylabel('y')
plt.colorbar(label='Amplitude')
plt.title('2D White Noise')
plt.subplot(2, 2, 2)
plt.imshow(autocorrelation_white, extent=[-5, 5, -5, 5], cmap='viridis')
plt.xlabel('x')
plt.ylabel('y')
plt.colorbar(label='Amplitude')
plt.title('2D White Noise Autocorrelation')
# Spatially correlated noise
plt.subplot(2, 2, 3)
plt.imshow(spatially_correlated_noise, extent=[0, 10, 0, 10], cmap='viridis')
plt.xlabel('x')
plt.ylabel('y')
plt.colorbar(label='Amplitude')
plt.title('2D Spatially Correlated Noise')
plt.subplot(2, 2, 4)
plt.imshow(autocorrelation, extent=[-5, 5, -5, 5], cmap='viridis')
plt.xlabel('x')
plt.ylabel('y')
plt.colorbar(label='Normalized Amplitude')
plt.title('Autocorrelation Function')
plt.tight_layout()
plt.show()
Estimez la caractéristique « longueur de corrélation » des deux images. La longueur de corrélation se mesure souvent comme la distance à laquelle la fonction d’autocorrélation décroît à 1/e de sa valeur maximale.
center = n // 2
# correlation length for the white noise
autocorr_center = autocorrelation_white[center, center:]
distances = np.linspace(0, 5, center)
correlation_length_white = np.interp(1 / np.e, autocorr_center[::-1], distances[::-1])
# correlation length for the spatially correlated noise
autocorr_center = autocorrelation[center, center:]
correlation_length = np.interp(1 / np.e, autocorr_center[::-1], distances[::-1])
print(f'Estimated correlation length for white noise: {correlation_length_white:.2f} '
f'and for spatially correlated noise: {correlation_length:.2f}')Estimated correlation length for white noise: 0.07 and for spatially correlated noise: 0.61
Caractéristiques fractales¶
Les caractéristiques fractales décrivent l’auto-similarité ou la complexité des données à travers les échelles. La plus courante est la dimension fractale : une courbe lisse a une dimension proche de 1, tandis qu’une courbe rugueuse, qui remplit davantage le plan, a une dimension plus proche de 2.
La méthode de Higuchi estime la dimension fractale d’une série temporelle. Elle mesure la « longueur » moyenne de la courbe à des sous-échantillonnages de plus en plus grossiers ; la pente de log(longueur) en fonction de log(échelle) donne la dimension.
def higuchi_fd(series, kmax=10):
"""Higuchi fractal dimension of a 1D time series.
Builds kmax coarse-grained versions of the series, measures the mean
curve length at each scale k, and fits the slope of
log(length) vs log(1/k). Returns the estimated fractal dimension,
between 1 (smooth) and 2 (rough).
"""
series = np.asarray(series, dtype=float)
npts = len(series)
lengths = []
for k in range(1, kmax + 1):
lk = []
for m in range(k):
idx = np.arange(m, npts, k)
if len(idx) < 2:
continue
dist = np.sum(np.abs(np.diff(series[idx])))
# normalization factor for the subsampled curve
norm = (npts - 1) / (len(idx) - 1) / k
lk.append(dist * norm / k)
lengths.append(np.mean(lk))
coeffs = np.polyfit(np.log(1.0 / np.arange(1, kmax + 1)), np.log(lengths), 1)
return coeffs[0]
print(f'Higuchi fractal dimension of white noise: '
f'{higuchi_fd(white_series):.2f}')
print(f'Higuchi fractal dimension of the seasonal series: '
f'{higuchi_fd(seasonal_series):.2f}')
smooth_series = gaussian_filter(white_series, sigma=5)
print(f'Higuchi fractal dimension of smoothed noise: '
f'{higuchi_fd(smooth_series):.2f}')Higuchi fractal dimension of white noise: 1.99
Higuchi fractal dimension of the seasonal series: 1.98
Higuchi fractal dimension of smoothed noise: 1.10
Le bruit blanc est maximalement rugueux : sa dimension est donc proche de 2. Le lissage abaisse la dimension vers 1. La dimension fractale est un nombre unique qui résume la rugosité, ce qui en fait une caractéristique compacte pour classer des signaux.
2. Exemple sur des séries temporelles sismiques¶
Cette section montre comment extraire automatiquement des caractéristiques avec un simple paquet Python, tsfel, appliqué à des formes d’onde sismiques enregistrées dans le Nord-Ouest Pacifique.
Le sous-ensemble miniPNW contient des formes d’onde sismiques étiquetées pour des événements d’origines variées :
- Séismes
- Explosions (surtout des tirs de carrière)
- Événements de surface (avalanches et glissements de terrain, par exemple)
- Bangs soniques
- Tonnerre
Nous allons explorer comment les caractéristiques varient d’une classe d’événements sismiques à l’autre.
Citation du jeu de données. Les données sont un sous-ensemble du jeu de données de référence PNW-ML : Ni, Y., Hutko, A., Skene, F., Denolle, M., Malone, S., Bodin, P., Hartog, R., & Wright, A. (2023). Curated Pacific Northwest AI-ready Seismic Dataset. Seismica, 2(1). doi:Ni et al. (2023). Le chemin d’accès archivé au jeu de données complet est seisbench.data.PNW() ; nous utilisons ici un petit sous-ensemble (« miniPNW ») hébergé sur un serveur de la classe.
# Import modules for seismic data and feature extraction
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import scipy
import scipy.stats as st
import os
import urllib.request
import h5py # for reading .h5 filesNous téléchargeons deux fichiers depuis le stockage de la classe vers ./data/ : les formes d’onde dans un fichier HDF5 et leurs métadonnées associées dans un fichier CSV. Le fichier de métadonnées est petit. Le fichier de formes d’onde est volumineux (environ 1 Go), nous protégeons donc son téléchargement : en cas d’échec, le carnet affiche un message et saute les cellules qui dépendent des formes d’onde.
os.makedirs('data', exist_ok=True)
base_url = 'https://dasway.ess.washington.edu/shared/niyiyu/PNW-ML'
metadata_path = './data/miniPNW_metadata.csv'
waveform_path = './data/miniPNW_waveforms.hdf5'
# metadata: small CSV, always download if missing
if not os.path.exists(metadata_path):
urllib.request.urlretrieve(f'{base_url}/miniPNW_metadata.csv', metadata_path)
print(f'Metadata file present: {os.path.exists(metadata_path)}')
# waveforms: large HDF5, guarded download
if not os.path.exists(waveform_path):
try:
print('Downloading waveform file (large, be patient)...')
urllib.request.urlretrieve(f'{base_url}/miniPNW_waveforms.hdf5',
waveform_path)
except Exception as e:
if os.path.exists(waveform_path):
os.remove(waveform_path) # remove partial file
raise RuntimeError(
'Could not download miniPNW_waveforms.hdf5 from '
f'{base_url}. Check your network connection, or place the file '
'manually in ./data/. Original error: ' + repr(e)) from e
print(f'Waveform file present: {os.path.exists(waveform_path)}')Metadata file present: True
Waveform file present: True
Métadonnées¶
Nous lisons d’abord les métadonnées et les organisons dans une table de données (data frame) pandas, un objet DataFrame.
df = pd.read_csv(metadata_path)
# Display the first few rows of the DataFrame
df.head()La nature de la source de l’événement est stockée dans l’un des attributs de métadonnées.
df['source_type'].unique()<ArrowStringArray>
['earthquake', 'explosion', 'sonic_boom', 'thunder', 'surface_event']
Length: 5, dtype: strSupposons que nous explorions des caractéristiques pour classer les formes d’onde selon les catégories de types d’événements. Nous prenons l’attribut source_type comme étiquette.
labels = df['source_type']
print(labels.value_counts())source_type
earthquake 500
explosion 500
surface_event 500
sonic_boom 126
thunder 94
Name: count, dtype: int64
Combien y a-t-il de formes d’onde sismiques dans chaque catégorie ?
counts = labels.value_counts()
fractions = counts / counts.sum()
for name, count in counts.items():
print(f'{name:>15s}: {count:5d} waveforms ({100 * fractions[name]:.1f}%)') earthquake: 500 waveforms (29.1%)
explosion: 500 waveforms (29.1%)
surface_event: 500 waveforms (29.1%)
sonic_boom: 126 waveforms (7.3%)
thunder: 94 waveforms (5.5%)
Diriez-vous que ce jeu de données est équilibré vis-à-vis des classes qui nous intéressent ? Le déséquilibre des classes compte : un classifieur entraîné sur cet ensemble verra bien plus souvent la classe dominante, et l’exactitude seule devient un score trompeur.
counts.plot(kind='bar')
plt.ylabel('Number of waveforms')
plt.title('Class balance of the miniPNW dataset')
plt.tight_layout()
Données de formes d’onde¶
Nous lisons maintenant les données de formes d’onde. Elles sont stockées dans un fichier HDF5, réparties sur un nombre fini de groupes. Chaque groupe contient un tableau (array) de jeux de données correspondant aux formes d’onde. Pour relier les métadonnées aux fichiers de formes d’onde, la clé trace_name porte l’identifiant du jeu de données. L’adresse est étiquetée ainsi :
bucketX$i,:3,:noù X est le numéro du groupe HDF5 et i l’indice. Le fichier contient typiquement 3 formes d’onde, une par direction du mouvement du sol : N, E, Z. Dans ce qui suit, nous nous concentrons sur les formes d’onde verticales (Z).
Toutes les cellules qui ont besoin du fichier de formes d’onde sont conditionnées par un test d’existence du fichier, de sorte que le carnet s’exécute encore de bout en bout si le téléchargement a échoué.
chan_list = ['N', 'E', 'Z']
if os.path.exists(waveform_path):
f = h5py.File(waveform_path, 'r')
else:
print('Waveform file not found: skipping waveform cells.')Ci-dessous, une fonction pour lire une forme d’onde dans le fichier.
def read_data(tn, f):
"""
Read the waveform data from the .h5 file.
tn: trace_name of the waveform
f: open h5py file object
"""
bucket, narray = tn.split('$') # split trace_name into bucket and indices
x, y, z = [int(i) for i in narray.split(',:')]
data = f['/data/%s' % bucket][x, :y, :z] # read the data as a 2D array
return dataLe nom de la trace est stocké comme attribut de données dans les métadonnées.
trace_names = list(df['trace_name'])
trace_names[0]'bucket1$0,:3,:15001'if os.path.exists(waveform_path):
example_waveform = read_data(trace_names[530], f)
print(f'The first dimension of the data is {example_waveform.shape[0]}')
print(f'The second dimension of the data is {example_waveform.shape[1]}')
# the time vector goes from -50 to 100 s around the pick, at 100 Hz
t = np.linspace(-50, 100, example_waveform.shape[1])
# plot an example of the data in a 3-row subplot
fig, ax = plt.subplots(3, 1, figsize=(10, 6))
for i in range(3):
ax[i].plot(t, example_waveform[i, :])
ax[i].set_title(f'Channel {chan_list[i]}')
ax[i].set_xlabel('Time (s)')
ax[i].set_ylabel('Amplitude')
ax[i].grid(True)
ax[i].set_xlim(-50, 100)
plt.tight_layout()
else:
print('Waveform file not found: skipping waveform plot.')The first dimension of the data is 3
The second dimension of the data is 15001

Nous extrayons la composante Z de chaque forme d’onde et les empilons dans un seul tableau.
if os.path.exists(waveform_path):
nt = example_waveform.shape[-1]
ndata = len(labels)
Z = np.zeros(shape=(ndata, nt))
for i in range(ndata):
Z[i, :] = read_data(df.iloc[i]['trace_name'], f)[2, :nt]
print(f'We have a total of {Z.shape[0]} data samples and each has '
f'{Z.shape[1]} data points')
else:
print('Waveform file not found: skipping waveform stacking.')We have a total of 1720 data samples and each has 15001 data points
Extraction automatique de caractéristiques avec tsfel¶
Nous disposons maintenant des données et de leurs attributs, en particulier l’étiquette qu’est le type de source. Nous allons extraire des caractéristiques automatiquement avec tsfel et explorer comment elles varient d’une classe à l’autre.
tsfel organise les caractéristiques par domaine (statistique, temporel, spectral). Nous chargeons la configuration par défaut :
import tsfel
cfg = tsfel.get_features_by_domain()
print(list(cfg.keys()))['spectral', 'statistical', 'temporal', 'fractal']
tsfel prend un tableau 1D et la fréquence d’échantillonnage, et renvoie un DataFrame d’une seule ligne de caractéristiques. Nous encapsulons l’extraction dans une fonction qui boucle sur un ensemble de formes d’onde, y attache l’étiquette et nettoie les noms de colonnes (tsfel préfixe chaque colonne par 0_).
Note sur le temps de calcul : extraire le jeu complet de caractéristiques pour chaque forme d’onde de miniPNW prend trop de temps pour une séance de cours. Nous plafonnons l’extraction à un sous-ensemble aléatoire d’environ 200 formes d’onde. N’hésitez pas à relever ce plafond en dehors du cours.
def calculate_features(Z, indices, df, cfg, fs=100.0):
"""
Calculate tsfel features for a subset of waveforms.
Z: 2D array of seismic data (n_waveforms, n_samples)
indices: which rows of Z to process
df: metadata DataFrame (for the source_type label)
cfg: tsfel feature configuration
Returns:
X: DataFrame of features, one row per waveform, with a source_type column
"""
rows = []
for count, i in enumerate(indices):
if count % 20 == 0:
print(f'Extracting features from sample {count}/{len(indices)}')
Xi = tsfel.time_series_features_extractor(cfg, Z[i, :], fs=fs, verbose=0)
Xi['source_type'] = df.iloc[i]['source_type']
rows.append(Xi)
X = pd.concat(rows, axis=0, ignore_index=True)
# remove the 0_ prefix that tsfel adds to every column name
X.columns = X.columns.str.removeprefix('0_')
return Xif os.path.exists(waveform_path):
import time
n_subset = min(200, ndata)
subset_rng = np.random.default_rng(42)
subset_idx = subset_rng.choice(ndata, size=n_subset, replace=False)
start = time.time()
X = calculate_features(Z, subset_idx, df, cfg)
end = time.time()
print(f'Time taken to calculate features for {n_subset} waveforms: '
f'{end - start:.2f} seconds')
else:
print('Waveform file not found: skipping feature extraction.')Extracting features from sample 0/200
Extracting features from sample 20/200
Extracting features from sample 40/200
Extracting features from sample 60/200
Extracting features from sample 80/200
Extracting features from sample 100/200
Extracting features from sample 120/200
Extracting features from sample 140/200
Extracting features from sample 160/200
Extracting features from sample 180/200
Time taken to calculate features for 200 waveforms: 24.90 seconds
if os.path.exists(waveform_path):
display(X.head())
else:
print('Waveform file not found: no features to show.')Nettoyage de la nouvelle table de données¶
Retrait des échantillons contenant des NaN ou des infinis.
if os.path.exists(waveform_path):
print(f'no of samples in the dataframe: {X.shape[0]}')
# Replace infinities with NaN
new_X = X.replace([np.inf, -np.inf], np.nan, inplace=False)
# Drop rows with NaN values
new_X = new_X.dropna(inplace=False)
print(f'no of samples in the new dataframe: {new_X.shape[0]}')
else:
print('Waveform file not found: skipping cleanup.')no of samples in the dataframe: 200
no of samples in the new dataframe: 200
Explorer la corrélation entre caractéristiques¶
if os.path.exists(waveform_path):
import seaborn as sns
# Compute pairwise correlation of columns
corr_matrix = new_X.drop('source_type', axis=1).corr().abs()
# Mask the upper triangle (the matrix is symmetric)
mask = np.triu(np.ones_like(corr_matrix, dtype=bool))
plt.figure(figsize=(15, 10))
sns.heatmap(corr_matrix, mask=mask, cmap='coolwarm', vmax=1, vmin=-1,
center=0, square=True, linewidths=.5, annot=False)
plt.show()
else:
print('Waveform file not found: skipping correlation heatmap.')
Des blocs de caractéristiques fortement corrélées sont redondants : ils portent la même information. La réduction de dimension (leçon suivante) ou la sélection de caractéristiques permet de les élaguer.
Explorer l’espace des caractéristiques pour la classification¶
Nous traçons ici les distributions de caractéristiques choisies parmi les classes. Une caractéristique est utile à la classification quand ses distributions se séparent d’une classe à l’autre.
def plot_feature_histograms(new_X, feature):
import seaborn as sns
# Get unique source types
source_types = new_X['source_type'].unique()
# colorblind-friendly palette
colors = sns.color_palette('colorblind')
for i, source_type in enumerate(source_types):
# Select data for this source type
data = new_X[new_X['source_type'] == source_type][feature]
# Plot histogram for this source type
plt.hist(np.log10(data), color=colors[i % len(colors)], alpha=0.5,
label=source_type)
plt.title(f'np.log10({feature})')
plt.xlabel(f'np.log10({feature})')
plt.ylabel('Count')
plt.legend()
plt.show()if os.path.exists(waveform_path):
feature = 'Area under the curve'
plot_feature_histograms(new_X, feature)
feature = 'Kurtosis'
plot_feature_histograms(new_X, feature)
feature = 'Spectral variation'
plot_feature_histograms(new_X, feature)
else:
print('Waveform file not found: skipping feature histograms.')
/Users/marinedenolle/Dropbox/CLASSES/ESS490/curriculum-book/.pixi/envs/default/lib/python3.12/site-packages/pandas/core/arraylike.py:402: RuntimeWarning: invalid value encountered in log10
result = getattr(ufunc, method)(*inputs, **kwargs)


Exercice étudiant¶
- Laquelle de ces caractéristiques sera la meilleure pour classer les types d’événements ?
Appuyez-vous sur le canevas ci-dessous. L’idée : une caractéristique sépare bien deux classes quand l’écart entre les moyennes de classe est grand comparé à la dispersion à l’intérieur de chaque classe. Classez les caractéristiques selon ce critère.
if os.path.exists(waveform_path):
# Answer scaffold: rank features by class separation.
# Step 1: group the features by class.
grouped = new_X.groupby('source_type')
# Step 2: for each feature, compute the spread of the class means
# divided by the mean within-class standard deviation.
feature_cols = new_X.columns.drop('source_type')
class_means = grouped[feature_cols].mean()
class_stds = grouped[feature_cols].std()
separation = class_means.std(axis=0) / class_stds.mean(axis=0)
# Step 3: sort and show the 10 most discriminative features.
top10 = separation.sort_values(ascending=False).head(10)
print(top10)
# Step 4 (your turn): plot histograms of the top-ranked features with
# plot_feature_histograms(new_X, feature) and check the separation by eye.
# Step 5 (your turn): does the ranking change if you normalize each
# feature first? Try (new_X - mean) / std before Step 2.
else:
print('Waveform file not found: skipping student exercise scaffold.')ECDF_9 inf
ECDF_2 inf
Entropy inf
ECDF_6 inf
ECDF_5 inf
ECDF_4 inf
Spectral variation 2.033088
MFCC_0 1.909093
MFCC_3 1.392157
LPCC_11 1.376107
dtype: float64
Auto-vérification
separationest uneSeriesavec une valeur positive par colonne de caractéristique (156 ici) ;class_meansetclass_stdsont une ligne par classe (5) et une colonne par caractéristique.- Certaines entrées valent
inf: ces caractéristiques (plusieursECDF_*,Entropy) sont constantes à l’intérieur de chaque classe, donc l’écart-type intra-classe est nul. Écartez-les — une caractéristique constante ne porte aucune information par événement. - Parmi les valeurs finies, les caractéristiques spectrales (
Spectral variation,MFCC_*,LPCC_*) arrivent en tête, avec des séparations de l’ordre de 1 à 2. - Étape 4 : les histogrammes des caractéristiques les mieux classées montrent des distributions de classes qui ne se recouvrent que partiellement ; une caractéristique mal classée donne des histogrammes presque identiques pour toutes les classes.
- Étape 5 : le classement ne change pas. Remettre une caractéristique à l’échelle multiplie par le même facteur la dispersion de ses moyennes de classe et sa dispersion intra-classe : leur rapport — et donc le classement — reste inchangé.
- Ni, Y., Hutko, A., Skene, F., Denolle, M., Malone, S., Bodin, P., Hartog, R., & Wright, A. (2023). Curated Pacific Northwest AI-ready Seismic Dataset. Seismica, 2(1). 10.26443/seismica.v2i1.368