pandas รับแถวที่ไม่ได้อยู่ใน dataframe อื่น


229

ฉันมีกรอบข้อมูลแพนด้าสองตัวที่มีบางแถวเหมือนกัน

สมมติว่า dataframe2 เป็นชุดย่อยของ dataframe1

ฉันจะได้แถวของ dataframe1 ที่ไม่ได้อยู่ใน 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]})

1
@TedPetrou ฉันไม่เห็นว่าคำตอบที่คุณให้นั้นถูกต้องแค่ไหน หากฉันมีสอง dataframes ซึ่งอันหนึ่งเป็นเซตย่อยของอีกอันฉันต้องลบแถวเหล่านั้นทั้งหมดซึ่งอยู่ในเซ็ตย่อย ฉันไม่ต้องการลบข้อมูลซ้ำ ฉันต้องการลบชุดย่อยทั้งหมด
ตู้เพลง

คำตอบ:


172

วิธีหนึ่งคือการเก็บผลลัพธ์ของฟอร์มผสานภายในทั้งสอง dfs จากนั้นเราสามารถเลือกแถวได้เมื่อค่าของคอลัมน์หนึ่งไม่อยู่ในรูปแบบนี้:

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

แก้ไข

วิธีอื่นที่คุณพบคือการใช้isinซึ่งจะสร้างNaNแถวที่คุณสามารถปล่อย:

In [138]:

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

อย่างไรก็ตามหาก df2 ไม่เริ่มแถวในลักษณะเดียวกันสิ่งนี้จะไม่ทำงาน:

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

จะผลิต 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

13
df1[~df1.isin(df2)].dropna(how = 'all')ดูเหมือนว่าจะทำเคล็ดลับ ขอบคุณต่อไป - คำตอบของคุณช่วยฉันหาวิธีแก้ปัญหา
คิดสิ่งที่ดี

5
โปรดทราบว่าการใช้isinต้องการให้ dfs ทั้งสองเริ่มต้นด้วยค่าแถวเดียวกันดังนั้นตัวอย่างเช่นถ้า df2 เป็นdf2 = pd.DataFrame(data = {'col1' : [2, 3,4], 'col2' : [11,12, 13]})วิธีการของคุณจะไม่ทำงาน
EdChum

2
นี่เป็นการแปลง ints ทั้งหมดให้ลอย!
Chris Nielsen

3
@SergeyZakharov คำตอบนี้โพสต์เมื่อเกือบ 3 ปีที่แล้วถูกต้องเท่าที่ OP เกี่ยวข้องและสำหรับปัญหาของพวกเขาคำตอบอื่น ๆ คือคำตอบที่ดีกว่าและจัดการกับปัญหาที่กว้างกว่าซึ่งไม่เคยเป็นส่วนหนึ่งของคำถามเดิม คำตอบคือผิดมันถูกต้องให้ปัญหาตามที่กำหนดไว้ นอกจากนี้มีคน downvoted นี้โดยไม่มีคำอธิบายมีเล็ก ๆ น้อย ๆ ที่ฉันจะทำเช่นนี้เป็นคำตอบที่ได้รับการยอมรับ, OP ไม่ได้เปลี่ยนความคิดของพวกเขาและฉันไม่ได้ไป cannibalize คำตอบอื่นที่จะทำให้มันขวา
EdChum

1
@Cecilia คุณต้องผ่านkeep=False: df0.append(df1).drop_duplicates(keep=False)โดยค่าเริ่มต้นมันจะเก็บสำเนาที่ซ้ำกันแรกคุณต้องการปล่อยสำเนาที่ซ้ำกันทั้งหมด
EdChum

189

โซลูชันที่เลือกไว้ในปัจจุบันให้ผลลัพธ์ที่ไม่ถูกต้อง เพื่อแก้ปัญหานี้อย่างถูกต้องเราสามารถทำการรวมซ้ายdf1ไปถึงdf2ตรวจสอบให้แน่ใจว่าได้รับแถวที่ไม่ซ้ำกันก่อนdf2ก่อน

ก่อนอื่นเราต้องแก้ไข DataFrame ดั้งเดิมเพื่อเพิ่มแถวด้วย 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

ทำการซ้ายเข้าร่วมการขจัดซ้ำกันในdf2เพื่อให้แถวของแต่ละdf1ร่วมกับว่า 1 df2แถว ใช้พารามิเตอร์indicatorเพื่อส่งกลับคอลัมน์พิเศษที่ระบุว่าแถวมาจากไหน

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

สร้างเงื่อนไขบูลีน:

df_all['_merge'] == 'left_only'

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

เหตุใดโซลูชันอื่น ๆ จึงผิด

วิธีแก้ปัญหาสองสามข้อนั้นทำผิดพลาดเหมือนกัน - พวกเขาตรวจสอบเฉพาะว่าแต่ละค่านั้นมีความเป็นอิสระในแต่ละคอลัมน์ไม่รวมกันในแถวเดียวกัน การเพิ่มแถวสุดท้ายซึ่งไม่ซ้ำกัน แต่มีค่าจากทั้งสองคอลัมน์จากการdf2เปิดเผยข้อผิดพลาด:

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

วิธีนี้ได้รับผลลัพธ์ที่ผิดเหมือนกัน:

df1.isin(df2.to_dict('l')).all(1)

2
แต่ฉันคิดว่าพวกเขาสันนิษฐานว่า col1 นั้นไม่เหมือนใครเป็นดัชนี (ไม่ได้กล่าวถึงในคำถาม แต่ชัดเจน) ดังนั้นหากไม่เคยมีกรณีดังกล่าวที่มีสองค่าของ col2 สำหรับค่าเดียวกันของ col1 (ไม่สามารถมีสอง col1 = 3 แถว) คำตอบข้างต้นถูกต้อง
pashute

14
ไม่ชัดเจนดังนั้นจุดของคุณไม่ถูกต้อง วิธีการแก้ปัญหาของฉันทำให้เป็นกรณีทั่วไปมากขึ้น
Ted Petrou

คำถามมันจะง่ายกว่าไหมถ้าจะสร้างเป็นชิ้น ๆ แทนที่จะเป็นชุดบูลีน? เนื่องจากวัตถุประสงค์คือเพื่อให้ได้แถว
Matías Romo

5
ใช้df_all[df_all['_merge'] == 'left_only']เพื่อให้มี df กับผลลัพธ์
gies0r

77

สมมติว่าดัชนีมีความสอดคล้องกันใน dataframes (โดยไม่คำนึงถึงค่า col ที่แท้จริง):

df1[~df1.index.isin(df2.index)]

1
@ChrisNielsen ปฏิเสธสภาพ ดังนั้นในตัวอย่างนี้หมายความว่า "นำแถวจากdf1ดัชนีที่ไม่ได้อยู่ในdf2.index" เพิ่มเติมเกี่ยวกับการปฏิเสธ: stackoverflow.com/q/19960077/304209 (น่าแปลกใจที่ฉันไม่พบการกล่าวถึง tilde ใน pandas docs)
Dennis Golomazov

ดูเหมือนว่า dfs จะต้องมีความยาวเท่ากันไม่ใช่เหรอ? ฉันได้รับValueError: Item wrong length x instead of y.
คำต่อไป

@ คำว่าไม่มีเลยพวกเขาทำไม่ได้ หน้ากากมีความยาวของ df1 และถูกนำไปใช้กับ df1 เช่นกัน คุณสามารถให้ตัวอย่างของคุณ?
Dennis Golomazov

ในการแก้ไขปัญหาความยาวของรายการคุณควรเพิ่ม. loc
Moreno

13

ตามที่บอกใบ้ไว้แล้ว isin ต้องการคอลัมน์และดัชนีให้เหมือนกันสำหรับการแข่งขัน หากการจับคู่ควรอยู่ในเนื้อหาแถวเท่านั้นวิธีหนึ่งในการรับมาสก์สำหรับกรองแถวที่มีอยู่คือการแปลงแถวเป็นดัชนี (หลายรายการ):

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

หากควรนำดัชนีมาพิจารณา set_index จะมีอาร์กิวเมนต์คำหลักต่อท้ายเพื่อผนวกคอลัมน์ไปยังดัชนีที่มีอยู่ หากคอลัมน์ไม่เรียงแถวรายการ (df.columns) สามารถถูกแทนที่ด้วยข้อมูลจำเพาะของคอลัมน์เพื่อจัดเรียงข้อมูล

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

อาจใช้อีกทางหนึ่งในการสร้างดัชนีแม้ว่าฉันจะสงสัยว่ามันมีประสิทธิภาพมากกว่า


@ Dev_123 ลบเครื่องหมาย ~ ที่จุดเริ่มต้น แกนหลักคือการสร้างรายการเพรดิเคตของว่าแถวใน df1 ยังเกิดขึ้นใน df2 ดังนั้นแถวใน df1 ไม่ซ้ำกับ df1, ~ negates นี้ไปยังรายการเพรดิเคตของแถวใน df1 ไม่เกิดขึ้นใน df2
Rune Lyngsoe

11

สมมติว่าคุณมีสอง dataframes, df_1 และ df_2 มีหลายฟิลด์ (column_names) และคุณต้องการค้นหาเฉพาะรายการเหล่านั้นใน df_1 ที่ไม่ได้อยู่ใน df_2 บนพื้นฐานของบางฟิลด์ (เช่น fields_x, fields_y) ทำตามขั้นตอนต่อไปนี้

Step1 เพิ่มคอลัมน์ key1 และ key2 เป็น df_1 และ df_2 ตามลำดับ

ขั้นที่ 2. ทำการรวมไฟล์ข้อมูลดังที่แสดงด้านล่าง field_x และ field_y เป็นคอลัมน์ที่เราต้องการ

ขั้นที่ 3. เลือกเฉพาะแถวเหล่านั้นจาก df_1 โดยที่ key1 ไม่เท่ากับ key2

Step4.Drop key1 และ key2

วิธีนี้จะแก้ปัญหาของคุณและทำงานได้อย่างรวดเร็วแม้กับชุดข้อมูลขนาดใหญ่ ฉันลองใช้กับ dataframes ที่มีมากกว่า 1,000,000 แถว

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)

ฉันไม่คิดว่านี่เป็นสิ่งที่เขาต้องการในทางเทคนิค - เขาต้องการที่จะรู้ว่าแถวไหนมีลักษณะเฉพาะของ df แต่ฉันคิดว่าวิธีนี้จะคืนค่า df ของแถวที่ไม่ซ้ำกับ df แรกหรือ df ที่สอง
Legit Stack

6

สายไปหน่อย แต่มันอาจคุ้มค่าที่จะตรวจสอบพารามิเตอร์ "indicator" ของ pd.merge

ดูคำถามอื่นสำหรับตัวอย่างนี้: เปรียบเทียบ PandaS DataFrames และส่งคืนแถวที่หายไปจากแถวแรก


ใช่ นอกจากนี้ที่นี่: stackoverflow.com/questions/49487263/...
แดน

3

คุณสามารถทำได้โดยใช้วิธีisin (dict) :

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

คำอธิบาย:

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

สิ่งนี้สร้างผลลัพธ์ที่ผิด ดูคำอธิบายของฉันด้านล่าง
Ted Petrou

2

นอกจากนี้คุณยังสามารถ concat df1, df2:

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

แล้วลบรายการที่ซ้ำทั้งหมด:

y = x.drop_duplicates(keep=False, inplace=False)

ยินดีต้อนรับสู่ StackOverflow: หากคุณโพสต์โค้ด XML หรือตัวอย่างข้อมูลโปรดเน้นบรรทัดเหล่านั้นในตัวแก้ไขข้อความและคลิกที่ปุ่ม "ตัวอย่างโค้ด" ({}) บนแถบเครื่องมือตัวแก้ไขหรือใช้ Ctrl + K บนแป้นพิมพ์ของคุณเพื่อจัดรูปแบบ และไวยากรณ์เน้นมัน!
WhatsThePoint

4
นี่จะส่งคืนข้อมูลทั้งหมดที่อยู่ในชุดเดียวกันไม่ใช่เฉพาะข้อมูลที่อยู่ใน df1 เท่านั้น
เจมี่มาร์แชลล์

1

เกี่ยวกับสิ่งนี้:

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]

1

นี่เป็นอีกวิธีในการแก้ปัญหานี้:

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

หรือ:

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

0

วิธีการของฉันในการทำเช่นนี้เกี่ยวข้องกับการเพิ่มคอลัมน์ใหม่ที่ไม่ซ้ำกันในหนึ่ง dataframe และใช้สิ่งนี้เพื่อเลือกว่าจะเก็บรายการหรือไม่

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

สิ่งนี้ทำให้ทุกรายการใน df1 มีรหัส - 0 ถ้ามันไม่ซ้ำกับ df1, 1 ถ้ามันอยู่ใน dataFrames ทั้งสอง จากนั้นคุณใช้สิ่งนี้เพื่อ จำกัด สิ่งที่คุณต้องการ

answer = nonuni[nonuni['Empt'] == 0]

0
แยกแถวที่แตกต่างกันโดยใช้ฟังก์ชั่นผสาน
df = df.merge(same.drop_duplicates(), on=['col1','col2'], 
               how='left', indicator=True)
บันทึกแถวที่แตกต่างกันใน CSV
df[df['_merge'] == 'left_only'].to_csv('output.csv')
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.