Merging df mit period-Column und df mit date-column

mit matplotlib, NumPy, pandas, SciPy, SymPy und weiteren mathematischen Programmbibliotheken.
Antworten
liuniao
User
Beiträge: 2
Registriert: Freitag 18. August 2023, 20:07

Liebe KollegInnen,

nun bin ich auch endlich in diesem tollen Forum angemeldet. Ich bin Wissenschaftler mit fortgeschrittenen delphi- und python-Kenntnissen, aber kein gelernter ITler (learning by doing). Mit folgendem Problem komme ich leider nicht mehr alleine weiter und würde mich freuen über Tipps:

Kontext: ich untersuche Zeitreihen von Wortfrequenzen in sehr großen Textsammlungen (Korpora) unterschiedlicher Grundgesamtheit. Zu diesem Zweck ermittle ich die Verwendung eines Wortes in den Korpora. Anschließend aggregiere und visualisiere ich die Häufigkeiten über die Zeit in verschiedenen Intervallen (D, M, Y, 5Y usw) und relativ zur jeweiligen Grundgesamtheit. Um das machen zu können, muss ich den Datensatz mit den Häufigkeiten (enthält u.a.: Datum und Text-ID eines jeden Funds) mit dem Datensatz zur jeweiligen Grundgesamtheit auf einer identischen Periodisierung mergen.

Konkret will ich das (alles via Pandas) wie folgt umsetzen:
1) Lade Datensatz als df_corpus mit Angaben zu den Textgrößen [Text-ID, date, token-len]
2) Ermittle mindate und maxdate des Datensatzes und erstelle dann je nach Bedarf ein PeriodsArray mit freq=D/M/Y/5M..., der mindate und maxdate umfasst.
3) Merge df_corpus und PeriodsArray, so dass jedes Text-Date einer Period zugeordnet werden kann. Aggreggiere dann über die Periods (sum).
4) Schritte 1 und 3 auch für den Datensatz mit allen Worttreffern als df_frequencies.
5) Merge df_corpus und df_frequencies zur Berechnung relativer Frequenzen (z.B. f pro Mio)
6) Visualisierung (plt) über period mit klarer Explikation der jeweiligen Intervallgrenzen (z.B. 10.2000-11.2000, 12.2000-01.2001 usw.)

Mein Problem steckt bei Schritt 3: Wie kann ich effektiv (!) einen df mit datetime-Spalte mit einem Periodindex oder umgewandelt mit einem df (mit Start- und Enddate einer jeden Period) mergen?

df_corpus [text1, date=01.01.2000] <-> periods mit 2M ab 1.2.1990: [01.02.1990-31.03.1990, ... 01.02.2000-31.03.2000...] => df_corpus [text1, date=01.01.2000, period=01.12.1999-31.01.2000]

Die nachfolgende Lösung via apply und lambda (in def df_datecolumn_add_periodcolumn) ist völlig ineffektiv bei großen Datensätzen. Hat jemand einen Tipp? [resample scheint mein grundsätzliches Problem nicht zu lösen; merge_asof ist ungenau; Vektorisierung bekomme ich hier nicht hin]. Vielleicht stehe ich auch einfach auf dem Schlauch. :-/

Danke und viele Grüße,
Liuniao


Code: Alles auswählen

# Generiere PeriodIndex mit festem Intervall (D, M, Q, A-DEC), das sowohl Start- als auch Enddatum enthält
def generate_time_intervals(start_date, end_date, interval):   
    intervals = pd.period_range(start=pd.Period(start_date, freq=interval),
                    end=pd.Period(end_date, freq=interval), freq=interval)    

# Ergänze einen Dataframe mit einer Period-Spalte um einen String zur besseren Explikation des Intervalls
def AddPeriodStartEndStr(adfwithperiodcolumn, columnstr, strftimeformat = 'auto'):       
    if strftimeformat == 'auto':
        if adfwithperiodcolumn[columnstr][0].freqstr[-1] == 'D':
            strftimeformat = '%d.%m.%Y'
        if adfwithperiodcolumn[columnstr][0].freqstr[-1] == 'M':
            strftimeformat = '%m.%Y'
        if adfwithperiodcolumn[columnstr][0].freqstr[-5:] == 'A-DEC':
            strftimeformat = '%Y'    
    if adfwithperiodcolumn[columnstr][0].freqstr in ['D', 'M', 'A-DEC', 'Y']:
        adfwithperiodcolumn['periodstr'] = adfwithperiodcolumn[columnstr].apply(lambda period: period.start_time.strftime(strftimeformat))
    else:
        adfwithperiodcolumn['periodstr'] = adfwithperiodcolumn[columnstr].apply(lambda period: period.start_time.strftime(strftimeformat) + '-' + period.end_time.strftime(strftimeformat))    
    return adfwithperiodcolumn

# Erstelle periods und merge mit Korpus-DF
def df_datecolumn_add_periodcolumn(adfwithdates, intervall):
    def getperdiodofdate(adate, aperiodindex):
        for period in aperiodindex:
            if period.start_time <= adate <= period.end_time:
                return period    
    mindate = adfwithdates['date'].min()
    maxdate = adfwithdates['date'].max()   
    atimeintervallseries = generate_time_intervals(mindate, maxdate, intervall)    
    adfwithdates['period'] = adfwithdates['date'].apply(lambda x: getperdiodofdate(x, atimeintervallseries))    # <<-- ineffektiv
    adfwithdates = AddPeriodStartEndStr(adfwithdates, 'period', 'auto')  
    return adfwithdates
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@liuniao: Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).

Namen sollten keine kryptischen Abkürzungen enthalten oder gar nur daraus bestehen. Der Name soll dem Leser vermitteln was der Wert dahinter im Programm bedeutet, nicht zum rätseln zwingen. ADF kenne ich beispielsweise als „Amiga Disk Format“. Könnte aber auch für „Azure Data Factory“ stehen. OdervielleichtistesauchschwierigdiegrenzezwischenWortenrichtigzufindenwenndasallessodirektaneinenderklebt,ohneeinTrennzeichenzwischendenWorten. Letztlich gilt für den `a*`-Präfix das gleiche wie für Sachen wie `my_*`: Es ist überflüssig weil es dem Leser keinen Informationsgewinn liefert.

Grunddatentypen haben nichts in Namen verloren. Den Typen ändert man gar nicht so selten mal während der Programmentwicklung und dann muss man überall im Programm die betroffenen Namen ändern, oder man hat falsche, irreführende Namen im Quelltext.

`generate_time_intervals()` hat keinen Effekt und gibt `None` zurück. Was in `getperdiodofdate()` zu einem `TypeError` führt.

Hast Du Dir mal `pandas.Grouper` angeschaut, ob Du damit Deine Daten entsprechend gruppieren kannst?
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
liuniao
User
Beiträge: 2
Registriert: Freitag 18. August 2023, 20:07

__blackjack__ hat geschrieben: Samstag 19. August 2023, 10:50
Namen sollten keine kryptischen Abkürzungen enthalten oder gar nur daraus bestehen. [...]
Ja, danke, das sollte ich wissen, das ist die Macht der Gewohnheit, sry, wenn das hier zu Unleserlichkeit beigetragen hat.
__blackjack__ hat geschrieben: Samstag 19. August 2023, 10:50 `generate_time_intervals()` hat keinen Effekt und gibt `None` zurück. Was in `getperdiodofdate()` zu einem `TypeError` führt.
Hm, seltsam, bei mir läuft das problemlos.
__blackjack__ hat geschrieben: Samstag 19. August 2023, 10:50 Hast Du Dir mal `pandas.Grouper` angeschaut, ob Du damit Deine Daten entsprechend gruppieren kannst?
Ja, mit grouper habe ich bisher gearbeitet. Das Problem liegt darin, dass ich die beiden verschiedenen Datensätze auf ein gemeinsames Intervall mit definiertem Start- und Enddatum aggregieren muss. Und ich habe es nicht hinbekommen, pandas zu sagen, dass er einen df über date auf ein Startdatum hin aggreggiert, auch dann, wenn das Startdatum außerhalb des Datensatzes liegt (Bsp.: Intervalle 3Y ab 15.01.2010, aber erste DS gibt es erst ab 05.04.2011). Wenn das ginge, könnte ich mir (fast) das ganze Drumherum sparen.

Viele Grüße,
liuniao
Antworten