Identische Einträge in neue Datei schreiben

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
azibat
User
Beiträge: 4
Registriert: Freitag 13. Oktober 2017, 20:54

Hallo,
ich grübele hier über ein Problem - wobei ich überzeugt bin, einfach gerade den "Wald vor lauter Bäumen" nicht zu sehen.

Innerhalb eines Projektes erzeuge ich zwei Listen, von denen jede Liste ungefähr 40.000 Einträge hat (also recht umfangreich). Die Listen sind jeweils von der Struktur:

[*]Spalte 1 \tSpalte2 \tSpalte3 ...
[*]chr \t1234 \t5678

In der ersten Spalte stehen also Begriffe (hier mit "chr" dargestellt), in den Spalten 2 und 3 stehen jeweils Zahlen (hier mit 1234 bzw. 5678 dargestellt). Ich möchte nun die beiden Listen miteinander vergleichen auf identische Einträge in der Spalte 2 und diese Einträge dann in eine neue Liste ("common") speichern. Die Einträge aus Liste 1 (wenn man mit Liste 2 vergleicht), die nicht in Liste 2 vorkommen (also Zahlenwerte aus Liste 1 in Spalte 2 finden sich nicht in Liste 2, Spalte 2) sollen in eine zweite Datei ("unique") geschrieben werden.
Hierfür habe ich folgenden Code gebastelt:

Code: Alles auswählen

INFILE1= input("INFILE1: \n")
INFILE2= input("INFILE2: \n")
OUTPUT1= input("OUTPUT COMMON: \n")
OUTPUT2= input("OUTPUT UNIQUE: \n")
with open(INFILE2, "r") as file2:
    f2=file2.readlines()
with open (INFILE1, "r") as f1, open (OUTPUT1, "w") as OUTFILE1, open (OUTPUT2, "w") as OUTFILE2:
    for line in f1:
        if "chr" in line:
            linelist= line.rstrip().split('\t')
            if any (linelist[1] in x for x in f2):
                OUTFILE1.write(line)
            else:
                OUTFILE2.write(line)
Das Problem ist, dass alle (!) Einträge aus Liste 1 bei dem Vergleich mit Liste 2 in die Datei OUTFILE1 geschrieben werden, OUTFILE2 bleibt leer. Wo ist hier der Denkfehler?
Wäre super, wenn mich jemand von dem "Schlauch schubsen" könnte.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Bitte schau dir mal die Empfehlungen fuer Namenskonventionen in PEP8 an. Du verletzt die durch deine Grosschreibung, denn das ist vorgesehen fuer Konstanten. Das nicht wie gewohnt zu handhaben fuehrt dazu, dass anderen (also uns) das lesen und verstehen erschwert wird.

Dein eigentliches Problem besteht darin, das du mit deiner any-Zeile die gesamten Zeilen von f2 schon beim ersten Vergleich konsumiert hast. Danach iteriert der nie ueber irgendwas rueber.

Des weiteren ist dein Test auf Enthalten potentiell falsch. Du pruefst einfach, ob der Teilstring aus f1 in einer Zeile f2 vorkommt. 10 ist aber zB auch Teil von 23342310890. Das kann doch nicht gewuenscht sein, oder?

Um dein Problem zu loesen, ein paar Ratschlaege:

- 40000 Zeilen sind nicht "recht umfangreich". Das ist ein Klacks. Lies die Dateien einfach komplett in den Speicher.
- tu das mit dem csv-Modul, dann sparst du dir das rumgeroedel mit strippen hier und splitten da. Delimiter auf \t setzen nicht vergessen.
- statt jedes mal linear ueber die gesamten Eintraege in f2 zu gehen, bau dir einen simplen Index, bei dem du deine gewuenschte Spalte 2 mit einem dict auf die Werte der ganzen Zeile. Dann wird dein Test auf enthalten spucke-billig, und der Code deutlich einfacher.
nezzcarth
User
Beiträge: 1755
Registriert: Samstag 16. April 2011, 12:47

Hier mal ein Vorschlag, wie man es vielleicht lösen könnte:

Code: Alles auswählen

#!/usr/bin/env python3
import csv
DELIMITER = '\t'

def main():
    term = 'chr'
    first_in = input()
    second_in = input()
    unique_out = input()
    common_out = input()
    with open(first_in) as fh:
        reader = csv.DictReader(fh, delimiter=DELIMITER)
        fieldnames = reader.fieldnames
        first_data = [row for row in reader]
    with open(second_in) as fh:
        second_data = set(row['Spalte2'] for row in csv.DictReader(fh, delimiter=DELIMITER))
    common = list()
    unique = list()
    for row in first_data:
        if row['Spalte1'] == term:
            if row['Spalte2'] in second_data:
                common.append(row)
            else:
                unique.append(row)
    for data, file in ((common, common_out), (unique, unique_out)):
        with open(file, 'w') as fh:
            writer = csv.DictWriter(fh, fieldnames, delimiter=DELIMITER)
            writer.writeheader()
            writer.writerows(data)

if __name__ == '__main__':
    main()
nezzcarth
User
Beiträge: 1755
Registriert: Samstag 16. April 2011, 12:47

Oder mit Bordmitteln:

Code: Alles auswählen

$ cat test.tsv 
C1	C2	C3
chr	1	2
abc	3	4
abc	5	6
chr	7	8
chr	9	10

$ cat test2.tsv 
C1	C2	C3
chr	1	2
abc	3	4
abc	5	6
chr	0	8
chr	9	10

$ head -n1 test.tsv | tee unique.tsv common.tsv > /dev/null

$ comm -12 test.tsv test2.tsv | grep '^chr' >> common.tsv

$ comm -23 test.tsv test2.tsv | grep '^chr' >> unique.tsv

$ cat common.tsv
C1	C2	C3
chr	1	2
chr	9	10

$ cat unique.tsv
C1	C2	C3
chr	7	8
azibat
User
Beiträge: 4
Registriert: Freitag 13. Oktober 2017, 20:54

@ _deets_:
vielen Dank für Deinen Hinweis bezüglich PEP8.
Es ist mir klar, dass mit der any-Zeile der Inhalt aus f2 beim ersten Vergleich konsumiert wird, aber da ist ja der "Fuß auf dem Schlauch". Ich möchte eigentlich nicht die Dateien komplett in den Speicher lesen, da jede der Dateien ein Volumen von ca. 6 GB hat.

@nezzcarth:
Der Lösungsvorschlag funktioniert leider nicht. Bordmittel zu verwenden stellen leider keine Option dar. Trotzdem vielen Dank!
Benutzeravatar
__blackjack__
User
Beiträge: 14033
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@azibat: Wenn Du die Daten nicht komplett in den Speicher lesen möchtest, ist die naheliegende Alternative die ”innere” Datei jedes mal komplett neu Datensatzweise zu lesen. Das ist bei 6 GB auch nicht besser. Ändert sich an der Datenmenge denn durch den ”chr-Filter” signifikant etwas?

Wenn das weder in den Speicher passt, noch ständiges neulesen aller Datensätze einer der Dateien sinnvoll ist, würde ich sagen das man eine Datenbank ins Auge fassen könnte.

Wobei die Information 40.000 Datensätze und 6 GB pro Datei bedeuten würde das so ein Datensatz mit drei Spalten im Schnitt jeweils 1,43 MiB gross ist‽ Das ist ziemlich heftig für einen Begriff und zwei Zahlen. ;-)
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
azibat
User
Beiträge: 4
Registriert: Freitag 13. Oktober 2017, 20:54

@ _blackjack_:
Sorry, das angegebene Beispiel für die Struktur der verarbeiteten Listen ist nur ein Beispiel, da ich eben anhand der Einträge der zweiten Spalte von Liste 1 die Anwesenheit dieses Eintrages (kann 1 x oder mehrere Male in Liste 2 vorkommen) screenen möchte. Die Listen, mit denen ich es zu tun habe, tragen pro Zeile jeweils 126 Spalten. Die Einträge innehalb der Spalten sind mitunter sehr umfangreich (Delimiters hier ; , : oder ,). Daher kommt die Größe der Dateien zustande.
Die Verwendeung einer Datenbank ist leider keine Option, da die Listen nur temporär verarbeitet werden.
Wie gesagt, ich sehe momentan immer noch nicht wirklich, wo der Hase begraben ist ...
Danke _blackjack_ :)
Benutzeravatar
noisefloor
User
Beiträge: 4187
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Die Verwendeung einer Datenbank ist leider keine Option, da die Listen nur temporär verarbeitet werden.
Die Datenbank wäre hier ja auch nur Mittel zum Zweck, sprich um das Ziel zu erreichen. Du kannst du Tabelle(n) in der Datenbank ja wieder löschen und musst die DB nicht zur persistenten Speicherung nutzen.

Gruß, noisefloor
nezzcarth
User
Beiträge: 1755
Registriert: Samstag 16. April 2011, 12:47

azibat hat geschrieben: Mittwoch 29. August 2018, 11:59 @nezzcarth:
Der Lösungsvorschlag funktioniert leider nicht. Bordmittel zu verwenden stellen leider keine Option dar. Trotzdem vielen Dank!
Schade. Was heißt denn "funktioniert nicht"? Sind die Daten vielleicht signifkant anders strukturiert(hast du ja schon geschrieben; gibt es z.B. doppelte Spaltennamen, sind die Spalten der beiden Dateien unterscheidlich o.ä?) und/oder hat sich bei der Anpassung an das eigentliche Format ein Fehler eingeschlichen? Mit ausreichend RAM bzw. Swapspace sollte das nämlich auch für eine 6GB Datei funktionieren. Na ja...
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

azibat hat geschrieben: Mittwoch 29. August 2018, 11:59 Es ist mir klar, dass mit der any-Zeile der Inhalt aus f2 beim ersten Vergleich konsumiert wird, aber da ist ja der "Fuß auf dem Schlauch". Ich möchte eigentlich nicht die Dateien komplett in den Speicher lesen, da jede der Dateien ein Volumen von ca. 6 GB hat.
Also einen Tod musst du nunmal sterben. Daten NICHT im Speicher haben, obwohl man sie nicht einlesen will, widerspricht sich nunmal.

Und auch wenn 6GB zuzugebendermassen viel Daten sind, sind sie auf einem modernen System immer noch problemlos im Speicher zu halten. Hast du das mal probiert?

Wenn der Speicher nicht reicht, und umgekehrt das immer wieder einlesen zu langsam ist, dann bleibt einem nur bezogen auf die Daten einen Indizierungs-Mechanismus zu basteln. Entweder mit etwas fertigem, wie SQLite, oder von Hand. Wobei bloss weil sqlite eine Datenbank ist, das nicht heisst, dass deren Nutzung schwergewichtig ist. Deine DB kann genauso temporaer sein wie jede andere Hilfsdatei. Sie loest ein Problem, und ist nicht ein bestaendiges Artefakt deines Systems.
Sirius3
User
Beiträge: 18267
Registriert: Sonntag 21. Oktober 2012, 17:20

@nezzcarth: ich vermute mal stark, dass beim OP die Spalten nicht `Spalte1` und `Spalte2` heißen, grr.

Da nur die zweite Spalte interessiert, ist die Datenmenge, die man im Speicher halten muß, wahrscheinlich deutlich kleiner.
nezzcarth
User
Beiträge: 1755
Registriert: Samstag 16. April 2011, 12:47

__deets__ hat geschrieben: Mittwoch 29. August 2018, 13:48 Wenn der Speicher nicht reicht, und umgekehrt das immer wieder einlesen zu langsam ist, dann bleibt einem nur bezogen auf die Daten einen Indizierungs-Mechanismus zu basteln. Entweder mit etwas fertigem, wie SQLite, oder von Hand. Wobei bloss weil sqlite eine Datenbank ist, das nicht heisst, dass deren Nutzung schwergewichtig ist. Deine DB kann genauso temporaer sein wie jede andere Hilfsdatei. Sie loest ein Problem, und ist nicht ein bestaendiges Artefakt deines Systems.
Um ohne großen Aufwand an einen On-Disk Index für die Vergleichsmenge zu kommen, könnte man zum Beispiel in einem ersten Schritt einen Key Value Store nehmen und einen Schlüssel aus Spaltennummer und -wert als Key mit leeren Values ablegen. In Schritt Zwei kann man dann über die zu vergleichende Datei iterieren und für jedes zu vergleichende Elemente schauen, ob der KV-Store einen Treffer findet. Das ist zwar ein bisschen zweckentfremdet, arbeitete bei meinen Tests (mit LevelDB) aber recht schnell und hat keinen SQL-Overhead.
azibat
User
Beiträge: 4
Registriert: Freitag 13. Oktober 2017, 20:54

Ich habe das Problem über ein Dictionary lösen können (danke @deets für den "Schubs" :) ):

Code: Alles auswählen

infile1= input("INFILE1: \n")
infile2= input("INFILE2: \n")
output1= input("OUTPUT COMMON: \n")
output2= input("OUTPUT UNIQUE: \n")

with open (infile1, "r") as f1, open (infile2, "r") as f2, open (output1, "w+") as common_out, open (output2, "w+") as unique_out:
    check_dictionary = {}
    for line in f1:
        if "chr" in line:
            linelist= line.rstrip().split('\t')
            check_entry = tuple(linelist[2:7]) 
            check_dictionary[check_entry] = line
    for line in f2:
        linelist= line.rstrip().split('\t')
        check_entry = tuple(linelist[2:7]) 
        if check_entry in check_dictionary:
            common_out.write(line)
        else:
            unique_out.write(line)
Vielen Dank an alle für die Lösungsvorschläge!
Sirius3
User
Beiträge: 18267
Registriert: Sonntag 21. Oktober 2012, 17:20

@azibat: das `"chr" in line` ist zu allgemein, da Du ja wirklich nur die erste Spalte testen willst. Da der gesamte Zeileninhalt der ersten Datei nicht gebraucht wird, kannst Du auch ein set benutzen. "w+" ist selten ein sinnvoller Dateimodus, vor allem bei Text-Dateien macht er keinen Sinn. Entweder willst Du "w" oder "a" benutzen.

Code: Alles auswählen

with open (infile1, "r") as f1, open (infile2, "r") as f2, open (output1, "w") as common_out, open (output2, "w") as unique_out:
    check_entries = set()
    for line in f1:
        row = line.rstrip().split('\t')
        if row[0] == "chr":
            check_entry = tuple(row[2:7]) 
            check_entries .add(check_entry)
    for line in f2:
        row = line.rstrip().split('\t')
        check_entry = tuple(row[2:7]) 
        output = common_out if check_entry in check_entries else unique_out
        output.write(line)
Antworten