Matrixspalten mit Vektoren füllen

mit matplotlib, NumPy, pandas, SciPy, SymPy und weiteren mathematischen Programmbibliotheken.
Antworten
SimonOff
User
Beiträge: 5
Registriert: Sonntag 7. Juni 2020, 16:35

Hallo zusammen,

mein Problem ist folgendes:
Ich lade eine Tabelle (25x3) ein (Eingabedaten), möchte eine Ausgabedatei erzeugen mit in diesem Beispiel der Form 50x4.
In der Ausgabedatei sollen die erste und letzte Spalte verändert werden.
die 50 Zeilen und 4 Spalten entstehen daraus, dass ich die Eingabedatei einmal kopieren will --> 50 Zeilen und eine Spalte hinzufüge.

das ganze will ich in einer for Schleife machen, um später den Vorgang beliebig oft zu wiederholen.
Im Code wird der Einfachheit wegen nur die erste Spalte in der for Schleife bearbeitet.

Hier der Code, der nicht funktioniert... VIELEN DANK SCHON MAL :)
Fehlermeldung: line 28, in <module>
aData_3D[i*nM:(nM-1)*(i+1),0] = (aNodeNumber + i * nMaxNodes)
ValueError: output operand requires a reduction, but reduction is not enabled

# -*- coding: cp1252 -*-
# used libraries
import sys
import math
import os
import numpy as np

#------------- Daten einlesen ---------------------------------------------
sInNodes = "Platte_nodes.inp" # Name der Eingabedatei
sBaseDic = os.getcwd() # Verzeichnis
sInNodesD = sBaseDic + "\\" + sInNodes
aData_2D=np.genfromtxt(sInNodesD,delimiter=',',comments='*') # shape (25,3)
print np.shape(aData_2D)
print "\n"
#------------- Konstanten -------------------------------------------------
nMaxNodes = 1000000
nSmax = 5 # Gesamtdicke der Z-Koordinate /mm
nN = 2 # Anzahl der Punkte in Tiefenrichtung
nM = len(aData_2D) # Anzahl der Punkte aus der eingeladenen Datei (nM = 25)
nDeltaZ = nSmax / (nN - 1) # Änderung der Z-Koordinate
#------------- Vektoren aus Eingabedatei extrahieren ---------------------
aNodeNumber = aData_2D[:,0].reshape((nM,1)) # shape (25,1)
aNodePosition = aData_2D[:,1:3] # shape (25,2)
#------------ Matrix der Ausgabedatei ------------------------------------
aData_3D = np.zeros((nM*nN, 4)) # shape (50,4)
#--- Zuweisen der einzelnen Spalten 1 bis 4 der Ausgabedatei--------------
for i in range(nN-1):
aData_3D[i*nM:(nM-1)*(i+1),0] = (aNodeNumber + i * nMaxNodes)

# erster Schleifendurchgang: i = 0
# Zeilen 0 - 24, Spalte 0 --> Einträge in aData_3D(0:24,0) durch den Vektor aNodeNumber ersetzen
# zweiter Schleifendurchgang: i = 1
# Zeilen 25 - 49, Spalte 0 --> Einträge in aData_3D(25:49,0) durch den Vektor aNodeNumber*nMaxNodes ersetzen
einfachTobi
User
Beiträge: 510
Registriert: Mittwoch 13. November 2019, 08:38

Zum Code im Allgemeinen: Variablen schreibt man klein_mit_unterstrich. Konstanten werden KOMPLETT_GROSS geschrieben. Was sollen jeweils n, s und a vor den Variablen oder das D hinten dran? Verwende keine merkwürdigen Abkürzungen. Kommentare sollten aufzeigen warum etwas so gemacht wird, wie ist gemacht wird und nicht was dort gemacht wird - denn das steht ja schon da. Zumal der erste Kommentar eine Lüge ist, denn math und sys werden gar nicht verwendet und können daher, so wie der Kommentar, weg.
Im Speziellen:
Der Fehler entsteht, weil die Dimensionen nicht zusammenpassen. Ist gut zu prüfen mit einer Ausgabe des `shape`-Attributes.
Aber da gibt es ein viel größere Manko, denn: In der Regel läuft etwas verkehrt, wenn man mit Schleifen über Numpy-Array iterieren will. Was willst du dort eigentlich machen? Ich gehe stark davon aus, dass man das ohne Schleife besser hinkriegt.

Unabhängig vom Numpy-Kram:
Für die Pfade solltest du erstens pathlib und zweitens nicht getcwd() verwenden. sInNodes beinhaltet keine Knoten, sondern einen Dateinamen. Besserer Name: input_filename.

Code: Alles auswählen

from pathlib import Path

INPUT_FILENAME = "Platte_nodes.inp"
BASE_PATH = Path("C:/meinOrdner")

data_2d = np.genfromtext(BASE_PATH / INPUT_FILENAME, delimiter=",", comments="*")
print(data_2d.shape)
...
SimonOff
User
Beiträge: 5
Registriert: Sonntag 7. Juni 2020, 16:35

Danke für die schnelle Antwort!

Zu den Variablen: Danke für den Tipp zur Schreibweise. Die Buchstaben n uns a sollen mehr für mich sein (n für number, a für array).
Das Problem mit den Dimensionen verstehe ich nur zum Teil, da die zu befüllende Matrix die Form (50x4) hat, ich aber in der Schleife nur die erste Spalte mit einem Spaltenvektor füllen will.

Mein Ziel ist es die Eingabedatei (aData_2D) zweimal in die Ausgabedatei zu schreiben und die erste und vierte Spalte der Orginaldatei ggf. mit Rechenoperationen zu bearbeiten.
Die Schleife verwende ich, da ich in späteren Anwendungen diese Operation beliebig oft wiederholen will.
Benutzeravatar
__blackjack__
User
Beiträge: 13927
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@SimonOff: Bitte keine Grunddatentypen in die Namen einbauen. Das macht man nicht, auch nicht für sich selbst. Wähle die Namen so, das klar ist was ein Skalar ist und was ein Array. Und das `s` beim Pfad stimmt dann ja schon nicht mehr wenn man dafür ein `Path`-Objekt nimmt. Versuche am besten mal erst mal alles so zu benennen, dass die Namen keine kryptischen Abkürzungen enthalten und keine erklärenden Kommentare mehr nötig sind. Wenn man `sInNodes` zum Beispiel `input_filename` nennen würde, käme sicher niemand auf die Idee das wäre eine Zahl oder ein Array, und man bräuchte auch keinen Kommentar der dem Leser sagt, dass es sich dabei um den Namen der Eingabedatei handelt.

Der Hinweis mit den Schleifen bezog sich darauf das man Arrays in der Regel verwendet um die Schleifen nicht selbst im eigenen Code in Python schreiben zu müssen, sondern Numpy-Operationen dafür verwendet bei denen die Schleifen dann in der jeweiligen Operation stecken und in C implementiert sind.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Benutzeravatar
__blackjack__
User
Beiträge: 13927
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@SimonOff: Also vor den Namen solltest Du dringend noch etwas anderes fixen: Die Python-Version! Der Code ist für Python 2, das hat sein Lebensende bereits überschritten und das `numpy` ist dann auch schon nicht mehr aktuell, denn die haben die Entwicklung für Python 2 schon vor dem Ende der Python 2-Entwicklung eingestellt und sich auf Python 3 konzentriert.

Ausrichten von Gleichheitszeichen über mehrere Zeilen und ausgerichtete Kommentare am Zeilenende macht man nicht. Das ist aufwändig zu pflegen und führt potentiell zu Änderungen an Zeilen an denen sich inhaltlich gar nichts geändert hat.

Das aktuelle Arbeitsverzeichnis ermitteln und vor einen Dateinamen setzten ist nicht sinnvoll, denn wenn man nur den Dateinamen verwendet, bezieht der sich als relative Angabe ja bereits auf das aktuelle Arbeitsverzeichnis. Nur für den Fall: Man setzt Pfadteile nicht mit ``+`` zusammen und schon gar nicht mit einem hart kodierten Pfadtrenner. `os.path` hat dafür die `join()`-Funktion. Aber wie einfachTobi schon anmerkte, in neuem Code verwendet man dafür das `pathlib`-Modul.

Die `np.shape()`-Funktion ist hier recht umständlich um an das `shape`-Attribut von dem eingelesenen Array zu gelangen.

Der Kommentar ``Konstanten`` stimmt nicht ganz weil da nicht nur Konstanten definiert werden. Und die definiert man in der Regel am Anfang des Moduls.

Bei dem `reshape()` braucht man den `nM`-Wert nicht. Man kann bei einer Dimension eine -1 einsetzen und `reshape()` berechnet diesen Wert dann automatisch so dass es aufgeht.

Wenn ich das laufen lasse bekomme ich eine andere Ausnahme:

Code: Alles auswählen

Traceback (most recent call last):
  File "./forum20.py", line 56, in <module>
    main()
  File "./forum20.py", line 41, in main
    node_numbers + i * MAX_NODE_NUMBER
ValueError: could not broadcast input array from shape (25,1) into shape (24)
Für folgenden Code (ich habe da schon mal besser umbenannt):

Code: Alles auswählen

       data_3d[i * node_count : (node_count - 1) * (i + 1), 0] = (
            node_numbers + i * MAX_NODE_NUMBER
        )
Das ``node_count - 1`` ist der Schuldige, denn das Ende vom Slice ist der Index *nach* dem Element was als letztes enthalten sein soll. Das hast Du bei der ``for``-Schleife wahrscheinlich auch falsch gemacht, denn die wird nur *einmal* durchlaufen bei ``range(1)`` und nicht zwei mal.

Wenn man das repariert, dann stimmt die erste Dimension, aber die zweite nicht. Daran ist das `reshape()` schuld, das überflüssig ist.

Nochmal der Hinweis, dass es bei Numpy in der Regel ein „code smell“ ist, wenn man selber ``for``-Schleifen schreibt. Und auch das erstellen von leeren Arrays die dann mit Werten gefüllt werden kann ein Warnzeichen sein. Ich würde da deutlich funktionaler denken und beispielsweise überlegen wie man das durch Operationen mit dem originalen Array ausdrücken kann. Das zweimal untereinander zu haben ist beispielsweise `np.tile()`. Dann kann/muss man sich überlegen ob man da vorher oder nachher eine weitere Spalte anfügt, je nach dem ob man die Werte einmal für beide ”Kacheln” vorher berechnen kann, oder ob man dazu schon veränderte Werte in den anderen Spalten benötigt.

Falls `node_numbers` ganze Zahlen sind, und die anderen Spalten Gleitkommazahlen enthalten, könnte man auch über „record arrays“ nachdenken.

Code: Alles auswählen

#!/usr/bin/env python3
import numpy as np

MAX_NODE_NUMBER = 1_000_000
TOTAL_Z_THICKNESS = 5  # in mm.
REPETITION_COUNT = 2  # Anzahl der Punkte in Tiefenrichtung (FIXME Falsch‽)


def main():
    input_filename = "Platte_nodes.inp"
    #
    # shape (25,3)
    #
    # TODO Statt nur die Dimensionen zu kommentieren, wäre hier eine
    # Beschreibung der Daten, also was die Zeilen/Spalten bedeuten, für den
    # Leser interessant.
    #
    data_2d = np.genfromtxt(input_filename, delimiter=",", comments="*")
    print(data_2d.shape, end="\n" * 3)

    z_delta = TOTAL_Z_THICKNESS / (REPETITION_COUNT - 1)

    node_numbers = data_2d[:, 0]  # shape (25)
    node_positions = data_2d[:, 1:3]  # shape (25,2)

    data_3d = np.zeros((len(data_2d) * REPETITION_COUNT, 4))  # shape (50,4)
    data_3d[:, 0] = np.hstack(
        np.tile(node_numbers, (REPETITION_COUNT, 1))
        + (
            np.array(range(REPETITION_COUNT)).reshape((REPETITION_COUNT, -1))
            * MAX_NODE_NUMBER
        )
    )
    print(data_3d)


if __name__ == "__main__":
    main()
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
SimonOff
User
Beiträge: 5
Registriert: Sonntag 7. Juni 2020, 16:35

Ich habe mich jetzt mal mit deinem Code vertraut gemacht und das ist eine deutlich elegantere Lösungsvariante!
Vielen dank dir für die Hilfe! Ich konnte die Spalten 2 bis 4 auch mit 'np.tile()' füllen.

Jetzt ist nur noch ein kleines Problem aufgetreten und zwar sind in meiner Ausgabedatei data_3d jetzt alle Werte in der Exponentialdarstellung.
Kann man die Werte auch als Fließkommazahl darstellen?
Ich habe es mit 'dtype=float' versucht, aber es hat nicht geholfen.
Habt ihr da noch eine Idee?

Code: Alles auswählen

data_3d = np.zeros((len(data_2d) * REPETITION_COUNT, 4), dtype=float)  # shape (50,4)
    data_3d[:, 0] = np.hstack(
        np.tile(node_numbers, (REPETITION_COUNT, 1))
        + (
            np.array(range(REPETITION_COUNT)).reshape((REPETITION_COUNT, -1))
            * MAX_NODE_NUMBER))
Grüße Simon
Benutzeravatar
__blackjack__
User
Beiträge: 13927
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@SimonOff: Das ist ja auch keine Frage des Datentyps sondern wie die Zahlen beim speichern in die Datei formatiert werden. `numpy.savetxt()` hat da ein Argument für. Falls Du diese Funktion verwendest.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
SimonOff
User
Beiträge: 5
Registriert: Sonntag 7. Juni 2020, 16:35

@blackjack: Danke für die Antwort. Ich habe es mit 'np.savetxt()' versucht und auch fast richtig gelöst bekommen.
Das Problem ist jetzt, dass ich für meine erste Spalte von 'data_3D' das Format 'integer' möchte und für die restlichen 3 Splalten 'string'.

Ich habe es folgendermaßen versucht zu lösen:

Code: Alles auswählen

output_filename = "Platte_nodes_new.inp"
    
    fmt='%12i'+'%+12s'*3
    np.savetxt(output_filename, data_3d, fmt=fmt, delimiter=',')
Jetzt ist aber das Problem aufgetreten, dass ich in der neuen Datei keine Kommas als Trennzeichen habe, was wichtig ist.
So sieht es jetzt aus:

1 5.0 5.0 0.0
2 2.5 5.0 0.0
3 0.0 5.0 0.0
4 -2.5 5.0 0.0
5 -5.0 5.0 0.0

So hätte ich es gerne, nur mit der ersten Spalte als 'int':

1.0, 5.0, 5.0, 0.0
2.0, 2.5, 5.0, 0.0
3.0, 0.0, 5.0, 0.0
4.0, -2.5, 5.0, 0.0
5.0, -5.0, 5.0, 0.0

Hat jemand eine Idee, wie sich das beheben lässt?
Benutzeravatar
__blackjack__
User
Beiträge: 13927
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@SimonOff: Das sollte nicht verwundern denn das steht genau so in der Dokumentation zum `fmt`-Argument:
fmt : str or sequence of strs, optional

A single format (%10.5f), a sequence of formats, or a multi-format string, e.g. 'Iteration %d -- %10.5f', in which case `delimiter` is ignored. […]
Also entweder die zweite Möglichkeit benutzen Formate für die einzelnen Spalten anzugeben oder den Trenner halt mit in die eine Formatzeichenkette mit mehreren Platzhaltern aufnehmen.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
SimonOff
User
Beiträge: 5
Registriert: Sonntag 7. Juni 2020, 16:35

@blackjack: danke! Da hätte ich auch drauf kommen können, in die Dokumentation zu schauen. Habe die Trennzeichen direkt im Format verarbeitet.
Danke an alle für die Hilfe!
Antworten