Seite 1 von 1

Null-Values im pd.DF durch gruppenbezogene Durchschnittswerte ersetzen

Verfasst: Samstag 25. April 2020, 14:01
von nutelli
Hallo zusammen!

Dies ist seit sehr sehr sehr langer Zeit das erste Mal, dass ich in einem neuen Forum einen ersten Post abgebe und ich bin gespannt wie die Python-Community hier so drauf ist :wink:
Außerdem ist es gleichermaßen spannend mal wieder auf der Seite der Fragesteller zu stehen, denn ich habe erst vor kurzem damit angefangen Pyhton zu lernen...

Kurze Vorstellung gefällig? Okay, ich bin 34, Handwerker mit IT-Hintergrund und begeisterter "Neue-Sachen-Lerner" - außerdem schreibe ich derzeit an meiner Masterarbeit im Bereich KI. In den letzten Wochen habe ich mich durch die kaggle.com-Micro-Kurse, sowie einige Online-Tutorials geschlagen um eine erste praktische Übersicht über die Möglichkeiten von Python, pandas und Co., speziell im Hinblick auf das "Verschneiden" von Daten zwecks arbeiten mit neuronalen Netzen zu bekommen und habe schon einige Eigenschaften der Sprache kennengelernt, die mir in anderen Sprachen bisher gefehlt haben. Aber genug dazu und bei Fragen, fragen^^

Nun aber zu meinem Anliegen:

Ich habe einen Datensatz, der folgendermaßen aussieht:

Code: Alles auswählen

bezirk;adrortsteil;ebekomplex;stiwo;wache;datuhr;datuhranleg;uhralarm;uhr3;uhr4;uhr7;uhr8;uhr1;uhr2
8;OHM;1;FEU1;08;2007-01-02 09:06:05;2007-01-02 09:07:03;2007-01-02 09:07:39;2007-01-02 09:08:16;NULL;NULL;NULL;NULL;2007-01-02 13:56:29
5;NIP;INTERN2;05;2007-01-02 09:11:58;2007-01-02 09:13:03;2007-01-02 09:14:41;2007-01-02 09:14:53;NULL;NULL;NULL;NULL;2007-01-02 09:39:41
Hierin enthalten sind Metadaten von Rettungsdienst-Einsätzen mit verschiedenen Zeitangaben, welche eine Abfolge darstellen (Alarmdisposition, Alarmierung, FMS-Statusfolge).
Ich möchte nun fehlende Zeiten (NULL oder im DF NaT) durch spezielle mean-Values ersetzen.

Hierbei bin ich bisher wie folgt vorgegangen:

Ich habe Kombinationsgruppen gebildet, von denen ich die Differenz von Zeit A zu Zeit B berechne und am Ende über die jeweilige einen Durchschnitt für die jeweilige Gruppe bilde.
Das Ergebnis ist am Ende ein neuer DF, der zu jeder Kombination "stiwo", "jahr", "wache" die entsprechenden Durchschnittszeiten enthält. Nun möchte ich diese Durchschnittszeiten an jenen Stellen in den Rohdaten einfügen, bei denen Zeitfelder leer/null sind.

Könnt ihr mein Problem bitte lösen, denn ich kann eigentlich gar kein Python und bin auch zu faul :lol: (hab grade den Einsteiger-Post gelesen ^^)

Folgendes habe ich bisher (wahrscheinlicch viel zu umständlich) gebbaut:

Code: Alles auswählen

import pandas as pd

 

data_file = 'archiv.1k.csv'
data_file = 'archiv.2.csv'
#data_file = 'archiv.10k.csv'
#data_file = 'archiv.100k.csv'

#data_file = 'archiv.csv'

 
###########################################
#   Daten einlesen
###########################################

date_columns = ['datuhr', 'datuhranleg', 'uhralarm', 'uhr3', 'uhr4', 'uhr7', 'uhr8', 'uhr1', 'uhr2']

raw_data = pd.read_csv(data_file, delimiter=';', index_col='id', parse_dates=date_columns)
#raw_data.drop(columns=['plqr', 'plqh'], inplace=True)
 
drop_columns = ['bezirk', 'adrortsteil', 'ebekomplex', 'datuhr', 'datuhranleg', 'uhralarm', 'uhr3', 'uhr4', 'uhr7', 'uhr8', 'uhr1', 'uhr2']


def get_mean_times(data, combinations, agg_groups, agg_func):
    """
    Splittet data anhand cominations in neue DataFrames mit den in combinations enthaltenen
    Feldern. Filtert NaT/null-Values und berechnet die Differenz von comb in cominations zwischen
    comb[0] und comb[1]. Anschließend werden die Datensätze nach stiwo, jahr und wache gruppiert
    und für jede Kombination der Mittelwert berechnet.

    Dieser Gruppen-Mittelwert wird dazu verwendet fehlende Datensätze aufzufüllen.
    """
    result = pd.DataFrame()

    for comb in combinations:
        dc = set(drop_columns) - set(comb)
        dv = data.loc[data[comb[0]].notnull() & data[comb[1]].notnull()].drop(columns=dc)   

        diffcol = comb[0] + '_' + comb[1]
        dv['jahr'] = dv[comb[0]].dt.year
        dv[diffcol] = dv[comb[1]].sub(dv[comb[0]], axis=0).astype('timedelta64[s]')
        dv.drop(columns=comb, inplace=True)

        group = dv.groupby(agg_groups).mean().reset_index()

        if result.size == 0:
            result = group
        else:
            result = pd.merge(result, group, on=['stiwo', 'jahr', 'wache'])

    return result
 


combinations = [
    ['datuhr', 'uhralarm'], #Zuteilung der Einsatznummer - Alarmierung
    ['uhralarm', 'uhr3'],   #Alarmierung bis Auftrag übernommen
    ['uhr3', 'uhr4'],       #Auftrag übernomen bis E-Stelle an
    ['uhr4', 'uhr7'],       #E-Stelle an bis Patient aufgenommen
    ['uhr7', 'uhr8'],       #Patient augenommen bis Krankenhaus an
    ['uhr8', 'uhr1'],       #Krankenhaus an bis Einsatzbereit über Funk
    ['uhr1', 'uhr2'],       #Einsatzbereit über Funk bis Einsatzbereit auf Wache
    ['uhralarm', 'uhr1']    #Alarmiert bis Einsatzbereit
]

agg_groups = ['stiwo', 'jahr', 'wache']
agg_func = ['mean']

mean_times = get_mean_times(raw_data, combinations, agg_groups, agg_func)

Dabei steht am Ende in mean_times folgender Inhalt:

Code: Alles auswählen

     stiwo  jahr  wache  datuhr_uhralarm  uhralarm_uhr3  uhr3_uhr4  uhr4_uhr7  uhr7_uhr8  uhr8_uhr1  uhr1_uhr2  uhralarm_uhr1
1  INTERN1  2007      2           175.75          28.75       11.0        8.0       12.0        8.0        7.0          135.0
2  INTERN2  2007      4           118.00          10.00        5.0      100.0        8.0        5.0        6.0          128.0
3  INTERN2  2007      5           537.25          37.50       11.5       22.0       27.0        7.0       12.0           81.5
Dieser ist wie folgt zu interpretieren: Für jedes stiwo (Stichwort) existiert für jedes Jahr und jede Wache eine Durchschnittszeit für jeden Statusübergang, sodass ich damit in raw_data die fehlenden Zeitinformationen aufüllen könnte... Wenn ich einen Plan hätte wie..

Die Grundidee war, ähnlich wie mit anderen Sprachen und/oder SQL durch die Datensätze zu iterieren und für jeden Datensatz zu prüfen ob eine Zeit fehlt und wenn ja welche und diese dann aus den mean_times abzufragen. Beispiel:

Code: Alles auswählen

8;OHM;1;FEU1;08;2007-01-02 09:06:05;2007-01-02 09:07:03;2007-01-02 09:07:39;2007-01-02 09:08:16;NULL;NULL;NULL;NULL;2007-01-02 13:56:29
Hier fehlen uhr4, uhr7, uhr8, uhr1, also würde ich in meiner Denke für diesen Datensatz so etwas in dieser Art abfragen:

Code: Alles auswählen

mean_for_current = mean_times.loc[(mean_times.stiwo == 'FEU1') & (mean_times.jahr==2007) & (mean_times.wache == 8)]

0     FEU1  2007      8           104.00          93.00        8.0    17240.0        4.0        7.0        8.0        17352.0	#Beispiel
...und hätte die Liste der entsprechenden Durchschnittszeiten für genau diesen Roh-Datensatz.

Nun müsste der Wert uhr3_uhr4 (was dem Timedelta entspricht) auf den Wert in uhr3 addiert und in uhr4 geschrieben werden, usw...


Hier fehlt mir nun leider gänzlich eine sinnvolle Idee. Was wären geeignete Methoden von pandas um soetwas zu tun?
Ist meine grundsätzliche Vorgehensweise okay oder zu stark Java & Co. belastet? Wenn ja, wie macht man es besser?

Danke für eure Anregungen!
nutelli

Re: Null-Values im pd.DF durch gruppenbezogene Durchschnittswerte ersetzen

Verfasst: Sonntag 26. April 2020, 12:41
von nutelli
Servus zusammen!

Hier mal mein aktueller Zwischenstand, in Ergänzung zu o.g. Fragestellung:

Code: Alles auswählen

def raw_data_fillna(data, mean_times, mean_non_selective):
    #Durchschnittszeit-Gruppe auswählen
    mean_selective = mean_times.loc[
        (mean_times.stiwo == data.stiwo) & 
        (mean_times.jahr  == data.uhralarm.year) & 
        (mean_times.wache == data.wache)
    ]

    #Wenn keine Zeit-Gruppe existiert, Gesamtdurchschnitt übernehmen
    if mean_selective.empty:
        mean_for_current = mean_non_selective
    else:
        mean_for_current = mean_selective.drop(columns=agg_groups).mean()

    #Zeitfelder 3-4-7-8-1-2 prüfen und ggf auffüllen
    if (pd.isnull(data.uhr3)):
        data.uhr3 = data.uhralarm + pd.to_timedelta(mean_for_current.uhralarm_uhr3, unit='s')

    if (pd.isnull(data.uhr4)):
        data.uhr4 = data.uhr3 + pd.to_timedelta(mean_for_current.uhr3_uhr4, unit='s')

    if (pd.isnull(data.uhr7)):
        data.uhr7 = data.uhr4 + pd.to_timedelta(mean_for_current.uhr4_uhr7, unit='s')

    if (pd.isnull(data.uhr8)):
        data.uhr8 = data.uhr7 + pd.to_timedelta(mean_for_current.uhr7_uhr8, unit='s')

    if (pd.isnull(data.uhr1)):
        data.uhr1 = data.uhr8 + pd.to_timedelta(mean_for_current.uhr8_uhr1, unit='s')

    if (pd.isnull(data.uhr2)):
        data.uhr2 = data.uhr1 + pd.to_timedelta(mean_for_current.uhr1_uhr2, unit='s')

    return data


combinations = [
    ['datuhr', 'uhralarm'], #Zuteilung der Einsatznummer - Alarmierung
    ['uhralarm', 'uhr3'],   #Alarmierung bis Auftrag übernommen
    ['uhr3', 'uhr4'],       #Auftrag übernomen bis E-Stelle an
    ['uhr4', 'uhr7'],       #E-Stelle an bis Patient aufgenommen
    ['uhr7', 'uhr8'],       #Patient augenommen bis Krankenhaus an
    ['uhr8', 'uhr1'],       #Krankenhaus an bis Einsatzbereit über Funk
    ['uhr1', 'uhr2'],       #Einsatzbereit über Funk bis Einsatzbereit auf Wache
    ['uhralarm', 'uhr1']    #Alarmiert bis Einsatzbereit
]

agg_groups = ['stiwo', 'jahr', 'wache']
agg_func = ['mean']

#Berechnet die Durchschnittswerte / Intervalle der Statuswechselzeiten
mt = get_mean_times(raw_data, combinations, agg_groups, agg_func)

#Gesamtdurchschnitt bilden um Fälle abzufangen, für die keine Zeitgruppe existiert
mns = mt.drop(columns=agg_groups).mean()

#Füllt die missing-values der Rohdaten mit den spezifischen Durchschnittsdaten
raw_data = raw_data.apply(raw_data_fillna, args=(mt, mns), axis=1)
Ich bin sicher, dass das noch schöner geht!?

VG
nutelli