92943


Pythonで数値解析のソフトウェアを作成しているのですが、処理に時間が掛かっており高速化したいと考えています。

cProfileで解析したところ、以下のintegrate()とscipyのinterp1dという2つの関数で全体の約47%の処理時間を占めていることが分かりました（total:
54.4 s, integrate: 14.7 s, interp1d: 11.2 s）。

該当部分のコードを概略化したものを以下に示します。

[code]

    # -*- coding:utf-8 -*-
    
    import numpy as np
    import pandas as pd
    from scipy.interpolate import interp1d
    
    
    def integrate(matrix, t, start_time=0.0):
        time = np.array(matrix["time"])
        matches = np.where(start_time <= time, True, False) * np.where(time <= t, True, False)
        matched_time = time[matches]
        dt = (matched_time - np.insert(matched_time, 0, 0)[0:len(matched_time)])[1:]
        x = np.array(matrix["vel_x"])[matches]
        y = np.array(matrix["vel_y"])[matches]
        z = np.array(matrix["vel_z"])[matches]
    
        return np.array([
            np.sum(x[:len(matched_time)-1] * dt) + x[-1] * (t - matched_time[-1]),
            np.sum(y[:len(matched_time)-1] * dt) + y[-1] * (t - matched_time[-1]),
            np.sum(z[:len(matched_time)-1] * dt) + z[-1] * (t - matched_time[-1]),
        ])
    
    
    if __name__ == "__main__":
        data = pd.DataFrame([[0.0, 0.0, 0.0, 0.0], [1.0, 1.0, 0.5, 1.5], [2.0, 2.0, 4.0, 3.0], [3.0, 3.0, 9.0, 10.0]], columns=["time", "vel_x", "vel_y", "vel_z"])
        print("data:\n", data)
        #data:
        #       time  vel_x  vel_y  vel_z
        #   0   0.0    0.0    0.0    0.0
        #   1   1.0    1.0    0.5    1.5
        #   2   2.0    2.0    4.0    3.0
        #   3   3.0    3.0    9.0   10.0
    
        for t in np.arange(0, 3, 0.0001):
            position = integrate(data, t)
            vel_x_t = interp1d(data["time"], data["vel_x"], bounds_error=False, fill_value=(0, 0))(t) if isinstance(data, pd.DataFrame) else data
    
            # sampling
            if t == 2.5:
                print("t: ", t)
                print("integrate result(position):\n", position)
                print("interpolate result(vel_x_t):\n", vel_x_t)
                # t:  2.5
                # integrate result(position):
                #  [2.  2.5 3. ]
                # interpolate result(vel_x_t):
                #  2.5
    
    
    
[/code]

※ integrate()は以下の処理をしています。  
「時刻tとその時の状態xを記録したデータがあるとき、ある時刻 t = t1 時点における状態xの積分値を算出する。」

integrateは当初for文による計算、その後pandasのapplyを使用して書いていましたが、  
非常に時間がかかったので上記の通りnp.arrayで一旦取り出して処理する形に書き換えています。

interp1dの方は単純な処理ですので抜本的な高速化のアイデアも思いついていないところですが、  
ループで呼び出しているため時間がかかっているのか、体感でもう少し早くても良いのになと思っているところです。

これらをより高速な処理に書き換えることは可能でしょうか？  
よろしくお願いいたします。


CC BY-SA 4.0
2022-12-21T10:17:08.107
92940
2022-12-28T11:33:16.400
2022-12-23T02:17:13.050
3060
29729
question
python pandas numpy
pandas, numpyでの積分・線形保管の処理を高速化したい


[scipy.interpolate.InterpolatedUnivariateSpline — SciPy v1.9.3
Manual](https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.InterpolatedUnivariateSpline.html)
と
[scipy.interpolate.InterpolatedUnivariateSpline.integral](https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.InterpolatedUnivariateSpline.integral.html)
を使う場合。(~8倍程度の高速化)

※

> 「時刻tとその時の状態xを記録したデータがあるとき、ある時刻 t = t1 時点における状態xの積分値を算出する。」

線形補間(linear interpolation)と台形則(trapezoidal rule)による定積分(definite
integral)の事かと思うのですが、質問文にあるコードは台形則ではない様なので、計算結果が異なります。

[code]

    import numpy as np
    import pandas as pd
    from scipy.interpolate import InterpolatedUnivariateSpline
    
    data = pd.DataFrame([
      [0.0, 0.0, 0.0, 0.0], [1.0, 1.0, 0.5, 1.5],
      [2.0, 2.0, 4.0, 3.0], [3.0, 3.0, 9.0, 10.0]
    ], columns=["time", "vel_x", "vel_y", "vel_z"])
    print("data:\n", data)
    
    interp = [InterpolatedUnivariateSpline(data['time'], data[c], k=1) for c in data.columns[1:]]
    start_time = 0
    for t in np.arange(0, 3, 0.0001):
        position = [i.integral(start_time, t) for i in interp]
        vel_x_t = interp[0](t)
    
        # sampling
        if t == 2.5:
            print("t: ", t)
            print("integrate result(position):\n", position)
            print("interpolate result(vel_x_t):\n", vel_x_t)
    
    # t:  2.5
    # integrate result(position):
    #  [3.125, 5.125, 5.375]
    # interpolate result(vel_x_t):
    #  2.5
    
[/code]


CC BY-SA 4.0
2022-12-21T12:15:33.303
92943
2022-12-24T22:23:21.543
2022-12-24T22:23:21.543
47127
47127
92940
answer


他の (metropolis さんの)回答でかなり高速化されてそうなので  
それ以外の部分です

x, y, z を同時に演算することで, colabで微妙に速くなったようですが …  
実際の環境で比較したほうがよいかも？

[code]

    def integrate(matrix, t, start_time=0.0):
        time, xyz = np.split(matrix, [1])
        time = time[0]
        matches = (start_time <= time) & (time <= t)
        matched_time = time[matches]
        dt = np.append(np.diff(matched_time), t - matched_time[-1])
        xyz = xyz[:, matches]
        res = np.sum(xyz * dt, axis=1)
        return res
    
    # 呼び出し側 (いろいろ省略)
    if __name__ == "__main__":
        for t in np.arange(0, 3, 0.0001):
            position = integrate(args, t)   # ⇐ 扱い方異なるので '*' 無しで
    
[/code]

* * *

**(追記)**

`if __name__ == "__main__":` を `def main():` に変更し表示の部分取り除き計測してみました (colabで)

オリジナル:

  * 5.88 s ± 762 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

ループ外へ掃き出し:

  * 2.72 s ± 359 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

さらに 同時演算(上記):

  * 1.98 s ± 41.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)


CC BY-SA 4.0
2022-12-22T10:02:56.673
92960
2022-12-24T07:12:14.900
2022-12-24T07:12:14.900
43025
43025
92940
answer


回答頂いた内容を元のコードに埋め込み時間を測定し直したところ以下の通り高速化しました。

元のコード：integrate: 15.3秒 / 全体: 56.2秒  
改良版 : integrate: 0.454秒 / 全体: 41.2秒

(interpolateの方は修正箇所がかなり多くなってしまうため手が回りませんでしたが、  
ロジックはあまり変わらないので同様に早くなりそうです。)

エラーが出た部分や計算結果が変わってしまう部分等もあったため、回答頂いたコードを100%取り入れることは出来ませんでしたが、  
特にInterpolatedUnivariateSplineの導入とループ外への掃き出しは効果があったように思います。

msオーダーが見えてきたのでかなり軽量になりそうです。ありがとうございました。


CC BY-SA 4.0
2022-12-28T11:33:16.400
93073
2022-12-28T11:33:16.400
29729
92940
answer