【问题标题】:Lookup in 'dictionary' with pandas dataframes instead of for loops使用 pandas 数据帧而不是 for 循环在“字典”中查找
【发布时间】:2020-11-27 22:51:15
【问题描述】:

我有 2 个数据框:

充当带有列的字典:

  • “分数”
  • “翻译”
  • 具有不同单词变体的多个列

另一种一栏:“句子”

目标是:

  • 将句子分成单词
  • 在字典中查找单词(在不同的列中)并返回分数
  • 将分数最高的单词的分数作为“句子分数”
df_sentences = pd.DataFrame([["I run"], 
    ["he walks"], 
    ["we run and walk"]], 
    columns=['Sentence'])

df_dictionary = pd.DataFrame([[10, "I", "you", "he"], 
    [20, "running", "runs", "run"], 
    [30, "walking", "walk", "walks"]], 
    columns=['score', 'variantA', 'variantB', 'variantC'])

Out[1]: 
   Sentence           Score
0  "I run"             30
1  "he walks"          40
2  "we run and walk"   "error 'and' not found"

我在使用 for 循环和列表方面已经走了很长一段路,但这很慢,所以我正在寻找一种工作方式,让我可以在 pandas 数据框中完成所有/大部分工作。

这就是我使用 for 循环的方式:

for sentence in textaslist[:1]:
words = split_into_words(sentence)[0] # returns list of words
length = split_into_words(sentence)[1] #returns number of words
if minsentencelength <= length <= maxsentencelength: # filter out short and long sentences                                                     
    for word in words:
        score = LookupInDictionary.lookup(word, mydictionary)
        if str(score) != "None":
            do_something()
        else:
            print(word, " not found in dictionary list")
            not_found.append(word)      # Add word to not found list     
                                                   
print("The following words were not found in the dictionary: ", not_found)

使用

def lookup(word, df):
if word in df.values:                                                       # Check if the dictionary contains the word
    print(word,"was found in the dictionary")
    lookupreturn = df.loc[df.values == word,'score']                         # find the score of each word (first column)
    score = lookupreturn.values[0]                                           # take only the first instance of the word in the dictionary
    return(bare)   

问题是当我使用pandas“合并”功能时,我需要使用right_on left_on参数指定在哪一列中查找,我似乎无法找到如何在整个字典数据框中搜索并返回第一个以有效方式显示分数的列

【问题讨论】:

  • 请提供一小部分示例数据作为我们可以复制和粘贴的文本。包括相应的期望结果。查看how to make good reproducible pandas examples 上的指南。
  • 我添加了一些示例数据,希望现在更清楚:-)

标签: python pandas dataframe


【解决方案1】:

如果您以[word, score]这种格式修改您的字典,那么您可以按单词拆分句子,然后与字典合并groupby并求和。
由于这使用了 pandas 函数,因此对于您的数据集应该足够快,但不确定是否可以使其比这更快。

tl;博士

df_sentences = pd.DataFrame([["I run"], 
    ["he walks"], 
    ["we run and walk"]], 
    columns=['Sentence'])

df_dictionary = pd.DataFrame([[10, "I", "you", "he"], 
    [20, "running", "runs", "run"], 
    [30, "walking", "walk", "walks"]], 
    columns=['score', 'variantA', 'variantB', 'variantC'])

df_dictionary = pd.melt(df_dictionary, id_vars=['score'])[['value', 'score']]

df_sentences['words'] = df_sentences['Sentence'].str.split()
df_sentences = df_sentences.explode('words')

sentence_score = df_sentences.merge(df_dictionary, how='left', left_on='words', right_on='value')[['Sentence', 'score']]

sentence_score_sum = sentence_score.fillna('NaN').groupby('Sentence').sum()
# or
sentence_score_max = sentence_score.fillna('NaN').groupby('Sentence').max()

分解

要将字典修改为[word, score] 格式,您可以像这样使用melt

df_dictionary = pd.DataFrame([[10, "I", "you", "he"], 
    [20, "running", "runs", "run"], 
    [30, "walking", "walk", "walks"]], 
    columns=['score', 'variantA', 'variantB', 'variantC'])
df_dictionary = pd.melt(df_dictionary, id_vars=['score'])[['value', 'score']]

这会给你

     value  score
0        I     10
1  running     20
2  walking     30
3      you     10
4     runs     20
5     walk     30
6       he     10
7      run     20
8    walks     30

现在要使用句子,我们希望能够在跟踪主句的同时单独提取每个单词。 让我们添加一个包含单词作为列表的新列

df_sentences = pd.DataFrame([["I run"], 
    ["he walks"], 
    ["we run and walk"]], 
    columns=['Sentence'])

df_sentences['words'] = df_sentences['Sentence'].str.split()

这会给我们带来什么

          Sentence                 words
0            I run              [I, run]
1         he walks           [he, walks]
2  we run and walk  [we, run, and, walk]

然后explode的话

df_sentences = df_sentences.explode('words')

给你的

          Sentence  words
0            I run      I
0            I run    run
1         he walks     he
1         he walks  walks
2  we run and walk     we
2  we run and walk    run
2  we run and walk    and
2  we run and walk   walk

现在我们merge 在一起

sentence_score = df_sentences.merge(df_dictionary, how='left', left_on='words', right_on='value')[['Sentence', 'score']]

给我们

          Sentence  score
0            I run   10.0
1            I run   20.0
2         he walks   10.0
3         he walks   30.0
4  we run and walk    NaN
5  we run and walk   20.0
6  we run and walk    NaN
7  we run and walk   30.0

现在我们可以将groupbysum 结合起来,按每个句子的分数求和

请注意,pandas 会将NaN 视为我们不想要的0.0,因此我们使用fillna 将na 填充到字符串“NaN”中。

sentence_score_sum = sentence_score.fillna('NaN').groupby('Sentence').sum()

给你

                 score
Sentence
I run             30.0
he walks          40.0
we run and walk    NaN

你的问题说你想给句子最高的单词分数,但你的预期输出显示总和,如果你需要最高分数,那么这很简单

sentence_score_max = sentence_score.fillna('NaN').groupby('Sentence').max()

给你

                 score
Sentence
I run             20.0
he walks          30.0
we run and walk    NaN

注意:此解决方案依赖于具有 UNIQUE 句子,如果您有重复的句子,您可以在开始之前 drop_duplicates 或者您可以在合并之前应用 reset_index(drop=False) 以保留索引,然后 @旧索引上的 987654347@ 而不是 Sentence

【讨论】:

  • 您是在谈论过滤数据框,然后在底部附加列,以便我基本上得到 1 个包含 2 列的巨大列表,就像在 @kentaro 的示例中一样?这可能确实是一个好方法。一些如何进行拆分和追加的示例代码会很好:)
  • 是的,看看melt 方法可能会有所帮助。如果你想要一个例子,我可能会想出一些东西
  • @theotherguy 我已经用代码更新了我的答案,并详细说明了它的工作原理。请注意,这会在缺少单词时为您提供NaN,并且不会告诉您缺少哪些单词,但是您可以在合并后立即从中间步骤中获取这些缺失的单词
  • 哇,非常感谢您的分析!这正是我一直在寻找的。好先生,我向你脱帽致敬
【解决方案2】:

我会使用以下正则表达式方法:

# Store scores using index
score_dict = {x:0 for x in df_sentences.index}

# Loop through each row in the score df (df_dictionary):
for row in df_dictionary.values:
  # Access the score
  score = row[0]
  # Access the words & convert to a pattern
  words = "|".join([re.escape(x) for x in row[1:]])
  pattern = re.compile(r"\b(" + words + r")\b", re.I|re.M)
  
  # Loop through each row in the main df (df_sentences):
  for idx, row in df_sentences.iterrows():
    # Find the number of matches in the sentence
    matches = pattern.findall(row["Sentence"])
    # Multiply to get the score
    n_score = len(matches) * score
    # Store it using the index as key
    score_dict[idx] += n_score

# Now, add the dict as a column (or map it back to the main df) 
df_sentences["score"] = df_sentences.index.map(score_dict)

    Sentence    score
0   I run   30
1   he walks    40
2   we run and walk 50

【讨论】:

  • 感谢您的帮助,但我的数据量很大,而且使用 for 循环需要很长时间才能遍历所有迭代。
  • 您可以尝试转换为转换为函数并将df_dictionary 映射到函数同时应用到df_sentences。坦率地说,您要做的是计算密集型问题。
  • 除非您不关心多个单词,在这种情况下,您可以使用df_sentences.Sentences.str.replace(word, n) 替换wordn 的单词和分数......
【解决方案3】:

你关心重复吗?如果我有一个像“I I I”这样的字符串,从技术上讲是 30 分。

另外,您使用数据框存储评分词是否有特殊原因?

使用集合交集来快速删除重复项:

dictionary ={
"I": 10, "you": 10, "he": 10, 
"running": 20, "runs": 20, "run": 20, 
"walking": 30, "walk": 30, "walks": 30
}

df = pd.DataFrame({
    "Sentences":[
        "I run and he walks",
        "We walk and he runs",
        "I Run you run he runs",
        "I run he runs",
        "I I I I I"
    ]})

def split_score(sentence):
    x = sentence.split(' ')
    x = set(x) # remove duplicate words
    y = x.intersection(set(dictionary.keys())) # find matches in the dictionary
    z = x.difference(set(dictionary.keys())) # find words outside the dictionary
    if len(z) > 0:
        score = -1 # If non-dictionary words are found, fail
    elif len(z) == 0:
        score = sum([dictionary[word] for word in y])
    return score

df['Points'] = df['Sentences'].apply(lambda x: split_score(x))
df

【讨论】:

  • 另外,如果字典中没有单词,您是否希望它失败?
  • 之所以使用dataframes,是因为字典是一个巨大的csv文件。但是你这样做的方式和@Jimmar 一样也指出,将列与一个列/字典/列表中的变体结合起来可能是一个好方法。现在我只需要找出如何完成它:-D 是的,如果在字典中找不到单词,我希望它失败。然后应将其添加到“未找到”列表中
  • 请注意apply 有点慢,考虑使用 pandarallel 并行执行并加快速度。
  • 在这个解决方案中“帮助”会触发“他”吗?
  • 这不是使用正则表达式,而是将整个字符串相互比较。不过,一个混淆因素是标点符号。如果有任何逗号或句点,这将失败。我更新了我的答案以包括失败案例。此外,我将失败案例设置为 -1 以确保您不会将整个列转换为 pandas 对象或字符串。这将使以后处理结果更容易。
猜你喜欢
  • 2022-01-05
  • 1970-01-01
  • 2020-08-14
  • 2019-06-21
  • 2021-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多