小ネタです。
あるリストの要素について、別のリストと比較したときにマッチしない要素を取り出すコードを考えます。ここでは、1つだけでなく2つ以上の要素を1度に削除することを考慮します。
>>> hoge = [1, 'a', 'b', 4, 'c', 6, 7, 'd', 'e']
>>> fuga = [4, 'c', 6, 7]
>>> piyo = [1, 4, 7, 'e']
ここではリストhoge
が持つ要素のうち、リストfuga
やpiyo
が持つ要素と一致しない要素を取り出します。
[1, 'a', 'b', 'd', 'e']
たとえばリストhoge
とfuga
を比較したとき、上記の結果が返ってくればOKです。これをどう取得しましょうか。
>>> hoge.remove(fuga)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list
単純に考えると、リストfuga
が持つ要素をhoge
から「削除」してやればいいわけです。そして、リストから要素を削除するにはremove()
メソッドを使います。
ただ、remove()
は引数に指定した「値」が存在したときに該当の要素を削除します。上記のように、remove()
の引数にリストを指定すると、「リストである1要素」を削除しようとします。つまり、リストfuga
と同じ要素を持つリストが1要素になっている箇所を探そうとするわけです。
>>> foo
[[1, 2, 3], [4, 'A']]
>>> foo.remove([4, 'A'])
>>> foo
[[1, 2, 3]]
たとえば、上記のように要素がリストであるならば、削除したい要素としてリストを指定できます。ただ、リストhoge
にはリストである要素は存在しません。そのため、ValueError: list.remove(x): x not in list
と言われます。あくまでも引数には「削除したい要素」を指定するので、どんな形式や型であれ、もともとのリストに存在そなければこのエラーが返ってきます。
というわけで、今回は単純に引数に比較したいリストを渡すわけには行かなさそうです。
>>> hoge.remove(fuga[0])
>>>
となると、上記のようにリストfuga
の要素を1つずつ指定してremove()
すればいいことになります。そうなると繰り返し文の出番です。
>>> hoge
[1, 'a', 'b', 4, 'c', 6, 7, 'd', 'e']
>>> fuga
[4, 'c', 6, 7]
>>> for item in fuga:
... hoge.remove(item)
...
>>> print(hoge)
[1, 'a', 'b', 'd', 'e']
そのため、上記のようにfor
文を使ってfuga
の要素を1つずつ指定しつつremove()
すれば、効率はともかくリストfuga
には存在しなかったリストhoge
の要素のみを抽出できます。
ただ、リストhoge
本来の値を残しておきたい場合はこの処理がきません。なぜなら、remove()
は要素を直接削除してリストの内容を変えてしまうしまうためです。
そこでリスト内包表記を使います。
>>> hoge
[1, 'a', 'b', 4, 'c', 6, 7, 'd', 'e']
>>> fuga
[4, 'c', 6, 7]
>>> piyo
[1, 4, 7, 'e']
>>> [e for e in hoge if e not in fuga]
[1, 'a', 'b', 'd', 'e']
>>> [e for e in hoge if e not in piyo]
['a', 'b', 'c', 6, 'd']
>>> hoge
[1, 'a', 'b', 4, 'c', 6, 7, 'd', 'e']
for
を使って要素を1つずつ削除しなくとも、リスト内包表記はif
をつけて「比較したいリストに存在しないこと」という条件を設定できます。ここではnotを付与して、「~ではない」という条件に合致する要素で新しいリストを作成しています。そう、出力されるのは新しいリストです。つまり、既存のリストには変更を加えません。実際、リスト内包表記による要素の抽出を実行した前後、リストhoge
の内容は変化していません。
さらには、上記のようにリスト内包表記を使って記述することで可読性を向上できます。1行で済むコンパクトさもありつつ、出力がリストであり要素の出力元から出力条件までがちゃんと収まっているわけです。
>>> hoge
[1, 'a', 'b', 4, 'c', 6, 7, 'd', 'e']
>>> fuga
[4, 'c', 6, 7]
>>> piyo
[1, 4, 7, 'e']
>>> [e for e in hoge if e not in (fuga + piyo)]
['a', 'b', 'd']
さらには、上記のように複数のリストを指定すれば複数のリストと比較が可能なので、どのリストとも一致しない要素を抽出したりもできます。
>>> import pandas as pd
>>> import io
>>> s = '''a,b,c,d,e,f,g
... 1,2,3,4,5,6,7
... 8,9,10,11,12,13,14
... 15,16,17,18,19,20,21
... '''
>>> df = pd.read_csv(io.StringIO(s))
>>> df
a b c d e f g
0 1 2 3 4 5 6 7
1 8 9 10 11 12 13 14
2 15 16 17 18 19 20 21
>>> df.loc[:, [col for col in df.columns if col not in ['a', 'c', 'f']]]
b d e g
0 2 4 5 7
1 9 11 12 14
2 16 18 19 21
余談ですが、上記の考え方はpandasのDataFrameにて、列を抽出する場合にも利用できます。というか、リストの形式なので同様に処理できるわけですね。