Dataframe manipulieren

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
maGGTech
User
Beiträge: 21
Registriert: Donnerstag 21. Mai 2020, 12:11

Hallo zusammen,

ich bin immer noch bei der Ansatzfindung und am überlegen wie ich am besten anfangen kann. Leider habe ich nix vergleichbares im Internet gefunden - vielleicht könnt ihr mir ein paar Tipps geben.

Dafür habe ich einen kleinen Beispieldatensatz erstellt; Ausgangssituation:

Bild

ich möchte die Daten auf eine bestimme Art aufbereiten, und zwar so dass fortlaufend Längen für Classes (C1 bis C4) aufsummiert werden - und das dabei einerseits das Feld Position und andererseits das Feld NUMBER als "Trennung" dienen. Ich denke ein Bild beschreibt es besser:

Bild

Die Längen werden entsprechend mit (END - START) bestimmt.

Hier die Excel-Datei: https://easyupload.io/vb86lh
hier auf den Button klicken (nicht auf den anderen Kram):
Bild

Das hier ist bisher mein Beginn:

Code: Alles auswählen

import pandas as pd

d = {
    'ID': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40], 
    'CLASS': ['C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C'],
    'NUMBER': [21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22],
    'START': [0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000, 2100, 2200, 2300, 2400, 2500, 2600, 2700, 2800, 2900, 3000, 3100, 3200, 3300, 3400, 3500, 3600, 3700, 3800, 3900],
    'END': [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000, 2100, 2200, 2300, 2400, 2500, 2600, 2700, 2800, 2900, 3000, 3100, 3200, 3300, 3400, 3500, 3600, 3700, 3800, 3900, 4000],
    'POSITION': ['A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'A', 'A', 'A', 'A', 'A', 'A', 'A'],
    'VALUE': [4.4, 3.8, 3.7, 2.9, 1.7, 1.7, 2.8, 2.0, 2.5, 1.6, 1.9, 2.9, 2.7, 2.8, 3.5, 3.5, 2.7, 2.8, 3.5, 3.5, 2.7, 2.3, 1.3, 3.5, 3.6, 3.9, 3.6, 2.9, 2.2, 1.6, 1.1, 3.6, 3.9, 3.7, 3.6, 4.0, 3.6, 4.1, 5.0, 3.9]
    }

df = pd.DataFrame(data=d)

for number in df['NUMBER'].unique().tolist():

    print('to do')
Ja, wie man sieht bin ich noch nicht sicher wie ich das Ganze am besten angehen kann. Bin mir nicht sicher, ob ich mit .groupby() arbeiten muss, oder ob ich durch den Dataframe durchiterieren muss, oder ggf. sogar beides. Habt ihr Tipps?
derElch
User
Beiträge: 33
Registriert: Sonntag 25. Februar 2018, 13:14

Hallo maGGTech,

da ich gerade einiges mit Dataframes mache, hier ein Versuch der Lösung.
Mein Ansatz:
  • Die Farbe sind nur der "VALUE" Wert abgerundet.
  • Was mir aufgefallen ist, in Zeile 39 ist der Wert aufeinmal 5? und fällt aber in die "orange" Farbe.
  • In deiner Auswertung kommt aufeinmal die Spalte "SERIAL" vor. Keine Ahnung woher diese Daten kommen?
Nun zur Lösung. Ich gehe von deinem Dataframe als df aus.

Code: Alles auswählen

# Create column for C-columns
df['value_id'] = df['VALUE'].apply((np.floor)).astype('int')
# Scheinbar ist 4 der maximal Wert obwohl in Zeile 39 der Wert 5 ist, daher Korrektur auf maximum 4
df.loc[df['value_id'] > 4, 'value_id'] = 4

# Create a column for every  changing 'POSITION'
df['pos_shift'] = df['POSITION'].ne(df['POSITION'].shift()).cumsum()
df['value_id'] = 'C' + df['value_id'].astype(str)
df['delta'] = df['END'] - df['START']
Somit habe ich zunächst meine benötigten "helper"-Spalten. Jetzt geht es weiter zur Erstellung der C1 - Cn Spalten.

Code: Alles auswählen

df['IDnew'] = df['ID'] -1
df_piv = df.pivot(index='IDnew', columns='value_id', values='delta')
df_new = pd.concat([df, df_piv], axis=1)
Danach die lt. deiner Zusammenfassung nicht mehr gebrauchten Spalten löschen

Code: Alles auswählen

unnecessary_cols = ['ID', 'NUMBER', 'VALUE', 'value_id', 'delta', 'IDnew']
df_new.drop(columns=unnecessary_cols, inplace=True)
Da du hier alles mit int darstellst, gehe ich von int aus in den neuen Spalten:

Code: Alles auswählen

# Find all 'Cn' columns
c_cols = [col for col in df_new.columns
          if col.startswith('C') and len(col) == 2]

# Convert to int, and change NaNs to zero
df_new[c_cols] = df_new[c_cols].fillna(0)
df_new = df_new.astype({col : 'int64' for col in c_cols})
Und jetzt nur noch die Auswertung:

Code: Alles auswählen

# Define the aggregations for group
aggregations = {
    'START' : 'min',
    'END' : 'max',
    'POSITION' : 'max'}
aggregations.update({key : 'sum' for key in c_cols})

# Result Dataframe
result = df_new.groupby(['CLASS','pos_shift']).agg(aggregations).reset_index()
Somit bekomme ich folgendes Ergebnis (nur halt ohne dem mysteriösem "Serial'

Code: Alles auswählen

	CLASS  pos_shift  START   END POSITION   C1   C2   C3   C4
0     C          1      0  1000        A  300  400  200  100
1     C          2   1000  1700        B  100  400  200    0
2     C          3   1700  2400        A  100  300  300    0
3     C          4   2400  3300        B  200  200  500    0
4     C          5   3300  4000        A    0    0  400  300
Kleiner Edit: Ich habe die Position spalte vergessen, ist jetzt aber drin.
derElch
User
Beiträge: 33
Registriert: Sonntag 25. Februar 2018, 13:14

Bin beim durchgehen draufgekommen, das ich etwas in der Spalte "Number" übersehen habe.
Meine weitere Annahme ist:

Code: Alles auswählen

Serial = Number % 10
Daher ergibt das folgenden Code:

Code: Alles auswählen

# Create column for C-columns
# Scheinbar ist 4 der maximal Wert obwohl in Zeile 39 der Wert 5 ist, daher Korrektur auf maximum 4
df['value_id'] = df['VALUE'].apply((np.floor)).astype('int')
df.loc[df['value_id'] > 4, 'value_id'] = 4
df['value_id'] = "C" + df['value_id'].astype('str')

# Create a column for every  changing 'POSITION' and 'NUMBER'
df['categories'] = df['POSITION'].ne(df['POSITION'].shift()).cumsum() +\
                   df['NUMBER'].ne(df['NUMBER'].shift()).cumsum() - 1

df['delta'] = df['END'] - df['START']
df['Serial'] = df['NUMBER'] % 10

df['ID'] -= 1
df_piv = df.pivot(index='ID', columns='value_id', values='delta')
df_new = pd.concat([df, df_piv], axis=1)
# Drop unnecessary columns
unnecessary_cols = ['ID', 'NUMBER', 'VALUE', 'value_id', 'delta']
df_new.drop(columns=unnecessary_cols, inplace=True)

c_cols = [col for col in df_new.columns
          if col.startswith('C') and len(col) == 2]

# Convert to int, and change NaNs to zero
df_new[c_cols] = df_new[c_cols].fillna(0)
df_new = df_new.astype({col : 'int64' for col in c_cols})
df_new['categories'] = df_new['categories'].astype('int')

# Define the aggregations for group
aggregations = {
    'START' : 'min',
    'END' : 'max',
    'POSITION' : 'max'}
aggregations.update({key : 'sum' for key in c_cols})

# Result Dataframe
result = df_new.groupby(['categories', 'CLASS', 'Serial']).agg(aggregations).reset_index().drop(columns='categories')
print(result)
Ergibt dann folgendes Ergebnis:

Code: Alles auswählen

     CLASS  Serial  START END POSITION   C1   C2   C3   C4
0     C       1      0  1000        A  300  400  200  100
1     C       1   1000  1700        B  100  400  200    0
2     C       1   1700  2000        A    0  100  200    0
3     C       2   2000  2400        A  100  200  100    0
4     C       2   2400  3300        B  200  200  500    0
5     C       2   3300  4000        A    0    0  400  300
Antworten