Seite 1 von 1

Zeichenposition und Häufigkeit

Verfasst: Mittwoch 18. Januar 2023, 13:20
von DB-Hopf
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?

Re: Zeichenposition und Häufigkeit

Verfasst: Mittwoch 18. Januar 2023, 14:18
von __blackjack__
@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: '(=?"[^"]*"|.*?);'

Re: Zeichenposition und Häufigkeit

Verfasst: Mittwoch 18. Januar 2023, 14:56
von DB-Hopf
__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.

Re: Zeichenposition und Häufigkeit

Verfasst: Mittwoch 18. Januar 2023, 16:18
von DeaD_EyE
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.

Re: Zeichenposition und Häufigkeit

Verfasst: Mittwoch 18. Januar 2023, 16:50
von __blackjack__
Da gehen jetzt aber Daten verloren. Beim ersten Datensatz fehlt ein "s", beim zweiten sogar " Berlin" das einfach unter den Tisch fällt.

Re: Zeichenposition und Häufigkeit

Verfasst: Mittwoch 18. Januar 2023, 19:11
von DeaD_EyE
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?

Re: Zeichenposition und Häufigkeit

Verfasst: Mittwoch 18. Januar 2023, 19:45
von Sirius3
@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]

Re: Zeichenposition und Häufigkeit

Verfasst: Mittwoch 18. Januar 2023, 19:58
von sparrow
Was spricht dagegen nicht ";" durch "" zu ersetzen sondern ";=" durch ";"?
Auf den ersten Blick sollte das doch dann einer CSV-Dateien sehr nahe kommen.

Re: Zeichenposition und Häufigkeit

Verfasst: Mittwoch 18. Januar 2023, 20:37
von Manul
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.

Re: Zeichenposition und Häufigkeit

Verfasst: Mittwoch 18. Januar 2023, 22:48
von Sirius3
›ignoring quoted data‹ bedeutet, dass es nicht richtig funktioniert.

Re: Zeichenposition und Häufigkeit

Verfasst: Mittwoch 18. Januar 2023, 23:35
von Bloehdian
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.

Re: Zeichenposition und Häufigkeit

Verfasst: Donnerstag 19. Januar 2023, 00:37
von Manul
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.

Re: Zeichenposition und Häufigkeit

Verfasst: Donnerstag 19. Januar 2023, 06:23
von sparrow
@Manul: Da steht, dass die Funktion Quotes ignoriert. Damit ist nichts gewonnen aber der Panzer Pandas ausgepackt

Re: Zeichenposition und Häufigkeit

Verfasst: Donnerstag 19. Januar 2023, 08:15
von Manul
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.

Re: Zeichenposition und Häufigkeit

Verfasst: Donnerstag 19. Januar 2023, 09:38
von Sirius3
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.

Re: Zeichenposition und Häufigkeit

Verfasst: Donnerstag 19. Januar 2023, 17:41
von DeaD_EyE
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.

Re: Zeichenposition und Häufigkeit

Verfasst: Donnerstag 19. Januar 2023, 17:50
von __blackjack__
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.