Ich arbeite an der Textanalyse und versuche, den Wert des Satzes als die Summe des Wertes zu quantifizieren, der einigen Wörtern zugewiesen wurde, wenn sie im Satz enthalten sind. Ich habe einen DF mit Wörtern und Werten wie:

import pandas as pd
df_w = pd.DataFrame( { 'word': [ 'high', 'sell', 'hello'],
                          'value': [ 32, 45, 12] } )

Dann habe ich Sätze in einem anderen DF wie:

df_s = pd.DataFrame({'sentence': [ 'hello life if good',
                                   'i sell this at a high price',
                                   'i sell or you sell'] } )

Jetzt möchte ich eine Spalte in df_s mit der Summe des Werts jedes Wortes im Satz hinzufügen, wenn sich das Wort in df_w befindet. Dazu habe ich versucht:

df_s['value'] = df_s['sentence'].apply(lambda x: sum(df_w['value'][df_w['word'].isin(x.split(' '))]))

Das Ergebnis ist:

                      sentence  value
0           hello life if good     12
1  i sell this at a high price     77
2           i sell or you sell     45

Mein Problem mit dieser Antwort ist, dass ich für den letzten Satz i sell or you sell zweimal sell habe und 90 (2 * 45) erwartet habe, aber sell nur einmal berücksichtigt wurde, sodass ich 45 bekam .

Um dies zu lösen, habe ich beschlossen, ein Wörterbuch zu erstellen und dann ein apply zu erstellen:

dict_w = pd.Series(df_w['value'].values,index=df_w['word']).to_dict()
df_s['value'] = df_s['sentence'].apply(lambda x: sum([dict_w[word] for word in x.split(' ') if word in dict_w.keys()]))

Dieses Mal ist das Ergebnis das, was ich erwartet habe (90 für den letzten Satz). Mein Problem tritt jedoch bei größeren DF auf, und die Zeit zum Ausführen der Methode mit dict_w ist ungefähr 20 Mal länger als die Methode mit isin für meinen Testfall.

Kennen Sie eine Möglichkeit, den Wert eines Wortes mit seinem Auftreten in der Methode mit isin zu multiplizieren? Jede andere Lösung ist ebenfalls willkommen.

2
Ben.T 18 Apr. 2018 im 18:05

3 Antworten

Beste Antwort

Dank der Antwort von piRSquared mit seiner map -Funktion hatte ich die Idee, Merge zu verwenden, wie zum Beispiel:

df_s['value'] = df_s['sentence'].apply(lambda x: sum(pd.merge(pd.DataFrame({'word':x.split(' ')}),df_w)['value']))

Dank der Antwort von Wen mit seiner stack -Funktion verwende ich seine Idee, aber mit merge wie:

df_stack = pd.DataFrame({'word': df_s['sentence'].str.split(' ',expand=True).stack()})
df_s['value'] = df_stack.reset_index().merge(df_w).set_index(['level_0','level_1'])['value'].sum(level=0)

Und beide Methoden geben mir die richtige Antwort. Um zu testen, welche Lösung schneller ist, definiere ich Funktionen wie:

def sol_dict (df_s, df_w): # answer with a dict
    dict_w = pd.Series(df_w['value'].values,index=df_w['word']).to_dict()
    df_s['value'] = df_s['sentence'].apply(lambda x: sum([dict_w[word] for word in x.split(' ') if word in dict_w.keys()]))
    return df_s

def sol_wen(df_s, df_w): # answer of Wen
    s=df_s.sentence.str.split(' ',expand=True).stack()
    df_s['value']=s[s.isin(df_w.word)].replace(dict(zip(df_w.word,df_w.value))).sum(level=0)
    return df_s

def sol_pi (df_s, df_w): # answer of piRSquared
    dw = lambda x: dict(zip(df_w.word, df_w.value)).get(x, 0)
    df_s.assign(value=[sum(map(dw, s.split())) for s in df_s.sentence])
    # or df_s['value'] = [sum(map(dw, s.split())) for s in df_s.sentence]
    return df_s

def sol_merge(df_s, df_w): # answer with merge 
    df_s['value'] = df_s['sentence'].apply(lambda x: sum(pd.merge(pd.DataFrame({'word':x.split(' ')}),df_w)['value']))
    return df_s

def sol_stack(df_s, df_w): # answer with stack and merge
    df_stack = pd.DataFrame({'word': df_s['sentence'].str.split(' ',expand=True).stack()})
    df_s['value'] = df_stack.reset_index().merge(df_w).set_index(['level_0','level_1'])['value'].sum(level=0)
    return df_s

Meine "großen" Test-DFs bestanden aus ungefähr 3200 Wörtern in df_w und ungefähr 42700 Wörtern in df_s (einmal alle Sätze aufgeteilt). Ich führe timeit mit mehreren Größen von df_w (von 320 bis 3200 Wörtern) mit der vollen Größe von df_s und dann mit mehreren Größen von df_s (von 3500 bis 42700 Wörtern) mit der vollen Größe von df_w aus. Nachdem ich meine Ergebnisse an die Kurve angepasst hatte, erhielt ich: Bildbeschreibung hier eingeben

Unabhängig von der Größe beider DFs ist die Methode mit stack und dann merge sehr effizient (ca. 100 ms, in Diagrammen leider nicht wirklich sichtbar). Ich führe es auf meinen DFs in voller Größe mit ungefähr 54.000 Wörtern in df_w und 2,4 Millionen Wörtern in df_s aus und erhalte die Ergebnisse in wenigen Sekunden. Vielen Dank für Ihre Ideen.

0
Ben.T 19 Apr. 2018 im 20:42

Sie können str.split mit stack verwenden und das Ergebnis filtern (isin), replace diese Schlüsselwörter bewerten und dann zurückweisen

s=df_s.sentence.str.split(' ',expand=True).stack()
df_s['Value']=s[s.isin(df_w.word)].replace(dict(zip(df_w.word,df_w.value))).sum(level=0)
df_s
Out[984]: 
                      sentence  Value
0           hello life if good     12
1  i sell this at a high price     77
2           i sell or you sell     90
2
YOBEN_S 18 Apr. 2018 im 15:13

Erstellen Sie eine Funktion mit einem Standardwert aus der get -Methode eines Wörterbuchs

dw = lambda x: dict(zip(df_w.word, df_w.value)).get(x, 0)
df_s.assign(value=[sum(map(dw, s.split())) for s in df_s.sentence])

                      sentence  value
0           hello life if good     12
1  i sell this at a high price     77
2           i sell or you sell     90
2
piRSquared 18 Apr. 2018 im 15:31