ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TIO 측정 데이터 분석 - mat 파일을 데이터프레임으로 변환하기 (mdf2df)
    application 2025. 9. 1. 12:26

    이 글은 Jupyter 노트북으로 작성하였습니다. 

    tio_measure_data_analysis - how-to.ipynb
    9.08MB

     

    시작하기 전에

    • TIO로 디지털 신호와 아날로그 신호를 측정했다.
    • TIO는 CAN으로 통신하는 Tosun의 Input Output 장치이다. Digital IO, Analog IO, 저항, 릴레이 모듈들이 있다. 모듈들을 아래 사진처럼 TSMaster와 CAN으로 연결한다.

    TIO 모듈들과 TSMaster를 CAN 으로 연결한다.

     

     

    TIO의 Digitial Input/Output, Analog Input/Output 모듈

     

    • 위 실험 셋업에서 CAN 메시지로 Digital Output, Analog Output을 제어하며, Digitial Input, Aanlog Input 메시지로 수신하여 blf 파일로 저장하였다.
    • 제어 요청과 디지털, 아날로그 신호의 실제 값 사이에 얼마의 시간 지연이 있는지, 신호는 얼마나 정확한지 분석하고자 한다.
    • 이 분석을 TSMaster의 그래픽스 창에서 시각적으로 할 수 있다. 데이터를 처리하는 프로그램을 작성하여 할 수도 있다. 여기서는 파이썬으로 프로그램을 작성하여 해보려고 한다.
    • 분석을 위해서 blf 파일에서 필요한 신호들을 추출여야 한다. blf는 메시지 단위로 데이터를 저장한다. 이는 개별 신호 분석에 불편하다. TSMaster의 Log Converter 기능을 이용하여 신호 단위로 데이터를 저장하는 mat 파일로 변환하였다.
    • mat 파일을 데이터 분석에 많이 사용되는 파이썬 pandas 모듈의 df(DataFrame)으로 변환한다.
    • 여기서는 mat to df 방법을 설명한다. 이 방법을 mat 파일을 데이터프레임으로 변환하고 feather 파일로 저장하기 :: hsl's tsmaster 사용기 에서 설명한 적이 있다. 신호들의 타임스탬프들을 동일하게 만들기 위해서 데이터 처리한 부분이 있다. 이 부분을 pandas의 resample 기능을 이용하여 쉽게 할 수 있다는 것을 배웠다. 이 방법을 소개한다.

     

     

    개요

    • 한 신호를 df로 변환하는 방법
    • 타임스탬프가 동일한 경우, 신호들을 한 df로 변환하는 방법
    • 타임스탬프가 다른 경우, 신호들을 한 df로 변환하는 방법
      • 타임스탬프가 동일한 신호들끼리 한 df로 만들기
      • df들 합치기
      • 결측치 처리
      • Resample
    # import
    
    from pathlib import Path    # 파일 경로를 다루기 위한 모듈
    import h5py                 # .mat 파일을 읽기 위한 모듈
    import pandas as pd         # 데이터 분석을 위한 모듈
    import numpy as np          # 데이터 분석을 위한 모듈
    import plotly.express as px # 데이터 시각화를 위한 모듈
    from plotly.subplots import make_subplots # 데이터 시각화를 위한 모듈
    # 블로그에 올리기 위해 plotly renderer의 설정이 필요하다. 
    
    import plotly.io as pio
    pio.renderers.default = "notebook_connected" 
    # constant
    
    # 데이터 파일 저장 경로
    k_dir_data = Path('data')
    k_dir_data.mkdir(exist_ok=True)
    
    # data 디렉토리의 첫 번째 .mat 파일을 대상으로 한다.(파일 이름을 기억하기가 귀찮다.^^)
    mats = list(Path(k_dir_data).rglob('*.mat'))
    
    if len(mats) == 0:
        print('No mat files found in data directory')
    else:
        print('mats =')
        for i, mat in enumerate(mats):
            print(f'{i}: {mat.name}')
    mats =
    0: tio_2025_08_28_17_44_55.MAT
    # data 디렉토리의 첫 .mat 파일을 대상으로 한다.
    i_mat = 0
    mat = mats[i_mat] 
    print(f'{mat = }')
    
    mat = WindowsPath('data/tio_2025_08_28_17_44_55.MAT')

     

     

    mat 파일을 읽어서 데이터프레임으로 변환하는 방법 찾기

    • digital output Request, digitial input, analog output Request, analog input 신호들을 pandas df(DataFrame)으로 만든다.
    • 메시지별로 전송 주기가 다르다. 즉, 타임스탬프가 다르다. 타임스탬프를 맞춰야 계산이 편리하다.
    • 메시지별로 df를 만들고, 데이터프레임들을 합치고, 합치는 과정에서 발생하는 결측치를 처리하고, Resample을 한다.
    # mat 파일을 읽는다.
    trace = h5py.File(mat, 'r')
    
    # 파일에 포함된 신호(키)들을 확인한다.
    signals = list(trace.keys())
    for signal in signals:
        print(signal)
    
    CAN1_TIO9011_N01_REP_DI_LEVEL_TIO9011_CH7_Level
    CAN1_TIO9011_N01_REP_DI_LEVEL_TIO9011_CH9_Level
    CAN1_TIO9011_N01_SET_DO_LEVEL_REQ_TIO9011_Channel_Index
    CAN1_TIO9011_N01_SET_DO_LEVEL_REQ_TIO9011_IO_LEVEL
    CAN1_TIO9015_N02_REP_AI_CHN1_4_Voltage_CH2
    CAN1_TIO9015_N02_REP_AO_CHN1_4_Voltage_CH1
    CAN1_TIO9015_N02_SET_AO_VALUE_REQ_A_Value
    CAN1_TIO9015_N02_SET_AO_VALUE_REQ_Channel

    노트

    • 신호의 정의는 아래와 같다.

     

    디지털 신호

    • 나는 디지털 아웃풋으로 CH7을 디지털 인풋으로 CH9를 사용했다.
    • CAN1_TIO9011_N01_SETDO_LEVELREQTIO9011Channel_Index:
      • SET 메시지에 있는 Digitial Output 채널 지정
      • SET 메시지는 TSMaster가 TIO로 필요할 때마다 전송한다.
      • df 컬럼명: do_ch_set
    • CAN1_TIO9011_N01_SETDO_LEVELREQTIO9011_IOLEVEL:
      • SET 메시지에 있는 Digitial Output 요청값
      • SET 메시지는 TSMaster가 TIO로 필요할 때마다 전송한다.
      • df 컬럼명: do_req_set
    • CAN1_TIO9011_N01_REPDI_LEVEL_TIO9011_CH9Level: do_act_req
      • REP 메시지에 있는 Digital Input 신호값
      • REP 메시지는 TIO가 주기적으로 전송한다.
      • REP 메시지의 전송 주기는 SET 메시지로 설정 가능하다.
      • df 컬럼명: do_act_req
    • CAN1_TIO9011_N01_REPDI_LEVEL_TIO9011_CH7Level:
      • REP 메시지에 있는 Digital Output의 요청값 <-- 아웃풋으로 설정된 경우, Input 값이 아닌 Output 요청이다.
      • REP 메시지는 TIO가 주기적으로 전송한다.
      • REP 메시지의 전송 주기는 SET 메시지로 설정 가능하다.
      • df 컬럼명: do_req_rep

     

    아날로그 신호

    • 나는 아날로그 아웃풋으로 CH1을 아날로그 인풋으로 CH2를 사용했다.
    • CAN1_TIO9015_N02_SETAO_VALUEREQ_Channel:
      • SET 메시지에 있는 Analog Output 채널 지정
      • SET 메시지는 TSMaster가 TIO로 필요할 때마다 전송한다.
      • df 컬럼명: ao_ch_set
    • CAN1_TIO9015_N02_SETAO_VALUEREQAValue:
      • SET 메시지에 있는 Analog Output 요청값
      • SET 메시지는 TSMaster가 TIO로 필요할 때마다 전송한다.
      • df 컬럼명: ao_req_set
    • CAN1_TIO9015_N02_REP_AI_CHN1_4_Voltage_CH2:
      • REP 메시지에 있는 Analog Input 신호값
      • REP 메시지는 TIO가 주기적으로 전송한다.
      • REP 메시지의 전송 주기는 SET 메시지로 설정 가능하다.
      • df 컬럼명: ai_act_set
    • CAN1_TIO9015_N02_REP_AO_CHN1_4_Voltage_CH1:
      • REP 메시지에 있는 Analog Output의 요청값 <-- 아웃풋으로 설정된 경우, Input 값이 아닌 Output 요청이다.
      • REP 메시지는 TIO가 주기적으로 전송한다.
      • REP 메시지의 전송 주기는 SET 메시지로 설정 가능하다.
      • df 컬럼명: ao_req_rep

     

     

    한 신호를 df로 변환하는 방법

    do_req_rep = trace['CAN1_TIO9011_N01_REP_DI_LEVEL_TIO9011_CH7_Level'][:]
    df = pd.DataFrame(do_req_rep)
    df = df.T   # 신호가 1개일 경우, df를 Transpose 해야 한다. 
    df.columns = ['ts', 'do_req_rep']   # 열 이름 설정한다.
    df.head(3)
      ts do_req_rep
    0 26.998460 1.0
    1 27.008549 1.0
    2 27.018482 1.0

    발견 - trace[신호]와 trace[신호][:]의 차이

    • trace[신호][:]라고 해야 데이터를 볼 수 있다.
    do_req_rep = trace['CAN1_TIO9011_N01_REP_DI_LEVEL_TIO9011_CH7_Level']
    do_req_rep
    <HDF5 dataset "CAN1_TIO9011_N01_REP_DI_LEVEL_TIO9011_CH7_Level": shape (2, 2966), type "<f8">
    do_req_rep = trace['CAN1_TIO9011_N01_REP_DI_LEVEL_TIO9011_CH7_Level'][:]
    do_req_rep
    array([[26.99846 , 27.008549, 27.018482, ..., 56.628256, 56.638281,
            56.648308],
           [ 1.      ,  1.      ,  1.      , ...,  1.      ,  1.      ,
             1.      ]])

     

     

    타임스탬프가 동일한 경우, 신호들을 한 df로 변환하는 방법

    # 두 신호를 각각 numpy array로 변환한다. 
    do_req_rep = trace['CAN1_TIO9011_N01_REP_DI_LEVEL_TIO9011_CH7_Level'][:]
    do_act_req = trace['CAN1_TIO9011_N01_REP_DI_LEVEL_TIO9011_CH9_Level'][:]
    # 신호의 첫 번째 (인텍스 0) 컬럼은 타임스탬프이다.
    do_req_rep[0]
    array([26.99846 , 27.008549, 27.018482, ..., 56.628256, 56.638281,
           56.648308])
    # 신호의 두 번째 (인덱스 1) 컬럼은 신호의 값이다.
    do_req_rep[1]
    array([1., 1., 1., ..., 1., 1., 1.])
    # timestamp가 동일한지 확인한다.
    # 동일하지 않으면, 하나의 df로 만들 수 없다.
    
    # shape을 비교한다.
    print(f'{do_req_rep.shape = } == {do_act_req.shape = }')
    print(f'{(do_req_rep.shape == do_act_req.shape) = }')
    
    # 값을 비교한다.
    try:
        print(f'{(do_req_rep[0] == do_act_req[0]).all() = }')
    except ValueError as e:
        print("ValueError:", e)
    do_req_rep.shape = (2, 2966) == do_act_req.shape = (2, 2966)
    (do_req_rep.shape == do_act_req.shape) = True
    (do_req_rep[0] == do_act_req[0]).all() = np.True_
    # 두 신호를 하나의 DataFrame으로 합치기
    df = pd.DataFrame(
        {
            'do_req_rep': do_req_rep[1],    # 신호를 컬럼으로 한다.
            'do_act_req': do_act_req[1]     # 신호를 컬럼으로 한다.
        },
        index=do_req_rep[0] # 타임스탬프를 인덱스로 한다.
    )
    
    df.head(3)
      do_req_rep do_act_req
    26.998460 1.0 1.0
    27.008549 1.0 1.0
    27.018482 1.0 1.0

     

     

    타임스탬프가 다른 경우, 신호들을 한 df로 변환하는 방법

    # 두 신호를 각각 numpy array로 변환
    do_req_set = trace['CAN1_TIO9011_N01_SET_DO_LEVEL_REQ_TIO9011_IO_LEVEL'][:]
    do_act_req = trace['CAN1_TIO9011_N01_REP_DI_LEVEL_TIO9011_CH9_Level'][:]
    
    # timestamp가 동일한지 확인한다.
    # 동일하지 않으면, 하나의 df로 만들 수 없다.
    
    print(f'{do_req_set.shape = } {do_act_req.shape = }')
    print(f'{(do_req_set.shape == do_act_req.shape) = }')
    
    try:
        print(f'{(do_req_set[0] == do_act_req[0]).all() = }')
    except ValueError as e:
        print("ValueError:", e)
    do_req_set.shape = (2, 44) do_act_req.shape = (2, 2966)
    (do_req_set.shape == do_act_req.shape) = False
    ValueError: operands could not be broadcast together with shapes (44,) (2966,) 
    # 두 신호를 각각의 DataFrame으로 만든다.
    df_do_req_set = pd.DataFrame(do_req_set.T, columns=['ts', 'do_req_set'])    # 신호가 1개의 경우 Transpose를 해야한다.
    df_do_act_req = pd.DataFrame(do_act_req.T, columns=['ts', 'do_act_req'])    # 신호가 1개의 경우 Transpose를 해야한다.
    print(df_do_req_set.ts.diff().describe())
    df_do_req_set.head(3)
    count    43.000000
    mean      0.500017
    std       0.000298
    min       0.499481
    25%       0.499767
    50%       0.499997
    75%       0.500323
    max       0.500507
    Name: ts, dtype: float64
      ts do_req_set
    0 34.738190 0.0
    1 35.238565 1.0
    2 35.738881 0.0
    print(df_do_act_req.ts.diff().describe())
    df_do_act_req.head(3)
    count    2965.000000
    mean        0.010000
    std         0.000156
    min         0.008102
    25%         0.009971
    50%         0.009979
    75%         0.010027
    max         0.012109
    Name: ts, dtype: float64
      ts do_act_req
    0 26.998460 1.0
    1 27.008549 1.0
    2 27.018482 1.0
    df_do_req_set['ts_diff'] = df_do_req_set['ts'].diff()
    px.line(
        df_do_req_set, 
        x='ts', 
        y='ts_diff', 
        title='ts diff - df_do_req_set', 
        range_y=[df_do_req_set.ts_diff.mean() * 1.05, df_do_req_set.ts_diff.mean() * 0.95],
        width= 600,
        height= 400,
    ).show()
    df_do_req_set.ts_diff.describe()
    

    do_req_set 신호의 전송 주기는 500msec로 일정하다.

    count    43.000000
    mean      0.500017
    std       0.000298
    min       0.499481
    25%       0.499767
    50%       0.499997
    75%       0.500323
    max       0.500507
    Name: ts_diff, dtype: float64
    df_do_act_req['ts_diff'] = df_do_act_req['ts'].diff()
    px.line(
        df_do_act_req, 
        x='ts', 
        y='ts_diff', 
        title='ts diff - df_do_act_req', 
        range_y=[df_do_act_req.ts_diff.mean() * 1.2, df_do_act_req.ts_diff.mean() * 0.8],
        width= 600,
        height= 400,
    ).show()
    df_do_act_req.ts_diff.describe()
    

     

    do_act_req 신호의 전송 주기는 10msec로 일정하다.

    count    2965.000000
    mean        0.010000
    std         0.000156
    min         0.008102
    25%         0.009971
    50%         0.009979
    75%         0.010027
    max         0.012109
    Name: ts_diff, dtype: float64

    발견

    • 통계치의 mean을 보면,
      • df_do_req_set 타임스탬프의 주기는 500msec이다.
      • df_do_act_req 타임스탬프의 주기는 10msec이다.
    • 통계치의 std, min, max를 보면, 메시지의 전송 주기가 매우 일정하다.
    • 그래프를 보면, do_act_req는 do_req_set에 비해 들쭉날쭉하는 것으로 보인다. do_act_req의 경우, 메시지 전송 주기가 10msec로 do_req_set의 500msec에 비해 50배 짧다. 그래서 전송 주기가 약간만 늘어나거나 줄어들어도 크게 변동한 것처럼 보인다.

     

    df 합치기

    • 두 df들을 합혀서(merge) df_digital을 만든다.
    • 합칠 때, 결측치 (missing value)가 생긴다.
    • df_digital을 ts로 정렬한다.
    df_digital = pd.merge(df_do_req_set, df_do_act_req, on='ts', how='outer', suffixes=('_do_req', '_do_act_req'))
    df_digital.tail(3)  # 결측치 (Missing value)가 있는 것을 볼 수 있다.
      ts do_req_set ts_diff_do_req do_act_req ts_diff_do_act_req
    3007 56.628256 NaN NaN 1.0 0.009956
    3008 56.638281 NaN NaN 1.0 0.010025
    3009 56.648308 NaN NaN 1.0 0.010027

     

    결측치 처리

    • ffill() (forward fill)을 이용하여 결측치를 채운다.
    • 디지털 신호의 경우, 이번 메시지에서 다음 메시지까지 값이 일정하다.
    # 결측치에 대해 ffill을 실시한다.
    df_digital = df_digital.ffill()
    
    # ffill을 통해 결측치를 채운 결과를 확인할 수 있다.
    # 결측치 (Missing value)가 해결 되었다. 
    # 단, df 앞쪽에 있는 결측치는 해결되지 않았다. ffill은 이전 값을 이용해서 아래 결측치를 채우는 방식이기 때문이다.
    df_digital.tail(3)
      ts do_req_set ts_diff_do_req do_act_req ts_diff_do_act_req
    3007 56.628256 1.0 0.500003 1.0 0.009956
    3008 56.638281 1.0 0.500003 1.0 0.010025
    3009 56.648308 1.0 0.500003 1.0 0.010027

     

    ts_diff를 확인

    • 두 df가 합쳐지는 과정에서 서로 주기가 다른 타임스탬프들이 합쳐졌다.
    • 타임스탬프 사이의 간격이 각각의 df에서 처럼 일정하지 않다.
    df_digital['ts_diff'] = df_digital['ts'].diff()
    px.line(
        df_digital, 
        x='ts', 
        y='ts_diff', 
        title='ts diff - df들을 합친 후',
        width=600,
        height=400,
    ).show()
    df_digital.ts_diff.describe()
    

     

    df들을 합친 후 타임스탬프의 주기가 들쭉날쭉하다.

    count    3009.000000
    mean        0.009854
    std         0.001141
    min         0.000090
    25%         0.009963
    50%         0.009979
    75%         0.010025
    max         0.012109
    Name: ts_diff, dtype: float64

    발견 - ts_diff

    • 두 df 합쳐지면서 주기가 다른 타임스탬프들이 합쳐진다.
    • 일정하던 ts_diff가 타임스탬프가 합쳐지는 곳 인근에서 인접하게 된다.
    • 위 현상이 있은 후에 간혹 주기가 길어졌다가 짧아지는 현상이 있다.
    • 메시지가 등간격 전송되는 경우, 어떤 이유에서건 주기가 한 번 길어진 후에 곧바로 한 번 짧아지는 현상이 나타나는 것은 당연하다. 이번 전송에서 주기가 늘어난 만큼 다음 주기에서 주기가 짧아져야 주기가 등간격이 된다.
    • 애초에 왜 주기가 길어졌을까? 여러 메시지들이 전송되는 주기가 겹쳤을 때, 해당 메시지의 우선 순위가 낮아서 그렇지 않을까 추측한다.
    • 주기를 일정하게 하면 좋겠다. pandas의 resample 기능을 이용한다.

     

    Resample

    • 들쭉날쭉한 타임스탬프의 주기를 일정하게 변환한다.
    • pandas의 resample 기능을 이용한다.
    # 기존 데이터를 유지하기 위해서 복사한다.
    df = df_digital.copy()
    
    # ts가 초 단위 실수다. 
    # 타임스탬프 사이의 간격을 고려하여 보간 (interpolate)하려면, 
    # ts를 pandas의 TimedeltaIndex 형식으로 변환해야 한다.
    df['ts_timedelta'] = pd.to_timedelta(df['ts'], unit='s')
    
    # 0.01초(10ms) 간격으로 리샘플링 후 보간한다.
    df = df.resample('10ms', on='ts_timedelta').mean().interpolate()
    
    # 타임스탬프 사이의 간격이 일정한지 확인한다.
    df['ts_diff'] = df.index.diff() # 현재 index가 ts이다.
    df['ts_diff'] = df['ts_diff'].dt.total_seconds() # ts_diff를 초 단위로 변환한다.
    
    px.line(
        df, 
        x='ts', 
        y='ts_diff', 
        title='ts diff - after resampling', 
        range_y=[df.ts_diff.mean()*0.5, df.ts_diff.mean()*1.5],
        width=600,
        height=400
    ).show()
    df.ts_diff.describe()

     

    resample 후 타임스탬프 주기는 10msec로 일정하다.

    count    2.964000e+03
    mean     1.000000e-02
    std      1.735016e-18
    min      1.000000e-02
    25%      1.000000e-02
    50%      1.000000e-02
    75%      1.000000e-02
    max      1.000000e-02
    Name: ts_diff, dtype: float64

    발견 - Resample

    • 타임스탬프의 간격이 일정하게 (10msec)로 변환되었다.

     

    신호 변환 결과 확인

    # 'do_req_set', 'do_act_req'를 각각의 2 x 1 그래프에 그린다.
    fig = make_subplots(rows=2, cols=1, shared_xaxes=True)
    
    fig.add_trace(px.line(df, x='ts', y='do_req_set', title='DO Request').data[0], row=1, col=1)
    fig.add_trace(px.line(df, x='ts', y='do_act_req', title='DI Actual').data[0], row=2, col=1)
    fig.update_layout(height=400, width=600, title_text="Digital Output Request and Digital Input Actual over Time")
    
    fig.show()

     

    Resample 후 디지털 출력 요청과 실제 디지털 입력 신호. 틱 바이 틱으로 봐야한다.

    # 신호 사이의 관계를 scatter 그래프의 매트릭스로 확인한다.
    import plotly.figure_factory as ff
    
    fig = ff.create_scatterplotmatrix(
        df[['do_req_set', 'do_act_req']], 
        diag='histogram', 
        title='Digital Output Request vs Digital Input Actual', 
        width=600, 
        height=600
    )
    fig.show()

    resample 과정에서 신호 값을 보간(interpolate)한다. 디지털 신호인데 0, 1이 아닌 값들이 있다.

     

    발견 - 신호 변환 결과 확인

    • 리샘플링 중에 보간(interpolate)을 했다.
    • 디지털 신호인데 0, 1이 아닌 값들이 생겼다. 보간 때문에 생긴 것이다.

     

    Resample 2nd Trial

    # 기존 데이터를 유지하기 위해서 복사한다.
    df = df_digital.copy()
    
    # ts가 초 단위 실수다. 
    # 타임스탬프 사이의 간격을 고려하여 보간 (interpolate)하려면, 
    # ts를 pandas의 TimedeltaIndex 형식으로 변환해야 한다.
    df['ts_timedelta'] = pd.to_timedelta(df['ts'], unit='s')
    
    # 0.01초(10ms) 간격으로 리샘플링 후 보간한다.
    df = df.resample('10ms', on='ts_timedelta').mean().interpolate()
    
    # do_req_set은 정수로 반올림한다.
    # do_act_req는 내림한다.
    # 서로 방법을 달리하는 이유는 다양한 방법이 있다는 것을 보이기 위해서다. 
    df['do_req_set'] = df['do_req_set'].round()
    df['do_act_req'] = df['do_act_req'].apply(np.floor)
    
    # 타임스탬프 사이의 간격이 일정한지 확인한다.
    df['ts_diff'] = df.index.diff()
    df['ts_diff'] = df['ts_diff'].dt.total_seconds()
    
    px.line(
        df, 
        x='ts', 
        y='ts_diff', 
        title='ts diff after resampling 2nd trial', 
        range_y=[df.ts_diff.mean()*0.5, df.ts_diff.mean()*1.5],
        width=600,
        height=400
    ).show()

     

    다시 resampling 한다. 타임스탬프의 주기는 10msec로 일정하다.

    # 'do_req_set', 'do_act_req'를 각각의 2 x 1 그래프에 그린다.
    fig = make_subplots(rows=2, cols=1, shared_xaxes=True)
    
    fig.add_trace(px.line(df, x='ts', y='do_req_set', title='DO Request').data[0], row=1, col=1)
    fig.add_trace(px.line(df, x='ts', y='do_act_req', title='DI Actual').data[0], row=2, col=1)
    fig.update_layout(height=400, width=600, title_text="Digital Output Request and Digital Input Actual over Time")
    
    fig.show()

     

    다시 resample 후 디지털 출력 요청과 실제 디지털 입력 신호. 틱 바이 틱으로 봐야한다.

    # 신호 사이의 관계를 scatter 그래프의 매트릭스로 확인한다.
    fig = ff.create_scatterplotmatrix(
        df[['do_req_set', 'do_act_req']], 
        diag='histogram', 
        title='Digital Output Request vs Digital Input Actual', 
        width=600, 
        height=600
    )
    fig.show()

    resample 과정에서 신호 값을 보간(interpolate) 후 반올림 혹은 내림을 하여 0, 1이 아닌 값들을 0, 1로 대체하였다.

     

    발견 - Resample 2nd Trial

    • round(), floor(), ceil()로 데이터를 정수화처리하여 디지털 신호에 0, 1이 아닌 값들을 없앴다.
    • do_req_set 히스토그램을 보면, 0, 1의 도수 차이가 작다.
    • do_act_req 히스토그램을 보면, 0, 1의 도수 차이가 크다.
    • 샘플링과 정수화가 do_act_req에 불리(?)하게 작동했다. 불리하지 않게 하는 방법이 있을까? 향후 생각해볼 이슈인가?

     

    df를 feather 파일로 저장

    • 위 변환을 다시 할 필요가 없도록 df를 파일로 저장한다.
    • feather 포맷으로 저장한다.
    
    # df 저장할 feather 파일 이름을 준비한다.
    feather = k_dir_data/(mat.stem + '_digital.feather')
    print(f'{feather = }')
    
    # df를 feather 파일로 저장한다.
    df.to_feather(feather)
    
    # df를 feather 파일로부터 읽어온다.
    df = pd.read_feather(feather)
    
    # df를 확인한다.
    df.sample(5)
    feather = WindowsPath('data/tio_2025_08_28_17_44_55_digital.feather')
      ts do_req_set ts_diff_do_req do_act_req ts_diff_do_act_req ts_diff
    ts_timedelta            
    0 days 00:00:52.638460 52.648286 1.0 0.499997 1.0 0.009953 0.01
    0 days 00:00:55.728460 55.738317 1.0 0.500504 1.0 0.009972 0.01
    0 days 00:00:30.978460 30.983470 NaN NaN 1.0 0.010010 0.01
    0 days 00:00:32.998460 33.003449 NaN NaN 1.0 0.010010 0.01
    0 days 00:00:37.498460 37.508338 1.0 0.500507 1.0 0.009972 0.01

     

     

    결론

    • mat 파일의 신호들을 pandas의 df(DataFrame)으로 만드는 방법을 설명하였다.
    • 각 신호는 자신의 타임스탬프를 갖고 있다. 정확히는 CAN 메시지의 타임스탬프이다. 같은 메시지에 속한 신호들은 같은 타임스탬프를 갖는다.
    • 측정 주기가 다른 신호들의 타임스탬프들은 당연히 다르다.
    • 먼저, 타임스탬프가 같은 신호들끼리 df를 만들 수 있다.
    • 여러 df를 합쳐(merge)서 한 df로 만들 수 있다.
      • 이 경우, 타임스탬프의 주기가 일정하지 않다.
      • 결측치가 발생한다.
    • 결측치를 처리해야 한다.
      • 디지털 신호의 경우 ffill()을 사용할 수 있다.
      • 아날로그 신호의 경우 interpolate()을 사용할 수 있다. <-- 별도 포스트에서 설명할 예정이다.
    • pandas의 resample 기능을 이용하여, 타임스탬프의 주기를 일정하게 할 수 있다.
    • resample을 할 때 보간 (interpolate)을 하면, 디지털 신호에 0, 1이 아닌 값이 생길 수 있다. 이 값들은 반올림 등으로 처리할 수 있다.
    • 위 과정을 반복할 필요 없도록 df를 파일로 저장하여 사용한다.

     

    목차 :: hsl's tsmaster 사용기     

    mat 파일을 데이터프레임으로 변환하고 feather 파일로 저장하기 :: hsl's tsmaster 사용기