Правильная условная цепочка в Polars

У меня есть фрагмент кода, который условно применяет дополнительные манипуляции к столбцу ('NaicsDescription'). Я не уверен, что это лучший/самый чистый способ сделать это в Polars. Хотя кажется, что это работает, и я был бы признателен за некоторые рекомендации.

Идея заключается в том, чтобы...

  1. применить операцию нижнего регистра к столбцу,
  2. условно применить некоторые дополнительные операции,
  3. и, наконец, закончим некоторыми дополнительными общими операциями.

Это верно?

import polars as pl
from unicodedata import normalize 
from lingua import Language, LanguageDetectorBuilder
from nltk.corpus import stopwords

languages = [Language.ENGLISH, Language.FRENCH]
detector = LanguageDetectorBuilder.from_languages(*languages).build()

test = pl.DataFrame(
    {
        "id": [1, 2, 3, 4],
        "NaicsDescription": ['Full service restuarants', 'The Manufacturing of toys and trains', 'POWER GENERATING STATIONS', 'the short term rental of cottages']
    }
)


def lang_identifier(text: str):
    '''
    Language identification of text. This is just a simple wrapper around lingua function.

    Parameters
    ----------
    text : str
        Input string to identify language for.

    Returns
    -------
    language : str
        Returns either 'EN' or 'FR' or None if input text is not of type string.

    '''
    language = None
    if isinstance(text, str):
        language = detector.detect_language_of(text)
        if language:
            language = language.iso_code_639_1.name
    return language


en_stopwords = '|'.join(set(w.lower() for w in stopwords.words('english')))
fr_stopwords = '|'.join(set(w.lower() for w in stopwords.words('french')))
bilingual_stopwords = '|'.join(set(w.lower() for w in stopwords.words('english') + stopwords.words('french')))

test = test.with_columns(
pl.col('NaicsDescription')
    .str.to_lowercase().alias('NaicsDescription_')
    ).with_columns(
        pl.when(pl.col('NaicsDescription').map_elements(lang_identifier, return_dtype=pl.String) == 'FR')
        .then(
            pl.col('NaicsDescription_').str.replace_all(r'\b(?:' + fr_stopwords + r')\b', ' ')
            )
        .when(pl.col('NaicsDescription').map_elements(lang_identifier, return_dtype=pl.String) == 'EN')
        .then(
            pl.col('NaicsDescription_').str.replace_all(r'\b(?:' + en_stopwords + r')\b', ' ')
            )
        .otherwise(
            pl.col('NaicsDescription_').str.replace_all(r'\b(?:' + bilingual_stopwords + r')\b', ' ')
            )
        ).with_columns(
            pl.col('NaicsDescription_').map_elements(lambda x: normalize('NFKD',x)
                        .encode('ascii', errors='ignore')
                        .decode('utf-8'), return_dtype=pl.String)
            .str.replace_all(r'(?:[^\s\w]|_\d)+', ' ')
            .str.replace_all(r'\b(?:\d+|\w{1,2})\b', ' ')
            .str.replace_all(r'\s\s+', ' ')
            .str.strip_chars()
            .replace('', None)
    )

изменить: добавлен полный рабочий пример...

Кажется вероятным, что различные экземпляры pl.Expr.map_elements могут быть записаны в родной полярной форме. Если бы вы предоставили более полный работоспособный пример вместе с ожидаемым результатом, было бы легче ответить.

Hericks 04.06.2024 16:19
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
1
123
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Трудно сказать, не видя никаких данных, есть ли лучший способ подойти к тому, что вы делаете, или можно ли оптимизировать какие-либо шаги. В будущем работающий пример значительно облегчит помощь.

Кажется, что каждый шаг зависит от предыдущего. Обычно избегайте map_elements, если только ваша логика не может быть выражена в полярных выражениях. В этом случае кажется, что вашу функцию lang_identifier вряд ли удастся выразить как полярное выражение, но в этом невозможно быть уверенным. Ваша нормализация Юникода кажется лучшим подходом и соответствует этому ТАК-ответу.

Я думаю, что более всего этот код можно реорганизовать, чтобы цель стала более ясной, а повторяющиеся операции, такие как определение языка и удаление стоп-слов, были вынесены в функцию или переменную.

Вот моя попытка сделать это. Ничего не должно измениться, просто рефакторинг. Вероятно, он не идеален, поскольку я не смог его запустить.

def remove_stopwords(text: pl.Expr, stopwords: str) -> pl.Expr:
    """Removes supplied stopwords from a string."""
    return text.str.replace_all(r"\b(?:" + stopwords + r")\b", " ")


naics_description = pl.col("NaicsDescription")
naics_description_lower = pl.col("NaicsDescription").str.to_lowercase()

identified_language = naics_description.map_elements(
    lang_identifier,
    return_dtype=pl.String,
)

naics_description_stopwords_removed = (
    pl.when(identified_language == "FR")
    .then(remove_stopwords(naics_description_lower, fr_stopwords))
    .when(identified_language == "EN")
    .then(remove_stopwords(naics_description_lower, en_stopwords))
    .otherwise(remove_stopwords(naics_description_lower, bilingual_stopwords))
)

test = test.with_columns(
    naics_description_stopwords_removed.map_elements(
        lambda x: normalize("NFKD", x).encode("ascii", errors = "ignore").decode("utf-8"),
        return_dtype=pl.String,
    )
    .str.replace_all(r"(?:[^\s\w]|_\d)+", " ")
    .str.replace_all(r"\b(?:\d+|\w{1,2})\b", " ")
    .str.replace_all(r"\s\s+", " ")
    .str.strip_chars()
    .replace("", None)
)

Одна вещь, которую я подвергаю сомнению и с которой борюсь, — это производительность сопоставления регулярных выражений в строках str.replace_all, а теперь и в функции remove_stopwords. В случае с Pandas я бы заранее скомпилировал регулярное выражение только один раз и передал его в функцию Pandas str.replace. Не уверен, стоит ли мне беспокоиться по этому поводу, поскольку я не до конца осознаю, как Polars могут справиться с этой производительностью под капотом.

Jeff M 05.06.2024 14:21

использование этого метода работает хорошо, и его легче читать. Я протестировал 100 тысяч записей моего фрейма данных из почти 7 миллионов. Производительность приемлемая, однако когда я добавляю дополнительные Map_elements к функции, которая выполняет лемматизацию (с использованием пространственных моделей), производительность резко падает примерно до 7 минут для ввода 100 КБ. Насколько я понимаю, лемматизация в целом обходится дорого и, возможно, мне придется жить без нее... но хотелось бы обойти Map_elements, если смогу.

Jeff M 05.06.2024 15:40

Что касается производительности замены регулярных выражений, насколько я понимаю, Polars делает это в Rust, поэтому это будет весьма производительно. Если ваши замены регулярных выражений не зависят от предыдущего, вы можете использовать .str.replace_many, чтобы выполнить их все одновременно, что, как я полагаю, будет способствовать дальнейшему ускорению. Основным ударом по производительности будет любое использование map_elements. Это возвращается к Python, и вы теряете распараллеливание. Это на порядки медленнее, чем все, что вы делаете на родных поляках. Делайте как можно больше с помощью полярных выражений, однако я понимаю, что для НЛП это может быть невозможно.

Henry Harbeck 09.06.2024 10:45

Возможно, стоит посмотреть, поддерживают ли плагины Polars то, что вы пытаетесь сделать. Оставаясь в Polars (или, по крайней мере, в numpy или в других форматах, совместимых со стрелками, таких как DuckDB), ваш код не будет зацикливаться элемент за элементом (что на самом деле и делает map_elements). Это расширение, кажется, реализует некоторые вещи НЛП.

Henry Harbeck 09.06.2024 10:50

Другие вопросы по теме