panda mendapatkan baris yang TIDAK ada dalam bingkai data lain

229

Saya punya dua bingkai data panda yang memiliki beberapa baris yang sama.

Misalkan dataframe2 adalah subset dari dataframe1.

Bagaimana saya bisa mendapatkan baris dataframe1 yang tidak ada di dataframe2?

df1 = pandas.DataFrame(data = {'col1' : [1, 2, 3, 4, 5], 'col2' : [10, 11, 12, 13, 14]}) 
df2 = pandas.DataFrame(data = {'col1' : [1, 2, 3], 'col2' : [10, 11, 12]})
pikirkan hal-hal baik
sumber
1
@TedPetrou Saya gagal melihat bagaimana jawaban yang Anda berikan adalah yang benar. Jika saya memiliki dua dataframe yang satu adalah subset dari yang lain, saya perlu menghapus semua baris itu, yang ada di dalam subset. Saya tidak ingin menghapus duplikat. Saya benar-benar ingin menghapus subset.
jukebox

Jawaban:

172

Salah satu metode akan menyimpan hasil dari gabungan dalam bentuk kedua dfs, maka kita dapat dengan mudah memilih baris ketika nilai satu kolom tidak dalam kesamaan ini:

In [119]:

common = df1.merge(df2,on=['col1','col2'])
print(common)
df1[(~df1.col1.isin(common.col1))&(~df1.col2.isin(common.col2))]
   col1  col2
0     1    10
1     2    11
2     3    12
Out[119]:
   col1  col2
3     4    13
4     5    14

EDIT

Metode lain seperti yang Anda temukan adalah menggunakan isinyang akan menghasilkan NaNbaris yang bisa Anda jatuhkan:

In [138]:

df1[~df1.isin(df2)].dropna()
Out[138]:
   col1  col2
3     4    13
4     5    14

Namun jika df2 tidak memulai baris dengan cara yang sama maka ini tidak akan berfungsi:

df2 = pd.DataFrame(data = {'col1' : [2, 3,4], 'col2' : [11, 12,13]})

akan menghasilkan seluruh df:

In [140]:

df1[~df1.isin(df2)].dropna()
Out[140]:
   col1  col2
0     1    10
1     2    11
2     3    12
3     4    13
4     5    14
EdChum
sumber
13
df1[~df1.isin(df2)].dropna(how = 'all')sepertinya melakukan trik. Terima kasih - jawaban Anda membantu saya menemukan solusi.
pikirkan hal-hal baik
5
Perhatikan bahwa menggunakan isinmengharuskan kedua dfs mulai dengan nilai baris yang sama jadi misalnya jika df2 df2 = pd.DataFrame(data = {'col1' : [2, 3,4], 'col2' : [11,12, 13]})maka metode Anda tidak akan berfungsi
EdChum
2
ini mengubah semua int menjadi mengapung!
Chris Nielsen
3
@SergeyZakharov jawaban ini diposting hampir 3 tahun yang lalu adalah benar sejauh OP yang bersangkutan dan untuk masalah mereka, jawaban lainnya adalah jawaban yang lebih baik dan menangani masalah yang lebih luas yang tidak pernah menjadi bagian dari pertanyaan awal, tidak benar untuk menyatakan bahwa ini Jawabannya salah, sudah benar diberikan masalah sebagaimana diatur. Selain itu seseorang telah downvoted ini tanpa penjelasan, ada sedikit yang bisa saya lakukan karena ini adalah jawaban yang diterima, OP belum berubah pikiran mereka dan aku tidak akan mencopoti jawaban lain untuk membuatnya benar .
EdChum
1
@Cecilia Anda harus lulus keep=False: df0.append(df1).drop_duplicates(keep=False), secara default itu membuat duplikat pertama, Anda ingin drop semua duplikat
EdChum
189

Solusi yang dipilih saat ini menghasilkan hasil yang salah. Untuk mengatasi masalah ini dengan benar, kita dapat melakukan join-kiri dari df1hingga df2, memastikan untuk mendapatkan terlebih dahulu baris unik untukdf2 .

Pertama, kita perlu memodifikasi DataFrame asli untuk menambahkan baris dengan data [3, 10].

df1 = pd.DataFrame(data = {'col1' : [1, 2, 3, 4, 5, 3], 
                           'col2' : [10, 11, 12, 13, 14, 10]}) 
df2 = pd.DataFrame(data = {'col1' : [1, 2, 3],
                           'col2' : [10, 11, 12]})

df1

   col1  col2
0     1    10
1     2    11
2     3    12
3     4    13
4     5    14
5     3    10

df2

   col1  col2
0     1    10
1     2    11
2     3    12

Lakukan join-kiri, hilangkan duplikat df2sehingga setiap baris df1bergabung dengan tepat 1 baris df2. Gunakan parameter indicatoruntuk mengembalikan kolom tambahan yang menunjukkan dari tabel mana baris itu berasal.

df_all = df1.merge(df2.drop_duplicates(), on=['col1','col2'], 
                   how='left', indicator=True)
df_all

   col1  col2     _merge
0     1    10       both
1     2    11       both
2     3    12       both
3     4    13  left_only
4     5    14  left_only
5     3    10  left_only

Buat kondisi boolean:

df_all['_merge'] == 'left_only'

0    False
1    False
2    False
3     True
4     True
5     True
Name: _merge, dtype: bool

Mengapa solusi lain salah

Beberapa solusi membuat kesalahan yang sama - mereka hanya memeriksa bahwa setiap nilai secara independen di setiap kolom, tidak bersama di baris yang sama. Menambahkan baris terakhir, yang unik tetapi memiliki nilai dari kedua kolom dari df2memperlihatkan kesalahan:

common = df1.merge(df2,on=['col1','col2'])
(~df1.col1.isin(common.col1))&(~df1.col2.isin(common.col2))
0    False
1    False
2    False
3     True
4     True
5    False
dtype: bool

Solusi ini mendapatkan hasil yang salah yang sama:

df1.isin(df2.to_dict('l')).all(1)
Ted Petrou
sumber
2
tapi, saya kira, mereka berasumsi bahwa col1 unik menjadi indeks (tidak disebutkan dalam pertanyaan, tetapi jelas). Jadi, jika tidak pernah ada kasus seperti itu di mana ada dua nilai col2 untuk nilai yang sama dengan col1 (tidak mungkin ada dua col1 = 3 baris) jawaban di atas benar.
pashute
14
Jelas tidak jelas, jadi poin Anda tidak valid. Solusi saya menggeneralisasi lebih banyak kasus.
Ted Petrou
Pertanyaan, bukankah akan lebih mudah untuk membuat slice daripada boolean array? Karena tujuannya adalah untuk mendapatkan baris.
Matías Romo
5
Gunakan df_all[df_all['_merge'] == 'left_only']untuk memiliki df dengan hasilnya
gies0r
77

Dengan asumsi bahwa indeks konsisten dalam kerangka data (tidak memperhitungkan nilai col aktual):

df1[~df1.index.isin(df2.index)]
Dennis Golomazov
sumber
1
@ChrisNielsen menyangkal kondisi. Jadi dalam contoh ini ini berarti "ambil baris dari df1indeks yang TIDAK di dalamnya df2.index". Lebih lanjut tentang negasi: stackoverflow.com/q/19960077/304209 (yang mengejutkan, saya tidak dapat menemukan sebutan tilde di panda docs).
Dennis Golomazov
Sepertinya dfs harus sama panjang, bukan? Saya mendapatkanValueError: Item wrong length x instead of y.
kata
@wordssudah jelas tidak, mereka tidak. Topeng memiliki panjang df1 dan diterapkan ke df1 juga. Bisakah Anda memberikan contoh?
Dennis Golomazov
Untuk memperbaiki masalah panjang item, Anda harus menambahkan .loc
Moreno
13

Seperti yang sudah diisyaratkan, isin membutuhkan kolom dan indeks yang sama untuk sebuah pertandingan. Jika kecocokan hanya pada konten baris, salah satu cara untuk mendapatkan topeng untuk memfilter hadiah saat ini adalah dengan mengubah baris ke indeks (Multi):

In [77]: df1 = pandas.DataFrame(data = {'col1' : [1, 2, 3, 4, 5, 3], 'col2' : [10, 11, 12, 13, 14, 10]})
In [78]: df2 = pandas.DataFrame(data = {'col1' : [1, 3, 4], 'col2' : [10, 12, 13]})
In [79]: df1.loc[~df1.set_index(list(df1.columns)).index.isin(df2.set_index(list(df2.columns)).index)]
Out[79]:
   col1  col2
1     2    11
4     5    14
5     3    10

Jika indeks harus diperhitungkan, set_index memiliki argumen kata kunci ditambahkan untuk menambahkan kolom ke indeks yang ada. Jika kolom tidak berbaris, daftar (kolom df) dapat diganti dengan spesifikasi kolom untuk menyelaraskan data.

pandas.MultiIndex.from_tuples(df<N>.to_records(index = False).tolist())

sebagai alternatif dapat digunakan untuk membuat indeks, meskipun saya ragu ini lebih efisien.

Rune Lyngsoe
sumber
@ Dev_123 Hapus ~ di awal. Inti adalah untuk membuat daftar predikat apakah baris di df1 juga muncul di df2, sehingga baris di df1 tidak unik untuk df1, ~ meniadakan ini ke daftar predikat apakah baris di df1 tidak muncul di df2.
Rune Lyngsoe
11

Misalkan Anda memiliki dua kerangka data, df_1 dan df_2 yang memiliki banyak bidang (column_names) dan Anda ingin menemukan satu-satunya entri di df_1 yang tidak ada di df_2 berdasarkan beberapa bidang (mis. Fields_x, fields_y), ikuti langkah-langkah berikut.

Step1.Tambahkan kunci kolom1 dan kunci2 masing-masing ke df_1 dan df_2.

Step2.Merge kerangka data seperti yang ditunjukkan di bawah ini. field_x dan field_y adalah kolom yang diinginkan.

Step3.Pilih hanya baris-baris dari df_1 di mana key1 tidak sama dengan key2.

Step4.Drop key1 dan key2.

Metode ini akan menyelesaikan masalah Anda dan bekerja dengan cepat bahkan dengan set data besar. Saya sudah mencobanya untuk kerangka data dengan lebih dari 1.000.000 baris.

df_1['key1'] = 1
df_2['key2'] = 1
df_1 = pd.merge(df_1, df_2, on=['field_x', 'field_y'], how = 'left')
df_1 = df_1[~(df_1.key2 == df_1.key1)]
df_1 = df_1.drop(['key1','key2'], axis=1)
Pragalbh kulshrestha
sumber
Saya tidak berpikir ini secara teknis apa yang dia inginkan - dia ingin tahu baris mana yang unik untuk yang df. tapi, saya pikir solusi ini mengembalikan df baris yang unik untuk df pertama atau df kedua.
Legit Stack
3

Anda dapat melakukannya menggunakan metode isin (dict) :

In [74]: df1[~df1.isin(df2.to_dict('l')).all(1)]
Out[74]:
   col1  col2
3     4    13
4     5    14

Penjelasan:

In [75]: df2.to_dict('l')
Out[75]: {'col1': [1, 2, 3], 'col2': [10, 11, 12]}

In [76]: df1.isin(df2.to_dict('l'))
Out[76]:
    col1   col2
0   True   True
1   True   True
2   True   True
3  False  False
4  False  False

In [77]: df1.isin(df2.to_dict('l')).all(1)
Out[77]:
0     True
1     True
2     True
3    False
4    False
dtype: bool
MaxU
sumber
Ini menghasilkan hasil yang salah. Lihat penjelasan saya di bawah ini.
Ted Petrou
2

Anda juga dapat concat df1, df2:

x = pd.concat([df1, df2])

dan kemudian hapus semua duplikat:

y = x.drop_duplicates(keep=False, inplace=False)
Semeon Balagula
sumber
Selamat datang di StackOverflow: jika Anda memposting kode, XML, atau sampel data, sorot baris-baris itu di editor teks dan klik tombol "kode sampel" ({}) pada bilah alat editor atau gunakan Ctrl + K pada keyboard Anda untuk memformat dengan baik dan sintaks sorotnya!
WhatsThePoint
4
Ini akan mengembalikan semua data yang ada di set apa pun, bukan hanya data yang hanya di df1.
Jamie Marshall
1

Bagaimana dengan ini:

df1 = pandas.DataFrame(data = {'col1' : [1, 2, 3, 4, 5], 
                               'col2' : [10, 11, 12, 13, 14]}) 
df2 = pandas.DataFrame(data = {'col1' : [1, 2, 3], 
                               'col2' : [10, 11, 12]})
records_df2 = set([tuple(row) for row in df2.values])
in_df2_mask = np.array([tuple(row) in records_df2 for row in df1.values])
result = df1[~in_df2_mask]
adamwlev
sumber
1

Berikut ini cara lain untuk menyelesaikan ini:

df1[~df1.index.isin(df1.merge(df2, how='inner', on=['col1', 'col2']).index)]

Atau:

df1.loc[df1.index.difference(df1.merge(df2, how='inner', on=['col1', 'col2']).index)]
Sergey Zakharov
sumber
0

Cara saya melakukan ini melibatkan menambahkan kolom baru yang unik untuk satu kerangka data dan menggunakan ini untuk memilih apakah akan menyimpan entri

df2[col3] = 1
df1 = pd.merge(df_1, df_2, on=['field_x', 'field_y'], how = 'outer')
df1['Empt'].fillna(0, inplace=True)

Ini membuatnya jadi setiap entri di df1 memiliki kode - 0 jika unik untuk df1, 1 jika ada di kedua dataFrames. Anda kemudian menggunakan ini untuk membatasi apa yang Anda inginkan

answer = nonuni[nonuni['Empt'] == 0]
r.rz
sumber
0
ekstrak baris yang berbeda menggunakan fungsi gabung
df = df.merge(same.drop_duplicates(), on=['col1','col2'], 
               how='left', indicator=True)
simpan baris berbeda di CSV
df[df['_merge'] == 'left_only'].to_csv('output.csv')
Gajanan Kothawade
sumber