Zeichenposition und Häufigkeit

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
DB-Hopf
User
Beiträge: 7
Registriert: Dienstag 17. Januar 2023, 09:41

Hallo liebes Pythonforum,

ich habe folgendes Problem. Das Trennzeichen einer CSV-Datei kann nicht geändert werden und ist leider in den "Datensätzen" teilweise enthalten. Das sorgt beim öffnen der "Import.csv" per Excel dafür, dass es zu Verschiebung einzelnen Datensätze kommt. Es werden nur die Zeilen aus der "Import.csv" mit dem Anfang "NUMMER" und "1" benötigt.

Rohdaten: "Import.csv":

Code: Alles auswählen

Datum; 18.01.2023
Info; 19.98
NUMMER;KUNDENIDENT;KUNDENBEZEICHNUNG;
"1";="0815";="MC Donald's";
"1";="0816";="MC Donald's; Berlin";
"1";="0817";="MC Donald's Bayern";
Im folgenden Pythonscript wird der Inhalt der "Import.csv" eingelesen und nur die Zeilen mit dem Start "NUMMER" und "1" ausgegeben.

Pythonscript "Rohdaten aufbereiten.py"

Code: Alles auswählen

importdatei = "Import.csv"
startstrings = ('"1"', "NUMMER")			# Suchstring für Start der Zeilen

# Daten aus Datei einlesen
handle = open(importdatei, "r")			# Datei im Lesemodus öffnen
inhalt = handle.readlines()				# Text ist zeilenweise in der Liste "inhalt"
handle.close()						# Datei schliessen

# Daten aus Datei einlesen
zeilennummer = 1
for line in inhalt:						# Start der Schleife für die Zielenzählung
  if line.startswith(startstrings) == True:	# Die "startstrings" muss erfolgreich gefunden werden.
    print (line)							# Ausgabe der Zeile
  zeilennummer = zeilennummer + 1	# nächste Zeile
Da dieses Pythonscript jedoch noch nicht die Verschiebung löst, hoffe ich hier den entsprechenden Impuls zu finden.
Meine Idee war, dass zunächst das Semikolon zwischen einem v.l.n.r. ungerade Anzahl vorkommenden Anführungszeichen und dem nächsten folgenden gerade Anzahl vorkommenden Anführungszeichen entfernt wird. Jedoch nicht zwischen einer geraden Anzahl vorkommenden Anführungszeichen und dem nächsten folgenden ungerade Anzahl vorkommenden Anführungszeichen entfernt wird.
Bild
Eine Vorbedingung hierfür könnte auch sein: Kopfzeile: "line().count(";") = aktuelle Zeile: "line().count(";")"
  • wahr --> nächster Schleifenschritt
  • falsch --> entferne das Semikolon zwischen ungeraden und geraden Anführungszeichen
Folgender Code sollte vor der Ausgabe noch angewendet werden:

Code: Alles auswählen

line.rstrip())   # rstrip() entfernt am Ende '\r', '\n' bzw. ' '
Ausgabedaten: "Ausgabe 1.csv":

Code: Alles auswählen

NUMMER;KUNDENIDENT;KUNDENBEZEICHNUNG;
"1";="0815";="MC Donald&aposs";
"1";="0816";="MC Donald's Berlin";
"1";="0817";="MC Donald's Bayern";
finale Ausgabedaten: "Ausgabe 2.csv":

Code: Alles auswählen

NUMMER;KUNDENIDENT;KUNDENBEZEICHNUNG;
1;0815;MC Donald&aposs;
1;0816;MC Donald's Berlin;
1;0817;MC Donald's Bayern;
Somit habe ich folgende Fragen:
  • Wie bekomme ich die Entfernten des Semikolon zwischen ungeraden und geraden Anführungszeichen hin?
  • Welche "Funktion" läuft eine Zeichenkette durch?
  • Wie kann ich die "bereinigten" Rohdaten in eine Datei ausgeben?
Benutzeravatar
__blackjack__
User
Beiträge: 14065
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@DB-Hopf Das ist dann nicht wirklich eine CSV-Datei, denn da gibt es ja eigentlich einen Mechanismus um auch Feldtrenner *und* Datensatztrenner *in* Feldern zu speichern. Die "="-Zeichen machen das aber kaputt. Ohne würde das funktionieren:

Code: Alles auswählen

In [264]: print(text)
Datum; 18.01.2023
Info; 19.98
NUMMER;KUNDENIDENT;KUNDENBEZEICHNUNG;
"1";="0815";="MC Donald's";
"1";="0816";="MC Donald's; Berlin";
"1";="0817";="MC Donald's Bayern";


In [265]: list(csv.reader(text.replace("=", "").splitlines(True), delimiter=";"))
Out[265]: 
[['Datum', ' 18.01.2023'],
 ['Info', ' 19.98'],
 ['NUMMER', 'KUNDENIDENT', 'KUNDENBEZEICHNUNG', ''],
 ['1', '0815', 'MC Donald's', ''],
 ['1', '0816', "MC Donald's; Berlin", ''],
 ['1', '0817', "MC Donald's Bayern", '']]
Wäre natürlich ein Hack da jetzt tatsächlich einfach global die "="-Zeichen zu entfernen, weil das zu Problemen führt wenn *die* dann irgendwo mal innerhalb eines Feldes vorkommen.

Sollen die ";" denn wirklich entfernt werden? Das verfälscht doch die Daten und CSV-Dateien können auch Trennzeichen innerhalb von Feldern haben. Bei "Donald&aposs" nimmt man sich zudem die Möglichkeit das HTML- oder XML-Entity "'" mal irgendwann durch dessen Definition "'" zu ersetzen.

Minimal gequoted hätte ich als Ergebnis eher das hier erwartet:

Code: Alles auswählen

NUMMER;KUNDENIDENT;KUNDENBEZEICHNUNG
1;0815;"MC Donald's"
1;0816;"MC Donald's; Berlin"
1;0817;MC Donald's Bayern
Echte CSV-Daten sollte man auch nicht manuell selber auseinanderpflücken oder zusammenbasteln, sondern das dem `csv`-Modul überlassen.

Die Kopfzeilen würde man auch besser nicht am Inhalt filtern, sondern an der Position, also die beiden ersten Zeilen einfach überlesen.

Und für die Felder würde ich mir einen regulären Ausdruck schreiben (und hoffen das die tatsächlichen Daten nicht noch mehr Regeln folgen/komplexer sind).

Das hier sollte zumindest für die gezeigten Daten funktionieren: '(=?"[^"]*"|.*?);'
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
DB-Hopf
User
Beiträge: 7
Registriert: Dienstag 17. Januar 2023, 09:41

__blackjack__ hat geschrieben: Mittwoch 18. Januar 2023, 14:18 Ich möchte mich für den Impuls und die schnelle Rückmeldung bedanken.

"Das ist dann nicht wirklich eine CSV-Datei, denn da gibt es ja eigentlich einen Mechanismus um auch Feldtrenner *und* Datensatztrenner *in* Feldern zu speichern.
Vermutlich stimmt dies. Ich wollte eher damit sagen, dass die "Rohdaten" aus einem anderen System "A" kommen und die Ausgabe in diesem System "A" (Format und Trennzeichen) nicht geändert werden können. In ein weiteres System "B" sollen diese Datensätze dann eingelesen werden.

Leider hatten die vorigen Versuche mit "replace" von '="' in '" zu größeren Problemen geführt.

Ich versuch mal mein Glück.
Benutzeravatar
DeaD_EyE
User
Beiträge: 1242
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Mal eben zusammengehackt.
Das funktioniert dann bis zum nächsten Fehler.

Code: Alles auswählen

import io, csv
import datetime

data = io.StringIO(
    """Datum; 18.01.2023
Info; 19.98
NUMMER;KUNDENIDENT;KUNDENBEZEICHNUNG;
"1";="0815";="MC Donald's";
"1";="0816";="MC Donald's; Berlin";
"1";="0817";="MC Donald's Bayern";"""
)

reader = csv.reader(data, delimiter=";")

datum = datetime.datetime.strptime(next(reader)[1].strip(), "%d.%m.%Y").date()
info = next(reader)[1].strip()
header = [head for head in next(reader) if head]
columns = len(header)

print(f"Kopf: {datum} / {info}")

for row in reader:
    row = [col.removeprefix('="').removesuffix('"') for col in row[:columns]]
    row_data = dict(zip(header, row))
    print(row_data)
Kopf: 2023-01-18 / 19.98
{'NUMMER': '1', 'KUNDENIDENT': '0815', 'KUNDENBEZEICHNUNG': 'MC Donald&apos'}
{'NUMMER': '1', 'KUNDENIDENT': '0816', 'KUNDENBEZEICHNUNG': "MC Donald's"}
{'NUMMER': '1', 'KUNDENIDENT': '0817', 'KUNDENBEZEICHNUNG': "MC Donald's Bayern"}
Man könnte auch mit dem DictReader arbeiten, aber da stören die beiden Zeilen vor dem Header.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
__blackjack__
User
Beiträge: 14065
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Da gehen jetzt aber Daten verloren. Beim ersten Datensatz fehlt ein "s", beim zweiten sogar " Berlin" das einfach unter den Tisch fällt.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
DeaD_EyE
User
Beiträge: 1242
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Stimmt, ist noch viel schlimmer, als ich dachte.
Hier die Korrektur:

Code: Alles auswählen

import io, csv
import datetime
import itertools

data = io.StringIO(
    """Datum; 18.01.2023
Info; 19.98
NUMMER;KUNDENIDENT;KUNDENBEZEICHNUNG;
"1";="0815";="MC Donald's";
"1";="0816";="MC Donald's; Berlin";
"1";="0817";="MC Donald's Bayern";
;;
asädfj;
"""
)


def strip_col(col):
    return col.removeprefix('="').removesuffix('"')


def strip_row(row):
    return (strip_col(col) for col in row)


def get_second(reader):
    return next(reader)[1].strip()


def parse_date(reader):
    line = get_second(reader)
    return datetime.datetime.strptime(line, "%d.%m.%Y").date()


reader = csv.reader(data, delimiter=";", quoting=1, quotechar='"')

datum = parse_date(reader)
info = get_second(reader)
header = [head for head in next(reader) if head]
columns = len(header)

print(f"Kopf: {datum} / {info}")
print()
print(", ".join(header))

for row in reader:
    if len(row) > columns:
        # Mehl Felder, als Überschriften im Header
        data = list(strip_row(row[:columns]))
        data[-1] = data[-1] + "".join(strip_row(row[columns:]))
    elif len(row) == columns:
        # Gleiche Anzahl der Felder wie im Header
        data = list(strip_row(row))

        # Wenn ein Feld leer ist, dann überspringen
        if any(not col for col in data):
            continue
    else:
        # weniger Felder als Header
        print(f"Fehler ->  {row}")
        continue

    print(" | ".join(data))
Ausgabe:
1 | 0815 | MC Donald&aposs
1 | 0816 | MC Donald's Berlin
1 | 0817 | MC Donald's Bayern
Fehler -> ['asädfj', '']
Das geht dann wieder bis zum nächsten unerwarteten Fehler gut.


Gibt es auch Berufe, wo man sich jemand den ganzen Tag mit kaputt-formatierten D̶a̶t̶e̶n̶ quälen muss?
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

@DeaD_EyE: das macht es auch noch nicht richtig, und auch nicht besser.
Man braucht einen Parser, der auch das Format parsen kann und keinen csv-Parser, der erst kaputte Daten erzeugt, die Du dann hinterher wieder zu reparieren versuchst.
Ich würde da ja mit einem einfachen regulären Ausdruck rangehen:

Code: Alles auswählen

import re

with open("Import.csv", encoding="utf8") as lines:
    data = [tuple(map(''.join, re.findall('(?:=?"(.*?)"|([^;]*))(?:;|\n$)', line))) for line in lines]
Benutzeravatar
sparrow
User
Beiträge: 4538
Registriert: Freitag 17. April 2009, 10:28

Was spricht dagegen nicht ";" durch "" zu ersetzen sondern ";=" durch ";"?
Auf den ersten Blick sollte das doch dann einer CSV-Dateien sehr nahe kommen.
Manul
User
Beiträge: 53
Registriert: Samstag 13. Februar 2021, 16:00

read_csv in Pandas scheint Delimiter von mehr als einem Zeichen Länge zu erlauben: https://pandas.pydata.org/docs/referenc ... d_csv.html

Allerdings steht dort auch
In addition, separators longer than 1 character and different from '\s+' will be interpreted as regular expressions and will also force the use of the Python parsing engine. Note that regex delimiters are prone to ignoring quoted data. Regex example: '\r\t'.
Käme auf einen Versuch an, ob das funktioniert.
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

›ignoring quoted data‹ bedeutet, dass es nicht richtig funktioniert.
Bloehdian
User
Beiträge: 2
Registriert: Mittwoch 18. Januar 2023, 23:23

Hm, das sieht ja irgendwie nach einer Problemstellung aus dem business-Bereich aus.

Da wäre ein Impuls, dass, wenn im Lastenheft (oder wie auch immer die Anforderung spezifiziert wurde), die Bereitstellung einer csv-Datei zugesichert wurde, dem Anforderer in den Hintern getreten wird, dass er auch bitte eine csv-Datei liefern solle und nicht eine solche Daten-Müllhalde. Oder eben ein der Natur der Daten besser entsprechendes Dateiformat geliefert wird , e.g. JSON oder YAML.

Ich finde es immer wieder erstaunlich, mit was für einem Schrott Entwickler zugemüllt werden und dann von Ihnen erwartet wird, die Unfähigkeit anderer durch ihre Kunsfertigkeit zu kompensieren.
Manul
User
Beiträge: 53
Registriert: Samstag 13. Februar 2021, 16:00

Aber "are prone to" bedeutet, daß sie dafür lediglich anfällig sind. Solange die Umstände hierfür nicht näher definiert sind, denke ich, es wäre einen Versuch wert.
Benutzeravatar
sparrow
User
Beiträge: 4538
Registriert: Freitag 17. April 2009, 10:28

@Manul: Da steht, dass die Funktion Quotes ignoriert. Damit ist nichts gewonnen aber der Panzer Pandas ausgepackt
Manul
User
Beiträge: 53
Registriert: Samstag 13. Februar 2021, 16:00

Gemäß meinen bescheidenen Englischkenntnissen steht das in etwa, daß Delimiter, die als Regex interpretiert werden, "anfällig dafür sind, Daten in Quotes zu ignorieren". Ich halte diese Formulierung ohne nähere Spezifikation für wenig hilfreich, Leser ander nicht heraus, daß Quotes auf jeden Fall ignoriert werden. War aber auch nur ein Vorschlag, man müsste sicher Zeit investieren, um herauszufinden, unter welchen Umständen Quotes dem nun tatsächlich ignoriert werden - und wäre, wenn man das rein experimentell täte, immer noch nicht sicher, jeden Fall erwischt zu haben, der in der Praxis wirklich vorkommt.
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Du hast die Reihenfolge vertauscht, "quoted data" heißt "Inputdaten, die Anführungszeichen verwenden".
Ein " prone to ignoring quoted data" übersetzt sich etwas ausführlicher zu, "es besteht die Gefahr, dass in Inputdaten, die Anführungszeichen verwenden, die Anführungszeichen nicht korrekt beachtet werden".
Es bedeutet also, dass nicht immer alle Anführungszeichen ignoriert werden, sondern nur, wenn innerhalb der Anführungszeichen Delimiter vorkommen.

Code: Alles auswählen

In [5]: pandas.read_csv(io.StringIO('1;2;4;"a;b;c"\n2;3;=5;"a;=b;d"'), delimiter=";", engine='python', header=None)
Out[5]: 
   0  1   2       3
0  1  2   4   a;b;c
1  2  3  =5  a;=b;d

In [6]: pandas.read_csv(io.StringIO('1;2;4;"a;b;c"\n2;3;=5;"a;=b;d"'), delimiter=";=?", engine='python', header=None)
Out[6]: 
   0  1  2   3  4   5
0  1  2  4  "a  b  c"
1  2  3  5  "a  b  d"
"prone to" muß man bei der Programmierung immer so lesen, dass das gewünschte Verhalten nicht in allen Fällen garantiert werden kann, und Fehler, die nur manchmal auftreten möchte man als Programmierer auf keinen Fall haben.
Benutzeravatar
DeaD_EyE
User
Beiträge: 1242
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Sirius3 hat geschrieben: Mittwoch 18. Januar 2023, 19:45 Ich würde da ja mit einem einfachen regulären Ausdruck rangehen:
Ist das dein Ernst?
Der reguläre Ausdruck ist jetzt keine Verbesserung und notwendige Anpassungen können dadurch noch komplexer werden.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
__blackjack__
User
Beiträge: 14065
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wenn der reguläre Ausdruck im Beispiel keine Daten übersieht ist er eine Verbesserung und ja, ich meinte meinen regulären Ausdruck auch ernst, weil der genau auf die Daten zutrifft, und sich an der Struktur orientiert. Falls der für die realen Daten nicht reicht und anfangen würde zu unübersichtlich zu werden, wäre mein nächster Schritt ein Parser mit so etwas wie Pyparser.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Antworten