Каков наилучший способ найти все вхождения значений из одного кадра данных в другой? - PullRequest
3 голосов
/ 05 ноября 2019

Я работаю над искровым кластером, и у меня есть два кадра данных. Один содержит текст. Другой - справочная таблица. Обе таблицы огромны (M и N могут легко превышать 100 000 записей). Каков наилучший способ их сопоставления?

Выполнение перекрестного соединения, а затем фильтрация результатов на основе совпадений кажется безумной идеей, поскольку у меня наверняка не хватило бы памяти.

Мои фреймы данных выглядят примерно так:

df1:

        text
0       i like apples
1       oranges are good
2       eating bananas is healthy
.       ...
.       ...
M       tomatoes are red, bananas are yellow

df2:

        fruit_lookup
0       apples
1       oranges
2       bananas
.       ...
.       ...
N       tomatoes

Я ожидаю, что выходной фрейм данных будет выглядеть примерно так:

output_df:

        text                                     extracted_fruits
0       i like apples                            ['apples']
1       oranges are good                         ['oranges']
2       eating bananas is healthy                ['bananas']
.       ...
.       ...
M       tomatoes are red, bananas are yellow .   ['tomatoes','bananas']

1 Ответ

2 голосов
/ 05 ноября 2019

Один из способов - использовать CountVectorizerModel , так как слова поиска 100К должны быть управляемыми для этой модели (по умолчанию vocabSize = 262144 ):

Основная идея заключается в создании CountVectorizerModel на основе настроенного списка из df2 (справочная таблица). разбить df1.text на столбец массива, а затем преобразовать этот столбец в SparseVector, который затем можно отобразить в слова:

Редактировать: в функция split , отрегулироватьрегулярное выражение от \s+ до [\s\p{Punct}]+, чтобы убрать все знаки препинания. измените 'text' на lower(col('text')), если поиск нечувствителен к регистру.

from pyspark.ml.feature import CountVectorizerModel
from pyspark.sql.functions import split, udf, regexp_replace, lower

df2.show()                                                                                                   
+---+------------+
| id|fruit_lookup|
+---+------------+
|  0|      apples|
|  1|     oranges|
|  2|     bananas|
|  3|    tomatoes|
|  4|dragon fruit|
+---+------------+

Edit-2: Добавлен следующий шаг предварительной обработки df1 и создайте столбец массива, включающий все N-граммы комбинации. Для каждой строки с L словами, N = 2 добавит (L-1) больше элементов в массиве, если N = 3, (L-1)+(L-2) больше элементов.

# max number of words in a single entry of the lookup table df2
N = 2

# Pre-process the `text` field up to N-grams, 
# example: ngram_str('oranges are good', 3) 
#      --> ['oranges', 'are', 'good', 'oranges are', 'are good', 'oranges are good']
def ngram_str(s_t_r, N):
  arr = s_t_r.split()           
  L = len(arr)           
  for i in range(2,N+1):           
    if L - i < 0: break           
    arr += [ ' '.join(arr[j:j+i]) for j in range(L-i+1) ]           
  return arr           

udf_ngram_str = udf(lambda x: ngram_str(x, N), 'array<string>')

df1_processed = df1.withColumn('words_arr', udf_ngram_str(lower(regexp_replace('text', r'[\s\p{Punct}]+', ' '))))

Реализация модели на обработанном df1:

lst = [ r.fruit_lookup for r in df2.collect() ]

model = CountVectorizerModel.from_vocabulary(lst, inputCol='words_arr', outputCol='fruits_vec')

df3 = model.transform(df1_processed)
df3.show(20,40)
#+----------------------------------------+----------------------------------------+-------------------+
#|                                    text|                               words_arr|         fruits_vec|
#+----------------------------------------+----------------------------------------+-------------------+
#|                           I like apples|  [i, like, apples, i like, like apples]|      (5,[0],[1.0])|
#|                        oranges are good|[oranges, are, good, oranges are, are...|      (5,[1],[1.0])|
#|               eating bananas is healthy|[eating, bananas, is, healthy, eating...|      (5,[2],[1.0])|
#|    tomatoes are red, bananas are yellow|[tomatoes, are, red, bananas, are, ye...|(5,[2,3],[1.0,1.0])|
#|                                    test|                                  [test]|          (5,[],[])|
#|I have dragon fruit and apples in my bag|[i, have, dragon, fruit, and, apples,...|(5,[0,4],[1.0,1.0])|
#+----------------------------------------+----------------------------------------+-------------------+

Затем вы можете сопоставить fruits_vec с фруктами, используя model.vocabulary

vocabulary = model.vocabulary
#['apples', 'oranges', 'bananas', 'tomatoes', 'dragon fruit']

to_match = udf(lambda v: [ vocabulary[i] for i in v.indices ], 'array<string>')

df_new = df3.withColumn('extracted_fruits', to_match('fruits_vec')).drop('words_arr', 'fruits_vec')
df_new.show(truncate=False)                                      
#+----------------------------------------+----------------------+
#|text                                    |extracted_fruits      |
#+----------------------------------------+----------------------+
#|I like apples                           |[apples]              |
#|oranges are good                        |[oranges]             |
#|eating bananas is healthy               |[bananas]             |
#|tomatoes are red, bananas are yellow    |[bananas, tomatoes]   |
#|test                                    |[]                    |
#|I have dragon fruit and apples in my bag|[apples, dragon fruit]|
#+----------------------------------------+----------------------+

Метод-2: Поскольку ваш набор данных не очень большой с точки зренияконтекста Spark, может работать следующее, это будет работать со значением поиска, имеющим несколько слов в соответствии с вашим комментарием:

from pyspark.sql.functions import expr, collect_set

df1.alias('d1').join(
      df2.alias('d2')
    , expr('d1.text rlike concat("\\\\b", d2.fruit_lookup, "\\\\b")')
    , 'left'
).groupby('text') \
 .agg(collect_set('fruit_lookup').alias('extracted_fruits')) \
 .show()
+--------------------+-------------------+                                      
|                text|   extracted_fruits|
+--------------------+-------------------+
|    oranges are good|          [oranges]|
|       I like apples|           [apples]|
|tomatoes are red,...|[tomatoes, bananas]|
|eating bananas is...|          [bananas]|
|                test|                 []|
+--------------------+-------------------+

Где: "\\\\b - граница слова, чтобы значения поиска не перепутались сих контексты.

Примечание: вам может потребоваться очистить все знаки препинания и лишние пробелы в обоих столбцах, прежде чем присоединится кадр данных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...