2016-08-08 20 views
5

Ich habe eine Liste von Rechnungen an Kunden gesendet. Manchmal wird jedoch eine fehlerhafte Rechnung gesendet, die später storniert wird. Mein Pandas Datenrahmen sieht ungefähr so ​​aus, außer viel größer (ca. 3 Millionen Zeilen)Löschen von Zeilen aus Pandas Datareframe entfernen

index | customer | invoice_nr | amount | date 
--------------------------------------------------- 
0  | 1  | 1   | 10  | 01-01-2016 
1  | 1  | 1   | -10 | 01-01-2016 
2  | 1  | 1   | 11  | 01-01-2016 
3  | 1  | 2   | 10  | 02-01-2016 
4  | 2  | 3   | 7  | 01-01-2016 
5  | 2  | 4   | 12  | 02-01-2016 
6  | 2  | 4   | 8  | 02-01-2016 
7  | 2  | 4   | -12 | 02-01-2016 
8  | 2  | 4   | 4  | 02-01-2016 
... | ...  | ...  | ... | ... 
... | ...  | ...  | ... | ... 

Nun, ich möchte alle Zeilen fallen zu lassen, für die die customer, invoice_nr und date identisch sind, aber die amount entgegengesetzte Werte hat.
Korrekturen von Rechnungen erfolgen immer am selben Tag mit identischer Rechnungsnummer. Die Rechnungsnummer ist eindeutig an den Kunden gebunden und entspricht immer einer Transaktion (die aus mehreren Komponenten bestehen kann, zB für customer = 2, invoice_nr = 4). Korrekturen von Rechnungen erfolgen entweder nur zur Änderung amount berechnet, oder zur Aufteilung amount in kleinere Komponenten. Daher wird der annullierte Wert nicht auf demselben invoice_nr wiederholt.

Jede Hilfe, wie man dies programmiert, würde sehr geschätzt werden.

+0

Versuchen Sie, die Zeilen zu lesen in ein 'dict', wobei' invoice_nr' und 'datum' durch Trennzeichen getrennt sind, sagen wir' #'. Wenn Sie nun einen redundanten Schlüssel erhalten, löschen Sie ihn. –

+0

@KrishnachandraSharma Ich bin mir nicht ganz sicher, ob ich dir folge, was du meinst. Sollte ich die 'billing_nr' und das' date' als 'dict'-Schlüssel lesen? Wie würde ich dann mehrere Zeilen mit demselben 'invoice_nr' und 'Datum' behandeln? –

+0

Da Sie alle Zeilen mit demselben 'invoice_nr' und' datum' löschen möchten, würde das Vorbereiten der Schlüsselzeichenfolge als 'invoice_nr # date' Ihnen helfen, doppelte Zeilen zu identifizieren, die Sie löschen möchten. –

Antwort

2
def remove_cancelled_transactions(df): 
    trans_neg = df.amount < 0 
    return df.loc[~(trans_neg | trans_neg.shift(-1))] 

groups = [df.customer, df.invoice_nr, df.date, df.amount.abs()] 
df.groupby(groups, as_index=False, group_keys=False) \ 
    .apply(remove_cancelled_transactions) 

enter image description here

+0

Hmmm, ich denke, es ist komplizierter, weil Sie entgegengesetzte Werte der Menge entfernen müssen. Und es ist sehr schwer ... – jezrael

+0

@jezrael behoben. – piRSquared

+0

Danke. Hmmm, ich denke deine Lösung ist besser, weil allgemeiner. Meine Lösung ist schneller, aber einige Werte können nicht gefunden werden. – jezrael

0

Was ist, wenn Sie nur ein groupby auf allen 3 Feldern machen? Die sich daraus ergebenden Beträge würden netto aus alle storniert Rechnungen:

df2 = df.groupby(['customer','invoice_nr','date']).sum() 

Ergebnisse in

customer invoice_nr date 
1  1   2016/01/01  11 
     2   2016/02/01  10 
2  3   2016/01/01  7 
+0

Danke, das ist eine nette Lösung. Wie ich jedoch sehe, sind meine Beispieldaten nicht vollständig genug, weil meine Rechnungen gelegentlich in kleinere Beträge aufgeteilt werden, die ich getrennt betrachten möchte. Ich habe meine ursprüngliche Frage entsprechend aktualisiert. –

2

Sie filter alle Werte verwenden können, wobei jede Gruppe Werte hat, wo Summe 0 und Modulo von 2 ist 0:

print (df.groupby([df.customer, df.invoice_nr, df.date, df.amount.abs()]) 
     .filter(lambda x: (len(x.amount.abs()) % 2 == 0) and (x.amount.sum() == 0))) 

     customer invoice_nr amount  date 
index           
0    1   1  10 01-01-2016 
1    1   1  -10 01-01-2016 
5    2   4  12 02-01-2016 
6    2   4  -12 02-01-2016 

idx = df.groupby([df.customer, df.invoice_nr, df.date, df.amount.abs()]) 
     .filter(lambda x: (len(x.amount.abs()) % 2 == 0) and (x.amount.sum() == 0)).index 

print (idx)  
Int64Index([0, 1, 5, 6], dtype='int64', name='index') 

print (df.drop(idx)) 
     customer invoice_nr amount  date 
index           
2    1   1  11 01-01-2016 
3    1   2  10 02-01-2016 
4    2   3  7 01-01-2016 
7    2   4  8 02-01-2016 
8    2   4  4 02-01-2016 

EDIT von Kommentar:

Wenn in realen Daten sind keine Duplikate für eine Rechnung und einen Kunden und ein Datum, so dass Sie auf diese Weise verwenden können:

print (df) 
    index customer invoice_nr amount  date 
0  0   1   1  10 01-01-2016 
1  1   1   1  -10 01-01-2016 
2  2   1   1  11 01-01-2016 
3  3   1   2  10 02-01-2016 
4  4   2   3  7 01-01-2016 
5  5   2   4  12 02-01-2016 
6  6   2   4  -12 02-01-2016 
7  7   2   4  8 02-01-2016 
8  8   2   4  4 02-01-2016 

df['amount_abs'] = df.amount.abs() 
df.drop_duplicates(['customer','invoice_nr', 'date', 'amount_abs'], keep=False, inplace=True) 
df.drop('amount_abs', axis=1, inplace=True) 
print (df) 
    index customer invoice_nr amount  date 
2  2   1   1  11 01-01-2016 
3  3   1   2  10 02-01-2016 
4  4   2   3  7 01-01-2016 
7  7   2   4  8 02-01-2016 
8  8   2   4  4 02-01-2016 
+0

Vielen Dank für Ihre Hilfe, @jezrael! Ähnlich wie bei @ piRSquared funktioniert Ihre Lösung hervorragend für kleine Datenmengen. Leider dauert die Operation für meinen 3-Millionen-Zeilen-Datenrahmen eine ziemlich lange Zeit. –

+0

Ja, Ihre Aufgabe ist sehr kompliziert. Also wenn etwas schneller benötigt wird, ist es problematisch. Aber eine Frage - wie oft in 3M Reihen sind Rechnungen storniert (geschätzt)? – jezrael

+0

Sie können 'df1' mit allen Duplikaten erhalten:' df ['amount_abs'] = df.amount.abs() ' ' df1 = df [df.duplicated (['Kunde', 'Rechnungsnr', 'Datum', 'amount_abs'], keep = False)] ' ' print (df1) 'Was ist die Größe von' df1'? 'len (df1)' – jezrael