🐈

[Pandas] シーケンス番号の差分計算

2023/05/20に公開

シーケンス番号など固定のフィールド長で最大値に到達したら 0 になるデータの差分を計算

概略

Pandas では DataFrame の diff を使って差分計算ができる。
しかし、シーケンス番号などデータでは最大値に到達した後次のデータは 0 に戻る。

単純に diff を計算するとマイナスの値になつてしまう。
このため、最大値に到達した後 0 に戻ることを前提にシーケンス番号の差分を計算する方法を説明する。

参考にしたサイト

ソースコード

※ コードの解説は後程

import pandas as pd
import numpy as np

def calc_seqnumber_diff(df, M, fieldName, fieldNameforDiff):
	df_diff = df[ [fieldName] ].diff()
	print("Diff:")
	print(df_diff)

	# 最大値を含む行に True の印をつける
	df["max"]       = (df[fieldName] == (M-1) )

	# 最小値を含む行に True の印をつける
	df["min"]       = (df[fieldName] ==    0  )

	# 最小値を含む行で、かつ前の行が最大値の行を見つけるため、
	# 最大値の行の印を下に1行シフトする
	df["max_shift"] =  df["max"].shift(1)

	# 最小値を含む行で、かつ前の行が最大値の行に True の印をつける
	df["point"]     = (df["max_shift"] == True) & (df["min"] == True)

	# 最小値を含む行で、かつ前の行が最大値の行の場合差分は 1 とする。
	# それ以外の場合は前の行との差分を出力する。
	df[fieldNameforDiff] =  np.where(df["point"] == True, 1, df_diff[fieldName])
	print("Working:")
	print(df)

	# 一時的に使用した列を削除する
	del df["max"]
	del df["max_shift"]
	del df["min"]
	del df["point"]

	return df

N = 10
M =  4

df = pd.DataFrame(
	{
	'sequence_number': np.arange(0, N) % M,
	}
	)

print("Original:")
print(df)
df = calc_seqnumber_diff(df, M, "sequence_number", "diff")


print("Result:")
print(df)

実行結果

Original:
   sequence_number
0                0
1                1
2                2
3                3
4                0
5                1
6                2
7                3
8                0
9                1
Diff:
   sequence_number
0              NaN
1              1.0
2              1.0
3              1.0
4             -3.0
5              1.0
6              1.0
7              1.0
8             -3.0
9              1.0
Working:
   sequence_number    max    min max_shift  point  diff
0                0  False   True       NaN  False   NaN
1                1  False  False     False  False   1.0
2                2  False  False     False  False   1.0
3                3   True  False     False  False   1.0
4                0  False   True      True   True   1.0
5                1  False  False     False  False   1.0
6                2  False  False     False  False   1.0
7                3   True  False     False  False   1.0
8                0  False   True      True   True   1.0
9                1  False  False     False  False   1.0
Result:
   sequence_number  diff
0                0   NaN
1                1   1.0
2                2   1.0
3                3   1.0
4                0   1.0
5                1   1.0
6                2   1.0
7                3   1.0
8                0   1.0
9                1   1.0

解説

通常の diff と比べて注意する点は、前のデータが最大値で、その次のデータが 0 になっているデータに関して差分計算の結果が マイナスの最大ではなく、1 になるようにしたい。

このため、最小値のデータを持つ行でかつ、直前の行が、最大値のデータを特定してその場合だけ差分として 1 を採用するような仕組みを導入する。

最大値の行

以下のようにして、最大値を含む行が True となるようにする。

	# 最大値を含む行に True の印をつける
	df["max"]       = (df[fieldName] == (M-1) )

最小値の行

以下のようにして、最小値を含む行が True となるようにする。

	# 最小値を含む行に True の印をつける
	df["min"]       = (df[fieldName] ==    0  )

最小値を含む行の前の行が最大値の行

最小値の前の行が最大値であるものを確認するため、最大値の行を下に1行シフトする。
シフトした結果と、最小値の行の両方が True となる行を探す。

	# 最小値を含む行で、かつ前の行が最大値の行を見つけるため、
	# 最大値の行の印を下に1行シフトする
	df["max_shift"] =  df["max"].shift(1)

	# 最小値を含む行で、かつ前の行が最大値の行に True の印をつける
	df["point"]     = (df["max_shift"] == True) & (df["min"] == True)

実行例

   sequence_number    max    min max_shift  point  diff
0                0  False   True       NaN  False   NaN
1                1  False  False     False  False   1.0
2                2  False  False     False  False   1.0
3                3   True  False     False  False   1.0
4                0  False   True      True   True   1.0
5                1  False  False     False  False   1.0
6                2  False  False     False  False   1.0
7                3   True  False     False  False   1.0
8                0  False   True      True   True   1.0
9                1  False  False     False  False   1.0

シーケンス番号の差分の計算

最小値の前の行が最大値であるものは差分として 1 を採用して、それ以外の行に関しては通常の差分を返す。

	# 最小値を含む行で、かつ前の行が最大値の行の場合差分は 1 とする。
	# それ以外の場合は前の行との差分を出力する。
	df[fieldNameforDiff] =  np.where(df["point"] == True, 1, df_diff[fieldName])

Discussion