Python Pandas - Temukan perbedaan antara dua bingkai data

98

Saya memiliki dua bingkai data df1 dan df2, di mana df2 adalah bagian dari df1. Bagaimana cara mendapatkan bingkai data baru (df3) yang merupakan perbedaan antara kedua bingkai data tersebut?

Dengan kata lain, bingkai data yang memiliki semua baris / kolom di df1 yang tidak ada di df2?

masukkan deskripsi gambar di sini

userPyGeo
sumber
3
Cara termudah untuk melakukan ini akan bergantung pada bagaimana dataframe Anda disusun (misalnya apakah indeks dapat digunakan, dll.). Ini adalah contoh yang bagus mengapa Anda harus selalu menyertakan contoh yang dapat direproduksi dalam pertanyaan panda.
cmaher
Saya telah menambahkan gambar contoh kerangka data
userPyGeo
mirip dengan stackoverflow.com/q/20225110
SpeedCoder5

Jawaban:

153

Dengan menggunakan drop_duplicates

pd.concat([df1,df2]).drop_duplicates(keep=False)

Update :

Above method only working for those dataframes they do not have duplicate itself, For example

df1=pd.DataFrame({'A':[1,2,3,3],'B':[2,3,4,4]})
df2=pd.DataFrame({'A':[1],'B':[2]})

Ini akan menghasilkan seperti di bawah ini, yang salah

Keluaran Salah:

pd.concat([df1, df2]).drop_duplicates(keep=False)
Out[655]: 
   A  B
1  2  3

Output yang Benar

Out[656]: 
   A  B
1  2  3
2  3  4
3  3  4

Bagaimana cara mencapainya?

Metode 1: Menggunakan isindengantuple

df1[~df1.apply(tuple,1).isin(df2.apply(tuple,1))]
Out[657]: 
   A  B
1  2  3
2  3  4
3  3  4

Metode 2: mergedenganindicator

df1.merge(df2,indicator = True, how='left').loc[lambda x : x['_merge']!='both']
Out[421]: 
   A  B     _merge
1  2  3  left_only
2  3  4  left_only
3  3  4  left_only
BEN_YO
sumber
3
Anda juga dapat menentukan kolom mana yang akan dipertimbangkan, saat mencari duplikat:pd.concat([df1,df2]).drop_duplicates(subset = ['col1','col2'], keep=False)
Szpaqn
@Szpaqn perhatikan bahwa metode ini tidak akan menangani kasus khusus. :-)
BEN_YO
Perhatikan bahwa ini dapat menyebabkan baris yang tidak diharapkan tetap berada di hasil jika salah satu tipe data Anda adalah float(karena 12.00000000001 != 12). Praktik yang lebih baik adalah menemukan perpotongan kumpulan ID dalam dua bingkai data dan mendapatkan perbedaan berdasarkan itu.
Jiāgěng
1
@DtechNet Anda perlu membuat dua bingkai data memiliki nama yang sama
BEN_YO
2
Metode 2 ( indicator=True) adalah alat yang sangat serbaguna dan berguna, saya ingin melihatnya di bagian atas jawaban ini, tetapi dengan gabungan 'luar' bukan 'kiri' untuk mencakup semua 3 situasi.
mirekphd
32

Untuk baris, coba ini, di mana Namekolom indeks gabungan (dapat berupa daftar untuk beberapa kolom umum, atau tentukan left_ondan right_on):

m = df1.merge(df2, on='Name', how='outer', suffixes=['', '_'], indicator=True)

The indicator=TruePengaturan ini berguna saat menambahkan kolom yang disebut _merge, dengan semua perubahan antara df1dan df2, dikategorikan menjadi 3 kemungkinan jenis: "left_only", "right_only" atau "baik".

Untuk kolom, coba ini:

set(df1.columns).symmetric_difference(df2.columns)
jpp
sumber
8
Mau downvoter mau berkomentar? mergewith indicator=Trueadalah solusi klasik untuk membandingkan kerangka data dengan bidang tertentu.
jpp
9

Jawaban yang diterima Metode 1 tidak akan berfungsi untuk bingkai data dengan NaN di dalamnya, seperti pd.np.nan != pd.np.nan. Saya tidak yakin apakah ini cara terbaik, tetapi dapat dihindari dengan

df1[~df1.astype(str).apply(tuple, 1).isin(df2.astype(str).apply(tuple, 1))]
toecsnar42.dll
sumber
6

edit2, saya menemukan solusi baru tanpa perlu menyetel indeks

newdf=pd.concat[df1,df2].drop_duplicates(keep=False)

oke saya menemukan jawaban dari voting tertinggi sudah berisi apa yang saya ketahui. Ya, kita hanya dapat menggunakan kode ini dengan syarat tidak ada duplikat di setiap dua dfs.


Saya memiliki metode yang rumit. Pertama kita tetapkan 'Nama' sebagai indeks dari dua kerangka data yang diberikan oleh pertanyaan. Karena kita memiliki 'Nama' yang sama di dua dfs, kita bisa melepaskan indeks df 'lebih kecil' dari df 'lebih besar' . Ini kodenya.

df1.set_index('Name',inplace=True)
df2.set_index('Name',inplace=True)
newdf=df1.drop(df2.index)
liangli
sumber
Anda mungkin bermaksud pd.concat ([df1, df2]). drop_duplicates (keep = False)
Manaslu
4
import pandas as pd
# given
df1 = pd.DataFrame({'Name':['John','Mike','Smith','Wale','Marry','Tom','Menda','Bolt','Yuswa',],
    'Age':[23,45,12,34,27,44,28,39,40]})
df2 = pd.DataFrame({'Name':['John','Smith','Wale','Tom','Menda','Yuswa',],
    'Age':[23,12,34,44,28,40]})

# find elements in df1 that are not in df2
df_1notin2 = df1[~(df1['Name'].isin(df2['Name']) & df1['Age'].isin(df2['Age']))].reset_index(drop=True)

# output:
print('df1\n', df1)
print('df2\n', df2)
print('df_1notin2\n', df_1notin2)

# df1
#     Age   Name
# 0   23   John
# 1   45   Mike
# 2   12  Smith
# 3   34   Wale
# 4   27  Marry
# 5   44    Tom
# 6   28  Menda
# 7   39   Bolt
# 8   40  Yuswa
# df2
#     Age   Name
# 0   23   John
# 1   12  Smith
# 2   34   Wale
# 3   44    Tom
# 4   28  Menda
# 5   40  Yuswa
# df_1notin2
#     Age   Name
# 0   45   Mike
# 1   27  Marry
# 2   39   Bolt
SpeedCoder5
sumber
Apa artinya '~'?
Piotrek Leśniak
'~' bukan untuk pengindeksan boolean. Lihat: pandas.pydata.org/pandas-docs/stable/user_guide/…
SpeedCoder5
3

Mungkin satu baris yang lebih sederhana, dengan nama kolom yang identik atau berbeda. Bekerja bahkan ketika df2 ['Name2'] berisi nilai duplikat.

newDf = df1.set_index('Name1')
           .drop(df2['Name2'], errors='ignore')
           .reset_index(drop=False)
Cherif Diallo
sumber
2
sederhana dan efektif. Kesalahan yang ditambahkan = 'abaikan' untuk menyelesaikan masalah untuk kasus di mana nilai tujuan tidak dalam sumber (yaitu persimpangan) dan menyetel ulang indeks pada akhirnya membawa df yang mirip dengan aslinya.
MrE
0

Sedikit variasi dari solusi @ liangli yang bagus yang tidak perlu mengubah indeks kerangka data yang ada:

newdf = df1.drop(df1.join(df2.set_index('Name').index))
Serge Ballesta
sumber
0

Menemukan perbedaan berdasarkan indeks. Dengan asumsi df1 adalah himpunan bagian dari df2 dan indeks dibawa maju saat membuat subset

df1.loc[set(df1.index).symmetric_difference(set(df2.index))].dropna()

# Example

df1 = pd.DataFrame({"gender":np.random.choice(['m','f'],size=5), "subject":np.random.choice(["bio","phy","chem"],size=5)}, index = [1,2,3,4,5])

df2 =  df1.loc[[1,3,5]]

df1

 gender subject
1      f     bio
2      m    chem
3      f     phy
4      m     bio
5      f     bio

df2

  gender subject
1      f     bio
3      f     phy
5      f     bio

df3 = df1.loc[set(df1.index).symmetric_difference(set(df2.index))].dropna()

df3

  gender subject
2      m    chem
4      m     bio

DOS
sumber
0

Selain jawaban yang diterima, saya ingin mengusulkan satu solusi yang lebih luas yang dapat menemukan perbedaan set 2D dari dua dataframe dengan index/ columns(mereka mungkin tidak bertepatan untuk kedua datarames). Juga metode memungkinkan untuk mengatur toleransi untuk floatelemen untuk perbandingan kerangka data (yang digunakannya np.isclose)


import numpy as np
import pandas as pd

def get_dataframe_setdiff2d(df_new: pd.DataFrame, 
                            df_old: pd.DataFrame, 
                            rtol=1e-03, atol=1e-05) -> pd.DataFrame:
    """Returns set difference of two pandas DataFrames"""

    union_index = np.union1d(df_new.index, df_old.index)
    union_columns = np.union1d(df_new.columns, df_old.columns)

    new = df_new.reindex(index=union_index, columns=union_columns)
    old = df_old.reindex(index=union_index, columns=union_columns)

    mask_diff = ~np.isclose(new, old, rtol, atol)

    df_bool = pd.DataFrame(mask_diff, union_index, union_columns)

    df_diff = pd.concat([new[df_bool].stack(),
                         old[df_bool].stack()], axis=1)

    df_diff.columns = ["New", "Old"]

    return df_diff

Contoh:

In [1]

df1 = pd.DataFrame({'A':[2,1,2],'C':[2,1,2]})
df2 = pd.DataFrame({'A':[1,1],'B':[1,1]})

print("df1:\n", df1, "\n")

print("df2:\n", df2, "\n")

diff = get_dataframe_setdiff2d(df1, df2)

print("diff:\n", diff, "\n")
Out [1]

df1:
   A  C
0  2  2
1  1  1
2  2  2 

df2:
   A  B
0  1  1
1  1  1 

diff:
     New  Old
0 A  2.0  1.0
  B  NaN  1.0
  C  2.0  NaN
1 B  NaN  1.0
  C  1.0  NaN
2 A  2.0  NaN
  C  2.0  NaN 
Luchko
sumber
0

Seperti yang disebutkan di sini itu

df1[~df1.apply(tuple,1).isin(df2.apply(tuple,1))]

adalah solusi yang benar tetapi akan menghasilkan keluaran yang salah jika

df1=pd.DataFrame({'A':[1],'B':[2]})
df2=pd.DataFrame({'A':[1,2,3,3],'B':[2,3,4,4]})

Dalam kasus di atas solusi akan memberikan DataFrame Kosong , sebagai gantinya Anda harus menggunakan concatmetode setelah menghapus duplikat dari setiap datframe.

Menggunakan concate with drop_duplicates

df1=df1.drop_duplicates(keep="first") 
df2=df2.drop_duplicates(keep="first") 
pd.concat([df1,df2]).drop_duplicates(keep=False)
arun sobat
sumber