Проверка наличия набора строк в столбце с пользовательской функцией

Товарищи,

Я хотел бы проверить, существует ли набор определенных ключевых слов в сгруппированном кадре данных pandas. Слова, которые я хотел бы проверить, это start, pending и либо finished, либо almost_finished. Я хотел бы определить пользовательскую функцию для этого и apply ее для панд groupby, поскольку определение функции для применения к столбцам немного непонятно для меня по сравнению с операциями по строкам, когда мы обращаемся к каждой строке с помощью (row[colname]). В этом примере, если существует последовательность искомых слов, я бы хотел, чтобы последнее значение в столбце number для каждого ID было скопировано в новый столбец, и не имеет значения, являются ли другие значения до этого пустыми строками. Вот воспроизводимый пример:

import pandas as pd

df = pd.DataFrame({'ID' : [1100, 1100, 1100, 1200, 1200, 1200, 1300, 1300],
                  'number' : ['Yes', 'No', 'No', 'Yes', 'No', 'No', 'Yes', 'No'],
                  'status' : ['start', 'pending', 'finished', 'start', 'pending', 'partially_finished', 'start', 'pending']})

В этом случае последняя группа ID == 1300 не имеет возвращаемого значения. По сути, я задаю этот вопрос, чтобы узнать, как лучше всего решать такие проблемы, когда вам нужно проверить некоторые значения в столбце, поскольку я пришел из R, мне нужно ознакомиться с тем, как я сделал бы то же самое в Python. Я также был бы признателен за любое лучшее решение, которое вы можете предложить. Заранее большое спасибо.

Формы c голосовым вводом в React с помощью Speechly
Формы c голосовым вводом в React с помощью Speechly
Пытались ли вы когда-нибудь заполнить веб-форму в области электронной коммерции, которая требует много кликов и выбора? Вас попросят заполнить дату,...
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Будучи разработчиком веб-приложений, легко впасть в заблуждение, считая, что приложение без JavaScript не имеет права на жизнь. Нам становится удобно...
Flatpickr: простой модуль календаря для вашего приложения на React
Flatpickr: простой модуль календаря для вашего приложения на React
Если вы ищете пакет для быстрой интеграции календаря с выбором даты в ваше приложения, то библиотека Flatpickr отлично справится с этой задачей....
В чем разница между Promise и Observable?
В чем разница между Promise и Observable?
Разберитесь в этом вопросе, и вы значительно повысите уровень своей компетенции.
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Клиент для URL-адресов, cURL, позволяет взаимодействовать с множеством различных серверов по множеству различных протоколов с синтаксисом URL.
Четыре эффективных способа центрирования блочных элементов в CSS
Четыре эффективных способа центрирования блочных элементов в CSS
У каждого из нас бывали случаи, когда нам нужно отцентрировать блочный элемент, но мы не знаем, как это сделать. Даже если мы реализуем какой-то...
0
0
51
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

например следующая функция

def return_last_num(df):
    if df.status.str.contains('start').any() & df.status.str.contains('pending').any() \
        & (df.status.str.contains('finished').any() | df.status.str.contains('partically_finished').any()):
        df['last_number'] = df.number.values[-1]
    else:
        df['last_number'] = str()
    return df

При нанесении: df.groupby('ID').apply(return_last_num).

Возвращает:

     ID number              status last_number
0  1100    Yes               start          No
1  1100     No             pending          No
2  1100     No            finished          No
3  1200    Yes               start          No
4  1200     No             pending          No
5  1200     No  partially_finished          No
6  1300    Yes               start            
7  1300     No             pending            

В качестве альтернативы: возврат одного значения

def return_last_num(df):
    if df.status.str.contains('start').any() & df.status.str.contains('pending').any() \
        & (df.status.str.contains('finished').any() | df.status.str.contains('partically_finished').any()):
        return df.number.values[-1]
    else:
        return str()

При нанесении: df.groupby('ID').apply(return_last_num).

Возвращает только последнее числовое значение для каждого идентификатора, если условие последовательности было соблюдено:

ID
1100    No
1200    No
1300           

Большое спасибо за ваше решение, я сначала подумал об этом, и было интересно узнать, что без groupby я не мог применить пользовательскую функцию к столбцам фрейма данных pandas.

Anoushiravan R 17.05.2022 22:56

@AnoushiravanR, вы абсолютно точно можете проверить DataFrame.apply/applymap/transform.

quizzical_panini 17.05.2022 23:16

Я думал о векторном подходе.

Во-первых, если finished и almost finished имеют одинаковый эффект, я бы «объединил» их и сделал бы их уникальным числом, которое легко проверить:

>>> df['status2'] = df['status'].map({'finished':1,'partially_finished':1,'pending':10,'start':100})
>>> df
     ID number              status  status2
0  1100    Yes               start      100
1  1100     No             pending       10
2  1100     No            finished        1
3  1200    Yes               start      100
4  1200     No             pending       10
5  1200     No  partially_finished        1
6  1300    Yes               start      100
7  1300     No             pending       10

Это позволяет мне «извлекать» желаемый статус (100+10+1):

idstatus=df.groupby('ID', sort=False).sum('status2')==111
      status2
ID
1100     True
1200     True
1300    False

И фактические числовые значения: если вам не нужно сопоставлять слова все, достаточно будет только этой строки.

valuenumber=df.query('status2==1').set_index('ID')
     number              status  status2
ID
1100     No            finished        1
1200     No  partially_finished        1

И, наконец, объединить:

idstatus.merge(valuenumber, left_index=True, right_index=True, how='left')
>>> idstatus.merge(valuenumber, left_index=True, right_index=True, how='left')
      status2_x number              status  status2_y
ID
1100       True     No            finished        1.0
1200       True     No  partially_finished        1.0
1300      False    NaN                 NaN        NaN

можно объединить меньше записей:

>>> idstatus.merge(valuenumber[['number','status']], left_index=True, right_index=True, how='left')
      status2 number              status
ID
1100     True     No            finished
1200     True     No  partially_finished
1300    False    NaN                 NaN
>>>

Редактировать: Если вы хотите получить только готовые результаты, вы можете получить их также с помощью этого однострочника:

>>> valuenumber.loc[idstatus[idstatus.status2].index][['number','status']]
     number              status
ID
1100     No            finished
1200     No  partially_finished
>>>

Edit2: только что сравнил Интересно, что решением пересечения является Быстрее, по крайней мере, для выборочных данных:

  • групповое применение: 2,86 мс
  • объединение и слияние: 3,90 мс
  • вектор и местоположение: 3,96 мс
  • set-intersect: 2,4 мс, хотя и с использованием лямбда

Редактировать3: Вчера я был слишком тугой. Используя идею Эммы о «двух операциях в одной группе» и предполагая, что элементы упорядочены по состоянию:

df2['status'] = df2.status.map({'finished':1,'partially_finished':1,'pending':10,'start':100})
x = df2.groupby('ID').agg({'number': 'last', 'status': 'sum'})
x[x.status==111]

При 2,1 мс это немного быстрее, чем версии с apply/lambda

Большое спасибо за ваше решение, тот факт, что вы сначала сопоставили значения с категориями, а затем использовали сумму, действительно умная идея. Можно ли фильтровать группы панд без потери какого-либо столбца? Потому что остаются только сгруппированная переменная и агрегированные.

Anoushiravan R 17.05.2022 22:58

В целом это зависит от проводимой операции. «Agg» предлагает максимальную гибкость, позволяя выполнять агрегирование с различными функциями для разных столбцов. Существуют и другие функции, такие как «сумма», которые применяются ко всем столбцам, например, как groupby.sum.

Zaero Divide 17.05.2022 23:24

Да, вы правы, как-то не подумал об этом. Я должен объединить и другие. Но да, я работал с agg, и это выглядит интересно, особенно когда вы передаете словарь имени столбца с именем функции.

Anoushiravan R 17.05.2022 23:41
Ответ принят как подходящий

Вы можете агрегировать с set и использовать intersection для проверки.

Но сначала я бы сопоставил partially_finished или almost_finished с finished, если к ним нужно относиться одинаково.

df['status'] = df.status.replace('partially_finished|almost_finished', 'finished', regex=True)

Затем объединяю number с последним значением и status с set, затем я использую intersect, чтобы проверить, все ли значения существуют в status.

checkcriteria = {'start', 'pending', 'finished'}
df = df.groupby('ID').agg({'number': 'last', 'status': set})
df['check'] = df.status.transform(lambda x: len(x.intersection(checkcriteria)) == 3)

Это должно дать результат,

     number                      status  check
ID
1100     No  {start, pending, finished}   True
1200     No  {start, pending, finished}   True
1300     No            {start, pending}  False

Вы можете отфильтровать по check или mask и удалить значение для number.

# This will only return ID == 1100, 1200
df[df.check]

# OR mask to remove the number value for when check == False
df.loc[~df.check, 'number'] = None

Довольно интересно, что преобразование + лямбда на самом деле быстрее, чем другие варианты.

Zaero Divide 17.05.2022 21:37

не уверен, но, может быть, потому, что transform применяется к уже агрегированному кадру данных (~ 1/3 от исходного размера)?

Emma 17.05.2022 21:42

Действительно, это оправдало бы ускорение по сравнению с groupby-apply.

Zaero Divide 17.05.2022 22:25

Большое спасибо, Эмма, за ваше решение, оно действительно выглядит интересно.

Anoushiravan R 17.05.2022 22:55

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