识别有噪声的数据向量斜率变化开始的函数

问题描述 投票:0回答:1

我需要处理实验数据。该实验是将一块磁铁穿过 2 个线圈,一个在下落开始时,一个在下落结束时,然后根据差异测量物体(当我使用不同线圈之间的高度差重复该过程时),为此我需要识别有趣的事情真正发生的时刻和事件结束的时刻,并将它们与事件之前/之后的噪音区分开来。我上传了数据here(那里有几个实验的几个文件)。所有文件中的所有数据通常都是相同的,按原样绘制它会生成一种“高斯/洛伦兹导数形式”。其中有三个相关列:t 代表时间,X 是第一个线圈的电压数据,Y 是第二个线圈的电压数据。 原始数据图如下所示(减去两个红点):

我正在寻找一种方法来自动从数据中提取这两个红点的位置。最初的计划是两次导出数据,二阶导数上升到零以上或下降到零的时刻就是我正在寻找的图表中的两个红点。然而,数据不干净并且有很多噪音,所以二阶导数是一团糟,并且在主要事件之前的边缘绝对不为零。

经过反复试验,这是我想出的最好的办法。它确实适用于某些 Y 向量,但不适用于其他向量,并且绝对不适用于 X。

import numpy as np import matplotlib.pyplot as plt from matplotlib import gridspec import scipy from scipy.signal import argrelmax from scipy.ndimage.filters import gaussian_filter1d from scipy.interpolate import UnivariateSpline from whittaker_eilers import WhittakerSmoother from scipy.signal import savgol_filter def importData(filename): # use skiprows to skip the scope setup information and header # usecols column numbers starting from 0 t, v1, v2 = np.loadtxt(filename, delimiter=',', skiprows=47, usecols=[1,2,3]).T return t, v1, v2 #%% t,X,Y= importData(r"C:\Users\IMOE001\Desktop\inductance\15cm_10N.csv") whittaker_smoother =WhittakerSmoother(lmbda=30, order=2, data_length=len(t)) nX=gaussian_filter1d(Y, 2, order=0) nX=np.array(whittaker_smoother.smooth(nX)) dX= np.gradient(nX,t) ndX=np.array(whittaker_smoother.smooth(dX)) ndX=gaussian_filter1d(ndX,2, order=0) c1ndX=ndX/np.abs(np.mean(ndX)) c2ndX=(c1ndX-np.mean(c1ndX))/10000 ddX=np.gradient(c2ndX,t) c1bex=ddX/np.abs(np.mean(ddX)) c2bex=(c1bex-np.mean(c1bex))/50000 bex=gaussian_filter1d(c2bex, 2, order=0) bex=np.array(whittaker_smoother.smooth(bex)) plt.figure(dpi=500) index_f=np.argmin(np.abs(bex[::-1])<5e-2) index_i=np.argmin(np.abs(bex)<5e-2) val_i=np.abs(np.mean(bex[:index_i]))*350 val_f=np.abs(np.mean(bex[len(bex)-index_f:]))*250 a= np.argmin(np.abs(bex)<val_i) b= len(bex)-np.argmin(np.abs(bex[::-1])<val_f) print(a,b,index_i,index_f)

上面的构建是这样的:

    准备(导入数据和所需功能)-第1-18行
  1. 使用高斯滤波器和 Wittaker 平滑滤波器平滑数据(我承认我不知道这两个滤波器实际上是如何工作的,也不知道它们对数据做了什么,除了改进了我的结果)。请注意,Wittaker 平滑过滤器使用的是我在网上找到的一个库,并且必须
  2. pip install

    它。 - 第 19-21 行

    
    

  3. 使用
  4. np.gradient

    导出平滑数据并进一步平滑结果,这次将结果除以结果的平均值,以使事件前后的线更接近于零。 - 第 22-25 行

    
    

  5. 将结果除以 10000,因为事件周围导数的幅度非常高,我只关心事件开始时的小幅上升和事件结束时下降到几乎为零。 - 26号线
  6. 对二阶导数重复相同的过程,但这次出于同样的原因将其除以 50000。 - 第 27-31 行
  7. 在获得可以使用的二阶导数的形式后,我从中提取了二阶导数首次超过数字 0.05 的索引,以及它持续下降到该数字以下的索引。这是一个任意数字,似乎最适合其中一个数据向量,但显然不适用于所有数据向量。那些名为
  8. index_i

    index_f
    的表示与初始尾部和最终尾部相关。 - 第 36-37 行
    
    

  9. 为了补偿该任意数字与其他数据向量的不匹配,我尝试将其用作索引来平均二阶导数的尾部,并搜索其中的索引 它超过/下降超过相应平均值的某个乘数。同样,乘数是任意的(事件后尾部为 250,事件前尾部为 350),并且主要适合一个特定数据,但不适合其余数据。那些名为
  10. a-

    的是开始索引,

    b-
    是结束索引。 - 第 38-41 行
    
    

  11. 代码打印了
  12. a

    b
    index_i
    index_f
    来帮助我了解谁更准确,但实际上没有一个更准确。 42号线
    
    

python python-3.x numpy
1个回答
0
投票
斜率变化的开始

我只关心事件开始时的小幅上升,并假设您确实关心外部拐点。通过使用更标准的 filtfilt 而不是 Whittaker 来简化:

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import scipy.signal
from zipfile import ZipFile

data_list = []

archive = ZipFile('Data package from April 5th..zip')
for file_info in archive.infolist():
    set_df = pd.read_csv(
        filepath_or_buffer=archive.open(name=file_info.filename),
        skiprows=45, usecols=['Time(s)', 'Channel1', 'Channel2'],
        index_col='Time(s)',
    )

    # Convert to a series having a multi-index over experiment, time and channel
    set_df.columns.name = 'Channel'
    set_series = set_df.stack()
    set_series = pd.DataFrame({
        'Experiment': file_info.filename.removesuffix('.csv'),
        'Voltage': set_series,
    }).set_index('Experiment', append=True)['Voltage']
    set_series.index = (
        set_series.index
        .swaplevel('Experiment', 'Time(s)')
        .swaplevel('Channel', 'Time(s)')
    )
    data_list.append(set_series)
voltage = pd.concat(data_list)

# Used prior to differentials
filter_strength = 10
filter_b = np.full(shape=filter_strength, fill_value=1/filter_strength)

for experiment, egroup in voltage.groupby('Experiment'):
    fig = plt.figure()
    fig.suptitle(experiment)

    # For each experiment, a figure having 2x2 axes: (volts, differential) by (channels 1, 2)
    ax_volts = fig.add_subplot(2, 2, 1), fig.add_subplot(2, 2, 2)
    for col in ax_volts:
        col.tick_params('x', labelbottom=False)
    ax_diffs = (
        fig.add_subplot(2, 2, 3, sharex=ax_volts[0]),
        fig.add_subplot(2, 2, 4, sharex=ax_volts[1]),
    )
    for row, label in zip(
        (ax_volts, ax_diffs),
        ('Experimental data, volts', 'Second-order differential'),
    ):
        row[0].set_ylabel(label)

    cgroup: pd.Series
    for ax_volt, ax_diff, (channel, cgroup) in zip(
        ax_volts, ax_diffs, egroup.groupby('Channel'),
    ):
        filtered = pd.Series(scipy.signal.filtfilt(
            b=filter_b, a=1, x=cgroup,
        ))
        time = cgroup.index.get_level_values('Time(s)')
        ax_volt.plot(time, cgroup, label='Voltage')
        ax_volt.plot(time, filtered, label='Filtered')
        ax_volt.set_title(channel)

        # Finite-differences second-order differential: https://stackoverflow.com/a/59095333/313768
        diff2 = filtered - 2*filtered.shift(1) + filtered.shift(2)
        ax_diff.plot(time, diff2)

        '''
        Assumed structure:
        1. a positive peak
        2. 0 (inflection point A)
        3. minimum
        4. 0 (inflection point B)
        5. maximum
        6. 0 (inflection point C)
        7. a negative peak
        '''
        i_max = diff2.argmax()
        segc = diff2.iloc[i_max:]
        i_ipc = segc[segc < 0].index[0]
        segb = diff2.iloc[:i_max]
        i_ipb = segb[segb < 0].index[-1]
        sega = segb.iloc[:i_ipb]
        i_ipa = sega[sega > 0].index[-1]

        for ax, series in zip(
            (ax_volt, ax_diff), (cgroup, diff2)
        ):
            ax.scatter(
                time[[i_ipa, i_ipc]],
                series.iloc[[i_ipa, i_ipc]],
            )

    ax_volts[1].legend()

plt.show()

这可以正确找到您拥有的每个数据集的拐点。然而,您(含糊地)暗示这不是您真正关心的。根据您设置为 10,000 的调整参数,您似乎希望一些外部点具有较小的斜率。这根本不需要差速器:

import matplotlib.pyplot as plt import numpy as np import pandas as pd from zipfile import ZipFile data_list = [] archive = ZipFile('Data package from April 5th..zip') for file_info in archive.infolist(): set_df = pd.read_csv( filepath_or_buffer=archive.open(name=file_info.filename), skiprows=45, usecols=['Time(s)', 'Channel1', 'Channel2'], index_col='Time(s)', ) # Convert to a series having a multi-index over experiment, time and channel set_df.columns.name = 'Channel' set_series = set_df.stack() set_series = pd.DataFrame({ 'Experiment': file_info.filename.removesuffix('.csv'), 'Voltage': set_series, }).set_index('Experiment', append=True)['Voltage'] set_series.index = ( set_series.index .swaplevel('Experiment', 'Time(s)') .swaplevel('Channel', 'Time(s)') ) data_list.append(set_series) voltage = pd.concat(data_list) threshold = 0.1 for experiment, egroup in voltage.groupby('Experiment'): fig = plt.figure() fig.suptitle(experiment) # For each experiment, a figure having 1x2 axes: (channels 1, 2) ax_volts = fig.add_subplot(1, 2, 1), fig.add_subplot(1, 2, 2) ax_volts[0].set_ylabel('Volts') cgroup: pd.Series for ax_volt, (channel, cgroup) in zip( ax_volts, egroup.groupby('Channel'), ): time = cgroup.index.get_level_values('Time(s)') ax_volt.plot(time, cgroup, label='Voltage') ax_volt.set_title(channel) iv_max = cgroup.argmax() iv_min = cgroup.argmin() v_thresh_hi = threshold*cgroup.iloc[iv_max] v_thresh_lo = threshold*cgroup.iloc[iv_min] i_hi = cgroup.iloc[:iv_max].searchsorted(v_thresh_hi) i_lo = cgroup.iloc[iv_min:].searchsorted(v_thresh_lo) + iv_min ax_volt.scatter( time[[i_hi, i_lo]], cgroup.iloc[[i_hi, i_lo]], c='red', label='threshold', ) ax_volts[1].legend() plt.show()

© www.soinside.com 2019 - 2024. All rights reserved.