データフレームから対称ペアを削除 – 行ごとのユニーク値のみを保持

Find symmetric pairs quickly in numpy – StackOverflow

数値データの場合,Numpy solnがリーズナブルチョイス.

import pandas as pd
import numpy as np
from sklearn.utils.extmath import cartesian


def view1D(a):
    a = np.ascontiguousarray(a)
    void_dt = np.dtype((np.void, a.itemsize*a.shape[1]))
    return a.view(void_dt).ravel()


def isin_nd(a, b):
    A, B = view1D(a.reshape(a.shape[0], -1)), view1D(b.reshape(b.shape[0], -1))
    return np.isin(A, B)


def drop_symmetric_pairs(arr):
    m = isin_nd(arr, arr[:, ::-1])
    v = np.unique(view1D(np.sort(arr[m], 1))).view('(2,)i8')
    out = np.concatenate((arr[~m], v))
    return out


col = ['c1', 'c2']
df = (
    pd.DataFrame(cartesian((range(10),)*2), columns=col)
    .sample(90)
    .sort_values(col)
    .reset_index(drop=True)
)

res = pd.DataFrame(drop_symmetric_pairs(df.to_numpy()))
res
    0   1
0   0   9
1   1   5
2   2   2
3   4   2
4   5   9
5   6   9
6   7   2
7   8   6
8   9   3
9   9   6
10  0   0
11  0   1
12  0   3
13  0   4
14  0   5
15  0   6
16  0   7
17  0   9
18  1   1
19  1   2
20  1   4
21  1   5
22  1   7
23  1   8
24  1   9
25  2   2
26  2   3
27  2   4
28  2   5
29  2   6
30  2   8
31  2   9
32  3   3
33  3   5
34  3   6
35  3   7
36  3   8
37  4   4
38  4   5
39  4   6
40  4   7
41  4   8
42  4   9
43  5   5
44  5   6
45  5   7
46  5   9
47  6   6
48  6   7
49  7   7
50  7   8
51  7   9
52  8   8
53  8   9
54  9   9
def drop_symmetric_pairs(arr):
    m = isin_nd(arr, arr[:, ::-1])
    v = np.unique(view1D(np.sort(arr[m], 1))).view('(2,)i8')
    out = np.concatenate((arr[~m], v))
    return out


def drop_symmetric_pairs2(arr):
    a = np.sort(arr, 1)
    out = np.unique(view1D(a)).view('(2,)i8')
    return out


df2 = (
    pd.DataFrame(cartesian((range(10000),)*2), columns="c1 c2".split())
    .sample(10000)
    .sort_values(df.columns.tolist())
    .reset_index(drop=True)
)

res1 = drop_symmetric_pairs(df2.to_numpy())
res2 = drop_symmetric_pairs2(df2.to_numpy())
assert isin_nd(np.sort(res1, 1), np.sort(res2, 1)).all()
%timeit res1 = pd.DataFrame(drop_symmetric_pairs(df2.to_numpy()))
%timeit res2 = pd.DataFrame(drop_symmetric_pairs2(df2.to_numpy()))
100 loops, best of 3: 5.64 ms per loop
100 loops, best of 3: 2.67 ms per loop

効率はかなり悪くなる(x10-20 slower)が,Pandas solnを考えるとシンプルに書ける.

res = df.apply(np.sort, axis=1, raw=True).drop_duplicates()
res
    c1  c2
0   0   0
1   0   1
2   0   4
3   0   5
4   0   6
5   0   7
6   0   8
7   0   9
9   1   1
10  1   2
11  1   3
12  1   5
13  1   7
14  1   8
15  1   9
16  0   2
18  2   2
19  2   3
20  2   4
21  2   5
22  2   6
23  2   7
24  2   9
25  0   3
28  3   3
29  3   4
30  3   5
31  3   6
32  3   7
33  3   8
34  3   9
36  1   4
39  4   4
40  4   5
41  4   6
42  4   7
43  4   8
44  4   9
50  5   5
51  5   6
52  5   7
53  5   8
54  5   9
58  6   6
59  6   7
60  6   8
61  6   9
68  7   7
69  7   8
70  7   9
73  2   8
78  8   8
79  8   9
89  9   9

数値データ以外では,ピュアにやるか,Pandas solnを考える方が良い.

df2[~df2.apply(frozenset, 1).duplicated()]

数値データの場合は,効率は雲泥の差なので,Numpy solnを選択しない理由はない.

df2 = (
    pd.DataFrame(cartesian((range(10000),)*2), columns="c1 c2".split())
    .sample(10000)
    .sort_values(df.columns.tolist())
    .reset_index(drop=True)
)

res1 = drop_symmetric_pairs(df2.to_numpy())
res2 = drop_symmetric_pairs2(df2.to_numpy())
res3 = df2[~df2.apply(frozenset, 1).duplicated()].to_numpy()
assert isin_nd(np.sort(res1, 1), np.sort(res2, 1)).all()
assert isin_nd(np.sort(res1, 1), np.sort(res3, 1)).all()
%timeit res1 = pd.DataFrame(drop_symmetric_pairs(df2.to_numpy()))
%timeit res2 = pd.DataFrame(drop_symmetric_pairs2(df2.to_numpy()))
%timeit res3 = df2[~df2.apply(frozenset, 1).duplicated()]
100 loops, best of 3: 5.69 ms per loop
100 loops, best of 3: 2.67 ms per loop
10 loops, best of 3: 157 ms per loop
カテゴリー: 未分類 パーマリンク

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Google フォト

Google アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください