【Python】配列から特定の要素だけ削除したい

データ分析の際に配列から特定の要素(例えば注目対象ではない要素など)を削除する方法を備忘録としてまとめます.
使用言語はPythonです.

本記事中で例示するために使用したコードとデータは,こちらからダウンロード可能です.

方法

Pythonのremove関数およびpop関数を使用します.
実際のコードを以下で示します.

実際の使用例

ここでは例として,ある配列から任意の要素(1種類)を削除します.

list1 = [1, 2, 3, 4, 5]
target1 = 3
list1.remove(target1)

print(list1)
# [1, 2, 4, 5]

list1からtarget1の要素が削除されていることがわかります.

次に,元の配列に削除対象の要素が複数個含まれる場合を示します.

list2 = [1, 2, 3, 1, 2, 3]
target2 = 2
list2.remove(target2)

print(list2)
# [1, 3, 1, 2, 3]

元の配列のから削除対象の要素が1つしか削除されていないことがわかります.
この方法では,配列の先頭に最も近い削除対象の要素だけが削除されます.

実際の使用例 ~part2~

上記の方法では,任意に設定した要素を1つしか削除できないことがわかりました.
そこで,配列内に削除対象が複数存在する場合でも,全ての削除対象を削除可能な方法を実装します.

list2_a = [1, 2, 3, 1, 2, 3]
target2_a = 2

i = len(list2_a)-1
while i >= 0:
    if list2_a[i] == target2_a:
        list2_a.pop(i)
    i -= 1
print(list2_a)
# [1, 3, 1, 3]

この場合,繰り返し処理を加え,配列内の要素を全てチェックし削除します.
while文ではなくfor文で実装する場合は以下のようになります.

list2_a = [1, 2, 3, 1, 2, 3]
target2_a = 2

for i in range(len(list2_a)-1, -1, -1):
    if list2_a[i] == target2_a:
        list2_a.pop(i)
print(list2_a)
# [1, 3, 1, 3]

for文で繰り返し処理を加える際は,range関数の引数設定に要注意です.
繰り返し処理の最中にpop関数で要素削除を行うことにより,要素の削除後に配列内での各要素のインデックスが繰り上がることになるため,配列の最後尾から削除対象かどうかの判定と削除を行う必要があります.

実際の使用例の検証

ここで一つ検証を行います.
繰り返し処理を加えた上記の処理は,配列内のすべての要素を逐次参照し,削除するか否かを判定しています.
上記の例では,使用している配列の要素数が6であるため,何の問題もありませんが,実際のデータ分析においては,これが膨大な数になることが予想されます.
そこで本検証では,配列の要素数を増やしたときの処理時間を計測し,より高速に処理可能な方法を模索します.
処理時間は,timeモジュールのtime関数を処理手前および処理直後で実行し,その差分を処理時間としました.
まず,元の配列を用意します.

list2_b1, list2_b2 = [], []
target2_b = 2
for i in range(100000):
    lst = [1, 2, 3]
    list2_b1.extend(lst)
    list2_b2.extend(lst)

print(len(list2_b1))
# 300000
print(len(list2_b2))
# 300000

用意した配列の要素数は300000としました.

まずはじめに,上記のfor文による繰り返し処理を加えた方法の処理時間を計測します.

import time
start_time = time.time()

for i in range(len(list2_b1)-1, -1, -1):
    if list2_b1[i] == target2_b:
        list2_b1.pop(i)

end_time = time.time()
elapsed_time = end_time - start_time
print(elapsed_time)
# 2.1199512481689453

結果は,およそ2.1秒でした.

続いて,これに代わる方法を実装します.
具体的には,リスト内法表記による方法を実装します.

import time

start_time = time.time()

list2_b2_ = [x for x in list2_b2 if x != target2_b]

end_time = time.time()
elapsed_time = end_time - start_time
print(elapsed_time)
# 0.020160675048828125

結果は,およそ0.02秒でした.
繰り返し処理による逐次参照は配列の要素数が増えるほど処理時間に反映されることが明らかとなりました.
繰り返し処理による逐次参照はリスト内法表記に代替することで,大幅な高速化が実現できるというのは,他の処理にも生かせる知見だったと思います.

まとめ

「塵も積もれば山となる」ですね.

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です